import assert from "node:assert/strict"; import { newOperatorAccount } from "../../src/core/accounts"; import { createApp } from "../../src/core/app"; import { organizationStatement } from "../../src/core/auth"; import type { Db } from "../../src/core/db"; import { sha256Hex } from "../../src/core/ids"; import { migrate } from "../../src/core/migrations"; import type { WebAuthnCapability } from "../../src/core/webauthn"; import { clientVia } from "./harness"; function fakeWebAuthn(): WebAuthnCapability { let sequence = 0; return { configuration: { state: "enabled", rpId: "memory.example.com", origin: "https://memory.example.com", rpName: "Titen", }, async registrationOptions({ username, credentials }) { sequence += 1; return { challenge: `register-${sequence}`, rp: { id: "memory.example.com", name: "Titen" }, user: { id: `user-${sequence}`, name: username, displayName: username }, excludeCredentials: credentials.map((credential) => ({ id: credential.id })), }; }, async verifyRegistration({ response, challengeHash }) { const input = response as { id?: string; challenge?: string }; if (!input.id || !input.challenge || await sha256Hex(input.challenge) !== challengeHash) return { verified: false }; return { verified: true, credential: { id: input.id, publicKey: new Uint8Array([1, 2, 3, sequence]), counter: 0, transports: ["internal"], deviceType: "multiDevice", backedUp: true, }, }; }, async authenticationOptions({ credentials }) { sequence += 1; return { challenge: `authenticate-${sequence}`, rpId: "memory.example.com", allowCredentials: credentials.map((credential) => ({ id: credential.id })), }; }, async verifyAuthentication({ response, challengeHash, credential }) { const input = response as { id?: string; challenge?: string; counter?: number }; if ( input.id !== credential.id || !input.challenge || await sha256Hex(input.challenge) !== challengeHash || (input.counter ?? 0) <= credential.counter ) return { verified: false }; return { verified: true, newCounter: input.counter! }; }, }; } export async function assertWebAuthnContract(db: Db, runtime: string) { await migrate(db); let now = new Date("2026-08-30T02:00:00.000Z"); const orgId = `org_webauthn_${runtime}`; const account = await newOperatorAccount({ orgId, createdBy: `owner_webauthn_${runtime}`, username: `passkey-${runtime}`, role: "owner", scopes: ["views:compile", "memberships:read"], maxTrust: "verified", now, }); await db.batch([ organizationStatement(orgId, `WebAuthn ${runtime}`, now), ...account.statements, ]); const app = createApp({ db, runtime: `webauthn-${runtime}`, now: () => now, webauthn: fakeWebAuthn(), }); const client = clientVia(app, "https://memory.example.com"); const call = client.call; const temporary = await call("POST", "/v1/dashboard-sessions", { body: { username: account.username, password: account.temporaryPassword }, }); assert.equal(temporary.status, 201); assert.equal(temporary.body.data.auth_stage, "password_change"); const password = `correct horse battery ${runtime}`; const changed = await call("PATCH", "/v1/operator-accounts/current/password", { key: temporary.body.data.api_key, body: { password }, }); assert.equal(changed.status, 200); const established = await call("POST", "/v1/dashboard-sessions", { body: { username: account.username, password }, }); assert.equal(established.status, 201); assert.equal(established.body.data.auth_stage, "full"); const supersededSession = await call("POST", "/v1/dashboard-sessions", { body: { username: account.username, password }, }); assert.equal(supersededSession.status, 201); const options = await call("POST", "/v1/operator-accounts/current/passkeys/options", { key: established.body.data.api_key, body: {}, }); assert.equal(options.status, 201); assert.match(options.body.data.challenge_id, /^wch_/); assert.equal(options.body.data.options.challenge, "register-1"); const extraField = await call("POST", "/v1/operator-accounts/current/passkeys", { key: established.body.data.api_key, body: { challenge_id: options.body.data.challenge_id, label: "Primary device", response: { id: "credential-primary", challenge: "register-1" }, ignored: true, }, }); assert.equal(extraField.status, 400); assert.equal(extraField.body.error.code, "VALIDATION_ERROR"); const registration = await call("POST", "/v1/operator-accounts/current/passkeys", { key: established.body.data.api_key, body: { challenge_id: options.body.data.challenge_id, label: "Primary device", response: { id: "credential-primary", challenge: "register-1" }, }, }); assert.equal(registration.status, 201); assert.match(registration.body.data.credential.id, /^wcr_/); assert.equal(registration.body.data.credential.label, "Primary device"); assert.equal(registration.body.data.recovery_codes.length, 8); const recoveryCode = registration.body.data.recovery_codes[0] as string; assert.match(recoveryCode, /^[A-Za-z0-9_-]{22}$/); assert.equal((await call("GET", "/v1/principal", { key: supersededSession.body.data.api_key, })).status, 401, "first passkey enrollment must revoke older dashboard sessions"); assert.equal((await call("GET", "/v1/principal", { key: established.body.data.api_key, })).status, 200, "first passkey enrollment must preserve its current dashboard session"); const stored = await db.all<{ credentials: number; recovery_codes: number; raw_recovery_matches: number; raw_challenge_matches: number; }>( `SELECT (SELECT COUNT(*) FROM webauthn_credentials WHERE account_id = ?) AS credentials, (SELECT COUNT(*) FROM operator_recovery_codes WHERE account_id = ?) AS recovery_codes, (SELECT COUNT(*) FROM operator_recovery_codes WHERE code_hash = ?) AS raw_recovery_matches, (SELECT COUNT(*) FROM webauthn_challenges WHERE challenge_hash = ?) AS raw_challenge_matches`, [account.accountId, account.accountId, recoveryCode, "register-1"], ); assert.deepEqual(stored[0], { credentials: 1, recovery_codes: 8, raw_recovery_matches: 0, raw_challenge_matches: 0, }); const staged = await call("POST", "/v1/dashboard-sessions", { body: { username: account.username, password }, }); assert.equal(staged.status, 201); assert.equal(staged.body.data.auth_stage, "second_factor"); assert.equal(staged.body.data.second_factor_required, true); assert.deepEqual(staged.body.data.scopes, []); const forbidden = await call("GET", "/v1/memories", { key: staged.body.data.api_key }); assert.equal(forbidden.status, 403); assert.equal(forbidden.body.error.code, "STAGED_SESSION"); const authenticationOptions = await call( "POST", "/v1/dashboard-sessions/current/passkey-options", { key: staged.body.data.api_key, body: {} }, ); assert.equal(authenticationOptions.status, 201); assert.equal(authenticationOptions.body.data.options.challenge, "authenticate-2"); const completed = await call("POST", "/v1/dashboard-sessions/current/passkey", { key: staged.body.data.api_key, body: { challenge_id: authenticationOptions.body.data.challenge_id, response: { id: "credential-primary", challenge: "authenticate-2", counter: 1 }, }, }); assert.equal(completed.status, 201); assert.equal(completed.body.data.auth_stage, "full"); assert.match(completed.body.data.api_key, /^titen_sk_/); assert.equal((await call("GET", "/v1/memories", { key: completed.body.data.api_key })).status, 200); assert.equal((await call("GET", "/v1/principal", { key: staged.body.data.api_key })).status, 401); const [credential] = await db.all<{ counter: number }>( "SELECT counter FROM webauthn_credentials WHERE credential_id = ?", ["credential-primary"], ); assert.equal(credential!.counter, 1); const [usedChallenge] = await db.all<{ used_at_ms: number | null }>( "SELECT used_at_ms FROM webauthn_challenges WHERE id = ?", [authenticationOptions.body.data.challenge_id], ); assert.equal(usedChallenge!.used_at_ms, now.getTime()); const recoveryStage = await call("POST", "/v1/dashboard-sessions", { body: { username: account.username, password }, }); const recovered = await call("POST", "/v1/dashboard-sessions/current/recovery-code", { key: recoveryStage.body.data.api_key, body: { recovery_code: recoveryCode }, }); assert.equal(recovered.status, 201); assert.equal(recovered.body.data.auth_stage, "full"); const [consumed] = await db.all<{ count: number }>( "SELECT COUNT(*) AS count FROM operator_recovery_codes WHERE account_id = ? AND used_at_ms IS NOT NULL", [account.accountId], ); assert.equal(Number(consumed!.count), 1); const secondRecoveryStage = await call("POST", "/v1/dashboard-sessions", { body: { username: account.username, password }, }); const replay = await call("POST", "/v1/dashboard-sessions/current/recovery-code", { key: secondRecoveryStage.body.data.api_key, body: { recovery_code: recoveryCode }, }); assert.equal(replay.status, 401); assert.equal(replay.body.error.code, "SECOND_FACTOR_INVALID"); const concurrentCode = registration.body.data.recovery_codes[1] as string; const concurrentStages = await Promise.all([0, 1].map(() => call("POST", "/v1/dashboard-sessions", { body: { username: account.username, password }, }))); const concurrent = await Promise.all(concurrentStages.map((session) => call( "POST", "/v1/dashboard-sessions/current/recovery-code", { key: session.body.data.api_key, body: { recovery_code: concurrentCode } }, ))); assert.deepEqual(concurrent.map((response) => response.status).sort(), [201, 401]); const accountKey = await sha256Hex(`dashboard-login:${account.username}`); await db.batch([{ sql: "INSERT OR REPLACE INTO login_throttles VALUES (?, 50, ?, ?)", params: [accountKey, now.getTime() + 60_000, now.getTime()] }]); const blockedPassword = await call("POST", "/v1/dashboard-sessions", { body: { username: account.username, password } }); assert.equal(blockedPassword.status, 401); const startRecovery = async (username = account.username) => call("POST", "/v1/dashboard-sessions/recovery", { body: { username } }); const rescue = await startRecovery(); assert.equal(rescue.status, 201); assert.deepEqual(Object.keys(rescue.body.data).sort(), ["api_key", "auth_stage", "expires_at"]); const unknownRescue = await startRecovery("unknown-rescue-user"); assert.equal(unknownRescue.status, 201); assert.deepEqual(Object.keys(unknownRescue.body.data).sort(), Object.keys(rescue.body.data).sort()); assert.equal((await call("GET", "/v1/memories", { key: rescue.body.data.api_key })).status, 403); const rescueOptions = await call("POST", "/v1/dashboard-sessions/current/passkey-options", { key: rescue.body.data.api_key, body: {} }); const rescued = await call("POST", "/v1/dashboard-sessions/current/passkey", { key: rescue.body.data.api_key, body: { challenge_id: rescueOptions.body.data.challenge_id, response: { id: "credential-primary", challenge: rescueOptions.body.data.options.challenge, counter: 2 } }, }); assert.equal(rescued.status, 201); assert.equal(rescued.body.data.failed_attempts, 50); await db.batch([{ sql: "INSERT OR REPLACE INTO login_throttles VALUES (?, 50, ?, ?)", params: [accountKey, now.getTime() + 60_000, now.getTime()] }]); const rescueCodeSession = await startRecovery(); assert.equal((await call("POST", "/v1/dashboard-sessions/current/recovery-code", { key: rescueCodeSession.body.data.api_key, body: { recovery_code: "invalid-recovery-code" } })).status, 401); const rescueCode = registration.body.data.recovery_codes[3] as string; const codeRescued = await call("POST", "/v1/dashboard-sessions/current/recovery-code", { key: rescueCodeSession.body.data.api_key, body: { recovery_code: rescueCode } }); assert.equal(codeRescued.status, 201); assert.equal(codeRescued.body.data.auth_stage, "full"); const replayRescue = await startRecovery(); assert.equal((await call("POST", "/v1/dashboard-sessions/current/recovery-code", { key: replayRescue.body.data.api_key, body: { recovery_code: rescueCode } })).status, 401); const expiredRescue = await startRecovery(); now = new Date(now.getTime() + 16 * 60_000); assert.equal((await call("POST", "/v1/dashboard-sessions/current/recovery-code", { key: expiredRescue.body.data.api_key, body: { recovery_code: registration.body.data.recovery_codes[4] } })).status, 401); const disabledRescue = await startRecovery(); await db.batch([{ sql: "UPDATE operator_accounts SET disabled_at = ? WHERE id = ?", params: [now.toISOString(), account.accountId] }]); assert.equal((await call("POST", "/v1/dashboard-sessions/current/recovery-code", { key: disabledRescue.body.data.api_key, body: { recovery_code: registration.body.data.recovery_codes[4] } })).status, 401); await db.batch([{ sql: "UPDATE operator_accounts SET disabled_at = NULL, must_change_password = 1 WHERE id = ?", params: [account.accountId] }]); const rotateRescue = await startRecovery(); const rotateSession = await call("POST", "/v1/dashboard-sessions/current/recovery-code", { key: rotateRescue.body.data.api_key, body: { recovery_code: registration.body.data.recovery_codes[4] } }); assert.equal(rotateSession.status, 201); assert.equal(rotateSession.body.data.auth_stage, "password_change"); assert.deepEqual(rotateSession.body.data.scopes, []); await db.batch([{ sql: "UPDATE operator_accounts SET must_change_password = 0 WHERE id = ?", params: [account.accountId] }]); const racedRecoveryStage = await startRecovery(); let resetDuringRecovery = false; const racedRecoveryDb: Db = { ...db, async all(sql: string, params?: import("../../src/core/db").Param[]) { const rows = await db.all(sql, params); if (!resetDuringRecovery && sql.includes("a.must_change_password, m.role")) { resetDuringRecovery = true; await db.batch([ { sql: "UPDATE operator_accounts SET must_change_password = 1 WHERE id = ?", params: [account.accountId] }, { sql: "UPDATE api_keys SET revoked_at = ? WHERE principal_id = ? AND label = 'Dashboard session'", params: [now.toISOString(), account.principalId] }, ]); } return rows; }, }; const raceCall = clientVia(createApp({ db: racedRecoveryDb, runtime, now: () => now }), "https://memory.example.com").call; assert.equal((await raceCall("POST", "/v1/dashboard-sessions/current/recovery-code", { key: racedRecoveryStage.body.data.api_key, body: { recovery_code: registration.body.data.recovery_codes[5] }, })).status, 401, "reset must prevent stale recovery proof from issuing a session"); assert.equal(resetDuringRecovery, true); await db.batch([{ sql: "UPDATE operator_accounts SET must_change_password = 0 WHERE id = ?", params: [account.accountId] }]); const disabledCall = clientVia(createApp({ db, runtime: `webauthn-disabled-${runtime}`, now: () => now, }), "https://memory.example.com").call; const disabledStage = await disabledCall("POST", "/v1/dashboard-sessions", { body: { username: account.username, password }, }); assert.equal(disabledStage.body.data.auth_stage, "second_factor"); const disabledRecovery = await disabledCall("POST", "/v1/dashboard-sessions/current/recovery-code", { key: disabledStage.body.data.api_key, body: { recovery_code: registration.body.data.recovery_codes[2] }, }); assert.equal(disabledRecovery.status, 201, "recovery must survive disabled WebAuthn configuration"); const fullKey = disabledRecovery.body.data.api_key as string; const passkeys = await call("GET", "/v1/operator-accounts/current/passkeys", { key: fullKey }); assert.equal(passkeys.status, 200); assert.equal(passkeys.body.data.credentials.length, 1); const [listedPasskey] = passkeys.body.data.credentials; assert.deepEqual(Object.keys(listedPasskey).sort(), ["backed_up", "created_at", "device_type", "id", "label", "last_used_at"]); assert.equal(listedPasskey.id, registration.body.data.credential.id); assert.equal(listedPasskey.label, "Primary device"); assert.equal(typeof listedPasskey.backed_up, "boolean"); assert.notEqual(listedPasskey.last_used_at, null, "a used passkey reports when it was last used"); assert.equal(JSON.stringify(passkeys.body).includes("credential-primary"), false, "the raw credential id stays server-side"); // Regeneration must invalidate every code of the previous generation. const previousCodes = registration.body.data.recovery_codes as string[]; const regenerated = await call("POST", "/v1/operator-accounts/current/recovery-codes", { key: fullKey, body: {} }); assert.equal(regenerated.status, 201); const freshCodes = regenerated.body.data.recovery_codes as string[]; assert.equal(freshCodes.length, 8); assert.equal(new Set(freshCodes).size, 8); assert.equal(freshCodes.some((code) => previousCodes.includes(code)), false); const [regeneratedState] = await db.all<{ unused: number; generations: number; outside_current: number }>( `SELECT (SELECT COUNT(*) FROM operator_recovery_codes WHERE account_id = ? AND used_at_ms IS NULL) AS unused, (SELECT COUNT(*) FROM operator_recovery_generations WHERE account_id = ?) AS generations, (SELECT COUNT(*) FROM operator_recovery_codes c WHERE c.account_id = ? AND c.generation_id NOT IN ( SELECT generation_id FROM operator_recovery_generations WHERE account_id = ?)) AS outside_current`, [account.accountId, account.accountId, account.accountId, account.accountId], ); assert.deepEqual( { unused: Number(regeneratedState!.unused), generations: Number(regeneratedState!.generations), outside_current: Number(regeneratedState!.outside_current) }, { unused: 8, generations: 1, outside_current: 0 }, ); const [regenerateAudit] = await db.all<{ count: number }>( "SELECT COUNT(*) AS count FROM audit_log WHERE action = 'operator_account.recovery_regenerate' AND resource_id = ?", [account.accountId], ); assert.equal(Number(regenerateAudit!.count), 1); const oldCodeStage = await call("POST", "/v1/dashboard-sessions", { body: { username: account.username, password } }); const oldCode = await call("POST", "/v1/dashboard-sessions/current/recovery-code", { key: oldCodeStage.body.data.api_key, body: { recovery_code: previousCodes[7] }, }); assert.equal(oldCode.status, 401, "an unused code from the previous generation is rejected"); assert.equal(oldCode.body.error.code, "SECOND_FACTOR_INVALID"); const newCodeStage = await call("POST", "/v1/dashboard-sessions", { body: { username: account.username, password } }); const newCode = await call("POST", "/v1/dashboard-sessions/current/recovery-code", { key: newCodeStage.body.data.api_key, body: { recovery_code: freshCodes[0] }, }); assert.equal(newCode.status, 201); assert.equal(newCode.body.data.auth_stage, "full"); const lastCredentialId = registration.body.data.credential.id as string; const noConfirmation = await call("DELETE", `/v1/operator-accounts/current/passkeys/${lastCredentialId}`, { key: disabledRecovery.body.data.api_key, body: {}, }); assert.equal(noConfirmation.status, 401); const removed = await disabledCall("DELETE", `/v1/operator-accounts/current/passkeys/${lastCredentialId}`, { key: disabledRecovery.body.data.api_key, body: { password }, }); assert.equal(removed.status, 200); const noSecondFactor = await disabledCall("POST", "/v1/dashboard-sessions", { body: { username: account.username, password }, }); assert.equal(noSecondFactor.status, 201); assert.equal(noSecondFactor.body.data.auth_stage, "full"); const emptyPasskeys = await call("GET", "/v1/operator-accounts/current/passkeys", { key: noSecondFactor.body.data.api_key }); assert.equal(emptyPasskeys.status, 200); assert.deepEqual(emptyPasskeys.body.data.credentials, []); const withoutPasskey = await call("POST", "/v1/operator-accounts/current/recovery-codes", { key: noSecondFactor.body.data.api_key, body: {}, }); assert.equal(withoutPasskey.status, 409, "recovery codes require a registered passkey"); }