@nxgt/janus 0.2.0 → 0.2.2
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 +3 -2
- package/dist/auth/context.d.ts +1 -1
- package/dist/auth/context.d.ts.map +1 -1
- package/dist/auth/port/types.d.ts +3 -1
- package/dist/auth/port/types.d.ts.map +1 -1
- package/dist/auth/sessions.d.ts.map +1 -1
- package/dist/auth/users.d.ts.map +1 -1
- package/dist/conformance/cases/users.d.ts.map +1 -1
- package/dist/conformance/index.js +16 -1
- package/dist/conformance/index.js.map +3 -3
- package/dist/index.js +52 -10
- package/dist/index.js.map +7 -6
- package/dist/stores/storable.d.ts +15 -0
- package/dist/stores/storable.d.ts.map +1 -0
- package/docs/guide/adapters.md +5 -2
- package/docs/guide/sessions.md +3 -1
- package/docs/guide/users.md +6 -2
- package/docs/guide/vocabulary.md +3 -2
- package/docs/roadmap.md +48 -16
- package/docs/troubleshooting.md +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -279,6 +279,10 @@ function normalizer(rule, where) {
|
|
|
279
279
|
throw new TypeError(`${where} must be "none", "lowercase", "lowercaseTrim", "nfkcLowercaseTrim" or a function`);
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
// src/stores/storable.ts
|
|
283
|
+
var isStorable = (value) => !value.includes("\x00") && value.isWellFormed();
|
|
284
|
+
var UNSTORABLE = "holds a NUL character or a lone surrogate, which no store can keep";
|
|
285
|
+
|
|
282
286
|
// src/auth/outage.ts
|
|
283
287
|
function guardStores(stores) {
|
|
284
288
|
return {
|
|
@@ -339,7 +343,7 @@ var idOf = (user) => typeof user === "string" ? user : user.id;
|
|
|
339
343
|
async function validateFields(type, input, where) {
|
|
340
344
|
const result = await type.schema["~standard"].validate(input);
|
|
341
345
|
const issues = result.issues === undefined ? [] : result.issues.map((issue) => ({
|
|
342
|
-
path: (issue.path ?? []).map(segmentKey),
|
|
346
|
+
path: storablePrefix((issue.path ?? []).map(segmentKey)),
|
|
343
347
|
message: issue.message
|
|
344
348
|
}));
|
|
345
349
|
if (result.issues === undefined) {
|
|
@@ -349,12 +353,39 @@ async function validateFields(type, input, where) {
|
|
|
349
353
|
issues.push({ path: [key], message: "set by janus, not by a request" });
|
|
350
354
|
}
|
|
351
355
|
}
|
|
356
|
+
unstorableIn(value, [], issues);
|
|
352
357
|
}
|
|
353
358
|
if (issues.length > 0) {
|
|
354
359
|
throw new UserInvalidError2(`${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(", ")})`, { issues, userType: type.name });
|
|
355
360
|
}
|
|
356
361
|
return withoutUndefined(result.value);
|
|
357
362
|
}
|
|
363
|
+
function unstorableIn(value, path, issues) {
|
|
364
|
+
if (typeof value === "string") {
|
|
365
|
+
if (!isStorable(value))
|
|
366
|
+
issues.push({ path, message: UNSTORABLE });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (Array.isArray(value)) {
|
|
370
|
+
for (const [index, inner] of value.entries()) {
|
|
371
|
+
unstorableIn(inner, [...path, index], issues);
|
|
372
|
+
}
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (typeof value === "object" && value !== null) {
|
|
376
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
377
|
+
if (!isStorable(key)) {
|
|
378
|
+
issues.push({ path, message: `a key ${UNSTORABLE}` });
|
|
379
|
+
} else {
|
|
380
|
+
unstorableIn(inner, [...path, key], issues);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function storablePrefix(path) {
|
|
386
|
+
const cut = path.findIndex((segment) => typeof segment === "string" && !isStorable(segment));
|
|
387
|
+
return cut === -1 ? path : path.slice(0, cut);
|
|
388
|
+
}
|
|
358
389
|
function segmentKey(segment) {
|
|
359
390
|
const key = typeof segment === "object" ? segment.key : segment;
|
|
360
391
|
return typeof key === "number" ? key : String(key);
|
|
@@ -376,14 +407,24 @@ function emailOf(type, fields) {
|
|
|
376
407
|
const value = fields[type.email];
|
|
377
408
|
return typeof value === "string" ? value : null;
|
|
378
409
|
}
|
|
379
|
-
function loginsOf(type, fields) {
|
|
410
|
+
function loginsOf(type, fields, where) {
|
|
380
411
|
const logins = new Set;
|
|
381
412
|
if (type.password !== null) {
|
|
382
413
|
const login = fields[type.password.login];
|
|
383
414
|
if (typeof login !== "string") {
|
|
384
415
|
throw new TypeError(`janus: password.login "${type.password.login}" did not name a string in validated ${type.name} fields — it must name a required string field`);
|
|
385
416
|
}
|
|
386
|
-
|
|
417
|
+
const normalized = type.password.normalize(login);
|
|
418
|
+
if (!isStorable(normalized)) {
|
|
419
|
+
const path = [type.password.login];
|
|
420
|
+
throw new UserInvalidError2(`${where}: the fields do not match the ${type.name} schema (1 issue, at ${path.join(".")})`, {
|
|
421
|
+
issues: [
|
|
422
|
+
{ path, message: `normalises to a login that ${UNSTORABLE}` }
|
|
423
|
+
],
|
|
424
|
+
userType: type.name
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
logins.add(normalized);
|
|
387
428
|
}
|
|
388
429
|
const email = emailOf(type, fields);
|
|
389
430
|
if (email !== null)
|
|
@@ -575,7 +616,7 @@ function sharedApi(context) {
|
|
|
575
616
|
const id = idOf(user);
|
|
576
617
|
if (!isId2(id))
|
|
577
618
|
return 0;
|
|
578
|
-
return options?.except === undefined ? store.sessions.revokeUserSessions(id, clock.now()) : store.sessions.revokeUserSessions(id, clock.now(), options.except);
|
|
619
|
+
return options?.except === undefined || !isId2(options.except) ? store.sessions.revokeUserSessions(id, clock.now()) : store.sessions.revokeUserSessions(id, clock.now(), options.except);
|
|
579
620
|
},
|
|
580
621
|
async findUser(id) {
|
|
581
622
|
const record = await findRecord(context, id, null);
|
|
@@ -657,6 +698,7 @@ function headersOf(request) {
|
|
|
657
698
|
function typeApi(context, type) {
|
|
658
699
|
const { store, clock } = context;
|
|
659
700
|
const at = (operation) => context.config.single ? operation : `${type.name}.${operation}`;
|
|
701
|
+
const byLogin = async (login) => isStorable(login) ? store.users.findUserByLogin(type.name, login) : null;
|
|
660
702
|
const insert = async (input, where) => {
|
|
661
703
|
const { password, active, ...rest } = input ?? {};
|
|
662
704
|
const now = clock.now();
|
|
@@ -675,7 +717,7 @@ function typeApi(context, type) {
|
|
|
675
717
|
schemaVersion: type.schemaVersion,
|
|
676
718
|
active: active === undefined ? true : active === true,
|
|
677
719
|
fields,
|
|
678
|
-
logins: loginsOf(type, fields),
|
|
720
|
+
logins: loginsOf(type, fields, where),
|
|
679
721
|
password: hash,
|
|
680
722
|
emailVerifiedAt: null,
|
|
681
723
|
version: 0,
|
|
@@ -774,7 +816,7 @@ function typeApi(context, type) {
|
|
|
774
816
|
const emailChanged = (before === null ? null : normalizeEmail(before)) !== (after === null ? null : normalizeEmail(after));
|
|
775
817
|
return {
|
|
776
818
|
fields,
|
|
777
|
-
logins: loginsOf(type, fields),
|
|
819
|
+
logins: loginsOf(type, fields, where),
|
|
778
820
|
schemaVersion: type.schemaVersion,
|
|
779
821
|
...emailChanged ? { emailVerifiedAt: null } : {}
|
|
780
822
|
};
|
|
@@ -814,7 +856,7 @@ function typeApi(context, type) {
|
|
|
814
856
|
const hasher = requireHasher(context, where);
|
|
815
857
|
const login = input?.[rule.login];
|
|
816
858
|
const password = input?.password;
|
|
817
|
-
const record = typeof login === "string" ? await
|
|
859
|
+
const record = typeof login === "string" ? await byLogin(rule.normalize(login)) : null;
|
|
818
860
|
const refuse = (reason) => new CredentialError2("CREDENTIALS_INVALID", `${where}: the login and the password do not match`, { reason, userType: type.name });
|
|
819
861
|
if (record === null || record.password === null) {
|
|
820
862
|
await hasher.verify(String(password), await context.dummyHash());
|
|
@@ -833,7 +875,7 @@ function typeApi(context, type) {
|
|
|
833
875
|
},
|
|
834
876
|
async findByLogin(login) {
|
|
835
877
|
const rule = passwordRule(at("findByLogin"));
|
|
836
|
-
const record = await
|
|
878
|
+
const record = await byLogin(rule.normalize(login));
|
|
837
879
|
return record === null ? null : toUser(record);
|
|
838
880
|
},
|
|
839
881
|
async setPassword(user, password, options) {
|
|
@@ -885,7 +927,7 @@ function typeApi(context, type) {
|
|
|
885
927
|
const where = at("resetPassword.request");
|
|
886
928
|
passwordRule(where);
|
|
887
929
|
const wanted = normalizeEmail(String(email));
|
|
888
|
-
const record = await
|
|
930
|
+
const record = await byLogin(wanted);
|
|
889
931
|
const held = record === null ? null : emailOf(type, record.fields);
|
|
890
932
|
if (record === null || held === null || normalizeEmail(held) !== wanted) {
|
|
891
933
|
return null;
|
|
@@ -979,5 +1021,5 @@ export {
|
|
|
979
1021
|
systemClock
|
|
980
1022
|
};
|
|
981
1023
|
|
|
982
|
-
//# debugId=
|
|
1024
|
+
//# debugId=A06CA262C3DD58D164756E2164756E21
|
|
983
1025
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/auth/hashers.ts", "../src/time/clock.ts", "../src/time/duration.ts", "../src/auth/config.ts", "../src/auth/outage.ts", "../src/auth/context.ts", "../src/auth/port/assert-stores.ts", "../src/auth/secrets.ts", "../src/auth/sessions.ts", "../src/auth/users.ts", "../src/auth/janus.ts"],
|
|
3
|
+
"sources": ["../src/auth/hashers.ts", "../src/time/clock.ts", "../src/time/duration.ts", "../src/auth/config.ts", "../src/stores/storable.ts", "../src/auth/outage.ts", "../src/auth/context.ts", "../src/auth/port/assert-stores.ts", "../src/auth/secrets.ts", "../src/auth/sessions.ts", "../src/auth/users.ts", "../src/auth/janus.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { randomBytes, scrypt, timingSafeEqual } from 'node:crypto';\nimport type { PasswordHasher } from './config';\n\n/**\n * scrypt through `node:crypto` — the hasher that runs **everywhere**: Node and\n * Bun both implement it, and it needs no dependency.\n *\n * Measured before choosing it: `bun build --target node` leaves `Bun.password`\n * in the output as it is — a target changes the bundle's format, not which\n * APIs exist — and `crypto.argon2` is absent from Node 22. scrypt is the one\n * memory-hard function both runtimes ship.\n *\n * The defaults are OWASP's: N = 2^17, r = 8, p = 1, about 128 MiB per hash.\n * The parameters are written into the hash —\n * `$scrypt$ln=17,r=8,p=1$<salt>$<key>` — and read back from it to verify, so\n * raising `cost` later leaves every existing hash readable.\n *\n * `cost` is log2(N). Lower it only in tests: 10 is fast and still exercises\n * every line.\n *\n * A hash written with other parameters than these is rewritten with these the\n * next time its owner signs in (`needsRehash`), so raising `cost` reaches every\n * active user without a migration.\n */\nexport function scryptHasher(options?: {\n\treadonly cost?: number;\n}): PasswordHasher {\n\tconst cost = options?.cost ?? 17;\n\tif (!Number.isInteger(cost) || cost < 10 || cost > 20) {\n\t\tthrow new TypeError(\n\t\t\t'scryptHasher: cost is log2(N), an integer from 10 to 20 — 17 is the recommended value',\n\t\t);\n\t}\n\n\treturn {\n\t\tprefix: SCRYPT_PREFIX,\n\t\tasync hash(plain) {\n\t\t\tconst salt = randomBytes(16);\n\t\t\tconst key = await derive(plain, salt, cost, 8, 1);\n\t\t\treturn `${SCRYPT_PREFIX}ln=${cost},r=8,p=1$${salt.toString('base64')}$${key.toString('base64')}`;\n\t\t},\n\t\tasync verify(plain, hash) {\n\t\t\tconst parsed = SCRYPT_HASH.exec(hash);\n\t\t\t// A hash this hasher cannot parse is not a wrong password, and not\n\t\t\t// something to guess at: the core picked this verifier by prefix, so\n\t\t\t// the stored hash is corrupt.\n\t\t\tif (parsed === null) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t'scryptHasher: the stored hash has the $scrypt$ prefix and not its format',\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst [, ln, r, p, salt, key] = parsed as unknown as [\n\t\t\t\tstring,\n\t\t\t\tstring,\n\t\t\t\tstring,\n\t\t\t\tstring,\n\t\t\t\tstring,\n\t\t\t\tstring,\n\t\t\t];\n\t\t\tconst expected = Buffer.from(key, 'base64');\n\t\t\tconst actual = await derive(\n\t\t\t\tplain,\n\t\t\t\tBuffer.from(salt, 'base64'),\n\t\t\t\tNumber(ln),\n\t\t\t\tNumber(r),\n\t\t\t\tNumber(p),\n\t\t\t\texpected.length,\n\t\t\t);\n\t\t\treturn timingSafeEqual(actual, expected);\n\t\t},\n\t\tneedsRehash(hash) {\n\t\t\tconst parsed = SCRYPT_HASH.exec(hash);\n\t\t\t// Unparseable is verify's refusal to make, not a reason to rewrite.\n\t\t\tif (parsed === null) return false;\n\t\t\tconst [, ln, r, p] = parsed;\n\t\t\treturn Number(ln) !== cost || r !== '8' || p !== '1';\n\t\t},\n\t};\n}\n\nconst SCRYPT_PREFIX = '$scrypt$';\nconst SCRYPT_HASH =\n\t/^\\$scrypt\\$ln=(\\d+),r=(\\d+),p=(\\d+)\\$([A-Za-z0-9+/=]+)\\$([A-Za-z0-9+/=]+)$/;\n\nfunction derive(\n\tplain: string,\n\tsalt: Buffer,\n\tln: number,\n\tr: number,\n\tp: number,\n\tlength = 32,\n): Promise<Buffer> {\n\t// Node's camelCase aliases for N, r and p: this repository has no other\n\t// casing, and Node accepts both spellings.\n\tconst n = 2 ** ln;\n\treturn new Promise((resolve, reject) => {\n\t\t// maxmem: Node's default (32 MiB) is below what N = 2^17, r = 8 needs.\n\t\t// `error` is null on Node and undefined on Bun when there is none —\n\t\t// measured: testing `=== null` rejected every hash under Bun.\n\t\tscrypt(\n\t\t\tplain,\n\t\t\tsalt,\n\t\t\tlength,\n\t\t\t{ cost: n, blockSize: r, parallelization: p, maxmem: 256 * n * r },\n\t\t\t(error, key) => (error ? reject(error) : resolve(key)),\n\t\t);\n\t});\n}\n\n/**\n * argon2id through `Bun.password`.\n *\n * **Loaded only when named**: nothing reads `Bun` until this function is called,\n * so the package imports cleanly under Node. On Node, `scryptHasher()` is the\n * hasher; a hash written here reads anywhere a verifier for `$argon2id$` is\n * wired, so a database can move between the two runtimes.\n */\nexport function bunHasher(): PasswordHasher {\n\t// `typeof` on an undeclared global is 'undefined' rather than a throw, so\n\t// this line is safe under Node.\n\tconst password: BunPassword | undefined =\n\t\ttypeof Bun === 'undefined' ? undefined : Bun.password;\n\n\tif (password === undefined) {\n\t\tthrow new TypeError(\n\t\t\t'bunHasher: Bun.password is not available — this runtime is not Bun; wire scryptHasher() instead',\n\t\t);\n\t}\n\n\treturn {\n\t\tprefix: '$argon2id$',\n\t\thash: (plain) => password.hash(plain, ARGON2ID),\n\t\tverify: (plain, hash) => password.verify(plain, hash),\n\t\tneedsRehash: (hash) => !hash.startsWith(ARGON2ID_PARAMETERS),\n\t};\n}\n\n/**\n * Pinned rather than left to `Bun.password`'s defaults — which they equal\n * today, measured: `$argon2id$v=19$m=65536,t=2,p=1$` — so that a change of\n * default in a Bun release is a rehash this package decided, not one it\n * inherited.\n */\nconst ARGON2ID = {\n\talgorithm: 'argon2id',\n\tmemoryCost: 65_536,\n\ttimeCost: 2,\n} as const;\nconst ARGON2ID_PARAMETERS = '$argon2id$v=19$m=65536,t=2,p=1$';\n\ninterface BunPassword {\n\thash(plain: string, options: typeof ARGON2ID): Promise<string>;\n\tverify(plain: string, hash: string): Promise<boolean>;\n}\n",
|
|
6
6
|
"/**\n * Where `now` comes from.\n *\n * Injected rather than read from `Date` directly, for one practical reason:\n * every expiry in this package — sessions, one-time tokens, refresh windows —\n * is a comparison against the current time, and a spec that has to move the\n * real clock to test one is a spec that needs fake timers, leaks them, and\n * fails differently under load. A `Clock` makes those specs synchronous and\n * exact.\n *\n * It is also the seam an application needs if it ever has to reason about a\n * store's clock rather than its own: MongoDB stamps a TTL against the server's\n * time, not the process's.\n */\nexport interface Clock {\n\tnow(): Date;\n}\n\n/** The process's own clock. The default everywhere. */\nexport const systemClock: Clock = {\n\tnow: () => new Date(),\n};\n\n/**\n * A clock a spec drives by hand.\n *\n * Shipped rather than kept in `test/`: a consumer writing their own tests\n * against this package needs exactly this, and writing it again is how two\n * subtly different versions of \"advance time\" come to exist.\n */\nexport function fixedClock(start: Date | number = 0): Clock & {\n\tadvance(ms: number): void;\n\tset(at: Date | number): void;\n} {\n\tlet ms = start instanceof Date ? start.getTime() : start;\n\n\treturn {\n\t\tnow: () => new Date(ms),\n\t\tadvance: (by) => {\n\t\t\tms += by;\n\t\t},\n\t\tset: (at) => {\n\t\t\tms = at instanceof Date ? at.getTime() : at;\n\t\t},\n\t};\n}\n",
|
|
7
7
|
"/**\n * A span of time, written the way a configuration file writes one: `'15m'`,\n * `'720h'`, `'30d'`. A plain number is milliseconds.\n *\n * Strings rather than milliseconds everywhere, because `2592000000` in a config\n * object is a number nobody reads back correctly, and Ory's own configuration\n * uses exactly this notation — so a team moving across recognises it.\n */\nexport type Duration = number | `${number}${'ms' | 's' | 'm' | 'h' | 'd'}`;\n\nconst UNITS = {\n\tms: 1,\n\ts: 1_000,\n\tm: 60_000,\n\th: 3_600_000,\n\td: 86_400_000,\n} as const;\n\nconst PATTERN = /^(\\d+(?:\\.\\d+)?)(ms|s|m|h|d)$/;\n\n/**\n * The duration in milliseconds.\n *\n * `where` names the option the consumer wrote — `session.lifespan`, not\n * `parseDuration` — because this package has half a dozen durations and a\n * message that does not say which one leaves the reader to guess.\n *\n * A bare `TypeError`: durations are written when the application is wired, so\n * this cannot come from a request, and no handler should answer it. The refusal\n * reports the **shape** expected and not the value's own bytes beyond echoing\n * it, which is the rule this repository inherits.\n */\nexport function parseDuration(value: Duration, where: string): number {\n\tif (typeof value === 'number') {\n\t\tif (!Number.isFinite(value) || value <= 0) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`${where}: a duration in milliseconds must be a finite number above zero`,\n\t\t\t);\n\t\t}\n\t\treturn value;\n\t}\n\n\tconst match = PATTERN.exec(value);\n\n\tif (!match) {\n\t\tthrow new TypeError(\n\t\t\t`${where}: \"${value}\" is not a duration; write a number followed by ms, s, m, h or d — for example \"15m\" or \"720h\"`,\n\t\t);\n\t}\n\n\tconst amount = Number(match[1]);\n\tconst unit = match[2] as keyof typeof UNITS;\n\tconst ms = amount * UNITS[unit];\n\n\tif (ms <= 0) {\n\t\tthrow new TypeError(`${where}: a duration must be above zero`);\n\t}\n\n\treturn ms;\n}\n",
|
|
8
8
|
"/**\n * What `janus()` accepts, and how it is resolved at run time.\n *\n * The types refuse first, on the offending key (`checks.ts`). What follows at\n * run time is the net for JavaScript callers, and every refusal is a bare\n * `TypeError`: a configuration is written when the application is wired, never\n * from a request.\n */\n\nimport type { RelationStore } from '../permissions/port/types';\nimport type { Clock } from '../time/clock';\nimport { type Duration, parseDuration } from '../time/duration';\nimport type { JanusStores } from './port/types';\nimport type { StandardSchemaV1 } from './standard-schema';\n\n/**\n * How a login is normalised before any store sees it. `'lowercaseTrim'` when\n * absent — right for an e-mail and for most usernames.\n *\n * A function is accepted for anything else. It must be deterministic: the same\n * rule normalises at sign-up and at sign-in.\n */\nexport type Normalize =\n\t| 'none'\n\t| 'lowercase'\n\t| 'lowercaseTrim'\n\t| 'nfkcLowercaseTrim'\n\t| ((value: string) => string);\n\n/**\n * What a user schema may produce: JSON, where an object property may also be\n * `undefined` — which is what every validator's optional field produces.\n *\n * The core drops an `undefined` property before a store sees the fields, so the\n * port stays strict JSON: an optional field left out is **absent** in the\n * store, and reads back absent, which the schema's output type already allows.\n */\nexport type FieldsJson =\n\t| string\n\t| number\n\t| boolean\n\t| null\n\t| readonly FieldsJson[]\n\t| { readonly [key: string]: FieldsJson | undefined };\n\n/**\n * A user type's schema: any Standard Schema — Zod 4, Valibot, ArkType — whose\n * output is an object of JSON. A `Date` round-trips through one store and not\n * the next, so a schema that produces one is refused at compile time.\n */\nexport type UserSchema = StandardSchemaV1<\n\tunknown,\n\t{ readonly [key: string]: FieldsJson | undefined }\n>;\n\n/**\n * A password hashing scheme — the second port.\n *\n * Hashes are self-describing, so **every wired hasher can verify, and exactly\n * one hashes** new passwords. A database written under Bun reads under Node and\n * back, as long as a verifier for each prefix is wired.\n */\nexport interface PasswordHasher {\n\t/** The prefix every hash it writes starts with: `'$argon2id$'`, `'$scrypt$'`. */\n\treadonly prefix: string;\n\thash(plain: string): Promise<string>;\n\tverify(plain: string, hash: string): Promise<boolean>;\n\t/**\n\t * Whether a hash **this hasher** wrote should be written again: its\n\t * parameters are not the ones `hash` uses now — a raised `cost`, say.\n\t * Optional; without it only a hash from another hasher (a `verifiers` one)\n\t * is rewritten. Called with hashes carrying this hasher's prefix only.\n\t */\n\tneedsRehash?(hash: string): boolean;\n}\n\n/** Signing in with a password. */\nexport interface PasswordConfig {\n\t/**\n\t * The field users sign in with: a **top-level, required string** field of\n\t * the schema — `'email'`, `'username'`. A typo is a compile error.\n\t */\n\treadonly login: string;\n\t/** `'lowercaseTrim'` when absent. */\n\treadonly normalize?: Normalize;\n\t/** At least 1. `8` when absent. */\n\treadonly minLength?: number;\n}\n\n/** How long a session lives, and when it is renewed. */\nexport interface SessionConfig {\n\t/** `'7d'` when absent. */\n\treadonly lifespan?: Duration;\n\t/**\n\t * `authenticate` renews a session once this much has passed since it was\n\t * opened or last renewed — a sliding session, written at most once per\n\t * period. `'1d'` when absent; `false` for a fixed lifespan.\n\t */\n\treadonly renewAfter?: Duration | false;\n}\n\n/** One user type: its schema, and how it signs in. */\nexport interface UserTypeConfig {\n\treadonly schema: UserSchema;\n\treadonly password?: PasswordConfig;\n\t/**\n\t * The field holding the user's e-mail, which `verifyEmail` and\n\t * `resetPassword` send to. `'email'` when absent — and when the schema has\n\t * no required string `email` either, those two flows do not exist on the\n\t * type.\n\t */\n\treadonly email?: string;\n\treadonly session?: SessionConfig;\n\t/**\n\t * Recorded on every user written, and read by nothing yet. Bump it when the\n\t * schema tightens, and the users validated against the old one can be found.\n\t * `'1'` when absent.\n\t */\n\treadonly schemaVersion?: string;\n}\n\n/** The session cookie. Every default is the strict one. */\nexport interface CookieConfig {\n\t/** `'janus-session'` when absent. A cookie-name token: no space, no `;`, no `=`. */\n\treadonly name?: string;\n\treadonly domain?: string;\n\t/** `'/'` when absent. */\n\treadonly path?: string;\n\t/** `'lax'` when absent. */\n\treadonly sameSite?: 'lax' | 'strict' | 'none';\n\t/** `true` when absent. `sameSite: 'none'` requires it. */\n\treadonly secure?: boolean;\n}\n\ninterface SharedConfig {\n\t/** The three stores: `createMemoryStores()`, or an adapter's. */\n\treadonly store: JanusStores;\n\t/**\n\t * The relation store `permissions()` is given, when the application has\n\t * one. Wired here, deleting a user deletes every tuple naming them too — as\n\t * a subject, and through no one else's memory of it.\n\t */\n\treadonly relations?: RelationStore;\n\t/**\n\t * Hashes new passwords. **Required as soon as a type signs in with a\n\t * password** — there is no silent fallback. `scryptHasher()` runs on Node and\n\t * Bun; `bunHasher()` is argon2id, on Bun only.\n\t */\n\treadonly hasher?: PasswordHasher;\n\t/** Hashers that only verify: those a database was written with before. */\n\treadonly verifiers?: readonly PasswordHasher[];\n\treadonly clock?: Clock;\n\treadonly cookie?: CookieConfig;\n\treadonly tokens?: {\n\t\t/** `'24h'` when absent. */\n\t\treadonly verifyEmail?: Duration;\n\t\t/** `'1h'` when absent. */\n\t\treadonly resetPassword?: Duration;\n\t};\n}\n\n/** An application with one user type: `user` is its schema. */\nexport interface SingleTypeConfig\n\textends SharedConfig,\n\t\tOmit<UserTypeConfig, 'schema'> {\n\treadonly user: UserSchema;\n\treadonly users?: never;\n}\n\n/** An application with several user types — patients and staff. */\nexport interface MultiTypeConfig extends SharedConfig {\n\treadonly users: { readonly [type: string]: UserTypeConfig };\n\treadonly user?: never;\n\treadonly password?: never;\n\treadonly email?: never;\n\treadonly session?: never;\n\treadonly schemaVersion?: never;\n}\n\nexport type JanusConfig = SingleTypeConfig | MultiTypeConfig;\n\n/** The type name the single-type form gives its users. */\nexport const SINGLE_TYPE = 'user';\n\n/**\n * Keys `janus` sets on every user, so a schema may not declare them.\n * `password` too: it is taken beside the fields, and never stored among them.\n */\nexport const RESERVED_FIELDS = [\n\t'id',\n\t'type',\n\t'emailVerified',\n\t'active',\n\t'hasPassword',\n\t'version',\n\t'createdAt',\n\t'updatedAt',\n\t'password',\n] as const;\n\n/** Names on `janus()`'s answer, so a user type may not take one. */\nexport const RESERVED_TYPES = [\n\t'authenticate',\n\t'signOut',\n\t'signOutEverywhere',\n\t'findUser',\n\t'getUser',\n\t'cookie',\n\t'collectExpired',\n\t'types',\n] as const;\n\n/** One user type, with every default applied and every duration in milliseconds. */\nexport interface ResolvedType {\n\treadonly name: string;\n\treadonly schema: UserSchema;\n\treadonly schemaVersion: string;\n\treadonly password: {\n\t\treadonly login: string;\n\t\treadonly normalize: (value: string) => string;\n\t\treadonly minLength: number;\n\t} | null;\n\treadonly email: string;\n\treadonly lifespanMs: number;\n\treadonly renewAfterMs: number | null;\n}\n\nexport interface ResolvedConfig {\n\treadonly single: boolean;\n\treadonly types: ReadonlyMap<string, ResolvedType>;\n\treadonly tokenTtlMs: {\n\t\treadonly verifyEmail: number;\n\t\treadonly resetPassword: number;\n\t};\n\treadonly cookie: {\n\t\treadonly name: string;\n\t\treadonly domain: string | null;\n\t\treadonly path: string;\n\t\treadonly sameSite: 'lax' | 'strict' | 'none';\n\t\treadonly secure: boolean;\n\t};\n}\n\nconst NORMALIZERS = {\n\tnone: (value: string) => value,\n\tlowercase: (value: string) => value.toLowerCase(),\n\tlowercaseTrim: (value: string) => value.toLowerCase().trim(),\n\tnfkcLowercaseTrim: (value: string) =>\n\t\tvalue.normalize('NFKC').toLowerCase().trim(),\n} as const satisfies Record<string, (value: string) => string>;\n\n/** How an e-mail is compared: always the same rule, whatever the login's. */\nexport const normalizeEmail = NORMALIZERS.lowercaseTrim;\n\n/** RFC 6265's cookie-name token: no control character, space, or separator. */\nconst COOKIE_NAME = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\n\n/** A type name, or a field name: camelCase, as every key in this package. */\nconst NAME = /^[A-Za-z][A-Za-z0-9]*$/;\n\n/** Applies the defaults and refuses what cannot be wired. `where` names the call. */\nexport function resolveConfig(\n\tconfig: JanusConfig,\n\twhere: string,\n): ResolvedConfig {\n\tif (typeof config !== 'object' || config === null) {\n\t\tthrow new TypeError(`${where}: expected a configuration object`);\n\t}\n\n\tconst hasUser = config.user !== undefined;\n\tconst hasUsers = config.users !== undefined;\n\tif (hasUser === hasUsers) {\n\t\tthrow new TypeError(\n\t\t\t`${where}: pass either user (one user type) or users (several user types), and exactly one of them`,\n\t\t);\n\t}\n\n\tconst entries: [string, UserTypeConfig][] = hasUsers\n\t\t? Object.entries(config.users as MultiTypeConfig['users'])\n\t\t: [\n\t\t\t\t[\n\t\t\t\t\tSINGLE_TYPE,\n\t\t\t\t\t{\n\t\t\t\t\t\t...(config as SingleTypeConfig),\n\t\t\t\t\t\tschema: config.user as UserSchema,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t];\n\n\tif (entries.length === 0) {\n\t\tthrow new TypeError(`${where}: users declares no user type`);\n\t}\n\n\tconst types = new Map<string, ResolvedType>();\n\tfor (const [name, type] of entries) {\n\t\tconst at = hasUsers ? `${where}: users.${name}` : where;\n\n\t\tif (!NAME.test(name)) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`${where}: the user type \"${name}\" must be a camelCase name — letters and digits, starting with a letter`,\n\t\t\t);\n\t\t}\n\t\tif ((RESERVED_TYPES as readonly string[]).includes(name)) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`${where}: \"${name}\" cannot name a user type — janus() answers a method of that name`,\n\t\t\t);\n\t\t}\n\t\ttypes.set(name, resolveType(name, type, at, hasUsers ? 'schema' : 'user'));\n\t}\n\n\tconst hashing = [...types.values()].some((type) => type.password !== null);\n\tif (hashing && config.hasher === undefined) {\n\t\tthrow new TypeError(\n\t\t\t`${where}: a user type signs in with a password and no hasher is wired — pass hasher: scryptHasher(), or bunHasher() on Bun. There is no silent fallback`,\n\t\t);\n\t}\n\n\treturn {\n\t\tsingle: !hasUsers,\n\t\ttypes,\n\t\ttokenTtlMs: {\n\t\t\tverifyEmail: parseDuration(\n\t\t\t\tconfig.tokens?.verifyEmail ?? '24h',\n\t\t\t\t`${where}: tokens.verifyEmail`,\n\t\t\t),\n\t\t\tresetPassword: parseDuration(\n\t\t\t\tconfig.tokens?.resetPassword ?? '1h',\n\t\t\t\t`${where}: tokens.resetPassword`,\n\t\t\t),\n\t\t},\n\t\tcookie: resolveCookie(config.cookie ?? {}, where),\n\t};\n}\n\nfunction resolveType(\n\tname: string,\n\ttype: UserTypeConfig,\n\tat: string,\n\tschemaKey: string,\n): ResolvedType {\n\tif (\n\t\ttypeof type?.schema !== 'object' ||\n\t\ttype.schema === null ||\n\t\ttypeof type.schema['~standard']?.validate !== 'function'\n\t) {\n\t\tthrow new TypeError(\n\t\t\t`${at}: ${schemaKey} must be a Standard Schema — a Zod 4, Valibot or ArkType schema`,\n\t\t);\n\t}\n\n\tlet password: ResolvedType['password'] = null;\n\tif (type.password !== undefined) {\n\t\tconst minLength = type.password.minLength ?? 8;\n\t\tif (!Number.isInteger(minLength) || minLength < 1) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`${at}: password.minLength must be an integer of at least 1`,\n\t\t\t);\n\t\t}\n\t\tpassword = {\n\t\t\tlogin: fieldName(type.password.login, `${at}: password.login`),\n\t\t\tnormalize: normalizer(\n\t\t\t\ttype.password.normalize ?? 'lowercaseTrim',\n\t\t\t\t`${at}: password.normalize`,\n\t\t\t),\n\t\t\tminLength,\n\t\t};\n\t}\n\n\tconst renewAfter = type.session?.renewAfter ?? '1d';\n\n\treturn {\n\t\tname,\n\t\tschema: type.schema,\n\t\tschemaVersion: type.schemaVersion ?? '1',\n\t\tpassword,\n\t\temail: fieldName(type.email ?? 'email', `${at}: email`),\n\t\tlifespanMs: parseDuration(\n\t\t\ttype.session?.lifespan ?? '7d',\n\t\t\t`${at}: session.lifespan`,\n\t\t),\n\t\trenewAfterMs:\n\t\t\trenewAfter === false\n\t\t\t\t? null\n\t\t\t\t: parseDuration(renewAfter, `${at}: session.renewAfter`),\n\t};\n}\n\nfunction resolveCookie(\n\tcookie: CookieConfig,\n\twhere: string,\n): ResolvedConfig['cookie'] {\n\tconst name = cookie.name ?? 'janus-session';\n\tif (!COOKIE_NAME.test(name)) {\n\t\tthrow new TypeError(\n\t\t\t`${where}: cookie.name must be a cookie-name token — letters, digits and !#$%&'*+-.^_\\`|~, with no space, \";\" or \"=\"`,\n\t\t);\n\t}\n\tconst sameSite = cookie.sameSite ?? 'lax';\n\tconst secure = cookie.secure ?? true;\n\tif (sameSite === 'none' && !secure) {\n\t\tthrow new TypeError(\n\t\t\t`${where}: cookie.sameSite \"none\" requires cookie.secure — browsers refuse the cookie otherwise`,\n\t\t);\n\t}\n\n\treturn {\n\t\tname,\n\t\tdomain: cookie.domain ?? null,\n\t\tpath: cookie.path ?? '/',\n\t\tsameSite,\n\t\tsecure,\n\t};\n}\n\nfunction fieldName(field: unknown, where: string): string {\n\tif (typeof field !== 'string' || !NAME.test(field)) {\n\t\tthrow new TypeError(\n\t\t\t`${where} must name a top-level field of the schema, such as \"email\"`,\n\t\t);\n\t}\n\tif ((RESERVED_FIELDS as readonly string[]).includes(field)) {\n\t\tthrow new TypeError(`${where}: \"${field}\" is a field janus sets itself`);\n\t}\n\treturn field;\n}\n\nfunction normalizer(rule: Normalize, where: string): (value: string) => string {\n\tif (typeof rule === 'function') return rule;\n\tif (typeof rule === 'string' && Object.hasOwn(NORMALIZERS, rule)) {\n\t\treturn NORMALIZERS[rule];\n\t}\n\n\tthrow new TypeError(\n\t\t`${where} must be \"none\", \"lowercase\", \"lowercaseTrim\", \"nfkcLowercaseTrim\" or a function`,\n\t);\n}\n",
|
|
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",
|
|
9
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",
|
|
10
|
-
"/**\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 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: (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}\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\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(type: ResolvedType, fields: JsonObject): 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\tlogins.add(type.password.normalize(login));\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",
|
|
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",
|
|
11
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
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",
|
|
13
|
-
"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\treturn options?.except === undefined\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",
|
|
14
|
-
"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 { 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/** 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),\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),\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'\n\t\t\t\t\t? await store.users.findUserByLogin(type.name, rule.normalize(login))\n\t\t\t\t\t: 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 store.users.findUserByLogin(\n\t\t\t\ttype.name,\n\t\t\t\trule.normalize(login),\n\t\t\t);\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 store.users.findUserByLogin(type.name, 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",
|
|
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
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"
|
|
16
17
|
],
|
|
17
|
-
"mappings": "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",
|
|
18
|
-
"debugId": "
|
|
18
|
+
"mappings": "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",
|
|
19
|
+
"debugId": "A06CA262C3DD58D164756E2164756E21",
|
|
19
20
|
"names": []
|
|
20
21
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every store can keep. PostgreSQL refuses `\u0000` in `text` and
|
|
3
|
+
* `jsonb`, and a lone surrogate in `jsonb`; MongoDB and the memory store keep
|
|
4
|
+
* both. The core refuses them before a store is asked, so a request that sends
|
|
5
|
+
* one is answered the same way on every adapter — not `STORE_FAILED` on one of
|
|
6
|
+
* them, a 503 for a database that is up.
|
|
7
|
+
*
|
|
8
|
+
* Shared by both sides of the package, like `guard.ts`: a string from a
|
|
9
|
+
* request reaches the identity stores and the relation store alike.
|
|
10
|
+
*/
|
|
11
|
+
/** Whether every store can keep this string. */
|
|
12
|
+
export declare const isStorable: (value: string) => boolean;
|
|
13
|
+
/** What an issue says about a string no store can keep. */
|
|
14
|
+
export declare const UNSTORABLE = "holds a NUL character or a lone surrogate, which no store can keep";
|
|
15
|
+
//# sourceMappingURL=storable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storable.d.ts","sourceRoot":"","sources":["../../src/stores/storable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gDAAgD;AAChD,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,KAAG,OACO,CAAC;AAEnD,2DAA2D;AAC3D,eAAO,MAAM,UAAU,uEAC8C,CAAC"}
|
package/docs/guide/adapters.md
CHANGED
|
@@ -141,7 +141,8 @@ Written on the port's types, and checked by the suites:
|
|
|
141
141
|
3. **Uniqueness is a constraint** — a unique index, never a read followed by a
|
|
142
142
|
write.
|
|
143
143
|
4. **Bytes round-trip.** No normalising, trimming or retyping. The core
|
|
144
|
-
normalises logins before a store sees them
|
|
144
|
+
normalises logins before a store sees them, and never hands a store
|
|
145
|
+
`\u0000` or a lone surrogate; every other character comes back as written.
|
|
145
146
|
5. **Every method is atomic on its own.** The core opens no transaction; an
|
|
146
147
|
adapter may open one inside a method.
|
|
147
148
|
6. **Schema management is not on the port.** Expose your own `sync`; the core
|
|
@@ -184,7 +185,7 @@ compile error naming the missing method.
|
|
|
184
185
|
|
|
185
186
|
| Suite | Cases | Harness opens |
|
|
186
187
|
| --- | --- | --- |
|
|
187
|
-
| `describeJanusStores({ name, harness, runner?, faults?, skip? })` |
|
|
188
|
+
| `describeJanusStores({ name, harness, runner?, faults?, skip? })` | 38: users, sessions, tokens, and one outage per method whose honest answer can be "nothing" | `{ stores, faults?, close? }` |
|
|
188
189
|
| `describeRelationStores({ name, harness, runner?, faults?, skip? })` | 15: the relation store, and one outage per method | `{ store, faults?, close? }` |
|
|
189
190
|
|
|
190
191
|
`harness.open()` is called **once per case** and must answer fresh, empty
|
|
@@ -298,3 +299,5 @@ Types: `ConformanceHarness`, `OpenedStores`, `StoreFaults`, `ConformanceCase`,
|
|
|
298
299
|
- [Errors](errors.md) — `StoreFailure`, `StoreConflict` and the rule behind them
|
|
299
300
|
- [Vocabulary](vocabulary.md) — ids, cursors and `invalidCursor`
|
|
300
301
|
- `@nxgt/janus-mongo` — an adapter that passes both suites against a real mongod, outages included
|
|
302
|
+
- `@nxgt/janus-drizzle` — both suites on PostgreSQL 17 and PGlite
|
|
303
|
+
- `@nxgt/janus-redis` — the sessions and one-time tokens cases on Redis 7.4
|
package/docs/guide/sessions.md
CHANGED
|
@@ -147,7 +147,9 @@ export async function signOut(request: Request): Promise<Response> {
|
|
|
147
147
|
|
|
148
148
|
`signOutEverywhere(user, { except })` revokes every standing session of a
|
|
149
149
|
user, but the one named, and answers how many it revoked — "sign out
|
|
150
|
-
everywhere else"
|
|
150
|
+
everywhere else". An `except` that names no session of theirs — an unknown
|
|
151
|
+
id, or anything that is not an id at all — keeps none: every session goes,
|
|
152
|
+
the current one included.
|
|
151
153
|
|
|
152
154
|
```ts
|
|
153
155
|
const current = await auth.authenticate(request);
|