@cauthyhq/node 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -7
- package/README.md +10 -29
- package/dist/{cauthy-CCSSj3Lg.d.mts → cauthy-DHRZLndK.d.mts} +31 -147
- package/dist/{cauthy-C7gZ-pbv.d.ts → cauthy-JSYh7J2o.d.ts} +31 -147
- package/dist/express.d.mts +2 -5
- package/dist/express.d.ts +2 -5
- package/dist/express.js +1 -2
- package/dist/express.js.map +1 -1
- package/dist/express.mjs +1 -2
- package/dist/express.mjs.map +1 -1
- package/dist/fastify.d.mts +1 -3
- package/dist/fastify.d.ts +1 -3
- package/dist/fastify.js +0 -2
- package/dist/fastify.js.map +1 -1
- package/dist/fastify.mjs +0 -2
- package/dist/fastify.mjs.map +1 -1
- package/dist/hono.d.mts +2 -5
- package/dist/hono.d.ts +2 -5
- package/dist/hono.js +1 -2
- package/dist/hono.js.map +1 -1
- package/dist/hono.mjs +1 -2
- package/dist/hono.mjs.map +1 -1
- package/dist/index.d.mts +1 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +159 -524
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +152 -514
- package/dist/index.mjs.map +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.mjs.map +1 -1
- package/dist/next.d.mts +1 -3
- package/dist/next.d.ts +1 -3
- package/dist/testing.d.mts +11 -5
- package/dist/testing.d.ts +11 -5
- package/dist/testing.js +51 -49
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +51 -49
- package/dist/testing.mjs.map +1 -1
- package/package.json +3 -15
- package/dist/identity-BTJ8NB_w.d.mts +0 -27
- package/dist/identity-BTJ8NB_w.d.ts +0 -27
- package/dist/keys-D5k0omTb.d.mts +0 -64
- package/dist/keys-D5k0omTb.d.ts +0 -64
- package/dist/keys.d.mts +0 -8
- package/dist/keys.d.ts +0 -8
- package/dist/keys.js +0 -157
- package/dist/keys.js.map +0 -1
- package/dist/keys.mjs +0 -129
- 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,
|
|
@@ -132,261 +129,6 @@ async function unsealIndexed(sealed, ring) {
|
|
|
132
129
|
return null;
|
|
133
130
|
}
|
|
134
131
|
|
|
135
|
-
// src/types.ts
|
|
136
|
-
var CauthyConfigError = class extends Error {
|
|
137
|
-
constructor(message) {
|
|
138
|
-
super(`@cauthyhq/node: ${message}`);
|
|
139
|
-
this.name = "CauthyConfigError";
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
var CauthyKeyError = class extends CauthyConfigError {
|
|
143
|
-
constructor(reason, message) {
|
|
144
|
-
super(`CAUTHY_KEY: ${message} (${reason})`);
|
|
145
|
-
this.reason = reason;
|
|
146
|
-
this.name = "CauthyKeyError";
|
|
147
|
-
}
|
|
148
|
-
reason;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
// src/url.ts
|
|
152
|
-
function withoutTrailingSlashes(s) {
|
|
153
|
-
let end = s.length;
|
|
154
|
-
while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
|
|
155
|
-
return s.slice(0, end);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// src/contract.ts
|
|
159
|
-
var STANDARD_VARIABLES = [
|
|
160
|
-
"CAUTHY_ISSUER",
|
|
161
|
-
"CAUTHY_CLIENT_ID",
|
|
162
|
-
"CAUTHY_CLIENT_SECRET",
|
|
163
|
-
"CAUTHY_SECRET",
|
|
164
|
-
"CAUTHY_SYSTEM",
|
|
165
|
-
"CAUTHY_TENANT",
|
|
166
|
-
"CAUTHY_SYSTEM_KEY",
|
|
167
|
-
"CAUTHY_SYSTEM_KEY_ID"
|
|
168
|
-
];
|
|
169
|
-
function failClosed(environment) {
|
|
170
|
-
const set = STANDARD_VARIABLES.filter((name) => (environment?.[name] ?? "") !== "");
|
|
171
|
-
if (set.length > 0) {
|
|
172
|
-
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.");
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
var SLUG = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/;
|
|
176
|
-
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
177
|
-
var NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
178
|
-
var text = (v) => typeof v === "string" ? v : "";
|
|
179
|
-
function httpsAddress(raw, what) {
|
|
180
|
-
let u;
|
|
181
|
-
try {
|
|
182
|
-
u = new URL(raw);
|
|
183
|
-
} catch {
|
|
184
|
-
throw new CauthyConfigError(`the contract's ${what} is not an address: ${JSON.stringify(raw)}`);
|
|
185
|
-
}
|
|
186
|
-
const local = u.hostname === "localhost" || u.hostname === "127.0.0.1";
|
|
187
|
-
if (u.protocol !== "https:" && !(u.protocol === "http:" && local)) {
|
|
188
|
-
throw new CauthyConfigError(`the contract's ${what} must be https: ${raw}`);
|
|
189
|
-
}
|
|
190
|
-
return u;
|
|
191
|
-
}
|
|
192
|
-
function readContract(options) {
|
|
193
|
-
const { system } = options;
|
|
194
|
-
const provider = options.provider ?? "cauthy";
|
|
195
|
-
if (!SLUG.test(system ?? "")) throw new CauthyConfigError(`contract.system must be the system's id in the graph, e.g. "acme-crm"`);
|
|
196
|
-
const graph = options.graph && typeof options.graph === "object" && "default" in options.graph && !("edges" in options.graph) ? options.graph.default : options.graph;
|
|
197
|
-
if (!graph || !Array.isArray(graph.edges) || !Array.isArray(graph.nodes)) {
|
|
198
|
-
throw new CauthyConfigError("contract.graph is not a context graph: pass the graph.json that `cauthy contracts pull` writes");
|
|
199
|
-
}
|
|
200
|
-
const node = (id) => graph.nodes?.find((n) => n.type === "system" && n.id === id);
|
|
201
|
-
const own = node(system);
|
|
202
|
-
if (!own) throw new CauthyConfigError(`the graph has no system "${system}"`);
|
|
203
|
-
const cauthy = node(provider);
|
|
204
|
-
if (!cauthy) throw new CauthyConfigError(`the graph has no system "${provider}"`);
|
|
205
|
-
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);
|
|
206
|
-
if (links.length === 0) {
|
|
207
|
-
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`);
|
|
208
|
-
}
|
|
209
|
-
if (links.length > 1) {
|
|
210
|
-
throw new CauthyConfigError(`${system} has ${links.length} links to ${provider}: name the one to read (contract.link): ${links.map((l) => text(l.link)).join(", ")}`);
|
|
211
|
-
}
|
|
212
|
-
const e = links[0];
|
|
213
|
-
const link = text(e.link);
|
|
214
|
-
const named = text(e.identity?.tenant);
|
|
215
|
-
const pinned = text(e.identity?.tenant_id);
|
|
216
|
-
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`);
|
|
217
|
-
let tenantId;
|
|
218
|
-
let tenant;
|
|
219
|
-
if (UUID.test(named)) {
|
|
220
|
-
if (pinned !== "" && pinned.toLowerCase() !== named.toLowerCase()) {
|
|
221
|
-
throw new CauthyConfigError(`the link ${link} names the workspace ${named} and its tenant_id ${pinned} does not match: pull the graph again`);
|
|
222
|
-
}
|
|
223
|
-
tenantId = named;
|
|
224
|
-
tenant = text((cauthy.tenants ?? []).find((t) => t.id === named)?.slug);
|
|
225
|
-
if (tenant === "") throw new CauthyConfigError(`the graph does not name the slug of the workspace ${named} that the link ${link} uses`);
|
|
226
|
-
} else {
|
|
227
|
-
if (!SLUG.test(named)) throw new CauthyConfigError(`the link ${link} names no workspace: identity.tenant is a UUID or a slug`);
|
|
228
|
-
if (!UUID.test(pinned)) {
|
|
229
|
-
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`);
|
|
230
|
-
}
|
|
231
|
-
tenantId = pinned;
|
|
232
|
-
tenant = named;
|
|
233
|
-
}
|
|
234
|
-
const issuer = withoutTrailingSlashes(httpsAddress(text(cauthy.url), `address of ${provider}`).href);
|
|
235
|
-
const signIn = e.sign_in ?? {};
|
|
236
|
-
const clientId = text(signIn.client_id) || `pact.${link}`;
|
|
237
|
-
const redirectUris = Array.isArray(signIn.redirect_uris) ? signIn.redirect_uris.map(text).filter(Boolean) : [];
|
|
238
|
-
if (redirectUris.length === 0) throw new CauthyConfigError(`the link ${link} names no redirect URI`);
|
|
239
|
-
const proof = text(signIn.system_proof);
|
|
240
|
-
if (signIn.system_proof !== void 0 && proof !== "off" && proof !== "auto" && proof !== "required") {
|
|
241
|
-
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`);
|
|
242
|
-
}
|
|
243
|
-
const systemProof = proof === "off" || proof === "auto" || proof === "required" ? proof : signIn.require_system_proof === true ? "required" : "off";
|
|
244
|
-
if (systemProof !== "off" && !e.service) {
|
|
245
|
-
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`);
|
|
246
|
-
}
|
|
247
|
-
const out2 = {
|
|
248
|
-
system,
|
|
249
|
-
link,
|
|
250
|
-
issuer,
|
|
251
|
-
tenantId,
|
|
252
|
-
tenant,
|
|
253
|
-
clientId,
|
|
254
|
-
redirectUris,
|
|
255
|
-
requireSystemProof: systemProof === "required",
|
|
256
|
-
systemProof,
|
|
257
|
-
requireVerifiedEmail: signIn.require_verified_email === true,
|
|
258
|
-
persistentSessions: signIn.persistent_sessions === true
|
|
259
|
-
};
|
|
260
|
-
if (e.service) {
|
|
261
|
-
out2.serviceClientId = text(e.service.client_id) || `pact.${link}.service`;
|
|
262
|
-
const jwks = text(e.service.jwks_url) || text(own.jwks_url);
|
|
263
|
-
if (jwks === "") throw new CauthyConfigError(`the link ${link} gives ${system} its own identity, and ${system} names no jwks_url`);
|
|
264
|
-
out2.jwksUrl = httpsAddress(jwks, "jwks_url").href;
|
|
265
|
-
out2.jwksPath = new URL(out2.jwksUrl).pathname;
|
|
266
|
-
}
|
|
267
|
-
if (text(own.events_url) !== "") out2.eventsUrl = httpsAddress(text(own.events_url), "events_url").href;
|
|
268
|
-
return out2;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// src/keys.ts
|
|
272
|
-
var import_jose2 = require("jose");
|
|
273
|
-
var RETIRE_AFTER_MS = 24 * 60 * 60 * 1e3;
|
|
274
|
-
var FORGET_AFTER_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
275
|
-
var CURRENT_TTL_MS = 5 * 60 * 1e3;
|
|
276
|
-
var JWKS_MAX_AGE_S = 300;
|
|
277
|
-
var SIGN_AFTER_MS = (JWKS_MAX_AGE_S + 60) * 1e3;
|
|
278
|
-
var SETTLING_TTL_MS = 5 * 1e3;
|
|
279
|
-
var newestFirst = (keys2) => [...keys2].sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.kid.localeCompare(a.kid));
|
|
280
|
-
function published(key) {
|
|
281
|
-
const { kty, n, e } = key.publicJwk;
|
|
282
|
-
return { kty, n, e, kid: key.kid, alg: "RS256", use: "sig" };
|
|
283
|
-
}
|
|
284
|
-
function pactKeys(store, sealKeys, now = Date.now) {
|
|
285
|
-
let remembered;
|
|
286
|
-
let making;
|
|
287
|
-
async function make(after) {
|
|
288
|
-
const { publicKey, privateKey } = await (0, import_jose2.generateKeyPair)("RS256", { modulusLength: 2048, extractable: true });
|
|
289
|
-
const publicJwk = await (0, import_jose2.exportJWK)(publicKey);
|
|
290
|
-
const kid = await (0, import_jose2.calculateJwkThumbprint)(publicJwk);
|
|
291
|
-
const at = Math.max(now(), after ? Date.parse(after.createdAt) + 1 : 0);
|
|
292
|
-
const key = { kid, publicJwk, sealed: await seal(await (0, import_jose2.exportJWK)(privateKey), (await sealKeys)[0]), createdAt: new Date(at).toISOString() };
|
|
293
|
-
if (!after && store.addFirst) await store.addFirst(key);
|
|
294
|
-
else await store.add(key);
|
|
295
|
-
return key;
|
|
296
|
-
}
|
|
297
|
-
const age = (key) => now() - Date.parse(key.createdAt);
|
|
298
|
-
function signer(kept) {
|
|
299
|
-
const ripe = kept.find((k) => age(k) >= SIGN_AFTER_MS);
|
|
300
|
-
if (!ripe) return { key: kept[kept.length - 1], until: now() + SETTLING_TTL_MS };
|
|
301
|
-
const until = ripe === kept[0] ? now() + CURRENT_TTL_MS : Math.min(now() + CURRENT_TTL_MS, Date.parse(kept[0].createdAt) + SIGN_AFTER_MS);
|
|
302
|
-
return { key: ripe, until };
|
|
303
|
-
}
|
|
304
|
-
async function open(key) {
|
|
305
|
-
const ring = await sealKeys;
|
|
306
|
-
for (let i = 0; i < ring.length; i++) {
|
|
307
|
-
const jwk = await unseal(key.sealed, ring[i]);
|
|
308
|
-
if (jwk !== null) return { jwk, fresh: i === 0 };
|
|
309
|
-
}
|
|
310
|
-
return null;
|
|
311
|
-
}
|
|
312
|
-
async function keptKeys() {
|
|
313
|
-
const kept = newestFirst(await store.list());
|
|
314
|
-
if (kept.length > 0) return kept;
|
|
315
|
-
making ??= make().finally(() => {
|
|
316
|
-
making = void 0;
|
|
317
|
-
});
|
|
318
|
-
await making;
|
|
319
|
-
const again = newestFirst(await store.list());
|
|
320
|
-
if (again.length === 0) throw new CauthyConfigError("the key store kept no key: check that it writes");
|
|
321
|
-
return again;
|
|
322
|
-
}
|
|
323
|
-
return {
|
|
324
|
-
async current() {
|
|
325
|
-
if (remembered && remembered.until > now()) return remembered;
|
|
326
|
-
const { key, until } = signer(await keptKeys());
|
|
327
|
-
if (remembered?.kid === key.kid) {
|
|
328
|
-
remembered.until = until;
|
|
329
|
-
return remembered;
|
|
330
|
-
}
|
|
331
|
-
const opened = await open(key);
|
|
332
|
-
if (opened === null) {
|
|
333
|
-
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`);
|
|
334
|
-
}
|
|
335
|
-
remembered = { kid: key.kid, privateKey: await (0, import_jose2.importJWK)(opened.jwk, "RS256"), until };
|
|
336
|
-
return remembered;
|
|
337
|
-
},
|
|
338
|
-
async published() {
|
|
339
|
-
const kept = await keptKeys();
|
|
340
|
-
const out2 = [];
|
|
341
|
-
for (let i = 0; i < kept.length; i++) {
|
|
342
|
-
if (i === 0 || now() - Date.parse(kept[i - 1].createdAt) < RETIRE_AFTER_MS) out2.push(published(kept[i]));
|
|
343
|
-
else if (now() - Date.parse(kept[i - 1].createdAt) > FORGET_AFTER_MS) await store.remove(kept[i].kid);
|
|
344
|
-
}
|
|
345
|
-
return { keys: out2 };
|
|
346
|
-
},
|
|
347
|
-
async rotate() {
|
|
348
|
-
const key = await make(newestFirst(await store.list())[0]);
|
|
349
|
-
remembered = void 0;
|
|
350
|
-
return key.kid;
|
|
351
|
-
},
|
|
352
|
-
async reseal() {
|
|
353
|
-
let changed = 0;
|
|
354
|
-
const ring = await sealKeys;
|
|
355
|
-
for (const key of await store.list()) {
|
|
356
|
-
const opened = await open(key);
|
|
357
|
-
if (opened === null || opened.fresh) continue;
|
|
358
|
-
await store.update({ ...key, sealed: await seal(opened.jwk, ring[0]) });
|
|
359
|
-
changed++;
|
|
360
|
-
}
|
|
361
|
-
return changed;
|
|
362
|
-
}
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// src/rootkey.ts
|
|
367
|
-
var encoder2 = new TextEncoder();
|
|
368
|
-
var MIN_ROOT_KEY = 32;
|
|
369
|
-
async function derive(root, purpose) {
|
|
370
|
-
const ikm = await crypto.subtle.importKey("raw", encoder2.encode(root), "HKDF", false, ["deriveBits"]);
|
|
371
|
-
const bits = await crypto.subtle.deriveBits(
|
|
372
|
-
{ name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: encoder2.encode(`pact/${purpose}/v1`) },
|
|
373
|
-
ikm,
|
|
374
|
-
256
|
|
375
|
-
);
|
|
376
|
-
return new Uint8Array(bits);
|
|
377
|
-
}
|
|
378
|
-
function deriveKeys(root, previous) {
|
|
379
|
-
if (root.length < MIN_ROOT_KEY) {
|
|
380
|
-
throw new CauthyConfigError(`rootKey (PACT_ROOT_KEY) must be at least ${MIN_ROOT_KEY} characters: 32 random bytes, e.g. openssl rand -base64 32`);
|
|
381
|
-
}
|
|
382
|
-
if (previous !== void 0 && previous !== "" && previous.length < MIN_ROOT_KEY) {
|
|
383
|
-
throw new CauthyConfigError(`the previous root key (PACT_ROOT_KEY_PREVIOUS) must be at least ${MIN_ROOT_KEY} characters`);
|
|
384
|
-
}
|
|
385
|
-
const roots = previous && previous !== root ? [root, previous] : [root];
|
|
386
|
-
const ring = (purpose) => Promise.all(roots.map((r) => derive(r, purpose)));
|
|
387
|
-
return { seal: ring("seal"), cookie: ring("cookie"), flow: ring("flow") };
|
|
388
|
-
}
|
|
389
|
-
|
|
390
132
|
// src/deadline.ts
|
|
391
133
|
var DEFAULT_DEADLINE_MS = 5e3;
|
|
392
134
|
var isTimeout = (err) => err?.name === "TimeoutError";
|
|
@@ -420,6 +162,13 @@ function anySignal(signals) {
|
|
|
420
162
|
return controller.signal;
|
|
421
163
|
}
|
|
422
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
|
+
|
|
423
172
|
// src/permissions.ts
|
|
424
173
|
function matches(entry, resource, action) {
|
|
425
174
|
const i = entry.lastIndexOf(":");
|
|
@@ -489,19 +238,19 @@ var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
|
489
238
|
function policySource(system, read) {
|
|
490
239
|
const roles = Array.isArray(read) ? read : read.roles;
|
|
491
240
|
const catalog = Array.isArray(read) ? [] : read.catalog ?? [];
|
|
492
|
-
const
|
|
493
|
-
const key = (k) => IDENT.test(k) ? k :
|
|
241
|
+
const text = (v) => JSON.stringify(v);
|
|
242
|
+
const key = (k) => IDENT.test(k) ? k : text(k);
|
|
494
243
|
const one = (r, pad) => {
|
|
495
|
-
const parts = [`role: ${
|
|
496
|
-
if (r.name) parts.push(`name: ${
|
|
497
|
-
if (r.description) parts.push(`description: ${
|
|
498
|
-
if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(
|
|
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(", ")}]`);
|
|
499
248
|
if (r.permissions?.length) {
|
|
500
249
|
const list = r.permissions.map((p) => `${pad} { ${[
|
|
501
|
-
...p.kind && p.kind !== "*" ? [`${key("kind")}: ${
|
|
502
|
-
`resource: ${
|
|
503
|
-
`action: ${
|
|
504
|
-
...p.condition ? [`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)}`] : []
|
|
505
254
|
].join(", ")} },`);
|
|
506
255
|
parts.push(`permissions: [
|
|
507
256
|
${list.join("\n")}
|
|
@@ -513,14 +262,14 @@ ${pad} `)},
|
|
|
513
262
|
${pad} },`;
|
|
514
263
|
};
|
|
515
264
|
const resource = (c) => {
|
|
516
|
-
const parts = [`resource: ${
|
|
517
|
-
if (c.description) parts.push(`description: ${
|
|
518
|
-
parts.push(`actions: [${c.actions.map(
|
|
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(", ")}]`);
|
|
519
268
|
const attrs = Object.entries(c.attributes ?? {});
|
|
520
|
-
if (attrs.length) parts.push(`attributes: { ${attrs.map(([k, v]) => `${key(k)}: ${
|
|
269
|
+
if (attrs.length) parts.push(`attributes: { ${attrs.map(([k, v]) => `${key(k)}: ${text(v)}`).join(", ")} }`);
|
|
521
270
|
return ` { ${parts.join(", ")} },`;
|
|
522
271
|
};
|
|
523
|
-
const head = `// The catalog of roles of ${
|
|
272
|
+
const head = `// The catalog of roles of ${text(system)}, as Cauthy has it.
|
|
524
273
|
// Written by \`pullPolicy\`: run it again after the catalog
|
|
525
274
|
// changes, and do not edit this file by hand.
|
|
526
275
|
`;
|
|
@@ -630,7 +379,7 @@ function organizationIdOf(o) {
|
|
|
630
379
|
}
|
|
631
380
|
|
|
632
381
|
// src/system.ts
|
|
633
|
-
var
|
|
382
|
+
var import_jose2 = require("jose");
|
|
634
383
|
|
|
635
384
|
// src/theme.ts
|
|
636
385
|
var MAX_RADIUS = 40;
|
|
@@ -647,7 +396,7 @@ function withoutComments(css) {
|
|
|
647
396
|
}
|
|
648
397
|
}
|
|
649
398
|
function declarations(css) {
|
|
650
|
-
const
|
|
399
|
+
const text = withoutComments(css);
|
|
651
400
|
const out2 = [];
|
|
652
401
|
const within = [];
|
|
653
402
|
let chunk = "";
|
|
@@ -658,7 +407,7 @@ function declarations(css) {
|
|
|
658
407
|
if (m) out2.push({ name: m[1], value: m[2].replace(/\s+/g, " ").replace(/\s*!important$/, ""), within: [...within] });
|
|
659
408
|
chunk = "";
|
|
660
409
|
};
|
|
661
|
-
for (const ch of
|
|
410
|
+
for (const ch of text) {
|
|
662
411
|
if (quote) {
|
|
663
412
|
chunk += ch;
|
|
664
413
|
if (ch === quote) quote = "";
|
|
@@ -994,6 +743,22 @@ async function pushTheme(o) {
|
|
|
994
743
|
}
|
|
995
744
|
}
|
|
996
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
|
+
|
|
997
762
|
// src/system.ts
|
|
998
763
|
var ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
999
764
|
var SKEW_MS = 3e4;
|
|
@@ -1033,7 +798,7 @@ function asJwk(k) {
|
|
|
1033
798
|
}
|
|
1034
799
|
async function loadKey(k) {
|
|
1035
800
|
const jwk = asJwk(k);
|
|
1036
|
-
return jwk ? (0,
|
|
801
|
+
return jwk ? (0, import_jose2.importJWK)(jwk, "RS256") : (0, import_jose2.importPKCS8)(k.privateKey, "RS256", { extractable: true });
|
|
1037
802
|
}
|
|
1038
803
|
function createSystem(given = {}) {
|
|
1039
804
|
const issuer = withoutTrailingSlashes(given.issuer ?? env("CAUTHY_ISSUER") ?? "");
|
|
@@ -1060,10 +825,6 @@ function createSystem(given = {}) {
|
|
|
1060
825
|
if (given.signer) return;
|
|
1061
826
|
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address");
|
|
1062
827
|
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
1063
|
-
if (given.keys) {
|
|
1064
|
-
if (!given.keysClientId) throw new CauthyConfigError("the contract gives the system no identity of its own: its link needs a service block");
|
|
1065
|
-
return;
|
|
1066
|
-
}
|
|
1067
828
|
if (key ? key.keyId === "" : clientSecret === "") {
|
|
1068
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)");
|
|
1069
830
|
}
|
|
@@ -1081,15 +842,15 @@ function createSystem(given = {}) {
|
|
|
1081
842
|
let asking;
|
|
1082
843
|
async function ask() {
|
|
1083
844
|
configured();
|
|
1084
|
-
const self = given.signer ? given.signer.clientId :
|
|
845
|
+
const self = given.signer ? given.signer.clientId : key?.clientId || clientId;
|
|
1085
846
|
const form = new URLSearchParams({ grant_type: "client_credentials", client_id: self });
|
|
1086
847
|
if (given.signer) {
|
|
1087
848
|
const s = await given.signer.signer;
|
|
1088
849
|
form.set("client_assertion_type", ASSERTION_TYPE);
|
|
1089
850
|
form.set("client_assertion", await s.assertion(self, tokenEndpoint));
|
|
1090
|
-
} else if (
|
|
1091
|
-
const signer =
|
|
1092
|
-
const assertion = await new
|
|
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);
|
|
1093
854
|
form.set("client_assertion_type", ASSERTION_TYPE);
|
|
1094
855
|
form.set("client_assertion", assertion);
|
|
1095
856
|
} else {
|
|
@@ -1197,9 +958,8 @@ function createSystem(given = {}) {
|
|
|
1197
958
|
return checkResultOf(await data("POST", `/v1/systems/${id(system)}/check`, { organization_id: q.organizationId, user_id: q.userId, ...checkWire(q) }));
|
|
1198
959
|
},
|
|
1199
960
|
async publicJwks() {
|
|
1200
|
-
if (given.keys) return given.keys.published();
|
|
1201
961
|
if (!key) return { keys: [] };
|
|
1202
|
-
const { kty, n, e } = asJwk(key) ?? await (0,
|
|
962
|
+
const { kty, n, e } = asJwk(key) ?? await (0, import_jose2.exportJWK)(await signingKey());
|
|
1203
963
|
return { keys: [{ kty, n, e, kid: key.keyId, alg: "RS256", use: "sig" }] };
|
|
1204
964
|
}
|
|
1205
965
|
};
|
|
@@ -1361,7 +1121,7 @@ function firstProblem(...checks) {
|
|
|
1361
1121
|
}
|
|
1362
1122
|
|
|
1363
1123
|
// src/webhook.ts
|
|
1364
|
-
var
|
|
1124
|
+
var import_jose3 = require("jose");
|
|
1365
1125
|
|
|
1366
1126
|
// src/sha256.ts
|
|
1367
1127
|
var K = new Uint32Array([
|
|
@@ -1533,8 +1293,8 @@ var fail = (reason, message) => {
|
|
|
1533
1293
|
throw new CauthyKeyError(reason, message);
|
|
1534
1294
|
};
|
|
1535
1295
|
var NOT_A_KEY = "not a Cauthy key (copy it again from the dashboard)";
|
|
1536
|
-
function parseKey(
|
|
1537
|
-
const s =
|
|
1296
|
+
function parseKey(text) {
|
|
1297
|
+
const s = text.replace(/^[ \t\r\n]+|[ \t\r\n]+$/g, "");
|
|
1538
1298
|
if (s === "" || s.length > MAX_LEN || !printableAscii(s)) fail("key_format", NOT_A_KEY);
|
|
1539
1299
|
const head = s.split(".", 1)[0];
|
|
1540
1300
|
if (head !== KEY_PREFIX) {
|
|
@@ -1602,7 +1362,7 @@ var keys = /* @__PURE__ */ new Map();
|
|
|
1602
1362
|
function keysOf(issuer) {
|
|
1603
1363
|
let set = keys.get(issuer);
|
|
1604
1364
|
if (set === void 0) {
|
|
1605
|
-
set = (0,
|
|
1365
|
+
set = (0, import_jose3.createRemoteJWKSet)(new URL(`${issuer}/.well-known/jwks.json`), { cooldownDuration: 3e4 });
|
|
1606
1366
|
keys.set(issuer, set);
|
|
1607
1367
|
}
|
|
1608
1368
|
return set;
|
|
@@ -1619,7 +1379,7 @@ async function verifyWebhook(request, options = {}) {
|
|
|
1619
1379
|
const audience = options.url ?? request.url.split("?")[0];
|
|
1620
1380
|
let claims;
|
|
1621
1381
|
try {
|
|
1622
|
-
const verified = await (0,
|
|
1382
|
+
const verified = await (0, import_jose3.jwtVerify)(signature, options.jwks ?? keysOf(issuer), {
|
|
1623
1383
|
algorithms: ["RS256"],
|
|
1624
1384
|
audience,
|
|
1625
1385
|
typ: "cauthy-webhook+jwt",
|
|
@@ -1717,10 +1477,10 @@ async function finish(res, route, cacheable = false) {
|
|
|
1717
1477
|
if (!cacheable && !(headers.get("cache-control") ?? "").includes("no-store")) headers.set("cache-control", "no-store");
|
|
1718
1478
|
let body = res.body;
|
|
1719
1479
|
if (route !== null && SIGN_IN_ROUTES.has(route) && res.ok && (headers.get("content-type") ?? "").startsWith("application/json")) {
|
|
1720
|
-
const
|
|
1721
|
-
body =
|
|
1480
|
+
const text = await res.text();
|
|
1481
|
+
body = text;
|
|
1722
1482
|
try {
|
|
1723
|
-
const parsed = JSON.parse(
|
|
1483
|
+
const parsed = JSON.parse(text);
|
|
1724
1484
|
const next = nextOf(route, parsed?.status);
|
|
1725
1485
|
if (parsed !== null && next !== void 0) body = JSON.stringify({ ...parsed, next });
|
|
1726
1486
|
} catch {
|
|
@@ -1731,29 +1491,29 @@ async function finish(res, route, cacheable = false) {
|
|
|
1731
1491
|
}
|
|
1732
1492
|
|
|
1733
1493
|
// src/verify.ts
|
|
1734
|
-
var
|
|
1494
|
+
var import_jose4 = require("jose");
|
|
1735
1495
|
|
|
1736
1496
|
// src/json.ts
|
|
1737
1497
|
var SPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r"]);
|
|
1738
1498
|
var LIMIT = 2n ** 53n;
|
|
1739
|
-
function parseStrict(
|
|
1499
|
+
function parseStrict(text, integerMembers = []) {
|
|
1740
1500
|
let i = 0;
|
|
1741
1501
|
let depth = 0;
|
|
1742
1502
|
const fail2 = (what) => {
|
|
1743
1503
|
throw new SyntaxError(`${what} at ${i}`);
|
|
1744
1504
|
};
|
|
1745
1505
|
const skip = () => {
|
|
1746
|
-
while (i <
|
|
1506
|
+
while (i < text.length && SPACE.has(text[i])) i++;
|
|
1747
1507
|
};
|
|
1748
1508
|
const value = (integer = false) => {
|
|
1749
1509
|
skip();
|
|
1750
|
-
const c =
|
|
1510
|
+
const c = text[i];
|
|
1751
1511
|
if (c === "{") return object();
|
|
1752
1512
|
if (c === "[") return array();
|
|
1753
1513
|
if (c === '"') return string();
|
|
1754
1514
|
if (c === "-" || c >= "0" && c <= "9") return number(integer);
|
|
1755
1515
|
for (const [word, v] of [["true", true], ["false", false], ["null", null]]) {
|
|
1756
|
-
if (
|
|
1516
|
+
if (text.startsWith(word, i)) {
|
|
1757
1517
|
i += word.length;
|
|
1758
1518
|
return v;
|
|
1759
1519
|
}
|
|
@@ -1763,13 +1523,13 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1763
1523
|
const string = () => {
|
|
1764
1524
|
const start = i;
|
|
1765
1525
|
i++;
|
|
1766
|
-
while (i <
|
|
1767
|
-
if (
|
|
1526
|
+
while (i < text.length && text[i] !== '"') i += text[i] === "\\" ? 2 : 1;
|
|
1527
|
+
if (text[i] !== '"') fail2("unterminated string");
|
|
1768
1528
|
i++;
|
|
1769
|
-
return JSON.parse(
|
|
1529
|
+
return JSON.parse(text.slice(start, i));
|
|
1770
1530
|
};
|
|
1771
1531
|
const number = (integer) => {
|
|
1772
|
-
const m = /^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/.exec(
|
|
1532
|
+
const m = /^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/.exec(text.slice(i));
|
|
1773
1533
|
if (!m) return fail2("bad number");
|
|
1774
1534
|
i += m[0].length;
|
|
1775
1535
|
if (integer && (m[2] !== void 0 || m[3] !== void 0)) return Number.NaN;
|
|
@@ -1784,7 +1544,7 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1784
1544
|
depth++;
|
|
1785
1545
|
const out2 = [];
|
|
1786
1546
|
skip();
|
|
1787
|
-
if (
|
|
1547
|
+
if (text[i] === "]") {
|
|
1788
1548
|
i++;
|
|
1789
1549
|
depth--;
|
|
1790
1550
|
return out2;
|
|
@@ -1792,8 +1552,8 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1792
1552
|
for (; ; ) {
|
|
1793
1553
|
out2.push(value());
|
|
1794
1554
|
skip();
|
|
1795
|
-
if (
|
|
1796
|
-
else if (
|
|
1555
|
+
if (text[i] === ",") i++;
|
|
1556
|
+
else if (text[i] === "]") {
|
|
1797
1557
|
i++;
|
|
1798
1558
|
depth--;
|
|
1799
1559
|
return out2;
|
|
@@ -1806,24 +1566,24 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1806
1566
|
const out2 = {};
|
|
1807
1567
|
const seen = /* @__PURE__ */ new Set();
|
|
1808
1568
|
skip();
|
|
1809
|
-
if (
|
|
1569
|
+
if (text[i] === "}") {
|
|
1810
1570
|
i++;
|
|
1811
1571
|
depth--;
|
|
1812
1572
|
return out2;
|
|
1813
1573
|
}
|
|
1814
1574
|
for (; ; ) {
|
|
1815
1575
|
skip();
|
|
1816
|
-
if (
|
|
1576
|
+
if (text[i] !== '"') fail2("expected a name");
|
|
1817
1577
|
const name = string();
|
|
1818
1578
|
if (seen.has(name)) fail2(`repeated name ${JSON.stringify(name)}`);
|
|
1819
1579
|
seen.add(name);
|
|
1820
1580
|
skip();
|
|
1821
|
-
if (
|
|
1581
|
+
if (text[i] !== ":") fail2("expected :");
|
|
1822
1582
|
i++;
|
|
1823
1583
|
Object.defineProperty(out2, name, { value: value(depth === 1 && integerMembers.includes(name)), enumerable: true, writable: true, configurable: true });
|
|
1824
1584
|
skip();
|
|
1825
|
-
if (
|
|
1826
|
-
else if (
|
|
1585
|
+
if (text[i] === ",") i++;
|
|
1586
|
+
else if (text[i] === "}") {
|
|
1827
1587
|
i++;
|
|
1828
1588
|
depth--;
|
|
1829
1589
|
return out2;
|
|
@@ -1832,7 +1592,7 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1832
1592
|
};
|
|
1833
1593
|
const result = value();
|
|
1834
1594
|
skip();
|
|
1835
|
-
if (i <
|
|
1595
|
+
if (i < text.length) fail2("trailing characters");
|
|
1836
1596
|
return result;
|
|
1837
1597
|
}
|
|
1838
1598
|
|
|
@@ -1853,6 +1613,15 @@ function deprecated(what, instead) {
|
|
|
1853
1613
|
var enc2 = new TextEncoder();
|
|
1854
1614
|
var PKCS8_ED25519 = Uint8Array.from([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32]);
|
|
1855
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)));
|
|
1856
1625
|
async function seedOf(secret) {
|
|
1857
1626
|
const ikm = await crypto.subtle.importKey("raw", enc2.encode(secret), "HKDF", false, ["deriveBits"]);
|
|
1858
1627
|
return new Uint8Array(await crypto.subtle.deriveBits(
|
|
@@ -1890,12 +1659,11 @@ async function signerOf(secret) {
|
|
|
1890
1659
|
}
|
|
1891
1660
|
function deriveFromKey(key, previous) {
|
|
1892
1661
|
const other = previous && previous.secret !== key.secret ? previous : void 0;
|
|
1893
|
-
const
|
|
1662
|
+
const secrets = other ? [key.secret, other.secret] : [key.secret];
|
|
1894
1663
|
return {
|
|
1895
|
-
cookie:
|
|
1896
|
-
flow:
|
|
1897
|
-
signer: signerOf(key.secret)
|
|
1898
|
-
...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)
|
|
1899
1667
|
};
|
|
1900
1668
|
}
|
|
1901
1669
|
|
|
@@ -2014,8 +1782,8 @@ var CauthyTokenError = class extends Error {
|
|
|
2014
1782
|
detail;
|
|
2015
1783
|
};
|
|
2016
1784
|
var MAX_TOKEN_BYTES = 8192;
|
|
2017
|
-
var
|
|
2018
|
-
var
|
|
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";
|
|
2019
1787
|
var SEGMENT = /^[A-Za-z0-9_-]+$/;
|
|
2020
1788
|
var MAX_KEYS_BYTES = 64 * 1024;
|
|
2021
1789
|
var KEYS_TIMEOUT_MS = 5e3;
|
|
@@ -2043,7 +1811,7 @@ function remoteKeys(issuer, fetchKeys = fetch) {
|
|
|
2043
1811
|
if (seen.has(k.kid)) throw new Error(`two usable keys share the kid ${JSON.stringify(k.kid)}`);
|
|
2044
1812
|
seen.add(k.kid);
|
|
2045
1813
|
}
|
|
2046
|
-
set = (0,
|
|
1814
|
+
set = (0, import_jose4.createLocalJWKSet)(parsed);
|
|
2047
1815
|
const maxAge = /max-age=(\d+)/.exec(res.headers.get("cache-control") ?? "");
|
|
2048
1816
|
const ttl = Math.max(KEYS_MIN_TTL_S, Math.min(maxAge ? Number(maxAge[1]) : KEYS_DEFAULT_TTL_S, KEYS_MAX_TTL_S));
|
|
2049
1817
|
expiresAt = Date.now() + ttl * 1e3;
|
|
@@ -2095,7 +1863,7 @@ async function readCapped(res, max) {
|
|
|
2095
1863
|
}
|
|
2096
1864
|
return out2;
|
|
2097
1865
|
}
|
|
2098
|
-
var
|
|
1866
|
+
var published = /* @__PURE__ */ new Map();
|
|
2099
1867
|
var withFetch = /* @__PURE__ */ new WeakMap();
|
|
2100
1868
|
function remoteKeysFor(issuer, fetchKeys) {
|
|
2101
1869
|
let byIssuer = withFetch.get(fetchKeys);
|
|
@@ -2105,10 +1873,10 @@ function remoteKeysFor(issuer, fetchKeys) {
|
|
|
2105
1873
|
return set;
|
|
2106
1874
|
}
|
|
2107
1875
|
function keysOf2(issuer) {
|
|
2108
|
-
let set =
|
|
1876
|
+
let set = published.get(issuer);
|
|
2109
1877
|
if (set === void 0) {
|
|
2110
1878
|
set = remoteKeys(issuer);
|
|
2111
|
-
|
|
1879
|
+
published.set(issuer, set);
|
|
2112
1880
|
}
|
|
2113
1881
|
return set;
|
|
2114
1882
|
}
|
|
@@ -2150,7 +1918,7 @@ function createTokenVerifier(options = {}) {
|
|
|
2150
1918
|
if (audience === "") throw new CauthyConfigError("audience (CAUTHY_AUDIENCE) is not set: without it a token issued for another application would be accepted");
|
|
2151
1919
|
const tenant = options.tenantId ?? env2("CAUTHY_TENANT_ID") ?? "";
|
|
2152
1920
|
if (tenant === "") throw new CauthyConfigError("tenantId (CAUTHY_TENANT_ID) is not set: without it a token of another workspace would be accepted");
|
|
2153
|
-
if (!
|
|
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");
|
|
2154
1922
|
const leeway = options.leeway ?? 30;
|
|
2155
1923
|
if (!Number.isFinite(leeway) || leeway < 0 || leeway > 60) throw new CauthyConfigError("leeway must be 0 to 60 seconds");
|
|
2156
1924
|
return async (from) => {
|
|
@@ -2174,7 +1942,7 @@ function createTokenVerifier(options = {}) {
|
|
|
2174
1942
|
if (typeof header.kid !== "string" || header.kid === "") throw new CauthyTokenError("missing_kid");
|
|
2175
1943
|
let payloadBytes;
|
|
2176
1944
|
try {
|
|
2177
|
-
const verified = await (0,
|
|
1945
|
+
const verified = await (0, import_jose4.compactVerify)(token, options.jwks ?? (options.fetch ? remoteKeysFor(issuer, options.fetch) : keysOf2(issuer)), { algorithms: ["RS256"] });
|
|
2178
1946
|
payloadBytes = verified.payload;
|
|
2179
1947
|
} catch (err) {
|
|
2180
1948
|
const code = err?.code;
|
|
@@ -2199,8 +1967,8 @@ function createTokenVerifier(options = {}) {
|
|
|
2199
1967
|
throw new CauthyTokenError("invalid_claim", name);
|
|
2200
1968
|
};
|
|
2201
1969
|
if (typeof claims.iss !== "string") bad("iss");
|
|
2202
|
-
if (typeof claims.sub !== "string" || !
|
|
2203
|
-
if (typeof claims.tid !== "string" || !
|
|
1970
|
+
if (typeof claims.sub !== "string" || !UUID.test(claims.sub)) bad("sub");
|
|
1971
|
+
if (typeof claims.tid !== "string" || !UUID.test(claims.tid)) bad("tid");
|
|
2204
1972
|
const aud = claims.aud;
|
|
2205
1973
|
if (!(typeof aud === "string" || Array.isArray(aud) && aud.length > 0 && aud.every((a) => typeof a === "string"))) bad("aud");
|
|
2206
1974
|
for (const name of ["exp", "iat", "nbf"]) if (claims[name] !== void 0 && !Number.isInteger(claims[name])) bad(name);
|
|
@@ -2238,13 +2006,13 @@ async function verifyToken(from, options = {}) {
|
|
|
2238
2006
|
return createTokenVerifier(options)(from);
|
|
2239
2007
|
}
|
|
2240
2008
|
async function createVerifierFromEnv(options = {}) {
|
|
2241
|
-
const
|
|
2242
|
-
if (
|
|
2009
|
+
const text = options.key ?? env2("CAUTHY_KEY");
|
|
2010
|
+
if (text === void 0 || text === "") return createTokenVerifier(options);
|
|
2243
2011
|
refuseBesideKey(typeof process !== "undefined" ? process.env : void 0);
|
|
2244
2012
|
for (const name of ["issuer", "audience", "tenantId"]) {
|
|
2245
2013
|
if (options[name] !== void 0) throw new CauthyConfigError(`with CAUTHY_KEY, ${name} comes from CAUTHY_KEY: do not pass it`);
|
|
2246
2014
|
}
|
|
2247
|
-
const key = parseKey(
|
|
2015
|
+
const key = parseKey(text);
|
|
2248
2016
|
const system = createSystem({ issuer: key.issuer, clientId: key.clientId, signer: { clientId: key.clientId, signer: deriveFromKey(key).signer }, fetch: options.fetch });
|
|
2249
2017
|
const id = await createIdentity({ issuer: key.issuer, token: () => system.token(), fetch: deadline(options.fetch, KEYS_TIMEOUT_MS) }).ready();
|
|
2250
2018
|
const { key: _, ...rest } = options;
|
|
@@ -2342,8 +2110,8 @@ function conditionalOf(c) {
|
|
|
2342
2110
|
return { resource: String(c?.resource ?? ""), action: String(c?.action ?? ""), effect: c?.effect === "deny" ? "deny" : "allow" };
|
|
2343
2111
|
}
|
|
2344
2112
|
function byKey(given) {
|
|
2345
|
-
const
|
|
2346
|
-
if (
|
|
2113
|
+
const text = given.key ?? env3("CAUTHY_KEY");
|
|
2114
|
+
if (text === void 0 || text === "") return void 0;
|
|
2347
2115
|
refuseBesideKey(typeof process !== "undefined" ? process.env : void 0);
|
|
2348
2116
|
for (const [name, value] of [
|
|
2349
2117
|
["issuer", given.issuer],
|
|
@@ -2354,12 +2122,11 @@ function byKey(given) {
|
|
|
2354
2122
|
["tenant", given.tenant],
|
|
2355
2123
|
["audience", given.audience],
|
|
2356
2124
|
["tenantId", given.tenantId],
|
|
2357
|
-
["systemKey", given.systemKey]
|
|
2358
|
-
["rootKey", given.rootKey]
|
|
2125
|
+
["systemKey", given.systemKey]
|
|
2359
2126
|
]) {
|
|
2360
2127
|
if (value !== void 0) throw new CauthyConfigError(`with CAUTHY_KEY, ${name} comes from CAUTHY_KEY: do not pass it`);
|
|
2361
2128
|
}
|
|
2362
|
-
const key = parseKey(
|
|
2129
|
+
const key = parseKey(text);
|
|
2363
2130
|
const prevText = given.keyPrevious ?? env3("CAUTHY_KEY_PREVIOUS");
|
|
2364
2131
|
let previous;
|
|
2365
2132
|
if (prevText) {
|
|
@@ -2374,14 +2141,8 @@ function byKey(given) {
|
|
|
2374
2141
|
throw new CauthyKeyError("key_previous_mismatch", "CAUTHY_KEY_PREVIOUS names another Cauthy or client than CAUTHY_KEY");
|
|
2375
2142
|
}
|
|
2376
2143
|
const legacySecret = env3("CAUTHY_SECRET") || void 0;
|
|
2377
|
-
const legacyRoot = env3("PACT_ROOT_KEY") || void 0;
|
|
2378
|
-
const legacyRootPrevious = legacyRoot ? env3("PACT_ROOT_KEY_PREVIOUS") || void 0 : void 0;
|
|
2379
2144
|
if (legacySecret) deprecated("CAUTHY_SECRET beside CAUTHY_KEY", "It is read only for sessions made before the move: remove it after the rotation window.");
|
|
2380
|
-
|
|
2381
|
-
if (given.keys && !(legacyRoot && given.contract)) {
|
|
2382
|
-
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)");
|
|
2383
|
-
}
|
|
2384
|
-
return { key, previous, legacySecret, legacyRoot, legacyRootPrevious };
|
|
2145
|
+
return { key, previous, legacySecret };
|
|
2385
2146
|
}
|
|
2386
2147
|
var UnknownOriginError = class extends Error {
|
|
2387
2148
|
constructor(origin, registered) {
|
|
@@ -2392,78 +2153,40 @@ var UnknownOriginError = class extends Error {
|
|
|
2392
2153
|
origin;
|
|
2393
2154
|
registered;
|
|
2394
2155
|
};
|
|
2395
|
-
function contractOnKey(given, keyed) {
|
|
2396
|
-
const contract = readContract(given.contract);
|
|
2397
|
-
if (contract.issuer !== keyed.key.issuer) {
|
|
2398
|
-
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`);
|
|
2399
|
-
}
|
|
2400
|
-
return contract;
|
|
2401
|
-
}
|
|
2402
|
-
function byContract(given) {
|
|
2403
|
-
failClosed(typeof process !== "undefined" ? process.env : void 0);
|
|
2404
|
-
for (const [name, value] of [
|
|
2405
|
-
["clientId", given.clientId],
|
|
2406
|
-
["clientSecret", given.clientSecret],
|
|
2407
|
-
["secret", given.secret],
|
|
2408
|
-
["system", given.system],
|
|
2409
|
-
["tenant", given.tenant],
|
|
2410
|
-
["systemKey", given.systemKey]
|
|
2411
|
-
]) {
|
|
2412
|
-
if (value !== void 0) throw new CauthyConfigError(`with a contract, ${name} comes from the contract: do not pass it`);
|
|
2413
|
-
}
|
|
2414
|
-
const contract = readContract(given.contract);
|
|
2415
|
-
const root = given.rootKey ?? env3("PACT_ROOT_KEY") ?? "";
|
|
2416
|
-
if (root === "") throw new CauthyConfigError("rootKey (PACT_ROOT_KEY) is required with a contract: 32 random bytes, e.g. openssl rand -base64 32");
|
|
2417
|
-
if (contract.serviceClientId && !given.keys) {
|
|
2418
|
-
throw new CauthyConfigError(`the link ${contract.link} gives ${contract.system} its own identity: pass keys, where its key is kept (@cauthyhq/node/keys)`);
|
|
2419
|
-
}
|
|
2420
|
-
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.");
|
|
2421
|
-
return { contract, root, previous: given.rootKeyPrevious ?? env3("PACT_ROOT_KEY_PREVIOUS") };
|
|
2422
|
-
}
|
|
2423
2156
|
var toldOfHeader = false;
|
|
2424
2157
|
var toldOfUnopened = false;
|
|
2425
2158
|
function toldUnopened() {
|
|
2426
2159
|
if (toldOfUnopened) return;
|
|
2427
2160
|
toldOfUnopened = true;
|
|
2428
|
-
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
|
|
2161
|
+
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.");
|
|
2429
2162
|
}
|
|
2430
2163
|
function createCauthy(given = {}) {
|
|
2431
2164
|
const keyed = byKey(given);
|
|
2432
|
-
const
|
|
2433
|
-
const
|
|
2434
|
-
const
|
|
2435
|
-
const
|
|
2436
|
-
const
|
|
2437
|
-
const
|
|
2438
|
-
const askSelf = keyed !== void 0 && contract === void 0;
|
|
2439
|
-
const system = askSelf ? "" : contract?.system ?? given.system ?? env3("CAUTHY_SYSTEM") ?? "";
|
|
2440
|
-
const tenant = askSelf ? "" : contract?.tenant ?? given.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
2165
|
+
const issuer = withoutTrailingSlashes(keyed?.key.issuer ?? given.issuer ?? env3("CAUTHY_ISSUER") ?? "");
|
|
2166
|
+
const clientId = keyed?.key.clientId ?? given.clientId ?? env3("CAUTHY_CLIENT_ID") ?? "";
|
|
2167
|
+
const clientSecret = keyed ? void 0 : given.clientSecret ?? env3("CAUTHY_CLIENT_SECRET");
|
|
2168
|
+
const secret = keyed ? "" : given.secret ?? env3("CAUTHY_SECRET") ?? "";
|
|
2169
|
+
const system = keyed ? "" : given.system ?? env3("CAUTHY_SYSTEM") ?? "";
|
|
2170
|
+
const tenant = keyed ? "" : given.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
2441
2171
|
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address, e.g. https://id.example.com");
|
|
2442
2172
|
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
2443
|
-
if (!
|
|
2173
|
+
if (!keyed && secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
|
|
2444
2174
|
const fromKey = keyed ? deriveFromKey(keyed.key, keyed.previous) : void 0;
|
|
2445
2175
|
let signingKid;
|
|
2446
2176
|
fromKey?.signer.then((s) => {
|
|
2447
2177
|
signingKid = s.kid;
|
|
2448
2178
|
}, () => void 0);
|
|
2449
|
-
const derived = pacted ? deriveKeys(pacted.root, pacted.previous) : void 0;
|
|
2450
|
-
const single = derived || fromKey ? void 0 : keyOf(secret).then((k) => [k]);
|
|
2451
|
-
const legacyRings = keyed?.legacyRoot ? deriveKeys(keyed.legacyRoot, keyed.legacyRootPrevious) : void 0;
|
|
2452
2179
|
const legacySingle = keyed?.legacySecret ? keyOf(keyed.legacySecret) : void 0;
|
|
2453
|
-
const withLegacy = async (own
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
const cookieKeys = fromKey ? withLegacy(fromKey.cookie, "cookie") : derived?.cookie ?? single;
|
|
2459
|
-
const flowKeys = fromKey ? withLegacy(fromKey.flow, "flow") : derived?.flow ?? single;
|
|
2460
|
-
if (!keyed && !pacted) {
|
|
2180
|
+
const withLegacy = async (own) => [...await own, ...legacySingle ? [await legacySingle] : []];
|
|
2181
|
+
const single = fromKey ? Promise.resolve([]) : keyOf(secret).then((k) => [k]);
|
|
2182
|
+
const cookieKeys = fromKey ? withLegacy(fromKey.cookie) : single;
|
|
2183
|
+
const flowKeys = fromKey ? withLegacy(fromKey.flow) : single;
|
|
2184
|
+
if (!keyed) {
|
|
2461
2185
|
deprecated(
|
|
2462
2186
|
"Configuring with CAUTHY_ISSUER, CAUTHY_CLIENT_ID, CAUTHY_CLIENT_SECRET, CAUTHY_SECRET and the other CAUTHY_* variables (or their options)",
|
|
2463
2187
|
"Connect the application in Cauthy's dashboard and use the one variable CAUTHY_KEY."
|
|
2464
2188
|
);
|
|
2465
2189
|
}
|
|
2466
|
-
const ownKeys = derived && given.keys ? pactKeys(given.keys, derived.seal) : legacyRings && contract && given.keys ? pactKeys(given.keys, legacyRings.seal) : void 0;
|
|
2467
2190
|
const baseURL = given.baseURL ?? (keyed ? void 0 : env3("CAUTHY_BASE_URL"));
|
|
2468
2191
|
if (baseURL !== void 0 && baseURL !== "" && !/^https?:\/\/[^/]+\/*$/.test(baseURL)) {
|
|
2469
2192
|
throw new CauthyConfigError("baseURL (CAUTHY_BASE_URL) must be the application's public origin, e.g. https://app.example.com");
|
|
@@ -2500,21 +2223,10 @@ function createCauthy(given = {}) {
|
|
|
2500
2223
|
acceptInviteUrl,
|
|
2501
2224
|
fetch: given.fetch,
|
|
2502
2225
|
timeoutMs: systemTimeout,
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
...fromKey ? {
|
|
2506
|
-
signer: { clientId: contract ? contract.serviceClientId ?? keyed.key.clientId : clientId, signer: fromKey.signer },
|
|
2507
|
-
currentSystem: () => identity?.current()?.system ?? ""
|
|
2508
|
-
} : {}
|
|
2509
|
-
});
|
|
2510
|
-
const selfOptions = () => ({
|
|
2511
|
-
issuer,
|
|
2512
|
-
token: () => ownSystem.token(),
|
|
2513
|
-
fetch: deadline(given.fetch, requestTimeout),
|
|
2514
|
-
kid: () => signingKid,
|
|
2515
|
-
log: (m) => console.error(`@cauthyhq/node: ${m}`)
|
|
2226
|
+
// On CAUTHY_KEY the key proves the key's own client.
|
|
2227
|
+
...fromKey ? { signer: { clientId, signer: fromKey.signer }, currentSystem: () => identity?.current()?.system ?? "" } : {}
|
|
2516
2228
|
});
|
|
2517
|
-
const identity =
|
|
2229
|
+
const identity = keyed ? createIdentity({
|
|
2518
2230
|
issuer,
|
|
2519
2231
|
token: () => ownSystem.token(),
|
|
2520
2232
|
fetch: deadline(given.fetch, requestTimeout),
|
|
@@ -2524,6 +2236,8 @@ function createCauthy(given = {}) {
|
|
|
2524
2236
|
identity?.ready().catch(() => {
|
|
2525
2237
|
});
|
|
2526
2238
|
const tenantNow = () => identity?.current()?.tenant.slug ?? tenant;
|
|
2239
|
+
const signInClientNow = () => identity?.current()?.sign_in_client_id || clientId;
|
|
2240
|
+
const signInClient = async () => identity ? (await identity.ready()).sign_in_client_id || clientId : clientId;
|
|
2527
2241
|
const systemNow = () => identity?.current()?.system ?? system;
|
|
2528
2242
|
const requestOrigin = (request) => {
|
|
2529
2243
|
const url = new URL(request.url);
|
|
@@ -2548,7 +2262,7 @@ function createCauthy(given = {}) {
|
|
|
2548
2262
|
};
|
|
2549
2263
|
const attrs = (request, maxAge) => ({ secure: safeOrigin(request).startsWith("https://"), maxAge });
|
|
2550
2264
|
async function clientAuth(form) {
|
|
2551
|
-
if (fromKey &&
|
|
2265
|
+
if (fromKey && form.get("client_id") === clientId) {
|
|
2552
2266
|
const s = await fromKey.signer;
|
|
2553
2267
|
form.set("client_assertion_type", ASSERTION_TYPE2);
|
|
2554
2268
|
form.set("client_assertion", await s.assertion(clientId, `${issuer}/v1/oauth/token`));
|
|
@@ -2558,8 +2272,8 @@ function createCauthy(given = {}) {
|
|
|
2558
2272
|
return form;
|
|
2559
2273
|
}
|
|
2560
2274
|
async function token(form, proof = {}) {
|
|
2561
|
-
const body2 = await clientAuth(new URLSearchParams({ client_id: clientId, ...form }));
|
|
2562
2275
|
try {
|
|
2276
|
+
const body2 = await clientAuth(new URLSearchParams({ client_id: await signInClient(), ...form }));
|
|
2563
2277
|
const res = await doFetch(`${issuer}/v1/oauth/token`, {
|
|
2564
2278
|
method: "POST",
|
|
2565
2279
|
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json", ...proof },
|
|
@@ -2597,18 +2311,16 @@ function createCauthy(given = {}) {
|
|
|
2597
2311
|
}
|
|
2598
2312
|
const kindOf = (body2, was) => (typeof body2?.persistent === "boolean" ? body2.persistent : was?.p === 1) ? { p: 1 } : {};
|
|
2599
2313
|
async function revoke(refreshToken) {
|
|
2600
|
-
|
|
2601
|
-
|
|
2314
|
+
try {
|
|
2315
|
+
const form = await clientAuth(new URLSearchParams({ client_id: await signInClient(), token: refreshToken }));
|
|
2316
|
+
await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form });
|
|
2317
|
+
} catch {
|
|
2318
|
+
}
|
|
2602
2319
|
}
|
|
2603
|
-
const unverified = (u) => contract?.requireVerifiedEmail === true && !u.emailVerified;
|
|
2604
2320
|
async function stored(body2, was) {
|
|
2605
2321
|
if (typeof body2?.access_token !== "string" || typeof body2?.refresh_token !== "string") return null;
|
|
2606
2322
|
const who = await userinfo(body2.access_token);
|
|
2607
2323
|
if (who === null || who.u.id === "") return null;
|
|
2608
|
-
if (unverified(who.u)) {
|
|
2609
|
-
await revoke(body2.refresh_token);
|
|
2610
|
-
throw new CauthyApiError(403, "email_not_confirmed", "Confirm your email address first.");
|
|
2611
|
-
}
|
|
2612
2324
|
const lifetime = typeof body2.expires_in === "number" && body2.expires_in > 0 ? body2.expires_in : 300;
|
|
2613
2325
|
return { at: body2.access_token, rt: body2.refresh_token, exp: Math.floor(Date.now() / 1e3) + lifetime, ...who, ...kindOf(body2, was) };
|
|
2614
2326
|
}
|
|
@@ -2636,13 +2348,7 @@ function createCauthy(given = {}) {
|
|
|
2636
2348
|
running = (async () => {
|
|
2637
2349
|
const res = await token({ grant_type: "refresh_token", refresh_token: rt });
|
|
2638
2350
|
if (!res.ok) return res.ended ? "ended" : "unreachable";
|
|
2639
|
-
|
|
2640
|
-
try {
|
|
2641
|
-
next = await stored(res.body, was);
|
|
2642
|
-
} catch (err) {
|
|
2643
|
-
if (err instanceof CauthyApiError && err.code === "email_not_confirmed") return "ended";
|
|
2644
|
-
throw err;
|
|
2645
|
-
}
|
|
2351
|
+
const next = await stored(res.body, was);
|
|
2646
2352
|
if (next !== null) return next;
|
|
2647
2353
|
if (typeof res.body?.access_token !== "string" || typeof res.body?.refresh_token !== "string") return "unreachable";
|
|
2648
2354
|
const lifetime = typeof res.body.expires_in === "number" && res.body.expires_in > 0 ? res.body.expires_in : 300;
|
|
@@ -2664,7 +2370,6 @@ function createCauthy(given = {}) {
|
|
|
2664
2370
|
if (present) toldUnopened();
|
|
2665
2371
|
return { session: null, status: "unauthenticated", cookies: present ? clearParts(cookieName, attrs(request, 0), had) : [] };
|
|
2666
2372
|
}
|
|
2667
|
-
if (unverified(value.u)) return { session: null, status: "unauthenticated", cookies: clearParts(cookieName, attrs(request, 0), had) };
|
|
2668
2373
|
if (fresh(value)) return { session: toSession(value), status: "authenticated", cookies: stale ? await write(request, value, had) : [] };
|
|
2669
2374
|
const renewed = await renew(value);
|
|
2670
2375
|
if (renewed === "ended") {
|
|
@@ -2676,7 +2381,7 @@ function createCauthy(given = {}) {
|
|
|
2676
2381
|
async function getSession(request, responseHeaders) {
|
|
2677
2382
|
if (responseHeaders === void 0) {
|
|
2678
2383
|
const { value } = await read(request);
|
|
2679
|
-
return value !== null && fresh(value)
|
|
2384
|
+
return value !== null && fresh(value) ? toSession(value) : null;
|
|
2680
2385
|
}
|
|
2681
2386
|
const r = await resolve2(request);
|
|
2682
2387
|
for (const c of r.cookies) responseHeaders.append("set-cookie", c);
|
|
@@ -2684,7 +2389,7 @@ function createCauthy(given = {}) {
|
|
|
2684
2389
|
}
|
|
2685
2390
|
async function status(request) {
|
|
2686
2391
|
const { value } = await read(request);
|
|
2687
|
-
if (value === null
|
|
2392
|
+
if (value === null) return "unauthenticated";
|
|
2688
2393
|
return fresh(value) ? "authenticated" : "expired";
|
|
2689
2394
|
}
|
|
2690
2395
|
const orgCache = /* @__PURE__ */ new Map();
|
|
@@ -2771,7 +2476,7 @@ function createCauthy(given = {}) {
|
|
|
2771
2476
|
const flow = { state: random(), verifier: random(48), returnTo: localPath(url.searchParams.get("return_to"), afterSignIn) };
|
|
2772
2477
|
const challenge = b64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(flow.verifier))));
|
|
2773
2478
|
const q = new URLSearchParams({
|
|
2774
|
-
client_id:
|
|
2479
|
+
client_id: await signInClient(),
|
|
2775
2480
|
redirect_uri: `${originOf(request)}${basePath}/callback`,
|
|
2776
2481
|
response_type: "code",
|
|
2777
2482
|
state: flow.state,
|
|
@@ -2833,28 +2538,17 @@ function createCauthy(given = {}) {
|
|
|
2833
2538
|
);
|
|
2834
2539
|
};
|
|
2835
2540
|
if (!res.ok && res.code === "email_not_confirmed") return unconfirmed();
|
|
2836
|
-
|
|
2837
|
-
try {
|
|
2838
|
-
s = res.ok ? await stored(res.body) : null;
|
|
2839
|
-
} catch (err) {
|
|
2840
|
-
if (!(err instanceof CauthyApiError && err.code === "email_not_confirmed")) throw err;
|
|
2841
|
-
return unconfirmed();
|
|
2842
|
-
}
|
|
2541
|
+
const s = res.ok ? await stored(res.body) : null;
|
|
2843
2542
|
if (s === null) return failed(request, "exchange_failed");
|
|
2844
2543
|
return redirect(flow.returnTo, [...await write(request, s, had), writeCookie(flowCookie, "", attrs(request, 0)), writeCookie(againCookie, "", attrs(request, 0))]);
|
|
2845
2544
|
}
|
|
2846
2545
|
async function logout(request) {
|
|
2847
2546
|
const { had, value } = await read(request);
|
|
2848
2547
|
if (value !== null) {
|
|
2849
|
-
|
|
2850
|
-
await doFetch(`${issuer}/v1/oauth/revoke`, {
|
|
2851
|
-
method: "POST",
|
|
2852
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
2853
|
-
body: body2
|
|
2854
|
-
}).catch(() => void 0);
|
|
2548
|
+
await revoke(value.rt);
|
|
2855
2549
|
orgCache.delete(value.at);
|
|
2856
2550
|
}
|
|
2857
|
-
const q = new URLSearchParams({ client_id:
|
|
2551
|
+
const q = new URLSearchParams({ client_id: signInClientNow(), post_logout_redirect_uri: `${originOf(request)}${afterSignOut}` });
|
|
2858
2552
|
return redirect(`${issuer}/v1/oauth/logout?${q}`, clearParts(cookieName, attrs(request, 0), had));
|
|
2859
2553
|
}
|
|
2860
2554
|
const STEP_SECONDS = 600;
|
|
@@ -2872,7 +2566,7 @@ function createCauthy(given = {}) {
|
|
|
2872
2566
|
createdAt: String(d?.created_at ?? ""),
|
|
2873
2567
|
lastUsedAt: d?.last_used_at ? String(d.last_used_at) : null
|
|
2874
2568
|
});
|
|
2875
|
-
const
|
|
2569
|
+
const UUID2 = /^[0-9a-f-]{36}$/i;
|
|
2876
2570
|
const seatOf = (d) => ({
|
|
2877
2571
|
id: String(d?.id ?? ""),
|
|
2878
2572
|
system: String(d?.system ?? ""),
|
|
@@ -2977,10 +2671,10 @@ function createCauthy(given = {}) {
|
|
|
2977
2671
|
...page ? { "x-cauthy-link-to": `${originOf(request)}${page}` } : { "x-cauthy-links": "hosted" },
|
|
2978
2672
|
// Whose request it is: what Cauthy mails goes in this application's
|
|
2979
2673
|
// name, under its logo.
|
|
2980
|
-
...
|
|
2674
|
+
...signInClientNow() ? { "x-cauthy-client-id": signInClientNow() } : {},
|
|
2981
2675
|
...await passedOn(request),
|
|
2982
2676
|
...bearer2 ? { authorization: `Bearer ${bearer2}` } : {},
|
|
2983
|
-
// The person asked to stay signed in: Cauthy keeps the session long if the
|
|
2677
|
+
// The person asked to stay signed in: Cauthy keeps the session long if the workspace allows it.
|
|
2984
2678
|
...remember ? { "x-cauthy-remember": "1" } : {},
|
|
2985
2679
|
// The answer to the workspace's Turnstile challenge, from the
|
|
2986
2680
|
// person's browser: Cauthy checks it at the doors that ask for it.
|
|
@@ -3121,7 +2815,7 @@ function createCauthy(given = {}) {
|
|
|
3121
2815
|
}
|
|
3122
2816
|
if (route === "/look" && !post) {
|
|
3123
2817
|
if (lookCache === null || lookCache.until < Date.now()) {
|
|
3124
|
-
const res = await ask(request, "GET", `/v1/auth/look/${encodeURIComponent(
|
|
2818
|
+
const res = await ask(request, "GET", `/v1/auth/look/${encodeURIComponent(signInClientNow())}`);
|
|
3125
2819
|
lookCache = { until: Date.now() + LOOK_TTL_MS, look: res.error ? {} : res.data ?? {} };
|
|
3126
2820
|
}
|
|
3127
2821
|
return json(lookCache.look);
|
|
@@ -3146,7 +2840,7 @@ function createCauthy(given = {}) {
|
|
|
3146
2840
|
signUp: settings.data?.allow_signup !== false && settings.data?.signup?.mode !== "invite_only",
|
|
3147
2841
|
// The application's system makes an organization per sign-up: the form asks the company's name.
|
|
3148
2842
|
...settings.data?.signup?.organization?.ask === true ? { organization: { ask: true, required: settings.data.signup.organization.required === true } } : {},
|
|
3149
|
-
// Whether the person may ask to stay signed in: the
|
|
2843
|
+
// Whether the person may ask to stay signed in: the workspace's setting in Cauthy (persistent_sessions).
|
|
3150
2844
|
...typeof settings.data?.persistent_sessions === "boolean" ? { persistentSessions: settings.data.persistent_sessions } : {},
|
|
3151
2845
|
// A sign-in link needs a page of the application to open.
|
|
3152
2846
|
magicLink: settings.data?.allow_magic_link === true && pages.signIn !== void 0,
|
|
@@ -3310,7 +3004,7 @@ function createCauthy(given = {}) {
|
|
|
3310
3004
|
let path = "/v1/auth/me/organizations";
|
|
3311
3005
|
if (route !== "/me/organizations") {
|
|
3312
3006
|
const id = url.searchParams.get("id") ?? "";
|
|
3313
|
-
if (!
|
|
3007
|
+
if (!UUID2.test(id)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
|
|
3314
3008
|
path += `/${id}/${route.endsWith("/roles") ? "roles" : "members"}`;
|
|
3315
3009
|
} else if (url.searchParams.get("system")) {
|
|
3316
3010
|
path += `?system=${encodeURIComponent(url.searchParams.get("system"))}`;
|
|
@@ -3371,8 +3065,7 @@ function createCauthy(given = {}) {
|
|
|
3371
3065
|
if (route === "/sign-out") {
|
|
3372
3066
|
const { had, value } = await read(request);
|
|
3373
3067
|
if (value !== null) {
|
|
3374
|
-
|
|
3375
|
-
await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form }).catch(() => void 0);
|
|
3068
|
+
await revoke(value.rt);
|
|
3376
3069
|
orgCache.delete(value.at);
|
|
3377
3070
|
}
|
|
3378
3071
|
const h = new Headers();
|
|
@@ -3599,7 +3292,7 @@ function createCauthy(given = {}) {
|
|
|
3599
3292
|
}
|
|
3600
3293
|
if (route === "/me/organizations/rename" || route === "/me/organizations/archive" || route === "/me/organizations/delete") {
|
|
3601
3294
|
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
3602
|
-
if (!
|
|
3295
|
+
if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
|
|
3603
3296
|
const path = `/v1/auth/me/organizations/${org}`;
|
|
3604
3297
|
if (route === "/me/organizations/rename") {
|
|
3605
3298
|
const bad = checkOrganizationName(b.name);
|
|
@@ -3616,7 +3309,7 @@ function createCauthy(given = {}) {
|
|
|
3616
3309
|
}
|
|
3617
3310
|
if (route.startsWith("/me/organizations/members/")) {
|
|
3618
3311
|
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
3619
|
-
if (!
|
|
3312
|
+
if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
|
|
3620
3313
|
const base = `/v1/auth/me/organizations/${org}/members`;
|
|
3621
3314
|
if (route === "/me/organizations/members/add") {
|
|
3622
3315
|
const bad = checkEmail(b.email);
|
|
@@ -3627,7 +3320,7 @@ function createCauthy(given = {}) {
|
|
|
3627
3320
|
email: String(b.email).trim(),
|
|
3628
3321
|
role,
|
|
3629
3322
|
name: typeof b.name === "string" ? b.name.trim() : "",
|
|
3630
|
-
client_id:
|
|
3323
|
+
client_id: signInClientNow(),
|
|
3631
3324
|
only_new: true,
|
|
3632
3325
|
...typeof b.locale === "string" && b.locale ? { locale: b.locale } : {}
|
|
3633
3326
|
}, pages.acceptInvite, mine.token);
|
|
@@ -3636,7 +3329,7 @@ function createCauthy(given = {}) {
|
|
|
3636
3329
|
return json({ status: "added", member: orgMemberOf(res.data) }, 200, mine.headers);
|
|
3637
3330
|
}
|
|
3638
3331
|
const user = typeof b.userId === "string" ? b.userId : "";
|
|
3639
|
-
if (!
|
|
3332
|
+
if (!UUID2.test(user)) return problem(400, "invalid_member", "Say which member.", void 0, mine.headers);
|
|
3640
3333
|
if (route === "/me/organizations/members/role") {
|
|
3641
3334
|
const role = typeof b.role === "string" ? b.role : "";
|
|
3642
3335
|
if (role === "") return problem(400, "invalid_role", "Choose a role.", "role", mine.headers);
|
|
@@ -3651,7 +3344,7 @@ function createCauthy(given = {}) {
|
|
|
3651
3344
|
return json({ status: "removed", userId: user }, 200, mine.headers);
|
|
3652
3345
|
}
|
|
3653
3346
|
if (route === "/me/organizations/members/invite") {
|
|
3654
|
-
const res = await ask(request, "POST", `${base}/${user}/invite`, { client_id:
|
|
3347
|
+
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);
|
|
3655
3348
|
if (res.error) return refused(res);
|
|
3656
3349
|
return json({ status: "sent", userId: user }, 200, mine.headers);
|
|
3657
3350
|
}
|
|
@@ -3769,18 +3462,15 @@ function createCauthy(given = {}) {
|
|
|
3769
3462
|
const url = new URL(request.url);
|
|
3770
3463
|
const inside = url.pathname === basePath || url.pathname.startsWith(`${basePath}/`);
|
|
3771
3464
|
const route = inside ? withoutTrailingSlashes(url.pathname.slice(basePath.length)) || "/" : null;
|
|
3772
|
-
const published3 = pact !== void 0 && url.pathname === contract?.jwksPath && (request.method === "GET" || request.method === "HEAD");
|
|
3773
3465
|
if (identity && route !== null) {
|
|
3774
3466
|
const unready = await identityGate();
|
|
3775
3467
|
if (unready) return finish(unready, route);
|
|
3776
3468
|
}
|
|
3777
3469
|
try {
|
|
3778
|
-
return finish(await serve(request), route
|
|
3470
|
+
return finish(await serve(request), route);
|
|
3779
3471
|
} catch (err) {
|
|
3780
3472
|
if (err instanceof UnknownOriginError) return finish(problem(400, "unknown_origin", err.message), route);
|
|
3781
|
-
|
|
3782
|
-
const wantsPage = request.method === "GET" && (request.headers.get("sec-fetch-mode") ?? "navigate") === "navigate";
|
|
3783
|
-
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);
|
|
3473
|
+
throw err;
|
|
3784
3474
|
}
|
|
3785
3475
|
}
|
|
3786
3476
|
async function identityGate() {
|
|
@@ -3804,7 +3494,6 @@ function createCauthy(given = {}) {
|
|
|
3804
3494
|
}
|
|
3805
3495
|
async function serve(request) {
|
|
3806
3496
|
const url = new URL(request.url);
|
|
3807
|
-
if (pact && url.pathname === contract?.jwksPath && (request.method === "GET" || request.method === "HEAD")) return pact.jwks();
|
|
3808
3497
|
if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) return new Response("Not found", { status: 404 });
|
|
3809
3498
|
const route = withoutTrailingSlashes(url.pathname.slice(basePath.length)) || "/";
|
|
3810
3499
|
const get = request.method === "GET" || request.method === "HEAD";
|
|
@@ -3828,7 +3517,7 @@ function createCauthy(given = {}) {
|
|
|
3828
3517
|
return redirect(back, r.cookies);
|
|
3829
3518
|
}
|
|
3830
3519
|
if (route === "/account" && get) {
|
|
3831
|
-
const q = new URLSearchParams({ client_id:
|
|
3520
|
+
const q = new URLSearchParams({ client_id: signInClientNow() });
|
|
3832
3521
|
const back = localPath(url.searchParams.get("return_to"), "");
|
|
3833
3522
|
if (back !== "") q.set("return_to", `${originOf(request)}${back}`);
|
|
3834
3523
|
const locale = url.searchParams.get("locale");
|
|
@@ -3847,7 +3536,7 @@ function createCauthy(given = {}) {
|
|
|
3847
3536
|
const asker = await signedInAs(request);
|
|
3848
3537
|
if ("response" in asker) return asker.response;
|
|
3849
3538
|
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
3850
|
-
if (!
|
|
3539
|
+
if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, asker.headers);
|
|
3851
3540
|
const named = (v) => typeof v === "string" && v !== "" && v.length <= 200;
|
|
3852
3541
|
if (!named(b.resource) || !named(b.action)) return problem(400, "bad_request", "Say which resource and action.", void 0, asker.headers);
|
|
3853
3542
|
const attributes2 = b.attributes;
|
|
@@ -3870,8 +3559,7 @@ function createCauthy(given = {}) {
|
|
|
3870
3559
|
}
|
|
3871
3560
|
}
|
|
3872
3561
|
async function protect(request) {
|
|
3873
|
-
const
|
|
3874
|
-
const value = read1.value !== null && unverified(read1.value.u) ? null : read1.value;
|
|
3562
|
+
const { value } = await read(request);
|
|
3875
3563
|
if (value !== null && fresh(value)) return { session: toSession(value) };
|
|
3876
3564
|
const url = new URL(request.url);
|
|
3877
3565
|
const state = value === null ? "none" : "expired";
|
|
@@ -3884,85 +3572,36 @@ function createCauthy(given = {}) {
|
|
|
3884
3572
|
return { response: redirect(to) };
|
|
3885
3573
|
}
|
|
3886
3574
|
const replay = given.webhookReplay ?? memoryReplayStore();
|
|
3887
|
-
let lateSelf;
|
|
3888
3575
|
const tokenVerifier = (o = {}) => {
|
|
3889
|
-
if (
|
|
3890
|
-
if (contract && o.clientId !== void 0 && o.clientId !== contract.clientId) {
|
|
3891
|
-
throw new CauthyConfigError(`clientId is the contract's (${contract.clientId}): remove it from the call`);
|
|
3892
|
-
}
|
|
3576
|
+
if (identity) {
|
|
3893
3577
|
let built;
|
|
3894
3578
|
return async (from) => {
|
|
3895
|
-
const id = await
|
|
3579
|
+
const id = await identity.ready();
|
|
3896
3580
|
if (built?.from !== id) {
|
|
3897
|
-
built = { from: id, verify: createTokenVerifier({
|
|
3898
|
-
issuer,
|
|
3899
|
-
tenantId: contract?.tenantId ?? id.tenant.id,
|
|
3900
|
-
audience: id.audience,
|
|
3901
|
-
fetch: given.fetch,
|
|
3902
|
-
...contract ? { clientId: contract.clientId } : {},
|
|
3903
|
-
...o
|
|
3904
|
-
}) };
|
|
3581
|
+
built = { from: id, verify: createTokenVerifier({ issuer, tenantId: id.tenant.id, audience: id.audience, fetch: given.fetch, ...o }) };
|
|
3905
3582
|
}
|
|
3906
3583
|
return built.verify(from);
|
|
3907
3584
|
};
|
|
3908
3585
|
}
|
|
3909
|
-
if (contract && o.clientId !== void 0 && o.clientId !== contract.clientId) {
|
|
3910
|
-
throw new CauthyConfigError(`clientId is the contract's (${contract.clientId}): remove it from the call`);
|
|
3911
|
-
}
|
|
3912
3586
|
return createTokenVerifier({
|
|
3913
3587
|
issuer,
|
|
3914
|
-
tenantId:
|
|
3588
|
+
tenantId: given.tenantId,
|
|
3915
3589
|
audience: given.audience,
|
|
3916
3590
|
fetch: given.fetch,
|
|
3917
|
-
...contract ? { clientId: contract.clientId } : {},
|
|
3918
3591
|
...o
|
|
3919
3592
|
});
|
|
3920
3593
|
};
|
|
3921
3594
|
const webhook = (request, o = {}) => verifyWebhook(request, { issuer, url: o.url ?? `${originOf(request)}${new URL(request.url).pathname}`, jwks: o.jwks, now: o.now, replay });
|
|
3922
|
-
async function keyJwks() {
|
|
3923
|
-
const s = await fromKey.signer;
|
|
3924
|
-
const keys2 = [{ ...s.publicJwk, kid: s.kid, alg: "EdDSA", use: "sig" }];
|
|
3925
|
-
if (fromKey.previousJwk) keys2.push({ ...await fromKey.previousJwk, alg: "EdDSA", use: "sig" });
|
|
3926
|
-
if (ownKeys) keys2.push(...(await ownKeys.published()).keys);
|
|
3927
|
-
return new Response(JSON.stringify({ keys: keys2 }), {
|
|
3928
|
-
headers: { "content-type": "application/json; charset=utf-8", "cache-control": `public, max-age=${JWKS_MAX_AGE_S}` }
|
|
3929
|
-
});
|
|
3930
|
-
}
|
|
3931
|
-
const noStore = () => {
|
|
3932
|
-
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");
|
|
3933
|
-
};
|
|
3934
|
-
const keyOnly = {
|
|
3935
|
-
current: async () => noStore(),
|
|
3936
|
-
published: async () => ({ keys: [] }),
|
|
3937
|
-
rotate: async () => noStore(),
|
|
3938
|
-
reseal: async () => 0
|
|
3939
|
-
};
|
|
3940
|
-
const pact = contract && fromKey ? {
|
|
3941
|
-
contract,
|
|
3942
|
-
keys: ownKeys ?? keyOnly,
|
|
3943
|
-
jwks: keyJwks,
|
|
3944
|
-
status: () => ownSystem.request("GET", `/v1/pact/status?system=${encodeURIComponent(contract.system)}`)
|
|
3945
|
-
} : contract && ownKeys ? {
|
|
3946
|
-
contract,
|
|
3947
|
-
keys: ownKeys,
|
|
3948
|
-
async jwks() {
|
|
3949
|
-
return new Response(JSON.stringify(await ownKeys.published()), {
|
|
3950
|
-
headers: { "content-type": "application/json; charset=utf-8", "cache-control": `public, max-age=${JWKS_MAX_AGE_S}` }
|
|
3951
|
-
});
|
|
3952
|
-
},
|
|
3953
|
-
status: () => ownSystem.request("GET", `/v1/pact/status?system=${encodeURIComponent(contract.system)}`)
|
|
3954
|
-
} : void 0;
|
|
3955
3595
|
const configured = () => ({
|
|
3956
3596
|
issuer,
|
|
3957
3597
|
audience: given.audience ?? env3("CAUTHY_AUDIENCE") ?? "",
|
|
3958
|
-
tenant: { id:
|
|
3598
|
+
tenant: { id: given.tenantId ?? env3("CAUTHY_TENANT_ID") ?? "", slug: tenant },
|
|
3959
3599
|
system,
|
|
3960
3600
|
client_id: clientId,
|
|
3961
3601
|
sign_in_client_id: clientId,
|
|
3962
3602
|
app_urls: baseURL ? [withoutTrailingSlashes(baseURL)] : [],
|
|
3963
3603
|
callback_path: `${basePath}/callback`,
|
|
3964
|
-
key: null
|
|
3965
|
-
contract: contract ? { link: contract.link, system_proof: contract.systemProof, require_verified_email: contract.requireVerifiedEmail } : null
|
|
3604
|
+
key: null
|
|
3966
3605
|
});
|
|
3967
3606
|
return {
|
|
3968
3607
|
handler,
|
|
@@ -3984,7 +3623,6 @@ function createCauthy(given = {}) {
|
|
|
3984
3623
|
check,
|
|
3985
3624
|
toPublic,
|
|
3986
3625
|
urls,
|
|
3987
|
-
...pact ? { pact } : {},
|
|
3988
3626
|
options: { issuer, clientId, basePath, afterSignIn, afterSignOut, cookieName }
|
|
3989
3627
|
};
|
|
3990
3628
|
}
|
|
@@ -4069,7 +3707,6 @@ async function connectSystem(o) {
|
|
|
4069
3707
|
KEY_REPLACES,
|
|
4070
3708
|
MIN_PASSWORD,
|
|
4071
3709
|
PROTOCOL_VERSION,
|
|
4072
|
-
STANDARD_VARIABLES,
|
|
4073
3710
|
can,
|
|
4074
3711
|
checkCode,
|
|
4075
3712
|
checkEmail,
|
|
@@ -4085,7 +3722,6 @@ async function connectSystem(o) {
|
|
|
4085
3722
|
definePolicy,
|
|
4086
3723
|
describePasswordPolicy,
|
|
4087
3724
|
explainPasswordProblems,
|
|
4088
|
-
failClosed,
|
|
4089
3725
|
memoryReplayStore,
|
|
4090
3726
|
parseKey,
|
|
4091
3727
|
passwordPolicyOf,
|
|
@@ -4096,7 +3732,6 @@ async function connectSystem(o) {
|
|
|
4096
3732
|
pushCatalog,
|
|
4097
3733
|
pushPolicy,
|
|
4098
3734
|
pushTheme,
|
|
4099
|
-
readContract,
|
|
4100
3735
|
remoteKeys,
|
|
4101
3736
|
themeFromStackmyth,
|
|
4102
3737
|
tokensFromStackmyth,
|