OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-note commits / 6c0264f

Add hyper-identity integration plan doc
463-line plan document for migrating hypernote's custom identity / auth implementation onto the shared hyper-identity library (extracted from pear-git's auth stack, generic permission model using 'resources' instead of 'repos'). Architecture rationale + task-by-task migration steps. Preserves hypernote-specific behavior (notebook permissions, mirror sync, auto-admin bootstrap) while replacing the parallel auth stack (IdentityRegistry, ChallengeManager, RateLimiter, 3 providers) with hyper-identity imports. Was sitting uncommitted on disk since 2026-03-10. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6c0264f903af147afa31086a9ccab37a84eaca87
by plankatron <fleeky@gmail.com> · 2026-04-15T21:30:29.000Z

Changed files (1) +463 -0

docs/hyper-identity-integration-plan.md +463 -0
……@@ -0,0 +1,463 @@
1# Hypernote → hyper-identity Integration Plan
2
3> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
5**Goal:** Replace hypernote's custom identity/auth implementation with `hyper-identity` library.
6
7**Architecture:** Hypernote currently has its own parallel auth system (IdentityRegistry, ChallengeManager, RateLimiter, 3 providers) that evolved independently from pear-git's. The `hyper-identity` library extracts pear-git's battle-tested implementation with a generic permission model (`resources` instead of `repos`). This migration replaces hypernote's auth internals with hyper-identity imports while preserving hypernote-specific behavior (notebook permissions, mirror sync, auto-admin bootstrap).
8
9**Tech Stack:** hyper-identity, autobase, hyperbee, hypercore-crypto, b4a
10
11---
12
13## Current State
14
15Hypernote auth files (all under `lib/auth/`):
16- `identity-registry.js` — Hyperbee-backed user/identity CRUD (NOT Autobase)
17- `challenge-manager.js` — Challenge generation/verification
18- `rate-limiter.js` — Token bucket rate limiting
19- `base-provider.js` — Abstract provider
20- `providers/web-identity.js` — ANProto browser auth
21- `providers/keet-identity.js` — BIP39 mnemonic auth
22- `providers/bearer-token.js` — API token auth
23
24Main orchestrator: `lib/auth.js` (AuthManager)
25
26**Key difference:** Hypernote's IdentityRegistry uses plain Hyperbee (not Autobase). hyper-identity uses Autobase-backed registry. The migration adds multi-writer consensus for free.
27
28---
29
30## Task 1: Add hyper-identity dependency
31
32**Files:**
33- Modify: `package.json`
34
35**Step 1:** Add hyper-identity as a local dependency:
36```json
37{
38 "dependencies": {
39 "hyper-identity": "file:../packages/hyper-identity"
40 }
41}
42```
43
44**Step 2:** Install:
45```bash
46cd hyper-fleeky/hypernote && npm install
47```
48
49**Step 3:** Verify import works:
50```bash
51node -e "import('hyper-identity').then(m => console.log(Object.keys(m)))"
52```
53
54**Step 4: Commit**
55
56---
57
58## Task 2: Create hypernote-specific permission adapter
59
60**Files:**
61- Create: `lib/auth/notebook-permissions.js`
62
63Hypernote uses `notebooks` array on users (not generic `resources`). Create an adapter that maps between hyper-identity's `resources` model and hypernote's `notebooks` model.
64
65**Step 1:** Write the adapter:
66```javascript
67/**
68 * Notebook permission adapter for hyper-identity
69 *
70 * Maps hyper-identity's generic 'resources' to hypernote's 'notebooks'.
71 * Injected as checkPermission callback into AuthManager.
72 */
73
74/**
75 * Check if auth has permission for action on notebook
76 * Injectable into hyper-identity's AuthManager as checkPermission callback.
77 *
78 * @param {Object} auth - Auth result from authenticate()
79 * @param {string} action - 'read', 'write', or 'admin'
80 * @param {string} notebookName - Notebook slug
81 * @returns {boolean}
82 */
83export function checkNotebookPermission (auth, action, notebookName) {
84 if (!auth || !auth.verified) return false
85 const perms = auth.permissions
86 if (perms.admin) return true
87 if (!perms[action]) return false
88 if (notebookName) {
89 return perms.resources.includes('*') || perms.resources.includes(notebookName)
90 }
91 return true
92}
93
94/**
95 * Convert hypernote user's notebooks array to hyper-identity resources format
96 * Used when creating/updating users in the registry.
97 *
98 * @param {Array} notebooks - [{ name: 'slug', permissions: ['read', 'write'] }]
99 * @returns {Array} resources - Same format (notebooks IS resources in this context)
100 */
101export function notebooksToResources (notebooks) {
102 return notebooks // Same shape — just a semantic rename
103}
104
105/**
106 * Convert hyper-identity resources back to hypernote notebooks
107 * Used when reading users from the registry.
108 */
109export function resourcesToNotebooks (resources) {
110 return resources // Same shape
111}
112```
113
114**Step 2: Commit**
115
116---
117
118## Task 3: Replace AuthManager with hyper-identity's AuthManager
119
120**Files:**
121- Modify: `lib/auth.js`
122
123This is the main change. Replace hypernote's custom AuthManager with hyper-identity's, adding the notebook permission adapter.
124
125**Step 1:** Rewrite `lib/auth.js`:
126
127```javascript
128import { AuthManager as HyperAuthManager, Permissions, sendAuthError } from 'hyper-identity'
129import { checkNotebookPermission } from './auth/notebook-permissions.js'
130
131/**
132 * HypernoteAuthManager — Extends hyper-identity's AuthManager
133 * with notebook-specific behavior and mirror sync support.
134 */
135export class AuthManager extends HyperAuthManager {
136 constructor (options = {}) {
137 super({
138 ...options,
139 checkPermission: checkNotebookPermission
140 })
141
142 // Shared registry reference for mirror auth fallback
143 this.sharedRegistry = null
144 }
145
146 /**
147 * Override authenticate to add mirror fallback behavior
148 */
149 async authenticate (req, options = {}) {
150 // First try standard hyper-identity auth
151 const auth = await super.authenticate(req, options)
152
153 if (auth) return auth
154
155 // Mirror fallback: if no local user found, check shared registry
156 if (this.sharedRegistry) {
157 return this._mirrorFallbackAuth(req, options)
158 }
159
160 return null
161 }
162
163 /**
164 * Mirror fallback — check shared registry for synced users
165 * Only used when local auth fails and a shared registry is configured.
166 */
167 async _mirrorFallbackAuth (req, options = {}) {
168 // Extract public key from request
169 const publicKey = req.headers['x-wirecore-publickey'] ||
170 req.headers['x-identity-publickey']
171 if (!publicKey) return null
172
173 try {
174 // Check if pubkey already maps to a valid local user
175 const existingIdentity = await this.identityRegistry.getIdentityByPublicKey(publicKey)
176 if (existingIdentity && existingIdentity.userId) {
177 const existingUser = await this.identityRegistry.getUser(existingIdentity.userId)
178 if (existingUser) {
179 return {
180 userId: existingUser.userId,
181 identityId: existingIdentity.identityId || publicKey,
182 method: 'web-identity',
183 permissions: this.formatUserPermissions(existingUser, null),
184 user: existingUser,
185 verified: true
186 }
187 }
188 }
189
190 // Look up in shared registry
191 const sharedUser = await this.sharedRegistry.getSharedUserByPublicKey(publicKey)
192 if (!sharedUser) return null
193
194 // Create local user from shared data
195 const userObj = {
196 userId: sharedUser.userId,
197 username: sharedUser.username || null,
198 name: sharedUser.name || 'Anonymous',
199 email: null,
200 profilePicture: null,
201 globalPermissions: sharedUser.globalPermissions || ['read'],
202 resources: sharedUser.resources || sharedUser.notebooks ||
203 [{ name: '*', permissions: ['read'] }],
204 identities: [publicKey],
205 createdAt: Date.now(),
206 updatedAt: Date.now()
207 }
208
209 // Store in identity registry
210 if (this.identityRegistry.db) {
211 await this.identityRegistry.db.put(`user:${sharedUser.userId}`, userObj)
212 }
213
214 // Link identity
215 const identity = await this.identityRegistry.getIdentity(publicKey)
216 if (identity) {
217 identity.userId = sharedUser.userId
218 if (this.identityRegistry.db) {
219 await this.identityRegistry.db.put(`identity:${publicKey}`, identity)
220 }
221 }
222
223 return {
224 userId: sharedUser.userId,
225 identityId: publicKey,
226 method: 'web-identity',
227 permissions: this.formatUserPermissions(userObj, null),
228 user: userObj,
229 verified: true
230 }
231 } catch {
232 return null
233 }
234 }
235
236 // Backward compat: hasNotebookAccess wraps hasResourceAccess
237 hasNotebookAccess (auth, notebookName, action = 'read') {
238 return this.hasResourceAccess(auth, notebookName, action)
239 }
240}
241
242export { Permissions, sendAuthError }
243```
244
245**Step 2:** Update all callers of `hasNotebookAccess` — should still work via the backward compat wrapper.
246
247**Step 3: Commit**
248
249---
250
251## Task 4: Replace IdentityRegistry with hyper-identity's
252
253**Files:**
254- Modify: `lib/auth.js` (constructor)
255- Delete: `lib/auth/identity-registry.js` (replaced by hyper-identity)
256
257**Step 1:** In AuthManager constructor, use hyper-identity's IdentityRegistry:
258
259```javascript
260import { IdentityRegistry } from 'hyper-identity/auth/identity-registry'
261
262// In constructor:
263this.identityRegistry = new IdentityRegistry(store, {
264 db: options.registry, // hypernote's existing Hyperbee
265 prefix: ''
266})
267await this.identityRegistry.ready()
268```
269
270**Step 2:** Data migration — existing hypernote identity data uses the same key schema (`user:`, `identity:`, `pubkey:`), so no migration needed for direct-write data. The Autobase view is new — it will build from ops going forward.
271
272**Step 3:** Verify existing users/identities still resolve correctly.
273
274**Step 4: Commit**
275
276---
277
278## Task 5: Replace auth providers with hyper-identity's
279
280**Files:**
281- Delete: `lib/auth/providers/web-identity.js`
282- Delete: `lib/auth/providers/keet-identity.js`
283- Delete: `lib/auth/providers/bearer-token.js`
284- Delete: `lib/auth/base-provider.js`
285- Modify: `lib/auth.js` (imports)
286
287**Step 1:** Remove local provider files.
288
289**Step 2:** Update imports in auth.js to use hyper-identity providers (already done in Task 3 since the new AuthManager auto-registers them).
290
291**Step 3:** Verify all three auth methods work:
292- Web identity: Browser sends `X-Wirecore-PublicKey` header → auth succeeds
293- Bearer token: `Authorization: Bearer <token>` → auth succeeds
294- Keet identity: `X-Keet-PublicKey` header → auth succeeds
295
296**Step 4: Commit**
297
298---
299
300## Task 6: Replace ChallengeManager and RateLimiter
301
302**Files:**
303- Delete: `lib/auth/challenge-manager.js`
304- Delete: `lib/auth/rate-limiter.js`
305- Modify: `lib/auth.js` (already uses hyper-identity's versions)
306
307**Step 1:** Remove local files.
308
309**Step 2:** Verify challenge-response flow:
310```
311GET /api/auth/challenge → { challengeId, challenge }
312# Client signs challenge with ANProto
313# Client sends signed request with X-Wirecore-Challenge header
314```
315
316**Step 3:** Verify rate limiting still works (check with rapid requests).
317
318**Step 4: Commit**
319
320---
321
322## Task 7: Update hypernote-server.js initialization
323
324**Files:**
325- Modify: `hypernote-server.js`
326
327**Step 1:** Update AuthManager initialization to use new constructor:
328
329```javascript
330import { AuthManager } from './lib/auth.js'
331import { IdentityRegistry } from 'hyper-identity/auth/identity-registry'
332
333// In server init:
334const identityRegistry = new IdentityRegistry(this.store, {
335 db: this.registryBee
336})
337await identityRegistry.ready()
338
339this.authManager = new AuthManager({
340 identityRegistry,
341 db: this.registryBee,
342 trustedProxies: ['127.0.0.1', '::1', '::ffff:127.0.0.1'],
343 rateLimits: {
344 auth: { tokens: 1000, refillRate: 100, refillInterval: 60 * 1000 },
345 api: { tokens: 10000, refillRate: 1000, refillInterval: 60 * 1000 },
346 notes: { tokens: 5000, refillRate: 500, refillInterval: 60 * 1000 },
347 'identity-generate': { tokens: 5, refillRate: 1, refillInterval: 60 * 60 * 1000 }
348 }
349})
350```
351
352**Step 2:** Pass `sharedRegistry` reference for mirror sync:
353```javascript
354if (this.multiwriter && this.multiwriter.registry) {
355 this.authManager.sharedRegistry = this.multiwriter.registry
356}
357```
358
359**Step 3:** Update `_deduplicateIdentities()` to use hyper-identity's `mergeAccounts()` method instead of manual Hyperbee manipulation.
360
361**Step 4: Commit**
362
363---
364
365## Task 8: Update multiwriter manager to use hyper-identity registry
366
367**Files:**
368- Modify: `lib/multiwriter/manager.js`
369
370**Step 1:** The multiwriter manager's identity sync (`_syncIdentities`) currently reads user records and pushes them to the shared Autobase registry. Update to use hyper-identity's IdentityRegistry methods instead of raw Hyperbee reads.
371
372**Step 2:** The pull sync that imports users from shared registry should use hyper-identity's `createUser()` instead of raw `db.put()`.
373
374**Step 3: Commit**
375
376---
377
378## Task 9: Update routes to use new auth API
379
380**Files:**
381- Modify: `lib/routes/notebooks.js`
382- Modify: `lib/routes/multiwriter.js`
383- Modify: `lib/routes/web.js`
384
385**Step 1:** The `requireAuth` helper in notebooks.js calls `server.authManager.authorizeRequest()` — this API is preserved, no changes needed.
386
387**Step 2:** Check all routes that call `authManager.identityRegistry.*` directly and update method names if any changed.
388
389**Step 3:** The multiwriter routes that call `authManager.identityRegistry.getUser()`, `getUserByUsername()`, etc. — verify these methods exist on hyper-identity's IdentityRegistry.
390
391**Step 4: Commit**
392
393---
394
395## Task 10: Cleanup and final verification
396
397**Files:**
398- Delete: `lib/auth/identity-registry.js` (if not already deleted)
399- Delete: `lib/auth/challenge-manager.js`
400- Delete: `lib/auth/rate-limiter.js`
401- Delete: `lib/auth/base-provider.js`
402- Delete: `lib/auth/providers/` directory (all 3 providers)
403
404**Step 1:** Remove all replaced files.
405
406**Step 2:** Verify no dangling imports:
407```bash
408grep -r "from.*\./auth/" lib/ --include="*.js" | grep -v node_modules
409```
410
411**Step 3:** Start server and test:
412- Anonymous read: `curl http://localhost:4040/api/notebooks` → 200
413- Auth challenge: `curl http://localhost:4040/api/auth/challenge` → challengeId
414- Bearer token: `curl -H "Authorization: Bearer <token>" http://localhost:4040/api/notebooks?mine=true` → filtered list
415- Admin panel: Open browser, verify identity auto-registration works
416
417**Step 4:** Test mirror sync between tabertron and stortron:
418- Create notebook on one instance
419- Verify it appears on the other after sync
420- Verify auth works on both instances
421
422**Step 5: Commit**
423
424---
425
426## Migration Notes
427
428### Data Compatibility
429- Existing `user:`, `identity:`, `pubkey:` key schemas are preserved — no data migration needed
430- hyper-identity adds Autobase on top but the direct-write Hyperbee (`db`) stores identity records in the same format
431- Bearer token records (`token:*`) are compatible
432
433### Breaking Changes
434- None for HTTP API consumers (same headers, same endpoints)
435- None for web UI (same challenge-response flow)
436- Internal: `user.notebooks` → `user.resources` in registry, but the permission adapter handles the mapping
437
438### Rollback
439If issues arise, revert to the commit before Task 3. The old auth files can be restored from git.
440
441### Benefits After Migration
4421. **Autobase consensus** — multi-writer identity management without manual dedup
4432. **Shared codebase** — pear-git and hypernote use same identity library
4443. **Device linking** — DeviceLinker + SecureStorage for mnemonic exchange
4454. **Future-proof** — any new app can use hyper-identity for auth
446
447---
448
449## Key Files
450
451| File | Action |
452|------|--------|
453| `package.json` | Add hyper-identity dependency |
454| `lib/auth.js` | Rewrite to extend hyper-identity AuthManager |
455| `lib/auth/notebook-permissions.js` | New: permission adapter |
456| `lib/auth/identity-registry.js` | Delete (replaced) |
457| `lib/auth/challenge-manager.js` | Delete (replaced) |
458| `lib/auth/rate-limiter.js` | Delete (replaced) |
459| `lib/auth/base-provider.js` | Delete (replaced) |
460| `lib/auth/providers/*.js` | Delete (replaced) |
461| `hypernote-server.js` | Update initialization |
462| `lib/multiwriter/manager.js` | Update identity sync |
463| `lib/routes/*.js` | Verify API compatibility |