@cauthyhq/node 1.2.0 → 1.3.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 (49) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +16 -28
  3. package/dist/{cauthy-CCSSj3Lg.d.mts → cauthy-CsWUgyv_.d.mts} +42 -145
  4. package/dist/{cauthy-C7gZ-pbv.d.ts → cauthy-DHLBEuqP.d.ts} +42 -145
  5. package/dist/express.d.mts +2 -5
  6. package/dist/express.d.ts +2 -5
  7. package/dist/express.js +1 -2
  8. package/dist/express.js.map +1 -1
  9. package/dist/express.mjs +1 -2
  10. package/dist/express.mjs.map +1 -1
  11. package/dist/fastify.d.mts +1 -3
  12. package/dist/fastify.d.ts +1 -3
  13. package/dist/fastify.js +0 -2
  14. package/dist/fastify.js.map +1 -1
  15. package/dist/fastify.mjs +0 -2
  16. package/dist/fastify.mjs.map +1 -1
  17. package/dist/hono.d.mts +2 -5
  18. package/dist/hono.d.ts +2 -5
  19. package/dist/hono.js +1 -2
  20. package/dist/hono.js.map +1 -1
  21. package/dist/hono.mjs +1 -2
  22. package/dist/hono.mjs.map +1 -1
  23. package/dist/index.d.mts +1 -3
  24. package/dist/index.d.ts +1 -3
  25. package/dist/index.js +187 -530
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +180 -520
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/mcp.js.map +1 -1
  30. package/dist/mcp.mjs.map +1 -1
  31. package/dist/next.d.mts +1 -3
  32. package/dist/next.d.ts +1 -3
  33. package/dist/testing.d.mts +11 -5
  34. package/dist/testing.d.ts +11 -5
  35. package/dist/testing.js +52 -50
  36. package/dist/testing.js.map +1 -1
  37. package/dist/testing.mjs +52 -50
  38. package/dist/testing.mjs.map +1 -1
  39. package/package.json +5 -14
  40. package/dist/identity-BTJ8NB_w.d.mts +0 -27
  41. package/dist/identity-BTJ8NB_w.d.ts +0 -27
  42. package/dist/keys-D5k0omTb.d.mts +0 -64
  43. package/dist/keys-D5k0omTb.d.ts +0 -64
  44. package/dist/keys.d.mts +0 -8
  45. package/dist/keys.d.ts +0 -8
  46. package/dist/keys.js +0 -157
  47. package/dist/keys.js.map +0 -1
  48. package/dist/keys.mjs +0 -129
  49. package/dist/keys.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -54,266 +54,14 @@ async function unseal(sealed, key) {
54
54
  }
55
55
  }
56
56
  async function unsealAny(sealed, ring) {
57
- for (const key of ring) {
58
- const value = await unseal(sealed, key);
59
- if (value !== null) return value;
60
- }
61
- return null;
62
- }
63
-
64
- // src/types.ts
65
- var CauthyConfigError = class extends Error {
66
- constructor(message) {
67
- super(`@cauthyhq/node: ${message}`);
68
- this.name = "CauthyConfigError";
69
- }
70
- };
71
- var CauthyKeyError = class extends CauthyConfigError {
72
- constructor(reason, message) {
73
- super(`CAUTHY_KEY: ${message} (${reason})`);
74
- this.reason = reason;
75
- this.name = "CauthyKeyError";
76
- }
77
- reason;
78
- };
79
-
80
- // src/url.ts
81
- function withoutTrailingSlashes(s) {
82
- let end = s.length;
83
- while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
84
- return s.slice(0, end);
85
- }
86
-
87
- // src/contract.ts
88
- var STANDARD_VARIABLES = [
89
- "CAUTHY_ISSUER",
90
- "CAUTHY_CLIENT_ID",
91
- "CAUTHY_CLIENT_SECRET",
92
- "CAUTHY_SECRET",
93
- "CAUTHY_SYSTEM",
94
- "CAUTHY_TENANT",
95
- "CAUTHY_SYSTEM_KEY",
96
- "CAUTHY_SYSTEM_KEY_ID"
97
- ];
98
- function failClosed(environment) {
99
- const set = STANDARD_VARIABLES.filter((name) => (environment?.[name] ?? "") !== "");
100
- if (set.length > 0) {
101
- throw new CauthyConfigError(`the application runs by contract, and ${set.join(", ")} ${set.length === 1 ? "is" : "are"} also set: remove ` + (set.length === 1 ? "it" : "them") + " from the environment. Everything comes from the contract.");
102
- }
103
- }
104
- var SLUG = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/;
105
- var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
106
- var NIL_UUID = "00000000-0000-0000-0000-000000000000";
107
- var text = (v) => typeof v === "string" ? v : "";
108
- function httpsAddress(raw, what) {
109
- let u;
110
- try {
111
- u = new URL(raw);
112
- } catch {
113
- throw new CauthyConfigError(`the contract's ${what} is not an address: ${JSON.stringify(raw)}`);
114
- }
115
- const local = u.hostname === "localhost" || u.hostname === "127.0.0.1";
116
- if (u.protocol !== "https:" && !(u.protocol === "http:" && local)) {
117
- throw new CauthyConfigError(`the contract's ${what} must be https: ${raw}`);
118
- }
119
- return u;
120
- }
121
- function readContract(options) {
122
- const { system } = options;
123
- const provider = options.provider ?? "cauthy";
124
- if (!SLUG.test(system ?? "")) throw new CauthyConfigError(`contract.system must be the system's id in the graph, e.g. "acme-crm"`);
125
- const graph = options.graph && typeof options.graph === "object" && "default" in options.graph && !("edges" in options.graph) ? options.graph.default : options.graph;
126
- if (!graph || !Array.isArray(graph.edges) || !Array.isArray(graph.nodes)) {
127
- throw new CauthyConfigError("contract.graph is not a context graph: pass the graph.json that `cauthy contracts pull` writes");
128
- }
129
- const node = (id) => graph.nodes?.find((n) => n.type === "system" && n.id === id);
130
- const own = node(system);
131
- if (!own) throw new CauthyConfigError(`the graph has no system "${system}"`);
132
- const cauthy = node(provider);
133
- if (!cauthy) throw new CauthyConfigError(`the graph has no system "${provider}"`);
134
- const links = graph.edges.filter((e2) => e2.relation === "links" && e2.from === system && e2.to === provider && e2.sign_in).filter((e2) => options.link === void 0 || e2.link === options.link);
135
- if (links.length === 0) {
136
- throw new CauthyConfigError(options.link ? `the graph has no link "${options.link}" from ${system} to ${provider} with sign_in` : `no link of the graph signs ${system}'s people in with ${provider}: merge one in the contracts repository, then pull the graph again`);
137
- }
138
- if (links.length > 1) {
139
- throw new CauthyConfigError(`${system} has ${links.length} links to ${provider}: name the one to read (contract.link): ${links.map((l) => text(l.link)).join(", ")}`);
140
- }
141
- const e = links[0];
142
- const link = text(e.link);
143
- const named = text(e.identity?.tenant);
144
- const pinned = text(e.identity?.tenant_id);
145
- if (named === NIL_UUID || pinned === NIL_UUID) throw new CauthyConfigError(`the link ${link} names the nil UUID as its workspace: name the workspace by its slug`);
146
- let tenantId;
147
- let tenant;
148
- if (UUID.test(named)) {
149
- if (pinned !== "" && pinned.toLowerCase() !== named.toLowerCase()) {
150
- throw new CauthyConfigError(`the link ${link} names the workspace ${named} and its tenant_id ${pinned} does not match: pull the graph again`);
151
- }
152
- tenantId = named;
153
- tenant = text((cauthy.tenants ?? []).find((t) => t.id === named)?.slug);
154
- if (tenant === "") throw new CauthyConfigError(`the graph does not name the slug of the workspace ${named} that the link ${link} uses`);
155
- } else {
156
- if (!SLUG.test(named)) throw new CauthyConfigError(`the link ${link} names no workspace: identity.tenant is a UUID or a slug`);
157
- if (!UUID.test(pinned)) {
158
- throw new CauthyConfigError(e.identity?.create_tenant === true ? `the link ${link} asks Cauthy for a new workspace "${named}", and the graph has no tenant_id for it yet: once Cauthy has created it and core-contracts lists it, pull the graph again` : `the link ${link} names the workspace "${named}" by its slug, and the graph carries no tenant_id for it: pull the graph again`);
159
- }
160
- tenantId = pinned;
161
- tenant = named;
162
- }
163
- const issuer = withoutTrailingSlashes(httpsAddress(text(cauthy.url), `address of ${provider}`).href);
164
- const signIn = e.sign_in ?? {};
165
- const clientId = text(signIn.client_id) || `pact.${link}`;
166
- const redirectUris = Array.isArray(signIn.redirect_uris) ? signIn.redirect_uris.map(text).filter(Boolean) : [];
167
- if (redirectUris.length === 0) throw new CauthyConfigError(`the link ${link} names no redirect URI`);
168
- const proof = text(signIn.system_proof);
169
- if (signIn.system_proof !== void 0 && proof !== "off" && proof !== "auto" && proof !== "required") {
170
- throw new CauthyConfigError(`the link ${link} has system_proof ${JSON.stringify(signIn.system_proof)}: it is off, auto or required. Update @cauthyhq/node, or pull the graph again`);
171
- }
172
- const systemProof = proof === "off" || proof === "auto" || proof === "required" ? proof : signIn.require_system_proof === true ? "required" : "off";
173
- if (systemProof !== "off" && !e.service) {
174
- throw new CauthyConfigError(`the link ${link} asks for the system's proof (${systemProof}) and gives ${system} no identity to make it with: it needs a service block`);
175
- }
176
- const out2 = {
177
- system,
178
- link,
179
- issuer,
180
- tenantId,
181
- tenant,
182
- clientId,
183
- redirectUris,
184
- requireSystemProof: systemProof === "required",
185
- systemProof,
186
- requireVerifiedEmail: signIn.require_verified_email === true,
187
- persistentSessions: signIn.persistent_sessions === true
188
- };
189
- if (e.service) {
190
- out2.serviceClientId = text(e.service.client_id) || `pact.${link}.service`;
191
- const jwks = text(e.service.jwks_url) || text(own.jwks_url);
192
- if (jwks === "") throw new CauthyConfigError(`the link ${link} gives ${system} its own identity, and ${system} names no jwks_url`);
193
- out2.jwksUrl = httpsAddress(jwks, "jwks_url").href;
194
- out2.jwksPath = new URL(out2.jwksUrl).pathname;
195
- }
196
- if (text(own.events_url) !== "") out2.eventsUrl = httpsAddress(text(own.events_url), "events_url").href;
197
- return out2;
57
+ return (await unsealIndexed(sealed, ring))?.value ?? null;
198
58
  }
199
-
200
- // src/keys.ts
201
- import { calculateJwkThumbprint, exportJWK, generateKeyPair, importJWK } from "jose";
202
- var RETIRE_AFTER_MS = 24 * 60 * 60 * 1e3;
203
- var FORGET_AFTER_MS = 7 * 24 * 60 * 60 * 1e3;
204
- var CURRENT_TTL_MS = 5 * 60 * 1e3;
205
- var JWKS_MAX_AGE_S = 300;
206
- var SIGN_AFTER_MS = (JWKS_MAX_AGE_S + 60) * 1e3;
207
- var SETTLING_TTL_MS = 5 * 1e3;
208
- var newestFirst = (keys2) => [...keys2].sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.kid.localeCompare(a.kid));
209
- function published(key) {
210
- const { kty, n, e } = key.publicJwk;
211
- return { kty, n, e, kid: key.kid, alg: "RS256", use: "sig" };
212
- }
213
- function pactKeys(store, sealKeys, now = Date.now) {
214
- let remembered;
215
- let making;
216
- async function make(after) {
217
- const { publicKey, privateKey } = await generateKeyPair("RS256", { modulusLength: 2048, extractable: true });
218
- const publicJwk = await exportJWK(publicKey);
219
- const kid = await calculateJwkThumbprint(publicJwk);
220
- const at = Math.max(now(), after ? Date.parse(after.createdAt) + 1 : 0);
221
- const key = { kid, publicJwk, sealed: await seal(await exportJWK(privateKey), (await sealKeys)[0]), createdAt: new Date(at).toISOString() };
222
- if (!after && store.addFirst) await store.addFirst(key);
223
- else await store.add(key);
224
- return key;
225
- }
226
- const age = (key) => now() - Date.parse(key.createdAt);
227
- function signer(kept) {
228
- const ripe = kept.find((k) => age(k) >= SIGN_AFTER_MS);
229
- if (!ripe) return { key: kept[kept.length - 1], until: now() + SETTLING_TTL_MS };
230
- const until = ripe === kept[0] ? now() + CURRENT_TTL_MS : Math.min(now() + CURRENT_TTL_MS, Date.parse(kept[0].createdAt) + SIGN_AFTER_MS);
231
- return { key: ripe, until };
232
- }
233
- async function open(key) {
234
- const ring = await sealKeys;
235
- for (let i = 0; i < ring.length; i++) {
236
- const jwk = await unseal(key.sealed, ring[i]);
237
- if (jwk !== null) return { jwk, fresh: i === 0 };
238
- }
239
- return null;
240
- }
241
- async function keptKeys() {
242
- const kept = newestFirst(await store.list());
243
- if (kept.length > 0) return kept;
244
- making ??= make().finally(() => {
245
- making = void 0;
246
- });
247
- await making;
248
- const again = newestFirst(await store.list());
249
- if (again.length === 0) throw new CauthyConfigError("the key store kept no key: check that it writes");
250
- return again;
59
+ async function unsealIndexed(sealed, ring) {
60
+ for (let index = 0; index < ring.length; index++) {
61
+ const value = await unseal(sealed, ring[index]);
62
+ if (value !== null) return { value, index };
251
63
  }
252
- return {
253
- async current() {
254
- if (remembered && remembered.until > now()) return remembered;
255
- const { key, until } = signer(await keptKeys());
256
- if (remembered?.kid === key.kid) {
257
- remembered.until = until;
258
- return remembered;
259
- }
260
- const opened = await open(key);
261
- if (opened === null) {
262
- throw new CauthyConfigError(`the key ${key.kid} cannot be opened with the root key (PACT_ROOT_KEY): if the root key was rotated, set the previous one as PACT_ROOT_KEY_PREVIOUS`);
263
- }
264
- remembered = { kid: key.kid, privateKey: await importJWK(opened.jwk, "RS256"), until };
265
- return remembered;
266
- },
267
- async published() {
268
- const kept = await keptKeys();
269
- const out2 = [];
270
- for (let i = 0; i < kept.length; i++) {
271
- if (i === 0 || now() - Date.parse(kept[i - 1].createdAt) < RETIRE_AFTER_MS) out2.push(published(kept[i]));
272
- else if (now() - Date.parse(kept[i - 1].createdAt) > FORGET_AFTER_MS) await store.remove(kept[i].kid);
273
- }
274
- return { keys: out2 };
275
- },
276
- async rotate() {
277
- const key = await make(newestFirst(await store.list())[0]);
278
- remembered = void 0;
279
- return key.kid;
280
- },
281
- async reseal() {
282
- let changed = 0;
283
- const ring = await sealKeys;
284
- for (const key of await store.list()) {
285
- const opened = await open(key);
286
- if (opened === null || opened.fresh) continue;
287
- await store.update({ ...key, sealed: await seal(opened.jwk, ring[0]) });
288
- changed++;
289
- }
290
- return changed;
291
- }
292
- };
293
- }
294
-
295
- // src/rootkey.ts
296
- var encoder2 = new TextEncoder();
297
- var MIN_ROOT_KEY = 32;
298
- async function derive(root, purpose) {
299
- const ikm = await crypto.subtle.importKey("raw", encoder2.encode(root), "HKDF", false, ["deriveBits"]);
300
- const bits = await crypto.subtle.deriveBits(
301
- { name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: encoder2.encode(`pact/${purpose}/v1`) },
302
- ikm,
303
- 256
304
- );
305
- return new Uint8Array(bits);
306
- }
307
- function deriveKeys(root, previous) {
308
- if (root.length < MIN_ROOT_KEY) {
309
- throw new CauthyConfigError(`rootKey (PACT_ROOT_KEY) must be at least ${MIN_ROOT_KEY} characters: 32 random bytes, e.g. openssl rand -base64 32`);
310
- }
311
- if (previous !== void 0 && previous !== "" && previous.length < MIN_ROOT_KEY) {
312
- throw new CauthyConfigError(`the previous root key (PACT_ROOT_KEY_PREVIOUS) must be at least ${MIN_ROOT_KEY} characters`);
313
- }
314
- const roots = previous && previous !== root ? [root, previous] : [root];
315
- const ring = (purpose) => Promise.all(roots.map((r) => derive(r, purpose)));
316
- return { seal: ring("seal"), cookie: ring("cookie"), flow: ring("flow") };
64
+ return null;
317
65
  }
318
66
 
319
67
  // src/deadline.ts
@@ -349,6 +97,13 @@ function anySignal(signals) {
349
97
  return controller.signal;
350
98
  }
351
99
 
100
+ // src/url.ts
101
+ function withoutTrailingSlashes(s) {
102
+ let end = s.length;
103
+ while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
104
+ return s.slice(0, end);
105
+ }
106
+
352
107
  // src/permissions.ts
353
108
  function matches(entry, resource, action) {
354
109
  const i = entry.lastIndexOf(":");
@@ -418,19 +173,19 @@ var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
418
173
  function policySource(system, read) {
419
174
  const roles = Array.isArray(read) ? read : read.roles;
420
175
  const catalog = Array.isArray(read) ? [] : read.catalog ?? [];
421
- const text2 = (v) => JSON.stringify(v);
422
- const key = (k) => IDENT.test(k) ? k : text2(k);
176
+ const text = (v) => JSON.stringify(v);
177
+ const key = (k) => IDENT.test(k) ? k : text(k);
423
178
  const one = (r, pad) => {
424
- const parts = [`role: ${text2(r.role)}`];
425
- if (r.name) parts.push(`name: ${text2(r.name)}`);
426
- if (r.description) parts.push(`description: ${text2(r.description)}`);
427
- if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(text2).join(", ")}]`);
179
+ const parts = [`role: ${text(r.role)}`];
180
+ if (r.name) parts.push(`name: ${text(r.name)}`);
181
+ if (r.description) parts.push(`description: ${text(r.description)}`);
182
+ if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(text).join(", ")}]`);
428
183
  if (r.permissions?.length) {
429
184
  const list = r.permissions.map((p) => `${pad} { ${[
430
- ...p.kind && p.kind !== "*" ? [`${key("kind")}: ${text2(p.kind)}`] : [],
431
- `resource: ${text2(p.resource)}`,
432
- `action: ${text2(p.action)}`,
433
- ...p.condition ? [`condition: ${text2(p.condition)}`] : []
185
+ ...p.kind && p.kind !== "*" ? [`${key("kind")}: ${text(p.kind)}`] : [],
186
+ `resource: ${text(p.resource)}`,
187
+ `action: ${text(p.action)}`,
188
+ ...p.condition ? [`condition: ${text(p.condition)}`] : []
434
189
  ].join(", ")} },`);
435
190
  parts.push(`permissions: [
436
191
  ${list.join("\n")}
@@ -442,14 +197,14 @@ ${pad} `)},
442
197
  ${pad} },`;
443
198
  };
444
199
  const resource = (c) => {
445
- const parts = [`resource: ${text2(c.resource)}`];
446
- if (c.description) parts.push(`description: ${text2(c.description)}`);
447
- parts.push(`actions: [${c.actions.map(text2).join(", ")}]`);
200
+ const parts = [`resource: ${text(c.resource)}`];
201
+ if (c.description) parts.push(`description: ${text(c.description)}`);
202
+ parts.push(`actions: [${c.actions.map(text).join(", ")}]`);
448
203
  const attrs = Object.entries(c.attributes ?? {});
449
- if (attrs.length) parts.push(`attributes: { ${attrs.map(([k, v]) => `${key(k)}: ${text2(v)}`).join(", ")} }`);
204
+ if (attrs.length) parts.push(`attributes: { ${attrs.map(([k, v]) => `${key(k)}: ${text(v)}`).join(", ")} }`);
450
205
  return ` { ${parts.join(", ")} },`;
451
206
  };
452
- const head = `// The catalog of roles of ${text2(system)}, as Cauthy has it.
207
+ const head = `// The catalog of roles of ${text(system)}, as Cauthy has it.
453
208
  // Written by \`pullPolicy\`: run it again after the catalog
454
209
  // changes, and do not edit this file by hand.
455
210
  `;
@@ -559,7 +314,7 @@ function organizationIdOf(o) {
559
314
  }
560
315
 
561
316
  // src/system.ts
562
- import { exportJWK as exportJWK2, importJWK as importJWK2, importPKCS8, SignJWT } from "jose";
317
+ import { exportJWK, importJWK, importPKCS8, SignJWT } from "jose";
563
318
 
564
319
  // src/theme.ts
565
320
  var MAX_RADIUS = 40;
@@ -576,7 +331,7 @@ function withoutComments(css) {
576
331
  }
577
332
  }
578
333
  function declarations(css) {
579
- const text2 = withoutComments(css);
334
+ const text = withoutComments(css);
580
335
  const out2 = [];
581
336
  const within = [];
582
337
  let chunk = "";
@@ -587,7 +342,7 @@ function declarations(css) {
587
342
  if (m) out2.push({ name: m[1], value: m[2].replace(/\s+/g, " ").replace(/\s*!important$/, ""), within: [...within] });
588
343
  chunk = "";
589
344
  };
590
- for (const ch of text2) {
345
+ for (const ch of text) {
591
346
  if (quote) {
592
347
  chunk += ch;
593
348
  if (ch === quote) quote = "";
@@ -923,6 +678,22 @@ async function pushTheme(o) {
923
678
  }
924
679
  }
925
680
 
681
+ // src/types.ts
682
+ var CauthyConfigError = class extends Error {
683
+ constructor(message) {
684
+ super(`@cauthyhq/node: ${message}`);
685
+ this.name = "CauthyConfigError";
686
+ }
687
+ };
688
+ var CauthyKeyError = class extends CauthyConfigError {
689
+ constructor(reason, message) {
690
+ super(`CAUTHY_KEY: ${message} (${reason})`);
691
+ this.reason = reason;
692
+ this.name = "CauthyKeyError";
693
+ }
694
+ reason;
695
+ };
696
+
926
697
  // src/system.ts
927
698
  var ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
928
699
  var SKEW_MS = 3e4;
@@ -962,7 +733,7 @@ function asJwk(k) {
962
733
  }
963
734
  async function loadKey(k) {
964
735
  const jwk = asJwk(k);
965
- return jwk ? importJWK2(jwk, "RS256") : importPKCS8(k.privateKey, "RS256", { extractable: true });
736
+ return jwk ? importJWK(jwk, "RS256") : importPKCS8(k.privateKey, "RS256", { extractable: true });
966
737
  }
967
738
  function createSystem(given = {}) {
968
739
  const issuer = withoutTrailingSlashes(given.issuer ?? env("CAUTHY_ISSUER") ?? "");
@@ -989,10 +760,6 @@ function createSystem(given = {}) {
989
760
  if (given.signer) return;
990
761
  if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address");
991
762
  if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
992
- if (given.keys) {
993
- if (!given.keysClientId) throw new CauthyConfigError("the contract gives the system no identity of its own: its link needs a service block");
994
- return;
995
- }
996
763
  if (key ? key.keyId === "" : clientSecret === "") {
997
764
  throw new CauthyConfigError(key ? "the key needs its id (CAUTHY_SYSTEM_KEY_ID)" : "to act on its own the application needs its secret (CAUTHY_CLIENT_SECRET) or a key (CAUTHY_SYSTEM_KEY)");
998
765
  }
@@ -1010,14 +777,14 @@ function createSystem(given = {}) {
1010
777
  let asking;
1011
778
  async function ask() {
1012
779
  configured();
1013
- const self = given.signer ? given.signer.clientId : given.keys ? given.keysClientId : key?.clientId || clientId;
780
+ const self = given.signer ? given.signer.clientId : key?.clientId || clientId;
1014
781
  const form = new URLSearchParams({ grant_type: "client_credentials", client_id: self });
1015
782
  if (given.signer) {
1016
783
  const s = await given.signer.signer;
1017
784
  form.set("client_assertion_type", ASSERTION_TYPE);
1018
785
  form.set("client_assertion", await s.assertion(self, tokenEndpoint));
1019
- } else if (given.keys || key) {
1020
- const signer = given.keys ? await given.keys.current() : { kid: key.keyId, privateKey: await signingKey() };
786
+ } else if (key) {
787
+ const signer = { kid: key.keyId, privateKey: await signingKey() };
1021
788
  const assertion = await new SignJWT({}).setProtectedHeader({ alg: "RS256", kid: signer.kid }).setIssuer(self).setSubject(self).setAudience(tokenEndpoint).setJti(crypto.randomUUID()).setIssuedAt().setExpirationTime("2m").sign(signer.privateKey);
1022
789
  form.set("client_assertion_type", ASSERTION_TYPE);
1023
790
  form.set("client_assertion", assertion);
@@ -1126,9 +893,8 @@ function createSystem(given = {}) {
1126
893
  return checkResultOf(await data("POST", `/v1/systems/${id(system)}/check`, { organization_id: q.organizationId, user_id: q.userId, ...checkWire(q) }));
1127
894
  },
1128
895
  async publicJwks() {
1129
- if (given.keys) return given.keys.published();
1130
896
  if (!key) return { keys: [] };
1131
- const { kty, n, e } = asJwk(key) ?? await exportJWK2(await signingKey());
897
+ const { kty, n, e } = asJwk(key) ?? await exportJWK(await signingKey());
1132
898
  return { keys: [{ kty, n, e, kid: key.keyId, alg: "RS256", use: "sig" }] };
1133
899
  }
1134
900
  };
@@ -1462,8 +1228,8 @@ var fail = (reason, message) => {
1462
1228
  throw new CauthyKeyError(reason, message);
1463
1229
  };
1464
1230
  var NOT_A_KEY = "not a Cauthy key (copy it again from the dashboard)";
1465
- function parseKey(text2) {
1466
- const s = text2.replace(/^[ \t\r\n]+|[ \t\r\n]+$/g, "");
1231
+ function parseKey(text) {
1232
+ const s = text.replace(/^[ \t\r\n]+|[ \t\r\n]+$/g, "");
1467
1233
  if (s === "" || s.length > MAX_LEN || !printableAscii(s)) fail("key_format", NOT_A_KEY);
1468
1234
  const head = s.split(".", 1)[0];
1469
1235
  if (head !== KEY_PREFIX) {
@@ -1646,10 +1412,10 @@ async function finish(res, route, cacheable = false) {
1646
1412
  if (!cacheable && !(headers.get("cache-control") ?? "").includes("no-store")) headers.set("cache-control", "no-store");
1647
1413
  let body = res.body;
1648
1414
  if (route !== null && SIGN_IN_ROUTES.has(route) && res.ok && (headers.get("content-type") ?? "").startsWith("application/json")) {
1649
- const text2 = await res.text();
1650
- body = text2;
1415
+ const text = await res.text();
1416
+ body = text;
1651
1417
  try {
1652
- const parsed = JSON.parse(text2);
1418
+ const parsed = JSON.parse(text);
1653
1419
  const next = nextOf(route, parsed?.status);
1654
1420
  if (parsed !== null && next !== void 0) body = JSON.stringify({ ...parsed, next });
1655
1421
  } catch {
@@ -1665,24 +1431,24 @@ import { compactVerify, createLocalJWKSet } from "jose";
1665
1431
  // src/json.ts
1666
1432
  var SPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r"]);
1667
1433
  var LIMIT = 2n ** 53n;
1668
- function parseStrict(text2, integerMembers = []) {
1434
+ function parseStrict(text, integerMembers = []) {
1669
1435
  let i = 0;
1670
1436
  let depth = 0;
1671
1437
  const fail2 = (what) => {
1672
1438
  throw new SyntaxError(`${what} at ${i}`);
1673
1439
  };
1674
1440
  const skip = () => {
1675
- while (i < text2.length && SPACE.has(text2[i])) i++;
1441
+ while (i < text.length && SPACE.has(text[i])) i++;
1676
1442
  };
1677
1443
  const value = (integer = false) => {
1678
1444
  skip();
1679
- const c = text2[i];
1445
+ const c = text[i];
1680
1446
  if (c === "{") return object();
1681
1447
  if (c === "[") return array();
1682
1448
  if (c === '"') return string();
1683
1449
  if (c === "-" || c >= "0" && c <= "9") return number(integer);
1684
1450
  for (const [word, v] of [["true", true], ["false", false], ["null", null]]) {
1685
- if (text2.startsWith(word, i)) {
1451
+ if (text.startsWith(word, i)) {
1686
1452
  i += word.length;
1687
1453
  return v;
1688
1454
  }
@@ -1692,13 +1458,13 @@ function parseStrict(text2, integerMembers = []) {
1692
1458
  const string = () => {
1693
1459
  const start = i;
1694
1460
  i++;
1695
- while (i < text2.length && text2[i] !== '"') i += text2[i] === "\\" ? 2 : 1;
1696
- if (text2[i] !== '"') fail2("unterminated string");
1461
+ while (i < text.length && text[i] !== '"') i += text[i] === "\\" ? 2 : 1;
1462
+ if (text[i] !== '"') fail2("unterminated string");
1697
1463
  i++;
1698
- return JSON.parse(text2.slice(start, i));
1464
+ return JSON.parse(text.slice(start, i));
1699
1465
  };
1700
1466
  const number = (integer) => {
1701
- const m = /^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/.exec(text2.slice(i));
1467
+ const m = /^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/.exec(text.slice(i));
1702
1468
  if (!m) return fail2("bad number");
1703
1469
  i += m[0].length;
1704
1470
  if (integer && (m[2] !== void 0 || m[3] !== void 0)) return Number.NaN;
@@ -1713,7 +1479,7 @@ function parseStrict(text2, integerMembers = []) {
1713
1479
  depth++;
1714
1480
  const out2 = [];
1715
1481
  skip();
1716
- if (text2[i] === "]") {
1482
+ if (text[i] === "]") {
1717
1483
  i++;
1718
1484
  depth--;
1719
1485
  return out2;
@@ -1721,8 +1487,8 @@ function parseStrict(text2, integerMembers = []) {
1721
1487
  for (; ; ) {
1722
1488
  out2.push(value());
1723
1489
  skip();
1724
- if (text2[i] === ",") i++;
1725
- else if (text2[i] === "]") {
1490
+ if (text[i] === ",") i++;
1491
+ else if (text[i] === "]") {
1726
1492
  i++;
1727
1493
  depth--;
1728
1494
  return out2;
@@ -1735,24 +1501,24 @@ function parseStrict(text2, integerMembers = []) {
1735
1501
  const out2 = {};
1736
1502
  const seen = /* @__PURE__ */ new Set();
1737
1503
  skip();
1738
- if (text2[i] === "}") {
1504
+ if (text[i] === "}") {
1739
1505
  i++;
1740
1506
  depth--;
1741
1507
  return out2;
1742
1508
  }
1743
1509
  for (; ; ) {
1744
1510
  skip();
1745
- if (text2[i] !== '"') fail2("expected a name");
1511
+ if (text[i] !== '"') fail2("expected a name");
1746
1512
  const name = string();
1747
1513
  if (seen.has(name)) fail2(`repeated name ${JSON.stringify(name)}`);
1748
1514
  seen.add(name);
1749
1515
  skip();
1750
- if (text2[i] !== ":") fail2("expected :");
1516
+ if (text[i] !== ":") fail2("expected :");
1751
1517
  i++;
1752
1518
  Object.defineProperty(out2, name, { value: value(depth === 1 && integerMembers.includes(name)), enumerable: true, writable: true, configurable: true });
1753
1519
  skip();
1754
- if (text2[i] === ",") i++;
1755
- else if (text2[i] === "}") {
1520
+ if (text[i] === ",") i++;
1521
+ else if (text[i] === "}") {
1756
1522
  i++;
1757
1523
  depth--;
1758
1524
  return out2;
@@ -1761,7 +1527,7 @@ function parseStrict(text2, integerMembers = []) {
1761
1527
  };
1762
1528
  const result = value();
1763
1529
  skip();
1764
- if (i < text2.length) fail2("trailing characters");
1530
+ if (i < text.length) fail2("trailing characters");
1765
1531
  return result;
1766
1532
  }
1767
1533
 
@@ -1782,6 +1548,15 @@ function deprecated(what, instead) {
1782
1548
  var enc2 = new TextEncoder();
1783
1549
  var PKCS8_ED25519 = Uint8Array.from([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32]);
1784
1550
  var ASSERTION_TYPE2 = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
1551
+ async function ringKey(secret, purpose) {
1552
+ const ikm = await crypto.subtle.importKey("raw", enc2.encode(secret), "HKDF", false, ["deriveBits"]);
1553
+ return new Uint8Array(await crypto.subtle.deriveBits(
1554
+ { name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: enc2.encode(`pact/${purpose}/v1`) },
1555
+ ikm,
1556
+ 256
1557
+ ));
1558
+ }
1559
+ var ringOf = (secrets, purpose) => Promise.all(secrets.map((s) => ringKey(s, purpose)));
1785
1560
  async function seedOf(secret) {
1786
1561
  const ikm = await crypto.subtle.importKey("raw", enc2.encode(secret), "HKDF", false, ["deriveBits"]);
1787
1562
  return new Uint8Array(await crypto.subtle.deriveBits(
@@ -1819,12 +1594,11 @@ async function signerOf(secret) {
1819
1594
  }
1820
1595
  function deriveFromKey(key, previous) {
1821
1596
  const other = previous && previous.secret !== key.secret ? previous : void 0;
1822
- const rings = deriveKeys(key.secret, other?.secret);
1597
+ const secrets = other ? [key.secret, other.secret] : [key.secret];
1823
1598
  return {
1824
- cookie: rings.cookie,
1825
- flow: rings.flow,
1826
- signer: signerOf(key.secret),
1827
- ...other ? { previousJwk: signerOf(other.secret).then((s) => ({ ...s.publicJwk, kid: s.kid })) } : {}
1599
+ cookie: ringOf(secrets, "cookie"),
1600
+ flow: ringOf(secrets, "flow"),
1601
+ signer: signerOf(key.secret)
1828
1602
  };
1829
1603
  }
1830
1604
 
@@ -1943,8 +1717,8 @@ var CauthyTokenError = class extends Error {
1943
1717
  detail;
1944
1718
  };
1945
1719
  var MAX_TOKEN_BYTES = 8192;
1946
- var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
1947
- var NIL_UUID2 = "00000000-0000-0000-0000-000000000000";
1720
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
1721
+ var NIL_UUID = "00000000-0000-0000-0000-000000000000";
1948
1722
  var SEGMENT = /^[A-Za-z0-9_-]+$/;
1949
1723
  var MAX_KEYS_BYTES = 64 * 1024;
1950
1724
  var KEYS_TIMEOUT_MS = 5e3;
@@ -2024,7 +1798,7 @@ async function readCapped(res, max) {
2024
1798
  }
2025
1799
  return out2;
2026
1800
  }
2027
- var published2 = /* @__PURE__ */ new Map();
1801
+ var published = /* @__PURE__ */ new Map();
2028
1802
  var withFetch = /* @__PURE__ */ new WeakMap();
2029
1803
  function remoteKeysFor(issuer, fetchKeys) {
2030
1804
  let byIssuer = withFetch.get(fetchKeys);
@@ -2034,10 +1808,10 @@ function remoteKeysFor(issuer, fetchKeys) {
2034
1808
  return set;
2035
1809
  }
2036
1810
  function keysOf2(issuer) {
2037
- let set = published2.get(issuer);
1811
+ let set = published.get(issuer);
2038
1812
  if (set === void 0) {
2039
1813
  set = remoteKeys(issuer);
2040
- published2.set(issuer, set);
1814
+ published.set(issuer, set);
2041
1815
  }
2042
1816
  return set;
2043
1817
  }
@@ -2079,7 +1853,7 @@ function createTokenVerifier(options = {}) {
2079
1853
  if (audience === "") throw new CauthyConfigError("audience (CAUTHY_AUDIENCE) is not set: without it a token issued for another application would be accepted");
2080
1854
  const tenant = options.tenantId ?? env2("CAUTHY_TENANT_ID") ?? "";
2081
1855
  if (tenant === "") throw new CauthyConfigError("tenantId (CAUTHY_TENANT_ID) is not set: without it a token of another workspace would be accepted");
2082
- if (!UUID2.test(tenant) || tenant === NIL_UUID2) throw new CauthyConfigError("tenantId must be the workspace's id, a lowercase UUID that is not the nil UUID");
1856
+ if (!UUID.test(tenant) || tenant === NIL_UUID) throw new CauthyConfigError("tenantId must be the workspace's id, a lowercase UUID that is not the nil UUID");
2083
1857
  const leeway = options.leeway ?? 30;
2084
1858
  if (!Number.isFinite(leeway) || leeway < 0 || leeway > 60) throw new CauthyConfigError("leeway must be 0 to 60 seconds");
2085
1859
  return async (from) => {
@@ -2128,8 +1902,8 @@ function createTokenVerifier(options = {}) {
2128
1902
  throw new CauthyTokenError("invalid_claim", name);
2129
1903
  };
2130
1904
  if (typeof claims.iss !== "string") bad("iss");
2131
- if (typeof claims.sub !== "string" || !UUID2.test(claims.sub)) bad("sub");
2132
- if (typeof claims.tid !== "string" || !UUID2.test(claims.tid)) bad("tid");
1905
+ if (typeof claims.sub !== "string" || !UUID.test(claims.sub)) bad("sub");
1906
+ if (typeof claims.tid !== "string" || !UUID.test(claims.tid)) bad("tid");
2133
1907
  const aud = claims.aud;
2134
1908
  if (!(typeof aud === "string" || Array.isArray(aud) && aud.length > 0 && aud.every((a) => typeof a === "string"))) bad("aud");
2135
1909
  for (const name of ["exp", "iat", "nbf"]) if (claims[name] !== void 0 && !Number.isInteger(claims[name])) bad(name);
@@ -2167,13 +1941,13 @@ async function verifyToken(from, options = {}) {
2167
1941
  return createTokenVerifier(options)(from);
2168
1942
  }
2169
1943
  async function createVerifierFromEnv(options = {}) {
2170
- const text2 = options.key ?? env2("CAUTHY_KEY");
2171
- if (text2 === void 0 || text2 === "") return createTokenVerifier(options);
1944
+ const text = options.key ?? env2("CAUTHY_KEY");
1945
+ if (text === void 0 || text === "") return createTokenVerifier(options);
2172
1946
  refuseBesideKey(typeof process !== "undefined" ? process.env : void 0);
2173
1947
  for (const name of ["issuer", "audience", "tenantId"]) {
2174
1948
  if (options[name] !== void 0) throw new CauthyConfigError(`with CAUTHY_KEY, ${name} comes from CAUTHY_KEY: do not pass it`);
2175
1949
  }
2176
- const key = parseKey(text2);
1950
+ const key = parseKey(text);
2177
1951
  const system = createSystem({ issuer: key.issuer, clientId: key.clientId, signer: { clientId: key.clientId, signer: deriveFromKey(key).signer }, fetch: options.fetch });
2178
1952
  const id = await createIdentity({ issuer: key.issuer, token: () => system.token(), fetch: deadline(options.fetch, KEYS_TIMEOUT_MS) }).ready();
2179
1953
  const { key: _, ...rest } = options;
@@ -2270,9 +2044,18 @@ function redirect(location, cookies = []) {
2270
2044
  function conditionalOf(c) {
2271
2045
  return { resource: String(c?.resource ?? ""), action: String(c?.action ?? ""), effect: c?.effect === "deny" ? "deny" : "allow" };
2272
2046
  }
2047
+ function refuseContracts(given) {
2048
+ const options = given;
2049
+ const set = [
2050
+ ...["contract", "keys", "rootKey", "rootKeyPrevious"].filter((name) => options[name] !== void 0).map((name) => `the ${name} option`),
2051
+ ...["PACT_ROOT_KEY", "PACT_ROOT_KEY_PREVIOUS"].filter((name) => (env3(name) ?? "") !== "")
2052
+ ];
2053
+ if (set.length === 0) return;
2054
+ throw new CauthyConfigError(`contracts are retired: run on CAUTHY_KEY alone, and remove ${set.join(", ")}. An application without a key makes it on its card in Cauthy's dashboard; the same key keeps its people signed in. Protect sign-in with the "Protect sign-in" switches on that card. Sessions sealed with PACT_ROOT_KEY are no longer read.`);
2055
+ }
2273
2056
  function byKey(given) {
2274
- const text2 = given.key ?? env3("CAUTHY_KEY");
2275
- if (text2 === void 0 || text2 === "") return void 0;
2057
+ const text = given.key ?? env3("CAUTHY_KEY");
2058
+ if (text === void 0 || text === "") return void 0;
2276
2059
  refuseBesideKey(typeof process !== "undefined" ? process.env : void 0);
2277
2060
  for (const [name, value] of [
2278
2061
  ["issuer", given.issuer],
@@ -2283,12 +2066,11 @@ function byKey(given) {
2283
2066
  ["tenant", given.tenant],
2284
2067
  ["audience", given.audience],
2285
2068
  ["tenantId", given.tenantId],
2286
- ["systemKey", given.systemKey],
2287
- ["rootKey", given.rootKey]
2069
+ ["systemKey", given.systemKey]
2288
2070
  ]) {
2289
2071
  if (value !== void 0) throw new CauthyConfigError(`with CAUTHY_KEY, ${name} comes from CAUTHY_KEY: do not pass it`);
2290
2072
  }
2291
- const key = parseKey(text2);
2073
+ const key = parseKey(text);
2292
2074
  const prevText = given.keyPrevious ?? env3("CAUTHY_KEY_PREVIOUS");
2293
2075
  let previous;
2294
2076
  if (prevText) {
@@ -2303,13 +2085,8 @@ function byKey(given) {
2303
2085
  throw new CauthyKeyError("key_previous_mismatch", "CAUTHY_KEY_PREVIOUS names another Cauthy or client than CAUTHY_KEY");
2304
2086
  }
2305
2087
  const legacySecret = env3("CAUTHY_SECRET") || void 0;
2306
- const legacyRoot = env3("PACT_ROOT_KEY") || void 0;
2307
2088
  if (legacySecret) deprecated("CAUTHY_SECRET beside CAUTHY_KEY", "It is read only for sessions made before the move: remove it after the rotation window.");
2308
- if (legacyRoot) deprecated("PACT_ROOT_KEY beside CAUTHY_KEY", "It is read only for sessions made before the move: remove it, the keys option and its table after the rotation window.");
2309
- if (given.keys && !(legacyRoot && given.contract)) {
2310
- throw new CauthyConfigError("with CAUTHY_KEY the application keeps no key store: remove keys (it is read only beside PACT_ROOT_KEY and a contract, while moving)");
2311
- }
2312
- return { key, previous, legacySecret, legacyRoot };
2089
+ return { key, previous, legacySecret };
2313
2090
  }
2314
2091
  var UnknownOriginError = class extends Error {
2315
2092
  constructor(origin, registered) {
@@ -2320,72 +2097,41 @@ var UnknownOriginError = class extends Error {
2320
2097
  origin;
2321
2098
  registered;
2322
2099
  };
2323
- function contractOnKey(given, keyed) {
2324
- const contract = readContract(given.contract);
2325
- if (contract.issuer !== keyed.key.issuer) {
2326
- throw new CauthyKeyError("key_contract_issuer", `the key names ${keyed.key.issuer}, and the contract's Cauthy is ${contract.issuer}: make the key in that Cauthy's dashboard`);
2327
- }
2328
- return contract;
2329
- }
2330
- function byContract(given) {
2331
- failClosed(typeof process !== "undefined" ? process.env : void 0);
2332
- for (const [name, value] of [
2333
- ["clientId", given.clientId],
2334
- ["clientSecret", given.clientSecret],
2335
- ["secret", given.secret],
2336
- ["system", given.system],
2337
- ["tenant", given.tenant],
2338
- ["systemKey", given.systemKey]
2339
- ]) {
2340
- if (value !== void 0) throw new CauthyConfigError(`with a contract, ${name} comes from the contract: do not pass it`);
2341
- }
2342
- const contract = readContract(given.contract);
2343
- const root = given.rootKey ?? env3("PACT_ROOT_KEY") ?? "";
2344
- if (root === "") throw new CauthyConfigError("rootKey (PACT_ROOT_KEY) is required with a contract: 32 random bytes, e.g. openssl rand -base64 32");
2345
- if (contract.serviceClientId && !given.keys) {
2346
- throw new CauthyConfigError(`the link ${contract.link} gives ${contract.system} its own identity: pass keys, where its key is kept (@cauthyhq/node/keys)`);
2347
- }
2348
- deprecated("PACT_ROOT_KEY", "Make the application's CAUTHY_KEY in Cauthy's dashboard (the application's card), then remove PACT_ROOT_KEY, the keys option and its table.");
2349
- return { contract, root, previous: given.rootKeyPrevious ?? env3("PACT_ROOT_KEY_PREVIOUS") };
2350
- }
2351
2100
  var toldOfHeader = false;
2101
+ var toldOfUnopened = false;
2102
+ function toldUnopened() {
2103
+ if (toldOfUnopened) return;
2104
+ toldOfUnopened = true;
2105
+ console.warn("@cauthyhq/node: a session cookie arrived that no key of this application opens; it was cleared and the person is signed out. Moving to CAUTHY_KEY, keep the secret that sealed the old sessions (CAUTHY_SECRET) beside it for the rotation window; rotating the key, set CAUTHY_KEY_PREVIOUS.");
2106
+ }
2352
2107
  function createCauthy(given = {}) {
2108
+ refuseContracts(given);
2353
2109
  const keyed = byKey(given);
2354
- const pacted = given.contract && !keyed ? byContract(given) : void 0;
2355
- const contract = keyed && given.contract ? contractOnKey(given, keyed) : pacted?.contract;
2356
- const issuer = withoutTrailingSlashes(keyed?.key.issuer ?? given.issuer ?? contract?.issuer ?? env3("CAUTHY_ISSUER") ?? "");
2357
- const clientId = contract?.clientId ?? keyed?.key.clientId ?? given.clientId ?? env3("CAUTHY_CLIENT_ID") ?? "";
2358
- const clientSecret = contract || keyed ? void 0 : given.clientSecret ?? env3("CAUTHY_CLIENT_SECRET");
2359
- const secret = contract || keyed ? "" : given.secret ?? env3("CAUTHY_SECRET") ?? "";
2360
- const askSelf = keyed !== void 0 && contract === void 0;
2361
- const system = askSelf ? "" : contract?.system ?? given.system ?? env3("CAUTHY_SYSTEM") ?? "";
2362
- const tenant = askSelf ? "" : contract?.tenant ?? given.tenant ?? env3("CAUTHY_TENANT") ?? "";
2110
+ const issuer = withoutTrailingSlashes(keyed?.key.issuer ?? given.issuer ?? env3("CAUTHY_ISSUER") ?? "");
2111
+ const clientId = keyed?.key.clientId ?? given.clientId ?? env3("CAUTHY_CLIENT_ID") ?? "";
2112
+ const clientSecret = keyed ? void 0 : given.clientSecret ?? env3("CAUTHY_CLIENT_SECRET");
2113
+ const secret = keyed ? "" : given.secret ?? env3("CAUTHY_SECRET") ?? "";
2114
+ const system = keyed ? "" : given.system ?? env3("CAUTHY_SYSTEM") ?? "";
2115
+ const tenant = keyed ? "" : given.tenant ?? env3("CAUTHY_TENANT") ?? "";
2363
2116
  if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address, e.g. https://id.example.com");
2364
2117
  if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
2365
- if (!contract && !keyed && secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
2118
+ if (!keyed && secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
2366
2119
  const fromKey = keyed ? deriveFromKey(keyed.key, keyed.previous) : void 0;
2367
2120
  let signingKid;
2368
2121
  fromKey?.signer.then((s) => {
2369
2122
  signingKid = s.kid;
2370
2123
  }, () => void 0);
2371
- const derived = pacted ? deriveKeys(pacted.root, pacted.previous) : void 0;
2372
- const single = derived || fromKey ? void 0 : keyOf(secret).then((k) => [k]);
2373
- const legacyRings = keyed?.legacyRoot ? deriveKeys(keyed.legacyRoot) : void 0;
2374
2124
  const legacySingle = keyed?.legacySecret ? keyOf(keyed.legacySecret) : void 0;
2375
- const withLegacy = async (own, purpose) => [
2376
- ...await own,
2377
- ...legacyRings ? [(await legacyRings[purpose])[0]] : [],
2378
- ...legacySingle ? [await legacySingle] : []
2379
- ];
2380
- const cookieKeys = fromKey ? withLegacy(fromKey.cookie, "cookie") : derived?.cookie ?? single;
2381
- const flowKeys = fromKey ? withLegacy(fromKey.flow, "flow") : derived?.flow ?? single;
2382
- if (!keyed && !pacted) {
2125
+ const withLegacy = async (own) => [...await own, ...legacySingle ? [await legacySingle] : []];
2126
+ const single = fromKey ? Promise.resolve([]) : keyOf(secret).then((k) => [k]);
2127
+ const cookieKeys = fromKey ? withLegacy(fromKey.cookie) : single;
2128
+ const flowKeys = fromKey ? withLegacy(fromKey.flow) : single;
2129
+ if (!keyed) {
2383
2130
  deprecated(
2384
2131
  "Configuring with CAUTHY_ISSUER, CAUTHY_CLIENT_ID, CAUTHY_CLIENT_SECRET, CAUTHY_SECRET and the other CAUTHY_* variables (or their options)",
2385
2132
  "Connect the application in Cauthy's dashboard and use the one variable CAUTHY_KEY."
2386
2133
  );
2387
2134
  }
2388
- const ownKeys = derived && given.keys ? pactKeys(given.keys, derived.seal) : legacyRings && contract && given.keys ? pactKeys(given.keys, legacyRings.seal) : void 0;
2389
2135
  const baseURL = given.baseURL ?? (keyed ? void 0 : env3("CAUTHY_BASE_URL"));
2390
2136
  if (baseURL !== void 0 && baseURL !== "" && !/^https?:\/\/[^/]+\/*$/.test(baseURL)) {
2391
2137
  throw new CauthyConfigError("baseURL (CAUTHY_BASE_URL) must be the application's public origin, e.g. https://app.example.com");
@@ -2422,21 +2168,10 @@ function createCauthy(given = {}) {
2422
2168
  acceptInviteUrl,
2423
2169
  fetch: given.fetch,
2424
2170
  timeoutMs: systemTimeout,
2425
- ...ownKeys ? { keys: ownKeys, keysClientId: contract?.serviceClientId } : {},
2426
- // With a contract the key proves the link's service client; without, the key's own client.
2427
- ...fromKey ? {
2428
- signer: { clientId: contract ? contract.serviceClientId ?? keyed.key.clientId : clientId, signer: fromKey.signer },
2429
- currentSystem: () => identity?.current()?.system ?? ""
2430
- } : {}
2171
+ // On CAUTHY_KEY the key proves the key's own client.
2172
+ ...fromKey ? { signer: { clientId, signer: fromKey.signer }, currentSystem: () => identity?.current()?.system ?? "" } : {}
2431
2173
  });
2432
- const selfOptions = () => ({
2433
- issuer,
2434
- token: () => ownSystem.token(),
2435
- fetch: deadline(given.fetch, requestTimeout),
2436
- kid: () => signingKid,
2437
- log: (m) => console.error(`@cauthyhq/node: ${m}`)
2438
- });
2439
- const identity = askSelf ? createIdentity({
2174
+ const identity = keyed ? createIdentity({
2440
2175
  issuer,
2441
2176
  token: () => ownSystem.token(),
2442
2177
  fetch: deadline(given.fetch, requestTimeout),
@@ -2446,6 +2181,8 @@ function createCauthy(given = {}) {
2446
2181
  identity?.ready().catch(() => {
2447
2182
  });
2448
2183
  const tenantNow = () => identity?.current()?.tenant.slug ?? tenant;
2184
+ const signInClientNow = () => identity?.current()?.sign_in_client_id || clientId;
2185
+ const signInClient = async () => identity ? (await identity.ready()).sign_in_client_id || clientId : clientId;
2449
2186
  const systemNow = () => identity?.current()?.system ?? system;
2450
2187
  const requestOrigin = (request) => {
2451
2188
  const url = new URL(request.url);
@@ -2470,7 +2207,7 @@ function createCauthy(given = {}) {
2470
2207
  };
2471
2208
  const attrs = (request, maxAge) => ({ secure: safeOrigin(request).startsWith("https://"), maxAge });
2472
2209
  async function clientAuth(form) {
2473
- if (fromKey && !contract) {
2210
+ if (fromKey && form.get("client_id") === clientId) {
2474
2211
  const s = await fromKey.signer;
2475
2212
  form.set("client_assertion_type", ASSERTION_TYPE2);
2476
2213
  form.set("client_assertion", await s.assertion(clientId, `${issuer}/v1/oauth/token`));
@@ -2480,8 +2217,8 @@ function createCauthy(given = {}) {
2480
2217
  return form;
2481
2218
  }
2482
2219
  async function token(form, proof = {}) {
2483
- const body2 = await clientAuth(new URLSearchParams({ client_id: clientId, ...form }));
2484
2220
  try {
2221
+ const body2 = await clientAuth(new URLSearchParams({ client_id: await signInClient(), ...form }));
2485
2222
  const res = await doFetch(`${issuer}/v1/oauth/token`, {
2486
2223
  method: "POST",
2487
2224
  headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json", ...proof },
@@ -2519,18 +2256,16 @@ function createCauthy(given = {}) {
2519
2256
  }
2520
2257
  const kindOf = (body2, was) => (typeof body2?.persistent === "boolean" ? body2.persistent : was?.p === 1) ? { p: 1 } : {};
2521
2258
  async function revoke(refreshToken) {
2522
- const form = await clientAuth(new URLSearchParams({ client_id: clientId, token: refreshToken }));
2523
- await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form }).catch(() => void 0);
2259
+ try {
2260
+ const form = await clientAuth(new URLSearchParams({ client_id: await signInClient(), token: refreshToken }));
2261
+ await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form });
2262
+ } catch {
2263
+ }
2524
2264
  }
2525
- const unverified = (u) => contract?.requireVerifiedEmail === true && !u.emailVerified;
2526
2265
  async function stored(body2, was) {
2527
2266
  if (typeof body2?.access_token !== "string" || typeof body2?.refresh_token !== "string") return null;
2528
2267
  const who = await userinfo(body2.access_token);
2529
2268
  if (who === null || who.u.id === "") return null;
2530
- if (unverified(who.u)) {
2531
- await revoke(body2.refresh_token);
2532
- throw new CauthyApiError(403, "email_not_confirmed", "Confirm your email address first.");
2533
- }
2534
2269
  const lifetime = typeof body2.expires_in === "number" && body2.expires_in > 0 ? body2.expires_in : 300;
2535
2270
  return { at: body2.access_token, rt: body2.refresh_token, exp: Math.floor(Date.now() / 1e3) + lifetime, ...who, ...kindOf(body2, was) };
2536
2271
  }
@@ -2540,10 +2275,11 @@ function createCauthy(given = {}) {
2540
2275
  async function read(request) {
2541
2276
  const had = readCookies(request.headers.get("cookie"));
2542
2277
  const raw = readParts(had, cookieName);
2543
- if (raw === null) return { had, value: null, present: false };
2544
- const value = await unsealAny(raw, await cookieKeys);
2278
+ if (raw === null) return { had, value: null, present: false, stale: false };
2279
+ const opened = await unsealIndexed(raw, await cookieKeys);
2280
+ const value = opened?.value ?? null;
2545
2281
  const sound = value !== null && typeof value.at === "string" && typeof value.rt === "string" && typeof value.exp === "number";
2546
- return { had, value: sound ? value : null, present: true };
2282
+ return { had, value: sound ? value : null, present: true, stale: sound && opened.index > 0 };
2547
2283
  }
2548
2284
  async function write(request, s, had) {
2549
2285
  return writeParts(cookieName, await seal(s, (await cookieKeys)[0]), attrs(request, s.p === 1 ? PERSISTENT_SECONDS : void 0), had);
@@ -2557,13 +2293,7 @@ function createCauthy(given = {}) {
2557
2293
  running = (async () => {
2558
2294
  const res = await token({ grant_type: "refresh_token", refresh_token: rt });
2559
2295
  if (!res.ok) return res.ended ? "ended" : "unreachable";
2560
- let next;
2561
- try {
2562
- next = await stored(res.body, was);
2563
- } catch (err) {
2564
- if (err instanceof CauthyApiError && err.code === "email_not_confirmed") return "ended";
2565
- throw err;
2566
- }
2296
+ const next = await stored(res.body, was);
2567
2297
  if (next !== null) return next;
2568
2298
  if (typeof res.body?.access_token !== "string" || typeof res.body?.refresh_token !== "string") return "unreachable";
2569
2299
  const lifetime = typeof res.body.expires_in === "number" && res.body.expires_in > 0 ? res.body.expires_in : 300;
@@ -2580,12 +2310,12 @@ function createCauthy(given = {}) {
2580
2310
  return running;
2581
2311
  }
2582
2312
  async function resolve2(request) {
2583
- const { had, value, present } = await read(request);
2313
+ const { had, value, present, stale } = await read(request);
2584
2314
  if (value === null) {
2315
+ if (present) toldUnopened();
2585
2316
  return { session: null, status: "unauthenticated", cookies: present ? clearParts(cookieName, attrs(request, 0), had) : [] };
2586
2317
  }
2587
- if (unverified(value.u)) return { session: null, status: "unauthenticated", cookies: clearParts(cookieName, attrs(request, 0), had) };
2588
- if (fresh(value)) return { session: toSession(value), status: "authenticated", cookies: [] };
2318
+ if (fresh(value)) return { session: toSession(value), status: "authenticated", cookies: stale ? await write(request, value, had) : [] };
2589
2319
  const renewed = await renew(value);
2590
2320
  if (renewed === "ended") {
2591
2321
  return { session: null, status: "unauthenticated", cookies: clearParts(cookieName, attrs(request, 0), had) };
@@ -2596,7 +2326,7 @@ function createCauthy(given = {}) {
2596
2326
  async function getSession(request, responseHeaders) {
2597
2327
  if (responseHeaders === void 0) {
2598
2328
  const { value } = await read(request);
2599
- return value !== null && fresh(value) && !unverified(value.u) ? toSession(value) : null;
2329
+ return value !== null && fresh(value) ? toSession(value) : null;
2600
2330
  }
2601
2331
  const r = await resolve2(request);
2602
2332
  for (const c of r.cookies) responseHeaders.append("set-cookie", c);
@@ -2604,7 +2334,7 @@ function createCauthy(given = {}) {
2604
2334
  }
2605
2335
  async function status(request) {
2606
2336
  const { value } = await read(request);
2607
- if (value === null || unverified(value.u)) return "unauthenticated";
2337
+ if (value === null) return "unauthenticated";
2608
2338
  return fresh(value) ? "authenticated" : "expired";
2609
2339
  }
2610
2340
  const orgCache = /* @__PURE__ */ new Map();
@@ -2691,7 +2421,7 @@ function createCauthy(given = {}) {
2691
2421
  const flow = { state: random(), verifier: random(48), returnTo: localPath(url.searchParams.get("return_to"), afterSignIn) };
2692
2422
  const challenge = b64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(flow.verifier))));
2693
2423
  const q = new URLSearchParams({
2694
- client_id: clientId,
2424
+ client_id: await signInClient(),
2695
2425
  redirect_uri: `${originOf(request)}${basePath}/callback`,
2696
2426
  response_type: "code",
2697
2427
  state: flow.state,
@@ -2753,28 +2483,17 @@ function createCauthy(given = {}) {
2753
2483
  );
2754
2484
  };
2755
2485
  if (!res.ok && res.code === "email_not_confirmed") return unconfirmed();
2756
- let s;
2757
- try {
2758
- s = res.ok ? await stored(res.body) : null;
2759
- } catch (err) {
2760
- if (!(err instanceof CauthyApiError && err.code === "email_not_confirmed")) throw err;
2761
- return unconfirmed();
2762
- }
2486
+ const s = res.ok ? await stored(res.body) : null;
2763
2487
  if (s === null) return failed(request, "exchange_failed");
2764
2488
  return redirect(flow.returnTo, [...await write(request, s, had), writeCookie(flowCookie, "", attrs(request, 0)), writeCookie(againCookie, "", attrs(request, 0))]);
2765
2489
  }
2766
2490
  async function logout(request) {
2767
2491
  const { had, value } = await read(request);
2768
2492
  if (value !== null) {
2769
- const body2 = await clientAuth(new URLSearchParams({ client_id: clientId, token: value.rt }));
2770
- await doFetch(`${issuer}/v1/oauth/revoke`, {
2771
- method: "POST",
2772
- headers: { "content-type": "application/x-www-form-urlencoded" },
2773
- body: body2
2774
- }).catch(() => void 0);
2493
+ await revoke(value.rt);
2775
2494
  orgCache.delete(value.at);
2776
2495
  }
2777
- const q = new URLSearchParams({ client_id: clientId, post_logout_redirect_uri: `${originOf(request)}${afterSignOut}` });
2496
+ const q = new URLSearchParams({ client_id: signInClientNow(), post_logout_redirect_uri: `${originOf(request)}${afterSignOut}` });
2778
2497
  return redirect(`${issuer}/v1/oauth/logout?${q}`, clearParts(cookieName, attrs(request, 0), had));
2779
2498
  }
2780
2499
  const STEP_SECONDS = 600;
@@ -2792,7 +2511,7 @@ function createCauthy(given = {}) {
2792
2511
  createdAt: String(d?.created_at ?? ""),
2793
2512
  lastUsedAt: d?.last_used_at ? String(d.last_used_at) : null
2794
2513
  });
2795
- const UUID3 = /^[0-9a-f-]{36}$/i;
2514
+ const UUID2 = /^[0-9a-f-]{36}$/i;
2796
2515
  const seatOf = (d) => ({
2797
2516
  id: String(d?.id ?? ""),
2798
2517
  system: String(d?.system ?? ""),
@@ -2897,10 +2616,10 @@ function createCauthy(given = {}) {
2897
2616
  ...page ? { "x-cauthy-link-to": `${originOf(request)}${page}` } : { "x-cauthy-links": "hosted" },
2898
2617
  // Whose request it is: what Cauthy mails goes in this application's
2899
2618
  // name, under its logo.
2900
- ...clientId ? { "x-cauthy-client-id": clientId } : {},
2619
+ ...signInClientNow() ? { "x-cauthy-client-id": signInClientNow() } : {},
2901
2620
  ...await passedOn(request),
2902
2621
  ...bearer2 ? { authorization: `Bearer ${bearer2}` } : {},
2903
- // The person asked to stay signed in: Cauthy keeps the session long if the client allows it.
2622
+ // The person asked to stay signed in: Cauthy keeps the session long if the workspace allows it.
2904
2623
  ...remember ? { "x-cauthy-remember": "1" } : {},
2905
2624
  // The answer to the workspace's Turnstile challenge, from the
2906
2625
  // person's browser: Cauthy checks it at the doors that ask for it.
@@ -3041,7 +2760,7 @@ function createCauthy(given = {}) {
3041
2760
  }
3042
2761
  if (route === "/look" && !post) {
3043
2762
  if (lookCache === null || lookCache.until < Date.now()) {
3044
- const res = await ask(request, "GET", `/v1/auth/look/${encodeURIComponent(clientId)}`);
2763
+ const res = await ask(request, "GET", `/v1/auth/look/${encodeURIComponent(signInClientNow())}`);
3045
2764
  lookCache = { until: Date.now() + LOOK_TTL_MS, look: res.error ? {} : res.data ?? {} };
3046
2765
  }
3047
2766
  return json(lookCache.look);
@@ -3066,7 +2785,7 @@ function createCauthy(given = {}) {
3066
2785
  signUp: settings.data?.allow_signup !== false && settings.data?.signup?.mode !== "invite_only",
3067
2786
  // The application's system makes an organization per sign-up: the form asks the company's name.
3068
2787
  ...settings.data?.signup?.organization?.ask === true ? { organization: { ask: true, required: settings.data.signup.organization.required === true } } : {},
3069
- // Whether the person may ask to stay signed in: the client's choice in Cauthy.
2788
+ // Whether the person may ask to stay signed in: the workspace's setting in Cauthy (persistent_sessions).
3070
2789
  ...typeof settings.data?.persistent_sessions === "boolean" ? { persistentSessions: settings.data.persistent_sessions } : {},
3071
2790
  // A sign-in link needs a page of the application to open.
3072
2791
  magicLink: settings.data?.allow_magic_link === true && pages.signIn !== void 0,
@@ -3230,7 +2949,7 @@ function createCauthy(given = {}) {
3230
2949
  let path = "/v1/auth/me/organizations";
3231
2950
  if (route !== "/me/organizations") {
3232
2951
  const id = url.searchParams.get("id") ?? "";
3233
- if (!UUID3.test(id)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
2952
+ if (!UUID2.test(id)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
3234
2953
  path += `/${id}/${route.endsWith("/roles") ? "roles" : "members"}`;
3235
2954
  } else if (url.searchParams.get("system")) {
3236
2955
  path += `?system=${encodeURIComponent(url.searchParams.get("system"))}`;
@@ -3291,8 +3010,7 @@ function createCauthy(given = {}) {
3291
3010
  if (route === "/sign-out") {
3292
3011
  const { had, value } = await read(request);
3293
3012
  if (value !== null) {
3294
- const form = await clientAuth(new URLSearchParams({ client_id: clientId, token: value.rt }));
3295
- await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form }).catch(() => void 0);
3013
+ await revoke(value.rt);
3296
3014
  orgCache.delete(value.at);
3297
3015
  }
3298
3016
  const h = new Headers();
@@ -3519,7 +3237,7 @@ function createCauthy(given = {}) {
3519
3237
  }
3520
3238
  if (route === "/me/organizations/rename" || route === "/me/organizations/archive" || route === "/me/organizations/delete") {
3521
3239
  const org = typeof b.organizationId === "string" ? b.organizationId : "";
3522
- if (!UUID3.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
3240
+ if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
3523
3241
  const path = `/v1/auth/me/organizations/${org}`;
3524
3242
  if (route === "/me/organizations/rename") {
3525
3243
  const bad = checkOrganizationName(b.name);
@@ -3536,7 +3254,7 @@ function createCauthy(given = {}) {
3536
3254
  }
3537
3255
  if (route.startsWith("/me/organizations/members/")) {
3538
3256
  const org = typeof b.organizationId === "string" ? b.organizationId : "";
3539
- if (!UUID3.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
3257
+ if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
3540
3258
  const base = `/v1/auth/me/organizations/${org}/members`;
3541
3259
  if (route === "/me/organizations/members/add") {
3542
3260
  const bad = checkEmail(b.email);
@@ -3547,7 +3265,7 @@ function createCauthy(given = {}) {
3547
3265
  email: String(b.email).trim(),
3548
3266
  role,
3549
3267
  name: typeof b.name === "string" ? b.name.trim() : "",
3550
- client_id: clientId,
3268
+ client_id: signInClientNow(),
3551
3269
  only_new: true,
3552
3270
  ...typeof b.locale === "string" && b.locale ? { locale: b.locale } : {}
3553
3271
  }, pages.acceptInvite, mine.token);
@@ -3556,7 +3274,7 @@ function createCauthy(given = {}) {
3556
3274
  return json({ status: "added", member: orgMemberOf(res.data) }, 200, mine.headers);
3557
3275
  }
3558
3276
  const user = typeof b.userId === "string" ? b.userId : "";
3559
- if (!UUID3.test(user)) return problem(400, "invalid_member", "Say which member.", void 0, mine.headers);
3277
+ if (!UUID2.test(user)) return problem(400, "invalid_member", "Say which member.", void 0, mine.headers);
3560
3278
  if (route === "/me/organizations/members/role") {
3561
3279
  const role = typeof b.role === "string" ? b.role : "";
3562
3280
  if (role === "") return problem(400, "invalid_role", "Choose a role.", "role", mine.headers);
@@ -3571,7 +3289,7 @@ function createCauthy(given = {}) {
3571
3289
  return json({ status: "removed", userId: user }, 200, mine.headers);
3572
3290
  }
3573
3291
  if (route === "/me/organizations/members/invite") {
3574
- const res = await ask(request, "POST", `${base}/${user}/invite`, { client_id: clientId, ...typeof b.locale === "string" && b.locale ? { locale: b.locale } : {} }, pages.acceptInvite, mine.token);
3292
+ const res = await ask(request, "POST", `${base}/${user}/invite`, { client_id: signInClientNow(), ...typeof b.locale === "string" && b.locale ? { locale: b.locale } : {} }, pages.acceptInvite, mine.token);
3575
3293
  if (res.error) return refused(res);
3576
3294
  return json({ status: "sent", userId: user }, 200, mine.headers);
3577
3295
  }
@@ -3689,18 +3407,15 @@ function createCauthy(given = {}) {
3689
3407
  const url = new URL(request.url);
3690
3408
  const inside = url.pathname === basePath || url.pathname.startsWith(`${basePath}/`);
3691
3409
  const route = inside ? withoutTrailingSlashes(url.pathname.slice(basePath.length)) || "/" : null;
3692
- const published3 = pact !== void 0 && url.pathname === contract?.jwksPath && (request.method === "GET" || request.method === "HEAD");
3693
3410
  if (identity && route !== null) {
3694
3411
  const unready = await identityGate();
3695
3412
  if (unready) return finish(unready, route);
3696
3413
  }
3697
3414
  try {
3698
- return finish(await serve(request), route, published3);
3415
+ return finish(await serve(request), route);
3699
3416
  } catch (err) {
3700
3417
  if (err instanceof UnknownOriginError) return finish(problem(400, "unknown_origin", err.message), route);
3701
- if (!(err instanceof CauthyApiError && err.code === "email_not_confirmed")) throw err;
3702
- const wantsPage = request.method === "GET" && (request.headers.get("sec-fetch-mode") ?? "navigate") === "navigate";
3703
- return finish(wantsPage ? new Response("Sign-in refused (email_not_confirmed). Confirm your email address first.", { status: 403, headers: { "content-type": "text/plain; charset=utf-8" } }) : problem(403, "email_not_confirmed", "Confirm your email address first."), route);
3418
+ throw err;
3704
3419
  }
3705
3420
  }
3706
3421
  async function identityGate() {
@@ -3724,7 +3439,6 @@ function createCauthy(given = {}) {
3724
3439
  }
3725
3440
  async function serve(request) {
3726
3441
  const url = new URL(request.url);
3727
- if (pact && url.pathname === contract?.jwksPath && (request.method === "GET" || request.method === "HEAD")) return pact.jwks();
3728
3442
  if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) return new Response("Not found", { status: 404 });
3729
3443
  const route = withoutTrailingSlashes(url.pathname.slice(basePath.length)) || "/";
3730
3444
  const get = request.method === "GET" || request.method === "HEAD";
@@ -3748,7 +3462,7 @@ function createCauthy(given = {}) {
3748
3462
  return redirect(back, r.cookies);
3749
3463
  }
3750
3464
  if (route === "/account" && get) {
3751
- const q = new URLSearchParams({ client_id: clientId });
3465
+ const q = new URLSearchParams({ client_id: signInClientNow() });
3752
3466
  const back = localPath(url.searchParams.get("return_to"), "");
3753
3467
  if (back !== "") q.set("return_to", `${originOf(request)}${back}`);
3754
3468
  const locale = url.searchParams.get("locale");
@@ -3767,7 +3481,7 @@ function createCauthy(given = {}) {
3767
3481
  const asker = await signedInAs(request);
3768
3482
  if ("response" in asker) return asker.response;
3769
3483
  const org = typeof b.organizationId === "string" ? b.organizationId : "";
3770
- if (!UUID3.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, asker.headers);
3484
+ if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, asker.headers);
3771
3485
  const named = (v) => typeof v === "string" && v !== "" && v.length <= 200;
3772
3486
  if (!named(b.resource) || !named(b.action)) return problem(400, "bad_request", "Say which resource and action.", void 0, asker.headers);
3773
3487
  const attributes2 = b.attributes;
@@ -3790,8 +3504,7 @@ function createCauthy(given = {}) {
3790
3504
  }
3791
3505
  }
3792
3506
  async function protect(request) {
3793
- const read1 = await read(request);
3794
- const value = read1.value !== null && unverified(read1.value.u) ? null : read1.value;
3507
+ const { value } = await read(request);
3795
3508
  if (value !== null && fresh(value)) return { session: toSession(value) };
3796
3509
  const url = new URL(request.url);
3797
3510
  const state = value === null ? "none" : "expired";
@@ -3804,85 +3517,36 @@ function createCauthy(given = {}) {
3804
3517
  return { response: redirect(to) };
3805
3518
  }
3806
3519
  const replay = given.webhookReplay ?? memoryReplayStore();
3807
- let lateSelf;
3808
3520
  const tokenVerifier = (o = {}) => {
3809
- if (keyed) {
3810
- if (contract && o.clientId !== void 0 && o.clientId !== contract.clientId) {
3811
- throw new CauthyConfigError(`clientId is the contract's (${contract.clientId}): remove it from the call`);
3812
- }
3521
+ if (identity) {
3813
3522
  let built;
3814
3523
  return async (from) => {
3815
- const id = await (identity ?? (lateSelf ??= createIdentity(selfOptions()))).ready();
3524
+ const id = await identity.ready();
3816
3525
  if (built?.from !== id) {
3817
- built = { from: id, verify: createTokenVerifier({
3818
- issuer,
3819
- tenantId: contract?.tenantId ?? id.tenant.id,
3820
- audience: id.audience,
3821
- fetch: given.fetch,
3822
- ...contract ? { clientId: contract.clientId } : {},
3823
- ...o
3824
- }) };
3526
+ built = { from: id, verify: createTokenVerifier({ issuer, tenantId: id.tenant.id, audience: id.audience, fetch: given.fetch, ...o }) };
3825
3527
  }
3826
3528
  return built.verify(from);
3827
3529
  };
3828
3530
  }
3829
- if (contract && o.clientId !== void 0 && o.clientId !== contract.clientId) {
3830
- throw new CauthyConfigError(`clientId is the contract's (${contract.clientId}): remove it from the call`);
3831
- }
3832
3531
  return createTokenVerifier({
3833
3532
  issuer,
3834
- tenantId: contract?.tenantId ?? given.tenantId,
3533
+ tenantId: given.tenantId,
3835
3534
  audience: given.audience,
3836
3535
  fetch: given.fetch,
3837
- ...contract ? { clientId: contract.clientId } : {},
3838
3536
  ...o
3839
3537
  });
3840
3538
  };
3841
3539
  const webhook = (request, o = {}) => verifyWebhook(request, { issuer, url: o.url ?? `${originOf(request)}${new URL(request.url).pathname}`, jwks: o.jwks, now: o.now, replay });
3842
- async function keyJwks() {
3843
- const s = await fromKey.signer;
3844
- const keys2 = [{ ...s.publicJwk, kid: s.kid, alg: "EdDSA", use: "sig" }];
3845
- if (fromKey.previousJwk) keys2.push({ ...await fromKey.previousJwk, alg: "EdDSA", use: "sig" });
3846
- if (ownKeys) keys2.push(...(await ownKeys.published()).keys);
3847
- return new Response(JSON.stringify({ keys: keys2 }), {
3848
- headers: { "content-type": "application/json; charset=utf-8", "cache-control": `public, max-age=${JWKS_MAX_AGE_S}` }
3849
- });
3850
- }
3851
- const noStore = () => {
3852
- throw new CauthyConfigError("on CAUTHY_KEY the application keeps no key of its own: make a new key on the application's card in Cauthy's dashboard");
3853
- };
3854
- const keyOnly = {
3855
- current: async () => noStore(),
3856
- published: async () => ({ keys: [] }),
3857
- rotate: async () => noStore(),
3858
- reseal: async () => 0
3859
- };
3860
- const pact = contract && fromKey ? {
3861
- contract,
3862
- keys: ownKeys ?? keyOnly,
3863
- jwks: keyJwks,
3864
- status: () => ownSystem.request("GET", `/v1/pact/status?system=${encodeURIComponent(contract.system)}`)
3865
- } : contract && ownKeys ? {
3866
- contract,
3867
- keys: ownKeys,
3868
- async jwks() {
3869
- return new Response(JSON.stringify(await ownKeys.published()), {
3870
- headers: { "content-type": "application/json; charset=utf-8", "cache-control": `public, max-age=${JWKS_MAX_AGE_S}` }
3871
- });
3872
- },
3873
- status: () => ownSystem.request("GET", `/v1/pact/status?system=${encodeURIComponent(contract.system)}`)
3874
- } : void 0;
3875
3540
  const configured = () => ({
3876
3541
  issuer,
3877
3542
  audience: given.audience ?? env3("CAUTHY_AUDIENCE") ?? "",
3878
- tenant: { id: contract?.tenantId ?? given.tenantId ?? env3("CAUTHY_TENANT_ID") ?? "", slug: tenant },
3543
+ tenant: { id: given.tenantId ?? env3("CAUTHY_TENANT_ID") ?? "", slug: tenant },
3879
3544
  system,
3880
3545
  client_id: clientId,
3881
3546
  sign_in_client_id: clientId,
3882
3547
  app_urls: baseURL ? [withoutTrailingSlashes(baseURL)] : [],
3883
3548
  callback_path: `${basePath}/callback`,
3884
- key: null,
3885
- contract: contract ? { link: contract.link, system_proof: contract.systemProof, require_verified_email: contract.requireVerifiedEmail } : null
3549
+ key: null
3886
3550
  });
3887
3551
  return {
3888
3552
  handler,
@@ -3904,7 +3568,6 @@ function createCauthy(given = {}) {
3904
3568
  check,
3905
3569
  toPublic,
3906
3570
  urls,
3907
- ...pact ? { pact } : {},
3908
3571
  options: { issuer, clientId, basePath, afterSignIn, afterSignOut, cookieName }
3909
3572
  };
3910
3573
  }
@@ -3988,7 +3651,6 @@ export {
3988
3651
  KEY_REPLACES,
3989
3652
  MIN_PASSWORD,
3990
3653
  PROTOCOL_VERSION,
3991
- STANDARD_VARIABLES,
3992
3654
  can,
3993
3655
  checkCode,
3994
3656
  checkEmail,
@@ -4004,7 +3666,6 @@ export {
4004
3666
  definePolicy,
4005
3667
  describePasswordPolicy,
4006
3668
  explainPasswordProblems,
4007
- failClosed,
4008
3669
  memoryReplayStore,
4009
3670
  parseKey,
4010
3671
  passwordPolicyOf,
@@ -4015,7 +3676,6 @@ export {
4015
3676
  pushCatalog,
4016
3677
  pushPolicy,
4017
3678
  pushTheme,
4018
- readContract,
4019
3679
  remoteKeys,
4020
3680
  themeFromStackmyth,
4021
3681
  tokensFromStackmyth,