@nxgt/janus 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +135 -15
  2. package/dist/auth/config.d.ts +25 -1
  3. package/dist/auth/config.d.ts.map +1 -1
  4. package/dist/auth/context.d.ts.map +1 -1
  5. package/dist/auth/index.d.ts +4 -3
  6. package/dist/auth/index.d.ts.map +1 -1
  7. package/dist/auth/one-time.d.ts +39 -0
  8. package/dist/auth/one-time.d.ts.map +1 -0
  9. package/dist/auth/port/types.d.ts +74 -3
  10. package/dist/auth/port/types.d.ts.map +1 -1
  11. package/dist/auth/sealing.d.ts +39 -0
  12. package/dist/auth/sealing.d.ts.map +1 -0
  13. package/dist/auth/second-factor/challenge.d.ts +20 -0
  14. package/dist/auth/second-factor/challenge.d.ts.map +1 -0
  15. package/dist/auth/second-factor/factor.d.ts +30 -0
  16. package/dist/auth/second-factor/factor.d.ts.map +1 -0
  17. package/dist/auth/second-factor/flows.d.ts +19 -0
  18. package/dist/auth/second-factor/flows.d.ts.map +1 -0
  19. package/dist/auth/second-factor/lifecycle.d.ts +8 -0
  20. package/dist/auth/second-factor/lifecycle.d.ts.map +1 -0
  21. package/dist/auth/sessions.d.ts.map +1 -1
  22. package/dist/auth/totp.d.ts +36 -0
  23. package/dist/auth/totp.d.ts.map +1 -0
  24. package/dist/auth/types.d.ts +91 -7
  25. package/dist/auth/types.d.ts.map +1 -1
  26. package/dist/auth/users.d.ts +2 -2
  27. package/dist/auth/users.d.ts.map +1 -1
  28. package/dist/chunks/{index-qwfkhqkk.js → index-06vp9c5r.js} +12 -3
  29. package/dist/chunks/{index-qwfkhqkk.js.map → index-06vp9c5r.js.map} +3 -3
  30. package/dist/chunks/{index-53y1afjz.js → index-5vr13kkb.js} +2 -2
  31. package/dist/chunks/{index-mgh85djb.js → index-c4v27jfr.js} +2 -2
  32. package/dist/chunks/{index-8tksqzkr.js → index-gbwn2tts.js} +14 -3
  33. package/dist/chunks/{index-8tksqzkr.js.map → index-gbwn2tts.js.map} +3 -3
  34. package/dist/conformance/cases/outage.d.ts.map +1 -1
  35. package/dist/conformance/cases/tokens.d.ts.map +1 -1
  36. package/dist/conformance/cases/users.d.ts.map +1 -1
  37. package/dist/conformance/fixtures.d.ts.map +1 -1
  38. package/dist/conformance/index.js +122 -5
  39. package/dist/conformance/index.js.map +6 -6
  40. package/dist/errors/janus-error.d.ts +28 -2
  41. package/dist/errors/janus-error.d.ts.map +1 -1
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +390 -40
  45. package/dist/index.js.map +15 -8
  46. package/dist/permissions/index.js +3 -3
  47. package/docs/README.md +2 -1
  48. package/docs/guide/adapters.md +129 -3
  49. package/docs/guide/errors.md +18 -6
  50. package/docs/guide/second-factor.md +569 -0
  51. package/docs/guide/sessions.md +18 -0
  52. package/docs/guide/users.md +8 -2
  53. package/docs/guide/vocabulary.md +6 -1
  54. package/docs/roadmap.md +33 -46
  55. package/docs/troubleshooting.md +276 -5
  56. package/package.json +1 -1
  57. /package/dist/chunks/{index-53y1afjz.js.map → index-5vr13kkb.js.map} +0 -0
  58. /package/dist/chunks/{index-mgh85djb.js.map → index-c4v27jfr.js.map} +0 -0
@@ -3,9 +3,9 @@
3
3
  "sources": ["../src/ids/id.ts", "../src/auth/port/memory.ts"],
4
4
  "sourcesContent": [
5
5
  "/**\n * An id — of a user, a session: a UUIDv7, lowercase, hyphenated.\n *\n * **The core mints it, not the store**, and that decision pays for itself three\n * times over:\n *\n * - A UUIDv7 leads with a 48-bit millisecond timestamp, so ids sort in\n * creation order as strings. The pagination cursor is therefore *the last\n * id* — one index on the id, and the ordering is already total. A\n * store-minted id would need a `createdAt` index plus the id as a tiebreak,\n * because a timestamp alone is not a total order, and an opaque cursor per\n * adapter.\n * - `insertUser` becomes **idempotent under retry**: the id is decided\n * before the call, so a retry after a timeout writes the same row rather\n * than a second user.\n * - Every adapter reports the same shape, so moving an application from one\n * adapter to another is a copy rather than a rewrite of every stored\n * reference.\n *\n * The price, stated plainly: an adapter cannot reuse an existing numeric primary\n * key. It gets a `uuid` column, or a 36-character string, and the monotonic\n * prefix gives it the index locality a UUIDv4 destroys.\n */\nexport type Id = string;\n\nconst HEX: readonly string[] = Array.from({ length: 256 }, (_, i) =>\n\ti.toString(16).padStart(2, '0'),\n);\n\nlet lastMs = -1;\nlet sequence = 0;\n\n/**\n * The 12-bit sequence UUIDv7 keeps for ordering inside one millisecond.\n *\n * RFC 9562 calls this the \"replace leftmost random bits with an increased clock\n * precision\" method. Without it, two ids minted in the same millisecond order\n * by their random tail — still a total order, which is all the port requires,\n * but not creation order, and \"newest first\" would then be wrong for anything\n * created in a burst. A signup storm is exactly such a burst.\n */\nconst SEQUENCE_MAX = 0xfff;\n\n/**\n * A fresh id.\n *\n * Strictly increasing: within one millisecond it uses the sequence, and if that\n * overflows — more than 4096 ids in a millisecond, which no real\n * application reaches — it borrows the next millisecond rather than repeating\n * one. Across processes the 62 random bits of the tail are what keep two machines\n * apart.\n */\nexport function mintId(now: number = Date.now()): Id {\n\tlet ms = now;\n\n\tif (ms === lastMs) {\n\t\tsequence += 1;\n\t\tif (sequence > SEQUENCE_MAX) {\n\t\t\t// Borrow from the next millisecond. This keeps ids strictly\n\t\t\t// increasing at the cost of a timestamp a hair ahead of the clock,\n\t\t\t// which is the right trade: a repeated id would break the cursor.\n\t\t\tms = lastMs + 1;\n\t\t\tsequence = 0;\n\t\t}\n\t} else if (ms > lastMs) {\n\t\tsequence = 0;\n\t} else {\n\t\t// The clock went backwards — an NTP step, or a container resumed. Hold\n\t\t// the last millisecond and keep counting, so ids stay ordered even\n\t\t// though they stop matching the wall clock for a moment.\n\t\tms = lastMs;\n\t\tsequence += 1;\n\t\tif (sequence > SEQUENCE_MAX) {\n\t\t\tms = lastMs + 1;\n\t\t\tsequence = 0;\n\t\t}\n\t}\n\n\tlastMs = ms;\n\n\tconst bytes = new Uint8Array(16);\n\tcrypto.getRandomValues(bytes.subarray(8));\n\n\tbytes[0] = (ms / 2 ** 40) & 0xff;\n\tbytes[1] = (ms / 2 ** 32) & 0xff;\n\tbytes[2] = (ms / 2 ** 24) & 0xff;\n\tbytes[3] = (ms / 2 ** 16) & 0xff;\n\tbytes[4] = (ms / 2 ** 8) & 0xff;\n\tbytes[5] = ms & 0xff;\n\t// Version 7 in the high nibble, the sequence's top 4 bits below it.\n\tbytes[6] = 0x70 | ((sequence >> 8) & 0x0f);\n\tbytes[7] = sequence & 0xff;\n\t// Variant 10 in the two high bits of byte 8; the rest of it stays random.\n\tbytes[8] = 0x80 | ((bytes[8] as number) & 0x3f);\n\n\treturn `${HEX[bytes[0] as number]}${HEX[bytes[1] as number]}${HEX[bytes[2] as number]}${HEX[bytes[3] as number]}-${HEX[bytes[4] as number]}${HEX[bytes[5] as number]}-${HEX[bytes[6] as number]}${HEX[bytes[7] as number]}-${HEX[bytes[8] as number]}${HEX[bytes[9] as number]}-${HEX[bytes[10] as number]}${HEX[bytes[11] as number]}${HEX[bytes[12] as number]}${HEX[bytes[13] as number]}${HEX[bytes[14] as number]}${HEX[bytes[15] as number]}`;\n}\n\nconst UUID_V7 =\n\t/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;\n\n/**\n * Whether this string is an id this package could have minted.\n *\n * Used by an adapter to answer `null` for a malformed id **without reaching the\n * store**: an id arrives off a URL, and one of the wrong shape is \"no such\n * user\", not an outage and not a query. Rejecting it here also keeps a\n * hand-written id out of a store that would then hold something the cursor\n * cannot order.\n */\nexport function isId(value: string): boolean {\n\treturn UUID_V7.test(value);\n}\n\n/**\n * When this id was minted, from the id itself.\n *\n * Offered because the timestamp is *in* the id, so a `createdAt` column is a\n * convenience rather than the truth — and an adapter that loses one can still\n * answer. It is not a substitute for `createdAt`: the sequence may have borrowed\n * a millisecond, and a clock that stepped backwards is held rather than\n * followed, so this is accurate to the millisecond and no further.\n */\nexport function mintedAt(id: Id): Date {\n\tconst hex = id.replace(/-/g, '').slice(0, 12);\n\treturn new Date(Number.parseInt(hex, 16));\n}\n",
6
- "import { NotFoundError, StoreConflict } from '../../errors/janus-error';\nimport type { Id } from '../../ids/id';\nimport type {\n\tJanusStores,\n\tSessionId,\n\tSessionRecord,\n\tSessionStore,\n\tTokenRecord,\n\tTokenStore,\n\tUserPatch,\n\tUserRecord,\n\tUserStore,\n} from './types';\n\n/**\n * The reference implementation of the store port, in memory.\n *\n * **Shipped and documented, not a test helper.** It is what a consumer uses in\n * their own unit tests, and what an adapter author compares against when a\n * conformance case they do not understand turns red. So it keeps the six rules\n * for real rather than approximately:\n *\n * - uniqueness is a constraint — an index checked and written inside the same\n * synchronous step, which is what atomic means on one event loop;\n * - `consumeToken` reads and writes with no `await` between the two, so twenty\n * concurrent calls see exactly one unspent token;\n * - every record is **copied in and copied out**, so a caller that mutates what\n * it passed or what it got back cannot reach the store — the in-memory\n * version of \"bytes round-trip\";\n * - a patch applies only the fields it names, and only the fields the port\n * declares: a stray `version` or `id` in a patch from JavaScript is ignored,\n * not written.\n *\n * Every method is `async` even though none waits on anything: a caller that\n * forgot an `await` must fail here the way it would against a real database.\n */\nexport function createMemoryStores(): JanusStores {\n\treturn {\n\t\tusers: memoryUserStore(),\n\t\tsessions: memorySessionStore(),\n\t\ttokens: memoryTokenStore(),\n\t};\n}\n\nconst copy = <T>(value: T): T => structuredClone(value);\n\n/** The unique key of one login. `\\u0000` cannot occur in a type name the core accepts. */\nconst keyOf = (type: string, login: string): string => `${type}\\u0000${login}`;\n\nfunction memoryUserStore(): UserStore {\n\tconst byId = new Map<Id, UserRecord>();\n\t// The unique index: (type, login) key → the id holding it.\n\tconst byLogin = new Map<string, Id>();\n\n\t/** The first of `logins` held by a user of `type` other than `id`. */\n\tconst takenBy = (\n\t\ttype: string,\n\t\tlogins: readonly string[],\n\t\tid: Id,\n\t): string | undefined =>\n\t\tlogins.find((login) => {\n\t\t\tconst holder = byLogin.get(keyOf(type, login));\n\t\t\treturn holder !== undefined && holder !== id;\n\t\t});\n\n\tconst taken = (operation: string, type: string, login: string) =>\n\t\tnew StoreConflict(\n\t\t\t'login',\n\t\t\t`${operation}: the login is taken by another ${type}`,\n\t\t\t{ login, userType: type, operation },\n\t\t);\n\n\treturn {\n\t\tasync insertUser(record) {\n\t\t\tconst stored = byId.get(record.id);\n\t\t\tif (stored !== undefined) return copy(stored);\n\n\t\t\tconst collision = takenBy(record.type, record.logins, record.id);\n\t\t\tif (collision !== undefined) {\n\t\t\t\tthrow taken('insertUser', record.type, collision);\n\t\t\t}\n\n\t\t\tconst written = copy(record);\n\t\t\tbyId.set(written.id, written);\n\t\t\tfor (const login of written.logins) {\n\t\t\t\tbyLogin.set(keyOf(written.type, login), written.id);\n\t\t\t}\n\n\t\t\treturn copy(written);\n\t\t},\n\n\t\tasync findUser(id) {\n\t\t\tconst stored = byId.get(id);\n\t\t\treturn stored === undefined ? null : copy(stored);\n\t\t},\n\n\t\tasync findUserByLogin(type, login) {\n\t\t\tconst id = byLogin.get(keyOf(type, login));\n\t\t\tconst stored = id === undefined ? undefined : byId.get(id);\n\t\t\treturn stored === undefined ? null : copy(stored);\n\t\t},\n\n\t\tasync listUsers({ type, after, limit }) {\n\t\t\t// A Map keeps insertion order, not id order: a retried insert or an\n\t\t\t// id minted on another machine can land out of sequence.\n\t\t\tconst ids = [...byId.values()]\n\t\t\t\t.filter((user) => user.type === type)\n\t\t\t\t.map((user) => user.id)\n\t\t\t\t.filter((id) => after === null || id > after)\n\t\t\t\t.sort();\n\t\t\tconst pageIds = ids.slice(0, limit);\n\t\t\tconst last = pageIds.at(-1);\n\n\t\t\treturn {\n\t\t\t\titems: pageIds.map((id) => copy(byId.get(id) as UserRecord)),\n\t\t\t\tnextCursor: ids.length > limit && last !== undefined ? last : null,\n\t\t\t};\n\t\t},\n\n\t\tasync updateUser(id, patch, ifVersion) {\n\t\t\tconst stored = byId.get(id);\n\n\t\t\tif (stored === undefined) {\n\t\t\t\tthrow new NotFoundError('updateUser: no user has this id', {\n\t\t\t\t\tuserId: id,\n\t\t\t\t\toperation: 'updateUser',\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (stored.version !== ifVersion) {\n\t\t\t\tthrow new StoreConflict(\n\t\t\t\t\t'version',\n\t\t\t\t\t`updateUser: expected version ${ifVersion}, found ${stored.version}`,\n\t\t\t\t\t{\n\t\t\t\t\t\tuserId: id,\n\t\t\t\t\t\texpectedVersion: ifVersion,\n\t\t\t\t\t\tactualVersion: stored.version,\n\t\t\t\t\t\toperation: 'updateUser',\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (patch.logins !== undefined) {\n\t\t\t\tconst collision = takenBy(stored.type, patch.logins, id);\n\t\t\t\tif (collision !== undefined) {\n\t\t\t\t\tthrow taken('updateUser', stored.type, collision);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst written = applyPatch(stored, copy(patch));\n\n\t\t\tbyId.set(id, written);\n\t\t\tif (patch.logins !== undefined) {\n\t\t\t\tfor (const login of stored.logins) {\n\t\t\t\t\tbyLogin.delete(keyOf(stored.type, login));\n\t\t\t\t}\n\t\t\t\tfor (const login of written.logins) {\n\t\t\t\t\tbyLogin.set(keyOf(written.type, login), id);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn copy(written);\n\t\t},\n\n\t\tasync deleteUser(id) {\n\t\t\tconst stored = byId.get(id);\n\t\t\tif (stored === undefined) return false;\n\n\t\t\tbyId.delete(id);\n\t\t\tfor (const login of stored.logins) {\n\t\t\t\tbyLogin.delete(keyOf(stored.type, login));\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\t};\n}\n\n/**\n * The record a patch produces: the fields it names, replaced whole; the fields\n * it does not name, untouched.\n *\n * Reads each field by name rather than spreading the patch, so a key the port\n * does not declare — `version`, `id`, `type`, `createdAt`, or a `snake_case`\n * typo from JavaScript — never reaches the record. A key present as\n * `undefined` is absent, never an erasure.\n */\nfunction applyPatch(stored: UserRecord, patch: UserPatch): UserRecord {\n\treturn {\n\t\t...stored,\n\t\tschemaVersion: patch.schemaVersion ?? stored.schemaVersion,\n\t\tactive: patch.active ?? stored.active,\n\t\tfields: patch.fields ?? stored.fields,\n\t\tlogins: patch.logins ?? stored.logins,\n\t\tpassword: patch.password === undefined ? stored.password : patch.password,\n\t\temailVerifiedAt:\n\t\t\tpatch.emailVerifiedAt === undefined\n\t\t\t\t? stored.emailVerifiedAt\n\t\t\t\t: patch.emailVerifiedAt,\n\t\tversion: stored.version + 1,\n\t\tupdatedAt: patch.updatedAt,\n\t};\n}\n\nfunction memorySessionStore(): SessionStore {\n\tconst byId = new Map<SessionId, SessionRecord>();\n\tconst byTokenHash = new Map<string, SessionId>();\n\n\tconst revoke = (id: SessionId, stored: SessionRecord, at: Date): void => {\n\t\tbyId.set(id, { ...stored, revokedAt: new Date(at) });\n\t};\n\n\treturn {\n\t\tasync insertSession(record) {\n\t\t\tif (byId.has(record.id)) return;\n\n\t\t\tconst written = copy(record);\n\t\t\tbyId.set(written.id, written);\n\t\t\tbyTokenHash.set(written.tokenHash, written.id);\n\t\t},\n\n\t\tasync findSessionByTokenHash(tokenHash) {\n\t\t\tconst id = byTokenHash.get(tokenHash);\n\t\t\tconst stored = id === undefined ? undefined : byId.get(id);\n\t\t\treturn stored === undefined ? null : copy(stored);\n\t\t},\n\n\t\tasync extendSession(id, expiresAt) {\n\t\t\tconst stored = byId.get(id);\n\t\t\tif (stored === undefined || stored.revokedAt !== null) return null;\n\n\t\t\tconst written: SessionRecord = {\n\t\t\t\t...stored,\n\t\t\t\texpiresAt: new Date(expiresAt),\n\t\t\t};\n\t\t\tbyId.set(id, written);\n\t\t\treturn copy(written);\n\t\t},\n\n\t\tasync revokeSession(id, at) {\n\t\t\tconst stored = byId.get(id);\n\t\t\tif (stored === undefined) return false;\n\n\t\t\tif (stored.revokedAt === null) revoke(id, stored, at);\n\t\t\treturn true;\n\t\t},\n\n\t\tasync revokeUserSessions(userId, at, except) {\n\t\t\tlet revoked = 0;\n\n\t\t\tfor (const [id, stored] of byId) {\n\t\t\t\tif (\n\t\t\t\t\tstored.userId === userId &&\n\t\t\t\t\tstored.revokedAt === null &&\n\t\t\t\t\tid !== except\n\t\t\t\t) {\n\t\t\t\t\trevoke(id, stored, at);\n\t\t\t\t\trevoked += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn revoked;\n\t\t},\n\n\t\tasync deleteUserSessions(userId) {\n\t\t\tlet deleted = 0;\n\n\t\t\tfor (const [id, stored] of byId) {\n\t\t\t\tif (stored.userId === userId) {\n\t\t\t\t\tbyId.delete(id);\n\t\t\t\t\tbyTokenHash.delete(stored.tokenHash);\n\t\t\t\t\tdeleted += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn deleted;\n\t\t},\n\n\t\tasync deleteExpiredSessions(before) {\n\t\t\tlet deleted = 0;\n\n\t\t\tfor (const [id, stored] of byId) {\n\t\t\t\tif (stored.expiresAt.getTime() <= before.getTime()) {\n\t\t\t\t\tbyId.delete(id);\n\t\t\t\t\tbyTokenHash.delete(stored.tokenHash);\n\t\t\t\t\tdeleted += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n\nfunction memoryTokenStore(): TokenStore {\n\tconst byTokenHash = new Map<string, TokenRecord>();\n\n\treturn {\n\t\tasync insertToken(record) {\n\t\t\tif (byTokenHash.has(record.tokenHash)) return;\n\t\t\tbyTokenHash.set(record.tokenHash, copy(record));\n\t\t},\n\n\t\tasync consumeToken(tokenHash, kind, at) {\n\t\t\t// No `await` between this read and the write below: on one event loop\n\t\t\t// that is what makes the pair a single conditional write.\n\t\t\tconst stored = byTokenHash.get(tokenHash);\n\t\t\tif (stored === undefined || stored.kind !== kind) return null;\n\n\t\t\tconst before = copy(stored);\n\t\t\tif (stored.spentAt === null) {\n\t\t\t\tbyTokenHash.set(tokenHash, { ...stored, spentAt: new Date(at) });\n\t\t\t}\n\n\t\t\treturn before;\n\t\t},\n\n\t\tasync deleteUserTokens(userId) {\n\t\t\tlet deleted = 0;\n\n\t\t\tfor (const [tokenHash, stored] of byTokenHash) {\n\t\t\t\tif (stored.userId === userId) {\n\t\t\t\t\tbyTokenHash.delete(tokenHash);\n\t\t\t\t\tdeleted += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n"
6
+ "import { NotFoundError, StoreConflict } from '../../errors/janus-error';\nimport type { Id } from '../../ids/id';\nimport type {\n\tJanusStores,\n\tSessionId,\n\tSessionRecord,\n\tSessionStore,\n\tTokenRecord,\n\tTokenStore,\n\tUserPatch,\n\tUserRecord,\n\tUserStore,\n} from './types';\n\n/**\n * The reference implementation of the store port, in memory.\n *\n * **Shipped and documented, not a test helper.** It is what a consumer uses in\n * their own unit tests, and what an adapter author compares against when a\n * conformance case they do not understand turns red. So it keeps the six rules\n * for real rather than approximately:\n *\n * - uniqueness is a constraint — an index checked and written inside the same\n * synchronous step, which is what atomic means on one event loop;\n * - `consumeToken` reads and writes with no `await` between the two, so twenty\n * concurrent calls see exactly one unspent token;\n * - every record is **copied in and copied out**, so a caller that mutates what\n * it passed or what it got back cannot reach the store — the in-memory\n * version of \"bytes round-trip\";\n * - a patch applies only the fields it names, and only the fields the port\n * declares: a stray `version` or `id` in a patch from JavaScript is ignored,\n * not written.\n *\n * Every method is `async` even though none waits on anything: a caller that\n * forgot an `await` must fail here the way it would against a real database.\n */\nexport function createMemoryStores(): JanusStores {\n\treturn {\n\t\tusers: memoryUserStore(),\n\t\tsessions: memorySessionStore(),\n\t\ttokens: memoryTokenStore(),\n\t};\n}\n\nconst copy = <T>(value: T): T => structuredClone(value);\n\n/** The unique key of one login. `\\u0000` cannot occur in a type name the core accepts. */\nconst keyOf = (type: string, login: string): string => `${type}\\u0000${login}`;\n\nfunction memoryUserStore(): UserStore {\n\tconst byId = new Map<Id, UserRecord>();\n\t// The unique index: (type, login) key → the id holding it.\n\tconst byLogin = new Map<string, Id>();\n\n\t/** The first of `logins` held by a user of `type` other than `id`. */\n\tconst takenBy = (\n\t\ttype: string,\n\t\tlogins: readonly string[],\n\t\tid: Id,\n\t): string | undefined =>\n\t\tlogins.find((login) => {\n\t\t\tconst holder = byLogin.get(keyOf(type, login));\n\t\t\treturn holder !== undefined && holder !== id;\n\t\t});\n\n\tconst taken = (operation: string, type: string, login: string) =>\n\t\tnew StoreConflict(\n\t\t\t'login',\n\t\t\t`${operation}: the login is taken by another ${type}`,\n\t\t\t{ login, userType: type, operation },\n\t\t);\n\n\treturn {\n\t\tasync insertUser(record) {\n\t\t\tconst stored = byId.get(record.id);\n\t\t\tif (stored !== undefined) return copy(stored);\n\n\t\t\tconst collision = takenBy(record.type, record.logins, record.id);\n\t\t\tif (collision !== undefined) {\n\t\t\t\tthrow taken('insertUser', record.type, collision);\n\t\t\t}\n\n\t\t\tconst written = copy(record);\n\t\t\tbyId.set(written.id, written);\n\t\t\tfor (const login of written.logins) {\n\t\t\t\tbyLogin.set(keyOf(written.type, login), written.id);\n\t\t\t}\n\n\t\t\treturn copy(written);\n\t\t},\n\n\t\tasync findUser(id) {\n\t\t\tconst stored = byId.get(id);\n\t\t\treturn stored === undefined ? null : copy(stored);\n\t\t},\n\n\t\tasync findUserByLogin(type, login) {\n\t\t\tconst id = byLogin.get(keyOf(type, login));\n\t\t\tconst stored = id === undefined ? undefined : byId.get(id);\n\t\t\treturn stored === undefined ? null : copy(stored);\n\t\t},\n\n\t\tasync listUsers({ type, after, limit }) {\n\t\t\t// A Map keeps insertion order, not id order: a retried insert or an\n\t\t\t// id minted on another machine can land out of sequence.\n\t\t\tconst ids = [...byId.values()]\n\t\t\t\t.filter((user) => user.type === type)\n\t\t\t\t.map((user) => user.id)\n\t\t\t\t.filter((id) => after === null || id > after)\n\t\t\t\t.sort();\n\t\t\tconst pageIds = ids.slice(0, limit);\n\t\t\tconst last = pageIds.at(-1);\n\n\t\t\treturn {\n\t\t\t\titems: pageIds.map((id) => copy(byId.get(id) as UserRecord)),\n\t\t\t\tnextCursor: ids.length > limit && last !== undefined ? last : null,\n\t\t\t};\n\t\t},\n\n\t\tasync updateUser(id, patch, ifVersion) {\n\t\t\tconst stored = byId.get(id);\n\n\t\t\tif (stored === undefined) {\n\t\t\t\tthrow new NotFoundError('updateUser: no user has this id', {\n\t\t\t\t\tuserId: id,\n\t\t\t\t\toperation: 'updateUser',\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (stored.version !== ifVersion) {\n\t\t\t\tthrow new StoreConflict(\n\t\t\t\t\t'version',\n\t\t\t\t\t`updateUser: expected version ${ifVersion}, found ${stored.version}`,\n\t\t\t\t\t{\n\t\t\t\t\t\tuserId: id,\n\t\t\t\t\t\texpectedVersion: ifVersion,\n\t\t\t\t\t\tactualVersion: stored.version,\n\t\t\t\t\t\toperation: 'updateUser',\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (patch.logins !== undefined) {\n\t\t\t\tconst collision = takenBy(stored.type, patch.logins, id);\n\t\t\t\tif (collision !== undefined) {\n\t\t\t\t\tthrow taken('updateUser', stored.type, collision);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst written = applyPatch(stored, copy(patch));\n\n\t\t\tbyId.set(id, written);\n\t\t\tif (patch.logins !== undefined) {\n\t\t\t\tfor (const login of stored.logins) {\n\t\t\t\t\tbyLogin.delete(keyOf(stored.type, login));\n\t\t\t\t}\n\t\t\t\tfor (const login of written.logins) {\n\t\t\t\t\tbyLogin.set(keyOf(written.type, login), id);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn copy(written);\n\t\t},\n\n\t\tasync deleteUser(id) {\n\t\t\tconst stored = byId.get(id);\n\t\t\tif (stored === undefined) return false;\n\n\t\t\tbyId.delete(id);\n\t\t\tfor (const login of stored.logins) {\n\t\t\t\tbyLogin.delete(keyOf(stored.type, login));\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\t};\n}\n\n/**\n * The record a patch produces: the fields it names, replaced whole; the fields\n * it does not name, untouched.\n *\n * Reads each field by name rather than spreading the patch, so a key the port\n * does not declare — `version`, `id`, `type`, `createdAt`, or a `snake_case`\n * typo from JavaScript — never reaches the record. A key present as\n * `undefined` is absent, never an erasure.\n */\nfunction applyPatch(stored: UserRecord, patch: UserPatch): UserRecord {\n\treturn {\n\t\t...stored,\n\t\tschemaVersion: patch.schemaVersion ?? stored.schemaVersion,\n\t\tactive: patch.active ?? stored.active,\n\t\tfields: patch.fields ?? stored.fields,\n\t\tlogins: patch.logins ?? stored.logins,\n\t\tpassword: patch.password === undefined ? stored.password : patch.password,\n\t\tsecondFactor:\n\t\t\tpatch.secondFactor === undefined\n\t\t\t\t? stored.secondFactor\n\t\t\t\t: patch.secondFactor,\n\t\temailVerifiedAt:\n\t\t\tpatch.emailVerifiedAt === undefined\n\t\t\t\t? stored.emailVerifiedAt\n\t\t\t\t: patch.emailVerifiedAt,\n\t\tversion: stored.version + 1,\n\t\tupdatedAt: patch.updatedAt,\n\t};\n}\n\nfunction memorySessionStore(): SessionStore {\n\tconst byId = new Map<SessionId, SessionRecord>();\n\tconst byTokenHash = new Map<string, SessionId>();\n\n\tconst revoke = (id: SessionId, stored: SessionRecord, at: Date): void => {\n\t\tbyId.set(id, { ...stored, revokedAt: new Date(at) });\n\t};\n\n\treturn {\n\t\tasync insertSession(record) {\n\t\t\tif (byId.has(record.id)) return;\n\n\t\t\tconst written = copy(record);\n\t\t\tbyId.set(written.id, written);\n\t\t\tbyTokenHash.set(written.tokenHash, written.id);\n\t\t},\n\n\t\tasync findSessionByTokenHash(tokenHash) {\n\t\t\tconst id = byTokenHash.get(tokenHash);\n\t\t\tconst stored = id === undefined ? undefined : byId.get(id);\n\t\t\treturn stored === undefined ? null : copy(stored);\n\t\t},\n\n\t\tasync extendSession(id, expiresAt) {\n\t\t\tconst stored = byId.get(id);\n\t\t\tif (stored === undefined || stored.revokedAt !== null) return null;\n\n\t\t\tconst written: SessionRecord = {\n\t\t\t\t...stored,\n\t\t\t\texpiresAt: new Date(expiresAt),\n\t\t\t};\n\t\t\tbyId.set(id, written);\n\t\t\treturn copy(written);\n\t\t},\n\n\t\tasync revokeSession(id, at) {\n\t\t\tconst stored = byId.get(id);\n\t\t\tif (stored === undefined) return false;\n\n\t\t\tif (stored.revokedAt === null) revoke(id, stored, at);\n\t\t\treturn true;\n\t\t},\n\n\t\tasync revokeUserSessions(userId, at, except) {\n\t\t\tlet revoked = 0;\n\n\t\t\tfor (const [id, stored] of byId) {\n\t\t\t\tif (\n\t\t\t\t\tstored.userId === userId &&\n\t\t\t\t\tstored.revokedAt === null &&\n\t\t\t\t\tid !== except\n\t\t\t\t) {\n\t\t\t\t\trevoke(id, stored, at);\n\t\t\t\t\trevoked += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn revoked;\n\t\t},\n\n\t\tasync deleteUserSessions(userId) {\n\t\t\tlet deleted = 0;\n\n\t\t\tfor (const [id, stored] of byId) {\n\t\t\t\tif (stored.userId === userId) {\n\t\t\t\t\tbyId.delete(id);\n\t\t\t\t\tbyTokenHash.delete(stored.tokenHash);\n\t\t\t\t\tdeleted += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn deleted;\n\t\t},\n\n\t\tasync deleteExpiredSessions(before) {\n\t\t\tlet deleted = 0;\n\n\t\t\tfor (const [id, stored] of byId) {\n\t\t\t\tif (stored.expiresAt.getTime() <= before.getTime()) {\n\t\t\t\t\tbyId.delete(id);\n\t\t\t\t\tbyTokenHash.delete(stored.tokenHash);\n\t\t\t\t\tdeleted += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n\nfunction memoryTokenStore(): TokenStore {\n\tconst byTokenHash = new Map<string, TokenRecord>();\n\n\treturn {\n\t\tasync insertToken(record) {\n\t\t\tif (byTokenHash.has(record.tokenHash)) return;\n\t\t\tbyTokenHash.set(record.tokenHash, copy(record));\n\t\t},\n\n\t\tasync consumeToken(tokenHash, kind, at) {\n\t\t\t// No `await` between this read and the write below: on one event loop\n\t\t\t// that is what makes the pair a single conditional write.\n\t\t\tconst stored = byTokenHash.get(tokenHash);\n\t\t\tif (stored === undefined || stored.kind !== kind) return null;\n\n\t\t\tconst before = copy(stored);\n\t\t\tif (stored.spentAt === null) {\n\t\t\t\tbyTokenHash.set(tokenHash, { ...stored, spentAt: new Date(at) });\n\t\t\t}\n\n\t\t\treturn before;\n\t\t},\n\n\t\tasync countAttempt(tokenHash, kind) {\n\t\t\t// The same single conditional write as consumeToken: no `await`\n\t\t\t// between the read and the write.\n\t\t\tconst stored = byTokenHash.get(tokenHash);\n\t\t\tif (stored === undefined || stored.kind !== kind) return null;\n\t\t\tif (stored.spentAt !== null) return copy(stored);\n\n\t\t\tconst counted = { ...stored, attempts: stored.attempts + 1 };\n\t\t\tbyTokenHash.set(tokenHash, counted);\n\t\t\treturn copy(counted);\n\t\t},\n\n\t\tasync deleteUserTokens(userId) {\n\t\t\tlet deleted = 0;\n\n\t\t\tfor (const [tokenHash, stored] of byTokenHash) {\n\t\t\t\tif (stored.userId === userId) {\n\t\t\t\t\tbyTokenHash.delete(tokenHash);\n\t\t\t\t\tdeleted += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "609E38330FBCD6ED64756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "A6E6F8D3147D2C5864756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"outage.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/outage.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAe,eAAe,EAAc,MAAM,UAAU,CAAC;AA4DzE,eAAO,MAAM,WAAW,EAAE,SAAS,eAAe,EAuEjD,CAAC"}
1
+ {"version":3,"file":"outage.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/outage.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAe,eAAe,EAAc,MAAM,UAAU,CAAC;AA4DzE,eAAO,MAAM,WAAW,EAAE,SAAS,eAAe,EA8EjD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/tokens.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,eAAO,MAAM,eAAe,EAAE,SAAS,eAAe,EAgMrD,CAAC"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/tokens.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,eAAO,MAAM,eAAe,EAAE,SAAS,eAAe,EAoWrD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/users.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,eAAO,MAAM,cAAc,EAAE,SAAS,eAAe,EA8dpD,CAAC"}
1
+ {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/users.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAYhD,eAAO,MAAM,cAAc,EAAE,SAAS,eAAe,EA8hBpD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../src/conformance/fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,aAAa,EACb,WAAW,EACX,UAAU,EACV,MAAM,oBAAoB,CAAC;AAG5B;;;;GAIG;AAEH,eAAO,MAAM,EAAE,GAAI,KAAK,MAAM,KAAG,IAAqB,CAAC;AAKvD,wBAAgB,UAAU,CAAC,SAAS,GAAE,OAAO,CAAC,UAAU,CAAM,GAAG,UAAU,CAmB1E;AAED,wBAAgB,aAAa,CAC5B,SAAS,GAAE,OAAO,CAAC,aAAa,CAAM,GACpC,aAAa,CAWf;AAED,wBAAgB,WAAW,CAAC,SAAS,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAW7E"}
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../src/conformance/fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,aAAa,EACb,WAAW,EACX,UAAU,EACV,MAAM,oBAAoB,CAAC;AAG5B;;;;GAIG;AAEH,eAAO,MAAM,EAAE,GAAI,KAAK,MAAM,KAAG,IAAqB,CAAC;AAKvD,wBAAgB,UAAU,CAAC,SAAS,GAAE,OAAO,CAAC,UAAU,CAAM,GAAG,UAAU,CAoB1E;AAED,wBAAgB,aAAa,CAC5B,SAAS,GAAE,OAAO,CAAC,aAAa,CAAM,GACpC,aAAa,CAWf;AAED,wBAAgB,WAAW,CAAC,SAAS,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAa7E"}
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  mintId2,
3
3
  createMemoryStores2
4
- } from "../chunks/index-8tksqzkr.js";
4
+ } from "../chunks/index-gbwn2tts.js";
5
5
  import {
6
6
  JanusError2,
7
7
  StoreFailure2,
8
8
  StoreConflict2,
9
9
  NotFoundError2,
10
10
  formatSubject2
11
- } from "../chunks/index-qwfkhqkk.js";
11
+ } from "../chunks/index-06vp9c5r.js";
12
12
  import {
13
13
  createMemoryRelations2
14
- } from "../chunks/index-mgh85djb.js";
14
+ } from "../chunks/index-c4v27jfr.js";
15
15
 
16
16
  // src/conformance/assert.ts
17
17
  function equal(actual, expected, what) {
@@ -113,6 +113,7 @@ function userRecord(overrides = {}) {
113
113
  hash: "$scrypt$ln=17,r=8,p=1$c2FsdA==$a2V5",
114
114
  updatedAt: at("2026-01-01T00:00:00.000Z")
115
115
  },
116
+ secondFactor: null,
116
117
  emailVerifiedAt: null,
117
118
  version: 0,
118
119
  createdAt: at("2026-01-01T00:00:00.000Z"),
@@ -138,6 +139,8 @@ function tokenRecord(overrides = {}) {
138
139
  kind: "resetPassword",
139
140
  userId: mintId2(),
140
141
  address: fresh(),
142
+ codeHash: null,
143
+ attempts: 0,
141
144
  expiresAt: at("2099-01-01T00:00:00.000Z"),
142
145
  spentAt: null,
143
146
  createdAt: at("2026-01-01T00:00:00.000Z"),
@@ -184,6 +187,7 @@ var outageCases = [
184
187
  outage("sessions", "revokeSession", ({ stores }) => stores.sessions.revokeSession(session.id, now), seed),
185
188
  outage("sessions", "revokeUserSessions", ({ stores }) => stores.sessions.revokeUserSessions(user.id, now), seed),
186
189
  outage("tokens", "consumeToken", ({ stores }) => stores.tokens.consumeToken(token.tokenHash, "resetPassword", now), seed),
190
+ outage("tokens", "countAttempt", ({ stores }) => stores.tokens.countAttempt(token.tokenHash, "resetPassword"), seed),
187
191
  outage("users", "deleteUser", ({ stores }) => stores.users.deleteUser(user.id), seed),
188
192
  outage("sessions", "deleteUserSessions", ({ stores }) => stores.sessions.deleteUserSessions(user.id), seed),
189
193
  outage("tokens", "deleteUserTokens", ({ stores }) => stores.tokens.deleteUserTokens(user.id), seed)
@@ -378,6 +382,87 @@ var tokenStoreCases = [
378
382
  isNull(await stores.tokens.consumeToken("0".repeat(64), "resetPassword", at("2026-02-01T00:00:00.000Z")), "consumeToken for an unknown hash");
379
383
  }
380
384
  },
385
+ {
386
+ id: "tokens.countAttempt",
387
+ group: group2,
388
+ name: "counts an attempt at a code, and answers the token as it is after the call",
389
+ async run({ stores }) {
390
+ const record = tokenRecord({
391
+ kind: "signInCode",
392
+ codeHash: "c".repeat(64)
393
+ });
394
+ await stores.tokens.insertToken(record);
395
+ equal(await stores.tokens.countAttempt(record.tokenHash, "signInCode"), { ...record, attempts: 1 }, "countAttempt the first time should answer the token with attempts 1, codeHash as written");
396
+ equal(await stores.tokens.countAttempt(record.tokenHash, "signInCode"), { ...record, attempts: 2 }, "countAttempt the second time should answer attempts 2");
397
+ equal(await stores.tokens.consumeToken(record.tokenHash, "signInCode", at("2026-02-01T00:00:00.000Z")), { ...record, attempts: 2 }, "consumeToken should answer the attempts counted, and spend the token");
398
+ }
399
+ },
400
+ {
401
+ id: "tokens.countAttemptConcurrency",
402
+ group: group2,
403
+ name: "gives twenty concurrent attempts twenty distinct counts — one conditional write, never a read then a write",
404
+ async run({ stores }) {
405
+ const record = tokenRecord({ kind: "secondFactor" });
406
+ await stores.tokens.insertToken(record);
407
+ const answers = await Promise.all(Array.from({ length: 20 }, () => stores.tokens.countAttempt(record.tokenHash, "secondFactor")));
408
+ equal(answers.map((answer) => answer?.attempts).sort((a, b) => (a ?? 0) - (b ?? 0)), Array.from({ length: 20 }, (_, index) => index + 1), "countAttempt: twenty concurrent calls should answer 1 to 20, each once — two guesses reading one count is a guess for free");
409
+ }
410
+ },
411
+ {
412
+ id: "tokens.countAttemptRace",
413
+ group: group2,
414
+ name: "never counts an attempt once the token is spent, when attempts and a redemption race",
415
+ async run({ stores }) {
416
+ const record = tokenRecord({ kind: "signInCode" });
417
+ await stores.tokens.insertToken(record);
418
+ const now = at("2026-02-01T00:00:00.000Z");
419
+ const count = () => stores.tokens.countAttempt(record.tokenHash, "signInCode");
420
+ const answers = await Promise.all([
421
+ ...Array.from({ length: 10 }, count),
422
+ stores.tokens.consumeToken(record.tokenHash, "signInCode", now),
423
+ ...Array.from({ length: 10 }, count)
424
+ ]);
425
+ const final = await stores.tokens.consumeToken(record.tokenHash, "signInCode", now);
426
+ if (final === null) {
427
+ throw new Error("consumeToken after the race should answer the token");
428
+ }
429
+ const counted = answers.slice(0, 10).concat(answers.slice(11)).filter((answer) => answer !== null && answer.spentAt === null).map((answer) => answer?.attempts ?? 0).sort((a, b) => a - b);
430
+ equal(counted, Array.from({ length: final.attempts }, (_, index) => index + 1), "countAttempt: the attempts answered unspent should be 1 to the final count, each once");
431
+ equal(answers.filter((answer) => answer !== null && answer.spentAt !== null).every((answer) => answer?.attempts === final.attempts), true, "countAttempt: every answer after the spend should carry the final count — an attempt counted on a spent token is one the limit never saw");
432
+ }
433
+ },
434
+ {
435
+ id: "tokens.countAttemptSpent",
436
+ group: group2,
437
+ name: "does not count an attempt at a spent token, nor at a token of another kind, nor at none",
438
+ async run({ stores }) {
439
+ const spent = tokenRecord({
440
+ kind: "signInCode",
441
+ attempts: 3,
442
+ spentAt: at("2026-01-02T00:00:00.000Z")
443
+ });
444
+ const other = tokenRecord({ kind: "resetPassword" });
445
+ await stores.tokens.insertToken(spent);
446
+ await stores.tokens.insertToken(other);
447
+ for (const call of ["first", "second"]) {
448
+ equal(await stores.tokens.countAttempt(spent.tokenHash, "signInCode"), spent, `countAttempt on a spent token, the ${call} time, should answer it as it is: attempts still 3, nothing written`);
449
+ }
450
+ isNull(await stores.tokens.countAttempt(other.tokenHash, "signInCode"), "countAttempt for a resetPassword token counted as signInCode");
451
+ equal(await stores.tokens.consumeToken(other.tokenHash, "resetPassword", at("2026-02-01T00:00:00.000Z")), other, "countAttempt of the other kind should not have counted: attempts still 0");
452
+ isNull(await stores.tokens.countAttempt("0".repeat(64), "signInCode"), "countAttempt for an unknown hash");
453
+ }
454
+ },
455
+ {
456
+ id: "tokens.challenge",
457
+ group: group2,
458
+ name: "keeps a second-factor challenge, whose address is '' — nothing was sent for it",
459
+ async run({ stores }) {
460
+ const record = tokenRecord({ kind: "secondFactor", address: "" });
461
+ await stores.tokens.insertToken(record);
462
+ equal(await stores.tokens.countAttempt(record.tokenHash, "secondFactor"), { ...record, attempts: 1 }, "countAttempt should answer a challenge with its empty address kept — a store that refuses '' refuses every sign-in with a second factor");
463
+ equal(await stores.tokens.consumeToken(record.tokenHash, "secondFactor", at("2026-02-01T00:00:00.000Z")), { ...record, attempts: 1 }, "consumeToken should spend the challenge and answer it as it was");
464
+ }
465
+ },
381
466
  {
382
467
  id: "tokens.lapsed",
383
468
  group: group2,
@@ -434,6 +519,12 @@ var tokenStoreCases = [
434
519
  ];
435
520
  // src/conformance/cases/users.ts
436
521
  var group3 = "users";
522
+ var secondFactor = {
523
+ method: "totp",
524
+ secret: "v1.2026-09.aXY.Y2lwaGVydGV4dA",
525
+ confirmedAt: null,
526
+ lastStep: null
527
+ };
437
528
  var userStoreCases = [
438
529
  {
439
530
  id: "users.roundTrip",
@@ -594,6 +685,11 @@ var userStoreCases = [
594
685
  async run({ stores }) {
595
686
  const record = userRecord({
596
687
  fields: { email: "kept@example.test", plan: "pro" },
688
+ secondFactor: {
689
+ ...secondFactor,
690
+ confirmedAt: at("2026-01-03T00:00:00.000Z"),
691
+ lastStep: 59000000
692
+ },
597
693
  emailVerifiedAt: at("2026-01-02T00:00:00.000Z")
598
694
  });
599
695
  await stores.users.insertUser(record);
@@ -603,7 +699,7 @@ var userStoreCases = [
603
699
  schemaVersion: "2",
604
700
  version: 1,
605
701
  updatedAt: at("2026-02-01T00:00:00.000Z")
606
- }, "updateUser naming only schemaVersion should leave active, fields, logins, password and emailVerifiedAt untouched");
702
+ }, "updateUser naming only schemaVersion should leave active, fields, logins, password, secondFactor and emailVerifiedAt untouched");
607
703
  }
608
704
  },
609
705
  {
@@ -619,6 +715,27 @@ var userStoreCases = [
619
715
  isNull(removed.password, "updateUser with password: null should remove the password");
620
716
  }
621
717
  },
718
+ {
719
+ id: "users.secondFactorSlot",
720
+ group: group3,
721
+ name: "round-trips a second factor, keeps it when a patch does not name it, and removes it when the patch names null",
722
+ async run({ stores }) {
723
+ const record = userRecord({ secondFactor });
724
+ equal(await stores.users.insertUser(record), record, "insertUser should answer a pending second factor as written: confirmedAt and lastStep null");
725
+ const confirmed = {
726
+ ...secondFactor,
727
+ confirmedAt: at("2026-02-01T00:00:00.000Z"),
728
+ lastStep: 59000000
729
+ };
730
+ equal((await stores.users.updateUser(record.id, {
731
+ updatedAt: at("2026-02-01T00:00:00.000Z"),
732
+ secondFactor: confirmed
733
+ }, 0)).secondFactor, confirmed, "updateUser naming secondFactor should replace it whole");
734
+ equal((await stores.users.updateUser(record.id, { updatedAt: at("2026-02-02T00:00:00.000Z") }, 1)).secondFactor, confirmed, "updateUser not naming secondFactor should keep it");
735
+ equal((await stores.users.findUser(record.id))?.secondFactor, confirmed, "findUser should answer the second factor as written");
736
+ isNull((await stores.users.updateUser(record.id, { updatedAt: at("2026-02-03T00:00:00.000Z"), secondFactor: null }, 2)).secondFactor, "updateUser with secondFactor: null should remove the second factor");
737
+ }
738
+ },
622
739
  {
623
740
  id: "users.loginsMove",
624
741
  group: group3,
@@ -1132,5 +1249,5 @@ export {
1132
1249
  userStoreCases
1133
1250
  };
1134
1251
 
1135
- //# debugId=A6164C95D20DC72764756E2164756E21
1252
+ //# debugId=A1B6C7BA2F6BC85464756E2164756E21
1136
1253
  //# sourceMappingURL=index.js.map