@cauthyhq/node 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +13 -27
- package/dist/{cauthy-CCSSj3Lg.d.mts → cauthy-CsWUgyv_.d.mts} +42 -145
- package/dist/{cauthy-C7gZ-pbv.d.ts → cauthy-DHLBEuqP.d.ts} +42 -145
- 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 +169 -524
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +162 -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 +52 -50
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +52 -50
- package/dist/testing.mjs.map +1 -1
- package/package.json +4 -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;
|
|
@@ -2341,9 +2109,18 @@ function redirect(location, cookies = []) {
|
|
|
2341
2109
|
function conditionalOf(c) {
|
|
2342
2110
|
return { resource: String(c?.resource ?? ""), action: String(c?.action ?? ""), effect: c?.effect === "deny" ? "deny" : "allow" };
|
|
2343
2111
|
}
|
|
2112
|
+
function refuseContracts(given) {
|
|
2113
|
+
const options = given;
|
|
2114
|
+
const set = [
|
|
2115
|
+
...["contract", "keys", "rootKey", "rootKeyPrevious"].filter((name) => options[name] !== void 0).map((name) => `the ${name} option`),
|
|
2116
|
+
...["PACT_ROOT_KEY", "PACT_ROOT_KEY_PREVIOUS"].filter((name) => (env3(name) ?? "") !== "")
|
|
2117
|
+
];
|
|
2118
|
+
if (set.length === 0) return;
|
|
2119
|
+
throw new CauthyConfigError(`contracts are retired: run on CAUTHY_KEY alone, and remove ${set.join(", ")}. An application without a key makes it on its card in Cauthy's dashboard; the same key keeps its people signed in. Protect sign-in with the "Protect sign-in" switches on that card. Sessions sealed with PACT_ROOT_KEY are no longer read.`);
|
|
2120
|
+
}
|
|
2344
2121
|
function byKey(given) {
|
|
2345
|
-
const
|
|
2346
|
-
if (
|
|
2122
|
+
const text = given.key ?? env3("CAUTHY_KEY");
|
|
2123
|
+
if (text === void 0 || text === "") return void 0;
|
|
2347
2124
|
refuseBesideKey(typeof process !== "undefined" ? process.env : void 0);
|
|
2348
2125
|
for (const [name, value] of [
|
|
2349
2126
|
["issuer", given.issuer],
|
|
@@ -2354,12 +2131,11 @@ function byKey(given) {
|
|
|
2354
2131
|
["tenant", given.tenant],
|
|
2355
2132
|
["audience", given.audience],
|
|
2356
2133
|
["tenantId", given.tenantId],
|
|
2357
|
-
["systemKey", given.systemKey]
|
|
2358
|
-
["rootKey", given.rootKey]
|
|
2134
|
+
["systemKey", given.systemKey]
|
|
2359
2135
|
]) {
|
|
2360
2136
|
if (value !== void 0) throw new CauthyConfigError(`with CAUTHY_KEY, ${name} comes from CAUTHY_KEY: do not pass it`);
|
|
2361
2137
|
}
|
|
2362
|
-
const key = parseKey(
|
|
2138
|
+
const key = parseKey(text);
|
|
2363
2139
|
const prevText = given.keyPrevious ?? env3("CAUTHY_KEY_PREVIOUS");
|
|
2364
2140
|
let previous;
|
|
2365
2141
|
if (prevText) {
|
|
@@ -2374,14 +2150,8 @@ function byKey(given) {
|
|
|
2374
2150
|
throw new CauthyKeyError("key_previous_mismatch", "CAUTHY_KEY_PREVIOUS names another Cauthy or client than CAUTHY_KEY");
|
|
2375
2151
|
}
|
|
2376
2152
|
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
2153
|
if (legacySecret) deprecated("CAUTHY_SECRET beside CAUTHY_KEY", "It is read only for sessions made before the move: remove it after the rotation window.");
|
|
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 };
|
|
2154
|
+
return { key, previous, legacySecret };
|
|
2385
2155
|
}
|
|
2386
2156
|
var UnknownOriginError = class extends Error {
|
|
2387
2157
|
constructor(origin, registered) {
|
|
@@ -2392,78 +2162,41 @@ var UnknownOriginError = class extends Error {
|
|
|
2392
2162
|
origin;
|
|
2393
2163
|
registered;
|
|
2394
2164
|
};
|
|
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
2165
|
var toldOfHeader = false;
|
|
2424
2166
|
var toldOfUnopened = false;
|
|
2425
2167
|
function toldUnopened() {
|
|
2426
2168
|
if (toldOfUnopened) return;
|
|
2427
2169
|
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
|
|
2170
|
+
console.warn("@cauthyhq/node: a session cookie arrived that no key of this application opens; it was cleared and the person is signed out. Moving to CAUTHY_KEY, keep the secret that sealed the old sessions (CAUTHY_SECRET) beside it for the rotation window; rotating the key, set CAUTHY_KEY_PREVIOUS.");
|
|
2429
2171
|
}
|
|
2430
2172
|
function createCauthy(given = {}) {
|
|
2173
|
+
refuseContracts(given);
|
|
2431
2174
|
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") ?? "";
|
|
2175
|
+
const issuer = withoutTrailingSlashes(keyed?.key.issuer ?? given.issuer ?? env3("CAUTHY_ISSUER") ?? "");
|
|
2176
|
+
const clientId = keyed?.key.clientId ?? given.clientId ?? env3("CAUTHY_CLIENT_ID") ?? "";
|
|
2177
|
+
const clientSecret = keyed ? void 0 : given.clientSecret ?? env3("CAUTHY_CLIENT_SECRET");
|
|
2178
|
+
const secret = keyed ? "" : given.secret ?? env3("CAUTHY_SECRET") ?? "";
|
|
2179
|
+
const system = keyed ? "" : given.system ?? env3("CAUTHY_SYSTEM") ?? "";
|
|
2180
|
+
const tenant = keyed ? "" : given.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
2441
2181
|
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address, e.g. https://id.example.com");
|
|
2442
2182
|
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
2443
|
-
if (!
|
|
2183
|
+
if (!keyed && secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
|
|
2444
2184
|
const fromKey = keyed ? deriveFromKey(keyed.key, keyed.previous) : void 0;
|
|
2445
2185
|
let signingKid;
|
|
2446
2186
|
fromKey?.signer.then((s) => {
|
|
2447
2187
|
signingKid = s.kid;
|
|
2448
2188
|
}, () => 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
2189
|
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) {
|
|
2190
|
+
const withLegacy = async (own) => [...await own, ...legacySingle ? [await legacySingle] : []];
|
|
2191
|
+
const single = fromKey ? Promise.resolve([]) : keyOf(secret).then((k) => [k]);
|
|
2192
|
+
const cookieKeys = fromKey ? withLegacy(fromKey.cookie) : single;
|
|
2193
|
+
const flowKeys = fromKey ? withLegacy(fromKey.flow) : single;
|
|
2194
|
+
if (!keyed) {
|
|
2461
2195
|
deprecated(
|
|
2462
2196
|
"Configuring with CAUTHY_ISSUER, CAUTHY_CLIENT_ID, CAUTHY_CLIENT_SECRET, CAUTHY_SECRET and the other CAUTHY_* variables (or their options)",
|
|
2463
2197
|
"Connect the application in Cauthy's dashboard and use the one variable CAUTHY_KEY."
|
|
2464
2198
|
);
|
|
2465
2199
|
}
|
|
2466
|
-
const ownKeys = derived && given.keys ? pactKeys(given.keys, derived.seal) : legacyRings && contract && given.keys ? pactKeys(given.keys, legacyRings.seal) : void 0;
|
|
2467
2200
|
const baseURL = given.baseURL ?? (keyed ? void 0 : env3("CAUTHY_BASE_URL"));
|
|
2468
2201
|
if (baseURL !== void 0 && baseURL !== "" && !/^https?:\/\/[^/]+\/*$/.test(baseURL)) {
|
|
2469
2202
|
throw new CauthyConfigError("baseURL (CAUTHY_BASE_URL) must be the application's public origin, e.g. https://app.example.com");
|
|
@@ -2500,21 +2233,10 @@ function createCauthy(given = {}) {
|
|
|
2500
2233
|
acceptInviteUrl,
|
|
2501
2234
|
fetch: given.fetch,
|
|
2502
2235
|
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}`)
|
|
2236
|
+
// On CAUTHY_KEY the key proves the key's own client.
|
|
2237
|
+
...fromKey ? { signer: { clientId, signer: fromKey.signer }, currentSystem: () => identity?.current()?.system ?? "" } : {}
|
|
2516
2238
|
});
|
|
2517
|
-
const identity =
|
|
2239
|
+
const identity = keyed ? createIdentity({
|
|
2518
2240
|
issuer,
|
|
2519
2241
|
token: () => ownSystem.token(),
|
|
2520
2242
|
fetch: deadline(given.fetch, requestTimeout),
|
|
@@ -2524,6 +2246,8 @@ function createCauthy(given = {}) {
|
|
|
2524
2246
|
identity?.ready().catch(() => {
|
|
2525
2247
|
});
|
|
2526
2248
|
const tenantNow = () => identity?.current()?.tenant.slug ?? tenant;
|
|
2249
|
+
const signInClientNow = () => identity?.current()?.sign_in_client_id || clientId;
|
|
2250
|
+
const signInClient = async () => identity ? (await identity.ready()).sign_in_client_id || clientId : clientId;
|
|
2527
2251
|
const systemNow = () => identity?.current()?.system ?? system;
|
|
2528
2252
|
const requestOrigin = (request) => {
|
|
2529
2253
|
const url = new URL(request.url);
|
|
@@ -2548,7 +2272,7 @@ function createCauthy(given = {}) {
|
|
|
2548
2272
|
};
|
|
2549
2273
|
const attrs = (request, maxAge) => ({ secure: safeOrigin(request).startsWith("https://"), maxAge });
|
|
2550
2274
|
async function clientAuth(form) {
|
|
2551
|
-
if (fromKey &&
|
|
2275
|
+
if (fromKey && form.get("client_id") === clientId) {
|
|
2552
2276
|
const s = await fromKey.signer;
|
|
2553
2277
|
form.set("client_assertion_type", ASSERTION_TYPE2);
|
|
2554
2278
|
form.set("client_assertion", await s.assertion(clientId, `${issuer}/v1/oauth/token`));
|
|
@@ -2558,8 +2282,8 @@ function createCauthy(given = {}) {
|
|
|
2558
2282
|
return form;
|
|
2559
2283
|
}
|
|
2560
2284
|
async function token(form, proof = {}) {
|
|
2561
|
-
const body2 = await clientAuth(new URLSearchParams({ client_id: clientId, ...form }));
|
|
2562
2285
|
try {
|
|
2286
|
+
const body2 = await clientAuth(new URLSearchParams({ client_id: await signInClient(), ...form }));
|
|
2563
2287
|
const res = await doFetch(`${issuer}/v1/oauth/token`, {
|
|
2564
2288
|
method: "POST",
|
|
2565
2289
|
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json", ...proof },
|
|
@@ -2597,18 +2321,16 @@ function createCauthy(given = {}) {
|
|
|
2597
2321
|
}
|
|
2598
2322
|
const kindOf = (body2, was) => (typeof body2?.persistent === "boolean" ? body2.persistent : was?.p === 1) ? { p: 1 } : {};
|
|
2599
2323
|
async function revoke(refreshToken) {
|
|
2600
|
-
|
|
2601
|
-
|
|
2324
|
+
try {
|
|
2325
|
+
const form = await clientAuth(new URLSearchParams({ client_id: await signInClient(), token: refreshToken }));
|
|
2326
|
+
await doFetch(`${issuer}/v1/oauth/revoke`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form });
|
|
2327
|
+
} catch {
|
|
2328
|
+
}
|
|
2602
2329
|
}
|
|
2603
|
-
const unverified = (u) => contract?.requireVerifiedEmail === true && !u.emailVerified;
|
|
2604
2330
|
async function stored(body2, was) {
|
|
2605
2331
|
if (typeof body2?.access_token !== "string" || typeof body2?.refresh_token !== "string") return null;
|
|
2606
2332
|
const who = await userinfo(body2.access_token);
|
|
2607
2333
|
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
2334
|
const lifetime = typeof body2.expires_in === "number" && body2.expires_in > 0 ? body2.expires_in : 300;
|
|
2613
2335
|
return { at: body2.access_token, rt: body2.refresh_token, exp: Math.floor(Date.now() / 1e3) + lifetime, ...who, ...kindOf(body2, was) };
|
|
2614
2336
|
}
|
|
@@ -2636,13 +2358,7 @@ function createCauthy(given = {}) {
|
|
|
2636
2358
|
running = (async () => {
|
|
2637
2359
|
const res = await token({ grant_type: "refresh_token", refresh_token: rt });
|
|
2638
2360
|
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
|
-
}
|
|
2361
|
+
const next = await stored(res.body, was);
|
|
2646
2362
|
if (next !== null) return next;
|
|
2647
2363
|
if (typeof res.body?.access_token !== "string" || typeof res.body?.refresh_token !== "string") return "unreachable";
|
|
2648
2364
|
const lifetime = typeof res.body.expires_in === "number" && res.body.expires_in > 0 ? res.body.expires_in : 300;
|
|
@@ -2664,7 +2380,6 @@ function createCauthy(given = {}) {
|
|
|
2664
2380
|
if (present) toldUnopened();
|
|
2665
2381
|
return { session: null, status: "unauthenticated", cookies: present ? clearParts(cookieName, attrs(request, 0), had) : [] };
|
|
2666
2382
|
}
|
|
2667
|
-
if (unverified(value.u)) return { session: null, status: "unauthenticated", cookies: clearParts(cookieName, attrs(request, 0), had) };
|
|
2668
2383
|
if (fresh(value)) return { session: toSession(value), status: "authenticated", cookies: stale ? await write(request, value, had) : [] };
|
|
2669
2384
|
const renewed = await renew(value);
|
|
2670
2385
|
if (renewed === "ended") {
|
|
@@ -2676,7 +2391,7 @@ function createCauthy(given = {}) {
|
|
|
2676
2391
|
async function getSession(request, responseHeaders) {
|
|
2677
2392
|
if (responseHeaders === void 0) {
|
|
2678
2393
|
const { value } = await read(request);
|
|
2679
|
-
return value !== null && fresh(value)
|
|
2394
|
+
return value !== null && fresh(value) ? toSession(value) : null;
|
|
2680
2395
|
}
|
|
2681
2396
|
const r = await resolve2(request);
|
|
2682
2397
|
for (const c of r.cookies) responseHeaders.append("set-cookie", c);
|
|
@@ -2684,7 +2399,7 @@ function createCauthy(given = {}) {
|
|
|
2684
2399
|
}
|
|
2685
2400
|
async function status(request) {
|
|
2686
2401
|
const { value } = await read(request);
|
|
2687
|
-
if (value === null
|
|
2402
|
+
if (value === null) return "unauthenticated";
|
|
2688
2403
|
return fresh(value) ? "authenticated" : "expired";
|
|
2689
2404
|
}
|
|
2690
2405
|
const orgCache = /* @__PURE__ */ new Map();
|
|
@@ -2771,7 +2486,7 @@ function createCauthy(given = {}) {
|
|
|
2771
2486
|
const flow = { state: random(), verifier: random(48), returnTo: localPath(url.searchParams.get("return_to"), afterSignIn) };
|
|
2772
2487
|
const challenge = b64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(flow.verifier))));
|
|
2773
2488
|
const q = new URLSearchParams({
|
|
2774
|
-
client_id:
|
|
2489
|
+
client_id: await signInClient(),
|
|
2775
2490
|
redirect_uri: `${originOf(request)}${basePath}/callback`,
|
|
2776
2491
|
response_type: "code",
|
|
2777
2492
|
state: flow.state,
|
|
@@ -2833,28 +2548,17 @@ function createCauthy(given = {}) {
|
|
|
2833
2548
|
);
|
|
2834
2549
|
};
|
|
2835
2550
|
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
|
-
}
|
|
2551
|
+
const s = res.ok ? await stored(res.body) : null;
|
|
2843
2552
|
if (s === null) return failed(request, "exchange_failed");
|
|
2844
2553
|
return redirect(flow.returnTo, [...await write(request, s, had), writeCookie(flowCookie, "", attrs(request, 0)), writeCookie(againCookie, "", attrs(request, 0))]);
|
|
2845
2554
|
}
|
|
2846
2555
|
async function logout(request) {
|
|
2847
2556
|
const { had, value } = await read(request);
|
|
2848
2557
|
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);
|
|
2558
|
+
await revoke(value.rt);
|
|
2855
2559
|
orgCache.delete(value.at);
|
|
2856
2560
|
}
|
|
2857
|
-
const q = new URLSearchParams({ client_id:
|
|
2561
|
+
const q = new URLSearchParams({ client_id: signInClientNow(), post_logout_redirect_uri: `${originOf(request)}${afterSignOut}` });
|
|
2858
2562
|
return redirect(`${issuer}/v1/oauth/logout?${q}`, clearParts(cookieName, attrs(request, 0), had));
|
|
2859
2563
|
}
|
|
2860
2564
|
const STEP_SECONDS = 600;
|
|
@@ -2872,7 +2576,7 @@ function createCauthy(given = {}) {
|
|
|
2872
2576
|
createdAt: String(d?.created_at ?? ""),
|
|
2873
2577
|
lastUsedAt: d?.last_used_at ? String(d.last_used_at) : null
|
|
2874
2578
|
});
|
|
2875
|
-
const
|
|
2579
|
+
const UUID2 = /^[0-9a-f-]{36}$/i;
|
|
2876
2580
|
const seatOf = (d) => ({
|
|
2877
2581
|
id: String(d?.id ?? ""),
|
|
2878
2582
|
system: String(d?.system ?? ""),
|
|
@@ -2977,10 +2681,10 @@ function createCauthy(given = {}) {
|
|
|
2977
2681
|
...page ? { "x-cauthy-link-to": `${originOf(request)}${page}` } : { "x-cauthy-links": "hosted" },
|
|
2978
2682
|
// Whose request it is: what Cauthy mails goes in this application's
|
|
2979
2683
|
// name, under its logo.
|
|
2980
|
-
...
|
|
2684
|
+
...signInClientNow() ? { "x-cauthy-client-id": signInClientNow() } : {},
|
|
2981
2685
|
...await passedOn(request),
|
|
2982
2686
|
...bearer2 ? { authorization: `Bearer ${bearer2}` } : {},
|
|
2983
|
-
// The person asked to stay signed in: Cauthy keeps the session long if the
|
|
2687
|
+
// The person asked to stay signed in: Cauthy keeps the session long if the workspace allows it.
|
|
2984
2688
|
...remember ? { "x-cauthy-remember": "1" } : {},
|
|
2985
2689
|
// The answer to the workspace's Turnstile challenge, from the
|
|
2986
2690
|
// person's browser: Cauthy checks it at the doors that ask for it.
|
|
@@ -3121,7 +2825,7 @@ function createCauthy(given = {}) {
|
|
|
3121
2825
|
}
|
|
3122
2826
|
if (route === "/look" && !post) {
|
|
3123
2827
|
if (lookCache === null || lookCache.until < Date.now()) {
|
|
3124
|
-
const res = await ask(request, "GET", `/v1/auth/look/${encodeURIComponent(
|
|
2828
|
+
const res = await ask(request, "GET", `/v1/auth/look/${encodeURIComponent(signInClientNow())}`);
|
|
3125
2829
|
lookCache = { until: Date.now() + LOOK_TTL_MS, look: res.error ? {} : res.data ?? {} };
|
|
3126
2830
|
}
|
|
3127
2831
|
return json(lookCache.look);
|
|
@@ -3146,7 +2850,7 @@ function createCauthy(given = {}) {
|
|
|
3146
2850
|
signUp: settings.data?.allow_signup !== false && settings.data?.signup?.mode !== "invite_only",
|
|
3147
2851
|
// The application's system makes an organization per sign-up: the form asks the company's name.
|
|
3148
2852
|
...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
|
|
2853
|
+
// Whether the person may ask to stay signed in: the workspace's setting in Cauthy (persistent_sessions).
|
|
3150
2854
|
...typeof settings.data?.persistent_sessions === "boolean" ? { persistentSessions: settings.data.persistent_sessions } : {},
|
|
3151
2855
|
// A sign-in link needs a page of the application to open.
|
|
3152
2856
|
magicLink: settings.data?.allow_magic_link === true && pages.signIn !== void 0,
|
|
@@ -3310,7 +3014,7 @@ function createCauthy(given = {}) {
|
|
|
3310
3014
|
let path = "/v1/auth/me/organizations";
|
|
3311
3015
|
if (route !== "/me/organizations") {
|
|
3312
3016
|
const id = url.searchParams.get("id") ?? "";
|
|
3313
|
-
if (!
|
|
3017
|
+
if (!UUID2.test(id)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
|
|
3314
3018
|
path += `/${id}/${route.endsWith("/roles") ? "roles" : "members"}`;
|
|
3315
3019
|
} else if (url.searchParams.get("system")) {
|
|
3316
3020
|
path += `?system=${encodeURIComponent(url.searchParams.get("system"))}`;
|
|
@@ -3371,8 +3075,7 @@ function createCauthy(given = {}) {
|
|
|
3371
3075
|
if (route === "/sign-out") {
|
|
3372
3076
|
const { had, value } = await read(request);
|
|
3373
3077
|
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);
|
|
3078
|
+
await revoke(value.rt);
|
|
3376
3079
|
orgCache.delete(value.at);
|
|
3377
3080
|
}
|
|
3378
3081
|
const h = new Headers();
|
|
@@ -3599,7 +3302,7 @@ function createCauthy(given = {}) {
|
|
|
3599
3302
|
}
|
|
3600
3303
|
if (route === "/me/organizations/rename" || route === "/me/organizations/archive" || route === "/me/organizations/delete") {
|
|
3601
3304
|
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
3602
|
-
if (!
|
|
3305
|
+
if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
|
|
3603
3306
|
const path = `/v1/auth/me/organizations/${org}`;
|
|
3604
3307
|
if (route === "/me/organizations/rename") {
|
|
3605
3308
|
const bad = checkOrganizationName(b.name);
|
|
@@ -3616,7 +3319,7 @@ function createCauthy(given = {}) {
|
|
|
3616
3319
|
}
|
|
3617
3320
|
if (route.startsWith("/me/organizations/members/")) {
|
|
3618
3321
|
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
3619
|
-
if (!
|
|
3322
|
+
if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
|
|
3620
3323
|
const base = `/v1/auth/me/organizations/${org}/members`;
|
|
3621
3324
|
if (route === "/me/organizations/members/add") {
|
|
3622
3325
|
const bad = checkEmail(b.email);
|
|
@@ -3627,7 +3330,7 @@ function createCauthy(given = {}) {
|
|
|
3627
3330
|
email: String(b.email).trim(),
|
|
3628
3331
|
role,
|
|
3629
3332
|
name: typeof b.name === "string" ? b.name.trim() : "",
|
|
3630
|
-
client_id:
|
|
3333
|
+
client_id: signInClientNow(),
|
|
3631
3334
|
only_new: true,
|
|
3632
3335
|
...typeof b.locale === "string" && b.locale ? { locale: b.locale } : {}
|
|
3633
3336
|
}, pages.acceptInvite, mine.token);
|
|
@@ -3636,7 +3339,7 @@ function createCauthy(given = {}) {
|
|
|
3636
3339
|
return json({ status: "added", member: orgMemberOf(res.data) }, 200, mine.headers);
|
|
3637
3340
|
}
|
|
3638
3341
|
const user = typeof b.userId === "string" ? b.userId : "";
|
|
3639
|
-
if (!
|
|
3342
|
+
if (!UUID2.test(user)) return problem(400, "invalid_member", "Say which member.", void 0, mine.headers);
|
|
3640
3343
|
if (route === "/me/organizations/members/role") {
|
|
3641
3344
|
const role = typeof b.role === "string" ? b.role : "";
|
|
3642
3345
|
if (role === "") return problem(400, "invalid_role", "Choose a role.", "role", mine.headers);
|
|
@@ -3651,7 +3354,7 @@ function createCauthy(given = {}) {
|
|
|
3651
3354
|
return json({ status: "removed", userId: user }, 200, mine.headers);
|
|
3652
3355
|
}
|
|
3653
3356
|
if (route === "/me/organizations/members/invite") {
|
|
3654
|
-
const res = await ask(request, "POST", `${base}/${user}/invite`, { client_id:
|
|
3357
|
+
const res = await ask(request, "POST", `${base}/${user}/invite`, { client_id: signInClientNow(), ...typeof b.locale === "string" && b.locale ? { locale: b.locale } : {} }, pages.acceptInvite, mine.token);
|
|
3655
3358
|
if (res.error) return refused(res);
|
|
3656
3359
|
return json({ status: "sent", userId: user }, 200, mine.headers);
|
|
3657
3360
|
}
|
|
@@ -3769,18 +3472,15 @@ function createCauthy(given = {}) {
|
|
|
3769
3472
|
const url = new URL(request.url);
|
|
3770
3473
|
const inside = url.pathname === basePath || url.pathname.startsWith(`${basePath}/`);
|
|
3771
3474
|
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
3475
|
if (identity && route !== null) {
|
|
3774
3476
|
const unready = await identityGate();
|
|
3775
3477
|
if (unready) return finish(unready, route);
|
|
3776
3478
|
}
|
|
3777
3479
|
try {
|
|
3778
|
-
return finish(await serve(request), route
|
|
3480
|
+
return finish(await serve(request), route);
|
|
3779
3481
|
} catch (err) {
|
|
3780
3482
|
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);
|
|
3483
|
+
throw err;
|
|
3784
3484
|
}
|
|
3785
3485
|
}
|
|
3786
3486
|
async function identityGate() {
|
|
@@ -3804,7 +3504,6 @@ function createCauthy(given = {}) {
|
|
|
3804
3504
|
}
|
|
3805
3505
|
async function serve(request) {
|
|
3806
3506
|
const url = new URL(request.url);
|
|
3807
|
-
if (pact && url.pathname === contract?.jwksPath && (request.method === "GET" || request.method === "HEAD")) return pact.jwks();
|
|
3808
3507
|
if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) return new Response("Not found", { status: 404 });
|
|
3809
3508
|
const route = withoutTrailingSlashes(url.pathname.slice(basePath.length)) || "/";
|
|
3810
3509
|
const get = request.method === "GET" || request.method === "HEAD";
|
|
@@ -3828,7 +3527,7 @@ function createCauthy(given = {}) {
|
|
|
3828
3527
|
return redirect(back, r.cookies);
|
|
3829
3528
|
}
|
|
3830
3529
|
if (route === "/account" && get) {
|
|
3831
|
-
const q = new URLSearchParams({ client_id:
|
|
3530
|
+
const q = new URLSearchParams({ client_id: signInClientNow() });
|
|
3832
3531
|
const back = localPath(url.searchParams.get("return_to"), "");
|
|
3833
3532
|
if (back !== "") q.set("return_to", `${originOf(request)}${back}`);
|
|
3834
3533
|
const locale = url.searchParams.get("locale");
|
|
@@ -3847,7 +3546,7 @@ function createCauthy(given = {}) {
|
|
|
3847
3546
|
const asker = await signedInAs(request);
|
|
3848
3547
|
if ("response" in asker) return asker.response;
|
|
3849
3548
|
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
3850
|
-
if (!
|
|
3549
|
+
if (!UUID2.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, asker.headers);
|
|
3851
3550
|
const named = (v) => typeof v === "string" && v !== "" && v.length <= 200;
|
|
3852
3551
|
if (!named(b.resource) || !named(b.action)) return problem(400, "bad_request", "Say which resource and action.", void 0, asker.headers);
|
|
3853
3552
|
const attributes2 = b.attributes;
|
|
@@ -3870,8 +3569,7 @@ function createCauthy(given = {}) {
|
|
|
3870
3569
|
}
|
|
3871
3570
|
}
|
|
3872
3571
|
async function protect(request) {
|
|
3873
|
-
const
|
|
3874
|
-
const value = read1.value !== null && unverified(read1.value.u) ? null : read1.value;
|
|
3572
|
+
const { value } = await read(request);
|
|
3875
3573
|
if (value !== null && fresh(value)) return { session: toSession(value) };
|
|
3876
3574
|
const url = new URL(request.url);
|
|
3877
3575
|
const state = value === null ? "none" : "expired";
|
|
@@ -3884,85 +3582,36 @@ function createCauthy(given = {}) {
|
|
|
3884
3582
|
return { response: redirect(to) };
|
|
3885
3583
|
}
|
|
3886
3584
|
const replay = given.webhookReplay ?? memoryReplayStore();
|
|
3887
|
-
let lateSelf;
|
|
3888
3585
|
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
|
-
}
|
|
3586
|
+
if (identity) {
|
|
3893
3587
|
let built;
|
|
3894
3588
|
return async (from) => {
|
|
3895
|
-
const id = await
|
|
3589
|
+
const id = await identity.ready();
|
|
3896
3590
|
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
|
-
}) };
|
|
3591
|
+
built = { from: id, verify: createTokenVerifier({ issuer, tenantId: id.tenant.id, audience: id.audience, fetch: given.fetch, ...o }) };
|
|
3905
3592
|
}
|
|
3906
3593
|
return built.verify(from);
|
|
3907
3594
|
};
|
|
3908
3595
|
}
|
|
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
3596
|
return createTokenVerifier({
|
|
3913
3597
|
issuer,
|
|
3914
|
-
tenantId:
|
|
3598
|
+
tenantId: given.tenantId,
|
|
3915
3599
|
audience: given.audience,
|
|
3916
3600
|
fetch: given.fetch,
|
|
3917
|
-
...contract ? { clientId: contract.clientId } : {},
|
|
3918
3601
|
...o
|
|
3919
3602
|
});
|
|
3920
3603
|
};
|
|
3921
3604
|
const webhook = (request, o = {}) => verifyWebhook(request, { issuer, url: o.url ?? `${originOf(request)}${new URL(request.url).pathname}`, jwks: o.jwks, now: o.now, replay });
|
|
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
3605
|
const configured = () => ({
|
|
3956
3606
|
issuer,
|
|
3957
3607
|
audience: given.audience ?? env3("CAUTHY_AUDIENCE") ?? "",
|
|
3958
|
-
tenant: { id:
|
|
3608
|
+
tenant: { id: given.tenantId ?? env3("CAUTHY_TENANT_ID") ?? "", slug: tenant },
|
|
3959
3609
|
system,
|
|
3960
3610
|
client_id: clientId,
|
|
3961
3611
|
sign_in_client_id: clientId,
|
|
3962
3612
|
app_urls: baseURL ? [withoutTrailingSlashes(baseURL)] : [],
|
|
3963
3613
|
callback_path: `${basePath}/callback`,
|
|
3964
|
-
key: null
|
|
3965
|
-
contract: contract ? { link: contract.link, system_proof: contract.systemProof, require_verified_email: contract.requireVerifiedEmail } : null
|
|
3614
|
+
key: null
|
|
3966
3615
|
});
|
|
3967
3616
|
return {
|
|
3968
3617
|
handler,
|
|
@@ -3984,7 +3633,6 @@ function createCauthy(given = {}) {
|
|
|
3984
3633
|
check,
|
|
3985
3634
|
toPublic,
|
|
3986
3635
|
urls,
|
|
3987
|
-
...pact ? { pact } : {},
|
|
3988
3636
|
options: { issuer, clientId, basePath, afterSignIn, afterSignOut, cookieName }
|
|
3989
3637
|
};
|
|
3990
3638
|
}
|
|
@@ -4069,7 +3717,6 @@ async function connectSystem(o) {
|
|
|
4069
3717
|
KEY_REPLACES,
|
|
4070
3718
|
MIN_PASSWORD,
|
|
4071
3719
|
PROTOCOL_VERSION,
|
|
4072
|
-
STANDARD_VARIABLES,
|
|
4073
3720
|
can,
|
|
4074
3721
|
checkCode,
|
|
4075
3722
|
checkEmail,
|
|
@@ -4085,7 +3732,6 @@ async function connectSystem(o) {
|
|
|
4085
3732
|
definePolicy,
|
|
4086
3733
|
describePasswordPolicy,
|
|
4087
3734
|
explainPasswordProblems,
|
|
4088
|
-
failClosed,
|
|
4089
3735
|
memoryReplayStore,
|
|
4090
3736
|
parseKey,
|
|
4091
3737
|
passwordPolicyOf,
|
|
@@ -4096,7 +3742,6 @@ async function connectSystem(o) {
|
|
|
4096
3742
|
pushCatalog,
|
|
4097
3743
|
pushPolicy,
|
|
4098
3744
|
pushTheme,
|
|
4099
|
-
readContract,
|
|
4100
3745
|
remoteKeys,
|
|
4101
3746
|
themeFromStackmyth,
|
|
4102
3747
|
tokensFromStackmyth,
|