@cauthyhq/node 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +181 -2
  4. package/dist/cauthy-B6Sm3T8M.d.ts +1035 -0
  5. package/dist/cauthy-ClP5orP4.d.mts +1035 -0
  6. package/dist/express.d.mts +25 -0
  7. package/dist/express.d.ts +25 -0
  8. package/dist/express.js +103 -0
  9. package/dist/express.js.map +1 -0
  10. package/dist/express.mjs +77 -0
  11. package/dist/express.mjs.map +1 -0
  12. package/dist/fastify.d.mts +38 -0
  13. package/dist/fastify.d.ts +38 -0
  14. package/dist/fastify.js +105 -0
  15. package/dist/fastify.js.map +1 -0
  16. package/dist/fastify.mjs +79 -0
  17. package/dist/fastify.mjs.map +1 -0
  18. package/dist/hono.d.mts +24 -0
  19. package/dist/hono.d.ts +24 -0
  20. package/dist/hono.js +51 -0
  21. package/dist/hono.js.map +1 -0
  22. package/dist/hono.mjs +25 -0
  23. package/dist/hono.mjs.map +1 -0
  24. package/dist/index.d.mts +138 -0
  25. package/dist/index.d.ts +138 -0
  26. package/dist/index.js +3474 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/index.mjs +3410 -0
  29. package/dist/index.mjs.map +1 -0
  30. package/dist/keys-D5k0omTb.d.mts +64 -0
  31. package/dist/keys-D5k0omTb.d.ts +64 -0
  32. package/dist/keys.d.mts +8 -0
  33. package/dist/keys.d.ts +8 -0
  34. package/dist/keys.js +157 -0
  35. package/dist/keys.js.map +1 -0
  36. package/dist/keys.mjs +129 -0
  37. package/dist/keys.mjs.map +1 -0
  38. package/dist/mcp.d.mts +151 -0
  39. package/dist/mcp.d.ts +151 -0
  40. package/dist/mcp.js +410 -0
  41. package/dist/mcp.js.map +1 -0
  42. package/dist/mcp.mjs +382 -0
  43. package/dist/mcp.mjs.map +1 -0
  44. package/dist/next.d.mts +24 -0
  45. package/dist/next.d.ts +24 -0
  46. package/dist/next.js +77 -0
  47. package/dist/next.js.map +1 -0
  48. package/dist/next.mjs +52 -0
  49. package/dist/next.mjs.map +1 -0
  50. package/dist/testing.d.mts +62 -0
  51. package/dist/testing.d.ts +62 -0
  52. package/dist/testing.js +182 -0
  53. package/dist/testing.js.map +1 -0
  54. package/dist/testing.mjs +156 -0
  55. package/dist/testing.mjs.map +1 -0
  56. package/dist/theme.d.mts +183 -0
  57. package/dist/theme.d.ts +183 -0
  58. package/dist/theme.js +531 -0
  59. package/dist/theme.js.map +1 -0
  60. package/dist/theme.mjs +496 -0
  61. package/dist/theme.mjs.map +1 -0
  62. package/package.json +187 -4
@@ -0,0 +1,64 @@
1
+ import { KeyLike, JWK } from 'jose';
2
+
3
+ /** A key as a KeyStore keeps it. Nothing in it is secret but sealed. */
4
+ interface StoredKey {
5
+ /** Its id, as published (the JWK thumbprint). */
6
+ kid: string;
7
+ /** Its public half, as published. */
8
+ publicJwk: JWK;
9
+ /** Its private half, sealed with the key derived from the root key. */
10
+ sealed: string;
11
+ /** When it was made (ISO 8601, UTC). The newest signs. */
12
+ createdAt: string;
13
+ }
14
+ /** Where the application keeps its keys: a table, a file. */
15
+ interface KeyStore {
16
+ /** Every key kept. */
17
+ list(): Promise<StoredKey[]>;
18
+ /** Keeps a new key. A kid already kept is left as it is. */
19
+ add(key: StoredKey): Promise<void>;
20
+ /** Keeps the key only while the store holds none, in one step: instances starting together make one key. */
21
+ addFirst?(key: StoredKey): Promise<void>;
22
+ /** Replaces a key's sealed private half (after the root key was rotated). */
23
+ update(key: StoredKey): Promise<void>;
24
+ /** Forgets a key no longer published. */
25
+ remove(kid: string): Promise<void>;
26
+ }
27
+ /** The application's keys, as auth.pact.keys gives them. */
28
+ interface PactKeys {
29
+ /** The key that signs now, made the first time there is none: the newest that has been published long enough. */
30
+ current(): Promise<{
31
+ kid: string;
32
+ privateKey: KeyLike | Uint8Array;
33
+ }>;
34
+ /** The public keys to publish: the one that signs, and the one before it for 24 hours after. */
35
+ published(): Promise<{
36
+ keys: JWK[];
37
+ }>;
38
+ /** Makes a new key, published at once and signing once SIGN_AFTER_MS has passed. Answers its id. */
39
+ rotate(): Promise<string>;
40
+ /** Seals every key again with the current root key; answers how many changed. */
41
+ reseal(): Promise<number>;
42
+ }
43
+ /** Keys kept in memory: for tests, and for trying contract mode out. Gone on restart. */
44
+ declare function memoryKeyStore(): KeyStore;
45
+ /**
46
+ * The table postgresKeyStore keeps the keys in, for the application's own
47
+ * migrations (Drizzle, Prisma, plain SQL). The store never creates it.
48
+ */
49
+ declare function pactKeysTableSql(table?: string): string;
50
+ /**
51
+ * Runs one SQL statement with $1, $2… parameters and answers its rows: what
52
+ * the application's own database client does. With node-postgres,
53
+ * `(text, params) => pool.query(text, params)`; with postgres.js,
54
+ * `(text, params) => sql.unsafe(text, params)`.
55
+ */
56
+ type Query = (text: string, params: unknown[]) => Promise<{
57
+ rows: unknown[];
58
+ } | unknown[]>;
59
+ /** Keys kept in a table of the application's Postgres database (see pactKeysTableSql). */
60
+ declare function postgresKeyStore(query: Query, options?: {
61
+ table?: string;
62
+ }): KeyStore;
63
+
64
+ export { type KeyStore as K, type PactKeys as P, type Query as Q, type StoredKey as S, postgresKeyStore as a, memoryKeyStore as m, pactKeysTableSql as p };
@@ -0,0 +1,64 @@
1
+ import { KeyLike, JWK } from 'jose';
2
+
3
+ /** A key as a KeyStore keeps it. Nothing in it is secret but sealed. */
4
+ interface StoredKey {
5
+ /** Its id, as published (the JWK thumbprint). */
6
+ kid: string;
7
+ /** Its public half, as published. */
8
+ publicJwk: JWK;
9
+ /** Its private half, sealed with the key derived from the root key. */
10
+ sealed: string;
11
+ /** When it was made (ISO 8601, UTC). The newest signs. */
12
+ createdAt: string;
13
+ }
14
+ /** Where the application keeps its keys: a table, a file. */
15
+ interface KeyStore {
16
+ /** Every key kept. */
17
+ list(): Promise<StoredKey[]>;
18
+ /** Keeps a new key. A kid already kept is left as it is. */
19
+ add(key: StoredKey): Promise<void>;
20
+ /** Keeps the key only while the store holds none, in one step: instances starting together make one key. */
21
+ addFirst?(key: StoredKey): Promise<void>;
22
+ /** Replaces a key's sealed private half (after the root key was rotated). */
23
+ update(key: StoredKey): Promise<void>;
24
+ /** Forgets a key no longer published. */
25
+ remove(kid: string): Promise<void>;
26
+ }
27
+ /** The application's keys, as auth.pact.keys gives them. */
28
+ interface PactKeys {
29
+ /** The key that signs now, made the first time there is none: the newest that has been published long enough. */
30
+ current(): Promise<{
31
+ kid: string;
32
+ privateKey: KeyLike | Uint8Array;
33
+ }>;
34
+ /** The public keys to publish: the one that signs, and the one before it for 24 hours after. */
35
+ published(): Promise<{
36
+ keys: JWK[];
37
+ }>;
38
+ /** Makes a new key, published at once and signing once SIGN_AFTER_MS has passed. Answers its id. */
39
+ rotate(): Promise<string>;
40
+ /** Seals every key again with the current root key; answers how many changed. */
41
+ reseal(): Promise<number>;
42
+ }
43
+ /** Keys kept in memory: for tests, and for trying contract mode out. Gone on restart. */
44
+ declare function memoryKeyStore(): KeyStore;
45
+ /**
46
+ * The table postgresKeyStore keeps the keys in, for the application's own
47
+ * migrations (Drizzle, Prisma, plain SQL). The store never creates it.
48
+ */
49
+ declare function pactKeysTableSql(table?: string): string;
50
+ /**
51
+ * Runs one SQL statement with $1, $2… parameters and answers its rows: what
52
+ * the application's own database client does. With node-postgres,
53
+ * `(text, params) => pool.query(text, params)`; with postgres.js,
54
+ * `(text, params) => sql.unsafe(text, params)`.
55
+ */
56
+ type Query = (text: string, params: unknown[]) => Promise<{
57
+ rows: unknown[];
58
+ } | unknown[]>;
59
+ /** Keys kept in a table of the application's Postgres database (see pactKeysTableSql). */
60
+ declare function postgresKeyStore(query: Query, options?: {
61
+ table?: string;
62
+ }): KeyStore;
63
+
64
+ export { type KeyStore as K, type PactKeys as P, type Query as Q, type StoredKey as S, postgresKeyStore as a, memoryKeyStore as m, pactKeysTableSql as p };
@@ -0,0 +1,8 @@
1
+ import { K as KeyStore } from './keys-D5k0omTb.mjs';
2
+ export { P as PactKeys, Q as Query, S as StoredKey, m as memoryKeyStore, p as pactKeysTableSql, a as postgresKeyStore } from './keys-D5k0omTb.mjs';
3
+ import 'jose';
4
+
5
+ /** Keys kept in a JSON file, readable only by its owner. For development. */
6
+ declare function fileKeyStore(path: string): KeyStore;
7
+
8
+ export { KeyStore, fileKeyStore };
package/dist/keys.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { K as KeyStore } from './keys-D5k0omTb.js';
2
+ export { P as PactKeys, Q as Query, S as StoredKey, m as memoryKeyStore, p as pactKeysTableSql, a as postgresKeyStore } from './keys-D5k0omTb.js';
3
+ import 'jose';
4
+
5
+ /** Keys kept in a JSON file, readable only by its owner. For development. */
6
+ declare function fileKeyStore(path: string): KeyStore;
7
+
8
+ export { KeyStore, fileKeyStore };
package/dist/keys.js ADDED
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/keystores.ts
21
+ var keystores_exports = {};
22
+ __export(keystores_exports, {
23
+ fileKeyStore: () => fileKeyStore,
24
+ memoryKeyStore: () => memoryKeyStore,
25
+ pactKeysTableSql: () => pactKeysTableSql,
26
+ postgresKeyStore: () => postgresKeyStore
27
+ });
28
+ module.exports = __toCommonJS(keystores_exports);
29
+ var import_promises = require("fs/promises");
30
+ var import_node_path = require("path");
31
+
32
+ // src/keys.ts
33
+ var import_jose = require("jose");
34
+
35
+ // src/types.ts
36
+ var CauthyConfigError = class extends Error {
37
+ constructor(message) {
38
+ super(`@cauthyhq/node: ${message}`);
39
+ this.name = "CauthyConfigError";
40
+ }
41
+ };
42
+
43
+ // src/keys.ts
44
+ var RETIRE_AFTER_MS = 24 * 60 * 60 * 1e3;
45
+ var FORGET_AFTER_MS = 7 * 24 * 60 * 60 * 1e3;
46
+ var CURRENT_TTL_MS = 5 * 60 * 1e3;
47
+ var JWKS_MAX_AGE_S = 300;
48
+ var SIGN_AFTER_MS = (JWKS_MAX_AGE_S + 60) * 1e3;
49
+ var SETTLING_TTL_MS = 5 * 1e3;
50
+ function memoryKeyStore() {
51
+ const keys = /* @__PURE__ */ new Map();
52
+ return {
53
+ async list() {
54
+ return [...keys.values()];
55
+ },
56
+ async add(k) {
57
+ if (!keys.has(k.kid)) keys.set(k.kid, k);
58
+ },
59
+ async addFirst(k) {
60
+ if (keys.size === 0) keys.set(k.kid, k);
61
+ },
62
+ async update(k) {
63
+ keys.set(k.kid, k);
64
+ },
65
+ async remove(kid) {
66
+ keys.delete(kid);
67
+ }
68
+ };
69
+ }
70
+ function pactKeysTableSql(table = "cauthy_pact_keys") {
71
+ checkTable(table);
72
+ return `CREATE TABLE IF NOT EXISTS ${table} (
73
+ kid TEXT PRIMARY KEY,
74
+ public_jwk JSONB NOT NULL,
75
+ sealed TEXT NOT NULL,
76
+ created_at TIMESTAMPTZ NOT NULL
77
+ );`;
78
+ }
79
+ function checkTable(table) {
80
+ if (!/^[a-z_][a-z0-9_]{0,62}$/.test(table)) throw new CauthyConfigError(`the key table's name must be a plain SQL identifier: ${table}`);
81
+ }
82
+ function postgresKeyStore(query, options = {}) {
83
+ const table = options.table ?? "cauthy_pact_keys";
84
+ checkTable(table);
85
+ const rows = async (text, params) => {
86
+ const out = await query(text, params);
87
+ return Array.isArray(out) ? out : out.rows;
88
+ };
89
+ const at = (v) => v instanceof Date ? v.toISOString() : new Date(String(v)).toISOString();
90
+ return {
91
+ async list() {
92
+ return (await rows(`SELECT kid, public_jwk, sealed, created_at FROM ${table}`, [])).map((r) => ({
93
+ kid: String(r.kid),
94
+ publicJwk: typeof r.public_jwk === "string" ? JSON.parse(r.public_jwk) : r.public_jwk,
95
+ sealed: String(r.sealed),
96
+ createdAt: at(r.created_at)
97
+ }));
98
+ },
99
+ async add(k) {
100
+ await rows(
101
+ `INSERT INTO ${table} (kid, public_jwk, sealed, created_at) VALUES ($1, $2, $3, $4) ON CONFLICT (kid) DO NOTHING`,
102
+ [k.kid, JSON.stringify(k.publicJwk), k.sealed, k.createdAt]
103
+ );
104
+ },
105
+ async addFirst(k) {
106
+ await rows(
107
+ `INSERT INTO ${table} (kid, public_jwk, sealed, created_at) SELECT $1, $2, $3, $4 WHERE NOT EXISTS (SELECT 1 FROM ${table}) ON CONFLICT (kid) DO NOTHING`,
108
+ [k.kid, JSON.stringify(k.publicJwk), k.sealed, k.createdAt]
109
+ );
110
+ },
111
+ async update(k) {
112
+ await rows(`UPDATE ${table} SET sealed = $2 WHERE kid = $1`, [k.kid, k.sealed]);
113
+ },
114
+ async remove(kid) {
115
+ await rows(`DELETE FROM ${table} WHERE kid = $1`, [kid]);
116
+ }
117
+ };
118
+ }
119
+
120
+ // src/keystores.ts
121
+ function fileKeyStore(path) {
122
+ async function read() {
123
+ try {
124
+ const parsed = JSON.parse(await (0, import_promises.readFile)(path, "utf8"));
125
+ return Array.isArray(parsed) ? parsed : [];
126
+ } catch (err) {
127
+ if (err.code === "ENOENT") return [];
128
+ throw err;
129
+ }
130
+ }
131
+ let writing = Promise.resolve();
132
+ function write(change) {
133
+ writing = writing.then(async () => {
134
+ const next = change(await read());
135
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(path), { recursive: true });
136
+ const tmp = `${path}.${process.pid}.tmp`;
137
+ await (0, import_promises.writeFile)(tmp, JSON.stringify(next, null, 2) + "\n", { mode: 384 });
138
+ await (0, import_promises.rename)(tmp, path);
139
+ });
140
+ return writing;
141
+ }
142
+ return {
143
+ list: read,
144
+ add: (k) => write((keys) => keys.some((x) => x.kid === k.kid) ? keys : [...keys, k]),
145
+ addFirst: (k) => write((keys) => keys.length > 0 ? keys : [k]),
146
+ update: (k) => write((keys) => keys.map((x) => x.kid === k.kid ? k : x)),
147
+ remove: (kid) => write((keys) => keys.filter((x) => x.kid !== kid))
148
+ };
149
+ }
150
+ // Annotate the CommonJS export names for ESM import in node:
151
+ 0 && (module.exports = {
152
+ fileKeyStore,
153
+ memoryKeyStore,
154
+ pactKeysTableSql,
155
+ postgresKeyStore
156
+ });
157
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/keystores.ts","../src/keys.ts","../src/types.ts"],"sourcesContent":["// @cauthyhq/node/keys: where a contracted application keeps its own key.\n//\n// import { postgresKeyStore } from '@cauthyhq/node/keys'\n// keys: postgresKeyStore((text, params) => pool.query(text, params))\n//\n// The file store is for a developer's machine: production keeps its key in\n// its database, where every instance reads the same one.\n\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport { dirname } from 'node:path'\nimport type { KeyStore, StoredKey } from './keys'\n\nexport { memoryKeyStore, postgresKeyStore, pactKeysTableSql, type KeyStore, type PactKeys, type Query, type StoredKey } from './keys'\n\n/** Keys kept in a JSON file, readable only by its owner. For development. */\nexport function fileKeyStore(path: string): KeyStore {\n async function read(): Promise<StoredKey[]> {\n try {\n const parsed = JSON.parse(await readFile(path, 'utf8')) as unknown\n return Array.isArray(parsed) ? (parsed as StoredKey[]) : []\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n }\n // Written whole to a file beside it, then moved over it: a crash never\n // leaves half a file.\n let writing: Promise<void> = Promise.resolve()\n function write(change: (keys: StoredKey[]) => StoredKey[]): Promise<void> {\n writing = writing.then(async () => {\n const next = change(await read())\n await mkdir(dirname(path), { recursive: true })\n const tmp = `${path}.${process.pid}.tmp`\n await writeFile(tmp, JSON.stringify(next, null, 2) + '\\n', { mode: 0o600 })\n await rename(tmp, path)\n })\n return writing\n }\n return {\n list: read,\n add: k => write(keys => (keys.some(x => x.kid === k.kid) ? keys : [...keys, k])),\n addFirst: k => write(keys => (keys.length > 0 ? keys : [k])),\n update: k => write(keys => keys.map(x => (x.kid === k.kid ? k : x))),\n remove: kid => write(keys => keys.filter(x => x.kid !== kid)),\n }\n}\n","// The application's own key: made by the application, kept in its own\n// database, its public half published, its private half sealed at rest.\n//\n// A contracted application proves who it is to Cauthy with an assertion it\n// signs (private_key_jwt). Nobody hands it that key: the first time it needs\n// one it makes it, seals the private half with the key derived from its root\n// key (pact/seal/v1) and keeps it in a KeyStore — a table of its database, a\n// file while developing. Its public half is served at the jwks_url its\n// manifest names, where Cauthy reads it.\n//\n// Rotating: `rotate()` makes a new key, published at once. It signs once it\n// has been published longer than anyone caches the keys (SIGN_AFTER_MS):\n// Cauthy, meeting a key it does not know, reads the keys again, and a copy\n// cached on the way must already hold it. The one before it stays published\n// 24 hours, so an assertion signed a moment ago, or by an instance that has\n// not seen the new key yet, still verifies.\n//\n// The first key is made by whichever instance needs it first: the store keeps\n// it only while it holds none (addFirst), so instances starting together end\n// with one key. A store without addFirst may end with several; every one is\n// published, and every instance settles on the same one within seconds.\n\nimport { calculateJwkThumbprint, exportJWK, generateKeyPair, importJWK, type JWK, type KeyLike } from 'jose'\nimport { seal, unseal } from './seal'\nimport type { KeyRing } from './rootkey'\nimport { CauthyConfigError } from './types'\n\n/** A key as a KeyStore keeps it. Nothing in it is secret but sealed. */\nexport interface StoredKey {\n /** Its id, as published (the JWK thumbprint). */\n kid: string\n /** Its public half, as published. */\n publicJwk: JWK\n /** Its private half, sealed with the key derived from the root key. */\n sealed: string\n /** When it was made (ISO 8601, UTC). The newest signs. */\n createdAt: string\n}\n\n/** Where the application keeps its keys: a table, a file. */\nexport interface KeyStore {\n /** Every key kept. */\n list(): Promise<StoredKey[]>\n /** Keeps a new key. A kid already kept is left as it is. */\n add(key: StoredKey): Promise<void>\n /** Keeps the key only while the store holds none, in one step: instances starting together make one key. */\n addFirst?(key: StoredKey): Promise<void>\n /** Replaces a key's sealed private half (after the root key was rotated). */\n update(key: StoredKey): Promise<void>\n /** Forgets a key no longer published. */\n remove(kid: string): Promise<void>\n}\n\n/** How long a key stays published after a newer one appears. */\nexport const RETIRE_AFTER_MS = 24 * 60 * 60 * 1000\n/** How long a key that is no longer published is kept before it is forgotten. */\nconst FORGET_AFTER_MS = 7 * 24 * 60 * 60 * 1000\n/** How long the key that signs is remembered before the store is read again. */\nconst CURRENT_TTL_MS = 5 * 60 * 1000\n/** How long the published keys may be cached (the jwks_url's max-age). */\nexport const JWKS_MAX_AGE_S = 300\n/** How long a new key is published before it signs: longer than any cached copy of the keys. */\nexport const SIGN_AFTER_MS = (JWKS_MAX_AGE_S + 60) * 1000\n/** While every key is younger than that, the store is read again this often, so instances settle on one. */\nconst SETTLING_TTL_MS = 5 * 1000\n\n/** The application's keys, as auth.pact.keys gives them. */\nexport interface PactKeys {\n /** The key that signs now, made the first time there is none: the newest that has been published long enough. */\n current(): Promise<{ kid: string; privateKey: KeyLike | Uint8Array }>\n /** The public keys to publish: the one that signs, and the one before it for 24 hours after. */\n published(): Promise<{ keys: JWK[] }>\n /** Makes a new key, published at once and signing once SIGN_AFTER_MS has passed. Answers its id. */\n rotate(): Promise<string>\n /** Seals every key again with the current root key; answers how many changed. */\n reseal(): Promise<number>\n}\n\nconst newestFirst = (keys: StoredKey[]) => [...keys].sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.kid.localeCompare(a.kid))\n\nfunction published(key: StoredKey): JWK {\n const { kty, n, e } = key.publicJwk\n return { kty, n, e, kid: key.kid, alg: 'RS256', use: 'sig' }\n}\n\nexport function pactKeys(store: KeyStore, sealKeys: Promise<KeyRing>, now: () => number = Date.now): PactKeys {\n let remembered: { kid: string; privateKey: KeyLike | Uint8Array; until: number } | undefined\n let making: Promise<StoredKey> | undefined\n\n async function make(after?: StoredKey): Promise<StoredKey> {\n const { publicKey, privateKey } = await generateKeyPair('RS256', { modulusLength: 2048, extractable: true })\n const publicJwk = await exportJWK(publicKey)\n const kid = await calculateJwkThumbprint(publicJwk)\n // Always newer than the key it follows, however fast the rotation.\n const at = Math.max(now(), after ? Date.parse(after.createdAt) + 1 : 0)\n const key: StoredKey = { kid, publicJwk, sealed: await seal(await exportJWK(privateKey), (await sealKeys)[0]), createdAt: new Date(at).toISOString() }\n if (!after && store.addFirst) await store.addFirst(key)\n else await store.add(key)\n return key\n }\n\n const age = (key: StoredKey) => now() - Date.parse(key.createdAt)\n\n // The key that signs: the newest published long enough. When none is (the\n // first minutes of the first key, or a race of first keys), the oldest, read\n // again shortly so every instance lands on the same one.\n function signer(kept: StoredKey[]): { key: StoredKey; until: number } {\n const ripe = kept.find(k => age(k) >= SIGN_AFTER_MS)\n if (!ripe) return { key: kept[kept.length - 1], until: now() + SETTLING_TTL_MS }\n // A newer key ripens later: read again then, not after the usual while.\n const until = ripe === kept[0] ? now() + CURRENT_TTL_MS : Math.min(now() + CURRENT_TTL_MS, Date.parse(kept[0].createdAt) + SIGN_AFTER_MS)\n return { key: ripe, until }\n }\n\n async function open(key: StoredKey): Promise<{ jwk: JWK; fresh: boolean } | null> {\n const ring = await sealKeys\n for (let i = 0; i < ring.length; i++) {\n const jwk = await unseal<JWK>(key.sealed, ring[i])\n if (jwk !== null) return { jwk, fresh: i === 0 }\n }\n return null\n }\n\n // Every key kept, newest first; the first one is made when there is none.\n async function keptKeys(): Promise<StoredKey[]> {\n const kept = newestFirst(await store.list())\n if (kept.length > 0) return kept\n // Two callers at once make one key; two instances at once, one with\n // addFirst, and otherwise several that are all published.\n making ??= make().finally(() => { making = undefined })\n await making\n const again = newestFirst(await store.list())\n if (again.length === 0) throw new CauthyConfigError('the key store kept no key: check that it writes')\n return again\n }\n\n return {\n async current() {\n if (remembered && remembered.until > now()) return remembered\n const { key, until } = signer(await keptKeys())\n if (remembered?.kid === key.kid) {\n remembered.until = until\n return remembered\n }\n const opened = await open(key)\n if (opened === null) {\n throw new CauthyConfigError(`the key ${key.kid} cannot be opened with the root key (PACT_ROOT_KEY): ` +\n 'if the root key was rotated, set the previous one as PACT_ROOT_KEY_PREVIOUS')\n }\n remembered = { kid: key.kid, privateKey: await importJWK(opened.jwk, 'RS256'), until }\n return remembered\n },\n async published() {\n // Asked before anything was signed: the key is made now, so what is\n // published is the key that will sign.\n const kept = await keptKeys()\n const out: JWK[] = []\n for (let i = 0; i < kept.length; i++) {\n // The newest is published; an older one while its successor is young.\n if (i === 0 || now() - Date.parse(kept[i - 1].createdAt) < RETIRE_AFTER_MS) out.push(published(kept[i]))\n else if (now() - Date.parse(kept[i - 1].createdAt) > FORGET_AFTER_MS) await store.remove(kept[i].kid)\n }\n return { keys: out }\n },\n async rotate() {\n const key = await make(newestFirst(await store.list())[0])\n remembered = undefined\n return key.kid\n },\n async reseal() {\n let changed = 0\n const ring = await sealKeys\n for (const key of await store.list()) {\n const opened = await open(key)\n if (opened === null || opened.fresh) continue\n await store.update({ ...key, sealed: await seal(opened.jwk, ring[0]) })\n changed++\n }\n return changed\n },\n }\n}\n\n/** Keys kept in memory: for tests, and for trying contract mode out. Gone on restart. */\nexport function memoryKeyStore(): KeyStore {\n const keys = new Map<string, StoredKey>()\n return {\n async list() { return [...keys.values()] },\n async add(k) { if (!keys.has(k.kid)) keys.set(k.kid, k) },\n async addFirst(k) { if (keys.size === 0) keys.set(k.kid, k) },\n async update(k) { keys.set(k.kid, k) },\n async remove(kid) { keys.delete(kid) },\n }\n}\n\n/**\n * The table postgresKeyStore keeps the keys in, for the application's own\n * migrations (Drizzle, Prisma, plain SQL). The store never creates it.\n */\nexport function pactKeysTableSql(table = 'cauthy_pact_keys'): string {\n checkTable(table)\n return `CREATE TABLE IF NOT EXISTS ${table} (\n kid TEXT PRIMARY KEY,\n public_jwk JSONB NOT NULL,\n sealed TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL\n);`\n}\n\nfunction checkTable(table: string): void {\n if (!/^[a-z_][a-z0-9_]{0,62}$/.test(table)) throw new CauthyConfigError(`the key table's name must be a plain SQL identifier: ${table}`)\n}\n\n/**\n * Runs one SQL statement with $1, $2… parameters and answers its rows: what\n * the application's own database client does. With node-postgres,\n * `(text, params) => pool.query(text, params)`; with postgres.js,\n * `(text, params) => sql.unsafe(text, params)`.\n */\nexport type Query = (text: string, params: unknown[]) => Promise<{ rows: unknown[] } | unknown[]>\n\n/** Keys kept in a table of the application's Postgres database (see pactKeysTableSql). */\nexport function postgresKeyStore(query: Query, options: { table?: string } = {}): KeyStore {\n const table = options.table ?? 'cauthy_pact_keys'\n checkTable(table)\n const rows = async (text: string, params: unknown[]) => {\n const out = await query(text, params)\n return (Array.isArray(out) ? out : out.rows) as Record<string, unknown>[]\n }\n const at = (v: unknown) => (v instanceof Date ? v.toISOString() : new Date(String(v)).toISOString())\n return {\n async list() {\n return (await rows(`SELECT kid, public_jwk, sealed, created_at FROM ${table}`, [])).map(r => ({\n kid: String(r.kid),\n publicJwk: (typeof r.public_jwk === 'string' ? JSON.parse(r.public_jwk) : r.public_jwk) as JWK,\n sealed: String(r.sealed),\n createdAt: at(r.created_at),\n }))\n },\n async add(k) {\n await rows(`INSERT INTO ${table} (kid, public_jwk, sealed, created_at) VALUES ($1, $2, $3, $4) ON CONFLICT (kid) DO NOTHING`,\n [k.kid, JSON.stringify(k.publicJwk), k.sealed, k.createdAt])\n },\n async addFirst(k) {\n // One statement: a key another instance committed a moment ago is seen.\n await rows(`INSERT INTO ${table} (kid, public_jwk, sealed, created_at) SELECT $1, $2, $3, $4 WHERE NOT EXISTS (SELECT 1 FROM ${table}) ON CONFLICT (kid) DO NOTHING`,\n [k.kid, JSON.stringify(k.publicJwk), k.sealed, k.createdAt])\n },\n async update(k) {\n await rows(`UPDATE ${table} SET sealed = $2 WHERE kid = $1`, [k.kid, k.sealed])\n },\n async remove(kid) {\n await rows(`DELETE FROM ${table} WHERE kid = $1`, [kid])\n },\n }\n}\n","/** How an application is set up to sign its people in with Cauthy. */\nexport interface CauthyOptions {\n /** Cauthy's public address, e.g. `https://id.example.com`. Default: `CAUTHY_ISSUER`. */\n issuer?: string\n /** The application's OAuth client id. Default: `CAUTHY_CLIENT_ID`. */\n clientId?: string\n /** Its secret, when the client is confidential. Default: `CAUTHY_CLIENT_SECRET`. */\n clientSecret?: string\n /**\n * The key the session cookie is encrypted with: any string of 32 characters\n * or more, kept on the server. Default: `CAUTHY_SECRET`.\n */\n secret?: string\n /**\n * The system this application is, as its organizations are filed under in\n * Cauthy (`\"acme-crm\"`). When set, a session's organizations are that\n * system's. Default: `CAUTHY_SYSTEM`.\n */\n system?: string\n /**\n * The workspace's slug in Cauthy. Needed for the application's own sign-in\n * screens (`signIn.email`, `signUp.email`…), which sign people in through\n * the application's server instead of Cauthy's page. Default: `CAUTHY_TENANT`.\n */\n tenant?: string\n /** Where the routes are mounted. Default: `/api/auth`. */\n basePath?: string\n /**\n * What happens to a POST without `X-Cauthy-Request: 1` (every POST but\n * `/logout`). `required`, the default, answers `403 csrf_required`, as\n * protocol level strict asks. `warn` lets it through and says so once per\n * process: for the one deploy that moves an application whose browser still\n * runs cauthy-auth 0.42, which does not send the header. Remove it once the\n * browser runs cauthy-auth 0.43 or @cauthyhq/js.\n */\n requestHeader?: 'required' | 'warn'\n /**\n * The audience of the access tokens `auth.verifyToken` accepts: the\n * deployment's `JWT_AUDIENCE`, which Cauthy requires its operator to set and\n * has no default for. Default: `CAUTHY_AUDIENCE`.\n */\n audience?: string\n /**\n * The workspace's id (a lowercase UUID) for `auth.verifyToken`. By contract\n * it comes from the link's `tenant_id`; otherwise: this, or `CAUTHY_TENANT_ID`.\n */\n tenantId?: string\n /**\n * The application's own public origin (`https://app.example.com`): the\n * redirect URI, the cookies' `Secure` flag and which forms are its own come\n * from it. Default: `CAUTHY_BASE_URL`, else the origin of each request, with\n * the scheme a proxy names in `x-forwarded-proto`. Set it whenever a proxy\n * also changes the host or the port.\n */\n baseURL?: string\n /** Where a person lands after signing in, when no `return_to` was asked. Default: `/`. */\n afterSignIn?: string\n /** Where a person lands after signing out. Default: `/`. */\n afterSignOut?: string\n /** Name of the session cookie. Default: `cauthy_session`. */\n cookieName?: string\n /**\n * A key the application proves who it is with when it acts on its own\n * (`auth.system`), instead of its secret. Default: `CAUTHY_SYSTEM_KEY` and\n * `CAUTHY_SYSTEM_KEY_ID`.\n */\n systemKey?: import('./system').SystemKey\n /**\n * The application's own pages for what arrives by mail. A link Cauthy mails\n * opens the page named here, with a `token` to give back; with none, it\n * opens Cauthy's own page.\n *\n * signIn where `<SignIn />` is: a sign-in link opens it\n * resetPassword where `<ResetPassword />` is\n * verifyEmail where `<VerifyEmail />` is\n * acceptInvite where `<AcceptInvite />` is: an invitation opens it\n *\n * An invitation sent by `auth.system.members.add` or `auth.system.invite`\n * opens `acceptInvite` only when the application knows its public origin\n * (`baseURL`): that call has no request to read it from.\n */\n pages?: { signIn?: string; resetPassword?: string; verifyEmail?: string; acceptInvite?: string }\n /**\n * The address of the person a request comes from, passed on to Cauthy so\n * its limits on attempts count the person and not this server. Default: the\n * last address in `X-Forwarded-For`, which is the one the proxy in front of\n * the application wrote. With several proxies, or none, give your own.\n */\n clientIp?: (request: Request) => string | undefined\n /** `fetch` to reach Cauthy with. Default: the global one. */\n fetch?: typeof fetch\n /**\n * How long a call to Cauthy may take before it fails, in milliseconds.\n * `system`: the application's own calls (`auth.system`, and the proof its\n * sign-ins carry). Default 2000. `request`: the sign-in screens' calls and\n * the code, refresh and userinfo calls of a session. Default 5000. The\n * push-approval event stream has no deadline.\n */\n timeouts?: { system?: number; request?: number }\n /**\n * Run by contract: the issuer, the workspace, the clients and where the\n * application's keys are published all come from its link in the network's\n * context graph, and none of the CAUTHY_* variables may be set. See\n * \"Connect a system by contract\".\n *\n * createCauthy({ contract: { graph, system: 'acme-crm' }, keys: postgresKeyStore(query) })\n */\n contract?: import('./contract').ContractOptions\n /** Where the application keeps its own key, with a contract (@cauthyhq/node/keys). */\n keys?: import('./keys').KeyStore\n /**\n * The one secret of a contracted application: every key it needs is\n * derived from it. 32 random bytes or more. Default: `PACT_ROOT_KEY`.\n */\n rootKey?: string\n /** The root key before the current one, while rotating it. Default: `PACT_ROOT_KEY_PREVIOUS`. */\n rootKeyPrevious?: string\n /**\n * Where `auth.webhook` remembers the deliveries it took, so one sent twice\n * is refused. Default: this process's memory. Give a shared one (a table)\n * when the application runs on several instances.\n */\n webhookReplay?: import('./webhook').ReplayStore\n}\n\n/** The person signed in. Every moment is UTC. */\nexport interface SessionUser {\n id: string\n email: string\n name: string\n emailVerified: boolean\n /**\n * True when the account ran out of time to confirm its address: let it read\n * and refuse its writes.\n */\n restricted: boolean\n /** When an unconfirmed address stops being enough (ISO 8601, UTC), if there is a deadline. */\n emailVerifyBy?: string\n /** The language the person reads in (\"es\", \"en-US\"), when Cauthy knows it. */\n locale?: string\n /** A guest: a visitor's session before they sign up. No email; `account.upgrade` makes it an account, keeping `id`. */\n isAnonymous?: boolean\n}\n\n/** A value JSON can carry. What crosses from server to browser is typed as this, never `unknown`. */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }\n\n/** An organization the person belongs to, with their role in it. */\nexport interface SessionOrganization {\n id: string\n system: string\n name: string\n kind: string\n externalId?: string\n role: string\n /** What the system keeps about the organization, as it filed it in Cauthy. */\n metadata: Record<string, JsonValue>\n /**\n * What the person may do here, as \"resource:action\" entries (\"*\" is any):\n * their role's permissions, what it inherits, and anything given to them\n * apart from it. Absent when the system keeps no catalog of roles in Cauthy.\n * Check with `can(organization, resource, action)`.\n */\n permissions?: string[]\n /** What the person is denied here, whatever else allows it. */\n denied?: string[]\n /**\n * Permissions that hold only when their condition does (an amount under a\n * limit, the person's own records…). They are in neither list above: `can`\n * answers no for them, and `auth.check` asks Cauthy. Present, maybe empty,\n * whenever `permissions` is.\n */\n conditional?: ConditionalPermission[]\n}\n\n/** A permission with a condition: Cauthy decides it per question (`auth.check`). */\nexport interface ConditionalPermission {\n resource: string\n action: string\n effect: 'allow' | 'deny'\n}\n\n/** What to ask Cauthy about: an action on a resource, with what the conditions read of it. */\nexport interface CheckQuestion {\n resource: string\n action: string\n /** What a condition reads as `resource.attrs`: `{ amount: 500 }`. */\n attributes?: Record<string, unknown>\n /** The record's id, as a condition reads it (`resource.id`). */\n resourceId?: string\n /** Whose the record is (`resource.owner_id`): `resource.owner_id == subject.id`. */\n ownerId?: string\n}\n\n/** An organization, by its id or as the session has it. */\nexport type OrganizationRef = string | { id: string }\n\n/** The rule that decided a check. */\nexport interface CheckRule {\n /** A permission of the person's role, or an exception given to the person. */\n source: 'role' | 'exception'\n /** The role, when `source` is `role`. */\n role?: string\n kind: string\n resource: string\n action: string\n effect: 'allow' | 'deny'\n condition?: string\n}\n\n/** Cauthy's answer to a check. */\nexport interface CheckResult {\n allowed: boolean\n /** Why, in words: \"allowed by role accountant: invoices:approve\". */\n reason: string\n /** The rule that decided; absent when none did (nothing allows it). */\n rule?: CheckRule\n}\n\n/** A signed-in person, as the server sees them. */\nexport interface Session {\n user: SessionUser\n /** The Cauthy tenant the account is of. */\n tenantId: string\n /** The roles the account holds in that tenant. */\n roles: string[]\n /**\n * Cauthy's access token for this person: send it as `Authorization: Bearer`\n * to another service that verifies Cauthy's tokens. Never give it to the browser.\n */\n accessToken: string\n /** When the access token expires and the session is checked with Cauthy again (ISO 8601, UTC). */\n expiresAt: string\n}\n\n/** What the browser is told of a session: who, and their organizations — never a token. */\nexport interface PublicSession {\n user: SessionUser\n tenantId: string\n roles: string[]\n organizations: SessionOrganization[]\n expiresAt: string\n}\n\n/** Why there is no session. */\nexport type SessionStatus =\n | 'authenticated'\n | 'unauthenticated' // nobody is signed in\n | 'expired' // signed in, but the session must be renewed first (see `Cauthy.resolve`)\n\nexport class CauthyConfigError extends Error {\n constructor(message: string) {\n super(`@cauthyhq/node: ${message}`)\n this.name = 'CauthyConfigError'\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,sBAAmD;AACnD,uBAAwB;;;ACaxB,kBAAsG;;;ACoO/F,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC3B,UAAM,mBAAmB,OAAO,EAAE;AAClC,SAAK,OAAO;AAAA,EACd;AACF;;;ADzMO,IAAM,kBAAkB,KAAK,KAAK,KAAK;AAE9C,IAAM,kBAAkB,IAAI,KAAK,KAAK,KAAK;AAE3C,IAAM,iBAAiB,IAAI,KAAK;AAEzB,IAAM,iBAAiB;AAEvB,IAAM,iBAAiB,iBAAiB,MAAM;AAErD,IAAM,kBAAkB,IAAI;AAwHrB,SAAS,iBAA2B;AACzC,QAAM,OAAO,oBAAI,IAAuB;AACxC,SAAO;AAAA,IACL,MAAM,OAAO;AAAE,aAAO,CAAC,GAAG,KAAK,OAAO,CAAC;AAAA,IAAE;AAAA,IACzC,MAAM,IAAI,GAAG;AAAE,UAAI,CAAC,KAAK,IAAI,EAAE,GAAG,EAAG,MAAK,IAAI,EAAE,KAAK,CAAC;AAAA,IAAE;AAAA,IACxD,MAAM,SAAS,GAAG;AAAE,UAAI,KAAK,SAAS,EAAG,MAAK,IAAI,EAAE,KAAK,CAAC;AAAA,IAAE;AAAA,IAC5D,MAAM,OAAO,GAAG;AAAE,WAAK,IAAI,EAAE,KAAK,CAAC;AAAA,IAAE;AAAA,IACrC,MAAM,OAAO,KAAK;AAAE,WAAK,OAAO,GAAG;AAAA,IAAE;AAAA,EACvC;AACF;AAMO,SAAS,iBAAiB,QAAQ,oBAA4B;AACnE,aAAW,KAAK;AAChB,SAAO,8BAA8B,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAM5C;AAEA,SAAS,WAAW,OAAqB;AACvC,MAAI,CAAC,0BAA0B,KAAK,KAAK,EAAG,OAAM,IAAI,kBAAkB,wDAAwD,KAAK,EAAE;AACzI;AAWO,SAAS,iBAAiB,OAAc,UAA8B,CAAC,GAAa;AACzF,QAAM,QAAQ,QAAQ,SAAS;AAC/B,aAAW,KAAK;AAChB,QAAM,OAAO,OAAO,MAAc,WAAsB;AACtD,UAAM,MAAM,MAAM,MAAM,MAAM,MAAM;AACpC,WAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,IAAI;AAAA,EACzC;AACA,QAAM,KAAK,CAAC,MAAgB,aAAa,OAAO,EAAE,YAAY,IAAI,IAAI,KAAK,OAAO,CAAC,CAAC,EAAE,YAAY;AAClG,SAAO;AAAA,IACL,MAAM,OAAO;AACX,cAAQ,MAAM,KAAK,mDAAmD,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,QAAM;AAAA,QAC5F,KAAK,OAAO,EAAE,GAAG;AAAA,QACjB,WAAY,OAAO,EAAE,eAAe,WAAW,KAAK,MAAM,EAAE,UAAU,IAAI,EAAE;AAAA,QAC5E,QAAQ,OAAO,EAAE,MAAM;AAAA,QACvB,WAAW,GAAG,EAAE,UAAU;AAAA,MAC5B,EAAE;AAAA,IACJ;AAAA,IACA,MAAM,IAAI,GAAG;AACX,YAAM;AAAA,QAAK,eAAe,KAAK;AAAA,QAC7B,CAAC,EAAE,KAAK,KAAK,UAAU,EAAE,SAAS,GAAG,EAAE,QAAQ,EAAE,SAAS;AAAA,MAAC;AAAA,IAC/D;AAAA,IACA,MAAM,SAAS,GAAG;AAEhB,YAAM;AAAA,QAAK,eAAe,KAAK,gGAAgG,KAAK;AAAA,QAClI,CAAC,EAAE,KAAK,KAAK,UAAU,EAAE,SAAS,GAAG,EAAE,QAAQ,EAAE,SAAS;AAAA,MAAC;AAAA,IAC/D;AAAA,IACA,MAAM,OAAO,GAAG;AACd,YAAM,KAAK,UAAU,KAAK,mCAAmC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC;AAAA,IAChF;AAAA,IACA,MAAM,OAAO,KAAK;AAChB,YAAM,KAAK,eAAe,KAAK,mBAAmB,CAAC,GAAG,CAAC;AAAA,IACzD;AAAA,EACF;AACF;;;ADhPO,SAAS,aAAa,MAAwB;AACnD,iBAAe,OAA6B;AAC1C,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,UAAM,0BAAS,MAAM,MAAM,CAAC;AACtD,aAAO,MAAM,QAAQ,MAAM,IAAK,SAAyB,CAAC;AAAA,IAC5D,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,QAAO,CAAC;AAC9D,YAAM;AAAA,IACR;AAAA,EACF;AAGA,MAAI,UAAyB,QAAQ,QAAQ;AAC7C,WAAS,MAAM,QAA2D;AACxE,cAAU,QAAQ,KAAK,YAAY;AACjC,YAAM,OAAO,OAAO,MAAM,KAAK,CAAC;AAChC,gBAAM,2BAAM,0BAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,YAAM,MAAM,GAAG,IAAI,IAAI,QAAQ,GAAG;AAClC,gBAAM,2BAAU,KAAK,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,EAAE,MAAM,IAAM,CAAC;AAC1E,gBAAM,wBAAO,KAAK,IAAI;AAAA,IACxB,CAAC;AACD,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK,OAAK,MAAM,UAAS,KAAK,KAAK,OAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC,CAAE;AAAA,IAC/E,UAAU,OAAK,MAAM,UAAS,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,CAAE;AAAA,IAC3D,QAAQ,OAAK,MAAM,UAAQ,KAAK,IAAI,OAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAE,CAAC;AAAA,IACnE,QAAQ,SAAO,MAAM,UAAQ,KAAK,OAAO,OAAK,EAAE,QAAQ,GAAG,CAAC;AAAA,EAC9D;AACF;","names":[]}
package/dist/keys.mjs ADDED
@@ -0,0 +1,129 @@
1
+ // src/keystores.ts
2
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
3
+ import { dirname } from "path";
4
+
5
+ // src/keys.ts
6
+ import { calculateJwkThumbprint, exportJWK, generateKeyPair, importJWK } from "jose";
7
+
8
+ // src/types.ts
9
+ var CauthyConfigError = class extends Error {
10
+ constructor(message) {
11
+ super(`@cauthyhq/node: ${message}`);
12
+ this.name = "CauthyConfigError";
13
+ }
14
+ };
15
+
16
+ // src/keys.ts
17
+ var RETIRE_AFTER_MS = 24 * 60 * 60 * 1e3;
18
+ var FORGET_AFTER_MS = 7 * 24 * 60 * 60 * 1e3;
19
+ var CURRENT_TTL_MS = 5 * 60 * 1e3;
20
+ var JWKS_MAX_AGE_S = 300;
21
+ var SIGN_AFTER_MS = (JWKS_MAX_AGE_S + 60) * 1e3;
22
+ var SETTLING_TTL_MS = 5 * 1e3;
23
+ function memoryKeyStore() {
24
+ const keys = /* @__PURE__ */ new Map();
25
+ return {
26
+ async list() {
27
+ return [...keys.values()];
28
+ },
29
+ async add(k) {
30
+ if (!keys.has(k.kid)) keys.set(k.kid, k);
31
+ },
32
+ async addFirst(k) {
33
+ if (keys.size === 0) keys.set(k.kid, k);
34
+ },
35
+ async update(k) {
36
+ keys.set(k.kid, k);
37
+ },
38
+ async remove(kid) {
39
+ keys.delete(kid);
40
+ }
41
+ };
42
+ }
43
+ function pactKeysTableSql(table = "cauthy_pact_keys") {
44
+ checkTable(table);
45
+ return `CREATE TABLE IF NOT EXISTS ${table} (
46
+ kid TEXT PRIMARY KEY,
47
+ public_jwk JSONB NOT NULL,
48
+ sealed TEXT NOT NULL,
49
+ created_at TIMESTAMPTZ NOT NULL
50
+ );`;
51
+ }
52
+ function checkTable(table) {
53
+ if (!/^[a-z_][a-z0-9_]{0,62}$/.test(table)) throw new CauthyConfigError(`the key table's name must be a plain SQL identifier: ${table}`);
54
+ }
55
+ function postgresKeyStore(query, options = {}) {
56
+ const table = options.table ?? "cauthy_pact_keys";
57
+ checkTable(table);
58
+ const rows = async (text, params) => {
59
+ const out = await query(text, params);
60
+ return Array.isArray(out) ? out : out.rows;
61
+ };
62
+ const at = (v) => v instanceof Date ? v.toISOString() : new Date(String(v)).toISOString();
63
+ return {
64
+ async list() {
65
+ return (await rows(`SELECT kid, public_jwk, sealed, created_at FROM ${table}`, [])).map((r) => ({
66
+ kid: String(r.kid),
67
+ publicJwk: typeof r.public_jwk === "string" ? JSON.parse(r.public_jwk) : r.public_jwk,
68
+ sealed: String(r.sealed),
69
+ createdAt: at(r.created_at)
70
+ }));
71
+ },
72
+ async add(k) {
73
+ await rows(
74
+ `INSERT INTO ${table} (kid, public_jwk, sealed, created_at) VALUES ($1, $2, $3, $4) ON CONFLICT (kid) DO NOTHING`,
75
+ [k.kid, JSON.stringify(k.publicJwk), k.sealed, k.createdAt]
76
+ );
77
+ },
78
+ async addFirst(k) {
79
+ await rows(
80
+ `INSERT INTO ${table} (kid, public_jwk, sealed, created_at) SELECT $1, $2, $3, $4 WHERE NOT EXISTS (SELECT 1 FROM ${table}) ON CONFLICT (kid) DO NOTHING`,
81
+ [k.kid, JSON.stringify(k.publicJwk), k.sealed, k.createdAt]
82
+ );
83
+ },
84
+ async update(k) {
85
+ await rows(`UPDATE ${table} SET sealed = $2 WHERE kid = $1`, [k.kid, k.sealed]);
86
+ },
87
+ async remove(kid) {
88
+ await rows(`DELETE FROM ${table} WHERE kid = $1`, [kid]);
89
+ }
90
+ };
91
+ }
92
+
93
+ // src/keystores.ts
94
+ function fileKeyStore(path) {
95
+ async function read() {
96
+ try {
97
+ const parsed = JSON.parse(await readFile(path, "utf8"));
98
+ return Array.isArray(parsed) ? parsed : [];
99
+ } catch (err) {
100
+ if (err.code === "ENOENT") return [];
101
+ throw err;
102
+ }
103
+ }
104
+ let writing = Promise.resolve();
105
+ function write(change) {
106
+ writing = writing.then(async () => {
107
+ const next = change(await read());
108
+ await mkdir(dirname(path), { recursive: true });
109
+ const tmp = `${path}.${process.pid}.tmp`;
110
+ await writeFile(tmp, JSON.stringify(next, null, 2) + "\n", { mode: 384 });
111
+ await rename(tmp, path);
112
+ });
113
+ return writing;
114
+ }
115
+ return {
116
+ list: read,
117
+ add: (k) => write((keys) => keys.some((x) => x.kid === k.kid) ? keys : [...keys, k]),
118
+ addFirst: (k) => write((keys) => keys.length > 0 ? keys : [k]),
119
+ update: (k) => write((keys) => keys.map((x) => x.kid === k.kid ? k : x)),
120
+ remove: (kid) => write((keys) => keys.filter((x) => x.kid !== kid))
121
+ };
122
+ }
123
+ export {
124
+ fileKeyStore,
125
+ memoryKeyStore,
126
+ pactKeysTableSql,
127
+ postgresKeyStore
128
+ };
129
+ //# sourceMappingURL=keys.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/keystores.ts","../src/keys.ts","../src/types.ts"],"sourcesContent":["// @cauthyhq/node/keys: where a contracted application keeps its own key.\n//\n// import { postgresKeyStore } from '@cauthyhq/node/keys'\n// keys: postgresKeyStore((text, params) => pool.query(text, params))\n//\n// The file store is for a developer's machine: production keeps its key in\n// its database, where every instance reads the same one.\n\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport { dirname } from 'node:path'\nimport type { KeyStore, StoredKey } from './keys'\n\nexport { memoryKeyStore, postgresKeyStore, pactKeysTableSql, type KeyStore, type PactKeys, type Query, type StoredKey } from './keys'\n\n/** Keys kept in a JSON file, readable only by its owner. For development. */\nexport function fileKeyStore(path: string): KeyStore {\n async function read(): Promise<StoredKey[]> {\n try {\n const parsed = JSON.parse(await readFile(path, 'utf8')) as unknown\n return Array.isArray(parsed) ? (parsed as StoredKey[]) : []\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n }\n // Written whole to a file beside it, then moved over it: a crash never\n // leaves half a file.\n let writing: Promise<void> = Promise.resolve()\n function write(change: (keys: StoredKey[]) => StoredKey[]): Promise<void> {\n writing = writing.then(async () => {\n const next = change(await read())\n await mkdir(dirname(path), { recursive: true })\n const tmp = `${path}.${process.pid}.tmp`\n await writeFile(tmp, JSON.stringify(next, null, 2) + '\\n', { mode: 0o600 })\n await rename(tmp, path)\n })\n return writing\n }\n return {\n list: read,\n add: k => write(keys => (keys.some(x => x.kid === k.kid) ? keys : [...keys, k])),\n addFirst: k => write(keys => (keys.length > 0 ? keys : [k])),\n update: k => write(keys => keys.map(x => (x.kid === k.kid ? k : x))),\n remove: kid => write(keys => keys.filter(x => x.kid !== kid)),\n }\n}\n","// The application's own key: made by the application, kept in its own\n// database, its public half published, its private half sealed at rest.\n//\n// A contracted application proves who it is to Cauthy with an assertion it\n// signs (private_key_jwt). Nobody hands it that key: the first time it needs\n// one it makes it, seals the private half with the key derived from its root\n// key (pact/seal/v1) and keeps it in a KeyStore — a table of its database, a\n// file while developing. Its public half is served at the jwks_url its\n// manifest names, where Cauthy reads it.\n//\n// Rotating: `rotate()` makes a new key, published at once. It signs once it\n// has been published longer than anyone caches the keys (SIGN_AFTER_MS):\n// Cauthy, meeting a key it does not know, reads the keys again, and a copy\n// cached on the way must already hold it. The one before it stays published\n// 24 hours, so an assertion signed a moment ago, or by an instance that has\n// not seen the new key yet, still verifies.\n//\n// The first key is made by whichever instance needs it first: the store keeps\n// it only while it holds none (addFirst), so instances starting together end\n// with one key. A store without addFirst may end with several; every one is\n// published, and every instance settles on the same one within seconds.\n\nimport { calculateJwkThumbprint, exportJWK, generateKeyPair, importJWK, type JWK, type KeyLike } from 'jose'\nimport { seal, unseal } from './seal'\nimport type { KeyRing } from './rootkey'\nimport { CauthyConfigError } from './types'\n\n/** A key as a KeyStore keeps it. Nothing in it is secret but sealed. */\nexport interface StoredKey {\n /** Its id, as published (the JWK thumbprint). */\n kid: string\n /** Its public half, as published. */\n publicJwk: JWK\n /** Its private half, sealed with the key derived from the root key. */\n sealed: string\n /** When it was made (ISO 8601, UTC). The newest signs. */\n createdAt: string\n}\n\n/** Where the application keeps its keys: a table, a file. */\nexport interface KeyStore {\n /** Every key kept. */\n list(): Promise<StoredKey[]>\n /** Keeps a new key. A kid already kept is left as it is. */\n add(key: StoredKey): Promise<void>\n /** Keeps the key only while the store holds none, in one step: instances starting together make one key. */\n addFirst?(key: StoredKey): Promise<void>\n /** Replaces a key's sealed private half (after the root key was rotated). */\n update(key: StoredKey): Promise<void>\n /** Forgets a key no longer published. */\n remove(kid: string): Promise<void>\n}\n\n/** How long a key stays published after a newer one appears. */\nexport const RETIRE_AFTER_MS = 24 * 60 * 60 * 1000\n/** How long a key that is no longer published is kept before it is forgotten. */\nconst FORGET_AFTER_MS = 7 * 24 * 60 * 60 * 1000\n/** How long the key that signs is remembered before the store is read again. */\nconst CURRENT_TTL_MS = 5 * 60 * 1000\n/** How long the published keys may be cached (the jwks_url's max-age). */\nexport const JWKS_MAX_AGE_S = 300\n/** How long a new key is published before it signs: longer than any cached copy of the keys. */\nexport const SIGN_AFTER_MS = (JWKS_MAX_AGE_S + 60) * 1000\n/** While every key is younger than that, the store is read again this often, so instances settle on one. */\nconst SETTLING_TTL_MS = 5 * 1000\n\n/** The application's keys, as auth.pact.keys gives them. */\nexport interface PactKeys {\n /** The key that signs now, made the first time there is none: the newest that has been published long enough. */\n current(): Promise<{ kid: string; privateKey: KeyLike | Uint8Array }>\n /** The public keys to publish: the one that signs, and the one before it for 24 hours after. */\n published(): Promise<{ keys: JWK[] }>\n /** Makes a new key, published at once and signing once SIGN_AFTER_MS has passed. Answers its id. */\n rotate(): Promise<string>\n /** Seals every key again with the current root key; answers how many changed. */\n reseal(): Promise<number>\n}\n\nconst newestFirst = (keys: StoredKey[]) => [...keys].sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.kid.localeCompare(a.kid))\n\nfunction published(key: StoredKey): JWK {\n const { kty, n, e } = key.publicJwk\n return { kty, n, e, kid: key.kid, alg: 'RS256', use: 'sig' }\n}\n\nexport function pactKeys(store: KeyStore, sealKeys: Promise<KeyRing>, now: () => number = Date.now): PactKeys {\n let remembered: { kid: string; privateKey: KeyLike | Uint8Array; until: number } | undefined\n let making: Promise<StoredKey> | undefined\n\n async function make(after?: StoredKey): Promise<StoredKey> {\n const { publicKey, privateKey } = await generateKeyPair('RS256', { modulusLength: 2048, extractable: true })\n const publicJwk = await exportJWK(publicKey)\n const kid = await calculateJwkThumbprint(publicJwk)\n // Always newer than the key it follows, however fast the rotation.\n const at = Math.max(now(), after ? Date.parse(after.createdAt) + 1 : 0)\n const key: StoredKey = { kid, publicJwk, sealed: await seal(await exportJWK(privateKey), (await sealKeys)[0]), createdAt: new Date(at).toISOString() }\n if (!after && store.addFirst) await store.addFirst(key)\n else await store.add(key)\n return key\n }\n\n const age = (key: StoredKey) => now() - Date.parse(key.createdAt)\n\n // The key that signs: the newest published long enough. When none is (the\n // first minutes of the first key, or a race of first keys), the oldest, read\n // again shortly so every instance lands on the same one.\n function signer(kept: StoredKey[]): { key: StoredKey; until: number } {\n const ripe = kept.find(k => age(k) >= SIGN_AFTER_MS)\n if (!ripe) return { key: kept[kept.length - 1], until: now() + SETTLING_TTL_MS }\n // A newer key ripens later: read again then, not after the usual while.\n const until = ripe === kept[0] ? now() + CURRENT_TTL_MS : Math.min(now() + CURRENT_TTL_MS, Date.parse(kept[0].createdAt) + SIGN_AFTER_MS)\n return { key: ripe, until }\n }\n\n async function open(key: StoredKey): Promise<{ jwk: JWK; fresh: boolean } | null> {\n const ring = await sealKeys\n for (let i = 0; i < ring.length; i++) {\n const jwk = await unseal<JWK>(key.sealed, ring[i])\n if (jwk !== null) return { jwk, fresh: i === 0 }\n }\n return null\n }\n\n // Every key kept, newest first; the first one is made when there is none.\n async function keptKeys(): Promise<StoredKey[]> {\n const kept = newestFirst(await store.list())\n if (kept.length > 0) return kept\n // Two callers at once make one key; two instances at once, one with\n // addFirst, and otherwise several that are all published.\n making ??= make().finally(() => { making = undefined })\n await making\n const again = newestFirst(await store.list())\n if (again.length === 0) throw new CauthyConfigError('the key store kept no key: check that it writes')\n return again\n }\n\n return {\n async current() {\n if (remembered && remembered.until > now()) return remembered\n const { key, until } = signer(await keptKeys())\n if (remembered?.kid === key.kid) {\n remembered.until = until\n return remembered\n }\n const opened = await open(key)\n if (opened === null) {\n throw new CauthyConfigError(`the key ${key.kid} cannot be opened with the root key (PACT_ROOT_KEY): ` +\n 'if the root key was rotated, set the previous one as PACT_ROOT_KEY_PREVIOUS')\n }\n remembered = { kid: key.kid, privateKey: await importJWK(opened.jwk, 'RS256'), until }\n return remembered\n },\n async published() {\n // Asked before anything was signed: the key is made now, so what is\n // published is the key that will sign.\n const kept = await keptKeys()\n const out: JWK[] = []\n for (let i = 0; i < kept.length; i++) {\n // The newest is published; an older one while its successor is young.\n if (i === 0 || now() - Date.parse(kept[i - 1].createdAt) < RETIRE_AFTER_MS) out.push(published(kept[i]))\n else if (now() - Date.parse(kept[i - 1].createdAt) > FORGET_AFTER_MS) await store.remove(kept[i].kid)\n }\n return { keys: out }\n },\n async rotate() {\n const key = await make(newestFirst(await store.list())[0])\n remembered = undefined\n return key.kid\n },\n async reseal() {\n let changed = 0\n const ring = await sealKeys\n for (const key of await store.list()) {\n const opened = await open(key)\n if (opened === null || opened.fresh) continue\n await store.update({ ...key, sealed: await seal(opened.jwk, ring[0]) })\n changed++\n }\n return changed\n },\n }\n}\n\n/** Keys kept in memory: for tests, and for trying contract mode out. Gone on restart. */\nexport function memoryKeyStore(): KeyStore {\n const keys = new Map<string, StoredKey>()\n return {\n async list() { return [...keys.values()] },\n async add(k) { if (!keys.has(k.kid)) keys.set(k.kid, k) },\n async addFirst(k) { if (keys.size === 0) keys.set(k.kid, k) },\n async update(k) { keys.set(k.kid, k) },\n async remove(kid) { keys.delete(kid) },\n }\n}\n\n/**\n * The table postgresKeyStore keeps the keys in, for the application's own\n * migrations (Drizzle, Prisma, plain SQL). The store never creates it.\n */\nexport function pactKeysTableSql(table = 'cauthy_pact_keys'): string {\n checkTable(table)\n return `CREATE TABLE IF NOT EXISTS ${table} (\n kid TEXT PRIMARY KEY,\n public_jwk JSONB NOT NULL,\n sealed TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL\n);`\n}\n\nfunction checkTable(table: string): void {\n if (!/^[a-z_][a-z0-9_]{0,62}$/.test(table)) throw new CauthyConfigError(`the key table's name must be a plain SQL identifier: ${table}`)\n}\n\n/**\n * Runs one SQL statement with $1, $2… parameters and answers its rows: what\n * the application's own database client does. With node-postgres,\n * `(text, params) => pool.query(text, params)`; with postgres.js,\n * `(text, params) => sql.unsafe(text, params)`.\n */\nexport type Query = (text: string, params: unknown[]) => Promise<{ rows: unknown[] } | unknown[]>\n\n/** Keys kept in a table of the application's Postgres database (see pactKeysTableSql). */\nexport function postgresKeyStore(query: Query, options: { table?: string } = {}): KeyStore {\n const table = options.table ?? 'cauthy_pact_keys'\n checkTable(table)\n const rows = async (text: string, params: unknown[]) => {\n const out = await query(text, params)\n return (Array.isArray(out) ? out : out.rows) as Record<string, unknown>[]\n }\n const at = (v: unknown) => (v instanceof Date ? v.toISOString() : new Date(String(v)).toISOString())\n return {\n async list() {\n return (await rows(`SELECT kid, public_jwk, sealed, created_at FROM ${table}`, [])).map(r => ({\n kid: String(r.kid),\n publicJwk: (typeof r.public_jwk === 'string' ? JSON.parse(r.public_jwk) : r.public_jwk) as JWK,\n sealed: String(r.sealed),\n createdAt: at(r.created_at),\n }))\n },\n async add(k) {\n await rows(`INSERT INTO ${table} (kid, public_jwk, sealed, created_at) VALUES ($1, $2, $3, $4) ON CONFLICT (kid) DO NOTHING`,\n [k.kid, JSON.stringify(k.publicJwk), k.sealed, k.createdAt])\n },\n async addFirst(k) {\n // One statement: a key another instance committed a moment ago is seen.\n await rows(`INSERT INTO ${table} (kid, public_jwk, sealed, created_at) SELECT $1, $2, $3, $4 WHERE NOT EXISTS (SELECT 1 FROM ${table}) ON CONFLICT (kid) DO NOTHING`,\n [k.kid, JSON.stringify(k.publicJwk), k.sealed, k.createdAt])\n },\n async update(k) {\n await rows(`UPDATE ${table} SET sealed = $2 WHERE kid = $1`, [k.kid, k.sealed])\n },\n async remove(kid) {\n await rows(`DELETE FROM ${table} WHERE kid = $1`, [kid])\n },\n }\n}\n","/** How an application is set up to sign its people in with Cauthy. */\nexport interface CauthyOptions {\n /** Cauthy's public address, e.g. `https://id.example.com`. Default: `CAUTHY_ISSUER`. */\n issuer?: string\n /** The application's OAuth client id. Default: `CAUTHY_CLIENT_ID`. */\n clientId?: string\n /** Its secret, when the client is confidential. Default: `CAUTHY_CLIENT_SECRET`. */\n clientSecret?: string\n /**\n * The key the session cookie is encrypted with: any string of 32 characters\n * or more, kept on the server. Default: `CAUTHY_SECRET`.\n */\n secret?: string\n /**\n * The system this application is, as its organizations are filed under in\n * Cauthy (`\"acme-crm\"`). When set, a session's organizations are that\n * system's. Default: `CAUTHY_SYSTEM`.\n */\n system?: string\n /**\n * The workspace's slug in Cauthy. Needed for the application's own sign-in\n * screens (`signIn.email`, `signUp.email`…), which sign people in through\n * the application's server instead of Cauthy's page. Default: `CAUTHY_TENANT`.\n */\n tenant?: string\n /** Where the routes are mounted. Default: `/api/auth`. */\n basePath?: string\n /**\n * What happens to a POST without `X-Cauthy-Request: 1` (every POST but\n * `/logout`). `required`, the default, answers `403 csrf_required`, as\n * protocol level strict asks. `warn` lets it through and says so once per\n * process: for the one deploy that moves an application whose browser still\n * runs cauthy-auth 0.42, which does not send the header. Remove it once the\n * browser runs cauthy-auth 0.43 or @cauthyhq/js.\n */\n requestHeader?: 'required' | 'warn'\n /**\n * The audience of the access tokens `auth.verifyToken` accepts: the\n * deployment's `JWT_AUDIENCE`, which Cauthy requires its operator to set and\n * has no default for. Default: `CAUTHY_AUDIENCE`.\n */\n audience?: string\n /**\n * The workspace's id (a lowercase UUID) for `auth.verifyToken`. By contract\n * it comes from the link's `tenant_id`; otherwise: this, or `CAUTHY_TENANT_ID`.\n */\n tenantId?: string\n /**\n * The application's own public origin (`https://app.example.com`): the\n * redirect URI, the cookies' `Secure` flag and which forms are its own come\n * from it. Default: `CAUTHY_BASE_URL`, else the origin of each request, with\n * the scheme a proxy names in `x-forwarded-proto`. Set it whenever a proxy\n * also changes the host or the port.\n */\n baseURL?: string\n /** Where a person lands after signing in, when no `return_to` was asked. Default: `/`. */\n afterSignIn?: string\n /** Where a person lands after signing out. Default: `/`. */\n afterSignOut?: string\n /** Name of the session cookie. Default: `cauthy_session`. */\n cookieName?: string\n /**\n * A key the application proves who it is with when it acts on its own\n * (`auth.system`), instead of its secret. Default: `CAUTHY_SYSTEM_KEY` and\n * `CAUTHY_SYSTEM_KEY_ID`.\n */\n systemKey?: import('./system').SystemKey\n /**\n * The application's own pages for what arrives by mail. A link Cauthy mails\n * opens the page named here, with a `token` to give back; with none, it\n * opens Cauthy's own page.\n *\n * signIn where `<SignIn />` is: a sign-in link opens it\n * resetPassword where `<ResetPassword />` is\n * verifyEmail where `<VerifyEmail />` is\n * acceptInvite where `<AcceptInvite />` is: an invitation opens it\n *\n * An invitation sent by `auth.system.members.add` or `auth.system.invite`\n * opens `acceptInvite` only when the application knows its public origin\n * (`baseURL`): that call has no request to read it from.\n */\n pages?: { signIn?: string; resetPassword?: string; verifyEmail?: string; acceptInvite?: string }\n /**\n * The address of the person a request comes from, passed on to Cauthy so\n * its limits on attempts count the person and not this server. Default: the\n * last address in `X-Forwarded-For`, which is the one the proxy in front of\n * the application wrote. With several proxies, or none, give your own.\n */\n clientIp?: (request: Request) => string | undefined\n /** `fetch` to reach Cauthy with. Default: the global one. */\n fetch?: typeof fetch\n /**\n * How long a call to Cauthy may take before it fails, in milliseconds.\n * `system`: the application's own calls (`auth.system`, and the proof its\n * sign-ins carry). Default 2000. `request`: the sign-in screens' calls and\n * the code, refresh and userinfo calls of a session. Default 5000. The\n * push-approval event stream has no deadline.\n */\n timeouts?: { system?: number; request?: number }\n /**\n * Run by contract: the issuer, the workspace, the clients and where the\n * application's keys are published all come from its link in the network's\n * context graph, and none of the CAUTHY_* variables may be set. See\n * \"Connect a system by contract\".\n *\n * createCauthy({ contract: { graph, system: 'acme-crm' }, keys: postgresKeyStore(query) })\n */\n contract?: import('./contract').ContractOptions\n /** Where the application keeps its own key, with a contract (@cauthyhq/node/keys). */\n keys?: import('./keys').KeyStore\n /**\n * The one secret of a contracted application: every key it needs is\n * derived from it. 32 random bytes or more. Default: `PACT_ROOT_KEY`.\n */\n rootKey?: string\n /** The root key before the current one, while rotating it. Default: `PACT_ROOT_KEY_PREVIOUS`. */\n rootKeyPrevious?: string\n /**\n * Where `auth.webhook` remembers the deliveries it took, so one sent twice\n * is refused. Default: this process's memory. Give a shared one (a table)\n * when the application runs on several instances.\n */\n webhookReplay?: import('./webhook').ReplayStore\n}\n\n/** The person signed in. Every moment is UTC. */\nexport interface SessionUser {\n id: string\n email: string\n name: string\n emailVerified: boolean\n /**\n * True when the account ran out of time to confirm its address: let it read\n * and refuse its writes.\n */\n restricted: boolean\n /** When an unconfirmed address stops being enough (ISO 8601, UTC), if there is a deadline. */\n emailVerifyBy?: string\n /** The language the person reads in (\"es\", \"en-US\"), when Cauthy knows it. */\n locale?: string\n /** A guest: a visitor's session before they sign up. No email; `account.upgrade` makes it an account, keeping `id`. */\n isAnonymous?: boolean\n}\n\n/** A value JSON can carry. What crosses from server to browser is typed as this, never `unknown`. */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }\n\n/** An organization the person belongs to, with their role in it. */\nexport interface SessionOrganization {\n id: string\n system: string\n name: string\n kind: string\n externalId?: string\n role: string\n /** What the system keeps about the organization, as it filed it in Cauthy. */\n metadata: Record<string, JsonValue>\n /**\n * What the person may do here, as \"resource:action\" entries (\"*\" is any):\n * their role's permissions, what it inherits, and anything given to them\n * apart from it. Absent when the system keeps no catalog of roles in Cauthy.\n * Check with `can(organization, resource, action)`.\n */\n permissions?: string[]\n /** What the person is denied here, whatever else allows it. */\n denied?: string[]\n /**\n * Permissions that hold only when their condition does (an amount under a\n * limit, the person's own records…). They are in neither list above: `can`\n * answers no for them, and `auth.check` asks Cauthy. Present, maybe empty,\n * whenever `permissions` is.\n */\n conditional?: ConditionalPermission[]\n}\n\n/** A permission with a condition: Cauthy decides it per question (`auth.check`). */\nexport interface ConditionalPermission {\n resource: string\n action: string\n effect: 'allow' | 'deny'\n}\n\n/** What to ask Cauthy about: an action on a resource, with what the conditions read of it. */\nexport interface CheckQuestion {\n resource: string\n action: string\n /** What a condition reads as `resource.attrs`: `{ amount: 500 }`. */\n attributes?: Record<string, unknown>\n /** The record's id, as a condition reads it (`resource.id`). */\n resourceId?: string\n /** Whose the record is (`resource.owner_id`): `resource.owner_id == subject.id`. */\n ownerId?: string\n}\n\n/** An organization, by its id or as the session has it. */\nexport type OrganizationRef = string | { id: string }\n\n/** The rule that decided a check. */\nexport interface CheckRule {\n /** A permission of the person's role, or an exception given to the person. */\n source: 'role' | 'exception'\n /** The role, when `source` is `role`. */\n role?: string\n kind: string\n resource: string\n action: string\n effect: 'allow' | 'deny'\n condition?: string\n}\n\n/** Cauthy's answer to a check. */\nexport interface CheckResult {\n allowed: boolean\n /** Why, in words: \"allowed by role accountant: invoices:approve\". */\n reason: string\n /** The rule that decided; absent when none did (nothing allows it). */\n rule?: CheckRule\n}\n\n/** A signed-in person, as the server sees them. */\nexport interface Session {\n user: SessionUser\n /** The Cauthy tenant the account is of. */\n tenantId: string\n /** The roles the account holds in that tenant. */\n roles: string[]\n /**\n * Cauthy's access token for this person: send it as `Authorization: Bearer`\n * to another service that verifies Cauthy's tokens. Never give it to the browser.\n */\n accessToken: string\n /** When the access token expires and the session is checked with Cauthy again (ISO 8601, UTC). */\n expiresAt: string\n}\n\n/** What the browser is told of a session: who, and their organizations — never a token. */\nexport interface PublicSession {\n user: SessionUser\n tenantId: string\n roles: string[]\n organizations: SessionOrganization[]\n expiresAt: string\n}\n\n/** Why there is no session. */\nexport type SessionStatus =\n | 'authenticated'\n | 'unauthenticated' // nobody is signed in\n | 'expired' // signed in, but the session must be renewed first (see `Cauthy.resolve`)\n\nexport class CauthyConfigError extends Error {\n constructor(message: string) {\n super(`@cauthyhq/node: ${message}`)\n this.name = 'CauthyConfigError'\n }\n}\n"],"mappings":"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","names":[]}