@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.mjs
CHANGED
|
@@ -64,261 +64,6 @@ async function unsealIndexed(sealed, ring) {
|
|
|
64
64
|
return null;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// src/types.ts
|
|
68
|
-
var CauthyConfigError = class extends Error {
|
|
69
|
-
constructor(message) {
|
|
70
|
-
super(`@cauthyhq/node: ${message}`);
|
|
71
|
-
this.name = "CauthyConfigError";
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
var CauthyKeyError = class extends CauthyConfigError {
|
|
75
|
-
constructor(reason, message) {
|
|
76
|
-
super(`CAUTHY_KEY: ${message} (${reason})`);
|
|
77
|
-
this.reason = reason;
|
|
78
|
-
this.name = "CauthyKeyError";
|
|
79
|
-
}
|
|
80
|
-
reason;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
// src/url.ts
|
|
84
|
-
function withoutTrailingSlashes(s) {
|
|
85
|
-
let end = s.length;
|
|
86
|
-
while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
|
|
87
|
-
return s.slice(0, end);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// src/contract.ts
|
|
91
|
-
var STANDARD_VARIABLES = [
|
|
92
|
-
"CAUTHY_ISSUER",
|
|
93
|
-
"CAUTHY_CLIENT_ID",
|
|
94
|
-
"CAUTHY_CLIENT_SECRET",
|
|
95
|
-
"CAUTHY_SECRET",
|
|
96
|
-
"CAUTHY_SYSTEM",
|
|
97
|
-
"CAUTHY_TENANT",
|
|
98
|
-
"CAUTHY_SYSTEM_KEY",
|
|
99
|
-
"CAUTHY_SYSTEM_KEY_ID"
|
|
100
|
-
];
|
|
101
|
-
function failClosed(environment) {
|
|
102
|
-
const set = STANDARD_VARIABLES.filter((name) => (environment?.[name] ?? "") !== "");
|
|
103
|
-
if (set.length > 0) {
|
|
104
|
-
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.");
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
var SLUG = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/;
|
|
108
|
-
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
109
|
-
var NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
110
|
-
var text = (v) => typeof v === "string" ? v : "";
|
|
111
|
-
function httpsAddress(raw, what) {
|
|
112
|
-
let u;
|
|
113
|
-
try {
|
|
114
|
-
u = new URL(raw);
|
|
115
|
-
} catch {
|
|
116
|
-
throw new CauthyConfigError(`the contract's ${what} is not an address: ${JSON.stringify(raw)}`);
|
|
117
|
-
}
|
|
118
|
-
const local = u.hostname === "localhost" || u.hostname === "127.0.0.1";
|
|
119
|
-
if (u.protocol !== "https:" && !(u.protocol === "http:" && local)) {
|
|
120
|
-
throw new CauthyConfigError(`the contract's ${what} must be https: ${raw}`);
|
|
121
|
-
}
|
|
122
|
-
return u;
|
|
123
|
-
}
|
|
124
|
-
function readContract(options) {
|
|
125
|
-
const { system } = options;
|
|
126
|
-
const provider = options.provider ?? "cauthy";
|
|
127
|
-
if (!SLUG.test(system ?? "")) throw new CauthyConfigError(`contract.system must be the system's id in the graph, e.g. "acme-crm"`);
|
|
128
|
-
const graph = options.graph && typeof options.graph === "object" && "default" in options.graph && !("edges" in options.graph) ? options.graph.default : options.graph;
|
|
129
|
-
if (!graph || !Array.isArray(graph.edges) || !Array.isArray(graph.nodes)) {
|
|
130
|
-
throw new CauthyConfigError("contract.graph is not a context graph: pass the graph.json that `cauthy contracts pull` writes");
|
|
131
|
-
}
|
|
132
|
-
const node = (id) => graph.nodes?.find((n) => n.type === "system" && n.id === id);
|
|
133
|
-
const own = node(system);
|
|
134
|
-
if (!own) throw new CauthyConfigError(`the graph has no system "${system}"`);
|
|
135
|
-
const cauthy = node(provider);
|
|
136
|
-
if (!cauthy) throw new CauthyConfigError(`the graph has no system "${provider}"`);
|
|
137
|
-
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);
|
|
138
|
-
if (links.length === 0) {
|
|
139
|
-
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`);
|
|
140
|
-
}
|
|
141
|
-
if (links.length > 1) {
|
|
142
|
-
throw new CauthyConfigError(`${system} has ${links.length} links to ${provider}: name the one to read (contract.link): ${links.map((l) => text(l.link)).join(", ")}`);
|
|
143
|
-
}
|
|
144
|
-
const e = links[0];
|
|
145
|
-
const link = text(e.link);
|
|
146
|
-
const named = text(e.identity?.tenant);
|
|
147
|
-
const pinned = text(e.identity?.tenant_id);
|
|
148
|
-
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`);
|
|
149
|
-
let tenantId;
|
|
150
|
-
let tenant;
|
|
151
|
-
if (UUID.test(named)) {
|
|
152
|
-
if (pinned !== "" && pinned.toLowerCase() !== named.toLowerCase()) {
|
|
153
|
-
throw new CauthyConfigError(`the link ${link} names the workspace ${named} and its tenant_id ${pinned} does not match: pull the graph again`);
|
|
154
|
-
}
|
|
155
|
-
tenantId = named;
|
|
156
|
-
tenant = text((cauthy.tenants ?? []).find((t) => t.id === named)?.slug);
|
|
157
|
-
if (tenant === "") throw new CauthyConfigError(`the graph does not name the slug of the workspace ${named} that the link ${link} uses`);
|
|
158
|
-
} else {
|
|
159
|
-
if (!SLUG.test(named)) throw new CauthyConfigError(`the link ${link} names no workspace: identity.tenant is a UUID or a slug`);
|
|
160
|
-
if (!UUID.test(pinned)) {
|
|
161
|
-
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`);
|
|
162
|
-
}
|
|
163
|
-
tenantId = pinned;
|
|
164
|
-
tenant = named;
|
|
165
|
-
}
|
|
166
|
-
const issuer = withoutTrailingSlashes(httpsAddress(text(cauthy.url), `address of ${provider}`).href);
|
|
167
|
-
const signIn = e.sign_in ?? {};
|
|
168
|
-
const clientId = text(signIn.client_id) || `pact.${link}`;
|
|
169
|
-
const redirectUris = Array.isArray(signIn.redirect_uris) ? signIn.redirect_uris.map(text).filter(Boolean) : [];
|
|
170
|
-
if (redirectUris.length === 0) throw new CauthyConfigError(`the link ${link} names no redirect URI`);
|
|
171
|
-
const proof = text(signIn.system_proof);
|
|
172
|
-
if (signIn.system_proof !== void 0 && proof !== "off" && proof !== "auto" && proof !== "required") {
|
|
173
|
-
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`);
|
|
174
|
-
}
|
|
175
|
-
const systemProof = proof === "off" || proof === "auto" || proof === "required" ? proof : signIn.require_system_proof === true ? "required" : "off";
|
|
176
|
-
if (systemProof !== "off" && !e.service) {
|
|
177
|
-
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`);
|
|
178
|
-
}
|
|
179
|
-
const out2 = {
|
|
180
|
-
system,
|
|
181
|
-
link,
|
|
182
|
-
issuer,
|
|
183
|
-
tenantId,
|
|
184
|
-
tenant,
|
|
185
|
-
clientId,
|
|
186
|
-
redirectUris,
|
|
187
|
-
requireSystemProof: systemProof === "required",
|
|
188
|
-
systemProof,
|
|
189
|
-
requireVerifiedEmail: signIn.require_verified_email === true,
|
|
190
|
-
persistentSessions: signIn.persistent_sessions === true
|
|
191
|
-
};
|
|
192
|
-
if (e.service) {
|
|
193
|
-
out2.serviceClientId = text(e.service.client_id) || `pact.${link}.service`;
|
|
194
|
-
const jwks = text(e.service.jwks_url) || text(own.jwks_url);
|
|
195
|
-
if (jwks === "") throw new CauthyConfigError(`the link ${link} gives ${system} its own identity, and ${system} names no jwks_url`);
|
|
196
|
-
out2.jwksUrl = httpsAddress(jwks, "jwks_url").href;
|
|
197
|
-
out2.jwksPath = new URL(out2.jwksUrl).pathname;
|
|
198
|
-
}
|
|
199
|
-
if (text(own.events_url) !== "") out2.eventsUrl = httpsAddress(text(own.events_url), "events_url").href;
|
|
200
|
-
return out2;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// src/keys.ts
|
|
204
|
-
import { calculateJwkThumbprint, exportJWK, generateKeyPair, importJWK } from "jose";
|
|
205
|
-
var RETIRE_AFTER_MS = 24 * 60 * 60 * 1e3;
|
|
206
|
-
var FORGET_AFTER_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
207
|
-
var CURRENT_TTL_MS = 5 * 60 * 1e3;
|
|
208
|
-
var JWKS_MAX_AGE_S = 300;
|
|
209
|
-
var SIGN_AFTER_MS = (JWKS_MAX_AGE_S + 60) * 1e3;
|
|
210
|
-
var SETTLING_TTL_MS = 5 * 1e3;
|
|
211
|
-
var newestFirst = (keys2) => [...keys2].sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.kid.localeCompare(a.kid));
|
|
212
|
-
function published(key) {
|
|
213
|
-
const { kty, n, e } = key.publicJwk;
|
|
214
|
-
return { kty, n, e, kid: key.kid, alg: "RS256", use: "sig" };
|
|
215
|
-
}
|
|
216
|
-
function pactKeys(store, sealKeys, now = Date.now) {
|
|
217
|
-
let remembered;
|
|
218
|
-
let making;
|
|
219
|
-
async function make(after) {
|
|
220
|
-
const { publicKey, privateKey } = await generateKeyPair("RS256", { modulusLength: 2048, extractable: true });
|
|
221
|
-
const publicJwk = await exportJWK(publicKey);
|
|
222
|
-
const kid = await calculateJwkThumbprint(publicJwk);
|
|
223
|
-
const at = Math.max(now(), after ? Date.parse(after.createdAt) + 1 : 0);
|
|
224
|
-
const key = { kid, publicJwk, sealed: await seal(await exportJWK(privateKey), (await sealKeys)[0]), createdAt: new Date(at).toISOString() };
|
|
225
|
-
if (!after && store.addFirst) await store.addFirst(key);
|
|
226
|
-
else await store.add(key);
|
|
227
|
-
return key;
|
|
228
|
-
}
|
|
229
|
-
const age = (key) => now() - Date.parse(key.createdAt);
|
|
230
|
-
function signer(kept) {
|
|
231
|
-
const ripe = kept.find((k) => age(k) >= SIGN_AFTER_MS);
|
|
232
|
-
if (!ripe) return { key: kept[kept.length - 1], until: now() + SETTLING_TTL_MS };
|
|
233
|
-
const until = ripe === kept[0] ? now() + CURRENT_TTL_MS : Math.min(now() + CURRENT_TTL_MS, Date.parse(kept[0].createdAt) + SIGN_AFTER_MS);
|
|
234
|
-
return { key: ripe, until };
|
|
235
|
-
}
|
|
236
|
-
async function open(key) {
|
|
237
|
-
const ring = await sealKeys;
|
|
238
|
-
for (let i = 0; i < ring.length; i++) {
|
|
239
|
-
const jwk = await unseal(key.sealed, ring[i]);
|
|
240
|
-
if (jwk !== null) return { jwk, fresh: i === 0 };
|
|
241
|
-
}
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
async function keptKeys() {
|
|
245
|
-
const kept = newestFirst(await store.list());
|
|
246
|
-
if (kept.length > 0) return kept;
|
|
247
|
-
making ??= make().finally(() => {
|
|
248
|
-
making = void 0;
|
|
249
|
-
});
|
|
250
|
-
await making;
|
|
251
|
-
const again = newestFirst(await store.list());
|
|
252
|
-
if (again.length === 0) throw new CauthyConfigError("the key store kept no key: check that it writes");
|
|
253
|
-
return again;
|
|
254
|
-
}
|
|
255
|
-
return {
|
|
256
|
-
async current() {
|
|
257
|
-
if (remembered && remembered.until > now()) return remembered;
|
|
258
|
-
const { key, until } = signer(await keptKeys());
|
|
259
|
-
if (remembered?.kid === key.kid) {
|
|
260
|
-
remembered.until = until;
|
|
261
|
-
return remembered;
|
|
262
|
-
}
|
|
263
|
-
const opened = await open(key);
|
|
264
|
-
if (opened === null) {
|
|
265
|
-
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`);
|
|
266
|
-
}
|
|
267
|
-
remembered = { kid: key.kid, privateKey: await importJWK(opened.jwk, "RS256"), until };
|
|
268
|
-
return remembered;
|
|
269
|
-
},
|
|
270
|
-
async published() {
|
|
271
|
-
const kept = await keptKeys();
|
|
272
|
-
const out2 = [];
|
|
273
|
-
for (let i = 0; i < kept.length; i++) {
|
|
274
|
-
if (i === 0 || now() - Date.parse(kept[i - 1].createdAt) < RETIRE_AFTER_MS) out2.push(published(kept[i]));
|
|
275
|
-
else if (now() - Date.parse(kept[i - 1].createdAt) > FORGET_AFTER_MS) await store.remove(kept[i].kid);
|
|
276
|
-
}
|
|
277
|
-
return { keys: out2 };
|
|
278
|
-
},
|
|
279
|
-
async rotate() {
|
|
280
|
-
const key = await make(newestFirst(await store.list())[0]);
|
|
281
|
-
remembered = void 0;
|
|
282
|
-
return key.kid;
|
|
283
|
-
},
|
|
284
|
-
async reseal() {
|
|
285
|
-
let changed = 0;
|
|
286
|
-
const ring = await sealKeys;
|
|
287
|
-
for (const key of await store.list()) {
|
|
288
|
-
const opened = await open(key);
|
|
289
|
-
if (opened === null || opened.fresh) continue;
|
|
290
|
-
await store.update({ ...key, sealed: await seal(opened.jwk, ring[0]) });
|
|
291
|
-
changed++;
|
|
292
|
-
}
|
|
293
|
-
return changed;
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// src/rootkey.ts
|
|
299
|
-
var encoder2 = new TextEncoder();
|
|
300
|
-
var MIN_ROOT_KEY = 32;
|
|
301
|
-
async function derive(root, purpose) {
|
|
302
|
-
const ikm = await crypto.subtle.importKey("raw", encoder2.encode(root), "HKDF", false, ["deriveBits"]);
|
|
303
|
-
const bits = await crypto.subtle.deriveBits(
|
|
304
|
-
{ name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: encoder2.encode(`pact/${purpose}/v1`) },
|
|
305
|
-
ikm,
|
|
306
|
-
256
|
|
307
|
-
);
|
|
308
|
-
return new Uint8Array(bits);
|
|
309
|
-
}
|
|
310
|
-
function deriveKeys(root, previous) {
|
|
311
|
-
if (root.length < MIN_ROOT_KEY) {
|
|
312
|
-
throw new CauthyConfigError(`rootKey (PACT_ROOT_KEY) must be at least ${MIN_ROOT_KEY} characters: 32 random bytes, e.g. openssl rand -base64 32`);
|
|
313
|
-
}
|
|
314
|
-
if (previous !== void 0 && previous !== "" && previous.length < MIN_ROOT_KEY) {
|
|
315
|
-
throw new CauthyConfigError(`the previous root key (PACT_ROOT_KEY_PREVIOUS) must be at least ${MIN_ROOT_KEY} characters`);
|
|
316
|
-
}
|
|
317
|
-
const roots = previous && previous !== root ? [root, previous] : [root];
|
|
318
|
-
const ring = (purpose) => Promise.all(roots.map((r) => derive(r, purpose)));
|
|
319
|
-
return { seal: ring("seal"), cookie: ring("cookie"), flow: ring("flow") };
|
|
320
|
-
}
|
|
321
|
-
|
|
322
67
|
// src/deadline.ts
|
|
323
68
|
var DEFAULT_DEADLINE_MS = 5e3;
|
|
324
69
|
var isTimeout = (err) => err?.name === "TimeoutError";
|
|
@@ -352,6 +97,13 @@ function anySignal(signals) {
|
|
|
352
97
|
return controller.signal;
|
|
353
98
|
}
|
|
354
99
|
|
|
100
|
+
// src/url.ts
|
|
101
|
+
function withoutTrailingSlashes(s) {
|
|
102
|
+
let end = s.length;
|
|
103
|
+
while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
|
|
104
|
+
return s.slice(0, end);
|
|
105
|
+
}
|
|
106
|
+
|
|
355
107
|
// src/permissions.ts
|
|
356
108
|
function matches(entry, resource, action) {
|
|
357
109
|
const i = entry.lastIndexOf(":");
|
|
@@ -421,19 +173,19 @@ var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
|
421
173
|
function policySource(system, read) {
|
|
422
174
|
const roles = Array.isArray(read) ? read : read.roles;
|
|
423
175
|
const catalog = Array.isArray(read) ? [] : read.catalog ?? [];
|
|
424
|
-
const
|
|
425
|
-
const key = (k) => IDENT.test(k) ? k :
|
|
176
|
+
const text = (v) => JSON.stringify(v);
|
|
177
|
+
const key = (k) => IDENT.test(k) ? k : text(k);
|
|
426
178
|
const one = (r, pad) => {
|
|
427
|
-
const parts = [`role: ${
|
|
428
|
-
if (r.name) parts.push(`name: ${
|
|
429
|
-
if (r.description) parts.push(`description: ${
|
|
430
|
-
if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(
|
|
179
|
+
const parts = [`role: ${text(r.role)}`];
|
|
180
|
+
if (r.name) parts.push(`name: ${text(r.name)}`);
|
|
181
|
+
if (r.description) parts.push(`description: ${text(r.description)}`);
|
|
182
|
+
if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(text).join(", ")}]`);
|
|
431
183
|
if (r.permissions?.length) {
|
|
432
184
|
const list = r.permissions.map((p) => `${pad} { ${[
|
|
433
|
-
...p.kind && p.kind !== "*" ? [`${key("kind")}: ${
|
|
434
|
-
`resource: ${
|
|
435
|
-
`action: ${
|
|
436
|
-
...p.condition ? [`condition: ${
|
|
185
|
+
...p.kind && p.kind !== "*" ? [`${key("kind")}: ${text(p.kind)}`] : [],
|
|
186
|
+
`resource: ${text(p.resource)}`,
|
|
187
|
+
`action: ${text(p.action)}`,
|
|
188
|
+
...p.condition ? [`condition: ${text(p.condition)}`] : []
|
|
437
189
|
].join(", ")} },`);
|
|
438
190
|
parts.push(`permissions: [
|
|
439
191
|
${list.join("\n")}
|
|
@@ -445,14 +197,14 @@ ${pad} `)},
|
|
|
445
197
|
${pad} },`;
|
|
446
198
|
};
|
|
447
199
|
const resource = (c) => {
|
|
448
|
-
const parts = [`resource: ${
|
|
449
|
-
if (c.description) parts.push(`description: ${
|
|
450
|
-
parts.push(`actions: [${c.actions.map(
|
|
200
|
+
const parts = [`resource: ${text(c.resource)}`];
|
|
201
|
+
if (c.description) parts.push(`description: ${text(c.description)}`);
|
|
202
|
+
parts.push(`actions: [${c.actions.map(text).join(", ")}]`);
|
|
451
203
|
const attrs = Object.entries(c.attributes ?? {});
|
|
452
|
-
if (attrs.length) parts.push(`attributes: { ${attrs.map(([k, v]) => `${key(k)}: ${
|
|
204
|
+
if (attrs.length) parts.push(`attributes: { ${attrs.map(([k, v]) => `${key(k)}: ${text(v)}`).join(", ")} }`);
|
|
453
205
|
return ` { ${parts.join(", ")} },`;
|
|
454
206
|
};
|
|
455
|
-
const head = `// The catalog of roles of ${
|
|
207
|
+
const head = `// The catalog of roles of ${text(system)}, as Cauthy has it.
|
|
456
208
|
// Written by \`pullPolicy\`: run it again after the catalog
|
|
457
209
|
// changes, and do not edit this file by hand.
|
|
458
210
|
`;
|
|
@@ -562,7 +314,7 @@ function organizationIdOf(o) {
|
|
|
562
314
|
}
|
|
563
315
|
|
|
564
316
|
// src/system.ts
|
|
565
|
-
import { exportJWK
|
|
317
|
+
import { exportJWK, importJWK, importPKCS8, SignJWT } from "jose";
|
|
566
318
|
|
|
567
319
|
// src/theme.ts
|
|
568
320
|
var MAX_RADIUS = 40;
|
|
@@ -579,7 +331,7 @@ function withoutComments(css) {
|
|
|
579
331
|
}
|
|
580
332
|
}
|
|
581
333
|
function declarations(css) {
|
|
582
|
-
const
|
|
334
|
+
const text = withoutComments(css);
|
|
583
335
|
const out2 = [];
|
|
584
336
|
const within = [];
|
|
585
337
|
let chunk = "";
|
|
@@ -590,7 +342,7 @@ function declarations(css) {
|
|
|
590
342
|
if (m) out2.push({ name: m[1], value: m[2].replace(/\s+/g, " ").replace(/\s*!important$/, ""), within: [...within] });
|
|
591
343
|
chunk = "";
|
|
592
344
|
};
|
|
593
|
-
for (const ch of
|
|
345
|
+
for (const ch of text) {
|
|
594
346
|
if (quote) {
|
|
595
347
|
chunk += ch;
|
|
596
348
|
if (ch === quote) quote = "";
|
|
@@ -926,6 +678,22 @@ async function pushTheme(o) {
|
|
|
926
678
|
}
|
|
927
679
|
}
|
|
928
680
|
|
|
681
|
+
// src/types.ts
|
|
682
|
+
var CauthyConfigError = class extends Error {
|
|
683
|
+
constructor(message) {
|
|
684
|
+
super(`@cauthyhq/node: ${message}`);
|
|
685
|
+
this.name = "CauthyConfigError";
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
var CauthyKeyError = class extends CauthyConfigError {
|
|
689
|
+
constructor(reason, message) {
|
|
690
|
+
super(`CAUTHY_KEY: ${message} (${reason})`);
|
|
691
|
+
this.reason = reason;
|
|
692
|
+
this.name = "CauthyKeyError";
|
|
693
|
+
}
|
|
694
|
+
reason;
|
|
695
|
+
};
|
|
696
|
+
|
|
929
697
|
// src/system.ts
|
|
930
698
|
var ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
931
699
|
var SKEW_MS = 3e4;
|
|
@@ -965,7 +733,7 @@ function asJwk(k) {
|
|
|
965
733
|
}
|
|
966
734
|
async function loadKey(k) {
|
|
967
735
|
const jwk = asJwk(k);
|
|
968
|
-
return jwk ?
|
|
736
|
+
return jwk ? importJWK(jwk, "RS256") : importPKCS8(k.privateKey, "RS256", { extractable: true });
|
|
969
737
|
}
|
|
970
738
|
function createSystem(given = {}) {
|
|
971
739
|
const issuer = withoutTrailingSlashes(given.issuer ?? env("CAUTHY_ISSUER") ?? "");
|
|
@@ -992,10 +760,6 @@ function createSystem(given = {}) {
|
|
|
992
760
|
if (given.signer) return;
|
|
993
761
|
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address");
|
|
994
762
|
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
995
|
-
if (given.keys) {
|
|
996
|
-
if (!given.keysClientId) throw new CauthyConfigError("the contract gives the system no identity of its own: its link needs a service block");
|
|
997
|
-
return;
|
|
998
|
-
}
|
|
999
763
|
if (key ? key.keyId === "" : clientSecret === "") {
|
|
1000
764
|
throw new CauthyConfigError(key ? "the key needs its id (CAUTHY_SYSTEM_KEY_ID)" : "to act on its own the application needs its secret (CAUTHY_CLIENT_SECRET) or a key (CAUTHY_SYSTEM_KEY)");
|
|
1001
765
|
}
|
|
@@ -1013,14 +777,14 @@ function createSystem(given = {}) {
|
|
|
1013
777
|
let asking;
|
|
1014
778
|
async function ask() {
|
|
1015
779
|
configured();
|
|
1016
|
-
const self = given.signer ? given.signer.clientId :
|
|
780
|
+
const self = given.signer ? given.signer.clientId : key?.clientId || clientId;
|
|
1017
781
|
const form = new URLSearchParams({ grant_type: "client_credentials", client_id: self });
|
|
1018
782
|
if (given.signer) {
|
|
1019
783
|
const s = await given.signer.signer;
|
|
1020
784
|
form.set("client_assertion_type", ASSERTION_TYPE);
|
|
1021
785
|
form.set("client_assertion", await s.assertion(self, tokenEndpoint));
|
|
1022
|
-
} else if (
|
|
1023
|
-
const signer =
|
|
786
|
+
} else if (key) {
|
|
787
|
+
const signer = { kid: key.keyId, privateKey: await signingKey() };
|
|
1024
788
|
const assertion = await new SignJWT({}).setProtectedHeader({ alg: "RS256", kid: signer.kid }).setIssuer(self).setSubject(self).setAudience(tokenEndpoint).setJti(crypto.randomUUID()).setIssuedAt().setExpirationTime("2m").sign(signer.privateKey);
|
|
1025
789
|
form.set("client_assertion_type", ASSERTION_TYPE);
|
|
1026
790
|
form.set("client_assertion", assertion);
|
|
@@ -1129,9 +893,8 @@ function createSystem(given = {}) {
|
|
|
1129
893
|
return checkResultOf(await data("POST", `/v1/systems/${id(system)}/check`, { organization_id: q.organizationId, user_id: q.userId, ...checkWire(q) }));
|
|
1130
894
|
},
|
|
1131
895
|
async publicJwks() {
|
|
1132
|
-
if (given.keys) return given.keys.published();
|
|
1133
896
|
if (!key) return { keys: [] };
|
|
1134
|
-
const { kty, n, e } = asJwk(key) ?? await
|
|
897
|
+
const { kty, n, e } = asJwk(key) ?? await exportJWK(await signingKey());
|
|
1135
898
|
return { keys: [{ kty, n, e, kid: key.keyId, alg: "RS256", use: "sig" }] };
|
|
1136
899
|
}
|
|
1137
900
|
};
|
|
@@ -1465,8 +1228,8 @@ var fail = (reason, message) => {
|
|
|
1465
1228
|
throw new CauthyKeyError(reason, message);
|
|
1466
1229
|
};
|
|
1467
1230
|
var NOT_A_KEY = "not a Cauthy key (copy it again from the dashboard)";
|
|
1468
|
-
function parseKey(
|
|
1469
|
-
const s =
|
|
1231
|
+
function parseKey(text) {
|
|
1232
|
+
const s = text.replace(/^[ \t\r\n]+|[ \t\r\n]+$/g, "");
|
|
1470
1233
|
if (s === "" || s.length > MAX_LEN || !printableAscii(s)) fail("key_format", NOT_A_KEY);
|
|
1471
1234
|
const head = s.split(".", 1)[0];
|
|
1472
1235
|
if (head !== KEY_PREFIX) {
|
|
@@ -1649,10 +1412,10 @@ async function finish(res, route, cacheable = false) {
|
|
|
1649
1412
|
if (!cacheable && !(headers.get("cache-control") ?? "").includes("no-store")) headers.set("cache-control", "no-store");
|
|
1650
1413
|
let body = res.body;
|
|
1651
1414
|
if (route !== null && SIGN_IN_ROUTES.has(route) && res.ok && (headers.get("content-type") ?? "").startsWith("application/json")) {
|
|
1652
|
-
const
|
|
1653
|
-
body =
|
|
1415
|
+
const text = await res.text();
|
|
1416
|
+
body = text;
|
|
1654
1417
|
try {
|
|
1655
|
-
const parsed = JSON.parse(
|
|
1418
|
+
const parsed = JSON.parse(text);
|
|
1656
1419
|
const next = nextOf(route, parsed?.status);
|
|
1657
1420
|
if (parsed !== null && next !== void 0) body = JSON.stringify({ ...parsed, next });
|
|
1658
1421
|
} catch {
|
|
@@ -1668,24 +1431,24 @@ import { compactVerify, createLocalJWKSet } from "jose";
|
|
|
1668
1431
|
// src/json.ts
|
|
1669
1432
|
var SPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r"]);
|
|
1670
1433
|
var LIMIT = 2n ** 53n;
|
|
1671
|
-
function parseStrict(
|
|
1434
|
+
function parseStrict(text, integerMembers = []) {
|
|
1672
1435
|
let i = 0;
|
|
1673
1436
|
let depth = 0;
|
|
1674
1437
|
const fail2 = (what) => {
|
|
1675
1438
|
throw new SyntaxError(`${what} at ${i}`);
|
|
1676
1439
|
};
|
|
1677
1440
|
const skip = () => {
|
|
1678
|
-
while (i <
|
|
1441
|
+
while (i < text.length && SPACE.has(text[i])) i++;
|
|
1679
1442
|
};
|
|
1680
1443
|
const value = (integer = false) => {
|
|
1681
1444
|
skip();
|
|
1682
|
-
const c =
|
|
1445
|
+
const c = text[i];
|
|
1683
1446
|
if (c === "{") return object();
|
|
1684
1447
|
if (c === "[") return array();
|
|
1685
1448
|
if (c === '"') return string();
|
|
1686
1449
|
if (c === "-" || c >= "0" && c <= "9") return number(integer);
|
|
1687
1450
|
for (const [word, v] of [["true", true], ["false", false], ["null", null]]) {
|
|
1688
|
-
if (
|
|
1451
|
+
if (text.startsWith(word, i)) {
|
|
1689
1452
|
i += word.length;
|
|
1690
1453
|
return v;
|
|
1691
1454
|
}
|
|
@@ -1695,13 +1458,13 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1695
1458
|
const string = () => {
|
|
1696
1459
|
const start = i;
|
|
1697
1460
|
i++;
|
|
1698
|
-
while (i <
|
|
1699
|
-
if (
|
|
1461
|
+
while (i < text.length && text[i] !== '"') i += text[i] === "\\" ? 2 : 1;
|
|
1462
|
+
if (text[i] !== '"') fail2("unterminated string");
|
|
1700
1463
|
i++;
|
|
1701
|
-
return JSON.parse(
|
|
1464
|
+
return JSON.parse(text.slice(start, i));
|
|
1702
1465
|
};
|
|
1703
1466
|
const number = (integer) => {
|
|
1704
|
-
const m = /^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/.exec(
|
|
1467
|
+
const m = /^-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/.exec(text.slice(i));
|
|
1705
1468
|
if (!m) return fail2("bad number");
|
|
1706
1469
|
i += m[0].length;
|
|
1707
1470
|
if (integer && (m[2] !== void 0 || m[3] !== void 0)) return Number.NaN;
|
|
@@ -1716,7 +1479,7 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1716
1479
|
depth++;
|
|
1717
1480
|
const out2 = [];
|
|
1718
1481
|
skip();
|
|
1719
|
-
if (
|
|
1482
|
+
if (text[i] === "]") {
|
|
1720
1483
|
i++;
|
|
1721
1484
|
depth--;
|
|
1722
1485
|
return out2;
|
|
@@ -1724,8 +1487,8 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1724
1487
|
for (; ; ) {
|
|
1725
1488
|
out2.push(value());
|
|
1726
1489
|
skip();
|
|
1727
|
-
if (
|
|
1728
|
-
else if (
|
|
1490
|
+
if (text[i] === ",") i++;
|
|
1491
|
+
else if (text[i] === "]") {
|
|
1729
1492
|
i++;
|
|
1730
1493
|
depth--;
|
|
1731
1494
|
return out2;
|
|
@@ -1738,24 +1501,24 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1738
1501
|
const out2 = {};
|
|
1739
1502
|
const seen = /* @__PURE__ */ new Set();
|
|
1740
1503
|
skip();
|
|
1741
|
-
if (
|
|
1504
|
+
if (text[i] === "}") {
|
|
1742
1505
|
i++;
|
|
1743
1506
|
depth--;
|
|
1744
1507
|
return out2;
|
|
1745
1508
|
}
|
|
1746
1509
|
for (; ; ) {
|
|
1747
1510
|
skip();
|
|
1748
|
-
if (
|
|
1511
|
+
if (text[i] !== '"') fail2("expected a name");
|
|
1749
1512
|
const name = string();
|
|
1750
1513
|
if (seen.has(name)) fail2(`repeated name ${JSON.stringify(name)}`);
|
|
1751
1514
|
seen.add(name);
|
|
1752
1515
|
skip();
|
|
1753
|
-
if (
|
|
1516
|
+
if (text[i] !== ":") fail2("expected :");
|
|
1754
1517
|
i++;
|
|
1755
1518
|
Object.defineProperty(out2, name, { value: value(depth === 1 && integerMembers.includes(name)), enumerable: true, writable: true, configurable: true });
|
|
1756
1519
|
skip();
|
|
1757
|
-
if (
|
|
1758
|
-
else if (
|
|
1520
|
+
if (text[i] === ",") i++;
|
|
1521
|
+
else if (text[i] === "}") {
|
|
1759
1522
|
i++;
|
|
1760
1523
|
depth--;
|
|
1761
1524
|
return out2;
|
|
@@ -1764,7 +1527,7 @@ function parseStrict(text2, integerMembers = []) {
|
|
|
1764
1527
|
};
|
|
1765
1528
|
const result = value();
|
|
1766
1529
|
skip();
|
|
1767
|
-
if (i <
|
|
1530
|
+
if (i < text.length) fail2("trailing characters");
|
|
1768
1531
|
return result;
|
|
1769
1532
|
}
|
|
1770
1533
|
|
|
@@ -1785,6 +1548,15 @@ function deprecated(what, instead) {
|
|
|
1785
1548
|
var enc2 = new TextEncoder();
|
|
1786
1549
|
var PKCS8_ED25519 = Uint8Array.from([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32]);
|
|
1787
1550
|
var ASSERTION_TYPE2 = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
1551
|
+
async function ringKey(secret, purpose) {
|
|
1552
|
+
const ikm = await crypto.subtle.importKey("raw", enc2.encode(secret), "HKDF", false, ["deriveBits"]);
|
|
1553
|
+
return new Uint8Array(await crypto.subtle.deriveBits(
|
|
1554
|
+
{ name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: enc2.encode(`pact/${purpose}/v1`) },
|
|
1555
|
+
ikm,
|
|
1556
|
+
256
|
|
1557
|
+
));
|
|
1558
|
+
}
|
|
1559
|
+
var ringOf = (secrets, purpose) => Promise.all(secrets.map((s) => ringKey(s, purpose)));
|
|
1788
1560
|
async function seedOf(secret) {
|
|
1789
1561
|
const ikm = await crypto.subtle.importKey("raw", enc2.encode(secret), "HKDF", false, ["deriveBits"]);
|
|
1790
1562
|
return new Uint8Array(await crypto.subtle.deriveBits(
|
|
@@ -1822,12 +1594,11 @@ async function signerOf(secret) {
|
|
|
1822
1594
|
}
|
|
1823
1595
|
function deriveFromKey(key, previous) {
|
|
1824
1596
|
const other = previous && previous.secret !== key.secret ? previous : void 0;
|
|
1825
|
-
const
|
|
1597
|
+
const secrets = other ? [key.secret, other.secret] : [key.secret];
|
|
1826
1598
|
return {
|
|
1827
|
-
cookie:
|
|
1828
|
-
flow:
|
|
1829
|
-
signer: signerOf(key.secret)
|
|
1830
|
-
...other ? { previousJwk: signerOf(other.secret).then((s) => ({ ...s.publicJwk, kid: s.kid })) } : {}
|
|
1599
|
+
cookie: ringOf(secrets, "cookie"),
|
|
1600
|
+
flow: ringOf(secrets, "flow"),
|
|
1601
|
+
signer: signerOf(key.secret)
|
|
1831
1602
|
};
|
|
1832
1603
|
}
|
|
1833
1604
|
|
|
@@ -1946,8 +1717,8 @@ var CauthyTokenError = class extends Error {
|
|
|
1946
1717
|
detail;
|
|
1947
1718
|
};
|
|
1948
1719
|
var MAX_TOKEN_BYTES = 8192;
|
|
1949
|
-
var
|
|
1950
|
-
var
|
|
1720
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
1721
|
+
var NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
1951
1722
|
var SEGMENT = /^[A-Za-z0-9_-]+$/;
|
|
1952
1723
|
var MAX_KEYS_BYTES = 64 * 1024;
|
|
1953
1724
|
var KEYS_TIMEOUT_MS = 5e3;
|
|
@@ -2027,7 +1798,7 @@ async function readCapped(res, max) {
|
|
|
2027
1798
|
}
|
|
2028
1799
|
return out2;
|
|
2029
1800
|
}
|
|
2030
|
-
var
|
|
1801
|
+
var published = /* @__PURE__ */ new Map();
|
|
2031
1802
|
var withFetch = /* @__PURE__ */ new WeakMap();
|
|
2032
1803
|
function remoteKeysFor(issuer, fetchKeys) {
|
|
2033
1804
|
let byIssuer = withFetch.get(fetchKeys);
|
|
@@ -2037,10 +1808,10 @@ function remoteKeysFor(issuer, fetchKeys) {
|
|
|
2037
1808
|
return set;
|
|
2038
1809
|
}
|
|
2039
1810
|
function keysOf2(issuer) {
|
|
2040
|
-
let set =
|
|
1811
|
+
let set = published.get(issuer);
|
|
2041
1812
|
if (set === void 0) {
|
|
2042
1813
|
set = remoteKeys(issuer);
|
|
2043
|
-
|
|
1814
|
+
published.set(issuer, set);
|
|
2044
1815
|
}
|
|
2045
1816
|
return set;
|
|
2046
1817
|
}
|
|
@@ -2082,7 +1853,7 @@ function createTokenVerifier(options = {}) {
|
|
|
2082
1853
|
if (audience === "") throw new CauthyConfigError("audience (CAUTHY_AUDIENCE) is not set: without it a token issued for another application would be accepted");
|
|
2083
1854
|
const tenant = options.tenantId ?? env2("CAUTHY_TENANT_ID") ?? "";
|
|
2084
1855
|
if (tenant === "") throw new CauthyConfigError("tenantId (CAUTHY_TENANT_ID) is not set: without it a token of another workspace would be accepted");
|
|
2085
|
-
if (!
|
|
1856
|
+
if (!UUID.test(tenant) || tenant === NIL_UUID) throw new CauthyConfigError("tenantId must be the workspace's id, a lowercase UUID that is not the nil UUID");
|
|
2086
1857
|
const leeway = options.leeway ?? 30;
|
|
2087
1858
|
if (!Number.isFinite(leeway) || leeway < 0 || leeway > 60) throw new CauthyConfigError("leeway must be 0 to 60 seconds");
|
|
2088
1859
|
return async (from) => {
|
|
@@ -2131,8 +1902,8 @@ function createTokenVerifier(options = {}) {
|
|
|
2131
1902
|
throw new CauthyTokenError("invalid_claim", name);
|
|
2132
1903
|
};
|
|
2133
1904
|
if (typeof claims.iss !== "string") bad("iss");
|
|
2134
|
-
if (typeof claims.sub !== "string" || !
|
|
2135
|
-
if (typeof claims.tid !== "string" || !
|
|
1905
|
+
if (typeof claims.sub !== "string" || !UUID.test(claims.sub)) bad("sub");
|
|
1906
|
+
if (typeof claims.tid !== "string" || !UUID.test(claims.tid)) bad("tid");
|
|
2136
1907
|
const aud = claims.aud;
|
|
2137
1908
|
if (!(typeof aud === "string" || Array.isArray(aud) && aud.length > 0 && aud.every((a) => typeof a === "string"))) bad("aud");
|
|
2138
1909
|
for (const name of ["exp", "iat", "nbf"]) if (claims[name] !== void 0 && !Number.isInteger(claims[name])) bad(name);
|
|
@@ -2170,13 +1941,13 @@ async function verifyToken(from, options = {}) {
|
|
|
2170
1941
|
return createTokenVerifier(options)(from);
|
|
2171
1942
|
}
|
|
2172
1943
|
async function createVerifierFromEnv(options = {}) {
|
|
2173
|
-
const
|
|
2174
|
-
if (
|
|
1944
|
+
const text = options.key ?? env2("CAUTHY_KEY");
|
|
1945
|
+
if (text === void 0 || text === "") return createTokenVerifier(options);
|
|
2175
1946
|
refuseBesideKey(typeof process !== "undefined" ? process.env : void 0);
|
|
2176
1947
|
for (const name of ["issuer", "audience", "tenantId"]) {
|
|
2177
1948
|
if (options[name] !== void 0) throw new CauthyConfigError(`with CAUTHY_KEY, ${name} comes from CAUTHY_KEY: do not pass it`);
|
|
2178
1949
|
}
|
|
2179
|
-
const key = parseKey(
|
|
1950
|
+
const key = parseKey(text);
|
|
2180
1951
|
const system = createSystem({ issuer: key.issuer, clientId: key.clientId, signer: { clientId: key.clientId, signer: deriveFromKey(key).signer }, fetch: options.fetch });
|
|
2181
1952
|
const id = await createIdentity({ issuer: key.issuer, token: () => system.token(), fetch: deadline(options.fetch, KEYS_TIMEOUT_MS) }).ready();
|
|
2182
1953
|
const { key: _, ...rest } = options;
|
|
@@ -2274,8 +2045,8 @@ function conditionalOf(c) {
|
|
|
2274
2045
|
return { resource: String(c?.resource ?? ""), action: String(c?.action ?? ""), effect: c?.effect === "deny" ? "deny" : "allow" };
|
|
2275
2046
|
}
|
|
2276
2047
|
function byKey(given) {
|
|
2277
|
-
const
|
|
2278
|
-
if (
|
|
2048
|
+
const text = given.key ?? env3("CAUTHY_KEY");
|
|
2049
|
+
if (text === void 0 || text === "") return void 0;
|
|
2279
2050
|
refuseBesideKey(typeof process !== "undefined" ? process.env : void 0);
|
|
2280
2051
|
for (const [name, value] of [
|
|
2281
2052
|
["issuer", given.issuer],
|
|
@@ -2286,12 +2057,11 @@ function byKey(given) {
|
|
|
2286
2057
|
["tenant", given.tenant],
|
|
2287
2058
|
["audience", given.audience],
|
|
2288
2059
|
["tenantId", given.tenantId],
|
|
2289
|
-
["systemKey", given.systemKey]
|
|
2290
|
-
["rootKey", given.rootKey]
|
|
2060
|
+
["systemKey", given.systemKey]
|
|
2291
2061
|
]) {
|
|
2292
2062
|
if (value !== void 0) throw new CauthyConfigError(`with CAUTHY_KEY, ${name} comes from CAUTHY_KEY: do not pass it`);
|
|
2293
2063
|
}
|
|
2294
|
-
const key = parseKey(
|
|
2064
|
+
const key = parseKey(text);
|
|
2295
2065
|
const prevText = given.keyPrevious ?? env3("CAUTHY_KEY_PREVIOUS");
|
|
2296
2066
|
let previous;
|
|
2297
2067
|
if (prevText) {
|
|
@@ -2306,14 +2076,8 @@ function byKey(given) {
|
|
|
2306
2076
|
throw new CauthyKeyError("key_previous_mismatch", "CAUTHY_KEY_PREVIOUS names another Cauthy or client than CAUTHY_KEY");
|
|
2307
2077
|
}
|
|
2308
2078
|
const legacySecret = env3("CAUTHY_SECRET") || void 0;
|
|
2309
|
-
const legacyRoot = env3("PACT_ROOT_KEY") || void 0;
|
|
2310
|
-
const legacyRootPrevious = legacyRoot ? env3("PACT_ROOT_KEY_PREVIOUS") || void 0 : void 0;
|
|
2311
2079
|
if (legacySecret) deprecated("CAUTHY_SECRET beside CAUTHY_KEY", "It is read only for sessions made before the move: remove it after the rotation window.");
|
|
2312
|
-
|
|
2313
|
-
if (given.keys && !(legacyRoot && given.contract)) {
|
|
2314
|
-
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)");
|
|
2315
|
-
}
|
|
2316
|
-
return { key, previous, legacySecret, legacyRoot, legacyRootPrevious };
|
|
2080
|
+
return { key, previous, legacySecret };
|
|
2317
2081
|
}
|
|
2318
2082
|
var UnknownOriginError = class extends Error {
|
|
2319
2083
|
constructor(origin, registered) {
|
|
@@ -2324,78 +2088,40 @@ var UnknownOriginError = class extends Error {
|
|
|
2324
2088
|
origin;
|
|
2325
2089
|
registered;
|
|
2326
2090
|
};
|
|
2327
|
-
function contractOnKey(given, keyed) {
|
|
2328
|
-
const contract = readContract(given.contract);
|
|
2329
|
-
if (contract.issuer !== keyed.key.issuer) {
|
|
2330
|
-
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`);
|
|
2331
|
-
}
|
|
2332
|
-
return contract;
|
|
2333
|
-
}
|
|
2334
|
-
function byContract(given) {
|
|
2335
|
-
failClosed(typeof process !== "undefined" ? process.env : void 0);
|
|
2336
|
-
for (const [name, value] of [
|
|
2337
|
-
["clientId", given.clientId],
|
|
2338
|
-
["clientSecret", given.clientSecret],
|
|
2339
|
-
["secret", given.secret],
|
|
2340
|
-
["system", given.system],
|
|
2341
|
-
["tenant", given.tenant],
|
|
2342
|
-
["systemKey", given.systemKey]
|
|
2343
|
-
]) {
|
|
2344
|
-
if (value !== void 0) throw new CauthyConfigError(`with a contract, ${name} comes from the contract: do not pass it`);
|
|
2345
|
-
}
|
|
2346
|
-
const contract = readContract(given.contract);
|
|
2347
|
-
const root = given.rootKey ?? env3("PACT_ROOT_KEY") ?? "";
|
|
2348
|
-
if (root === "") throw new CauthyConfigError("rootKey (PACT_ROOT_KEY) is required with a contract: 32 random bytes, e.g. openssl rand -base64 32");
|
|
2349
|
-
if (contract.serviceClientId && !given.keys) {
|
|
2350
|
-
throw new CauthyConfigError(`the link ${contract.link} gives ${contract.system} its own identity: pass keys, where its key is kept (@cauthyhq/node/keys)`);
|
|
2351
|
-
}
|
|
2352
|
-
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.");
|
|
2353
|
-
return { contract, root, previous: given.rootKeyPrevious ?? env3("PACT_ROOT_KEY_PREVIOUS") };
|
|
2354
|
-
}
|
|
2355
2091
|
var toldOfHeader = false;
|
|
2356
2092
|
var toldOfUnopened = false;
|
|
2357
2093
|
function toldUnopened() {
|
|
2358
2094
|
if (toldOfUnopened) return;
|
|
2359
2095
|
toldOfUnopened = true;
|
|
2360
|
-
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
|
|
2096
|
+
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.");
|
|
2361
2097
|
}
|
|
2362
2098
|
function createCauthy(given = {}) {
|
|
2363
2099
|
const keyed = byKey(given);
|
|
2364
|
-
const
|
|
2365
|
-
const
|
|
2366
|
-
const
|
|
2367
|
-
const
|
|
2368
|
-
const
|
|
2369
|
-
const
|
|
2370
|
-
const askSelf = keyed !== void 0 && contract === void 0;
|
|
2371
|
-
const system = askSelf ? "" : contract?.system ?? given.system ?? env3("CAUTHY_SYSTEM") ?? "";
|
|
2372
|
-
const tenant = askSelf ? "" : contract?.tenant ?? given.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
2100
|
+
const issuer = withoutTrailingSlashes(keyed?.key.issuer ?? given.issuer ?? env3("CAUTHY_ISSUER") ?? "");
|
|
2101
|
+
const clientId = keyed?.key.clientId ?? given.clientId ?? env3("CAUTHY_CLIENT_ID") ?? "";
|
|
2102
|
+
const clientSecret = keyed ? void 0 : given.clientSecret ?? env3("CAUTHY_CLIENT_SECRET");
|
|
2103
|
+
const secret = keyed ? "" : given.secret ?? env3("CAUTHY_SECRET") ?? "";
|
|
2104
|
+
const system = keyed ? "" : given.system ?? env3("CAUTHY_SYSTEM") ?? "";
|
|
2105
|
+
const tenant = keyed ? "" : given.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
2373
2106
|
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address, e.g. https://id.example.com");
|
|
2374
2107
|
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
2375
|
-
if (!
|
|
2108
|
+
if (!keyed && secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
|
|
2376
2109
|
const fromKey = keyed ? deriveFromKey(keyed.key, keyed.previous) : void 0;
|
|
2377
2110
|
let signingKid;
|
|
2378
2111
|
fromKey?.signer.then((s) => {
|
|
2379
2112
|
signingKid = s.kid;
|
|
2380
2113
|
}, () => void 0);
|
|
2381
|
-
const derived = pacted ? deriveKeys(pacted.root, pacted.previous) : void 0;
|
|
2382
|
-
const single = derived || fromKey ? void 0 : keyOf(secret).then((k) => [k]);
|
|
2383
|
-
const legacyRings = keyed?.legacyRoot ? deriveKeys(keyed.legacyRoot, keyed.legacyRootPrevious) : void 0;
|
|
2384
2114
|
const legacySingle = keyed?.legacySecret ? keyOf(keyed.legacySecret) : void 0;
|
|
2385
|
-
const withLegacy = async (own
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
const cookieKeys = fromKey ? withLegacy(fromKey.cookie, "cookie") : derived?.cookie ?? single;
|
|
2391
|
-
const flowKeys = fromKey ? withLegacy(fromKey.flow, "flow") : derived?.flow ?? single;
|
|
2392
|
-
if (!keyed && !pacted) {
|
|
2115
|
+
const withLegacy = async (own) => [...await own, ...legacySingle ? [await legacySingle] : []];
|
|
2116
|
+
const single = fromKey ? Promise.resolve([]) : keyOf(secret).then((k) => [k]);
|
|
2117
|
+
const cookieKeys = fromKey ? withLegacy(fromKey.cookie) : single;
|
|
2118
|
+
const flowKeys = fromKey ? withLegacy(fromKey.flow) : single;
|
|
2119
|
+
if (!keyed) {
|
|
2393
2120
|
deprecated(
|
|
2394
2121
|
"Configuring with CAUTHY_ISSUER, CAUTHY_CLIENT_ID, CAUTHY_CLIENT_SECRET, CAUTHY_SECRET and the other CAUTHY_* variables (or their options)",
|
|
2395
2122
|
"Connect the application in Cauthy's dashboard and use the one variable CAUTHY_KEY."
|
|
2396
2123
|
);
|
|
2397
2124
|
}
|
|
2398
|
-
const ownKeys = derived && given.keys ? pactKeys(given.keys, derived.seal) : legacyRings && contract && given.keys ? pactKeys(given.keys, legacyRings.seal) : void 0;
|
|
2399
2125
|
const baseURL = given.baseURL ?? (keyed ? void 0 : env3("CAUTHY_BASE_URL"));
|
|
2400
2126
|
if (baseURL !== void 0 && baseURL !== "" && !/^https?:\/\/[^/]+\/*$/.test(baseURL)) {
|
|
2401
2127
|
throw new CauthyConfigError("baseURL (CAUTHY_BASE_URL) must be the application's public origin, e.g. https://app.example.com");
|
|
@@ -2432,21 +2158,10 @@ function createCauthy(given = {}) {
|
|
|
2432
2158
|
acceptInviteUrl,
|
|
2433
2159
|
fetch: given.fetch,
|
|
2434
2160
|
timeoutMs: systemTimeout,
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
...fromKey ? {
|
|
2438
|
-
signer: { clientId: contract ? contract.serviceClientId ?? keyed.key.clientId : clientId, signer: fromKey.signer },
|
|
2439
|
-
currentSystem: () => identity?.current()?.system ?? ""
|
|
2440
|
-
} : {}
|
|
2441
|
-
});
|
|
2442
|
-
const selfOptions = () => ({
|
|
2443
|
-
issuer,
|
|
2444
|
-
token: () => ownSystem.token(),
|
|
2445
|
-
fetch: deadline(given.fetch, requestTimeout),
|
|
2446
|
-
kid: () => signingKid,
|
|
2447
|
-
log: (m) => console.error(`@cauthyhq/node: ${m}`)
|
|
2161
|
+
// On CAUTHY_KEY the key proves the key's own client.
|
|
2162
|
+
...fromKey ? { signer: { clientId, signer: fromKey.signer }, currentSystem: () => identity?.current()?.system ?? "" } : {}
|
|
2448
2163
|
});
|
|
2449
|
-
const identity =
|
|
2164
|
+
const identity = keyed ? createIdentity({
|
|
2450
2165
|
issuer,
|
|
2451
2166
|
token: () => ownSystem.token(),
|
|
2452
2167
|
fetch: deadline(given.fetch, requestTimeout),
|
|
@@ -2456,6 +2171,8 @@ function createCauthy(given = {}) {
|
|
|
2456
2171
|
identity?.ready().catch(() => {
|
|
2457
2172
|
});
|
|
2458
2173
|
const tenantNow = () => identity?.current()?.tenant.slug ?? tenant;
|
|
2174
|
+
const signInClientNow = () => identity?.current()?.sign_in_client_id || clientId;
|
|
2175
|
+
const signInClient = async () => identity ? (await identity.ready()).sign_in_client_id || clientId : clientId;
|
|
2459
2176
|
const systemNow = () => identity?.current()?.system ?? system;
|
|
2460
2177
|
const requestOrigin = (request) => {
|
|
2461
2178
|
const url = new URL(request.url);
|
|
@@ -2480,7 +2197,7 @@ function createCauthy(given = {}) {
|
|
|
2480
2197
|
};
|
|
2481
2198
|
const attrs = (request, maxAge) => ({ secure: safeOrigin(request).startsWith("https://"), maxAge });
|
|
2482
2199
|
async function clientAuth(form) {
|
|
2483
|
-
if (fromKey &&
|
|
2200
|
+
if (fromKey && form.get("client_id") === clientId) {
|
|
2484
2201
|
const s = await fromKey.signer;
|
|
2485
2202
|
form.set("client_assertion_type", ASSERTION_TYPE2);
|
|
2486
2203
|
form.set("client_assertion", await s.assertion(clientId, `${issuer}/v1/oauth/token`));
|
|
@@ -2490,8 +2207,8 @@ function createCauthy(given = {}) {
|
|
|
2490
2207
|
return form;
|
|
2491
2208
|
}
|
|
2492
2209
|
async function token(form, proof = {}) {
|
|
2493
|
-
const body2 = await clientAuth(new URLSearchParams({ client_id: clientId, ...form }));
|
|
2494
2210
|
try {
|
|
2211
|
+
const body2 = await clientAuth(new URLSearchParams({ client_id: await signInClient(), ...form }));
|
|
2495
2212
|
const res = await doFetch(`${issuer}/v1/oauth/token`, {
|
|
2496
2213
|
method: "POST",
|
|
2497
2214
|
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json", ...proof },
|
|
@@ -2529,18 +2246,16 @@ function createCauthy(given = {}) {
|
|
|
2529
2246
|
}
|
|
2530
2247
|
const kindOf = (body2, was) => (typeof body2?.persistent === "boolean" ? body2.persistent : was?.p === 1) ? { p: 1 } : {};
|
|
2531
2248
|
async function revoke(refreshToken) {
|
|
2532
|
-
|
|
2533
|
-
|
|
2249
|
+
try {
|
|
2250
|
+
const form = await clientAuth(new URLSearchParams({ client_id: await signInClient(), token: refreshToken }));
|
|
2251
|
+
await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form });
|
|
2252
|
+
} catch {
|
|
2253
|
+
}
|
|
2534
2254
|
}
|
|
2535
|
-
const unverified = (u) => contract?.requireVerifiedEmail === true && !u.emailVerified;
|
|
2536
2255
|
async function stored(body2, was) {
|
|
2537
2256
|
if (typeof body2?.access_token !== "string" || typeof body2?.refresh_token !== "string") return null;
|
|
2538
2257
|
const who = await userinfo(body2.access_token);
|
|
2539
2258
|
if (who === null || who.u.id === "") return null;
|
|
2540
|
-
if (unverified(who.u)) {
|
|
2541
|
-
await revoke(body2.refresh_token);
|
|
2542
|
-
throw new CauthyApiError(403, "email_not_confirmed", "Confirm your email address first.");
|
|
2543
|
-
}
|
|
2544
2259
|
const lifetime = typeof body2.expires_in === "number" && body2.expires_in > 0 ? body2.expires_in : 300;
|
|
2545
2260
|
return { at: body2.access_token, rt: body2.refresh_token, exp: Math.floor(Date.now() / 1e3) + lifetime, ...who, ...kindOf(body2, was) };
|
|
2546
2261
|
}
|
|
@@ -2568,13 +2283,7 @@ function createCauthy(given = {}) {
|
|
|
2568
2283
|
running = (async () => {
|
|
2569
2284
|
const res = await token({ grant_type: "refresh_token", refresh_token: rt });
|
|
2570
2285
|
if (!res.ok) return res.ended ? "ended" : "unreachable";
|
|
2571
|
-
|
|
2572
|
-
try {
|
|
2573
|
-
next = await stored(res.body, was);
|
|
2574
|
-
} catch (err) {
|
|
2575
|
-
if (err instanceof CauthyApiError && err.code === "email_not_confirmed") return "ended";
|
|
2576
|
-
throw err;
|
|
2577
|
-
}
|
|
2286
|
+
const next = await stored(res.body, was);
|
|
2578
2287
|
if (next !== null) return next;
|
|
2579
2288
|
if (typeof res.body?.access_token !== "string" || typeof res.body?.refresh_token !== "string") return "unreachable";
|
|
2580
2289
|
const lifetime = typeof res.body.expires_in === "number" && res.body.expires_in > 0 ? res.body.expires_in : 300;
|
|
@@ -2596,7 +2305,6 @@ function createCauthy(given = {}) {
|
|
|
2596
2305
|
if (present) toldUnopened();
|
|
2597
2306
|
return { session: null, status: "unauthenticated", cookies: present ? clearParts(cookieName, attrs(request, 0), had) : [] };
|
|
2598
2307
|
}
|
|
2599
|
-
if (unverified(value.u)) return { session: null, status: "unauthenticated", cookies: clearParts(cookieName, attrs(request, 0), had) };
|
|
2600
2308
|
if (fresh(value)) return { session: toSession(value), status: "authenticated", cookies: stale ? await write(request, value, had) : [] };
|
|
2601
2309
|
const renewed = await renew(value);
|
|
2602
2310
|
if (renewed === "ended") {
|
|
@@ -2608,7 +2316,7 @@ function createCauthy(given = {}) {
|
|
|
2608
2316
|
async function getSession(request, responseHeaders) {
|
|
2609
2317
|
if (responseHeaders === void 0) {
|
|
2610
2318
|
const { value } = await read(request);
|
|
2611
|
-
return value !== null && fresh(value)
|
|
2319
|
+
return value !== null && fresh(value) ? toSession(value) : null;
|
|
2612
2320
|
}
|
|
2613
2321
|
const r = await resolve2(request);
|
|
2614
2322
|
for (const c of r.cookies) responseHeaders.append("set-cookie", c);
|
|
@@ -2616,7 +2324,7 @@ function createCauthy(given = {}) {
|
|
|
2616
2324
|
}
|
|
2617
2325
|
async function status(request) {
|
|
2618
2326
|
const { value } = await read(request);
|
|
2619
|
-
if (value === null
|
|
2327
|
+
if (value === null) return "unauthenticated";
|
|
2620
2328
|
return fresh(value) ? "authenticated" : "expired";
|
|
2621
2329
|
}
|
|
2622
2330
|
const orgCache = /* @__PURE__ */ new Map();
|
|
@@ -2703,7 +2411,7 @@ function createCauthy(given = {}) {
|
|
|
2703
2411
|
const flow = { state: random(), verifier: random(48), returnTo: localPath(url.searchParams.get("return_to"), afterSignIn) };
|
|
2704
2412
|
const challenge = b64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(flow.verifier))));
|
|
2705
2413
|
const q = new URLSearchParams({
|
|
2706
|
-
client_id:
|
|
2414
|
+
client_id: await signInClient(),
|
|
2707
2415
|
redirect_uri: `${originOf(request)}${basePath}/callback`,
|
|
2708
2416
|
response_type: "code",
|
|
2709
2417
|
state: flow.state,
|
|
@@ -2765,28 +2473,17 @@ function createCauthy(given = {}) {
|
|
|
2765
2473
|
);
|
|
2766
2474
|
};
|
|
2767
2475
|
if (!res.ok && res.code === "email_not_confirmed") return unconfirmed();
|
|
2768
|
-
|
|
2769
|
-
try {
|
|
2770
|
-
s = res.ok ? await stored(res.body) : null;
|
|
2771
|
-
} catch (err) {
|
|
2772
|
-
if (!(err instanceof CauthyApiError && err.code === "email_not_confirmed")) throw err;
|
|
2773
|
-
return unconfirmed();
|
|
2774
|
-
}
|
|
2476
|
+
const s = res.ok ? await stored(res.body) : null;
|
|
2775
2477
|
if (s === null) return failed(request, "exchange_failed");
|
|
2776
2478
|
return redirect(flow.returnTo, [...await write(request, s, had), writeCookie(flowCookie, "", attrs(request, 0)), writeCookie(againCookie, "", attrs(request, 0))]);
|
|
2777
2479
|
}
|
|
2778
2480
|
async function logout(request) {
|
|
2779
2481
|
const { had, value } = await read(request);
|
|
2780
2482
|
if (value !== null) {
|
|
2781
|
-
|
|
2782
|
-
await doFetch(`${issuer}/v1/oauth/revoke`, {
|
|
2783
|
-
method: "POST",
|
|
2784
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
2785
|
-
body: body2
|
|
2786
|
-
}).catch(() => void 0);
|
|
2483
|
+
await revoke(value.rt);
|
|
2787
2484
|
orgCache.delete(value.at);
|
|
2788
2485
|
}
|
|
2789
|
-
const q = new URLSearchParams({ client_id:
|
|
2486
|
+
const q = new URLSearchParams({ client_id: signInClientNow(), post_logout_redirect_uri: `${originOf(request)}${afterSignOut}` });
|
|
2790
2487
|
return redirect(`${issuer}/v1/oauth/logout?${q}`, clearParts(cookieName, attrs(request, 0), had));
|
|
2791
2488
|
}
|
|
2792
2489
|
const STEP_SECONDS = 600;
|
|
@@ -2804,7 +2501,7 @@ function createCauthy(given = {}) {
|
|
|
2804
2501
|
createdAt: String(d?.created_at ?? ""),
|
|
2805
2502
|
lastUsedAt: d?.last_used_at ? String(d.last_used_at) : null
|
|
2806
2503
|
});
|
|
2807
|
-
const
|
|
2504
|
+
const UUID2 = /^[0-9a-f-]{36}$/i;
|
|
2808
2505
|
const seatOf = (d) => ({
|
|
2809
2506
|
id: String(d?.id ?? ""),
|
|
2810
2507
|
system: String(d?.system ?? ""),
|
|
@@ -2909,10 +2606,10 @@ function createCauthy(given = {}) {
|
|
|
2909
2606
|
...page ? { "x-cauthy-link-to": `${originOf(request)}${page}` } : { "x-cauthy-links": "hosted" },
|
|
2910
2607
|
// Whose request it is: what Cauthy mails goes in this application's
|
|
2911
2608
|
// name, under its logo.
|
|
2912
|
-
...
|
|
2609
|
+
...signInClientNow() ? { "x-cauthy-client-id": signInClientNow() } : {},
|
|
2913
2610
|
...await passedOn(request),
|
|
2914
2611
|
...bearer2 ? { authorization: `Bearer ${bearer2}` } : {},
|
|
2915
|
-
// The person asked to stay signed in: Cauthy keeps the session long if the
|
|
2612
|
+
// The person asked to stay signed in: Cauthy keeps the session long if the workspace allows it.
|
|
2916
2613
|
...remember ? { "x-cauthy-remember": "1" } : {},
|
|
2917
2614
|
// The answer to the workspace's Turnstile challenge, from the
|
|
2918
2615
|
// person's browser: Cauthy checks it at the doors that ask for it.
|
|
@@ -3053,7 +2750,7 @@ function createCauthy(given = {}) {
|
|
|
3053
2750
|
}
|
|
3054
2751
|
if (route === "/look" && !post) {
|
|
3055
2752
|
if (lookCache === null || lookCache.until < Date.now()) {
|
|
3056
|
-
const res = await ask(request, "GET", `/v1/auth/look/${encodeURIComponent(
|
|
2753
|
+
const res = await ask(request, "GET", `/v1/auth/look/${encodeURIComponent(signInClientNow())}`);
|
|
3057
2754
|
lookCache = { until: Date.now() + LOOK_TTL_MS, look: res.error ? {} : res.data ?? {} };
|
|
3058
2755
|
}
|
|
3059
2756
|
return json(lookCache.look);
|
|
@@ -3078,7 +2775,7 @@ function createCauthy(given = {}) {
|
|
|
3078
2775
|
signUp: settings.data?.allow_signup !== false && settings.data?.signup?.mode !== "invite_only",
|
|
3079
2776
|
// The application's system makes an organization per sign-up: the form asks the company's name.
|
|
3080
2777
|
...settings.data?.signup?.organization?.ask === true ? { organization: { ask: true, required: settings.data.signup.organization.required === true } } : {},
|
|
3081
|
-
// Whether the person may ask to stay signed in: the
|
|
2778
|
+
// Whether the person may ask to stay signed in: the workspace's setting in Cauthy (persistent_sessions).
|
|
3082
2779
|
...typeof settings.data?.persistent_sessions === "boolean" ? { persistentSessions: settings.data.persistent_sessions } : {},
|
|
3083
2780
|
// A sign-in link needs a page of the application to open.
|
|
3084
2781
|
magicLink: settings.data?.allow_magic_link === true && pages.signIn !== void 0,
|
|
@@ -3242,7 +2939,7 @@ function createCauthy(given = {}) {
|
|
|
3242
2939
|
let path = "/v1/auth/me/organizations";
|
|
3243
2940
|
if (route !== "/me/organizations") {
|
|
3244
2941
|
const id = url.searchParams.get("id") ?? "";
|
|
3245
|
-
if (!
|
|
2942
|
+
if (!UUID2.test(id)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
|
|
3246
2943
|
path += `/${id}/${route.endsWith("/roles") ? "roles" : "members"}`;
|
|
3247
2944
|
} else if (url.searchParams.get("system")) {
|
|
3248
2945
|
path += `?system=${encodeURIComponent(url.searchParams.get("system"))}`;
|
|
@@ -3303,8 +3000,7 @@ function createCauthy(given = {}) {
|
|
|
3303
3000
|
if (route === "/sign-out") {
|
|
3304
3001
|
const { had, value } = await read(request);
|
|
3305
3002
|
if (value !== null) {
|
|
3306
|
-
|
|
3307
|
-
await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form }).catch(() => void 0);
|
|
3003
|
+
await revoke(value.rt);
|
|
3308
3004
|
orgCache.delete(value.at);
|
|
3309
3005
|
}
|
|
3310
3006
|
const h = new Headers();
|
|
@@ -3531,7 +3227,7 @@ function createCauthy(given = {}) {
|
|
|
3531
3227
|
}
|
|
3532
3228
|
if (route === "/me/organizations/rename" || route === "/me/organizations/archive" || route === "/me/organizations/delete") {
|
|
3533
3229
|
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
3534
|
-
if (!
|
|
3230
|
+
if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
|
|
3535
3231
|
const path = `/v1/auth/me/organizations/${org}`;
|
|
3536
3232
|
if (route === "/me/organizations/rename") {
|
|
3537
3233
|
const bad = checkOrganizationName(b.name);
|
|
@@ -3548,7 +3244,7 @@ function createCauthy(given = {}) {
|
|
|
3548
3244
|
}
|
|
3549
3245
|
if (route.startsWith("/me/organizations/members/")) {
|
|
3550
3246
|
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
3551
|
-
if (!
|
|
3247
|
+
if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
|
|
3552
3248
|
const base = `/v1/auth/me/organizations/${org}/members`;
|
|
3553
3249
|
if (route === "/me/organizations/members/add") {
|
|
3554
3250
|
const bad = checkEmail(b.email);
|
|
@@ -3559,7 +3255,7 @@ function createCauthy(given = {}) {
|
|
|
3559
3255
|
email: String(b.email).trim(),
|
|
3560
3256
|
role,
|
|
3561
3257
|
name: typeof b.name === "string" ? b.name.trim() : "",
|
|
3562
|
-
client_id:
|
|
3258
|
+
client_id: signInClientNow(),
|
|
3563
3259
|
only_new: true,
|
|
3564
3260
|
...typeof b.locale === "string" && b.locale ? { locale: b.locale } : {}
|
|
3565
3261
|
}, pages.acceptInvite, mine.token);
|
|
@@ -3568,7 +3264,7 @@ function createCauthy(given = {}) {
|
|
|
3568
3264
|
return json({ status: "added", member: orgMemberOf(res.data) }, 200, mine.headers);
|
|
3569
3265
|
}
|
|
3570
3266
|
const user = typeof b.userId === "string" ? b.userId : "";
|
|
3571
|
-
if (!
|
|
3267
|
+
if (!UUID2.test(user)) return problem(400, "invalid_member", "Say which member.", void 0, mine.headers);
|
|
3572
3268
|
if (route === "/me/organizations/members/role") {
|
|
3573
3269
|
const role = typeof b.role === "string" ? b.role : "";
|
|
3574
3270
|
if (role === "") return problem(400, "invalid_role", "Choose a role.", "role", mine.headers);
|
|
@@ -3583,7 +3279,7 @@ function createCauthy(given = {}) {
|
|
|
3583
3279
|
return json({ status: "removed", userId: user }, 200, mine.headers);
|
|
3584
3280
|
}
|
|
3585
3281
|
if (route === "/me/organizations/members/invite") {
|
|
3586
|
-
const res = await ask(request, "POST", `${base}/${user}/invite`, { client_id:
|
|
3282
|
+
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);
|
|
3587
3283
|
if (res.error) return refused(res);
|
|
3588
3284
|
return json({ status: "sent", userId: user }, 200, mine.headers);
|
|
3589
3285
|
}
|
|
@@ -3701,18 +3397,15 @@ function createCauthy(given = {}) {
|
|
|
3701
3397
|
const url = new URL(request.url);
|
|
3702
3398
|
const inside = url.pathname === basePath || url.pathname.startsWith(`${basePath}/`);
|
|
3703
3399
|
const route = inside ? withoutTrailingSlashes(url.pathname.slice(basePath.length)) || "/" : null;
|
|
3704
|
-
const published3 = pact !== void 0 && url.pathname === contract?.jwksPath && (request.method === "GET" || request.method === "HEAD");
|
|
3705
3400
|
if (identity && route !== null) {
|
|
3706
3401
|
const unready = await identityGate();
|
|
3707
3402
|
if (unready) return finish(unready, route);
|
|
3708
3403
|
}
|
|
3709
3404
|
try {
|
|
3710
|
-
return finish(await serve(request), route
|
|
3405
|
+
return finish(await serve(request), route);
|
|
3711
3406
|
} catch (err) {
|
|
3712
3407
|
if (err instanceof UnknownOriginError) return finish(problem(400, "unknown_origin", err.message), route);
|
|
3713
|
-
|
|
3714
|
-
const wantsPage = request.method === "GET" && (request.headers.get("sec-fetch-mode") ?? "navigate") === "navigate";
|
|
3715
|
-
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);
|
|
3408
|
+
throw err;
|
|
3716
3409
|
}
|
|
3717
3410
|
}
|
|
3718
3411
|
async function identityGate() {
|
|
@@ -3736,7 +3429,6 @@ function createCauthy(given = {}) {
|
|
|
3736
3429
|
}
|
|
3737
3430
|
async function serve(request) {
|
|
3738
3431
|
const url = new URL(request.url);
|
|
3739
|
-
if (pact && url.pathname === contract?.jwksPath && (request.method === "GET" || request.method === "HEAD")) return pact.jwks();
|
|
3740
3432
|
if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) return new Response("Not found", { status: 404 });
|
|
3741
3433
|
const route = withoutTrailingSlashes(url.pathname.slice(basePath.length)) || "/";
|
|
3742
3434
|
const get = request.method === "GET" || request.method === "HEAD";
|
|
@@ -3760,7 +3452,7 @@ function createCauthy(given = {}) {
|
|
|
3760
3452
|
return redirect(back, r.cookies);
|
|
3761
3453
|
}
|
|
3762
3454
|
if (route === "/account" && get) {
|
|
3763
|
-
const q = new URLSearchParams({ client_id:
|
|
3455
|
+
const q = new URLSearchParams({ client_id: signInClientNow() });
|
|
3764
3456
|
const back = localPath(url.searchParams.get("return_to"), "");
|
|
3765
3457
|
if (back !== "") q.set("return_to", `${originOf(request)}${back}`);
|
|
3766
3458
|
const locale = url.searchParams.get("locale");
|
|
@@ -3779,7 +3471,7 @@ function createCauthy(given = {}) {
|
|
|
3779
3471
|
const asker = await signedInAs(request);
|
|
3780
3472
|
if ("response" in asker) return asker.response;
|
|
3781
3473
|
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
3782
|
-
if (!
|
|
3474
|
+
if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, asker.headers);
|
|
3783
3475
|
const named = (v) => typeof v === "string" && v !== "" && v.length <= 200;
|
|
3784
3476
|
if (!named(b.resource) || !named(b.action)) return problem(400, "bad_request", "Say which resource and action.", void 0, asker.headers);
|
|
3785
3477
|
const attributes2 = b.attributes;
|
|
@@ -3802,8 +3494,7 @@ function createCauthy(given = {}) {
|
|
|
3802
3494
|
}
|
|
3803
3495
|
}
|
|
3804
3496
|
async function protect(request) {
|
|
3805
|
-
const
|
|
3806
|
-
const value = read1.value !== null && unverified(read1.value.u) ? null : read1.value;
|
|
3497
|
+
const { value } = await read(request);
|
|
3807
3498
|
if (value !== null && fresh(value)) return { session: toSession(value) };
|
|
3808
3499
|
const url = new URL(request.url);
|
|
3809
3500
|
const state = value === null ? "none" : "expired";
|
|
@@ -3816,85 +3507,36 @@ function createCauthy(given = {}) {
|
|
|
3816
3507
|
return { response: redirect(to) };
|
|
3817
3508
|
}
|
|
3818
3509
|
const replay = given.webhookReplay ?? memoryReplayStore();
|
|
3819
|
-
let lateSelf;
|
|
3820
3510
|
const tokenVerifier = (o = {}) => {
|
|
3821
|
-
if (
|
|
3822
|
-
if (contract && o.clientId !== void 0 && o.clientId !== contract.clientId) {
|
|
3823
|
-
throw new CauthyConfigError(`clientId is the contract's (${contract.clientId}): remove it from the call`);
|
|
3824
|
-
}
|
|
3511
|
+
if (identity) {
|
|
3825
3512
|
let built;
|
|
3826
3513
|
return async (from) => {
|
|
3827
|
-
const id = await
|
|
3514
|
+
const id = await identity.ready();
|
|
3828
3515
|
if (built?.from !== id) {
|
|
3829
|
-
built = { from: id, verify: createTokenVerifier({
|
|
3830
|
-
issuer,
|
|
3831
|
-
tenantId: contract?.tenantId ?? id.tenant.id,
|
|
3832
|
-
audience: id.audience,
|
|
3833
|
-
fetch: given.fetch,
|
|
3834
|
-
...contract ? { clientId: contract.clientId } : {},
|
|
3835
|
-
...o
|
|
3836
|
-
}) };
|
|
3516
|
+
built = { from: id, verify: createTokenVerifier({ issuer, tenantId: id.tenant.id, audience: id.audience, fetch: given.fetch, ...o }) };
|
|
3837
3517
|
}
|
|
3838
3518
|
return built.verify(from);
|
|
3839
3519
|
};
|
|
3840
3520
|
}
|
|
3841
|
-
if (contract && o.clientId !== void 0 && o.clientId !== contract.clientId) {
|
|
3842
|
-
throw new CauthyConfigError(`clientId is the contract's (${contract.clientId}): remove it from the call`);
|
|
3843
|
-
}
|
|
3844
3521
|
return createTokenVerifier({
|
|
3845
3522
|
issuer,
|
|
3846
|
-
tenantId:
|
|
3523
|
+
tenantId: given.tenantId,
|
|
3847
3524
|
audience: given.audience,
|
|
3848
3525
|
fetch: given.fetch,
|
|
3849
|
-
...contract ? { clientId: contract.clientId } : {},
|
|
3850
3526
|
...o
|
|
3851
3527
|
});
|
|
3852
3528
|
};
|
|
3853
3529
|
const webhook = (request, o = {}) => verifyWebhook(request, { issuer, url: o.url ?? `${originOf(request)}${new URL(request.url).pathname}`, jwks: o.jwks, now: o.now, replay });
|
|
3854
|
-
async function keyJwks() {
|
|
3855
|
-
const s = await fromKey.signer;
|
|
3856
|
-
const keys2 = [{ ...s.publicJwk, kid: s.kid, alg: "EdDSA", use: "sig" }];
|
|
3857
|
-
if (fromKey.previousJwk) keys2.push({ ...await fromKey.previousJwk, alg: "EdDSA", use: "sig" });
|
|
3858
|
-
if (ownKeys) keys2.push(...(await ownKeys.published()).keys);
|
|
3859
|
-
return new Response(JSON.stringify({ keys: keys2 }), {
|
|
3860
|
-
headers: { "content-type": "application/json; charset=utf-8", "cache-control": `public, max-age=${JWKS_MAX_AGE_S}` }
|
|
3861
|
-
});
|
|
3862
|
-
}
|
|
3863
|
-
const noStore = () => {
|
|
3864
|
-
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");
|
|
3865
|
-
};
|
|
3866
|
-
const keyOnly = {
|
|
3867
|
-
current: async () => noStore(),
|
|
3868
|
-
published: async () => ({ keys: [] }),
|
|
3869
|
-
rotate: async () => noStore(),
|
|
3870
|
-
reseal: async () => 0
|
|
3871
|
-
};
|
|
3872
|
-
const pact = contract && fromKey ? {
|
|
3873
|
-
contract,
|
|
3874
|
-
keys: ownKeys ?? keyOnly,
|
|
3875
|
-
jwks: keyJwks,
|
|
3876
|
-
status: () => ownSystem.request("GET", `/v1/pact/status?system=${encodeURIComponent(contract.system)}`)
|
|
3877
|
-
} : contract && ownKeys ? {
|
|
3878
|
-
contract,
|
|
3879
|
-
keys: ownKeys,
|
|
3880
|
-
async jwks() {
|
|
3881
|
-
return new Response(JSON.stringify(await ownKeys.published()), {
|
|
3882
|
-
headers: { "content-type": "application/json; charset=utf-8", "cache-control": `public, max-age=${JWKS_MAX_AGE_S}` }
|
|
3883
|
-
});
|
|
3884
|
-
},
|
|
3885
|
-
status: () => ownSystem.request("GET", `/v1/pact/status?system=${encodeURIComponent(contract.system)}`)
|
|
3886
|
-
} : void 0;
|
|
3887
3530
|
const configured = () => ({
|
|
3888
3531
|
issuer,
|
|
3889
3532
|
audience: given.audience ?? env3("CAUTHY_AUDIENCE") ?? "",
|
|
3890
|
-
tenant: { id:
|
|
3533
|
+
tenant: { id: given.tenantId ?? env3("CAUTHY_TENANT_ID") ?? "", slug: tenant },
|
|
3891
3534
|
system,
|
|
3892
3535
|
client_id: clientId,
|
|
3893
3536
|
sign_in_client_id: clientId,
|
|
3894
3537
|
app_urls: baseURL ? [withoutTrailingSlashes(baseURL)] : [],
|
|
3895
3538
|
callback_path: `${basePath}/callback`,
|
|
3896
|
-
key: null
|
|
3897
|
-
contract: contract ? { link: contract.link, system_proof: contract.systemProof, require_verified_email: contract.requireVerifiedEmail } : null
|
|
3539
|
+
key: null
|
|
3898
3540
|
});
|
|
3899
3541
|
return {
|
|
3900
3542
|
handler,
|
|
@@ -3916,7 +3558,6 @@ function createCauthy(given = {}) {
|
|
|
3916
3558
|
check,
|
|
3917
3559
|
toPublic,
|
|
3918
3560
|
urls,
|
|
3919
|
-
...pact ? { pact } : {},
|
|
3920
3561
|
options: { issuer, clientId, basePath, afterSignIn, afterSignOut, cookieName }
|
|
3921
3562
|
};
|
|
3922
3563
|
}
|
|
@@ -4000,7 +3641,6 @@ export {
|
|
|
4000
3641
|
KEY_REPLACES,
|
|
4001
3642
|
MIN_PASSWORD,
|
|
4002
3643
|
PROTOCOL_VERSION,
|
|
4003
|
-
STANDARD_VARIABLES,
|
|
4004
3644
|
can,
|
|
4005
3645
|
checkCode,
|
|
4006
3646
|
checkEmail,
|
|
@@ -4016,7 +3656,6 @@ export {
|
|
|
4016
3656
|
definePolicy,
|
|
4017
3657
|
describePasswordPolicy,
|
|
4018
3658
|
explainPasswordProblems,
|
|
4019
|
-
failClosed,
|
|
4020
3659
|
memoryReplayStore,
|
|
4021
3660
|
parseKey,
|
|
4022
3661
|
passwordPolicyOf,
|
|
@@ -4027,7 +3666,6 @@ export {
|
|
|
4027
3666
|
pushCatalog,
|
|
4028
3667
|
pushPolicy,
|
|
4029
3668
|
pushTheme,
|
|
4030
|
-
readContract,
|
|
4031
3669
|
remoteKeys,
|
|
4032
3670
|
themeFromStackmyth,
|
|
4033
3671
|
tokensFromStackmyth,
|