@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.js CHANGED
@@ -30,7 +30,6 @@ __export(src_exports, {
30
30
  KEY_REPLACES: () => KEY_REPLACES,
31
31
  MIN_PASSWORD: () => MIN_PASSWORD,
32
32
  PROTOCOL_VERSION: () => PROTOCOL_VERSION,
33
- STANDARD_VARIABLES: () => STANDARD_VARIABLES,
34
33
  can: () => can,
35
34
  checkCode: () => checkCode,
36
35
  checkEmail: () => checkEmail,
@@ -46,7 +45,6 @@ __export(src_exports, {
46
45
  definePolicy: () => definePolicy,
47
46
  describePasswordPolicy: () => describePasswordPolicy,
48
47
  explainPasswordProblems: () => explainPasswordProblems,
49
- failClosed: () => failClosed,
50
48
  memoryReplayStore: () => memoryReplayStore,
51
49
  parseKey: () => parseKey,
52
50
  passwordPolicyOf: () => passwordPolicyOf,
@@ -57,7 +55,6 @@ __export(src_exports, {
57
55
  pushCatalog: () => pushCatalog,
58
56
  pushPolicy: () => pushPolicy,
59
57
  pushTheme: () => pushTheme,
60
- readContract: () => readContract,
61
58
  remoteKeys: () => remoteKeys,
62
59
  themeFromStackmyth: () => themeFromStackmyth,
63
60
  tokensFromStackmyth: () => tokensFromStackmyth,
@@ -122,266 +119,14 @@ async function unseal(sealed, key) {
122
119
  }
123
120
  }
124
121
  async function unsealAny(sealed, ring) {
125
- for (const key of ring) {
126
- const value = await unseal(sealed, key);
127
- if (value !== null) return value;
128
- }
129
- return null;
122
+ return (await unsealIndexed(sealed, ring))?.value ?? null;
130
123
  }
131
-
132
- // src/types.ts
133
- var CauthyConfigError = class extends Error {
134
- constructor(message) {
135
- super(`@cauthyhq/node: ${message}`);
136
- this.name = "CauthyConfigError";
137
- }
138
- };
139
- var CauthyKeyError = class extends CauthyConfigError {
140
- constructor(reason, message) {
141
- super(`CAUTHY_KEY: ${message} (${reason})`);
142
- this.reason = reason;
143
- this.name = "CauthyKeyError";
124
+ async function unsealIndexed(sealed, ring) {
125
+ for (let index = 0; index < ring.length; index++) {
126
+ const value = await unseal(sealed, ring[index]);
127
+ if (value !== null) return { value, index };
144
128
  }
145
- reason;
146
- };
147
-
148
- // src/url.ts
149
- function withoutTrailingSlashes(s) {
150
- let end = s.length;
151
- while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
152
- return s.slice(0, end);
153
- }
154
-
155
- // src/contract.ts
156
- var STANDARD_VARIABLES = [
157
- "CAUTHY_ISSUER",
158
- "CAUTHY_CLIENT_ID",
159
- "CAUTHY_CLIENT_SECRET",
160
- "CAUTHY_SECRET",
161
- "CAUTHY_SYSTEM",
162
- "CAUTHY_TENANT",
163
- "CAUTHY_SYSTEM_KEY",
164
- "CAUTHY_SYSTEM_KEY_ID"
165
- ];
166
- function failClosed(environment) {
167
- const set = STANDARD_VARIABLES.filter((name) => (environment?.[name] ?? "") !== "");
168
- if (set.length > 0) {
169
- 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.");
170
- }
171
- }
172
- var SLUG = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/;
173
- var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
174
- var NIL_UUID = "00000000-0000-0000-0000-000000000000";
175
- var text = (v) => typeof v === "string" ? v : "";
176
- function httpsAddress(raw, what) {
177
- let u;
178
- try {
179
- u = new URL(raw);
180
- } catch {
181
- throw new CauthyConfigError(`the contract's ${what} is not an address: ${JSON.stringify(raw)}`);
182
- }
183
- const local = u.hostname === "localhost" || u.hostname === "127.0.0.1";
184
- if (u.protocol !== "https:" && !(u.protocol === "http:" && local)) {
185
- throw new CauthyConfigError(`the contract's ${what} must be https: ${raw}`);
186
- }
187
- return u;
188
- }
189
- function readContract(options) {
190
- const { system } = options;
191
- const provider = options.provider ?? "cauthy";
192
- if (!SLUG.test(system ?? "")) throw new CauthyConfigError(`contract.system must be the system's id in the graph, e.g. "acme-crm"`);
193
- const graph = options.graph && typeof options.graph === "object" && "default" in options.graph && !("edges" in options.graph) ? options.graph.default : options.graph;
194
- if (!graph || !Array.isArray(graph.edges) || !Array.isArray(graph.nodes)) {
195
- throw new CauthyConfigError("contract.graph is not a context graph: pass the graph.json that `cauthy contracts pull` writes");
196
- }
197
- const node = (id) => graph.nodes?.find((n) => n.type === "system" && n.id === id);
198
- const own = node(system);
199
- if (!own) throw new CauthyConfigError(`the graph has no system "${system}"`);
200
- const cauthy = node(provider);
201
- if (!cauthy) throw new CauthyConfigError(`the graph has no system "${provider}"`);
202
- 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);
203
- if (links.length === 0) {
204
- 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`);
205
- }
206
- if (links.length > 1) {
207
- throw new CauthyConfigError(`${system} has ${links.length} links to ${provider}: name the one to read (contract.link): ${links.map((l) => text(l.link)).join(", ")}`);
208
- }
209
- const e = links[0];
210
- const link = text(e.link);
211
- const named = text(e.identity?.tenant);
212
- const pinned = text(e.identity?.tenant_id);
213
- 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`);
214
- let tenantId;
215
- let tenant;
216
- if (UUID.test(named)) {
217
- if (pinned !== "" && pinned.toLowerCase() !== named.toLowerCase()) {
218
- throw new CauthyConfigError(`the link ${link} names the workspace ${named} and its tenant_id ${pinned} does not match: pull the graph again`);
219
- }
220
- tenantId = named;
221
- tenant = text((cauthy.tenants ?? []).find((t) => t.id === named)?.slug);
222
- if (tenant === "") throw new CauthyConfigError(`the graph does not name the slug of the workspace ${named} that the link ${link} uses`);
223
- } else {
224
- if (!SLUG.test(named)) throw new CauthyConfigError(`the link ${link} names no workspace: identity.tenant is a UUID or a slug`);
225
- if (!UUID.test(pinned)) {
226
- 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`);
227
- }
228
- tenantId = pinned;
229
- tenant = named;
230
- }
231
- const issuer = withoutTrailingSlashes(httpsAddress(text(cauthy.url), `address of ${provider}`).href);
232
- const signIn = e.sign_in ?? {};
233
- const clientId = text(signIn.client_id) || `pact.${link}`;
234
- const redirectUris = Array.isArray(signIn.redirect_uris) ? signIn.redirect_uris.map(text).filter(Boolean) : [];
235
- if (redirectUris.length === 0) throw new CauthyConfigError(`the link ${link} names no redirect URI`);
236
- const proof = text(signIn.system_proof);
237
- if (signIn.system_proof !== void 0 && proof !== "off" && proof !== "auto" && proof !== "required") {
238
- 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`);
239
- }
240
- const systemProof = proof === "off" || proof === "auto" || proof === "required" ? proof : signIn.require_system_proof === true ? "required" : "off";
241
- if (systemProof !== "off" && !e.service) {
242
- 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`);
243
- }
244
- const out2 = {
245
- system,
246
- link,
247
- issuer,
248
- tenantId,
249
- tenant,
250
- clientId,
251
- redirectUris,
252
- requireSystemProof: systemProof === "required",
253
- systemProof,
254
- requireVerifiedEmail: signIn.require_verified_email === true,
255
- persistentSessions: signIn.persistent_sessions === true
256
- };
257
- if (e.service) {
258
- out2.serviceClientId = text(e.service.client_id) || `pact.${link}.service`;
259
- const jwks = text(e.service.jwks_url) || text(own.jwks_url);
260
- if (jwks === "") throw new CauthyConfigError(`the link ${link} gives ${system} its own identity, and ${system} names no jwks_url`);
261
- out2.jwksUrl = httpsAddress(jwks, "jwks_url").href;
262
- out2.jwksPath = new URL(out2.jwksUrl).pathname;
263
- }
264
- if (text(own.events_url) !== "") out2.eventsUrl = httpsAddress(text(own.events_url), "events_url").href;
265
- return out2;
266
- }
267
-
268
- // src/keys.ts
269
- var import_jose2 = require("jose");
270
- var RETIRE_AFTER_MS = 24 * 60 * 60 * 1e3;
271
- var FORGET_AFTER_MS = 7 * 24 * 60 * 60 * 1e3;
272
- var CURRENT_TTL_MS = 5 * 60 * 1e3;
273
- var JWKS_MAX_AGE_S = 300;
274
- var SIGN_AFTER_MS = (JWKS_MAX_AGE_S + 60) * 1e3;
275
- var SETTLING_TTL_MS = 5 * 1e3;
276
- var newestFirst = (keys2) => [...keys2].sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.kid.localeCompare(a.kid));
277
- function published(key) {
278
- const { kty, n, e } = key.publicJwk;
279
- return { kty, n, e, kid: key.kid, alg: "RS256", use: "sig" };
280
- }
281
- function pactKeys(store, sealKeys, now = Date.now) {
282
- let remembered;
283
- let making;
284
- async function make(after) {
285
- const { publicKey, privateKey } = await (0, import_jose2.generateKeyPair)("RS256", { modulusLength: 2048, extractable: true });
286
- const publicJwk = await (0, import_jose2.exportJWK)(publicKey);
287
- const kid = await (0, import_jose2.calculateJwkThumbprint)(publicJwk);
288
- const at = Math.max(now(), after ? Date.parse(after.createdAt) + 1 : 0);
289
- const key = { kid, publicJwk, sealed: await seal(await (0, import_jose2.exportJWK)(privateKey), (await sealKeys)[0]), createdAt: new Date(at).toISOString() };
290
- if (!after && store.addFirst) await store.addFirst(key);
291
- else await store.add(key);
292
- return key;
293
- }
294
- const age = (key) => now() - Date.parse(key.createdAt);
295
- function signer(kept) {
296
- const ripe = kept.find((k) => age(k) >= SIGN_AFTER_MS);
297
- if (!ripe) return { key: kept[kept.length - 1], until: now() + SETTLING_TTL_MS };
298
- const until = ripe === kept[0] ? now() + CURRENT_TTL_MS : Math.min(now() + CURRENT_TTL_MS, Date.parse(kept[0].createdAt) + SIGN_AFTER_MS);
299
- return { key: ripe, until };
300
- }
301
- async function open(key) {
302
- const ring = await sealKeys;
303
- for (let i = 0; i < ring.length; i++) {
304
- const jwk = await unseal(key.sealed, ring[i]);
305
- if (jwk !== null) return { jwk, fresh: i === 0 };
306
- }
307
- return null;
308
- }
309
- async function keptKeys() {
310
- const kept = newestFirst(await store.list());
311
- if (kept.length > 0) return kept;
312
- making ??= make().finally(() => {
313
- making = void 0;
314
- });
315
- await making;
316
- const again = newestFirst(await store.list());
317
- if (again.length === 0) throw new CauthyConfigError("the key store kept no key: check that it writes");
318
- return again;
319
- }
320
- return {
321
- async current() {
322
- if (remembered && remembered.until > now()) return remembered;
323
- const { key, until } = signer(await keptKeys());
324
- if (remembered?.kid === key.kid) {
325
- remembered.until = until;
326
- return remembered;
327
- }
328
- const opened = await open(key);
329
- if (opened === null) {
330
- 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`);
331
- }
332
- remembered = { kid: key.kid, privateKey: await (0, import_jose2.importJWK)(opened.jwk, "RS256"), until };
333
- return remembered;
334
- },
335
- async published() {
336
- const kept = await keptKeys();
337
- const out2 = [];
338
- for (let i = 0; i < kept.length; i++) {
339
- if (i === 0 || now() - Date.parse(kept[i - 1].createdAt) < RETIRE_AFTER_MS) out2.push(published(kept[i]));
340
- else if (now() - Date.parse(kept[i - 1].createdAt) > FORGET_AFTER_MS) await store.remove(kept[i].kid);
341
- }
342
- return { keys: out2 };
343
- },
344
- async rotate() {
345
- const key = await make(newestFirst(await store.list())[0]);
346
- remembered = void 0;
347
- return key.kid;
348
- },
349
- async reseal() {
350
- let changed = 0;
351
- const ring = await sealKeys;
352
- for (const key of await store.list()) {
353
- const opened = await open(key);
354
- if (opened === null || opened.fresh) continue;
355
- await store.update({ ...key, sealed: await seal(opened.jwk, ring[0]) });
356
- changed++;
357
- }
358
- return changed;
359
- }
360
- };
361
- }
362
-
363
- // src/rootkey.ts
364
- var encoder2 = new TextEncoder();
365
- var MIN_ROOT_KEY = 32;
366
- async function derive(root, purpose) {
367
- const ikm = await crypto.subtle.importKey("raw", encoder2.encode(root), "HKDF", false, ["deriveBits"]);
368
- const bits = await crypto.subtle.deriveBits(
369
- { name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: encoder2.encode(`pact/${purpose}/v1`) },
370
- ikm,
371
- 256
372
- );
373
- return new Uint8Array(bits);
374
- }
375
- function deriveKeys(root, previous) {
376
- if (root.length < MIN_ROOT_KEY) {
377
- throw new CauthyConfigError(`rootKey (PACT_ROOT_KEY) must be at least ${MIN_ROOT_KEY} characters: 32 random bytes, e.g. openssl rand -base64 32`);
378
- }
379
- if (previous !== void 0 && previous !== "" && previous.length < MIN_ROOT_KEY) {
380
- throw new CauthyConfigError(`the previous root key (PACT_ROOT_KEY_PREVIOUS) must be at least ${MIN_ROOT_KEY} characters`);
381
- }
382
- const roots = previous && previous !== root ? [root, previous] : [root];
383
- const ring = (purpose) => Promise.all(roots.map((r) => derive(r, purpose)));
384
- return { seal: ring("seal"), cookie: ring("cookie"), flow: ring("flow") };
129
+ return null;
385
130
  }
386
131
 
387
132
  // src/deadline.ts
@@ -417,6 +162,13 @@ function anySignal(signals) {
417
162
  return controller.signal;
418
163
  }
419
164
 
165
+ // src/url.ts
166
+ function withoutTrailingSlashes(s) {
167
+ let end = s.length;
168
+ while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
169
+ return s.slice(0, end);
170
+ }
171
+
420
172
  // src/permissions.ts
421
173
  function matches(entry, resource, action) {
422
174
  const i = entry.lastIndexOf(":");
@@ -486,19 +238,19 @@ var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
486
238
  function policySource(system, read) {
487
239
  const roles = Array.isArray(read) ? read : read.roles;
488
240
  const catalog = Array.isArray(read) ? [] : read.catalog ?? [];
489
- const text2 = (v) => JSON.stringify(v);
490
- const key = (k) => IDENT.test(k) ? k : text2(k);
241
+ const text = (v) => JSON.stringify(v);
242
+ const key = (k) => IDENT.test(k) ? k : text(k);
491
243
  const one = (r, pad) => {
492
- const parts = [`role: ${text2(r.role)}`];
493
- if (r.name) parts.push(`name: ${text2(r.name)}`);
494
- if (r.description) parts.push(`description: ${text2(r.description)}`);
495
- if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(text2).join(", ")}]`);
244
+ const parts = [`role: ${text(r.role)}`];
245
+ if (r.name) parts.push(`name: ${text(r.name)}`);
246
+ if (r.description) parts.push(`description: ${text(r.description)}`);
247
+ if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(text).join(", ")}]`);
496
248
  if (r.permissions?.length) {
497
249
  const list = r.permissions.map((p) => `${pad} { ${[
498
- ...p.kind && p.kind !== "*" ? [`${key("kind")}: ${text2(p.kind)}`] : [],
499
- `resource: ${text2(p.resource)}`,
500
- `action: ${text2(p.action)}`,
501
- ...p.condition ? [`condition: ${text2(p.condition)}`] : []
250
+ ...p.kind && p.kind !== "*" ? [`${key("kind")}: ${text(p.kind)}`] : [],
251
+ `resource: ${text(p.resource)}`,
252
+ `action: ${text(p.action)}`,
253
+ ...p.condition ? [`condition: ${text(p.condition)}`] : []
502
254
  ].join(", ")} },`);
503
255
  parts.push(`permissions: [
504
256
  ${list.join("\n")}
@@ -510,14 +262,14 @@ ${pad} `)},
510
262
  ${pad} },`;
511
263
  };
512
264
  const resource = (c) => {
513
- const parts = [`resource: ${text2(c.resource)}`];
514
- if (c.description) parts.push(`description: ${text2(c.description)}`);
515
- parts.push(`actions: [${c.actions.map(text2).join(", ")}]`);
265
+ const parts = [`resource: ${text(c.resource)}`];
266
+ if (c.description) parts.push(`description: ${text(c.description)}`);
267
+ parts.push(`actions: [${c.actions.map(text).join(", ")}]`);
516
268
  const attrs = Object.entries(c.attributes ?? {});
517
- if (attrs.length) parts.push(`attributes: { ${attrs.map(([k, v]) => `${key(k)}: ${text2(v)}`).join(", ")} }`);
269
+ if (attrs.length) parts.push(`attributes: { ${attrs.map(([k, v]) => `${key(k)}: ${text(v)}`).join(", ")} }`);
518
270
  return ` { ${parts.join(", ")} },`;
519
271
  };
520
- const head = `// The catalog of roles of ${text2(system)}, as Cauthy has it.
272
+ const head = `// The catalog of roles of ${text(system)}, as Cauthy has it.
521
273
  // Written by \`pullPolicy\`: run it again after the catalog
522
274
  // changes, and do not edit this file by hand.
523
275
  `;
@@ -627,7 +379,7 @@ function organizationIdOf(o) {
627
379
  }
628
380
 
629
381
  // src/system.ts
630
- var import_jose3 = require("jose");
382
+ var import_jose2 = require("jose");
631
383
 
632
384
  // src/theme.ts
633
385
  var MAX_RADIUS = 40;
@@ -644,7 +396,7 @@ function withoutComments(css) {
644
396
  }
645
397
  }
646
398
  function declarations(css) {
647
- const text2 = withoutComments(css);
399
+ const text = withoutComments(css);
648
400
  const out2 = [];
649
401
  const within = [];
650
402
  let chunk = "";
@@ -655,7 +407,7 @@ function declarations(css) {
655
407
  if (m) out2.push({ name: m[1], value: m[2].replace(/\s+/g, " ").replace(/\s*!important$/, ""), within: [...within] });
656
408
  chunk = "";
657
409
  };
658
- for (const ch of text2) {
410
+ for (const ch of text) {
659
411
  if (quote) {
660
412
  chunk += ch;
661
413
  if (ch === quote) quote = "";
@@ -991,6 +743,22 @@ async function pushTheme(o) {
991
743
  }
992
744
  }
993
745
 
746
+ // src/types.ts
747
+ var CauthyConfigError = class extends Error {
748
+ constructor(message) {
749
+ super(`@cauthyhq/node: ${message}`);
750
+ this.name = "CauthyConfigError";
751
+ }
752
+ };
753
+ var CauthyKeyError = class extends CauthyConfigError {
754
+ constructor(reason, message) {
755
+ super(`CAUTHY_KEY: ${message} (${reason})`);
756
+ this.reason = reason;
757
+ this.name = "CauthyKeyError";
758
+ }
759
+ reason;
760
+ };
761
+
994
762
  // src/system.ts
995
763
  var ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
996
764
  var SKEW_MS = 3e4;
@@ -1030,7 +798,7 @@ function asJwk(k) {
1030
798
  }
1031
799
  async function loadKey(k) {
1032
800
  const jwk = asJwk(k);
1033
- return jwk ? (0, import_jose3.importJWK)(jwk, "RS256") : (0, import_jose3.importPKCS8)(k.privateKey, "RS256", { extractable: true });
801
+ return jwk ? (0, import_jose2.importJWK)(jwk, "RS256") : (0, import_jose2.importPKCS8)(k.privateKey, "RS256", { extractable: true });
1034
802
  }
1035
803
  function createSystem(given = {}) {
1036
804
  const issuer = withoutTrailingSlashes(given.issuer ?? env("CAUTHY_ISSUER") ?? "");
@@ -1057,10 +825,6 @@ function createSystem(given = {}) {
1057
825
  if (given.signer) return;
1058
826
  if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address");
1059
827
  if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
1060
- if (given.keys) {
1061
- if (!given.keysClientId) throw new CauthyConfigError("the contract gives the system no identity of its own: its link needs a service block");
1062
- return;
1063
- }
1064
828
  if (key ? key.keyId === "" : clientSecret === "") {
1065
829
  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)");
1066
830
  }
@@ -1078,15 +842,15 @@ function createSystem(given = {}) {
1078
842
  let asking;
1079
843
  async function ask() {
1080
844
  configured();
1081
- const self = given.signer ? given.signer.clientId : given.keys ? given.keysClientId : key?.clientId || clientId;
845
+ const self = given.signer ? given.signer.clientId : key?.clientId || clientId;
1082
846
  const form = new URLSearchParams({ grant_type: "client_credentials", client_id: self });
1083
847
  if (given.signer) {
1084
848
  const s = await given.signer.signer;
1085
849
  form.set("client_assertion_type", ASSERTION_TYPE);
1086
850
  form.set("client_assertion", await s.assertion(self, tokenEndpoint));
1087
- } else if (given.keys || key) {
1088
- const signer = given.keys ? await given.keys.current() : { kid: key.keyId, privateKey: await signingKey() };
1089
- const assertion = await new import_jose3.SignJWT({}).setProtectedHeader({ alg: "RS256", kid: signer.kid }).setIssuer(self).setSubject(self).setAudience(tokenEndpoint).setJti(crypto.randomUUID()).setIssuedAt().setExpirationTime("2m").sign(signer.privateKey);
851
+ } else if (key) {
852
+ const signer = { kid: key.keyId, privateKey: await signingKey() };
853
+ const assertion = await new import_jose2.SignJWT({}).setProtectedHeader({ alg: "RS256", kid: signer.kid }).setIssuer(self).setSubject(self).setAudience(tokenEndpoint).setJti(crypto.randomUUID()).setIssuedAt().setExpirationTime("2m").sign(signer.privateKey);
1090
854
  form.set("client_assertion_type", ASSERTION_TYPE);
1091
855
  form.set("client_assertion", assertion);
1092
856
  } else {
@@ -1194,9 +958,8 @@ function createSystem(given = {}) {
1194
958
  return checkResultOf(await data("POST", `/v1/systems/${id(system)}/check`, { organization_id: q.organizationId, user_id: q.userId, ...checkWire(q) }));
1195
959
  },
1196
960
  async publicJwks() {
1197
- if (given.keys) return given.keys.published();
1198
961
  if (!key) return { keys: [] };
1199
- const { kty, n, e } = asJwk(key) ?? await (0, import_jose3.exportJWK)(await signingKey());
962
+ const { kty, n, e } = asJwk(key) ?? await (0, import_jose2.exportJWK)(await signingKey());
1200
963
  return { keys: [{ kty, n, e, kid: key.keyId, alg: "RS256", use: "sig" }] };
1201
964
  }
1202
965
  };
@@ -1358,7 +1121,7 @@ function firstProblem(...checks) {
1358
1121
  }
1359
1122
 
1360
1123
  // src/webhook.ts
1361
- var import_jose4 = require("jose");
1124
+ var import_jose3 = require("jose");
1362
1125
 
1363
1126
  // src/sha256.ts
1364
1127
  var K = new Uint32Array([
@@ -1530,8 +1293,8 @@ var fail = (reason, message) => {
1530
1293
  throw new CauthyKeyError(reason, message);
1531
1294
  };
1532
1295
  var NOT_A_KEY = "not a Cauthy key (copy it again from the dashboard)";
1533
- function parseKey(text2) {
1534
- const s = text2.replace(/^[ \t\r\n]+|[ \t\r\n]+$/g, "");
1296
+ function parseKey(text) {
1297
+ const s = text.replace(/^[ \t\r\n]+|[ \t\r\n]+$/g, "");
1535
1298
  if (s === "" || s.length > MAX_LEN || !printableAscii(s)) fail("key_format", NOT_A_KEY);
1536
1299
  const head = s.split(".", 1)[0];
1537
1300
  if (head !== KEY_PREFIX) {
@@ -1599,7 +1362,7 @@ var keys = /* @__PURE__ */ new Map();
1599
1362
  function keysOf(issuer) {
1600
1363
  let set = keys.get(issuer);
1601
1364
  if (set === void 0) {
1602
- set = (0, import_jose4.createRemoteJWKSet)(new URL(`${issuer}/.well-known/jwks.json`), { cooldownDuration: 3e4 });
1365
+ set = (0, import_jose3.createRemoteJWKSet)(new URL(`${issuer}/.well-known/jwks.json`), { cooldownDuration: 3e4 });
1603
1366
  keys.set(issuer, set);
1604
1367
  }
1605
1368
  return set;
@@ -1616,7 +1379,7 @@ async function verifyWebhook(request, options = {}) {
1616
1379
  const audience = options.url ?? request.url.split("?")[0];
1617
1380
  let claims;
1618
1381
  try {
1619
- const verified = await (0, import_jose4.jwtVerify)(signature, options.jwks ?? keysOf(issuer), {
1382
+ const verified = await (0, import_jose3.jwtVerify)(signature, options.jwks ?? keysOf(issuer), {
1620
1383
  algorithms: ["RS256"],
1621
1384
  audience,
1622
1385
  typ: "cauthy-webhook+jwt",
@@ -1714,10 +1477,10 @@ async function finish(res, route, cacheable = false) {
1714
1477
  if (!cacheable && !(headers.get("cache-control") ?? "").includes("no-store")) headers.set("cache-control", "no-store");
1715
1478
  let body = res.body;
1716
1479
  if (route !== null && SIGN_IN_ROUTES.has(route) && res.ok && (headers.get("content-type") ?? "").startsWith("application/json")) {
1717
- const text2 = await res.text();
1718
- body = text2;
1480
+ const text = await res.text();
1481
+ body = text;
1719
1482
  try {
1720
- const parsed = JSON.parse(text2);
1483
+ const parsed = JSON.parse(text);
1721
1484
  const next = nextOf(route, parsed?.status);
1722
1485
  if (parsed !== null && next !== void 0) body = JSON.stringify({ ...parsed, next });
1723
1486
  } catch {
@@ -1728,29 +1491,29 @@ async function finish(res, route, cacheable = false) {
1728
1491
  }
1729
1492
 
1730
1493
  // src/verify.ts
1731
- var import_jose5 = require("jose");
1494
+ var import_jose4 = require("jose");
1732
1495
 
1733
1496
  // src/json.ts
1734
1497
  var SPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r"]);
1735
1498
  var LIMIT = 2n ** 53n;
1736
- function parseStrict(text2, integerMembers = []) {
1499
+ function parseStrict(text, integerMembers = []) {
1737
1500
  let i = 0;
1738
1501
  let depth = 0;
1739
1502
  const fail2 = (what) => {
1740
1503
  throw new SyntaxError(`${what} at ${i}`);
1741
1504
  };
1742
1505
  const skip = () => {
1743
- while (i < text2.length && SPACE.has(text2[i])) i++;
1506
+ while (i < text.length && SPACE.has(text[i])) i++;
1744
1507
  };
1745
1508
  const value = (integer = false) => {
1746
1509
  skip();
1747
- const c = text2[i];
1510
+ const c = text[i];
1748
1511
  if (c === "{") return object();
1749
1512
  if (c === "[") return array();
1750
1513
  if (c === '"') return string();
1751
1514
  if (c === "-" || c >= "0" && c <= "9") return number(integer);
1752
1515
  for (const [word, v] of [["true", true], ["false", false], ["null", null]]) {
1753
- if (text2.startsWith(word, i)) {
1516
+ if (text.startsWith(word, i)) {
1754
1517
  i += word.length;
1755
1518
  return v;
1756
1519
  }
@@ -1760,13 +1523,13 @@ function parseStrict(text2, integerMembers = []) {
1760
1523
  const string = () => {
1761
1524
  const start = i;
1762
1525
  i++;
1763
- while (i < text2.length && text2[i] !== '"') i += text2[i] === "\\" ? 2 : 1;
1764
- if (text2[i] !== '"') fail2("unterminated string");
1526
+ while (i < text.length && text[i] !== '"') i += text[i] === "\\" ? 2 : 1;
1527
+ if (text[i] !== '"') fail2("unterminated string");
1765
1528
  i++;
1766
- return JSON.parse(text2.slice(start, i));
1529
+ return JSON.parse(text.slice(start, i));
1767
1530
  };
1768
1531
  const number = (integer) => {
1769
- const m = /^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/.exec(text2.slice(i));
1532
+ const m = /^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/.exec(text.slice(i));
1770
1533
  if (!m) return fail2("bad number");
1771
1534
  i += m[0].length;
1772
1535
  if (integer && (m[2] !== void 0 || m[3] !== void 0)) return Number.NaN;
@@ -1781,7 +1544,7 @@ function parseStrict(text2, integerMembers = []) {
1781
1544
  depth++;
1782
1545
  const out2 = [];
1783
1546
  skip();
1784
- if (text2[i] === "]") {
1547
+ if (text[i] === "]") {
1785
1548
  i++;
1786
1549
  depth--;
1787
1550
  return out2;
@@ -1789,8 +1552,8 @@ function parseStrict(text2, integerMembers = []) {
1789
1552
  for (; ; ) {
1790
1553
  out2.push(value());
1791
1554
  skip();
1792
- if (text2[i] === ",") i++;
1793
- else if (text2[i] === "]") {
1555
+ if (text[i] === ",") i++;
1556
+ else if (text[i] === "]") {
1794
1557
  i++;
1795
1558
  depth--;
1796
1559
  return out2;
@@ -1803,24 +1566,24 @@ function parseStrict(text2, integerMembers = []) {
1803
1566
  const out2 = {};
1804
1567
  const seen = /* @__PURE__ */ new Set();
1805
1568
  skip();
1806
- if (text2[i] === "}") {
1569
+ if (text[i] === "}") {
1807
1570
  i++;
1808
1571
  depth--;
1809
1572
  return out2;
1810
1573
  }
1811
1574
  for (; ; ) {
1812
1575
  skip();
1813
- if (text2[i] !== '"') fail2("expected a name");
1576
+ if (text[i] !== '"') fail2("expected a name");
1814
1577
  const name = string();
1815
1578
  if (seen.has(name)) fail2(`repeated name ${JSON.stringify(name)}`);
1816
1579
  seen.add(name);
1817
1580
  skip();
1818
- if (text2[i] !== ":") fail2("expected :");
1581
+ if (text[i] !== ":") fail2("expected :");
1819
1582
  i++;
1820
1583
  Object.defineProperty(out2, name, { value: value(depth === 1 && integerMembers.includes(name)), enumerable: true, writable: true, configurable: true });
1821
1584
  skip();
1822
- if (text2[i] === ",") i++;
1823
- else if (text2[i] === "}") {
1585
+ if (text[i] === ",") i++;
1586
+ else if (text[i] === "}") {
1824
1587
  i++;
1825
1588
  depth--;
1826
1589
  return out2;
@@ -1829,7 +1592,7 @@ function parseStrict(text2, integerMembers = []) {
1829
1592
  };
1830
1593
  const result = value();
1831
1594
  skip();
1832
- if (i < text2.length) fail2("trailing characters");
1595
+ if (i < text.length) fail2("trailing characters");
1833
1596
  return result;
1834
1597
  }
1835
1598
 
@@ -1850,6 +1613,15 @@ function deprecated(what, instead) {
1850
1613
  var enc2 = new TextEncoder();
1851
1614
  var PKCS8_ED25519 = Uint8Array.from([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32]);
1852
1615
  var ASSERTION_TYPE2 = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
1616
+ async function ringKey(secret, purpose) {
1617
+ const ikm = await crypto.subtle.importKey("raw", enc2.encode(secret), "HKDF", false, ["deriveBits"]);
1618
+ return new Uint8Array(await crypto.subtle.deriveBits(
1619
+ { name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: enc2.encode(`pact/${purpose}/v1`) },
1620
+ ikm,
1621
+ 256
1622
+ ));
1623
+ }
1624
+ var ringOf = (secrets, purpose) => Promise.all(secrets.map((s) => ringKey(s, purpose)));
1853
1625
  async function seedOf(secret) {
1854
1626
  const ikm = await crypto.subtle.importKey("raw", enc2.encode(secret), "HKDF", false, ["deriveBits"]);
1855
1627
  return new Uint8Array(await crypto.subtle.deriveBits(
@@ -1887,12 +1659,11 @@ async function signerOf(secret) {
1887
1659
  }
1888
1660
  function deriveFromKey(key, previous) {
1889
1661
  const other = previous && previous.secret !== key.secret ? previous : void 0;
1890
- const rings = deriveKeys(key.secret, other?.secret);
1662
+ const secrets = other ? [key.secret, other.secret] : [key.secret];
1891
1663
  return {
1892
- cookie: rings.cookie,
1893
- flow: rings.flow,
1894
- signer: signerOf(key.secret),
1895
- ...other ? { previousJwk: signerOf(other.secret).then((s) => ({ ...s.publicJwk, kid: s.kid })) } : {}
1664
+ cookie: ringOf(secrets, "cookie"),
1665
+ flow: ringOf(secrets, "flow"),
1666
+ signer: signerOf(key.secret)
1896
1667
  };
1897
1668
  }
1898
1669
 
@@ -2011,8 +1782,8 @@ var CauthyTokenError = class extends Error {
2011
1782
  detail;
2012
1783
  };
2013
1784
  var MAX_TOKEN_BYTES = 8192;
2014
- var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
2015
- var NIL_UUID2 = "00000000-0000-0000-0000-000000000000";
1785
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
1786
+ var NIL_UUID = "00000000-0000-0000-0000-000000000000";
2016
1787
  var SEGMENT = /^[A-Za-z0-9_-]+$/;
2017
1788
  var MAX_KEYS_BYTES = 64 * 1024;
2018
1789
  var KEYS_TIMEOUT_MS = 5e3;
@@ -2040,7 +1811,7 @@ function remoteKeys(issuer, fetchKeys = fetch) {
2040
1811
  if (seen.has(k.kid)) throw new Error(`two usable keys share the kid ${JSON.stringify(k.kid)}`);
2041
1812
  seen.add(k.kid);
2042
1813
  }
2043
- set = (0, import_jose5.createLocalJWKSet)(parsed);
1814
+ set = (0, import_jose4.createLocalJWKSet)(parsed);
2044
1815
  const maxAge = /max-age=(\d+)/.exec(res.headers.get("cache-control") ?? "");
2045
1816
  const ttl = Math.max(KEYS_MIN_TTL_S, Math.min(maxAge ? Number(maxAge[1]) : KEYS_DEFAULT_TTL_S, KEYS_MAX_TTL_S));
2046
1817
  expiresAt = Date.now() + ttl * 1e3;
@@ -2092,7 +1863,7 @@ async function readCapped(res, max) {
2092
1863
  }
2093
1864
  return out2;
2094
1865
  }
2095
- var published2 = /* @__PURE__ */ new Map();
1866
+ var published = /* @__PURE__ */ new Map();
2096
1867
  var withFetch = /* @__PURE__ */ new WeakMap();
2097
1868
  function remoteKeysFor(issuer, fetchKeys) {
2098
1869
  let byIssuer = withFetch.get(fetchKeys);
@@ -2102,10 +1873,10 @@ function remoteKeysFor(issuer, fetchKeys) {
2102
1873
  return set;
2103
1874
  }
2104
1875
  function keysOf2(issuer) {
2105
- let set = published2.get(issuer);
1876
+ let set = published.get(issuer);
2106
1877
  if (set === void 0) {
2107
1878
  set = remoteKeys(issuer);
2108
- published2.set(issuer, set);
1879
+ published.set(issuer, set);
2109
1880
  }
2110
1881
  return set;
2111
1882
  }
@@ -2147,7 +1918,7 @@ function createTokenVerifier(options = {}) {
2147
1918
  if (audience === "") throw new CauthyConfigError("audience (CAUTHY_AUDIENCE) is not set: without it a token issued for another application would be accepted");
2148
1919
  const tenant = options.tenantId ?? env2("CAUTHY_TENANT_ID") ?? "";
2149
1920
  if (tenant === "") throw new CauthyConfigError("tenantId (CAUTHY_TENANT_ID) is not set: without it a token of another workspace would be accepted");
2150
- 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");
1921
+ 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");
2151
1922
  const leeway = options.leeway ?? 30;
2152
1923
  if (!Number.isFinite(leeway) || leeway < 0 || leeway > 60) throw new CauthyConfigError("leeway must be 0 to 60 seconds");
2153
1924
  return async (from) => {
@@ -2171,7 +1942,7 @@ function createTokenVerifier(options = {}) {
2171
1942
  if (typeof header.kid !== "string" || header.kid === "") throw new CauthyTokenError("missing_kid");
2172
1943
  let payloadBytes;
2173
1944
  try {
2174
- const verified = await (0, import_jose5.compactVerify)(token, options.jwks ?? (options.fetch ? remoteKeysFor(issuer, options.fetch) : keysOf2(issuer)), { algorithms: ["RS256"] });
1945
+ const verified = await (0, import_jose4.compactVerify)(token, options.jwks ?? (options.fetch ? remoteKeysFor(issuer, options.fetch) : keysOf2(issuer)), { algorithms: ["RS256"] });
2175
1946
  payloadBytes = verified.payload;
2176
1947
  } catch (err) {
2177
1948
  const code = err?.code;
@@ -2196,8 +1967,8 @@ function createTokenVerifier(options = {}) {
2196
1967
  throw new CauthyTokenError("invalid_claim", name);
2197
1968
  };
2198
1969
  if (typeof claims.iss !== "string") bad("iss");
2199
- if (typeof claims.sub !== "string" || !UUID2.test(claims.sub)) bad("sub");
2200
- if (typeof claims.tid !== "string" || !UUID2.test(claims.tid)) bad("tid");
1970
+ if (typeof claims.sub !== "string" || !UUID.test(claims.sub)) bad("sub");
1971
+ if (typeof claims.tid !== "string" || !UUID.test(claims.tid)) bad("tid");
2201
1972
  const aud = claims.aud;
2202
1973
  if (!(typeof aud === "string" || Array.isArray(aud) && aud.length > 0 && aud.every((a) => typeof a === "string"))) bad("aud");
2203
1974
  for (const name of ["exp", "iat", "nbf"]) if (claims[name] !== void 0 && !Number.isInteger(claims[name])) bad(name);
@@ -2235,13 +2006,13 @@ async function verifyToken(from, options = {}) {
2235
2006
  return createTokenVerifier(options)(from);
2236
2007
  }
2237
2008
  async function createVerifierFromEnv(options = {}) {
2238
- const text2 = options.key ?? env2("CAUTHY_KEY");
2239
- if (text2 === void 0 || text2 === "") return createTokenVerifier(options);
2009
+ const text = options.key ?? env2("CAUTHY_KEY");
2010
+ if (text === void 0 || text === "") return createTokenVerifier(options);
2240
2011
  refuseBesideKey(typeof process !== "undefined" ? process.env : void 0);
2241
2012
  for (const name of ["issuer", "audience", "tenantId"]) {
2242
2013
  if (options[name] !== void 0) throw new CauthyConfigError(`with CAUTHY_KEY, ${name} comes from CAUTHY_KEY: do not pass it`);
2243
2014
  }
2244
- const key = parseKey(text2);
2015
+ const key = parseKey(text);
2245
2016
  const system = createSystem({ issuer: key.issuer, clientId: key.clientId, signer: { clientId: key.clientId, signer: deriveFromKey(key).signer }, fetch: options.fetch });
2246
2017
  const id = await createIdentity({ issuer: key.issuer, token: () => system.token(), fetch: deadline(options.fetch, KEYS_TIMEOUT_MS) }).ready();
2247
2018
  const { key: _, ...rest } = options;
@@ -2338,9 +2109,18 @@ function redirect(location, cookies = []) {
2338
2109
  function conditionalOf(c) {
2339
2110
  return { resource: String(c?.resource ?? ""), action: String(c?.action ?? ""), effect: c?.effect === "deny" ? "deny" : "allow" };
2340
2111
  }
2112
+ function refuseContracts(given) {
2113
+ const options = given;
2114
+ const set = [
2115
+ ...["contract", "keys", "rootKey", "rootKeyPrevious"].filter((name) => options[name] !== void 0).map((name) => `the ${name} option`),
2116
+ ...["PACT_ROOT_KEY", "PACT_ROOT_KEY_PREVIOUS"].filter((name) => (env3(name) ?? "") !== "")
2117
+ ];
2118
+ if (set.length === 0) return;
2119
+ 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.`);
2120
+ }
2341
2121
  function byKey(given) {
2342
- const text2 = given.key ?? env3("CAUTHY_KEY");
2343
- if (text2 === void 0 || text2 === "") return void 0;
2122
+ const text = given.key ?? env3("CAUTHY_KEY");
2123
+ if (text === void 0 || text === "") return void 0;
2344
2124
  refuseBesideKey(typeof process !== "undefined" ? process.env : void 0);
2345
2125
  for (const [name, value] of [
2346
2126
  ["issuer", given.issuer],
@@ -2351,12 +2131,11 @@ function byKey(given) {
2351
2131
  ["tenant", given.tenant],
2352
2132
  ["audience", given.audience],
2353
2133
  ["tenantId", given.tenantId],
2354
- ["systemKey", given.systemKey],
2355
- ["rootKey", given.rootKey]
2134
+ ["systemKey", given.systemKey]
2356
2135
  ]) {
2357
2136
  if (value !== void 0) throw new CauthyConfigError(`with CAUTHY_KEY, ${name} comes from CAUTHY_KEY: do not pass it`);
2358
2137
  }
2359
- const key = parseKey(text2);
2138
+ const key = parseKey(text);
2360
2139
  const prevText = given.keyPrevious ?? env3("CAUTHY_KEY_PREVIOUS");
2361
2140
  let previous;
2362
2141
  if (prevText) {
@@ -2371,13 +2150,8 @@ function byKey(given) {
2371
2150
  throw new CauthyKeyError("key_previous_mismatch", "CAUTHY_KEY_PREVIOUS names another Cauthy or client than CAUTHY_KEY");
2372
2151
  }
2373
2152
  const legacySecret = env3("CAUTHY_SECRET") || void 0;
2374
- const legacyRoot = env3("PACT_ROOT_KEY") || void 0;
2375
2153
  if (legacySecret) deprecated("CAUTHY_SECRET beside CAUTHY_KEY", "It is read only for sessions made before the move: remove it after the rotation window.");
2376
- 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.");
2377
- if (given.keys && !(legacyRoot && given.contract)) {
2378
- 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)");
2379
- }
2380
- return { key, previous, legacySecret, legacyRoot };
2154
+ return { key, previous, legacySecret };
2381
2155
  }
2382
2156
  var UnknownOriginError = class extends Error {
2383
2157
  constructor(origin, registered) {
@@ -2388,72 +2162,41 @@ var UnknownOriginError = class extends Error {
2388
2162
  origin;
2389
2163
  registered;
2390
2164
  };
2391
- function contractOnKey(given, keyed) {
2392
- const contract = readContract(given.contract);
2393
- if (contract.issuer !== keyed.key.issuer) {
2394
- 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`);
2395
- }
2396
- return contract;
2397
- }
2398
- function byContract(given) {
2399
- failClosed(typeof process !== "undefined" ? process.env : void 0);
2400
- for (const [name, value] of [
2401
- ["clientId", given.clientId],
2402
- ["clientSecret", given.clientSecret],
2403
- ["secret", given.secret],
2404
- ["system", given.system],
2405
- ["tenant", given.tenant],
2406
- ["systemKey", given.systemKey]
2407
- ]) {
2408
- if (value !== void 0) throw new CauthyConfigError(`with a contract, ${name} comes from the contract: do not pass it`);
2409
- }
2410
- const contract = readContract(given.contract);
2411
- const root = given.rootKey ?? env3("PACT_ROOT_KEY") ?? "";
2412
- if (root === "") throw new CauthyConfigError("rootKey (PACT_ROOT_KEY) is required with a contract: 32 random bytes, e.g. openssl rand -base64 32");
2413
- if (contract.serviceClientId && !given.keys) {
2414
- throw new CauthyConfigError(`the link ${contract.link} gives ${contract.system} its own identity: pass keys, where its key is kept (@cauthyhq/node/keys)`);
2415
- }
2416
- 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.");
2417
- return { contract, root, previous: given.rootKeyPrevious ?? env3("PACT_ROOT_KEY_PREVIOUS") };
2418
- }
2419
2165
  var toldOfHeader = false;
2166
+ var toldOfUnopened = false;
2167
+ function toldUnopened() {
2168
+ if (toldOfUnopened) return;
2169
+ toldOfUnopened = true;
2170
+ 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.");
2171
+ }
2420
2172
  function createCauthy(given = {}) {
2173
+ refuseContracts(given);
2421
2174
  const keyed = byKey(given);
2422
- const pacted = given.contract && !keyed ? byContract(given) : void 0;
2423
- const contract = keyed && given.contract ? contractOnKey(given, keyed) : pacted?.contract;
2424
- const issuer = withoutTrailingSlashes(keyed?.key.issuer ?? given.issuer ?? contract?.issuer ?? env3("CAUTHY_ISSUER") ?? "");
2425
- const clientId = contract?.clientId ?? keyed?.key.clientId ?? given.clientId ?? env3("CAUTHY_CLIENT_ID") ?? "";
2426
- const clientSecret = contract || keyed ? void 0 : given.clientSecret ?? env3("CAUTHY_CLIENT_SECRET");
2427
- const secret = contract || keyed ? "" : given.secret ?? env3("CAUTHY_SECRET") ?? "";
2428
- const askSelf = keyed !== void 0 && contract === void 0;
2429
- const system = askSelf ? "" : contract?.system ?? given.system ?? env3("CAUTHY_SYSTEM") ?? "";
2430
- const tenant = askSelf ? "" : contract?.tenant ?? given.tenant ?? env3("CAUTHY_TENANT") ?? "";
2175
+ const issuer = withoutTrailingSlashes(keyed?.key.issuer ?? given.issuer ?? env3("CAUTHY_ISSUER") ?? "");
2176
+ const clientId = keyed?.key.clientId ?? given.clientId ?? env3("CAUTHY_CLIENT_ID") ?? "";
2177
+ const clientSecret = keyed ? void 0 : given.clientSecret ?? env3("CAUTHY_CLIENT_SECRET");
2178
+ const secret = keyed ? "" : given.secret ?? env3("CAUTHY_SECRET") ?? "";
2179
+ const system = keyed ? "" : given.system ?? env3("CAUTHY_SYSTEM") ?? "";
2180
+ const tenant = keyed ? "" : given.tenant ?? env3("CAUTHY_TENANT") ?? "";
2431
2181
  if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address, e.g. https://id.example.com");
2432
2182
  if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
2433
- if (!contract && !keyed && secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
2183
+ if (!keyed && secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
2434
2184
  const fromKey = keyed ? deriveFromKey(keyed.key, keyed.previous) : void 0;
2435
2185
  let signingKid;
2436
2186
  fromKey?.signer.then((s) => {
2437
2187
  signingKid = s.kid;
2438
2188
  }, () => void 0);
2439
- const derived = pacted ? deriveKeys(pacted.root, pacted.previous) : void 0;
2440
- const single = derived || fromKey ? void 0 : keyOf(secret).then((k) => [k]);
2441
- const legacyRings = keyed?.legacyRoot ? deriveKeys(keyed.legacyRoot) : void 0;
2442
2189
  const legacySingle = keyed?.legacySecret ? keyOf(keyed.legacySecret) : void 0;
2443
- const withLegacy = async (own, purpose) => [
2444
- ...await own,
2445
- ...legacyRings ? [(await legacyRings[purpose])[0]] : [],
2446
- ...legacySingle ? [await legacySingle] : []
2447
- ];
2448
- const cookieKeys = fromKey ? withLegacy(fromKey.cookie, "cookie") : derived?.cookie ?? single;
2449
- const flowKeys = fromKey ? withLegacy(fromKey.flow, "flow") : derived?.flow ?? single;
2450
- if (!keyed && !pacted) {
2190
+ const withLegacy = async (own) => [...await own, ...legacySingle ? [await legacySingle] : []];
2191
+ const single = fromKey ? Promise.resolve([]) : keyOf(secret).then((k) => [k]);
2192
+ const cookieKeys = fromKey ? withLegacy(fromKey.cookie) : single;
2193
+ const flowKeys = fromKey ? withLegacy(fromKey.flow) : single;
2194
+ if (!keyed) {
2451
2195
  deprecated(
2452
2196
  "Configuring with CAUTHY_ISSUER, CAUTHY_CLIENT_ID, CAUTHY_CLIENT_SECRET, CAUTHY_SECRET and the other CAUTHY_* variables (or their options)",
2453
2197
  "Connect the application in Cauthy's dashboard and use the one variable CAUTHY_KEY."
2454
2198
  );
2455
2199
  }
2456
- const ownKeys = derived && given.keys ? pactKeys(given.keys, derived.seal) : legacyRings && contract && given.keys ? pactKeys(given.keys, legacyRings.seal) : void 0;
2457
2200
  const baseURL = given.baseURL ?? (keyed ? void 0 : env3("CAUTHY_BASE_URL"));
2458
2201
  if (baseURL !== void 0 && baseURL !== "" && !/^https?:\/\/[^/]+\/*$/.test(baseURL)) {
2459
2202
  throw new CauthyConfigError("baseURL (CAUTHY_BASE_URL) must be the application's public origin, e.g. https://app.example.com");
@@ -2490,21 +2233,10 @@ function createCauthy(given = {}) {
2490
2233
  acceptInviteUrl,
2491
2234
  fetch: given.fetch,
2492
2235
  timeoutMs: systemTimeout,
2493
- ...ownKeys ? { keys: ownKeys, keysClientId: contract?.serviceClientId } : {},
2494
- // With a contract the key proves the link's service client; without, the key's own client.
2495
- ...fromKey ? {
2496
- signer: { clientId: contract ? contract.serviceClientId ?? keyed.key.clientId : clientId, signer: fromKey.signer },
2497
- currentSystem: () => identity?.current()?.system ?? ""
2498
- } : {}
2236
+ // On CAUTHY_KEY the key proves the key's own client.
2237
+ ...fromKey ? { signer: { clientId, signer: fromKey.signer }, currentSystem: () => identity?.current()?.system ?? "" } : {}
2499
2238
  });
2500
- const selfOptions = () => ({
2501
- issuer,
2502
- token: () => ownSystem.token(),
2503
- fetch: deadline(given.fetch, requestTimeout),
2504
- kid: () => signingKid,
2505
- log: (m) => console.error(`@cauthyhq/node: ${m}`)
2506
- });
2507
- const identity = askSelf ? createIdentity({
2239
+ const identity = keyed ? createIdentity({
2508
2240
  issuer,
2509
2241
  token: () => ownSystem.token(),
2510
2242
  fetch: deadline(given.fetch, requestTimeout),
@@ -2514,6 +2246,8 @@ function createCauthy(given = {}) {
2514
2246
  identity?.ready().catch(() => {
2515
2247
  });
2516
2248
  const tenantNow = () => identity?.current()?.tenant.slug ?? tenant;
2249
+ const signInClientNow = () => identity?.current()?.sign_in_client_id || clientId;
2250
+ const signInClient = async () => identity ? (await identity.ready()).sign_in_client_id || clientId : clientId;
2517
2251
  const systemNow = () => identity?.current()?.system ?? system;
2518
2252
  const requestOrigin = (request) => {
2519
2253
  const url = new URL(request.url);
@@ -2538,7 +2272,7 @@ function createCauthy(given = {}) {
2538
2272
  };
2539
2273
  const attrs = (request, maxAge) => ({ secure: safeOrigin(request).startsWith("https://"), maxAge });
2540
2274
  async function clientAuth(form) {
2541
- if (fromKey && !contract) {
2275
+ if (fromKey && form.get("client_id") === clientId) {
2542
2276
  const s = await fromKey.signer;
2543
2277
  form.set("client_assertion_type", ASSERTION_TYPE2);
2544
2278
  form.set("client_assertion", await s.assertion(clientId, `${issuer}/v1/oauth/token`));
@@ -2548,8 +2282,8 @@ function createCauthy(given = {}) {
2548
2282
  return form;
2549
2283
  }
2550
2284
  async function token(form, proof = {}) {
2551
- const body2 = await clientAuth(new URLSearchParams({ client_id: clientId, ...form }));
2552
2285
  try {
2286
+ const body2 = await clientAuth(new URLSearchParams({ client_id: await signInClient(), ...form }));
2553
2287
  const res = await doFetch(`${issuer}/v1/oauth/token`, {
2554
2288
  method: "POST",
2555
2289
  headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json", ...proof },
@@ -2587,18 +2321,16 @@ function createCauthy(given = {}) {
2587
2321
  }
2588
2322
  const kindOf = (body2, was) => (typeof body2?.persistent === "boolean" ? body2.persistent : was?.p === 1) ? { p: 1 } : {};
2589
2323
  async function revoke(refreshToken) {
2590
- const form = await clientAuth(new URLSearchParams({ client_id: clientId, token: refreshToken }));
2591
- await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form }).catch(() => void 0);
2324
+ try {
2325
+ const form = await clientAuth(new URLSearchParams({ client_id: await signInClient(), token: refreshToken }));
2326
+ await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form });
2327
+ } catch {
2328
+ }
2592
2329
  }
2593
- const unverified = (u) => contract?.requireVerifiedEmail === true && !u.emailVerified;
2594
2330
  async function stored(body2, was) {
2595
2331
  if (typeof body2?.access_token !== "string" || typeof body2?.refresh_token !== "string") return null;
2596
2332
  const who = await userinfo(body2.access_token);
2597
2333
  if (who === null || who.u.id === "") return null;
2598
- if (unverified(who.u)) {
2599
- await revoke(body2.refresh_token);
2600
- throw new CauthyApiError(403, "email_not_confirmed", "Confirm your email address first.");
2601
- }
2602
2334
  const lifetime = typeof body2.expires_in === "number" && body2.expires_in > 0 ? body2.expires_in : 300;
2603
2335
  return { at: body2.access_token, rt: body2.refresh_token, exp: Math.floor(Date.now() / 1e3) + lifetime, ...who, ...kindOf(body2, was) };
2604
2336
  }
@@ -2608,10 +2340,11 @@ function createCauthy(given = {}) {
2608
2340
  async function read(request) {
2609
2341
  const had = readCookies(request.headers.get("cookie"));
2610
2342
  const raw = readParts(had, cookieName);
2611
- if (raw === null) return { had, value: null, present: false };
2612
- const value = await unsealAny(raw, await cookieKeys);
2343
+ if (raw === null) return { had, value: null, present: false, stale: false };
2344
+ const opened = await unsealIndexed(raw, await cookieKeys);
2345
+ const value = opened?.value ?? null;
2613
2346
  const sound = value !== null && typeof value.at === "string" && typeof value.rt === "string" && typeof value.exp === "number";
2614
- return { had, value: sound ? value : null, present: true };
2347
+ return { had, value: sound ? value : null, present: true, stale: sound && opened.index > 0 };
2615
2348
  }
2616
2349
  async function write(request, s, had) {
2617
2350
  return writeParts(cookieName, await seal(s, (await cookieKeys)[0]), attrs(request, s.p === 1 ? PERSISTENT_SECONDS : void 0), had);
@@ -2625,13 +2358,7 @@ function createCauthy(given = {}) {
2625
2358
  running = (async () => {
2626
2359
  const res = await token({ grant_type: "refresh_token", refresh_token: rt });
2627
2360
  if (!res.ok) return res.ended ? "ended" : "unreachable";
2628
- let next;
2629
- try {
2630
- next = await stored(res.body, was);
2631
- } catch (err) {
2632
- if (err instanceof CauthyApiError && err.code === "email_not_confirmed") return "ended";
2633
- throw err;
2634
- }
2361
+ const next = await stored(res.body, was);
2635
2362
  if (next !== null) return next;
2636
2363
  if (typeof res.body?.access_token !== "string" || typeof res.body?.refresh_token !== "string") return "unreachable";
2637
2364
  const lifetime = typeof res.body.expires_in === "number" && res.body.expires_in > 0 ? res.body.expires_in : 300;
@@ -2648,12 +2375,12 @@ function createCauthy(given = {}) {
2648
2375
  return running;
2649
2376
  }
2650
2377
  async function resolve2(request) {
2651
- const { had, value, present } = await read(request);
2378
+ const { had, value, present, stale } = await read(request);
2652
2379
  if (value === null) {
2380
+ if (present) toldUnopened();
2653
2381
  return { session: null, status: "unauthenticated", cookies: present ? clearParts(cookieName, attrs(request, 0), had) : [] };
2654
2382
  }
2655
- if (unverified(value.u)) return { session: null, status: "unauthenticated", cookies: clearParts(cookieName, attrs(request, 0), had) };
2656
- if (fresh(value)) return { session: toSession(value), status: "authenticated", cookies: [] };
2383
+ if (fresh(value)) return { session: toSession(value), status: "authenticated", cookies: stale ? await write(request, value, had) : [] };
2657
2384
  const renewed = await renew(value);
2658
2385
  if (renewed === "ended") {
2659
2386
  return { session: null, status: "unauthenticated", cookies: clearParts(cookieName, attrs(request, 0), had) };
@@ -2664,7 +2391,7 @@ function createCauthy(given = {}) {
2664
2391
  async function getSession(request, responseHeaders) {
2665
2392
  if (responseHeaders === void 0) {
2666
2393
  const { value } = await read(request);
2667
- return value !== null && fresh(value) && !unverified(value.u) ? toSession(value) : null;
2394
+ return value !== null && fresh(value) ? toSession(value) : null;
2668
2395
  }
2669
2396
  const r = await resolve2(request);
2670
2397
  for (const c of r.cookies) responseHeaders.append("set-cookie", c);
@@ -2672,7 +2399,7 @@ function createCauthy(given = {}) {
2672
2399
  }
2673
2400
  async function status(request) {
2674
2401
  const { value } = await read(request);
2675
- if (value === null || unverified(value.u)) return "unauthenticated";
2402
+ if (value === null) return "unauthenticated";
2676
2403
  return fresh(value) ? "authenticated" : "expired";
2677
2404
  }
2678
2405
  const orgCache = /* @__PURE__ */ new Map();
@@ -2759,7 +2486,7 @@ function createCauthy(given = {}) {
2759
2486
  const flow = { state: random(), verifier: random(48), returnTo: localPath(url.searchParams.get("return_to"), afterSignIn) };
2760
2487
  const challenge = b64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(flow.verifier))));
2761
2488
  const q = new URLSearchParams({
2762
- client_id: clientId,
2489
+ client_id: await signInClient(),
2763
2490
  redirect_uri: `${originOf(request)}${basePath}/callback`,
2764
2491
  response_type: "code",
2765
2492
  state: flow.state,
@@ -2821,28 +2548,17 @@ function createCauthy(given = {}) {
2821
2548
  );
2822
2549
  };
2823
2550
  if (!res.ok && res.code === "email_not_confirmed") return unconfirmed();
2824
- let s;
2825
- try {
2826
- s = res.ok ? await stored(res.body) : null;
2827
- } catch (err) {
2828
- if (!(err instanceof CauthyApiError && err.code === "email_not_confirmed")) throw err;
2829
- return unconfirmed();
2830
- }
2551
+ const s = res.ok ? await stored(res.body) : null;
2831
2552
  if (s === null) return failed(request, "exchange_failed");
2832
2553
  return redirect(flow.returnTo, [...await write(request, s, had), writeCookie(flowCookie, "", attrs(request, 0)), writeCookie(againCookie, "", attrs(request, 0))]);
2833
2554
  }
2834
2555
  async function logout(request) {
2835
2556
  const { had, value } = await read(request);
2836
2557
  if (value !== null) {
2837
- const body2 = await clientAuth(new URLSearchParams({ client_id: clientId, token: value.rt }));
2838
- await doFetch(`${issuer}/v1/oauth/revoke`, {
2839
- method: "POST",
2840
- headers: { "content-type": "application/x-www-form-urlencoded" },
2841
- body: body2
2842
- }).catch(() => void 0);
2558
+ await revoke(value.rt);
2843
2559
  orgCache.delete(value.at);
2844
2560
  }
2845
- const q = new URLSearchParams({ client_id: clientId, post_logout_redirect_uri: `${originOf(request)}${afterSignOut}` });
2561
+ const q = new URLSearchParams({ client_id: signInClientNow(), post_logout_redirect_uri: `${originOf(request)}${afterSignOut}` });
2846
2562
  return redirect(`${issuer}/v1/oauth/logout?${q}`, clearParts(cookieName, attrs(request, 0), had));
2847
2563
  }
2848
2564
  const STEP_SECONDS = 600;
@@ -2860,7 +2576,7 @@ function createCauthy(given = {}) {
2860
2576
  createdAt: String(d?.created_at ?? ""),
2861
2577
  lastUsedAt: d?.last_used_at ? String(d.last_used_at) : null
2862
2578
  });
2863
- const UUID3 = /^[0-9a-f-]{36}$/i;
2579
+ const UUID2 = /^[0-9a-f-]{36}$/i;
2864
2580
  const seatOf = (d) => ({
2865
2581
  id: String(d?.id ?? ""),
2866
2582
  system: String(d?.system ?? ""),
@@ -2965,10 +2681,10 @@ function createCauthy(given = {}) {
2965
2681
  ...page ? { "x-cauthy-link-to": `${originOf(request)}${page}` } : { "x-cauthy-links": "hosted" },
2966
2682
  // Whose request it is: what Cauthy mails goes in this application's
2967
2683
  // name, under its logo.
2968
- ...clientId ? { "x-cauthy-client-id": clientId } : {},
2684
+ ...signInClientNow() ? { "x-cauthy-client-id": signInClientNow() } : {},
2969
2685
  ...await passedOn(request),
2970
2686
  ...bearer2 ? { authorization: `Bearer ${bearer2}` } : {},
2971
- // The person asked to stay signed in: Cauthy keeps the session long if the client allows it.
2687
+ // The person asked to stay signed in: Cauthy keeps the session long if the workspace allows it.
2972
2688
  ...remember ? { "x-cauthy-remember": "1" } : {},
2973
2689
  // The answer to the workspace's Turnstile challenge, from the
2974
2690
  // person's browser: Cauthy checks it at the doors that ask for it.
@@ -3109,7 +2825,7 @@ function createCauthy(given = {}) {
3109
2825
  }
3110
2826
  if (route === "/look" && !post) {
3111
2827
  if (lookCache === null || lookCache.until < Date.now()) {
3112
- const res = await ask(request, "GET", `/v1/auth/look/${encodeURIComponent(clientId)}`);
2828
+ const res = await ask(request, "GET", `/v1/auth/look/${encodeURIComponent(signInClientNow())}`);
3113
2829
  lookCache = { until: Date.now() + LOOK_TTL_MS, look: res.error ? {} : res.data ?? {} };
3114
2830
  }
3115
2831
  return json(lookCache.look);
@@ -3134,7 +2850,7 @@ function createCauthy(given = {}) {
3134
2850
  signUp: settings.data?.allow_signup !== false && settings.data?.signup?.mode !== "invite_only",
3135
2851
  // The application's system makes an organization per sign-up: the form asks the company's name.
3136
2852
  ...settings.data?.signup?.organization?.ask === true ? { organization: { ask: true, required: settings.data.signup.organization.required === true } } : {},
3137
- // Whether the person may ask to stay signed in: the client's choice in Cauthy.
2853
+ // Whether the person may ask to stay signed in: the workspace's setting in Cauthy (persistent_sessions).
3138
2854
  ...typeof settings.data?.persistent_sessions === "boolean" ? { persistentSessions: settings.data.persistent_sessions } : {},
3139
2855
  // A sign-in link needs a page of the application to open.
3140
2856
  magicLink: settings.data?.allow_magic_link === true && pages.signIn !== void 0,
@@ -3298,7 +3014,7 @@ function createCauthy(given = {}) {
3298
3014
  let path = "/v1/auth/me/organizations";
3299
3015
  if (route !== "/me/organizations") {
3300
3016
  const id = url.searchParams.get("id") ?? "";
3301
- if (!UUID3.test(id)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
3017
+ if (!UUID2.test(id)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
3302
3018
  path += `/${id}/${route.endsWith("/roles") ? "roles" : "members"}`;
3303
3019
  } else if (url.searchParams.get("system")) {
3304
3020
  path += `?system=${encodeURIComponent(url.searchParams.get("system"))}`;
@@ -3359,8 +3075,7 @@ function createCauthy(given = {}) {
3359
3075
  if (route === "/sign-out") {
3360
3076
  const { had, value } = await read(request);
3361
3077
  if (value !== null) {
3362
- const form = await clientAuth(new URLSearchParams({ client_id: clientId, token: value.rt }));
3363
- await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form }).catch(() => void 0);
3078
+ await revoke(value.rt);
3364
3079
  orgCache.delete(value.at);
3365
3080
  }
3366
3081
  const h = new Headers();
@@ -3587,7 +3302,7 @@ function createCauthy(given = {}) {
3587
3302
  }
3588
3303
  if (route === "/me/organizations/rename" || route === "/me/organizations/archive" || route === "/me/organizations/delete") {
3589
3304
  const org = typeof b.organizationId === "string" ? b.organizationId : "";
3590
- if (!UUID3.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
3305
+ if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
3591
3306
  const path = `/v1/auth/me/organizations/${org}`;
3592
3307
  if (route === "/me/organizations/rename") {
3593
3308
  const bad = checkOrganizationName(b.name);
@@ -3604,7 +3319,7 @@ function createCauthy(given = {}) {
3604
3319
  }
3605
3320
  if (route.startsWith("/me/organizations/members/")) {
3606
3321
  const org = typeof b.organizationId === "string" ? b.organizationId : "";
3607
- if (!UUID3.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
3322
+ if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
3608
3323
  const base = `/v1/auth/me/organizations/${org}/members`;
3609
3324
  if (route === "/me/organizations/members/add") {
3610
3325
  const bad = checkEmail(b.email);
@@ -3615,7 +3330,7 @@ function createCauthy(given = {}) {
3615
3330
  email: String(b.email).trim(),
3616
3331
  role,
3617
3332
  name: typeof b.name === "string" ? b.name.trim() : "",
3618
- client_id: clientId,
3333
+ client_id: signInClientNow(),
3619
3334
  only_new: true,
3620
3335
  ...typeof b.locale === "string" && b.locale ? { locale: b.locale } : {}
3621
3336
  }, pages.acceptInvite, mine.token);
@@ -3624,7 +3339,7 @@ function createCauthy(given = {}) {
3624
3339
  return json({ status: "added", member: orgMemberOf(res.data) }, 200, mine.headers);
3625
3340
  }
3626
3341
  const user = typeof b.userId === "string" ? b.userId : "";
3627
- if (!UUID3.test(user)) return problem(400, "invalid_member", "Say which member.", void 0, mine.headers);
3342
+ if (!UUID2.test(user)) return problem(400, "invalid_member", "Say which member.", void 0, mine.headers);
3628
3343
  if (route === "/me/organizations/members/role") {
3629
3344
  const role = typeof b.role === "string" ? b.role : "";
3630
3345
  if (role === "") return problem(400, "invalid_role", "Choose a role.", "role", mine.headers);
@@ -3639,7 +3354,7 @@ function createCauthy(given = {}) {
3639
3354
  return json({ status: "removed", userId: user }, 200, mine.headers);
3640
3355
  }
3641
3356
  if (route === "/me/organizations/members/invite") {
3642
- 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);
3357
+ 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);
3643
3358
  if (res.error) return refused(res);
3644
3359
  return json({ status: "sent", userId: user }, 200, mine.headers);
3645
3360
  }
@@ -3757,18 +3472,15 @@ function createCauthy(given = {}) {
3757
3472
  const url = new URL(request.url);
3758
3473
  const inside = url.pathname === basePath || url.pathname.startsWith(`${basePath}/`);
3759
3474
  const route = inside ? withoutTrailingSlashes(url.pathname.slice(basePath.length)) || "/" : null;
3760
- const published3 = pact !== void 0 && url.pathname === contract?.jwksPath && (request.method === "GET" || request.method === "HEAD");
3761
3475
  if (identity && route !== null) {
3762
3476
  const unready = await identityGate();
3763
3477
  if (unready) return finish(unready, route);
3764
3478
  }
3765
3479
  try {
3766
- return finish(await serve(request), route, published3);
3480
+ return finish(await serve(request), route);
3767
3481
  } catch (err) {
3768
3482
  if (err instanceof UnknownOriginError) return finish(problem(400, "unknown_origin", err.message), route);
3769
- if (!(err instanceof CauthyApiError && err.code === "email_not_confirmed")) throw err;
3770
- const wantsPage = request.method === "GET" && (request.headers.get("sec-fetch-mode") ?? "navigate") === "navigate";
3771
- 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);
3483
+ throw err;
3772
3484
  }
3773
3485
  }
3774
3486
  async function identityGate() {
@@ -3792,7 +3504,6 @@ function createCauthy(given = {}) {
3792
3504
  }
3793
3505
  async function serve(request) {
3794
3506
  const url = new URL(request.url);
3795
- if (pact && url.pathname === contract?.jwksPath && (request.method === "GET" || request.method === "HEAD")) return pact.jwks();
3796
3507
  if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) return new Response("Not found", { status: 404 });
3797
3508
  const route = withoutTrailingSlashes(url.pathname.slice(basePath.length)) || "/";
3798
3509
  const get = request.method === "GET" || request.method === "HEAD";
@@ -3816,7 +3527,7 @@ function createCauthy(given = {}) {
3816
3527
  return redirect(back, r.cookies);
3817
3528
  }
3818
3529
  if (route === "/account" && get) {
3819
- const q = new URLSearchParams({ client_id: clientId });
3530
+ const q = new URLSearchParams({ client_id: signInClientNow() });
3820
3531
  const back = localPath(url.searchParams.get("return_to"), "");
3821
3532
  if (back !== "") q.set("return_to", `${originOf(request)}${back}`);
3822
3533
  const locale = url.searchParams.get("locale");
@@ -3835,7 +3546,7 @@ function createCauthy(given = {}) {
3835
3546
  const asker = await signedInAs(request);
3836
3547
  if ("response" in asker) return asker.response;
3837
3548
  const org = typeof b.organizationId === "string" ? b.organizationId : "";
3838
- if (!UUID3.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, asker.headers);
3549
+ if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, asker.headers);
3839
3550
  const named = (v) => typeof v === "string" && v !== "" && v.length <= 200;
3840
3551
  if (!named(b.resource) || !named(b.action)) return problem(400, "bad_request", "Say which resource and action.", void 0, asker.headers);
3841
3552
  const attributes2 = b.attributes;
@@ -3858,8 +3569,7 @@ function createCauthy(given = {}) {
3858
3569
  }
3859
3570
  }
3860
3571
  async function protect(request) {
3861
- const read1 = await read(request);
3862
- const value = read1.value !== null && unverified(read1.value.u) ? null : read1.value;
3572
+ const { value } = await read(request);
3863
3573
  if (value !== null && fresh(value)) return { session: toSession(value) };
3864
3574
  const url = new URL(request.url);
3865
3575
  const state = value === null ? "none" : "expired";
@@ -3872,85 +3582,36 @@ function createCauthy(given = {}) {
3872
3582
  return { response: redirect(to) };
3873
3583
  }
3874
3584
  const replay = given.webhookReplay ?? memoryReplayStore();
3875
- let lateSelf;
3876
3585
  const tokenVerifier = (o = {}) => {
3877
- if (keyed) {
3878
- if (contract && o.clientId !== void 0 && o.clientId !== contract.clientId) {
3879
- throw new CauthyConfigError(`clientId is the contract's (${contract.clientId}): remove it from the call`);
3880
- }
3586
+ if (identity) {
3881
3587
  let built;
3882
3588
  return async (from) => {
3883
- const id = await (identity ?? (lateSelf ??= createIdentity(selfOptions()))).ready();
3589
+ const id = await identity.ready();
3884
3590
  if (built?.from !== id) {
3885
- built = { from: id, verify: createTokenVerifier({
3886
- issuer,
3887
- tenantId: contract?.tenantId ?? id.tenant.id,
3888
- audience: id.audience,
3889
- fetch: given.fetch,
3890
- ...contract ? { clientId: contract.clientId } : {},
3891
- ...o
3892
- }) };
3591
+ built = { from: id, verify: createTokenVerifier({ issuer, tenantId: id.tenant.id, audience: id.audience, fetch: given.fetch, ...o }) };
3893
3592
  }
3894
3593
  return built.verify(from);
3895
3594
  };
3896
3595
  }
3897
- if (contract && o.clientId !== void 0 && o.clientId !== contract.clientId) {
3898
- throw new CauthyConfigError(`clientId is the contract's (${contract.clientId}): remove it from the call`);
3899
- }
3900
3596
  return createTokenVerifier({
3901
3597
  issuer,
3902
- tenantId: contract?.tenantId ?? given.tenantId,
3598
+ tenantId: given.tenantId,
3903
3599
  audience: given.audience,
3904
3600
  fetch: given.fetch,
3905
- ...contract ? { clientId: contract.clientId } : {},
3906
3601
  ...o
3907
3602
  });
3908
3603
  };
3909
3604
  const webhook = (request, o = {}) => verifyWebhook(request, { issuer, url: o.url ?? `${originOf(request)}${new URL(request.url).pathname}`, jwks: o.jwks, now: o.now, replay });
3910
- async function keyJwks() {
3911
- const s = await fromKey.signer;
3912
- const keys2 = [{ ...s.publicJwk, kid: s.kid, alg: "EdDSA", use: "sig" }];
3913
- if (fromKey.previousJwk) keys2.push({ ...await fromKey.previousJwk, alg: "EdDSA", use: "sig" });
3914
- if (ownKeys) keys2.push(...(await ownKeys.published()).keys);
3915
- return new Response(JSON.stringify({ keys: keys2 }), {
3916
- headers: { "content-type": "application/json; charset=utf-8", "cache-control": `public, max-age=${JWKS_MAX_AGE_S}` }
3917
- });
3918
- }
3919
- const noStore = () => {
3920
- 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");
3921
- };
3922
- const keyOnly = {
3923
- current: async () => noStore(),
3924
- published: async () => ({ keys: [] }),
3925
- rotate: async () => noStore(),
3926
- reseal: async () => 0
3927
- };
3928
- const pact = contract && fromKey ? {
3929
- contract,
3930
- keys: ownKeys ?? keyOnly,
3931
- jwks: keyJwks,
3932
- status: () => ownSystem.request("GET", `/v1/pact/status?system=${encodeURIComponent(contract.system)}`)
3933
- } : contract && ownKeys ? {
3934
- contract,
3935
- keys: ownKeys,
3936
- async jwks() {
3937
- return new Response(JSON.stringify(await ownKeys.published()), {
3938
- headers: { "content-type": "application/json; charset=utf-8", "cache-control": `public, max-age=${JWKS_MAX_AGE_S}` }
3939
- });
3940
- },
3941
- status: () => ownSystem.request("GET", `/v1/pact/status?system=${encodeURIComponent(contract.system)}`)
3942
- } : void 0;
3943
3605
  const configured = () => ({
3944
3606
  issuer,
3945
3607
  audience: given.audience ?? env3("CAUTHY_AUDIENCE") ?? "",
3946
- tenant: { id: contract?.tenantId ?? given.tenantId ?? env3("CAUTHY_TENANT_ID") ?? "", slug: tenant },
3608
+ tenant: { id: given.tenantId ?? env3("CAUTHY_TENANT_ID") ?? "", slug: tenant },
3947
3609
  system,
3948
3610
  client_id: clientId,
3949
3611
  sign_in_client_id: clientId,
3950
3612
  app_urls: baseURL ? [withoutTrailingSlashes(baseURL)] : [],
3951
3613
  callback_path: `${basePath}/callback`,
3952
- key: null,
3953
- contract: contract ? { link: contract.link, system_proof: contract.systemProof, require_verified_email: contract.requireVerifiedEmail } : null
3614
+ key: null
3954
3615
  });
3955
3616
  return {
3956
3617
  handler,
@@ -3972,7 +3633,6 @@ function createCauthy(given = {}) {
3972
3633
  check,
3973
3634
  toPublic,
3974
3635
  urls,
3975
- ...pact ? { pact } : {},
3976
3636
  options: { issuer, clientId, basePath, afterSignIn, afterSignOut, cookieName }
3977
3637
  };
3978
3638
  }
@@ -4057,7 +3717,6 @@ async function connectSystem(o) {
4057
3717
  KEY_REPLACES,
4058
3718
  MIN_PASSWORD,
4059
3719
  PROTOCOL_VERSION,
4060
- STANDARD_VARIABLES,
4061
3720
  can,
4062
3721
  checkCode,
4063
3722
  checkEmail,
@@ -4073,7 +3732,6 @@ async function connectSystem(o) {
4073
3732
  definePolicy,
4074
3733
  describePasswordPolicy,
4075
3734
  explainPasswordProblems,
4076
- failClosed,
4077
3735
  memoryReplayStore,
4078
3736
  parseKey,
4079
3737
  passwordPolicyOf,
@@ -4084,7 +3742,6 @@ async function connectSystem(o) {
4084
3742
  pushCatalog,
4085
3743
  pushPolicy,
4086
3744
  pushTheme,
4087
- readContract,
4088
3745
  remoteKeys,
4089
3746
  themeFromStackmyth,
4090
3747
  tokensFromStackmyth,