cauthy-auth 0.28.1 → 0.31.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/README.md +61 -3
- package/dist/{cauthy-CTVg4kvo.d.mts → cauthy-2jtP2LVQ.d.mts} +19 -1
- package/dist/{cauthy-BAwAJChs.d.ts → cauthy-CKfvvv_i.d.ts} +19 -1
- package/dist/cli.js +163 -59
- package/dist/client.d.mts +91 -10
- package/dist/client.d.ts +91 -10
- package/dist/client.js +39 -3
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +39 -3
- package/dist/client.mjs.map +1 -1
- package/dist/express.d.mts +2 -2
- package/dist/express.d.ts +2 -2
- package/dist/fastify.d.mts +2 -2
- package/dist/fastify.d.ts +2 -2
- package/dist/hono.d.mts +2 -2
- package/dist/hono.d.ts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +387 -148
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +385 -148
- package/dist/index.mjs.map +1 -1
- package/dist/next.d.mts +2 -2
- package/dist/next.d.ts +2 -2
- package/dist/react.d.mts +275 -23
- package/dist/react.d.ts +275 -23
- package/dist/react.js +312 -78
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +311 -78
- package/dist/react.mjs.map +1 -1
- package/dist/styles.css +8 -0
- package/dist/svelte.d.mts +2 -2
- package/dist/svelte.d.ts +2 -2
- package/dist/svelte.js +53 -4
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +53 -4
- package/dist/svelte.mjs.map +1 -1
- package/dist/{system-CVBW_AV8.d.mts → system-BrrJsrFW.d.mts} +321 -109
- package/dist/{system-Bn8uckvQ.d.ts → system-BvvoNs2y.d.ts} +321 -109
- package/dist/vue.d.mts +2 -2
- package/dist/vue.d.ts +2 -2
- package/dist/vue.js +53 -4
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +53 -4
- package/dist/vue.mjs.map +1 -1
- 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,9 @@ 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") ?? "";
|
|
694
|
+
const acceptInviteUrl = given.acceptInviteUrl ?? env("CAUTHY_ACCEPT_INVITE_URL") ?? "";
|
|
695
|
+
const inviteHeaders = /^https?:\/\//.test(acceptInviteUrl) ? { "x-cauthy-client-id": clientId, "x-cauthy-link-to": acceptInviteUrl } : {};
|
|
484
696
|
const doFetch = (...args) => (given.fetch ?? fetch)(...args);
|
|
485
697
|
const tokenEndpoint = `${issuer}/v1/oauth/token`;
|
|
486
698
|
function configured() {
|
|
@@ -523,10 +735,10 @@ function createSystem(given = {}) {
|
|
|
523
735
|
asking = void 0;
|
|
524
736
|
});
|
|
525
737
|
}
|
|
526
|
-
async function call(method, path, body) {
|
|
738
|
+
async function call(method, path, body, extra = {}) {
|
|
527
739
|
const send = async () => doFetch(`${issuer}${path}`, {
|
|
528
740
|
method,
|
|
529
|
-
headers: { authorization: `Bearer ${await token()}`, ...body === void 0 ? {} : { "content-type": "application/json" } },
|
|
741
|
+
headers: { authorization: `Bearer ${await token()}`, ...extra, ...body === void 0 ? {} : { "content-type": "application/json" } },
|
|
530
742
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
531
743
|
});
|
|
532
744
|
let res = await send();
|
|
@@ -537,7 +749,7 @@ function createSystem(given = {}) {
|
|
|
537
749
|
if (!res.ok) await fail(res, `${method} ${path}`);
|
|
538
750
|
return res.status === 204 ? void 0 : res.json();
|
|
539
751
|
}
|
|
540
|
-
const data = async (method, path, body) => (await call(method, path, body))?.data;
|
|
752
|
+
const data = async (method, path, body, extra) => (await call(method, path, body, extra))?.data;
|
|
541
753
|
const id = encodeURIComponent;
|
|
542
754
|
return {
|
|
543
755
|
token,
|
|
@@ -574,7 +786,8 @@ function createSystem(given = {}) {
|
|
|
574
786
|
return toMember(await data(
|
|
575
787
|
"POST",
|
|
576
788
|
`/v1/organizations/${id(orgId)}/members`,
|
|
577
|
-
{ role: m.role, user_id: m.userId, email: m.email, name: m.name, locale: m.locale, client_id: clientId }
|
|
789
|
+
{ role: m.role, user_id: m.userId, email: m.email, name: m.name, locale: m.locale, client_id: clientId },
|
|
790
|
+
inviteHeaders
|
|
578
791
|
));
|
|
579
792
|
},
|
|
580
793
|
async setRole(orgId, userId, role) {
|
|
@@ -585,7 +798,7 @@ function createSystem(given = {}) {
|
|
|
585
798
|
}
|
|
586
799
|
},
|
|
587
800
|
async invite(person) {
|
|
588
|
-
const u = await data("POST", "/v1/users", { ...person, client_id: clientId });
|
|
801
|
+
const u = await data("POST", "/v1/users", { ...person, client_id: clientId }, inviteHeaders);
|
|
589
802
|
return { id: u.id, email: u.email };
|
|
590
803
|
},
|
|
591
804
|
theme: {
|
|
@@ -598,6 +811,19 @@ function createSystem(given = {}) {
|
|
|
598
811
|
return (await data("GET", `/v1/systems/${id(system)}/policy`)).roles ?? [];
|
|
599
812
|
}
|
|
600
813
|
},
|
|
814
|
+
catalog: {
|
|
815
|
+
async get(system) {
|
|
816
|
+
return (await data("GET", `/v1/systems/${id(system)}/catalog`)).resources ?? [];
|
|
817
|
+
},
|
|
818
|
+
async set(system, resources) {
|
|
819
|
+
return (await data("PUT", `/v1/systems/${id(system)}/catalog`, { resources })).resources ?? [];
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
async check(q) {
|
|
823
|
+
const system = q.system ?? ownSystem;
|
|
824
|
+
if (system === "") throw new CauthyConfigError("name the system to check in (system, or CAUTHY_SYSTEM)");
|
|
825
|
+
return checkResultOf(await data("POST", `/v1/systems/${id(system)}/check`, { organization_id: q.organizationId, user_id: q.userId, ...checkWire(q) }));
|
|
826
|
+
},
|
|
601
827
|
async publicJwks() {
|
|
602
828
|
if (!key) return { keys: [] };
|
|
603
829
|
const { kty, n, e } = asJwk(key) ?? await exportJWK(await signingKey());
|
|
@@ -745,6 +971,12 @@ function checkOrganization(value, required = false) {
|
|
|
745
971
|
if (v.length > MAX_ORGANIZATION) return { field: "organization", code: "too_long", message: `Use at most ${MAX_ORGANIZATION} characters.` };
|
|
746
972
|
return null;
|
|
747
973
|
}
|
|
974
|
+
function checkOrganizationName(value) {
|
|
975
|
+
const v = typeof value === "string" ? value.trim() : "";
|
|
976
|
+
if (v === "") return { field: "name", code: "required", message: "Enter the organization's name." };
|
|
977
|
+
if (v.length > MAX_ORGANIZATION) return { field: "name", code: "too_long", message: `Use at most ${MAX_ORGANIZATION} characters.` };
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
748
980
|
function checkCode(value) {
|
|
749
981
|
const v = typeof value === "string" ? value.trim() : "";
|
|
750
982
|
if (v === "") return { field: "code", code: "required", message: "Enter the code." };
|
|
@@ -876,6 +1108,9 @@ function redirect(location, cookies = []) {
|
|
|
876
1108
|
for (const c of cookies) h.append("set-cookie", c);
|
|
877
1109
|
return new Response(null, { status: 302, headers: h });
|
|
878
1110
|
}
|
|
1111
|
+
function conditionalOf(c) {
|
|
1112
|
+
return { resource: String(c?.resource ?? ""), action: String(c?.action ?? ""), effect: c?.effect === "deny" ? "deny" : "allow" };
|
|
1113
|
+
}
|
|
879
1114
|
function createCauthy(given = {}) {
|
|
880
1115
|
const issuer = withoutTrailingSlashes(given.issuer ?? env2("CAUTHY_ISSUER") ?? "");
|
|
881
1116
|
const clientId = given.clientId ?? env2("CAUTHY_CLIENT_ID") ?? "";
|
|
@@ -898,13 +1133,16 @@ function createCauthy(given = {}) {
|
|
|
898
1133
|
const againCookie = `${cookieName}_again`;
|
|
899
1134
|
const stepCookie = `${cookieName}_step`;
|
|
900
1135
|
const doFetch = (...args) => (given.fetch ?? fetch)(...args);
|
|
1136
|
+
const iconOf = iconFor(issuer);
|
|
901
1137
|
const key = keyOf(secret);
|
|
902
1138
|
const pages = {
|
|
903
1139
|
signIn: given.pages?.signIn ? localPath(given.pages.signIn, "/") : void 0,
|
|
904
1140
|
resetPassword: given.pages?.resetPassword ? localPath(given.pages.resetPassword, "/") : void 0,
|
|
905
|
-
verifyEmail: given.pages?.verifyEmail ? localPath(given.pages.verifyEmail, "/") : void 0
|
|
1141
|
+
verifyEmail: given.pages?.verifyEmail ? localPath(given.pages.verifyEmail, "/") : void 0,
|
|
1142
|
+
acceptInvite: given.pages?.acceptInvite ? localPath(given.pages.acceptInvite, "/") : void 0
|
|
906
1143
|
};
|
|
907
|
-
const
|
|
1144
|
+
const acceptInviteUrl = baseURL && pages.acceptInvite ? `${withoutTrailingSlashes(baseURL)}${pages.acceptInvite}` : void 0;
|
|
1145
|
+
const ownSystem = createSystem({ issuer, clientId, clientSecret, key: given.systemKey, system: system || void 0, acceptInviteUrl, fetch: given.fetch });
|
|
908
1146
|
const originOf = (request) => {
|
|
909
1147
|
if (baseURL) return withoutTrailingSlashes(baseURL);
|
|
910
1148
|
const url = new URL(request.url);
|
|
@@ -1039,13 +1277,35 @@ function createCauthy(given = {}) {
|
|
|
1039
1277
|
role: String(o.role ?? ""),
|
|
1040
1278
|
metadata: o.metadata ?? {},
|
|
1041
1279
|
...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) : [] } : {}
|
|
1280
|
+
...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
1281
|
})
|
|
1044
1282
|
);
|
|
1045
1283
|
if (orgCache.size > 5e3) orgCache.clear();
|
|
1046
1284
|
orgCache.set(session.accessToken, { at: Date.now(), value });
|
|
1047
1285
|
return value;
|
|
1048
1286
|
}
|
|
1287
|
+
async function decide(token2, question) {
|
|
1288
|
+
const org = organizationIdOf(question.organization);
|
|
1289
|
+
if (org === "") return { allowed: false, reason: "no organization named" };
|
|
1290
|
+
const res = await doFetch(`${issuer}/v1/oauth/organizations/${encodeURIComponent(org)}/check`, {
|
|
1291
|
+
method: "POST",
|
|
1292
|
+
headers: { authorization: `Bearer ${token2}`, "content-type": "application/json", accept: "application/json" },
|
|
1293
|
+
body: JSON.stringify(checkWire(question))
|
|
1294
|
+
}).catch(() => null);
|
|
1295
|
+
if (res === null) throw new CauthyApiError(503, "unavailable", "Cauthy could not be reached to check a permission");
|
|
1296
|
+
const body2 = await res.json().catch(() => null);
|
|
1297
|
+
if (res.ok) return checkResultOf(body2?.data);
|
|
1298
|
+
const first = body2?.errors?.[0];
|
|
1299
|
+
if (res.status === 401 || res.status === 403 || res.status === 404) {
|
|
1300
|
+
return { allowed: false, reason: first?.message ?? (res.status === 401 ? "not signed in" : res.status === 404 ? "not a member of the organization" : "refused") };
|
|
1301
|
+
}
|
|
1302
|
+
throw new CauthyApiError(res.status, first?.code ?? "error", `Cauthy refused to check a permission (${res.status}): ${first?.message ?? res.statusText}`);
|
|
1303
|
+
}
|
|
1304
|
+
async function check(who, question) {
|
|
1305
|
+
const session = "accessToken" in who ? who : await getSession(who);
|
|
1306
|
+
if (!session) return { allowed: false, reason: "not signed in" };
|
|
1307
|
+
return decide(session.accessToken, question);
|
|
1308
|
+
}
|
|
1049
1309
|
const forgetting = (fn) => async (...args) => {
|
|
1050
1310
|
const out2 = await fn(...args);
|
|
1051
1311
|
orgCache.clear();
|
|
@@ -1178,7 +1438,15 @@ function createCauthy(given = {}) {
|
|
|
1178
1438
|
name: String(d?.name ?? ""),
|
|
1179
1439
|
kind: String(d?.kind ?? ""),
|
|
1180
1440
|
role: String(d?.role ?? ""),
|
|
1181
|
-
|
|
1441
|
+
// A Cauthy from before a power says nothing of it: then it is not given.
|
|
1442
|
+
can: {
|
|
1443
|
+
readMembers: d?.can?.read_members === true,
|
|
1444
|
+
manageMembers: d?.can?.manage_members === true,
|
|
1445
|
+
removeMembers: d?.can?.remove_members === true,
|
|
1446
|
+
update: d?.can?.update === true,
|
|
1447
|
+
archive: d?.can?.archive === true,
|
|
1448
|
+
delete: d?.can?.delete === true
|
|
1449
|
+
}
|
|
1182
1450
|
});
|
|
1183
1451
|
const orgMemberOf = (d) => ({
|
|
1184
1452
|
userId: String(d?.user_id ?? ""),
|
|
@@ -1301,6 +1569,14 @@ function createCauthy(given = {}) {
|
|
|
1301
1569
|
for (const c of [...await write(request, s, readCookies(request.headers.get("cookie"))), ...extra]) h.append("set-cookie", c);
|
|
1302
1570
|
return json({ status: "signed_in", session: await toPublic(toSession(s)), ...more }, 200, h);
|
|
1303
1571
|
}
|
|
1572
|
+
async function loggedIn(request, data) {
|
|
1573
|
+
if (data?.mfa_required === true) {
|
|
1574
|
+
const step = { kind: "mfa", token: String(data.mfa_token ?? ""), returnTo: afterSignIn };
|
|
1575
|
+
const how = data.mfa_method === "email" ? { method: "email" } : {};
|
|
1576
|
+
return json({ status: "mfa_required", ...how }, 200, new Headers({ "set-cookie": writeCookie(stepCookie, await seal(step, await key), attrs(request, STEP_SECONDS)) }));
|
|
1577
|
+
}
|
|
1578
|
+
return signedIn(request, data);
|
|
1579
|
+
}
|
|
1304
1580
|
let lookCache = null;
|
|
1305
1581
|
let policyCache = null;
|
|
1306
1582
|
const keepPolicy = (settings) => {
|
|
@@ -1371,7 +1647,12 @@ function createCauthy(given = {}) {
|
|
|
1371
1647
|
"/me/organizations/members/add",
|
|
1372
1648
|
"/me/organizations/members/role",
|
|
1373
1649
|
"/me/organizations/members/remove",
|
|
1374
|
-
"/me/organizations/members/invite"
|
|
1650
|
+
"/me/organizations/members/invite",
|
|
1651
|
+
"/me/organizations/rename",
|
|
1652
|
+
"/me/organizations/archive",
|
|
1653
|
+
"/me/organizations/delete",
|
|
1654
|
+
"/invitation",
|
|
1655
|
+
"/invitation/accept"
|
|
1375
1656
|
].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
1657
|
if (!own) return null;
|
|
1377
1658
|
if (tenant === "") {
|
|
@@ -1393,17 +1674,21 @@ function createCauthy(given = {}) {
|
|
|
1393
1674
|
]);
|
|
1394
1675
|
const list = Array.isArray(providers.data) ? providers.data : [];
|
|
1395
1676
|
const password = keepPolicy(settings);
|
|
1396
|
-
const
|
|
1677
|
+
const passkeys = settings.data?.allow_passkeys !== false;
|
|
1678
|
+
const here = passkeys && place.data?.here === true;
|
|
1397
1679
|
return json({
|
|
1398
|
-
providers: list.map((p) => ({ id: String(p.provider ?? p.id ?? ""), name: String(p.label ?? p.name ?? p.provider ?? "") })).filter((p) => p.id !== ""),
|
|
1680
|
+
providers: list.map((p) => ({ id: String(p.provider ?? p.id ?? ""), name: String(p.label ?? p.name ?? p.provider ?? ""), ...iconOf(p) })).filter((p) => p.id !== ""),
|
|
1681
|
+
// Whether the workspace signs people in with a password at all. A
|
|
1682
|
+
// Cauthy from before the setting says nothing of it: then it does.
|
|
1683
|
+
passwordSignIn: settings.data?.allow_password !== false,
|
|
1399
1684
|
signUp: settings.data?.allow_signup !== false,
|
|
1400
1685
|
// The application's system makes an organization per sign-up: the form asks the company's name.
|
|
1401
1686
|
...settings.data?.signup?.organization?.ask === true ? { organization: { ask: true, required: settings.data.signup.organization.required === true } } : {},
|
|
1402
1687
|
// A sign-in link needs a page of the application to open.
|
|
1403
1688
|
magicLink: settings.data?.allow_magic_link === true && pages.signIn !== void 0,
|
|
1404
|
-
passkey: place.error ? true : here || place.data?.on_cauthy === true,
|
|
1689
|
+
passkey: passkeys && (place.error ? true : here || place.data?.on_cauthy === true),
|
|
1405
1690
|
passkeyHere: here,
|
|
1406
|
-
passkeyOnCauthy: place.error ? true : place.data?.on_cauthy === true,
|
|
1691
|
+
passkeyOnCauthy: passkeys && (place.error ? true : place.data?.on_cauthy === true),
|
|
1407
1692
|
// A company's sign-in, found by the person's work address.
|
|
1408
1693
|
sso: Array.isArray(sso.data) && sso.data.some((c) => c?.discoverable === true),
|
|
1409
1694
|
// The terms a sign-up accepts and the consents it answers, when the workspace asks any.
|
|
@@ -1413,6 +1698,14 @@ function createCauthy(given = {}) {
|
|
|
1413
1698
|
...password ? { password } : {}
|
|
1414
1699
|
});
|
|
1415
1700
|
}
|
|
1701
|
+
if (route === "/invitation" && !post) {
|
|
1702
|
+
const token2 = (url.searchParams.get("token") ?? "").trim();
|
|
1703
|
+
if (token2 === "" || token2.length > 512) return problem(422, "invalid_token", "This invitation is not valid, or it has expired.");
|
|
1704
|
+
const res = await ask(request, "GET", `/v1/auth/invitation?token=${encodeURIComponent(token2)}`);
|
|
1705
|
+
if (res.error) return problem(res.status, res.error.code, res.error.message);
|
|
1706
|
+
const organization = typeof res.data?.organization === "string" && res.data.organization !== "" ? { organization: res.data.organization } : {};
|
|
1707
|
+
return json({ status: "ok", invitation: { email: String(res.data?.email ?? ""), workspace: String(res.data?.workspace ?? ""), ...organization } });
|
|
1708
|
+
}
|
|
1416
1709
|
if (route === "/sign-in/sso" && !post) {
|
|
1417
1710
|
const returnTo = localPath(url.searchParams.get("return_to"), afterSignIn);
|
|
1418
1711
|
const failed2 = (code) => redirect(`${returnTo}${returnTo.includes("?") ? "&" : "?"}auth_error=${encodeURIComponent(code)}`);
|
|
@@ -1642,12 +1935,7 @@ function createCauthy(given = {}) {
|
|
|
1642
1935
|
if (bad) return invalid(bad);
|
|
1643
1936
|
const res = await ask(request, "POST", "/v1/auth/login", { email: String(b.email).trim(), password: b.password });
|
|
1644
1937
|
if (res.error) return problem(res.status, res.error.code, res.error.message);
|
|
1645
|
-
|
|
1646
|
-
const step = { kind: "mfa", token: String(res.data.mfa_token ?? ""), returnTo: afterSignIn };
|
|
1647
|
-
const how = res.data.mfa_method === "email" ? { method: "email" } : {};
|
|
1648
|
-
return json({ status: "mfa_required", ...how }, 200, new Headers({ "set-cookie": writeCookie(stepCookie, await seal(step, await key), attrs(request, STEP_SECONDS)) }));
|
|
1649
|
-
}
|
|
1650
|
-
return signedIn(request, res.data);
|
|
1938
|
+
return loggedIn(request, res.data);
|
|
1651
1939
|
}
|
|
1652
1940
|
if (route === "/sign-in/mfa") {
|
|
1653
1941
|
const bad = checkCode(b.code);
|
|
@@ -1842,6 +2130,23 @@ function createCauthy(given = {}) {
|
|
|
1842
2130
|
if (res.error) return refused(res, res.error.code === "weak_password" ? "password" : "email");
|
|
1843
2131
|
return signedIn(request, res.data);
|
|
1844
2132
|
}
|
|
2133
|
+
if (route === "/me/organizations/rename" || route === "/me/organizations/archive" || route === "/me/organizations/delete") {
|
|
2134
|
+
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
2135
|
+
if (!UUID.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
|
|
2136
|
+
const path = `/v1/auth/me/organizations/${org}`;
|
|
2137
|
+
if (route === "/me/organizations/rename") {
|
|
2138
|
+
const bad = checkOrganizationName(b.name);
|
|
2139
|
+
if (bad) return invalid(bad);
|
|
2140
|
+
const res2 = await as(path, { name: String(b.name).trim() }, "PATCH");
|
|
2141
|
+
if (res2.error) return refused(res2, res2.error.code === "validation_error" ? "name" : void 0);
|
|
2142
|
+
orgCache.delete(mine.token);
|
|
2143
|
+
return json({ status: "renamed", organization: seatOf(res2.data) }, 200, mine.headers);
|
|
2144
|
+
}
|
|
2145
|
+
const res = route === "/me/organizations/archive" ? await as(`${path}/archive`) : await as(path, void 0, "DELETE");
|
|
2146
|
+
if (res.error) return refused(res);
|
|
2147
|
+
orgCache.delete(mine.token);
|
|
2148
|
+
return json({ status: route === "/me/organizations/archive" ? "archived" : "deleted", organizationId: org }, 200, mine.headers);
|
|
2149
|
+
}
|
|
1845
2150
|
if (route.startsWith("/me/organizations/members/")) {
|
|
1846
2151
|
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
1847
2152
|
if (!UUID.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, mine.headers);
|
|
@@ -1851,13 +2156,13 @@ function createCauthy(given = {}) {
|
|
|
1851
2156
|
if (bad) return invalid(bad);
|
|
1852
2157
|
const role = typeof b.role === "string" ? b.role : "";
|
|
1853
2158
|
if (role === "") return problem(400, "invalid_role", "Choose a role.", "role", mine.headers);
|
|
1854
|
-
const res = await
|
|
2159
|
+
const res = await ask(request, "POST", base, {
|
|
1855
2160
|
email: String(b.email).trim(),
|
|
1856
2161
|
role,
|
|
1857
2162
|
name: typeof b.name === "string" ? b.name.trim() : "",
|
|
1858
2163
|
client_id: clientId,
|
|
1859
2164
|
...typeof b.locale === "string" && b.locale ? { locale: b.locale } : {}
|
|
1860
|
-
});
|
|
2165
|
+
}, pages.acceptInvite, mine.token);
|
|
1861
2166
|
if (res.error) return refused(res, res.error.code === "unknown_role" ? "role" : "email");
|
|
1862
2167
|
return json({ status: "added", member: orgMemberOf(res.data) }, 200, mine.headers);
|
|
1863
2168
|
}
|
|
@@ -1877,7 +2182,7 @@ function createCauthy(given = {}) {
|
|
|
1877
2182
|
return json({ status: "removed", userId: user }, 200, mine.headers);
|
|
1878
2183
|
}
|
|
1879
2184
|
if (route === "/me/organizations/members/invite") {
|
|
1880
|
-
const res = await
|
|
2185
|
+
const res = await ask(request, "POST", `${base}/${user}/invite`, { client_id: clientId, ...typeof b.locale === "string" && b.locale ? { locale: b.locale } : {} }, pages.acceptInvite, mine.token);
|
|
1881
2186
|
if (res.error) return refused(res);
|
|
1882
2187
|
return json({ status: "sent", userId: user }, 200, mine.headers);
|
|
1883
2188
|
}
|
|
@@ -1916,6 +2221,14 @@ function createCauthy(given = {}) {
|
|
|
1916
2221
|
if (res.data?.access_token) return signedIn(request, res.data);
|
|
1917
2222
|
return json({ status: "verified" });
|
|
1918
2223
|
}
|
|
2224
|
+
if (route === "/invitation/accept") {
|
|
2225
|
+
if (!isToken(b.token)) return problem(422, "invalid_token", "This invitation is not valid, or it has expired.");
|
|
2226
|
+
const bad = await newPasswordProblem(request, b.password);
|
|
2227
|
+
if (bad) return invalid(bad);
|
|
2228
|
+
const res = await ask(request, "POST", "/v1/auth/invitation/accept", { token: String(b.token), password: b.password });
|
|
2229
|
+
if (res.error) return problem(res.status, res.error.code, res.error.message, res.error.code === "weak_password" ? "password" : void 0, void 0, res.error.reasons);
|
|
2230
|
+
return loggedIn(request, res.data);
|
|
2231
|
+
}
|
|
1919
2232
|
if (route === "/sign-in/passkey/begin") {
|
|
1920
2233
|
const email = typeof b.email === "string" && checkEmail(b.email) === null ? b.email.trim() : "";
|
|
1921
2234
|
const res = await ask(request, "POST", "/v1/auth/webauthn/login/begin", email !== "" ? { email } : {});
|
|
@@ -1987,6 +2300,7 @@ function createCauthy(given = {}) {
|
|
|
1987
2300
|
const get = request.method === "GET" || request.method === "HEAD";
|
|
1988
2301
|
const mine = await ownRoute(request, url, route);
|
|
1989
2302
|
if (mine !== null) return mine;
|
|
2303
|
+
if (route === "/me/organizations/check") return checkRoute(request);
|
|
1990
2304
|
if (route === "/login" && get) return login(request, url);
|
|
1991
2305
|
if (route === "/callback" && get) return callback(request, url);
|
|
1992
2306
|
if (route === "/logout" && (get || request.method === "POST")) return logout(request);
|
|
@@ -2013,6 +2327,36 @@ function createCauthy(given = {}) {
|
|
|
2013
2327
|
}
|
|
2014
2328
|
return new Response("Not found", { status: 404 });
|
|
2015
2329
|
}
|
|
2330
|
+
async function checkRoute(request) {
|
|
2331
|
+
if (request.method !== "POST") return new Response("Method not allowed", { status: 405, headers: { allow: "POST" } });
|
|
2332
|
+
if (!ownRequest(request)) return problem(403, "cross_site", "This request did not come from this application.");
|
|
2333
|
+
const b = await body(request);
|
|
2334
|
+
if (b === null) return problem(400, "bad_request", "Send the question as JSON.");
|
|
2335
|
+
const asker = await signedInAs(request);
|
|
2336
|
+
if ("response" in asker) return asker.response;
|
|
2337
|
+
const org = typeof b.organizationId === "string" ? b.organizationId : "";
|
|
2338
|
+
if (!UUID.test(org)) return problem(400, "invalid_organization", "Say which organization.", void 0, asker.headers);
|
|
2339
|
+
const named = (v) => typeof v === "string" && v !== "" && v.length <= 200;
|
|
2340
|
+
if (!named(b.resource) || !named(b.action)) return problem(400, "bad_request", "Say which resource and action.", void 0, asker.headers);
|
|
2341
|
+
const attributes2 = b.attributes;
|
|
2342
|
+
if (attributes2 !== void 0 && (typeof attributes2 !== "object" || attributes2 === null || Array.isArray(attributes2))) {
|
|
2343
|
+
return problem(400, "bad_request", "The attributes are an object.", void 0, asker.headers);
|
|
2344
|
+
}
|
|
2345
|
+
try {
|
|
2346
|
+
const r = await decide(asker.token, {
|
|
2347
|
+
organization: org,
|
|
2348
|
+
resource: b.resource,
|
|
2349
|
+
action: b.action,
|
|
2350
|
+
...attributes2 !== void 0 ? { attributes: attributes2 } : {},
|
|
2351
|
+
...typeof b.resourceId === "string" ? { resourceId: b.resourceId } : {},
|
|
2352
|
+
...typeof b.ownerId === "string" ? { ownerId: b.ownerId } : {}
|
|
2353
|
+
});
|
|
2354
|
+
return json({ status: "decided", ...r }, 200, asker.headers);
|
|
2355
|
+
} catch (e) {
|
|
2356
|
+
const err = e instanceof CauthyApiError ? e : new CauthyApiError(503, "unavailable", "Cauthy could not be reached.");
|
|
2357
|
+
return problem(err.status, err.code, err.message.replace(/^cauthy-auth: /, ""), void 0, asker.headers);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2016
2360
|
async function protect(request) {
|
|
2017
2361
|
const { value } = await read(request);
|
|
2018
2362
|
if (value !== null && fresh(value)) return { session: toSession(value) };
|
|
@@ -2036,11 +2380,24 @@ function createCauthy(given = {}) {
|
|
|
2036
2380
|
resolve: resolve2,
|
|
2037
2381
|
protect,
|
|
2038
2382
|
organizations,
|
|
2383
|
+
check,
|
|
2039
2384
|
toPublic,
|
|
2040
2385
|
urls,
|
|
2041
2386
|
options: { issuer, clientId, basePath, afterSignIn, afterSignOut, cookieName }
|
|
2042
2387
|
};
|
|
2043
2388
|
}
|
|
2389
|
+
function iconFor(issuer) {
|
|
2390
|
+
return (p) => {
|
|
2391
|
+
const raw = typeof p?.icon_url === "string" ? p.icon_url.trim() : "";
|
|
2392
|
+
if (raw === "") return {};
|
|
2393
|
+
try {
|
|
2394
|
+
const url = new URL(raw.startsWith("/") && !raw.startsWith("//") ? `${issuer}${raw}` : raw);
|
|
2395
|
+
return url.protocol === "https:" || url.protocol === "http:" ? { iconUrl: url.href } : {};
|
|
2396
|
+
} catch {
|
|
2397
|
+
return {};
|
|
2398
|
+
}
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2044
2401
|
function legalOf(raw) {
|
|
2045
2402
|
const documents = Array.isArray(raw?.documents) ? raw.documents : [];
|
|
2046
2403
|
const purposes = Array.isArray(raw?.consent_purposes) ? raw.consent_purposes : [];
|
|
@@ -2050,128 +2407,6 @@ function legalOf(raw) {
|
|
|
2050
2407
|
};
|
|
2051
2408
|
}
|
|
2052
2409
|
|
|
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
2410
|
// src/connect.ts
|
|
2176
2411
|
function sessionSecret() {
|
|
2177
2412
|
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
@@ -2296,7 +2531,9 @@ export {
|
|
|
2296
2531
|
passwordPolicyOf,
|
|
2297
2532
|
passwordProblems,
|
|
2298
2533
|
policySource,
|
|
2534
|
+
pullCatalog,
|
|
2299
2535
|
pullPolicy,
|
|
2536
|
+
pushCatalog,
|
|
2300
2537
|
pushPolicy,
|
|
2301
2538
|
pushTheme,
|
|
2302
2539
|
themeFromStackmyth,
|