cauthy-auth 0.28.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +51 -0
  2. package/dist/{cauthy-CTVg4kvo.d.mts → cauthy-BXgsZ-Um.d.mts} +19 -1
  3. package/dist/{cauthy-BAwAJChs.d.ts → cauthy-CHENs452.d.ts} +19 -1
  4. package/dist/cli.js +155 -54
  5. package/dist/client.d.mts +47 -7
  6. package/dist/client.d.ts +47 -7
  7. package/dist/client.js +23 -1
  8. package/dist/client.js.map +1 -1
  9. package/dist/client.mjs +23 -1
  10. package/dist/client.mjs.map +1 -1
  11. package/dist/express.d.mts +2 -2
  12. package/dist/express.d.ts +2 -2
  13. package/dist/fastify.d.mts +2 -2
  14. package/dist/fastify.d.ts +2 -2
  15. package/dist/hono.d.mts +2 -2
  16. package/dist/hono.d.ts +2 -2
  17. package/dist/index.d.mts +2 -2
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +325 -129
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +323 -129
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/next.d.mts +2 -2
  24. package/dist/next.d.ts +2 -2
  25. package/dist/react.d.mts +214 -18
  26. package/dist/react.d.ts +214 -18
  27. package/dist/react.js +129 -32
  28. package/dist/react.js.map +1 -1
  29. package/dist/react.mjs +129 -32
  30. package/dist/react.mjs.map +1 -1
  31. package/dist/svelte.d.mts +2 -2
  32. package/dist/svelte.d.ts +2 -2
  33. package/dist/svelte.js +37 -2
  34. package/dist/svelte.js.map +1 -1
  35. package/dist/svelte.mjs +37 -2
  36. package/dist/svelte.mjs.map +1 -1
  37. package/dist/{system-CVBW_AV8.d.mts → system-0mHrdAgO.d.mts} +307 -109
  38. package/dist/{system-Bn8uckvQ.d.ts → system-BIYbS7h2.d.ts} +307 -109
  39. package/dist/vue.d.mts +2 -2
  40. package/dist/vue.d.ts +2 -2
  41. package/dist/vue.js +37 -2
  42. package/dist/vue.js.map +1 -1
  43. package/dist/vue.mjs +37 -2
  44. package/dist/vue.mjs.map +1 -1
  45. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -54,9 +54,6 @@ async function unseal(sealed, key) {
54
54
  }
55
55
  }
56
56
 
57
- // src/system.ts
58
- import { exportJWK, importJWK, importPKCS8, SignJWT } from "jose";
59
-
60
57
  // src/url.ts
61
58
  function withoutTrailingSlashes(s) {
62
59
  let end = s.length;
@@ -64,6 +61,218 @@ function withoutTrailingSlashes(s) {
64
61
  return s.slice(0, end);
65
62
  }
66
63
 
64
+ // src/permissions.ts
65
+ function matches(entry, resource, action) {
66
+ const i = entry.lastIndexOf(":");
67
+ if (i < 0) return false;
68
+ const r = entry.slice(0, i);
69
+ const a = entry.slice(i + 1);
70
+ return (r === "*" || r === resource) && (a === "*" || a === action);
71
+ }
72
+ function can(organization, resource, action) {
73
+ if (!organization?.permissions) return false;
74
+ if ((organization.denied ?? []).some((d) => matches(d, resource, action))) return false;
75
+ return organization.permissions.some((p) => matches(p, resource, action));
76
+ }
77
+ function definePolicy(given) {
78
+ const roles = Array.isArray(given) ? given : given.roles;
79
+ const resources = Array.isArray(given) ? [] : given.catalog ?? [];
80
+ const entry = (p) => `${p.resource}:${p.action}`;
81
+ const byName = new Map(roles.map((r) => [r.role, r]));
82
+ const split = (permission) => {
83
+ const i = permission.lastIndexOf(":");
84
+ return i < 0 ? [permission, ""] : [permission.slice(0, i), permission.slice(i + 1)];
85
+ };
86
+ return {
87
+ roles: roles.map((r) => ({
88
+ role: r.role,
89
+ ...r.name !== void 0 ? { name: r.name } : {},
90
+ ...r.description !== void 0 ? { description: r.description } : {},
91
+ ...r.inherits ? { inherits: [...r.inherits] } : {},
92
+ ...r.permissions ? { permissions: r.permissions.map((p) => ({ ...p })) } : {}
93
+ })),
94
+ catalog: resources.map((c) => ({
95
+ resource: c.resource,
96
+ ...c.description !== void 0 ? { description: c.description } : {},
97
+ actions: [...c.actions],
98
+ ...c.attributes ? { attributes: { ...c.attributes } } : {}
99
+ })),
100
+ roleNames: roles.map((r) => r.role),
101
+ permissions: [...new Set(roles.flatMap((r) => (r.permissions ?? []).map(entry)))],
102
+ can(organization, permission) {
103
+ const [resource, action] = split(permission);
104
+ return can(organization, resource, action);
105
+ },
106
+ hasRole(organization, ...wanted) {
107
+ return organization != null && wanted.includes(organization.role);
108
+ },
109
+ permissionsOf(role) {
110
+ const seen = /* @__PURE__ */ new Set();
111
+ const out2 = /* @__PURE__ */ new Set();
112
+ const walk = (name) => {
113
+ if (seen.has(name)) return;
114
+ seen.add(name);
115
+ const r = byName.get(name);
116
+ for (const p of r?.permissions ?? []) out2.add(entry(p));
117
+ for (const parent of r?.inherits ?? []) walk(parent);
118
+ };
119
+ walk(role);
120
+ return [...out2];
121
+ },
122
+ // The types did the work; this only hands the question on, as a method of
123
+ // the asker so it keeps its `this`.
124
+ check(asker, ...args) {
125
+ return asker.check(...args);
126
+ }
127
+ };
128
+ }
129
+ var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
130
+ function policySource(system, read) {
131
+ const roles = Array.isArray(read) ? read : read.roles;
132
+ const catalog = Array.isArray(read) ? [] : read.catalog ?? [];
133
+ const text = (v) => JSON.stringify(v);
134
+ const key = (k) => IDENT.test(k) ? k : text(k);
135
+ const one = (r, pad) => {
136
+ const parts = [`role: ${text(r.role)}`];
137
+ if (r.name) parts.push(`name: ${text(r.name)}`);
138
+ if (r.description) parts.push(`description: ${text(r.description)}`);
139
+ if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(text).join(", ")}]`);
140
+ if (r.permissions?.length) {
141
+ const list = r.permissions.map((p) => `${pad} { ${[
142
+ ...p.kind && p.kind !== "*" ? [`${key("kind")}: ${text(p.kind)}`] : [],
143
+ `resource: ${text(p.resource)}`,
144
+ `action: ${text(p.action)}`,
145
+ ...p.condition ? [`condition: ${text(p.condition)}`] : []
146
+ ].join(", ")} },`);
147
+ parts.push(`permissions: [
148
+ ${list.join("\n")}
149
+ ${pad} ]`);
150
+ }
151
+ return `${pad} {
152
+ ${pad} ${parts.join(`,
153
+ ${pad} `)},
154
+ ${pad} },`;
155
+ };
156
+ const resource = (c) => {
157
+ const parts = [`resource: ${text(c.resource)}`];
158
+ if (c.description) parts.push(`description: ${text(c.description)}`);
159
+ parts.push(`actions: [${c.actions.map(text).join(", ")}]`);
160
+ const attrs = Object.entries(c.attributes ?? {});
161
+ if (attrs.length) parts.push(`attributes: { ${attrs.map(([k, v]) => `${key(k)}: ${text(v)}`).join(", ")} }`);
162
+ return ` { ${parts.join(", ")} },`;
163
+ };
164
+ const head = `// The catalog of roles of ${text(system)}, as Cauthy has it.
165
+ // Written by \`cauthy-auth policy --pull\`: run it again after the catalog
166
+ // changes, and do not edit this file by hand.
167
+ `;
168
+ if (catalog.length === 0) {
169
+ return `${head}
170
+ import { definePolicy, type PermissionOf, type RoleOf } from 'cauthy-auth'
171
+
172
+ export const policy = definePolicy([
173
+ ${roles.map((r) => one(r, "")).join("\n")}
174
+ ])
175
+
176
+ export const { can, hasRole } = policy
177
+ export type Role = RoleOf<typeof policy>
178
+ export type Permission = PermissionOf<typeof policy>
179
+ `;
180
+ }
181
+ return `${head}
182
+ import { definePolicy, type CheckOf, type PermissionOf, type RoleOf } from 'cauthy-auth'
183
+
184
+ export const policy = definePolicy({
185
+ catalog: [
186
+ ${catalog.map(resource).join("\n")}
187
+ ],
188
+ roles: [
189
+ ${roles.map((r) => one(r, " ")).join("\n")}
190
+ ],
191
+ })
192
+
193
+ export const { can, hasRole } = policy
194
+ export type Role = RoleOf<typeof policy>
195
+ export type Permission = PermissionOf<typeof policy>
196
+ /** What a check may ask: \`policy.check(auth, request, { organization, ...question })\`. */
197
+ export type Check = CheckOf<typeof policy>
198
+ `;
199
+ }
200
+ function reach(o, doing) {
201
+ const env4 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
202
+ const r = {
203
+ issuer: withoutTrailingSlashes(o.issuer ?? env4("CAUTHY_ISSUER") ?? ""),
204
+ system: o.system ?? env4("CAUTHY_SYSTEM") ?? "",
205
+ tenant: o.tenant ?? env4("CAUTHY_TENANT") ?? "",
206
+ token: o.token ?? env4("CAUTHY_TOKEN") ?? ""
207
+ };
208
+ for (const [name, v] of Object.entries({ CAUTHY_ISSUER: r.issuer, CAUTHY_SYSTEM: r.system, CAUTHY_TENANT: r.tenant, CAUTHY_TOKEN: r.token })) {
209
+ if (v === "") throw new Error(`cauthy-auth: ${name} is required to ${doing}`);
210
+ }
211
+ return { ...r, fetch: o.fetch ?? fetch };
212
+ }
213
+ async function systemCall(r, method, what, body) {
214
+ const res = await r.fetch(`${r.issuer}/v1/systems/${encodeURIComponent(r.system)}/${what}`, {
215
+ ...method === "PUT" ? { method } : {},
216
+ headers: {
217
+ authorization: `Bearer ${r.token}`,
218
+ "x-tenant-key": r.tenant,
219
+ ...body === void 0 ? { accept: "application/json" } : { "content-type": "application/json" }
220
+ },
221
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
222
+ });
223
+ const answer = await res.json().catch(() => null);
224
+ if (!res.ok) {
225
+ const verb = method === "GET" ? `refused to give the ${what}` : `refused the ${what}`;
226
+ throw new Error(`cauthy-auth: Cauthy ${verb} (${res.status}): ${answer?.errors?.[0]?.message ?? res.statusText}`);
227
+ }
228
+ return answer?.data;
229
+ }
230
+ async function pullPolicy(o = {}) {
231
+ const data = await systemCall(reach(o, "read a policy"), "GET", "policy");
232
+ return data?.roles ?? [];
233
+ }
234
+ async function pullCatalog(o = {}) {
235
+ const data = await systemCall(reach(o, "read a catalog"), "GET", "catalog");
236
+ return data?.resources ?? [];
237
+ }
238
+ async function pushPolicy(o) {
239
+ const r = reach(o, "push a policy");
240
+ if (o.catalog !== void 0) await systemCall(r, "PUT", "catalog", { resources: o.catalog });
241
+ await systemCall(r, "PUT", "policy", { roles: o.roles });
242
+ }
243
+ async function pushCatalog(o) {
244
+ const data = await systemCall(reach(o, "push a catalog"), "PUT", "catalog", { resources: o.resources });
245
+ return data?.resources ?? [];
246
+ }
247
+ function checkWire(q) {
248
+ return {
249
+ resource: q.resource,
250
+ action: q.action,
251
+ ...q.resourceId !== void 0 ? { resource_id: q.resourceId } : {},
252
+ ...q.ownerId !== void 0 ? { owner_id: q.ownerId } : {},
253
+ ...q.attributes !== void 0 ? { attributes: q.attributes } : {}
254
+ };
255
+ }
256
+ function checkResultOf(d) {
257
+ const r = d?.rule;
258
+ const rule = r && typeof r === "object" ? {
259
+ source: r.source === "exception" ? "exception" : "role",
260
+ ...r.role ? { role: String(r.role) } : {},
261
+ kind: String(r.kind ?? "*"),
262
+ resource: String(r.resource ?? ""),
263
+ action: String(r.action ?? ""),
264
+ effect: r.effect === "allow" ? "allow" : "deny",
265
+ ...r.condition ? { condition: String(r.condition) } : {}
266
+ } : void 0;
267
+ return { allowed: d?.allowed === true, reason: String(d?.reason ?? ""), ...rule ? { rule } : {} };
268
+ }
269
+ function organizationIdOf(o) {
270
+ return typeof o === "string" ? o : typeof o?.id === "string" ? o.id : "";
271
+ }
272
+
273
+ // src/system.ts
274
+ import { exportJWK, importJWK, importPKCS8, SignJWT } from "jose";
275
+
67
276
  // src/theme.ts
68
277
  var MAX_RADIUS = 40;
69
278
  function withoutComments(css) {
@@ -481,6 +690,7 @@ function createSystem(given = {}) {
481
690
  const clientSecret = given.clientSecret ?? env("CAUTHY_CLIENT_SECRET") ?? "";
482
691
  const envKey = env("CAUTHY_SYSTEM_KEY");
483
692
  const key = given.key ?? (envKey ? { privateKey: envKey, keyId: env("CAUTHY_SYSTEM_KEY_ID") ?? "" } : void 0);
693
+ const ownSystem = given.system ?? env("CAUTHY_SYSTEM") ?? "";
484
694
  const doFetch = (...args) => (given.fetch ?? fetch)(...args);
485
695
  const tokenEndpoint = `${issuer}/v1/oauth/token`;
486
696
  function configured() {
@@ -598,6 +808,19 @@ function createSystem(given = {}) {
598
808
  return (await data("GET", `/v1/systems/${id(system)}/policy`)).roles ?? [];
599
809
  }
600
810
  },
811
+ catalog: {
812
+ async get(system) {
813
+ return (await data("GET", `/v1/systems/${id(system)}/catalog`)).resources ?? [];
814
+ },
815
+ async set(system, resources) {
816
+ return (await data("PUT", `/v1/systems/${id(system)}/catalog`, { resources })).resources ?? [];
817
+ }
818
+ },
819
+ async check(q) {
820
+ const system = q.system ?? ownSystem;
821
+ if (system === "") throw new CauthyConfigError("name the system to check in (system, or CAUTHY_SYSTEM)");
822
+ return checkResultOf(await data("POST", `/v1/systems/${id(system)}/check`, { organization_id: q.organizationId, user_id: q.userId, ...checkWire(q) }));
823
+ },
601
824
  async publicJwks() {
602
825
  if (!key) return { keys: [] };
603
826
  const { kty, n, e } = asJwk(key) ?? await exportJWK(await signingKey());
@@ -745,6 +968,12 @@ function checkOrganization(value, required = false) {
745
968
  if (v.length > MAX_ORGANIZATION) return { field: "organization", code: "too_long", message: `Use at most ${MAX_ORGANIZATION} characters.` };
746
969
  return null;
747
970
  }
971
+ function checkOrganizationName(value) {
972
+ const v = typeof value === "string" ? value.trim() : "";
973
+ if (v === "") return { field: "name", code: "required", message: "Enter the organization's name." };
974
+ if (v.length > MAX_ORGANIZATION) return { field: "name", code: "too_long", message: `Use at most ${MAX_ORGANIZATION} characters.` };
975
+ return null;
976
+ }
748
977
  function checkCode(value) {
749
978
  const v = typeof value === "string" ? value.trim() : "";
750
979
  if (v === "") return { field: "code", code: "required", message: "Enter the code." };
@@ -876,6 +1105,9 @@ function redirect(location, cookies = []) {
876
1105
  for (const c of cookies) h.append("set-cookie", c);
877
1106
  return new Response(null, { status: 302, headers: h });
878
1107
  }
1108
+ function conditionalOf(c) {
1109
+ return { resource: String(c?.resource ?? ""), action: String(c?.action ?? ""), effect: c?.effect === "deny" ? "deny" : "allow" };
1110
+ }
879
1111
  function createCauthy(given = {}) {
880
1112
  const issuer = withoutTrailingSlashes(given.issuer ?? env2("CAUTHY_ISSUER") ?? "");
881
1113
  const clientId = given.clientId ?? env2("CAUTHY_CLIENT_ID") ?? "";
@@ -904,7 +1136,7 @@ function createCauthy(given = {}) {
904
1136
  resetPassword: given.pages?.resetPassword ? localPath(given.pages.resetPassword, "/") : void 0,
905
1137
  verifyEmail: given.pages?.verifyEmail ? localPath(given.pages.verifyEmail, "/") : void 0
906
1138
  };
907
- const ownSystem = createSystem({ issuer, clientId, clientSecret, key: given.systemKey, fetch: given.fetch });
1139
+ const ownSystem = createSystem({ issuer, clientId, clientSecret, key: given.systemKey, system: system || void 0, fetch: given.fetch });
908
1140
  const originOf = (request) => {
909
1141
  if (baseURL) return withoutTrailingSlashes(baseURL);
910
1142
  const url = new URL(request.url);
@@ -1039,13 +1271,35 @@ function createCauthy(given = {}) {
1039
1271
  role: String(o.role ?? ""),
1040
1272
  metadata: o.metadata ?? {},
1041
1273
  ...o.external_id ? { externalId: String(o.external_id) } : {},
1042
- ...Array.isArray(o.permissions) ? { permissions: o.permissions.map(String), denied: Array.isArray(o.denied) ? o.denied.map(String) : [] } : {}
1274
+ ...Array.isArray(o.permissions) ? { permissions: o.permissions.map(String), denied: Array.isArray(o.denied) ? o.denied.map(String) : [], conditional: Array.isArray(o.conditional) ? o.conditional.map(conditionalOf) : [] } : {}
1043
1275
  })
1044
1276
  );
1045
1277
  if (orgCache.size > 5e3) orgCache.clear();
1046
1278
  orgCache.set(session.accessToken, { at: Date.now(), value });
1047
1279
  return value;
1048
1280
  }
1281
+ async function decide(token2, question) {
1282
+ const org = organizationIdOf(question.organization);
1283
+ if (org === "") return { allowed: false, reason: "no organization named" };
1284
+ const res = await doFetch(`${issuer}/v1/oauth/organizations/${encodeURIComponent(org)}/check`, {
1285
+ method: "POST",
1286
+ headers: { authorization: `Bearer ${token2}`, "content-type": "application/json", accept: "application/json" },
1287
+ body: JSON.stringify(checkWire(question))
1288
+ }).catch(() => null);
1289
+ if (res === null) throw new CauthyApiError(503, "unavailable", "Cauthy could not be reached to check a permission");
1290
+ const body2 = await res.json().catch(() => null);
1291
+ if (res.ok) return checkResultOf(body2?.data);
1292
+ const first = body2?.errors?.[0];
1293
+ if (res.status === 401 || res.status === 403 || res.status === 404) {
1294
+ return { allowed: false, reason: first?.message ?? (res.status === 401 ? "not signed in" : res.status === 404 ? "not a member of the organization" : "refused") };
1295
+ }
1296
+ throw new CauthyApiError(res.status, first?.code ?? "error", `Cauthy refused to check a permission (${res.status}): ${first?.message ?? res.statusText}`);
1297
+ }
1298
+ async function check(who, question) {
1299
+ const session = "accessToken" in who ? who : await getSession(who);
1300
+ if (!session) return { allowed: false, reason: "not signed in" };
1301
+ return decide(session.accessToken, question);
1302
+ }
1049
1303
  const forgetting = (fn) => async (...args) => {
1050
1304
  const out2 = await fn(...args);
1051
1305
  orgCache.clear();
@@ -1178,7 +1432,15 @@ function createCauthy(given = {}) {
1178
1432
  name: String(d?.name ?? ""),
1179
1433
  kind: String(d?.kind ?? ""),
1180
1434
  role: String(d?.role ?? ""),
1181
- can: { readMembers: d?.can?.read_members === true, manageMembers: d?.can?.manage_members === true }
1435
+ // A Cauthy from before a power says nothing of it: then it is not given.
1436
+ can: {
1437
+ readMembers: d?.can?.read_members === true,
1438
+ manageMembers: d?.can?.manage_members === true,
1439
+ removeMembers: d?.can?.remove_members === true,
1440
+ update: d?.can?.update === true,
1441
+ archive: d?.can?.archive === true,
1442
+ delete: d?.can?.delete === true
1443
+ }
1182
1444
  });
1183
1445
  const orgMemberOf = (d) => ({
1184
1446
  userId: String(d?.user_id ?? ""),
@@ -1371,7 +1633,10 @@ function createCauthy(given = {}) {
1371
1633
  "/me/organizations/members/add",
1372
1634
  "/me/organizations/members/role",
1373
1635
  "/me/organizations/members/remove",
1374
- "/me/organizations/members/invite"
1636
+ "/me/organizations/members/invite",
1637
+ "/me/organizations/rename",
1638
+ "/me/organizations/archive",
1639
+ "/me/organizations/delete"
1375
1640
  ].includes(route) || route.startsWith("/sign-in/social/") || route.startsWith("/callback/") || route.startsWith("/me/identities/link/") || route.startsWith("/me/upgrade/social/") || route === "/sign-in/sso" || route === "/sign-in/sso/discover" || route === "/sso/callback";
1376
1641
  if (!own) return null;
1377
1642
  if (tenant === "") {
@@ -1842,6 +2107,23 @@ function createCauthy(given = {}) {
1842
2107
  if (res.error) return refused(res, res.error.code === "weak_password" ? "password" : "email");
1843
2108
  return signedIn(request, res.data);
1844
2109
  }
2110
+ if (route === "/me/organizations/rename" || route === "/me/organizations/archive" || route === "/me/organizations/delete") {
2111
+ const org = typeof b.organizationId === "string" ? b.organizationId : "";
2112
+ if (!UUID.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
2113
+ const path = `/v1/auth/me/organizations/${org}`;
2114
+ if (route === "/me/organizations/rename") {
2115
+ const bad = checkOrganizationName(b.name);
2116
+ if (bad) return invalid(bad);
2117
+ const res2 = await as(path, { name: String(b.name).trim() }, "PATCH");
2118
+ if (res2.error) return refused(res2, res2.error.code === "validation_error" ? "name" : void 0);
2119
+ orgCache.delete(mine.token);
2120
+ return json({ status: "renamed", organization: seatOf(res2.data) }, 200, mine.headers);
2121
+ }
2122
+ const res = route === "/me/organizations/archive" ? await as(`${path}/archive`) : await as(path, void 0, "DELETE");
2123
+ if (res.error) return refused(res);
2124
+ orgCache.delete(mine.token);
2125
+ return json({ status: route === "/me/organizations/archive" ? "archived" : "deleted", organizationId: org }, 200, mine.headers);
2126
+ }
1845
2127
  if (route.startsWith("/me/organizations/members/")) {
1846
2128
  const org = typeof b.organizationId === "string" ? b.organizationId : "";
1847
2129
  if (!UUID.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
@@ -1987,6 +2269,7 @@ function createCauthy(given = {}) {
1987
2269
  const get = request.method === "GET" || request.method === "HEAD";
1988
2270
  const mine = await ownRoute(request, url, route);
1989
2271
  if (mine !== null) return mine;
2272
+ if (route === "/me/organizations/check") return checkRoute(request);
1990
2273
  if (route === "/login" && get) return login(request, url);
1991
2274
  if (route === "/callback" && get) return callback(request, url);
1992
2275
  if (route === "/logout" && (get || request.method === "POST")) return logout(request);
@@ -2013,6 +2296,36 @@ function createCauthy(given = {}) {
2013
2296
  }
2014
2297
  return new Response("Not found", { status: 404 });
2015
2298
  }
2299
+ async function checkRoute(request) {
2300
+ if (request.method !== "POST") return new Response("Method not allowed", { status: 405, headers: { allow: "POST" } });
2301
+ if (!ownRequest(request)) return problem(403, "cross_site", "This request did not come from this application.");
2302
+ const b = await body(request);
2303
+ if (b === null) return problem(400, "bad_request", "Send the question as JSON.");
2304
+ const asker = await signedInAs(request);
2305
+ if ("response" in asker) return asker.response;
2306
+ const org = typeof b.organizationId === "string" ? b.organizationId : "";
2307
+ if (!UUID.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, asker.headers);
2308
+ const named = (v) => typeof v === "string" && v !== "" && v.length <= 200;
2309
+ if (!named(b.resource) || !named(b.action)) return problem(400, "bad_request", "Say which resource and action.", void 0, asker.headers);
2310
+ const attributes2 = b.attributes;
2311
+ if (attributes2 !== void 0 && (typeof attributes2 !== "object" || attributes2 === null || Array.isArray(attributes2))) {
2312
+ return problem(400, "bad_request", "The attributes are an object.", void 0, asker.headers);
2313
+ }
2314
+ try {
2315
+ const r = await decide(asker.token, {
2316
+ organization: org,
2317
+ resource: b.resource,
2318
+ action: b.action,
2319
+ ...attributes2 !== void 0 ? { attributes: attributes2 } : {},
2320
+ ...typeof b.resourceId === "string" ? { resourceId: b.resourceId } : {},
2321
+ ...typeof b.ownerId === "string" ? { ownerId: b.ownerId } : {}
2322
+ });
2323
+ return json({ status: "decided", ...r }, 200, asker.headers);
2324
+ } catch (e) {
2325
+ const err = e instanceof CauthyApiError ? e : new CauthyApiError(503, "unavailable", "Cauthy could not be reached.");
2326
+ return problem(err.status, err.code, err.message.replace(/^cauthy-auth: /, ""), void 0, asker.headers);
2327
+ }
2328
+ }
2016
2329
  async function protect(request) {
2017
2330
  const { value } = await read(request);
2018
2331
  if (value !== null && fresh(value)) return { session: toSession(value) };
@@ -2036,6 +2349,7 @@ function createCauthy(given = {}) {
2036
2349
  resolve: resolve2,
2037
2350
  protect,
2038
2351
  organizations,
2352
+ check,
2039
2353
  toPublic,
2040
2354
  urls,
2041
2355
  options: { issuer, clientId, basePath, afterSignIn, afterSignOut, cookieName }
@@ -2050,128 +2364,6 @@ function legalOf(raw) {
2050
2364
  };
2051
2365
  }
2052
2366
 
2053
- // src/permissions.ts
2054
- function matches(entry, resource, action) {
2055
- const i = entry.lastIndexOf(":");
2056
- if (i < 0) return false;
2057
- const r = entry.slice(0, i);
2058
- const a = entry.slice(i + 1);
2059
- return (r === "*" || r === resource) && (a === "*" || a === action);
2060
- }
2061
- function can(organization, resource, action) {
2062
- if (!organization?.permissions) return false;
2063
- if ((organization.denied ?? []).some((d) => matches(d, resource, action))) return false;
2064
- return organization.permissions.some((p) => matches(p, resource, action));
2065
- }
2066
- function definePolicy(roles) {
2067
- const entry = (p) => `${p.resource}:${p.action}`;
2068
- const byName = new Map(roles.map((r) => [r.role, r]));
2069
- const split = (permission) => {
2070
- const i = permission.lastIndexOf(":");
2071
- return i < 0 ? [permission, ""] : [permission.slice(0, i), permission.slice(i + 1)];
2072
- };
2073
- return {
2074
- roles: roles.map((r) => ({
2075
- role: r.role,
2076
- ...r.name !== void 0 ? { name: r.name } : {},
2077
- ...r.description !== void 0 ? { description: r.description } : {},
2078
- ...r.inherits ? { inherits: [...r.inherits] } : {},
2079
- ...r.permissions ? { permissions: r.permissions.map((p) => ({ ...p })) } : {}
2080
- })),
2081
- roleNames: roles.map((r) => r.role),
2082
- permissions: [...new Set(roles.flatMap((r) => (r.permissions ?? []).map(entry)))],
2083
- can(organization, permission) {
2084
- const [resource, action] = split(permission);
2085
- return can(organization, resource, action);
2086
- },
2087
- hasRole(organization, ...wanted) {
2088
- return organization != null && wanted.includes(organization.role);
2089
- },
2090
- permissionsOf(role) {
2091
- const seen = /* @__PURE__ */ new Set();
2092
- const out2 = /* @__PURE__ */ new Set();
2093
- const walk = (name) => {
2094
- if (seen.has(name)) return;
2095
- seen.add(name);
2096
- const r = byName.get(name);
2097
- for (const p of r?.permissions ?? []) out2.add(entry(p));
2098
- for (const parent of r?.inherits ?? []) walk(parent);
2099
- };
2100
- walk(role);
2101
- return [...out2];
2102
- }
2103
- };
2104
- }
2105
- var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2106
- function policySource(system, roles) {
2107
- const text = (v) => JSON.stringify(v);
2108
- const key = (k) => IDENT.test(k) ? k : text(k);
2109
- const one = (r) => {
2110
- const parts = [`role: ${text(r.role)}`];
2111
- if (r.name) parts.push(`name: ${text(r.name)}`);
2112
- if (r.description) parts.push(`description: ${text(r.description)}`);
2113
- if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(text).join(", ")}]`);
2114
- if (r.permissions?.length) {
2115
- const list = r.permissions.map((p) => ` { ${[...p.kind && p.kind !== "*" ? [`${key("kind")}: ${text(p.kind)}`] : [], `resource: ${text(p.resource)}`, `action: ${text(p.action)}`].join(", ")} },`);
2116
- parts.push(`permissions: [
2117
- ${list.join("\n")}
2118
- ]`);
2119
- }
2120
- return ` {
2121
- ${parts.join(",\n ")},
2122
- },`;
2123
- };
2124
- return `// The catalog of roles of ${text(system)}, as Cauthy has it.
2125
- // Written by \`cauthy-auth policy --pull\`: run it again after the catalog
2126
- // changes, and do not edit this file by hand.
2127
-
2128
- import { definePolicy, type PermissionOf, type RoleOf } from 'cauthy-auth'
2129
-
2130
- export const policy = definePolicy([
2131
- ${roles.map(one).join("\n")}
2132
- ])
2133
-
2134
- export const { can, hasRole } = policy
2135
- export type Role = RoleOf<typeof policy>
2136
- export type Permission = PermissionOf<typeof policy>
2137
- `;
2138
- }
2139
- async function pullPolicy(o = {}) {
2140
- const env4 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
2141
- const issuer = withoutTrailingSlashes(o.issuer ?? env4("CAUTHY_ISSUER") ?? "");
2142
- const system = o.system ?? env4("CAUTHY_SYSTEM") ?? "";
2143
- const tenant = o.tenant ?? env4("CAUTHY_TENANT") ?? "";
2144
- const token = o.token ?? env4("CAUTHY_TOKEN") ?? "";
2145
- for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
2146
- if (v === "") throw new Error(`cauthy-auth: ${name} is required to read a policy`);
2147
- }
2148
- const res = await (o.fetch ?? fetch)(`${issuer}/v1/systems/${encodeURIComponent(system)}/policy`, {
2149
- headers: { authorization: `Bearer ${token}`, "x-tenant-key": tenant, accept: "application/json" }
2150
- });
2151
- const body = await res.json().catch(() => null);
2152
- if (!res.ok) throw new Error(`cauthy-auth: Cauthy refused to give the policy (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`);
2153
- return body?.data?.roles ?? [];
2154
- }
2155
- async function pushPolicy(o) {
2156
- const env4 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
2157
- const issuer = withoutTrailingSlashes(o.issuer ?? env4("CAUTHY_ISSUER") ?? "");
2158
- const system = o.system ?? env4("CAUTHY_SYSTEM") ?? "";
2159
- const tenant = o.tenant ?? env4("CAUTHY_TENANT") ?? "";
2160
- const token = o.token ?? env4("CAUTHY_TOKEN") ?? "";
2161
- for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
2162
- if (v === "") throw new Error(`cauthy-auth: ${name} is required to push a policy`);
2163
- }
2164
- const res = await (o.fetch ?? fetch)(`${issuer}/v1/systems/${encodeURIComponent(system)}/policy`, {
2165
- method: "PUT",
2166
- headers: { authorization: `Bearer ${token}`, "x-tenant-key": tenant, "content-type": "application/json" },
2167
- body: JSON.stringify({ roles: o.roles })
2168
- });
2169
- if (!res.ok) {
2170
- const body = await res.json().catch(() => null);
2171
- throw new Error(`cauthy-auth: Cauthy refused the policy (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`);
2172
- }
2173
- }
2174
-
2175
2367
  // src/connect.ts
2176
2368
  function sessionSecret() {
2177
2369
  const bytes = crypto.getRandomValues(new Uint8Array(32));
@@ -2296,7 +2488,9 @@ export {
2296
2488
  passwordPolicyOf,
2297
2489
  passwordProblems,
2298
2490
  policySource,
2491
+ pullCatalog,
2299
2492
  pullPolicy,
2493
+ pushCatalog,
2300
2494
  pushPolicy,
2301
2495
  pushTheme,
2302
2496
  themeFromStackmyth,