// Non-destructive, readiness-gated TOFU (envelope-ctx.js).
//
// THE BUG THIS GUARDS: verifyEnvelopeToCtx resolves a web user by
// getUserByIdentity(rootPubkey). On a miss it TOFU-creates a fresh
// read-only user AND registers/links the key to it. But a miss can be a
// STALE VIEW — the registry autobase hadn't replicated/caught up yet (e.g.
// right after a daemon restart or an identity re-issue). TOFU-ing then
// silently REMAPS a known user's key to a brand-new read-only identity:
// the user "logs in and gets a different, read-only identity."
//
// THE CONTRACT: a lookup miss must trigger `base.update()` + a re-check
// against the caught-up view BEFORE deciding the key is genuinely unknown.
// A known key is then found and never remapped; a truly-absent key still
// TOFUs exactly once.
import test from 'brittle'
import { an } from 'anproto'
import { signTestEnvelope } from '../lib/auth/envelope.js'
import { verifyEnvelopeToCtx } from '../lib/auth/envelope-ctx.js'
function fakeRegistry ({ userAfterUpdate = null }) {
let updated = false
const calls = { createUser: 0, registerIdentity: 0, linkIdentityToUser: 0, update: 0 }
return {
calls,
db: {}, // truthy → isRevoked / lastSeenAt branches run (both no-op below)
base: {
writable: true,
update: async () => { updated = true; calls.update++ }
},
isRevoked: async () => false,
getIdentity: async () => null,
// Miss until base.update() runs, then the replicated record appears.
getUserByIdentity: async () => (updated ? userAfterUpdate : null),
listUsers: async () => [{ userId: 'someone' }], // non-empty → isFirst=false
createUser: async (o) => { calls.createUser++; return { userId: 'new-tofu', ...o } },
registerIdentity: async () => { calls.registerIdentity++ },
linkIdentityToUser: async () => { calls.linkIdentityToUser++ }
}
}
test('a stale-view miss re-checks after update and never remaps a known user', async (t) => {
const kp = await an.gen()
const env = await signTestEnvelope({ keypair: kp, method: 'whoami', params: {} })
const existing = { userId: 'real-admin', name: 'Admin', globalPermissions: ['admin', 'write', 'read'] }
const reg = fakeRegistry({ userAfterUpdate: existing })
const svc = { identityRegistry: reg, identity: {}, _origin: { serverKey: 'x' } }
const { user, becameOwner } = await verifyEnvelopeToCtx(svc, env, { transport: 'test' })
t.is(reg.calls.update, 1, 'pulled the latest replicated view before deciding')
t.is(reg.calls.createUser, 0, 'did NOT create a new user for a known key')
t.is(reg.calls.linkIdentityToUser, 0, 'did NOT remap the key')
t.is(user.userId, 'real-admin', 'resolved the existing user after catch-up')
t.is(becameOwner, false, 'no spurious ownership')
})
test('a genuinely-absent key still TOFUs exactly once after the re-check', async (t) => {
const kp = await an.gen()
const env = await signTestEnvelope({ keypair: kp, method: 'whoami', params: {} })
const reg = fakeRegistry({ userAfterUpdate: null })
const svc = { identityRegistry: reg, identity: {}, _origin: { serverKey: 'x' } }
const { user } = await verifyEnvelopeToCtx(svc, env, { transport: 'test' })
t.is(reg.calls.update, 1, 're-checked the caught-up view')
t.is(reg.calls.createUser, 1, 'created exactly one user for a truly-new key')
t.is(user.userId, 'new-tofu', 'returns the freshly-created user')
})