@nxgt/janus 0.3.0 → 0.4.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.
- package/README.md +40 -4
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/port/types.d.ts +70 -2
- package/dist/auth/port/types.d.ts.map +1 -1
- package/dist/auth/users.d.ts.map +1 -1
- package/dist/chunks/{index-8tksqzkr.js → index-thtyq7a9.js} +13 -2
- package/dist/chunks/{index-8tksqzkr.js.map → index-thtyq7a9.js.map} +3 -3
- package/dist/conformance/cases/outage.d.ts.map +1 -1
- package/dist/conformance/cases/tokens.d.ts.map +1 -1
- package/dist/conformance/cases/users.d.ts.map +1 -1
- package/dist/conformance/fixtures.d.ts.map +1 -1
- package/dist/conformance/index.js +109 -3
- package/dist/conformance/index.js.map +6 -6
- package/dist/index.js +6 -3
- package/dist/index.js.map +4 -4
- package/docs/README.md +1 -1
- package/docs/guide/adapters.md +124 -3
- package/docs/guide/vocabulary.md +3 -1
- package/docs/roadmap.md +14 -5
- package/docs/troubleshooting.md +16 -0
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
"/**\n * What every store can keep. PostgreSQL refuses `\\u0000` in `text` and\n * `jsonb`, and a lone surrogate in `jsonb`; MongoDB and the memory store keep\n * both. The core refuses them before a store is asked, so a request that sends\n * one is answered the same way on every adapter — not `STORE_FAILED` on one of\n * them, a 503 for a database that is up.\n *\n * Shared by both sides of the package, like `guard.ts`: a string from a\n * request reaches the identity stores and the relation store alike.\n */\n\n/** Whether every store can keep this string. */\nexport const isStorable = (value: string): boolean =>\n\t!value.includes('\\u0000') && value.isWellFormed();\n\n/** What an issue says about a string no store can keep. */\nexport const UNSTORABLE =\n\t'holds a NUL character or a lone surrogate, which no store can keep';\n",
|
|
10
10
|
"/**\n * **The identity side's two conversions**, and the one place a `null` becomes\n * an error.\n *\n * > An absence is `null`. A failure throws.\n *\n * {@link guardStores} puts the three identity stores behind the shared guard\n * (`src/stores/guard.ts`), the one place a store's answer is caught.\n * {@link required} turns an absence the caller cannot accept into its refusal,\n * and {@link unlessVersionConflict} absorbs one named conflict and rethrows\n * everything else.\n *\n * `outage.spec.ts` reads every other file of `src/auth/`, `src/permissions/`\n * and `src/stores/` and fails if a `catch` appears in one, because the failure\n * this design exists to prevent is a single careless `catch { return null }`.\n */\n\nimport { type JanusError, StoreConflict } from '../errors/janus-error';\nimport { guardStore } from '../stores/guard';\nimport type { JanusStores } from './port/types';\n\n/**\n * The stores, with every method guarded.\n *\n * - A `JanusError` the adapter threw — `StoreFailure`, `StoreConflict`,\n * `NotFoundError` — passes through untouched, so `instanceof` still holds.\n * - **Anything else it threw is a failure**: a driver error, a `TypeError` from\n * a bug in the adapter, a string. It becomes `StoreFailure` with the original\n * as `cause`, naming the slot and the method. It is never an absence.\n * - A method that answers `undefined` where the port says `null` is a store\n * that forgot to answer, and becomes `StoreFailure` rather than \"not found\".\n * Rule 2 of the port, enforced at run time for the JavaScript adapter the\n * compiler never saw.\n *\n * The optional `deleteExpiredSessions` is guarded when present and left absent\n * when absent, so capability detection still reads the truth.\n */\nexport function guardStores(stores: JanusStores): JanusStores {\n\treturn {\n\t\tusers: guardStore('users', stores.users),\n\t\tsessions: guardStore('sessions', stores.sessions),\n\t\ttokens: guardStore('tokens', stores.tokens),\n\t};\n}\n\n/**\n * A value the caller requires, or the refusal an absence deserves.\n *\n * The only place in the core where a `null` from a store turns into an error —\n * `get` becoming `NOT_FOUND`. Written as a function so that the conversion is\n * visible at every call site and nowhere else.\n */\nexport function required<T>(value: T | null, absent: () => JanusError): T {\n\tif (value === null) throw absent();\n\treturn value;\n}\n\n/**\n * A write that may lose a race, and is allowed to: its answer, or `null` when\n * the record's version moved under it.\n *\n * **Only `StoreConflict('version')` is absorbed.** A failure still throws, a\n * taken login still throws, `NOT_FOUND` still throws: an outage never reads as\n * \"somebody else wrote first\". It exists for writes the caller did not ask for\n * — rewriting a password hash on sign-in — where losing to a concurrent update\n * means only that the next sign-in tries again.\n */\nexport async function unlessVersionConflict<T>(\n\twrite: Promise<T>,\n): Promise<T | null> {\n\ttry {\n\t\treturn await write;\n\t} catch (error) {\n\t\tif (error instanceof StoreConflict && error.on === 'version') return null;\n\t\tthrow error;\n\t}\n}\n",
|
|
11
11
|
"/**\n * What every operation of the core shares: the resolved configuration, the\n * guarded stores, the clock, the hashers — and the handful of steps every\n * operation takes the same way.\n *\n * Internal and degenericised: fields are a `JsonObject` here, and `janus()`\n * casts once, at the boundary, after the schema has validated them.\n */\n\nimport {\n\tCredentialError,\n\ttype Issue,\n\tNotFoundError,\n\tStoreConflict,\n\tUserInvalidError,\n} from '../errors/janus-error';\nimport { isId, mintId } from '../ids/id';\nimport type { RelationStore } from '../permissions/port/types';\nimport { isStorable, UNSTORABLE } from '../stores/storable';\nimport type { Clock } from '../time/clock';\nimport {\n\tnormalizeEmail,\n\ttype PasswordHasher,\n\tRESERVED_FIELDS,\n\ttype ResolvedConfig,\n\ttype ResolvedType,\n} from './config';\nimport { required, unlessVersionConflict } from './outage';\nimport type {\n\tJanusStores,\n\tJson,\n\tJsonObject,\n\tStoreCapabilities,\n\tUserPatch,\n\tUserRecord,\n} from './port/types';\nimport type { StandardSchemaV1 } from './standard-schema';\nimport type { User, UserRef, WriteOptions } from './types';\n\nexport interface Context {\n\treadonly config: ResolvedConfig;\n\t/** Already guarded, by `src/stores/guard.ts`. */\n\treadonly store: JanusStores;\n\t/** Already guarded. `null` when no relation store is wired. */\n\treadonly relations: RelationStore | null;\n\treadonly capabilities: StoreCapabilities;\n\treadonly clock: Clock;\n\treadonly hasher: PasswordHasher | null;\n\t/** The hasher first, then every verifier: whoever claims a prefix verifies it. */\n\treadonly verifiers: readonly PasswordHasher[];\n\t/** Hashed once, lazily: what a missing user's password is compared against. */\n\tdummyHash(): Promise<string>;\n}\n\nexport function createContext(\n\tconfig: ResolvedConfig,\n\tstore: JanusStores,\n\trelations: RelationStore | null,\n\tcapabilities: StoreCapabilities,\n\tclock: Clock,\n\thasher: PasswordHasher | null,\n\tverifiers: readonly PasswordHasher[],\n): Context {\n\tlet dummy: Promise<string> | null = null;\n\n\treturn {\n\t\tconfig,\n\t\tstore,\n\t\trelations,\n\t\tcapabilities,\n\t\tclock,\n\t\thasher,\n\t\tverifiers,\n\t\tdummyHash: () => {\n\t\t\tif (hasher === null) {\n\t\t\t\tthrow new TypeError('janus: no hasher to compare a dummy hash with');\n\t\t\t}\n\t\t\tdummy ??= hasher.hash(`janus-dummy-${mintId()}`);\n\t\t\treturn dummy;\n\t\t},\n\t};\n}\n\n/** A user of any type, as the degenericised core handles them. */\nexport type AnyUser = User<string, Record<string, unknown>>;\n\n/**\n * A user as application code sees it: their fields at the top level, then\n * what `janus` sets — written last, so no stored key can shadow it. No password\n * hash, ever.\n */\nexport function toUser(record: UserRecord): AnyUser {\n\treturn {\n\t\t...record.fields,\n\t\tid: record.id,\n\t\ttype: record.type,\n\t\temailVerified: record.emailVerifiedAt !== null,\n\t\tactive: record.active,\n\t\thasPassword: record.password !== null,\n\t\tversion: record.version,\n\t\tcreatedAt: record.createdAt,\n\t\tupdatedAt: record.updatedAt,\n\t};\n}\n\n/** The id a {@link UserRef} names. */\nexport const idOf = (user: UserRef): string =>\n\ttypeof user === 'string' ? user : user.id;\n\n/**\n * Validates fields against the type's schema, and answers them as the port\n * holds them: an `undefined` property dropped, at every depth.\n */\nexport async function validateFields(\n\ttype: ResolvedType,\n\tinput: unknown,\n\twhere: string,\n): Promise<JsonObject> {\n\tconst result = await type.schema['~standard'].validate(input);\n\n\tconst issues: Issue[] =\n\t\tresult.issues === undefined\n\t\t\t? []\n\t\t\t: result.issues.map((issue) => ({\n\t\t\t\t\tpath: storablePrefix((issue.path ?? []).map(segmentKey)),\n\t\t\t\t\tmessage: issue.message,\n\t\t\t\t}));\n\n\t// A schema that passes unknown keys through would let a request set `id` or\n\t// `active` among the fields. `toUser` would shadow them anyway; refusing\n\t// them says so to whoever sent them.\n\tif (result.issues === undefined) {\n\t\tconst value = result.value as Record<string, unknown>;\n\t\tfor (const key of RESERVED_FIELDS) {\n\t\t\tif (Object.hasOwn(value, key)) {\n\t\t\t\tissues.push({ path: [key], message: 'set by janus, not by a request' });\n\t\t\t}\n\t\t}\n\t\tunstorableIn(value, [], issues);\n\t}\n\n\tif (issues.length > 0) {\n\t\t// The message reports how many and where, never the values: a field may\n\t\t// be anything the application chose to store, including something\n\t\t// private.\n\t\tthrow new UserInvalidError(\n\t\t\t`${where}: the fields do not match the ${type.name} schema (${issues.length} issue${issues.length === 1 ? '' : 's'}, at ${issues.map((i) => i.path.join('.') || '(root)').join(', ')})`,\n\t\t\t{ issues, userType: type.name },\n\t\t);\n\t}\n\n\treturn withoutUndefined((result as { value: unknown }).value) as JsonObject;\n}\n\n/** Pushes an issue for every key and every string no store can keep. */\nfunction unstorableIn(\n\tvalue: unknown,\n\tpath: (string | number)[],\n\tissues: Issue[],\n): void {\n\tif (typeof value === 'string') {\n\t\tif (!isStorable(value)) issues.push({ path, message: UNSTORABLE });\n\t\treturn;\n\t}\n\tif (Array.isArray(value)) {\n\t\tfor (const [index, inner] of value.entries()) {\n\t\t\tunstorableIn(inner, [...path, index], issues);\n\t\t}\n\t\treturn;\n\t}\n\tif (typeof value === 'object' && value !== null) {\n\t\tfor (const [key, inner] of Object.entries(value)) {\n\t\t\t// The key itself stays out of the path: the path reaches the message,\n\t\t\t// and a NUL has no business in a log line.\n\t\t\tif (!isStorable(key)) {\n\t\t\t\tissues.push({ path, message: `a key ${UNSTORABLE}` });\n\t\t\t} else {\n\t\t\t\tunstorableIn(inner, [...path, key], issues);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * A path up to its first key no store can keep: the path reaches the message,\n * and a NUL has no business in a log line.\n */\nfunction storablePrefix(path: (string | number)[]): (string | number)[] {\n\tconst cut = path.findIndex(\n\t\t(segment) => typeof segment === 'string' && !isStorable(segment),\n\t);\n\treturn cut === -1 ? path : path.slice(0, cut);\n}\n\nfunction segmentKey(\n\tsegment: PropertyKey | StandardSchemaV1.PathSegment,\n): string | number {\n\tconst key = typeof segment === 'object' ? segment.key : segment;\n\treturn typeof key === 'number' ? key : String(key);\n}\n\nfunction withoutUndefined(value: unknown): Json {\n\tif (Array.isArray(value)) return value.map(withoutUndefined);\n\n\tif (typeof value === 'object' && value !== null) {\n\t\tconst out: Record<string, Json> = {};\n\t\tfor (const [key, inner] of Object.entries(value)) {\n\t\t\tif (inner !== undefined) out[key] = withoutUndefined(inner);\n\t\t}\n\t\treturn out;\n\t}\n\n\treturn value as Json;\n}\n\n/** The user's e-mail, as stored, or `null` when the schema let it be absent. */\nexport function emailOf(type: ResolvedType, fields: JsonObject): string | null {\n\tconst value = fields[type.email];\n\treturn typeof value === 'string' ? value : null;\n}\n\n/**\n * What a user signs in with, normalised: the login field, by its own rule,\n * and the e-mail, by the e-mail rule — so a user whose login is a username can\n * still be found by the e-mail a reset is requested for.\n */\nexport function loginsOf(\n\ttype: ResolvedType,\n\tfields: JsonObject,\n\twhere: string,\n): string[] {\n\tconst logins = new Set<string>();\n\n\tif (type.password !== null) {\n\t\tconst login = fields[type.password.login];\n\t\tif (typeof login !== 'string') {\n\t\t\tthrow new TypeError(\n\t\t\t\t`janus: password.login \"${type.password.login}\" did not name a string in validated ${type.name} fields — it must name a required string field`,\n\t\t\t);\n\t\t}\n\t\tconst normalized = type.password.normalize(login);\n\t\t// The fields were checked; a function normaliser can still cut a\n\t\t// surrogate pair in half. Refused here, or the user is written with a\n\t\t// login nobody can sign in with — or not written at all, on PostgreSQL.\n\t\tif (!isStorable(normalized)) {\n\t\t\tconst path = [type.password.login];\n\t\t\tthrow new UserInvalidError(\n\t\t\t\t`${where}: the fields do not match the ${type.name} schema (1 issue, at ${path.join('.')})`,\n\t\t\t\t{\n\t\t\t\t\tissues: [\n\t\t\t\t\t\t{ path, message: `normalises to a login that ${UNSTORABLE}` },\n\t\t\t\t\t],\n\t\t\t\t\tuserType: type.name,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tlogins.add(normalized);\n\t}\n\n\tconst email = emailOf(type, fields);\n\tif (email !== null) logins.add(normalizeEmail(email));\n\n\treturn [...logins];\n}\n\n/** Refuses a password shorter than the policy. Reports the policy, never the password. */\nexport function checkPassword(\n\ttype: ResolvedType,\n\tpassword: string,\n\twhere: string,\n): void {\n\tconst minLength = type.password?.minLength ?? 8;\n\tif (typeof password !== 'string' || password.length < minLength) {\n\t\tthrow new CredentialError(\n\t\t\t'PASSWORD_TOO_SHORT',\n\t\t\t`${where}: the password is shorter than the policy's ${minLength} characters`,\n\t\t\t{ minLength, userType: type.name },\n\t\t);\n\t}\n}\n\n/** The hasher. `janus()` refused a password type without one, so this is a wiring net. */\nexport function requireHasher(context: Context, where: string): PasswordHasher {\n\tif (context.hasher === null) {\n\t\tthrow new TypeError(\n\t\t\t`${where}: no password hasher is wired — pass hasher: scryptHasher(), or bunHasher() on Bun`,\n\t\t);\n\t}\n\treturn context.hasher;\n}\n\n/**\n * Whether `password` matches the stored hash. A hash whose prefix no wired\n * verifier claims is `HASH_UNSUPPORTED`, reporting the prefix, never the hash.\n */\nexport async function passwordMatches(\n\tcontext: Context,\n\trecord: UserRecord,\n\tpassword: string,\n\twhere: string,\n): Promise<boolean> {\n\tconst stored = record.password;\n\tif (stored === null) return false;\n\n\tconst verifier = context.verifiers.find((candidate) =>\n\t\tstored.hash.startsWith(candidate.prefix),\n\t);\n\tif (verifier === undefined) {\n\t\tconst hashPrefix = /^\\$[^$]*\\$/.exec(stored.hash)?.[0] ?? '(none)';\n\t\tthrow new CredentialError(\n\t\t\t'HASH_UNSUPPORTED',\n\t\t\t`${where}: no wired verifier claims the prefix \"${hashPrefix}\"`,\n\t\t\t{ hashPrefix, userId: record.id, userType: record.type },\n\t\t);\n\t}\n\n\treturn verifier.verify(password, stored.hash);\n}\n\n/**\n * The record, with its password hash rewritten by the current hasher when the\n * stored one is stale: written by another hasher — a `verifiers` one — or by\n * this one with other parameters. Called only once `password` has matched.\n *\n * The rewrite is conditioned on the version just read. Losing that race to a\n * concurrent update is not the sign-in's failure: the record is answered as\n * read, and the next sign-in tries again. A store failure still throws.\n */\nexport async function rehashed(\n\tcontext: Context,\n\trecord: UserRecord,\n\tpassword: string,\n): Promise<UserRecord> {\n\tconst { hasher } = context;\n\tconst stored = record.password;\n\tif (hasher === null || stored === null) return record;\n\n\tconst stale =\n\t\t!stored.hash.startsWith(hasher.prefix) ||\n\t\thasher.needsRehash?.(stored.hash) === true;\n\tif (!stale) return record;\n\n\tconst written = await unlessVersionConflict(\n\t\tcontext.store.users.updateUser(\n\t\t\trecord.id,\n\t\t\t{\n\t\t\t\tupdatedAt: context.clock.now(),\n\t\t\t\t// The same password, so the same `updatedAt`: when it was *set*.\n\t\t\t\tpassword: {\n\t\t\t\t\thash: await hasher.hash(password),\n\t\t\t\t\tupdatedAt: stored.updatedAt,\n\t\t\t\t},\n\t\t\t},\n\t\t\trecord.version,\n\t\t),\n\t);\n\treturn written ?? record;\n}\n\nconst notFound = (where: string, id: string, type: string | null) =>\n\tnew NotFoundError(\n\t\t`${where}: no ${type ?? 'user'} has this id`,\n\t\ttype === null\n\t\t\t? { userId: id, operation: where }\n\t\t\t: { userId: id, userType: type, operation: where },\n\t);\n\n/**\n * The user — of this type, when one is given — or `null`. A malformed id is an\n * absence decided without reaching the store: it arrives off a URL, and it is\n * \"no such user\", not a query and not an outage. A user of another type is\n * absent too: `auth.staff.find(patientId)` finds nobody.\n */\nexport async function findRecord(\n\tcontext: Context,\n\tid: string,\n\ttype: string | null,\n): Promise<UserRecord | null> {\n\tif (!isId(id)) return null;\n\tconst record = await context.store.users.findUser(id);\n\treturn record !== null && (type === null || record.type === type)\n\t\t? record\n\t\t: null;\n}\n\n/** The user, or `NOT_FOUND`. */\nexport async function getRecord(\n\tcontext: Context,\n\tid: string,\n\ttype: string | null,\n\twhere: string,\n): Promise<UserRecord> {\n\treturn required(await findRecord(context, id, type), () =>\n\t\tnotFound(where, id, type),\n\t);\n}\n\n/**\n * One write that follows a read, under a version: the core reads the user,\n * checks `ifVersion` against what it read, computes the patch from the record,\n * and writes under the version it read. A user who changed in between is\n * `VERSION_CONFLICT`, and nothing is written.\n */\nexport async function writeUser(\n\tcontext: Context,\n\tuser: UserRef,\n\ttype: ResolvedType,\n\toptions: WriteOptions | undefined,\n\twhere: string,\n\tpatchOf: (\n\t\trecord: UserRecord,\n\t\tnow: Date,\n\t) => Omit<UserPatch, 'updatedAt'> | Promise<Omit<UserPatch, 'updatedAt'>>,\n): Promise<UserRecord> {\n\tconst id = idOf(user);\n\tconst record = await getRecord(context, id, type.name, where);\n\tconst ifVersion = options?.ifVersion;\n\n\tif (ifVersion !== undefined && ifVersion !== record.version) {\n\t\tthrow new StoreConflict(\n\t\t\t'version',\n\t\t\t`${where}: expected version ${ifVersion}, found ${record.version}`,\n\t\t\t{\n\t\t\t\tuserId: id,\n\t\t\t\texpectedVersion: ifVersion,\n\t\t\t\tactualVersion: record.version,\n\t\t\t\toperation: where,\n\t\t\t},\n\t\t);\n\t}\n\n\tconst now = context.clock.now();\n\tconst patch = await patchOf(record, now);\n\treturn context.store.users.updateUser(\n\t\trecord.id,\n\t\t{ ...patch, updatedAt: now },\n\t\trecord.version,\n\t);\n}\n",
|
|
12
|
-
"import type { JanusStores, StoreCapabilities } from './types';\n\n/**\n * The methods each slot must answer, in the order the port declares them.\n *\n * Kept beside the interfaces rather than derived from them: a type does not\n * exist at runtime, and this list is the net for the JavaScript caller the\n * compiler never saw.\n */\nconst REQUIRED = {\n\tusers: [\n\t\t'insertUser',\n\t\t'findUser',\n\t\t'findUserByLogin',\n\t\t'listUsers',\n\t\t'updateUser',\n\t\t'deleteUser',\n\t],\n\tsessions: [\n\t\t'insertSession',\n\t\t'findSessionByTokenHash',\n\t\t'extendSession',\n\t\t'revokeSession',\n\t\t'revokeUserSessions',\n\t\t'deleteUserSessions',\n\t],\n\ttokens: ['insertToken', 'consumeToken', 'deleteUserTokens'],\n} as const satisfies Record<keyof JanusStores, readonly string[]>;\n\n/**\n * Checks that every slot answers every method the port declares, and reports\n * the optional capabilities found.\n *\n * TypeScript already refuses a partial store and names the missing method;\n * this is the net for a JavaScript caller, and for a store assembled from\n * `any`. A bare `TypeError`, because a missing method can only come from how\n * the application was wired — no request produces one, and no handler should\n * answer one.\n *\n * `where` names the call the consumer wrote — `janus` — so the\n * sentence says which call to fix and which slot to change.\n */\nexport function assertStores(\n\tstores: unknown,\n\twhere: string,\n): StoreCapabilities {\n\tif (typeof stores !== 'object' || stores === null) {\n\t\tthrow new TypeError(\n\t\t\t`${where}: store must be an object with users, sessions and tokens`,\n\t\t);\n\t}\n\n\tfor (const slot of Object.keys(REQUIRED) as (keyof typeof REQUIRED)[]) {\n\t\tconst store: unknown = (stores as Record<string, unknown>)[slot];\n\n\t\tif (typeof store !== 'object' || store === null) {\n\t\t\tthrow new TypeError(`${where}: store.${slot} is missing`);\n\t\t}\n\n\t\tfor (const method of REQUIRED[slot]) {\n\t\t\tif (typeof (store as Record<string, unknown>)[method] !== 'function') {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`${where}: store.${slot} has no method ${method}, which the port requires`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst sessions = (stores as { sessions: Record<string, unknown> }).sessions;\n\tconst collect = sessions.deleteExpiredSessions;\n\n\t// Present but not a function is a wiring mistake, not an absent capability:\n\t// reporting it as \"unsupported\" would send the reader to the wrong fix.\n\tif (collect !== undefined && typeof collect !== 'function') {\n\t\tthrow new TypeError(\n\t\t\t`${where}: store.sessions.deleteExpiredSessions must be a function or absent`,\n\t\t);\n\t}\n\n\treturn { collectExpired: collect !== undefined };\n}\n",
|
|
12
|
+
"import type { JanusStores, StoreCapabilities } from './types';\n\n/**\n * The methods each slot must answer, in the order the port declares them.\n *\n * Kept beside the interfaces rather than derived from them: a type does not\n * exist at runtime, and this list is the net for the JavaScript caller the\n * compiler never saw.\n */\nconst REQUIRED = {\n\tusers: [\n\t\t'insertUser',\n\t\t'findUser',\n\t\t'findUserByLogin',\n\t\t'listUsers',\n\t\t'updateUser',\n\t\t'deleteUser',\n\t],\n\tsessions: [\n\t\t'insertSession',\n\t\t'findSessionByTokenHash',\n\t\t'extendSession',\n\t\t'revokeSession',\n\t\t'revokeUserSessions',\n\t\t'deleteUserSessions',\n\t],\n\ttokens: ['insertToken', 'consumeToken', 'countAttempt', 'deleteUserTokens'],\n} as const satisfies Record<keyof JanusStores, readonly string[]>;\n\n/**\n * Checks that every slot answers every method the port declares, and reports\n * the optional capabilities found.\n *\n * TypeScript already refuses a partial store and names the missing method;\n * this is the net for a JavaScript caller, and for a store assembled from\n * `any`. A bare `TypeError`, because a missing method can only come from how\n * the application was wired — no request produces one, and no handler should\n * answer one.\n *\n * `where` names the call the consumer wrote — `janus` — so the\n * sentence says which call to fix and which slot to change.\n */\nexport function assertStores(\n\tstores: unknown,\n\twhere: string,\n): StoreCapabilities {\n\tif (typeof stores !== 'object' || stores === null) {\n\t\tthrow new TypeError(\n\t\t\t`${where}: store must be an object with users, sessions and tokens`,\n\t\t);\n\t}\n\n\tfor (const slot of Object.keys(REQUIRED) as (keyof typeof REQUIRED)[]) {\n\t\tconst store: unknown = (stores as Record<string, unknown>)[slot];\n\n\t\tif (typeof store !== 'object' || store === null) {\n\t\t\tthrow new TypeError(`${where}: store.${slot} is missing`);\n\t\t}\n\n\t\tfor (const method of REQUIRED[slot]) {\n\t\t\tif (typeof (store as Record<string, unknown>)[method] !== 'function') {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`${where}: store.${slot} has no method ${method}, which the port requires`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst sessions = (stores as { sessions: Record<string, unknown> }).sessions;\n\tconst collect = sessions.deleteExpiredSessions;\n\n\t// Present but not a function is a wiring mistake, not an absent capability:\n\t// reporting it as \"unsupported\" would send the reader to the wrong fix.\n\tif (collect !== undefined && typeof collect !== 'function') {\n\t\tthrow new TypeError(\n\t\t\t`${where}: store.sessions.deleteExpiredSessions must be a function or absent`,\n\t\t);\n\t}\n\n\treturn { collectExpired: collect !== undefined };\n}\n",
|
|
13
13
|
"import { createHash, randomBytes } from 'node:crypto';\n\n/**\n * A fresh secret: 32 random bytes, base64url — safe in a header, a cookie and a\n * URL without escaping.\n *\n * Given to the application **once**. No store ever holds it.\n */\nexport function mintSecret(): string {\n\treturn randomBytes(32).toString('base64url');\n}\n\n/**\n * What a store holds instead of the secret: `sha256`, hex.\n *\n * SHA-256 and not argon2, on purpose. The secret carries 256 bits of entropy, so\n * there is nothing to brute-force, and an argon2 per request would put ~50 ms on\n * every authenticated call. What the hash buys is that a dump of the store\n * cannot be replayed.\n */\nexport function hashSecret(secret: string): string {\n\treturn createHash('sha256').update(secret).digest('hex');\n}\n",
|
|
14
14
|
"import { UnsupportedError } from '../errors/janus-error';\nimport { isId, mintId } from '../ids/id';\nimport type { ResolvedType } from './config';\nimport {\n\ttype AnyUser,\n\ttype Context,\n\tfindRecord,\n\tgetRecord,\n\tidOf,\n\ttoUser,\n} from './context';\nimport type { SessionRecord, UserRecord } from './port/types';\nimport { hashSecret, mintSecret } from './secrets';\nimport type {\n\tAuthenticated,\n\tHeaderRecord,\n\tRequestLike,\n\tSession,\n\tSharedApi,\n\tSignedIn,\n} from './types';\n\n/** `SharedApi`, degenericised: `janus()` casts it to the typed form once. */\nexport type InternalSharedApi = Omit<SharedApi<AnyUser>, 'authenticate'> & {\n\tauthenticate(\n\t\trequest: RequestLike,\n\t\toptions?: { readonly type?: string },\n\t): Promise<Authenticated<AnyUser> | null>;\n};\n\n/** A session as application code sees it: everything but the token's hash. */\nexport function toSession(record: SessionRecord): Session {\n\tconst { tokenHash: _, ...session } = record;\n\treturn session;\n}\n\n/**\n * Opens a session for a user who just proved who they are. The token is in\n * the answer and nowhere else: the store holds its hash.\n */\nexport async function openSession(\n\tcontext: Context,\n\ttype: ResolvedType,\n\tuser: UserRecord,\n): Promise<SignedIn<AnyUser>> {\n\tconst now = context.clock.now();\n\tconst token = mintSecret();\n\tconst record: SessionRecord = {\n\t\tid: mintId(now.getTime()),\n\t\ttokenHash: hashSecret(token),\n\t\tuserId: user.id,\n\t\tauthenticatedAt: now,\n\t\texpiresAt: new Date(now.getTime() + type.lifespanMs),\n\t\trevokedAt: null,\n\t\tcreatedAt: now,\n\t};\n\n\tawait context.store.sessions.insertSession(record);\n\treturn { user: toUser(user), session: toSession(record), token };\n}\n\n/** Everything `janus()` answers whatever its user types. */\nexport function sharedApi(context: Context): InternalSharedApi {\n\tconst { store, clock, config } = context;\n\n\treturn {\n\t\tasync authenticate(request, options) {\n\t\t\tconst token = presentedToken(request, config.cookie.name);\n\t\t\tif (token === null) return null;\n\n\t\t\tconst session = await store.sessions.findSessionByTokenHash(\n\t\t\t\thashSecret(token),\n\t\t\t);\n\t\t\t// Lapsed, revoked, or gone: anonymous. Expiry is decided here, against\n\t\t\t// this clock — a store may still hold a session past its term.\n\t\t\tconst now = clock.now().getTime();\n\t\t\tif (\n\t\t\t\tsession === null ||\n\t\t\t\tsession.revokedAt !== null ||\n\t\t\t\tsession.expiresAt.getTime() <= now\n\t\t\t) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// A user gone, inactive, or of another type than asked for is\n\t\t\t// anonymous: the session stands, and proves nothing here.\n\t\t\tconst user = await store.users.findUser(session.userId);\n\t\t\tif (\n\t\t\t\tuser === null ||\n\t\t\t\t!user.active ||\n\t\t\t\t(options?.type !== undefined && user.type !== options.type)\n\t\t\t) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst type = config.types.get(user.type);\n\t\t\tlet current = session;\n\t\t\tlet renewed = false;\n\n\t\t\t// Renewed once `renewAfter` has passed since it was opened or last\n\t\t\t// renewed — the lifespan minus what is left — so a busy session is\n\t\t\t// written at most once per period.\n\t\t\tif (\n\t\t\t\ttype !== undefined &&\n\t\t\t\ttype.renewAfterMs !== null &&\n\t\t\t\ttype.lifespanMs - (session.expiresAt.getTime() - now) >=\n\t\t\t\t\ttype.renewAfterMs\n\t\t\t) {\n\t\t\t\tconst extended = await store.sessions.extendSession(\n\t\t\t\t\tsession.id,\n\t\t\t\t\tnew Date(now + type.lifespanMs),\n\t\t\t\t);\n\t\t\t\t// Revoked between the read and the renewal: anonymous, and never\n\t\t\t\t// brought back.\n\t\t\t\tif (extended === null) return null;\n\t\t\t\tcurrent = extended;\n\t\t\t\trenewed = true;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tuser: toUser(user),\n\t\t\t\tsession: toSession(current),\n\t\t\t\ttoken,\n\t\t\t\trenewed,\n\t\t\t};\n\t\t},\n\n\t\tasync signOut(request) {\n\t\t\tconst token = presentedToken(request, config.cookie.name);\n\t\t\tif (token === null) return false;\n\n\t\t\tconst session = await store.sessions.findSessionByTokenHash(\n\t\t\t\thashSecret(token),\n\t\t\t);\n\t\t\tif (session === null) return false;\n\t\t\treturn store.sessions.revokeSession(session.id, clock.now());\n\t\t},\n\n\t\tasync signOutEverywhere(user, options) {\n\t\t\tconst id = idOf(user);\n\t\t\tif (!isId(id)) return 0;\n\t\t\t// An `except` that is no id this package minted names no session:\n\t\t\t// every session goes, as it would for an unknown one.\n\t\t\treturn options?.except === undefined || !isId(options.except)\n\t\t\t\t? store.sessions.revokeUserSessions(id, clock.now())\n\t\t\t\t: store.sessions.revokeUserSessions(id, clock.now(), options.except);\n\t\t},\n\n\t\tasync findUser(id) {\n\t\t\tconst record = await findRecord(context, id, null);\n\t\t\treturn record === null ? null : toUser(record);\n\t\t},\n\n\t\tasync getUser(id) {\n\t\t\treturn toUser(await getRecord(context, id, null, 'getUser'));\n\t\t},\n\n\t\tcookie: cookieOf(context),\n\n\t\tasync collectExpired() {\n\t\t\tconst collect = store.sessions.deleteExpiredSessions;\n\t\t\tif (!context.capabilities.collectExpired || collect === undefined) {\n\t\t\t\tthrow new UnsupportedError(\n\t\t\t\t\t'collectExpired: store.sessions does not implement deleteExpiredSessions — its store expires sessions on its own, or implement the method',\n\t\t\t\t\t{ slot: 'sessions', operation: 'deleteExpiredSessions' },\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn collect(clock.now());\n\t\t},\n\n\t\ttypes: [...config.types.keys()],\n\t};\n}\n\nfunction cookieOf(context: Context): SharedApi<AnyUser>['cookie'] {\n\tconst { name, domain, path, sameSite, secure } = context.config.cookie;\n\tconst attributes = [\n\t\t`Path=${path}`,\n\t\t...(domain === null ? [] : [`Domain=${domain}`]),\n\t\t'HttpOnly',\n\t\t`SameSite=${sameSite[0]?.toUpperCase()}${sameSite.slice(1)}`,\n\t\t...(secure ? ['Secure'] : []),\n\t].join('; ');\n\n\treturn {\n\t\tname,\n\t\tserialize: (token: string, session: Session) =>\n\t\t\t`${name}=${token}; Expires=${session.expiresAt.toUTCString()}; ${attributes}`,\n\t\tclear: () =>\n\t\t\t`${name}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; ${attributes}`,\n\t};\n}\n\n/**\n * The session token a request presents: `Authorization: Bearer`, then\n * `X-Session-Token`, then the cookie.\n *\n * **The first credential present wins, not the first valid one.** A browser\n * sending a lapsed bearer beside a live cookie is anonymous, and should fix its\n * header rather than be rescued in silence — the rule `resolve` in `nxgt-ory`'s\n * SDK learned. An `Authorization` header of another scheme (`Basic`) is not a\n * session credential, and does not count as one.\n */\nexport function presentedToken(\n\trequest: RequestLike,\n\tcookieName: string,\n): string | null {\n\tconst headers = headersOf(request);\n\tconst read = (name: string): string | null => {\n\t\tif (headers instanceof Headers) return headers.get(name);\n\t\tfor (const [key, value] of Object.entries(headers)) {\n\t\t\tif (key.toLowerCase() !== name || value === undefined) continue;\n\t\t\treturn typeof value === 'string' ? value : value.join(', ');\n\t\t}\n\t\treturn null;\n\t};\n\n\tconst authorization = read('authorization');\n\tif (authorization !== null) {\n\t\tconst match = /^bearer(?:\\s+(.*))?$/i.exec(authorization.trim());\n\t\tif (match) return match[1]?.trim() ?? '';\n\t}\n\n\tconst header = read('x-session-token');\n\tif (header !== null) return header.trim();\n\n\tconst cookie = read('cookie');\n\tif (cookie !== null) {\n\t\tfor (const pair of cookie.split(';')) {\n\t\t\tconst at = pair.indexOf('=');\n\t\t\tif (at !== -1 && pair.slice(0, at).trim() === cookieName) {\n\t\t\t\treturn pair.slice(at + 1).trim();\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * The headers of whatever was passed: a `Request`, `Headers`, anything with a\n * `headers` property — Node's `IncomingMessage` — or a plain record.\n */\nfunction headersOf(request: RequestLike): Headers | HeaderRecord {\n\tif (request instanceof Headers) return request;\n\tconst inner = (request as { headers?: unknown }).headers;\n\tif (inner instanceof Headers) return inner;\n\tif (typeof inner === 'object' && inner !== null) return inner as HeaderRecord;\n\treturn request as HeaderRecord;\n}\n",
|
|
15
|
-
"import {\n\tCredentialError,\n\tNotFoundError,\n\tTokenError,\n\tUserInactiveError,\n} from '../errors/janus-error';\nimport { isId, mintId } from '../ids/id';\nimport { invalidCursor, pageLimit } from '../pagination/cursor-page';\nimport { isStorable } from '../stores/storable';\nimport { normalizeEmail, type ResolvedType } from './config';\nimport {\n\ttype AnyUser,\n\ttype Context,\n\tcheckPassword,\n\temailOf,\n\tfindRecord,\n\tgetRecord,\n\tidOf,\n\tloginsOf,\n\tpasswordMatches,\n\trehashed,\n\trequireHasher,\n\ttoUser,\n\tvalidateFields,\n\twriteUser,\n} from './context';\nimport type { TokenKind, TokenRecord, UserRecord } from './port/types';\nimport { hashSecret, mintSecret } from './secrets';\nimport { openSession } from './sessions';\nimport type {\n\tIssuedToken,\n\tPasswordApi,\n\tResetPasswordApi,\n\tUserTypeApi,\n\tVerifyEmailApi,\n} from './types';\n\ntype Input = Record<string, unknown>;\n\n/** Everything one user type answers. Which flows it has is decided by its types; all are built. */\nexport type AnyTypeApi = UserTypeApi<AnyUser, Input> &\n\tPasswordApi<AnyUser, Input, string> &\n\tVerifyEmailApi<AnyUser> &\n\tResetPasswordApi<AnyUser>;\n\nexport function typeApi(context: Context, type: ResolvedType): AnyTypeApi {\n\tconst { store, clock } = context;\n\tconst at = (operation: string) =>\n\t\tcontext.config.single ? operation : `${type.name}.${operation}`;\n\n\t/**\n\t * The user holding this normalised login, or `null`. A login no store can\n\t * keep is nobody's: answered as an absence, without asking a store that\n\t * would fail on it.\n\t */\n\tconst byLogin = async (login: string): Promise<UserRecord | null> =>\n\t\tisStorable(login) ? store.users.findUserByLogin(type.name, login) : null;\n\n\t/** Validates, hashes, writes once. What `create` and `signUp` share. */\n\tconst insert = async (input: Input, where: string): Promise<UserRecord> => {\n\t\tconst { password, active, ...rest } = input ?? {};\n\t\tconst now = clock.now();\n\t\tconst fields = await validateFields(type, rest, where);\n\n\t\tlet hash: UserRecord['password'] = null;\n\t\tif (password !== undefined) {\n\t\t\tcheckPassword(type, password as string, where);\n\t\t\thash = {\n\t\t\t\thash: await requireHasher(context, where).hash(password as string),\n\t\t\t\tupdatedAt: now,\n\t\t\t};\n\t\t}\n\n\t\treturn store.users.insertUser({\n\t\t\tid: mintId(now.getTime()),\n\t\t\ttype: type.name,\n\t\t\tschemaVersion: type.schemaVersion,\n\t\t\tactive: active === undefined ? true : active === true,\n\t\t\tfields,\n\t\t\tlogins: loginsOf(type, fields, where),\n\t\t\tpassword: hash,\n\t\t\temailVerifiedAt: null,\n\t\t\tversion: 0,\n\t\t\tcreatedAt: now,\n\t\t\tupdatedAt: now,\n\t\t});\n\t};\n\n\t/**\n\t * Spends a token and says why it cannot be used, when it cannot.\n\t *\n\t * The store answers the token **as it was before the call**: `spentAt: null`\n\t * means this call spent it, and exactly one call ever sees that. A lapsed\n\t * token is spent all the same, so it cannot be retried. None of these\n\t * messages names the token: it is a secret, and so is its hash.\n\t */\n\tconst redeem = async (\n\t\tkind: TokenKind,\n\t\tsecret: string,\n\t\twhere: string,\n\t): Promise<{ token: TokenRecord; user: UserRecord }> => {\n\t\tconst now = clock.now();\n\t\tconst token = await store.tokens.consumeToken(\n\t\t\thashSecret(secret),\n\t\t\tkind,\n\t\t\tnow,\n\t\t);\n\n\t\tif (token === null) {\n\t\t\tthrow new TokenError('TOKEN_UNKNOWN', `${where}: no such token`, {\n\t\t\t\toperation: where,\n\t\t\t});\n\t\t}\n\t\tif (token.spentAt !== null) {\n\t\t\tthrow new TokenError(\n\t\t\t\t'TOKEN_SPENT',\n\t\t\t\t`${where}: the token was already used`,\n\t\t\t\t{\n\t\t\t\t\toperation: where,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (token.expiresAt.getTime() <= now.getTime()) {\n\t\t\tthrow new TokenError('TOKEN_EXPIRED', `${where}: the token has expired`, {\n\t\t\t\toperation: where,\n\t\t\t});\n\t\t}\n\n\t\t// A user gone since, or of another type, is as good as no token.\n\t\tconst user = await findRecord(context, token.userId, type.name);\n\t\tif (user === null) {\n\t\t\tthrow new TokenError('TOKEN_UNKNOWN', `${where}: no such token`, {\n\t\t\t\toperation: where,\n\t\t\t});\n\t\t}\n\n\t\tconst email = emailOf(type, user.fields);\n\t\tif (\n\t\t\temail === null ||\n\t\t\tnormalizeEmail(email) !== normalizeEmail(token.address)\n\t\t) {\n\t\t\tthrow new TokenError(\n\t\t\t\t'TOKEN_STALE',\n\t\t\t\t`${where}: the token was sent to an e-mail the user no longer has`,\n\t\t\t\t{ operation: where, userId: user.id, userType: type.name },\n\t\t\t);\n\t\t}\n\n\t\treturn { token, user };\n\t};\n\n\t/** Issues a one-time token for the user's current e-mail. */\n\tconst issue = async (\n\t\tkind: TokenKind,\n\t\tuser: UserRecord,\n\t\twhere: string,\n\t): Promise<IssuedToken> => {\n\t\tconst email = emailOf(type, user.fields);\n\t\tif (email === null) {\n\t\t\tthrow new NotFoundError(`${where}: the user has no e-mail`, {\n\t\t\t\tuserId: user.id,\n\t\t\t\tuserType: type.name,\n\t\t\t\toperation: where,\n\t\t\t});\n\t\t}\n\n\t\tconst now = clock.now();\n\t\tconst secret = mintSecret();\n\t\tconst expiresAt = new Date(now.getTime() + context.config.tokenTtlMs[kind]);\n\n\t\tawait store.tokens.insertToken({\n\t\t\ttokenHash: hashSecret(secret),\n\t\t\tkind,\n\t\t\tuserId: user.id,\n\t\t\taddress: email,\n\t\t\texpiresAt,\n\t\t\tspentAt: null,\n\t\t\tcreatedAt: now,\n\t\t});\n\n\t\treturn { token: secret, email, expiresAt };\n\t};\n\n\t/** The type's password rule, or a wiring refusal for a JavaScript caller. */\n\tconst passwordRule = (where: string) => {\n\t\tif (type.password === null) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`${where}: the ${type.name} type does not sign in with a password — add password: { login } to it`,\n\t\t\t);\n\t\t}\n\t\treturn type.password;\n\t};\n\n\treturn {\n\t\tasync create(input) {\n\t\t\treturn toUser(await insert(input as Input, at('create')));\n\t\t},\n\n\t\tasync find(id) {\n\t\t\tconst record = await findRecord(context, id, type.name);\n\t\t\treturn record === null ? null : toUser(record);\n\t\t},\n\n\t\tasync get(id) {\n\t\t\treturn toUser(await getRecord(context, id, type.name, at('get')));\n\t\t},\n\n\t\tasync list(page) {\n\t\t\tconst after = page?.after ?? null;\n\t\t\t// Never a silent first page: a caller paging a list would loop for\n\t\t\t// ever, and the loop would look like a slow query.\n\t\t\tif (after !== null && !isId(after)) {\n\t\t\t\tthrow invalidCursor(at('list'), after);\n\t\t\t}\n\n\t\t\tconst answer = await store.users.listUsers({\n\t\t\t\ttype: type.name,\n\t\t\t\tafter,\n\t\t\t\tlimit: pageLimit(page?.limit, at('list')),\n\t\t\t});\n\t\t\treturn { items: answer.items.map(toUser), nextCursor: answer.nextCursor };\n\t\t},\n\n\t\tasync update(user, patch, options) {\n\t\t\tconst where = at('update');\n\t\t\tconst written = await writeUser(\n\t\t\t\tcontext,\n\t\t\t\tuser,\n\t\t\t\ttype,\n\t\t\t\toptions,\n\t\t\t\twhere,\n\t\t\t\tasync (record) => {\n\t\t\t\t\tconst fields = await validateFields(\n\t\t\t\t\t\ttype,\n\t\t\t\t\t\t{ ...record.fields, ...(patch as Input) },\n\t\t\t\t\t\twhere,\n\t\t\t\t\t);\n\t\t\t\t\tconst before = emailOf(type, record.fields);\n\t\t\t\t\tconst after = emailOf(type, fields);\n\t\t\t\t\tconst emailChanged =\n\t\t\t\t\t\t(before === null ? null : normalizeEmail(before)) !==\n\t\t\t\t\t\t(after === null ? null : normalizeEmail(after));\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tfields,\n\t\t\t\t\t\tlogins: loginsOf(type, fields, where),\n\t\t\t\t\t\tschemaVersion: type.schemaVersion,\n\t\t\t\t\t\t// A new e-mail is an unproven one.\n\t\t\t\t\t\t...(emailChanged ? { emailVerifiedAt: null } : {}),\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn toUser(written);\n\t\t},\n\n\t\tasync setActive(user, active, options) {\n\t\t\treturn toUser(\n\t\t\t\tawait writeUser(context, user, type, options, at('setActive'), () => ({\n\t\t\t\t\tactive: active === true,\n\t\t\t\t})),\n\t\t\t);\n\t\t},\n\n\t\tasync delete(user) {\n\t\t\tconst id = idOf(user);\n\t\t\tif (!isId(id)) return false;\n\n\t\t\t// Read first, so a staff API never deletes a patient: another type's\n\t\t\t// id is answered as nobody, and nothing of theirs is touched.\n\t\t\tconst record = await store.users.findUser(id);\n\t\t\tif (record !== null && record.type !== type.name) return false;\n\n\t\t\t// The user first: from then on nobody can sign in as them, and what\n\t\t\t// is left — sessions, tokens — is refused for a user who is gone. An\n\t\t\t// outage between the steps leaves only that inert remainder, and a\n\t\t\t// replay, finding no user, still deletes it.\n\t\t\tconst deleted = record !== null && (await store.users.deleteUser(id));\n\t\t\tawait store.sessions.deleteUserSessions(id);\n\t\t\tawait store.tokens.deleteUserTokens(id);\n\t\t\t// Last, and on a replay too: the tuples naming them.\n\t\t\tawait context.relations?.deleteEntity({ type: type.name, id });\n\t\t\treturn deleted;\n\t\t},\n\n\t\tasync signUp(input) {\n\t\t\tconst where = at('signUp');\n\t\t\tpasswordRule(where);\n\t\t\tif ((input as Input)?.password === undefined) {\n\t\t\t\tcheckPassword(type, undefined as unknown as string, where);\n\t\t\t}\n\t\t\tconst record = await insert(input as Input, where);\n\t\t\treturn openSession(context, type, record);\n\t\t},\n\n\t\tasync signIn(input) {\n\t\t\tconst where = at('signIn');\n\t\t\tconst rule = passwordRule(where);\n\t\t\tconst hasher = requireHasher(context, where);\n\t\t\tconst login = (input as Input)?.[rule.login];\n\t\t\tconst password = (input as Input)?.password;\n\n\t\t\tconst record =\n\t\t\t\ttypeof login === 'string' ? await byLogin(rule.normalize(login)) : null;\n\t\t\tconst refuse = (\n\t\t\t\treason: 'unknownLogin' | 'noPassword' | 'wrongPassword',\n\t\t\t) =>\n\t\t\t\tnew CredentialError(\n\t\t\t\t\t'CREDENTIALS_INVALID',\n\t\t\t\t\t`${where}: the login and the password do not match`,\n\t\t\t\t\t{ reason, userType: type.name },\n\t\t\t\t);\n\n\t\t\tif (record === null || record.password === null) {\n\t\t\t\t// Compared all the same, so the response time does not say which\n\t\t\t\t// logins are registered. The store's own latency stays observable;\n\t\t\t\t// that limit is documented, not denied.\n\t\t\t\tawait hasher.verify(String(password), await context.dummyHash());\n\t\t\t\tthrow refuse(record === null ? 'unknownLogin' : 'noPassword');\n\t\t\t}\n\n\t\t\tif (!(await passwordMatches(context, record, String(password), where))) {\n\t\t\t\tthrow refuse('wrongPassword');\n\t\t\t}\n\t\t\t// Checked after the password, so an inactive account is only told to\n\t\t\t// somebody who knows its password.\n\t\t\tif (!record.active) {\n\t\t\t\tthrow new UserInactiveError(`${where}: the user is inactive`, {\n\t\t\t\t\tuserId: record.id,\n\t\t\t\t\tuserType: type.name,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn openSession(\n\t\t\t\tcontext,\n\t\t\t\ttype,\n\t\t\t\tawait rehashed(context, record, String(password)),\n\t\t\t);\n\t\t},\n\n\t\tasync findByLogin(login) {\n\t\t\tconst rule = passwordRule(at('findByLogin'));\n\t\t\tconst record = await byLogin(rule.normalize(login));\n\t\t\treturn record === null ? null : toUser(record);\n\t\t},\n\n\t\tasync setPassword(user, password, options) {\n\t\t\tconst where = at('setPassword');\n\t\t\tpasswordRule(where);\n\t\t\tcheckPassword(type, password, where);\n\t\t\tconst hash = await requireHasher(context, where).hash(password);\n\n\t\t\treturn toUser(\n\t\t\t\tawait writeUser(context, user, type, options, where, (_, now) => ({\n\t\t\t\t\tpassword: { hash, updatedAt: now },\n\t\t\t\t})),\n\t\t\t);\n\t\t},\n\n\t\tasync changePassword(user, change, options) {\n\t\t\tconst where = at('changePassword');\n\t\t\tpasswordRule(where);\n\t\t\tcheckPassword(type, change?.next, where);\n\t\t\tconst hasher = requireHasher(context, where);\n\n\t\t\treturn toUser(\n\t\t\t\tawait writeUser(\n\t\t\t\t\tcontext,\n\t\t\t\t\tuser,\n\t\t\t\t\ttype,\n\t\t\t\t\toptions,\n\t\t\t\t\twhere,\n\t\t\t\t\tasync (record, now) => {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\trecord.password === null ||\n\t\t\t\t\t\t\t!(await passwordMatches(context, record, change.current, where))\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tthrow new CredentialError(\n\t\t\t\t\t\t\t\t'CREDENTIALS_INVALID',\n\t\t\t\t\t\t\t\t`${where}: the current password does not match`,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treason:\n\t\t\t\t\t\t\t\t\t\trecord.password === null ? 'noPassword' : 'wrongPassword',\n\t\t\t\t\t\t\t\t\tuserId: record.id,\n\t\t\t\t\t\t\t\t\tuserType: type.name,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tpassword: {\n\t\t\t\t\t\t\t\thash: await hasher.hash(change.next),\n\t\t\t\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t},\n\n\t\tverifyEmail: {\n\t\t\tasync send(user) {\n\t\t\t\tconst where = at('verifyEmail.send');\n\t\t\t\tconst record = await getRecord(context, idOf(user), type.name, where);\n\t\t\t\treturn issue('verifyEmail', record, where);\n\t\t\t},\n\n\t\t\tasync confirm(secret) {\n\t\t\t\tconst where = at('verifyEmail.confirm');\n\t\t\t\tconst { user } = await redeem('verifyEmail', secret, where);\n\t\t\t\treturn toUser(\n\t\t\t\t\tawait writeUser(\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t\tuser.id,\n\t\t\t\t\t\ttype,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t\t(_, now) => ({\n\t\t\t\t\t\t\temailVerifiedAt: now,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t},\n\t\t},\n\n\t\tresetPassword: {\n\t\t\tasync request(email) {\n\t\t\t\tconst where = at('resetPassword.request');\n\t\t\t\tpasswordRule(where);\n\t\t\t\tconst wanted = normalizeEmail(String(email));\n\t\t\t\tconst record = await byLogin(wanted);\n\n\t\t\t\t// Found by a login that is not their e-mail — a username that looks\n\t\t\t\t// like one — is nobody's e-mail: no token.\n\t\t\t\tconst held = record === null ? null : emailOf(type, record.fields);\n\t\t\t\tif (\n\t\t\t\t\trecord === null ||\n\t\t\t\t\theld === null ||\n\t\t\t\t\tnormalizeEmail(held) !== wanted\n\t\t\t\t) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\tconst issued = await issue('resetPassword', record, where);\n\t\t\t\treturn { ...issued, user: toUser(record) };\n\t\t\t},\n\n\t\t\tasync confirm(secret, password) {\n\t\t\t\tconst where = at('resetPassword.confirm');\n\t\t\t\tpasswordRule(where);\n\t\t\t\t// Checked before the token is spent: a password refused for its\n\t\t\t\t// length must not cost the visitor their link.\n\t\t\t\tcheckPassword(type, password, where);\n\t\t\t\tconst hash = await requireHasher(context, where).hash(password);\n\n\t\t\t\tconst { user } = await redeem('resetPassword', secret, where);\n\t\t\t\tconst written = await writeUser(\n\t\t\t\t\tcontext,\n\t\t\t\t\tuser.id,\n\t\t\t\t\ttype,\n\t\t\t\t\tundefined,\n\t\t\t\t\twhere,\n\t\t\t\t\t(record, now) => ({\n\t\t\t\t\t\tpassword: { hash, updatedAt: now },\n\t\t\t\t\t\t// The link reached the inbox: that proves the e-mail.\n\t\t\t\t\t\t...(record.emailVerifiedAt === null\n\t\t\t\t\t\t\t? { emailVerifiedAt: now }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\t// Whoever had the old password is signed out.\n\t\t\t\tawait store.sessions.revokeUserSessions(written.id, clock.now());\n\t\t\t\treturn toUser(written);\n\t\t\t},\n\t\t},\n\t};\n}\n",
|
|
15
|
+
"import {\n\tCredentialError,\n\tNotFoundError,\n\tTokenError,\n\tUserInactiveError,\n} from '../errors/janus-error';\nimport { isId, mintId } from '../ids/id';\nimport { invalidCursor, pageLimit } from '../pagination/cursor-page';\nimport { isStorable } from '../stores/storable';\nimport { normalizeEmail, type ResolvedType } from './config';\nimport {\n\ttype AnyUser,\n\ttype Context,\n\tcheckPassword,\n\temailOf,\n\tfindRecord,\n\tgetRecord,\n\tidOf,\n\tloginsOf,\n\tpasswordMatches,\n\trehashed,\n\trequireHasher,\n\ttoUser,\n\tvalidateFields,\n\twriteUser,\n} from './context';\nimport type { TokenKind, TokenRecord, UserRecord } from './port/types';\nimport { hashSecret, mintSecret } from './secrets';\nimport { openSession } from './sessions';\nimport type {\n\tIssuedToken,\n\tPasswordApi,\n\tResetPasswordApi,\n\tUserTypeApi,\n\tVerifyEmailApi,\n} from './types';\n\ntype Input = Record<string, unknown>;\n\n/** Everything one user type answers. Which flows it has is decided by its types; all are built. */\nexport type AnyTypeApi = UserTypeApi<AnyUser, Input> &\n\tPasswordApi<AnyUser, Input, string> &\n\tVerifyEmailApi<AnyUser> &\n\tResetPasswordApi<AnyUser>;\n\nexport function typeApi(context: Context, type: ResolvedType): AnyTypeApi {\n\tconst { store, clock } = context;\n\tconst at = (operation: string) =>\n\t\tcontext.config.single ? operation : `${type.name}.${operation}`;\n\n\t/**\n\t * The user holding this normalised login, or `null`. A login no store can\n\t * keep is nobody's: answered as an absence, without asking a store that\n\t * would fail on it.\n\t */\n\tconst byLogin = async (login: string): Promise<UserRecord | null> =>\n\t\tisStorable(login) ? store.users.findUserByLogin(type.name, login) : null;\n\n\t/** Validates, hashes, writes once. What `create` and `signUp` share. */\n\tconst insert = async (input: Input, where: string): Promise<UserRecord> => {\n\t\tconst { password, active, ...rest } = input ?? {};\n\t\tconst now = clock.now();\n\t\tconst fields = await validateFields(type, rest, where);\n\n\t\tlet hash: UserRecord['password'] = null;\n\t\tif (password !== undefined) {\n\t\t\tcheckPassword(type, password as string, where);\n\t\t\thash = {\n\t\t\t\thash: await requireHasher(context, where).hash(password as string),\n\t\t\t\tupdatedAt: now,\n\t\t\t};\n\t\t}\n\n\t\treturn store.users.insertUser({\n\t\t\tid: mintId(now.getTime()),\n\t\t\ttype: type.name,\n\t\t\tschemaVersion: type.schemaVersion,\n\t\t\tactive: active === undefined ? true : active === true,\n\t\t\tfields,\n\t\t\tlogins: loginsOf(type, fields, where),\n\t\t\tpassword: hash,\n\t\t\tsecondFactor: null,\n\t\t\temailVerifiedAt: null,\n\t\t\tversion: 0,\n\t\t\tcreatedAt: now,\n\t\t\tupdatedAt: now,\n\t\t});\n\t};\n\n\t/**\n\t * Spends a token and says why it cannot be used, when it cannot.\n\t *\n\t * The store answers the token **as it was before the call**: `spentAt: null`\n\t * means this call spent it, and exactly one call ever sees that. A lapsed\n\t * token is spent all the same, so it cannot be retried. None of these\n\t * messages names the token: it is a secret, and so is its hash.\n\t */\n\tconst redeem = async (\n\t\tkind: TokenKind,\n\t\tsecret: string,\n\t\twhere: string,\n\t): Promise<{ token: TokenRecord; user: UserRecord }> => {\n\t\tconst now = clock.now();\n\t\tconst token = await store.tokens.consumeToken(\n\t\t\thashSecret(secret),\n\t\t\tkind,\n\t\t\tnow,\n\t\t);\n\n\t\tif (token === null) {\n\t\t\tthrow new TokenError('TOKEN_UNKNOWN', `${where}: no such token`, {\n\t\t\t\toperation: where,\n\t\t\t});\n\t\t}\n\t\tif (token.spentAt !== null) {\n\t\t\tthrow new TokenError(\n\t\t\t\t'TOKEN_SPENT',\n\t\t\t\t`${where}: the token was already used`,\n\t\t\t\t{\n\t\t\t\t\toperation: where,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tif (token.expiresAt.getTime() <= now.getTime()) {\n\t\t\tthrow new TokenError('TOKEN_EXPIRED', `${where}: the token has expired`, {\n\t\t\t\toperation: where,\n\t\t\t});\n\t\t}\n\n\t\t// A user gone since, or of another type, is as good as no token.\n\t\tconst user = await findRecord(context, token.userId, type.name);\n\t\tif (user === null) {\n\t\t\tthrow new TokenError('TOKEN_UNKNOWN', `${where}: no such token`, {\n\t\t\t\toperation: where,\n\t\t\t});\n\t\t}\n\n\t\tconst email = emailOf(type, user.fields);\n\t\tif (\n\t\t\temail === null ||\n\t\t\tnormalizeEmail(email) !== normalizeEmail(token.address)\n\t\t) {\n\t\t\tthrow new TokenError(\n\t\t\t\t'TOKEN_STALE',\n\t\t\t\t`${where}: the token was sent to an e-mail the user no longer has`,\n\t\t\t\t{ operation: where, userId: user.id, userType: type.name },\n\t\t\t);\n\t\t}\n\n\t\treturn { token, user };\n\t};\n\n\t/** Issues a one-time token for the user's current e-mail. */\n\tconst issue = async (\n\t\tkind: 'verifyEmail' | 'resetPassword',\n\t\tuser: UserRecord,\n\t\twhere: string,\n\t): Promise<IssuedToken> => {\n\t\tconst email = emailOf(type, user.fields);\n\t\tif (email === null) {\n\t\t\tthrow new NotFoundError(`${where}: the user has no e-mail`, {\n\t\t\t\tuserId: user.id,\n\t\t\t\tuserType: type.name,\n\t\t\t\toperation: where,\n\t\t\t});\n\t\t}\n\n\t\tconst now = clock.now();\n\t\tconst secret = mintSecret();\n\t\tconst expiresAt = new Date(now.getTime() + context.config.tokenTtlMs[kind]);\n\n\t\tawait store.tokens.insertToken({\n\t\t\ttokenHash: hashSecret(secret),\n\t\t\tkind,\n\t\t\tuserId: user.id,\n\t\t\taddress: email,\n\t\t\tcodeHash: null,\n\t\t\tattempts: 0,\n\t\t\texpiresAt,\n\t\t\tspentAt: null,\n\t\t\tcreatedAt: now,\n\t\t});\n\n\t\treturn { token: secret, email, expiresAt };\n\t};\n\n\t/** The type's password rule, or a wiring refusal for a JavaScript caller. */\n\tconst passwordRule = (where: string) => {\n\t\tif (type.password === null) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`${where}: the ${type.name} type does not sign in with a password — add password: { login } to it`,\n\t\t\t);\n\t\t}\n\t\treturn type.password;\n\t};\n\n\treturn {\n\t\tasync create(input) {\n\t\t\treturn toUser(await insert(input as Input, at('create')));\n\t\t},\n\n\t\tasync find(id) {\n\t\t\tconst record = await findRecord(context, id, type.name);\n\t\t\treturn record === null ? null : toUser(record);\n\t\t},\n\n\t\tasync get(id) {\n\t\t\treturn toUser(await getRecord(context, id, type.name, at('get')));\n\t\t},\n\n\t\tasync list(page) {\n\t\t\tconst after = page?.after ?? null;\n\t\t\t// Never a silent first page: a caller paging a list would loop for\n\t\t\t// ever, and the loop would look like a slow query.\n\t\t\tif (after !== null && !isId(after)) {\n\t\t\t\tthrow invalidCursor(at('list'), after);\n\t\t\t}\n\n\t\t\tconst answer = await store.users.listUsers({\n\t\t\t\ttype: type.name,\n\t\t\t\tafter,\n\t\t\t\tlimit: pageLimit(page?.limit, at('list')),\n\t\t\t});\n\t\t\treturn { items: answer.items.map(toUser), nextCursor: answer.nextCursor };\n\t\t},\n\n\t\tasync update(user, patch, options) {\n\t\t\tconst where = at('update');\n\t\t\tconst written = await writeUser(\n\t\t\t\tcontext,\n\t\t\t\tuser,\n\t\t\t\ttype,\n\t\t\t\toptions,\n\t\t\t\twhere,\n\t\t\t\tasync (record) => {\n\t\t\t\t\tconst fields = await validateFields(\n\t\t\t\t\t\ttype,\n\t\t\t\t\t\t{ ...record.fields, ...(patch as Input) },\n\t\t\t\t\t\twhere,\n\t\t\t\t\t);\n\t\t\t\t\tconst before = emailOf(type, record.fields);\n\t\t\t\t\tconst after = emailOf(type, fields);\n\t\t\t\t\tconst emailChanged =\n\t\t\t\t\t\t(before === null ? null : normalizeEmail(before)) !==\n\t\t\t\t\t\t(after === null ? null : normalizeEmail(after));\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tfields,\n\t\t\t\t\t\tlogins: loginsOf(type, fields, where),\n\t\t\t\t\t\tschemaVersion: type.schemaVersion,\n\t\t\t\t\t\t// A new e-mail is an unproven one.\n\t\t\t\t\t\t...(emailChanged ? { emailVerifiedAt: null } : {}),\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn toUser(written);\n\t\t},\n\n\t\tasync setActive(user, active, options) {\n\t\t\treturn toUser(\n\t\t\t\tawait writeUser(context, user, type, options, at('setActive'), () => ({\n\t\t\t\t\tactive: active === true,\n\t\t\t\t})),\n\t\t\t);\n\t\t},\n\n\t\tasync delete(user) {\n\t\t\tconst id = idOf(user);\n\t\t\tif (!isId(id)) return false;\n\n\t\t\t// Read first, so a staff API never deletes a patient: another type's\n\t\t\t// id is answered as nobody, and nothing of theirs is touched.\n\t\t\tconst record = await store.users.findUser(id);\n\t\t\tif (record !== null && record.type !== type.name) return false;\n\n\t\t\t// The user first: from then on nobody can sign in as them, and what\n\t\t\t// is left — sessions, tokens — is refused for a user who is gone. An\n\t\t\t// outage between the steps leaves only that inert remainder, and a\n\t\t\t// replay, finding no user, still deletes it.\n\t\t\tconst deleted = record !== null && (await store.users.deleteUser(id));\n\t\t\tawait store.sessions.deleteUserSessions(id);\n\t\t\tawait store.tokens.deleteUserTokens(id);\n\t\t\t// Last, and on a replay too: the tuples naming them.\n\t\t\tawait context.relations?.deleteEntity({ type: type.name, id });\n\t\t\treturn deleted;\n\t\t},\n\n\t\tasync signUp(input) {\n\t\t\tconst where = at('signUp');\n\t\t\tpasswordRule(where);\n\t\t\tif ((input as Input)?.password === undefined) {\n\t\t\t\tcheckPassword(type, undefined as unknown as string, where);\n\t\t\t}\n\t\t\tconst record = await insert(input as Input, where);\n\t\t\treturn openSession(context, type, record);\n\t\t},\n\n\t\tasync signIn(input) {\n\t\t\tconst where = at('signIn');\n\t\t\tconst rule = passwordRule(where);\n\t\t\tconst hasher = requireHasher(context, where);\n\t\t\tconst login = (input as Input)?.[rule.login];\n\t\t\tconst password = (input as Input)?.password;\n\n\t\t\tconst record =\n\t\t\t\ttypeof login === 'string' ? await byLogin(rule.normalize(login)) : null;\n\t\t\tconst refuse = (\n\t\t\t\treason: 'unknownLogin' | 'noPassword' | 'wrongPassword',\n\t\t\t) =>\n\t\t\t\tnew CredentialError(\n\t\t\t\t\t'CREDENTIALS_INVALID',\n\t\t\t\t\t`${where}: the login and the password do not match`,\n\t\t\t\t\t{ reason, userType: type.name },\n\t\t\t\t);\n\n\t\t\tif (record === null || record.password === null) {\n\t\t\t\t// Compared all the same, so the response time does not say which\n\t\t\t\t// logins are registered. The store's own latency stays observable;\n\t\t\t\t// that limit is documented, not denied.\n\t\t\t\tawait hasher.verify(String(password), await context.dummyHash());\n\t\t\t\tthrow refuse(record === null ? 'unknownLogin' : 'noPassword');\n\t\t\t}\n\n\t\t\tif (!(await passwordMatches(context, record, String(password), where))) {\n\t\t\t\tthrow refuse('wrongPassword');\n\t\t\t}\n\t\t\t// Checked after the password, so an inactive account is only told to\n\t\t\t// somebody who knows its password.\n\t\t\tif (!record.active) {\n\t\t\t\tthrow new UserInactiveError(`${where}: the user is inactive`, {\n\t\t\t\t\tuserId: record.id,\n\t\t\t\t\tuserType: type.name,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn openSession(\n\t\t\t\tcontext,\n\t\t\t\ttype,\n\t\t\t\tawait rehashed(context, record, String(password)),\n\t\t\t);\n\t\t},\n\n\t\tasync findByLogin(login) {\n\t\t\tconst rule = passwordRule(at('findByLogin'));\n\t\t\tconst record = await byLogin(rule.normalize(login));\n\t\t\treturn record === null ? null : toUser(record);\n\t\t},\n\n\t\tasync setPassword(user, password, options) {\n\t\t\tconst where = at('setPassword');\n\t\t\tpasswordRule(where);\n\t\t\tcheckPassword(type, password, where);\n\t\t\tconst hash = await requireHasher(context, where).hash(password);\n\n\t\t\treturn toUser(\n\t\t\t\tawait writeUser(context, user, type, options, where, (_, now) => ({\n\t\t\t\t\tpassword: { hash, updatedAt: now },\n\t\t\t\t})),\n\t\t\t);\n\t\t},\n\n\t\tasync changePassword(user, change, options) {\n\t\t\tconst where = at('changePassword');\n\t\t\tpasswordRule(where);\n\t\t\tcheckPassword(type, change?.next, where);\n\t\t\tconst hasher = requireHasher(context, where);\n\n\t\t\treturn toUser(\n\t\t\t\tawait writeUser(\n\t\t\t\t\tcontext,\n\t\t\t\t\tuser,\n\t\t\t\t\ttype,\n\t\t\t\t\toptions,\n\t\t\t\t\twhere,\n\t\t\t\t\tasync (record, now) => {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\trecord.password === null ||\n\t\t\t\t\t\t\t!(await passwordMatches(context, record, change.current, where))\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tthrow new CredentialError(\n\t\t\t\t\t\t\t\t'CREDENTIALS_INVALID',\n\t\t\t\t\t\t\t\t`${where}: the current password does not match`,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treason:\n\t\t\t\t\t\t\t\t\t\trecord.password === null ? 'noPassword' : 'wrongPassword',\n\t\t\t\t\t\t\t\t\tuserId: record.id,\n\t\t\t\t\t\t\t\t\tuserType: type.name,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tpassword: {\n\t\t\t\t\t\t\t\thash: await hasher.hash(change.next),\n\t\t\t\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t},\n\n\t\tverifyEmail: {\n\t\t\tasync send(user) {\n\t\t\t\tconst where = at('verifyEmail.send');\n\t\t\t\tconst record = await getRecord(context, idOf(user), type.name, where);\n\t\t\t\treturn issue('verifyEmail', record, where);\n\t\t\t},\n\n\t\t\tasync confirm(secret) {\n\t\t\t\tconst where = at('verifyEmail.confirm');\n\t\t\t\tconst { user } = await redeem('verifyEmail', secret, where);\n\t\t\t\treturn toUser(\n\t\t\t\t\tawait writeUser(\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t\tuser.id,\n\t\t\t\t\t\ttype,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\twhere,\n\t\t\t\t\t\t(_, now) => ({\n\t\t\t\t\t\t\temailVerifiedAt: now,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t},\n\t\t},\n\n\t\tresetPassword: {\n\t\t\tasync request(email) {\n\t\t\t\tconst where = at('resetPassword.request');\n\t\t\t\tpasswordRule(where);\n\t\t\t\tconst wanted = normalizeEmail(String(email));\n\t\t\t\tconst record = await byLogin(wanted);\n\n\t\t\t\t// Found by a login that is not their e-mail — a username that looks\n\t\t\t\t// like one — is nobody's e-mail: no token.\n\t\t\t\tconst held = record === null ? null : emailOf(type, record.fields);\n\t\t\t\tif (\n\t\t\t\t\trecord === null ||\n\t\t\t\t\theld === null ||\n\t\t\t\t\tnormalizeEmail(held) !== wanted\n\t\t\t\t) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\tconst issued = await issue('resetPassword', record, where);\n\t\t\t\treturn { ...issued, user: toUser(record) };\n\t\t\t},\n\n\t\t\tasync confirm(secret, password) {\n\t\t\t\tconst where = at('resetPassword.confirm');\n\t\t\t\tpasswordRule(where);\n\t\t\t\t// Checked before the token is spent: a password refused for its\n\t\t\t\t// length must not cost the visitor their link.\n\t\t\t\tcheckPassword(type, password, where);\n\t\t\t\tconst hash = await requireHasher(context, where).hash(password);\n\n\t\t\t\tconst { user } = await redeem('resetPassword', secret, where);\n\t\t\t\tconst written = await writeUser(\n\t\t\t\t\tcontext,\n\t\t\t\t\tuser.id,\n\t\t\t\t\ttype,\n\t\t\t\t\tundefined,\n\t\t\t\t\twhere,\n\t\t\t\t\t(record, now) => ({\n\t\t\t\t\t\tpassword: { hash, updatedAt: now },\n\t\t\t\t\t\t// The link reached the inbox: that proves the e-mail.\n\t\t\t\t\t\t...(record.emailVerifiedAt === null\n\t\t\t\t\t\t\t? { emailVerifiedAt: now }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\t// Whoever had the old password is signed out.\n\t\t\t\tawait store.sessions.revokeUserSessions(written.id, clock.now());\n\t\t\t\treturn toUser(written);\n\t\t\t},\n\t\t},\n\t};\n}\n",
|
|
16
16
|
"import { guardRelations } from '../stores/guard';\nimport { systemClock } from '../time/clock';\nimport { type JanusConfig, resolveConfig } from './config';\nimport { createContext } from './context';\nimport { guardStores } from './outage';\nimport { assertStores } from './port/assert-stores';\nimport { sharedApi } from './sessions';\nimport type { Checked, Janus } from './types';\nimport { typeApi } from './users';\n\n/**\n * Wires the identities side over the stores the application opened. **Synchronous,\n * and does no I/O**: it connects to nothing.\n *\n * ```ts\n * // One user type\n * const auth = janus({\n * user: z.object({ email: z.email(), name: z.string() }),\n * password: { login: 'email' },\n * store: createMemoryStores(),\n * hasher: scryptHasher(),\n * });\n * const { user, token } = await auth.signUp({ email, name, password });\n *\n * // Several\n * const auth = janus({\n * users: {\n * patient: { schema: Patient, password: { login: 'email' } },\n * staff: { schema: Staff, password: { login: 'email' }, session: { lifespan: '8h' } },\n * },\n * store, hasher,\n * });\n * await auth.staff.signIn({ email, password });\n * const current = await auth.authenticate(request, { type: 'staff' });\n * ```\n *\n * The types refuse first, on the offending key: a login or an e-mail that\n * names no required string field, a schema declaring a field `janus` sets, a\n * user type named like a method. What follows at run time is the net for\n * JavaScript callers, each a bare `TypeError` because only wiring produces one:\n * a store slot missing or incomplete; a password type with no hasher — there\n * is no silent fallback; two hashers claiming the same prefix, which would make\n * verification depend on the order they were listed in.\n */\nexport function janus<const C extends JanusConfig>(\n\tconfig: C & Checked<C>,\n): Janus<C> {\n\tconst where = 'janus';\n\tconst resolved = resolveConfig(config, where);\n\tconst capabilities = assertStores(config.store, where);\n\n\tconst hasher = config.hasher ?? null;\n\tconst verifiers = [\n\t\t...(hasher === null ? [] : [hasher]),\n\t\t...(config.verifiers ?? []),\n\t];\n\tconst prefixes = new Set<string>();\n\tfor (const verifier of verifiers) {\n\t\tif (typeof verifier?.prefix !== 'string' || verifier.prefix === '') {\n\t\t\tthrow new TypeError(`${where}: every hasher needs a non-empty prefix`);\n\t\t}\n\t\tif (prefixes.has(verifier.prefix)) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`${where}: two hashers claim the prefix \"${verifier.prefix}\" — which one verifies would depend on their order`,\n\t\t\t);\n\t\t}\n\t\tprefixes.add(verifier.prefix);\n\t}\n\n\tconst relations = config.relations ?? null;\n\tif (\n\t\trelations !== null &&\n\t\ttypeof (relations as { deleteEntity?: unknown }).deleteEntity !== 'function'\n\t) {\n\t\tthrow new TypeError(\n\t\t\t`${where}: relations must be a relation store — relations.deleteEntity is missing`,\n\t\t);\n\t}\n\n\tconst context = createContext(\n\t\tresolved,\n\t\tguardStores(config.store),\n\t\trelations === null ? null : guardRelations(relations),\n\t\tcapabilities,\n\t\tconfig.clock ?? systemClock,\n\t\thasher,\n\t\tverifiers,\n\t);\n\n\tconst shared = sharedApi(context);\n\tconst types = Object.fromEntries(\n\t\t[...resolved.types.values()].map((type) => [\n\t\t\ttype.name,\n\t\t\ttypeApi(context, type),\n\t\t]),\n\t);\n\n\t// The one cast at the boundary: every field was validated against its\n\t// type's schema before any of these answers was built.\n\tconst surface = resolved.single\n\t\t? { ...shared, ...types.user }\n\t\t: { ...shared, ...types };\n\treturn surface as unknown as Janus<C>;\n}\n"
|
|
17
17
|
],
|
|
18
|
-
"mappings": "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",
|
|
19
|
-
"debugId": "
|
|
18
|
+
"mappings": "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",
|
|
19
|
+
"debugId": "0C2151B9E5FBCB0864756E2164756E21",
|
|
20
20
|
"names": []
|
|
21
21
|
}
|
package/docs/README.md
CHANGED
|
@@ -28,6 +28,6 @@ below are defined once, in [Words](guide/vocabulary.md#words).
|
|
|
28
28
|
| --- | --- |
|
|
29
29
|
| [The shared vocabulary](guide/vocabulary.md) | You want the words the documentation uses, or subjects and the tuple notation, ids, cursors, durations or `fixedClock` |
|
|
30
30
|
| [Errors](guide/errors.md) | You are turning what the package throws into a status code, and want every code and what it carries |
|
|
31
|
-
| [Writing an adapter](guide/adapters.md) | You are implementing the identity stores or the relation store for your database, and running the conformance suites |
|
|
31
|
+
| [Writing an adapter](guide/adapters.md) | You are implementing the identity stores or the relation store for your database, upgrading one for `countAttempt` and the second factor, or running the conformance suites |
|
|
32
32
|
| [Troubleshooting](troubleshooting.md) | You have an error message and want its cause and its fix |
|
|
33
33
|
| [Roadmap](roadmap.md) | You want to know what is coming, what shipped, and what is deliberately not planned |
|
package/docs/guide/adapters.md
CHANGED
|
@@ -31,7 +31,7 @@ describeJanusStores({
|
|
|
31
31
|
|
|
32
32
|
| Port | Taken by | Methods |
|
|
33
33
|
| --- | --- | --- |
|
|
34
|
-
| `JanusStores` — `{ users: UserStore, sessions: SessionStore, tokens: TokenStore }` | `janus({ store })` | 6 + 6 (+ 1 optional) +
|
|
34
|
+
| `JanusStores` — `{ users: UserStore, sessions: SessionStore, tokens: TokenStore }` | `janus({ store })` | 6 + 6 (+ 1 optional) + 4 |
|
|
35
35
|
| `RelationStore` | `permissions({ store })`, `janus({ relations })` | 6 |
|
|
36
36
|
|
|
37
37
|
They are separate on purpose: an application that only authenticates
|
|
@@ -77,6 +77,43 @@ interface UserStore {
|
|
|
77
77
|
- `listUsers` pages in ascending id order; `after` is the last id of the
|
|
78
78
|
previous page, already checked by the core.
|
|
79
79
|
|
|
80
|
+
#### A user's password and second factor
|
|
81
|
+
|
|
82
|
+
Both are one field of `UserRecord`, `null` when the user has none, and a
|
|
83
|
+
patch treats both alike: **absent keeps it, `null` removes it, a value
|
|
84
|
+
replaces it whole**.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
interface UserRecord {
|
|
88
|
+
// …id, type, schemaVersion, active, fields, logins…
|
|
89
|
+
readonly password: { readonly hash: string; readonly updatedAt: Date } | null;
|
|
90
|
+
readonly secondFactor: {
|
|
91
|
+
readonly method: 'totp';
|
|
92
|
+
readonly secret: string; // opaque: store it byte for byte
|
|
93
|
+
readonly confirmedAt: Date | null; // null while enrolment waits for a first code
|
|
94
|
+
readonly lastStep: number | null; // the time step of the last code accepted
|
|
95
|
+
} | null;
|
|
96
|
+
// …emailVerifiedAt, version, createdAt, updatedAt
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`secret` is **opaque to a store**: once the second factor ships, the core
|
|
101
|
+
will seal it with a key the application holds before a store sees it, so a
|
|
102
|
+
dump of the users cannot produce a code. Keep it like a password hash — byte
|
|
103
|
+
for byte, no parsing, no trimming. Store
|
|
104
|
+
the second factor whole: a method without a secret, or a `lastStep` without a
|
|
105
|
+
method, is a record the core never writes.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import type { UserPatch } from '@nxgt/janus';
|
|
109
|
+
|
|
110
|
+
const keep: UserPatch = { updatedAt: new Date() }; // secondFactor untouched
|
|
111
|
+
const remove: UserPatch = { updatedAt: new Date(), secondFactor: null };
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
An adapter that stored users before this field existed reads its absence as
|
|
115
|
+
`null`, never `undefined` (rule 2): a user with no second factor holds `null`.
|
|
116
|
+
|
|
80
117
|
### `SessionStore` and `TokenStore`
|
|
81
118
|
|
|
82
119
|
```ts
|
|
@@ -93,6 +130,7 @@ interface SessionStore {
|
|
|
93
130
|
interface TokenStore {
|
|
94
131
|
insertToken(record: TokenRecord): Promise<void>;
|
|
95
132
|
consumeToken(tokenHash: string, kind: TokenKind, at: Date): Promise<TokenRecord | null>;
|
|
133
|
+
countAttempt(tokenHash: string, kind: TokenKind): Promise<TokenRecord | null>;
|
|
96
134
|
deleteUserTokens(userId: Id): Promise<number>;
|
|
97
135
|
}
|
|
98
136
|
```
|
|
@@ -103,12 +141,83 @@ Twenty concurrent calls must produce exactly one answer with `spentAt: null`;
|
|
|
103
141
|
in MongoDB that is one `findOneAndUpdate` returning the document before the
|
|
104
142
|
update. A read followed by a write lets two requests redeem one reset token.
|
|
105
143
|
|
|
144
|
+
A token is its hash, never its secret, and what it is for:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
type TokenKind = 'verifyEmail' | 'resetPassword' | 'secondFactor' | 'signInCode';
|
|
148
|
+
|
|
149
|
+
interface TokenRecord {
|
|
150
|
+
readonly tokenHash: string;
|
|
151
|
+
readonly kind: TokenKind;
|
|
152
|
+
readonly userId: Id;
|
|
153
|
+
readonly address: string;
|
|
154
|
+
readonly codeHash: string | null; // a signInCode's code, hashed; null for every other kind
|
|
155
|
+
readonly attempts: number; // 0 at insertion
|
|
156
|
+
readonly expiresAt: Date;
|
|
157
|
+
readonly spentAt: Date | null;
|
|
158
|
+
readonly createdAt: Date;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
A token redeemed for another kind is unknown: every method that takes a
|
|
163
|
+
`kind` matches on it. `codeHash` and `attempts` round-trip like every other
|
|
164
|
+
field. An adapter whose stored tokens predate them reads them as `null` and
|
|
165
|
+
`0`, as the three published adapters do, so no data migration is needed for
|
|
166
|
+
them.
|
|
167
|
+
|
|
106
168
|
Expiry is the core's decision: a read answers a stored session verbatim,
|
|
107
169
|
lapsed or revoked, and never a record it has changed. A store with its own
|
|
108
170
|
expiry — a TTL index, a Redis key TTL — may drop a lapsed session or token
|
|
109
171
|
before anyone asks: reads then answer `null`, and `deleteUserSessions` does
|
|
110
172
|
not count it. The conformance suite accepts both.
|
|
111
173
|
|
|
174
|
+
### `TokenStore.countAttempt`
|
|
175
|
+
|
|
176
|
+
Counts one attempt at a code against a token, and answers the token **as it
|
|
177
|
+
is after the call** — what bounds the attempts at a six-digit code:
|
|
178
|
+
|
|
179
|
+
| The stored token | Written | Answered |
|
|
180
|
+
| --- | --- | --- |
|
|
181
|
+
| unspent, of this `kind` | `attempts + 1` | the token, with the new count |
|
|
182
|
+
| spent, of this `kind` | nothing | the token as it is |
|
|
183
|
+
| another `kind`, or no token with this hash | nothing | `null` |
|
|
184
|
+
|
|
185
|
+
Like `consumeToken`, it is **one conditional write**, never a read followed by
|
|
186
|
+
a write: twenty concurrent calls answer the counts 1 to 20, each once. A count
|
|
187
|
+
two attempts both read is an attempt for free. Whether the count is past the
|
|
188
|
+
limit, and whether the code matches, is the core's decision after the call;
|
|
189
|
+
spending the token stays `consumeToken`'s.
|
|
190
|
+
|
|
191
|
+
A **lapsed** token is counted all the same, or answered `null` by a store that
|
|
192
|
+
has already dropped it (a TTL index, a Redis key TTL). Do not compare
|
|
193
|
+
`expiresAt` in the store: as for `consumeToken`, the core compares it after
|
|
194
|
+
the call.
|
|
195
|
+
|
|
196
|
+
In MongoDB, one `findOneAndUpdate` answering the document after it, then a
|
|
197
|
+
plain read for the spent case:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import type { TokenStore } from '@nxgt/janus';
|
|
201
|
+
|
|
202
|
+
// tokens: your collection; toToken: your document → TokenRecord
|
|
203
|
+
export const countAttempt: TokenStore['countAttempt'] = async (tokenHash, kind) => {
|
|
204
|
+
const after = await tokens.findOneAndUpdate(
|
|
205
|
+
{ _id: tokenHash, kind, spentAt: null },
|
|
206
|
+
{ $inc: { attempts: 1 } },
|
|
207
|
+
{ returnDocument: 'after' },
|
|
208
|
+
);
|
|
209
|
+
if (after !== null) return toToken(after);
|
|
210
|
+
const spent = await tokens.findOne({ _id: tokenHash, kind }); // written nothing
|
|
211
|
+
return spent === null ? null : toToken(spent);
|
|
212
|
+
};
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
In SQL, `update … set attempts = attempts + 1 where token_hash = $1 and kind =
|
|
216
|
+
$2 and spent_at is null returning *`, then the same plain read. In Redis, one
|
|
217
|
+
Lua script: `HINCRBY` only when `spentAt` is empty, then `HGETALL`. Wrap the
|
|
218
|
+
driver's error in `StoreFailure` as in [the six rules](#the-six-rules):
|
|
219
|
+
`countAttempt` has its own outage case.
|
|
220
|
+
|
|
112
221
|
### `RelationStore`
|
|
113
222
|
|
|
114
223
|
```ts
|
|
@@ -172,7 +281,7 @@ An adapter **defines no error class**. It throws `@nxgt/janus`'s own
|
|
|
172
281
|
copy of each class and `instanceof` holds in the application. A cursor it
|
|
173
282
|
cannot read is `invalidCursor(where, cursor)`. Records, patches and page
|
|
174
283
|
requests are exported as types: `UserRecord`, `UserPatch`, `UserPageRequest`,
|
|
175
|
-
`PasswordRecord`, `SessionRecord`, `TokenRecord`, `TokenKind`, `Json`,
|
|
284
|
+
`PasswordRecord`, `SecondFactorRecord`, `SessionRecord`, `TokenRecord`, `TokenKind`, `Json`,
|
|
176
285
|
`JsonObject`, and `ObjectPageRequest`, `RelationChanges` from
|
|
177
286
|
`@nxgt/janus/permissions`.
|
|
178
287
|
|
|
@@ -185,7 +294,7 @@ compile error naming the missing method.
|
|
|
185
294
|
|
|
186
295
|
| Suite | Cases | Harness opens |
|
|
187
296
|
| --- | --- | --- |
|
|
188
|
-
| `describeJanusStores({ name, harness, runner?, faults?, skip? })` |
|
|
297
|
+
| `describeJanusStores({ name, harness, runner?, faults?, skip? })` | 44: users, sessions, tokens, and one outage per method whose honest answer can be "nothing" — twelve of them | `{ stores, faults?, close? }` |
|
|
189
298
|
| `describeRelationStores({ name, harness, runner?, faults?, skip? })` | 15: the relation store, and one outage per method | `{ store, faults?, close? }` |
|
|
190
299
|
|
|
191
300
|
`harness.open()` is called **once per case** and must answer fresh, empty
|
|
@@ -202,6 +311,18 @@ stores: a case that leaks into the next is the hardest failure to debug.
|
|
|
202
311
|
|
|
203
312
|
The suites import no test framework and no assertion library.
|
|
204
313
|
|
|
314
|
+
The second factor and attempts have their own cases — skip one by its id
|
|
315
|
+
while you work on it, never to ship:
|
|
316
|
+
|
|
317
|
+
| Case | Checks |
|
|
318
|
+
| --- | --- |
|
|
319
|
+
| `users.secondFactorSlot` | round-trip; a patch not naming it keeps it; `null` removes it |
|
|
320
|
+
| `tokens.countAttempt` | two calls answer `attempts` 1 then 2, `codeHash` as written; `consumeToken` answers the count |
|
|
321
|
+
| `tokens.countAttemptConcurrency` | twenty concurrent calls answer 1 to 20, each once |
|
|
322
|
+
| `tokens.countAttemptRace` | attempts racing one redemption: the counts answered unspent are 1 to the final count, and every answer after the spend carries that final count |
|
|
323
|
+
| `tokens.countAttemptSpent` | a spent token answered unchanged; another kind and an unknown hash answer `null` and count nothing |
|
|
324
|
+
| `outage.countAttempt` | a store that cannot answer rejects, never `null` |
|
|
325
|
+
|
|
205
326
|
### `faults`: prove the outage invariant
|
|
206
327
|
|
|
207
328
|
`faults` is optional, and **its absence is reported, never passed over**:
|
package/docs/guide/vocabulary.md
CHANGED
|
@@ -47,7 +47,9 @@ either finds this row.
|
|
|
47
47
|
| **anonymous** | A request that presents no session credential, or one that authenticates nobody: `authenticate` answers `null` | "unauthenticated", "guest", "logged out" |
|
|
48
48
|
| **bearer client** | A client that sends its session token as `Authorization: Bearer` rather than in a cookie | |
|
|
49
49
|
| **one-time token** | A single-use token sent by e-mail, for verification or a password reset | "code" alone — `code` is an error's code |
|
|
50
|
-
| **one-time code** |
|
|
50
|
+
| **one-time code** | In progress, see [the roadmap](../roadmap.md): a one-time token short enough to type, sent by e-mail — or, for TOTP, computed by an authenticator app and never sent | "OTP", "PIN", "code" alone |
|
|
51
|
+
| **second factor** | What a user proves at sign-in beyond their password: a TOTP secret their authenticator app holds, `UserRecord.secondFactor`. **Enrolled** when stored, **confirmed** once a first code matched (`confirmedAt`) | "2FA", "MFA", "OTP device" |
|
|
52
|
+
| **attempt** | One code tried against a one-time token, counted by `countAttempt` in the token's `attempts` | "try"; "retry" is a repeated write, never a guess |
|
|
51
53
|
| **token** | Never alone in prose: a *session token* or a *one-time token*. The `tokens` store and the `TOKEN_*` codes are one-time tokens only | |
|
|
52
54
|
| **e-mail flow** | `verifyEmail` or `resetPassword`: send a one-time token, then confirm it | |
|
|
53
55
|
|
package/docs/roadmap.md
CHANGED
|
@@ -5,14 +5,16 @@ dates here, and the version something shipped in is the only number.
|
|
|
5
5
|
|
|
6
6
|
## Now
|
|
7
7
|
|
|
8
|
-
Nothing yet.
|
|
9
|
-
|
|
10
|
-
## Next
|
|
11
|
-
|
|
12
8
|
- **One-time codes** — a one-time token short enough to type, sent by e-mail
|
|
13
9
|
to sign in without a password, or to confirm a sensitive action, issued
|
|
14
10
|
and redeemed by `janus` like the verification and reset tokens today; and
|
|
15
|
-
TOTP, the one-time codes of an authenticator app, as a second factor
|
|
11
|
+
TOTP, the one-time codes of an authenticator app, as a second factor, its
|
|
12
|
+
secret sealed with a key your application holds. `signIn` will answer
|
|
13
|
+
`{ status: 'signedIn' }` or `{ status: 'secondFactor', challenge }`. The
|
|
14
|
+
store port that holds them shipped in v0.4.0, below.
|
|
15
|
+
|
|
16
|
+
## Next
|
|
17
|
+
|
|
16
18
|
- **Sending the e-mails** — in a package of its own, `@nxgt/janus-mail`, built
|
|
17
19
|
on a general mail toolkit shared with applications that are not about
|
|
18
20
|
sign-in: a `Mailer` port you plug your transport into (SMTP, Resend, SES…) —
|
|
@@ -84,6 +86,13 @@ Nothing yet.
|
|
|
84
86
|
|
|
85
87
|
Each entry names the version it came in.
|
|
86
88
|
|
|
89
|
+
- **The store port holds a second factor and counts attempts on a token,
|
|
90
|
+
v0.4.0** — `UserRecord.secondFactor`, a token's `codeHash` and `attempts`,
|
|
91
|
+
the token kinds `'secondFactor'` and `'signInCode'`, and
|
|
92
|
+
`TokenStore.countAttempt`, one conditional write per attempt, with six new
|
|
93
|
+
conformance cases. No flow uses them yet; the published adapters implement
|
|
94
|
+
them in `@nxgt/janus-drizzle` 0.2, `@nxgt/janus-mongo` 0.3 and
|
|
95
|
+
`@nxgt/janus-redis` 0.2.
|
|
87
96
|
- **Permissions on a user, v0.3.0** — a user type may also be an object type:
|
|
88
97
|
a staff member is the object `can()` asks about and is granted relations
|
|
89
98
|
on, like a record, and `grant(note, 'readers', setOf(bob, 'managers'))`
|
package/docs/troubleshooting.md
CHANGED
|
@@ -208,6 +208,10 @@ Also: `janus: store.<slot> is missing`, `janus: store must be an object with use
|
|
|
208
208
|
|
|
209
209
|
**When:** `janus({...})`, from JavaScript or with a store typed loosely. TypeScript refuses a partial store at compile time and names the method.
|
|
210
210
|
**Why:** `store` is `{ users, sessions, tokens }`, and each slot must answer every method of the port. `deleteExpiredSessions` is the one optional method: absent, or a function.
|
|
211
|
+
An adapter written against `@nxgt/janus` 0.3 reports
|
|
212
|
+
`store.tokens has no method countAttempt` until it implements the method 0.4
|
|
213
|
+
added.
|
|
214
|
+
|
|
211
215
|
**Fix:** pass the three stores, whole:
|
|
212
216
|
|
|
213
217
|
```ts
|
|
@@ -216,6 +220,18 @@ import { createMemoryStores, janus } from '@nxgt/janus';
|
|
|
216
220
|
janus({ ..., store: createMemoryStores() });
|
|
217
221
|
```
|
|
218
222
|
|
|
223
|
+
For `countAttempt`, upgrade the published adapter to the release that
|
|
224
|
+
implements it — `@nxgt/janus-drizzle` 0.2, `@nxgt/janus-mongo` 0.3,
|
|
225
|
+
`@nxgt/janus-redis` 0.2:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
bun add @nxgt/janus@^0.4 @nxgt/janus-drizzle@^0.2 # or @nxgt/janus-mongo@^0.3, @nxgt/janus-redis@^0.2
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Your own adapter implements it as
|
|
232
|
+
[`TokenStore.countAttempt`](guide/adapters.md#tokenstorecountattempt) sets
|
|
233
|
+
out, then runs the conformance suite.
|
|
234
|
+
|
|
219
235
|
### `janus: relations must be a relation store — relations.deleteEntity is missing`
|
|
220
236
|
|
|
221
237
|
**When:** `janus({ ..., relations })`.
|