| … | … | @@ -194,65 +194,113 @@ export class IdentityRegistry { |
| 194 | 194 | // ==================== IDENTITY OPERATIONS ==================== |
| 195 | 195 | |
| 196 | 196 | /** |
| 197 | | * Register identity in direct-write store |
| 198 | | * @param {Object} identity - { identityId, method, publicKey, ... } |
| 197 | * Register an identity record. |
| 198 | * |
| 199 | * Writes through the Autobase apply (`REGISTER_IDENTITY` op) so the |
| 200 | * full record + reverse indexes (pubkey, sshkey, noisekey) replicate |
| 201 | * to mirror peers via the autobase view. Keeps a parallel write to |
| 202 | * the local Hyperbee `db` for backward-compat with daemons that |
| 203 | * still read from there during the migration window. |
| 204 | * |
| 205 | * Pre-fix this method only wrote to `db` (local-only) — see |
| 206 | * pear-git-v3/test/identity-record-replication.test.js for the |
| 207 | * proof that putting it in the view fixes cross-daemon visibility. |
| 208 | * |
| 209 | * @param {Object} identity - { identityId, method, publicKey, credentials, ... } |
| 199 | 210 | */ |
| 200 | 211 | async registerIdentity (identity) { |
| 201 | | if (!this.db) throw new Error('No direct-write db configured') |
| 202 | | await this.db.put(`identity:${identity.identityId}`, identity) |
| 203 | | |
| 204 | | // Create reverse lookup indexes based on identity method |
| 205 | | if (identity.method === 'ssh-key') { |
| 206 | | const fingerprint = identity.credentials && identity.credentials.fingerprint |
| 207 | | if (fingerprint) { |
| 208 | | await this.db.put(`sshkey:${fingerprint}`, { identityId: identity.identityId }) |
| 209 | | } |
| 210 | | } else if (identity.method === 'noise-key') { |
| 211 | | const noiseKey = identity.credentials && identity.credentials.publicKey |
| 212 | | if (noiseKey) { |
| 213 | | await this.db.put(`noisekey:${noiseKey}`, { identityId: identity.identityId }) |
| 214 | | } |
| 212 | if (!identity || !identity.identityId || !identity.method) { |
| 213 | throw new Error('registerIdentity requires { identityId, method, ... }') |
| 215 | 214 | } |
| 216 | | if (identity.publicKey) { |
| 217 | | await this.db.put(`pubkey:${identity.publicKey}`, { identityId: identity.identityId }) |
| 215 | // 1. Append op to autobase — view picks up record + reverse indexes |
| 216 | // via apply, replicates to all peers. |
| 217 | await this._base.append(encodeOp({ |
| 218 | type: OpTypes.REGISTER_IDENTITY, |
| 219 | identity |
| 220 | })) |
| 221 | |
| 222 | // 2. Mirror to local db too, so legacy code paths reading directly |
| 223 | // from db keep working until everything reads from the view. |
| 224 | // (This write is local-only; no harm if it diverges — view is |
| 225 | // the source of truth.) |
| 226 | if (this.db) { |
| 227 | try { |
| 228 | await this.db.put(`identity:${identity.identityId}`, identity) |
| 229 | if (identity.method === 'ssh-key' && identity.credentials && identity.credentials.fingerprint) { |
| 230 | await this.db.put(`sshkey:${identity.credentials.fingerprint}`, { identityId: identity.identityId }) |
| 231 | } else if (identity.method === 'noise-key' && identity.credentials && identity.credentials.publicKey) { |
| 232 | await this.db.put(`noisekey:${identity.credentials.publicKey}`, { identityId: identity.identityId }) |
| 233 | } |
| 234 | if (identity.publicKey) { |
| 235 | await this.db.put(`pubkey:${identity.publicKey}`, { identityId: identity.identityId }) |
| 236 | } |
| 237 | } catch { |
| 238 | // db is best-effort cache; failure here doesn't undo the autobase append |
| 239 | } |
| 218 | 240 | } |
| 219 | 241 | } |
| 220 | 242 | |
| 221 | 243 | /** |
| 222 | | * Get identity by ID |
| 244 | * Get identity by ID. Try the autobase view first (replicated, source |
| 245 | * of truth post-fix); fall back to the local db (legacy/cache). |
| 223 | 246 | */ |
| 224 | 247 | async getIdentity (identityId) { |
| 248 | if (this._view) { |
| 249 | const entry = await this._view.get(`${this._prefix}identity:${identityId}`).catch(() => null) |
| 250 | if (entry && entry.value) return entry.value |
| 251 | } |
| 225 | 252 | if (!this.db) return null |
| 226 | | const entry = await this.db.get(`identity:${identityId}`) |
| 253 | const entry = await this.db.get(`identity:${identityId}`).catch(() => null) |
| 227 | 254 | return entry ? entry.value : null |
| 228 | 255 | } |
| 229 | 256 | |
| 230 | 257 | /** |
| 231 | | * Get identity by public key |
| 258 | * Get identity by public key. View first, then db fallback. |
| 232 | 259 | */ |
| 233 | 260 | async getIdentityByPublicKey (publicKey) { |
| 261 | if (this._view) { |
| 262 | const idx = await this._view.get(`${this._prefix}pubkey:${publicKey}`).catch(() => null) |
| 263 | if (idx && idx.value && idx.value.identityId) { |
| 264 | const id = await this.getIdentity(idx.value.identityId) |
| 265 | if (id) return id |
| 266 | } |
| 267 | } |
| 234 | 268 | if (!this.db) return null |
| 235 | | const entry = await this.db.get(`pubkey:${publicKey}`) |
| 269 | const entry = await this.db.get(`pubkey:${publicKey}`).catch(() => null) |
| 236 | 270 | if (!entry) return null |
| 237 | 271 | return this.getIdentity(entry.value.identityId) |
| 238 | 272 | } |
| 239 | 273 | |
| 240 | 274 | /** |
| 241 | | * Get identity by SSH key fingerprint |
| 275 | * Get identity by SSH key fingerprint. View first, then db fallback. |
| 242 | 276 | */ |
| 243 | 277 | async getIdentityBySSHKey (fingerprint) { |
| 278 | if (this._view) { |
| 279 | const idx = await this._view.get(`${this._prefix}sshkey:${fingerprint}`).catch(() => null) |
| 280 | if (idx && idx.value && idx.value.identityId) { |
| 281 | const id = await this.getIdentity(idx.value.identityId) |
| 282 | if (id) return id |
| 283 | } |
| 284 | } |
| 244 | 285 | if (!this.db) return null |
| 245 | | const entry = await this.db.get(`sshkey:${fingerprint}`) |
| 286 | const entry = await this.db.get(`sshkey:${fingerprint}`).catch(() => null) |
| 246 | 287 | if (!entry) return null |
| 247 | 288 | return this.getIdentity(entry.value.identityId) |
| 248 | 289 | } |
| 249 | 290 | |
| 250 | 291 | /** |
| 251 | | * Get identity by Noise protocol public key |
| 292 | * Get identity by Noise protocol public key. View first, then db fallback. |
| 252 | 293 | */ |
| 253 | 294 | async getIdentityByNoiseKey (noiseKey) { |
| 295 | if (this._view) { |
| 296 | const idx = await this._view.get(`${this._prefix}noisekey:${noiseKey}`).catch(() => null) |
| 297 | if (idx && idx.value && idx.value.identityId) { |
| 298 | const id = await this.getIdentity(idx.value.identityId) |
| 299 | if (id) return id |
| 300 | } |
| 301 | } |
| 254 | 302 | if (!this.db) return null |
| 255 | | const entry = await this.db.get(`noisekey:${noiseKey}`) |
| 303 | const entry = await this.db.get(`noisekey:${noiseKey}`).catch(() => null) |
| 256 | 304 | if (!entry) return null |
| 257 | 305 | return this.getIdentity(entry.value.identityId) |
| 258 | 306 | } |