cauthy-auth 0.5.1 → 0.8.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 +68 -3
- package/dist/{cauthy-BCTCqubt.d.ts → cauthy-Bn3iv50y.d.ts} +1 -1
- package/dist/{cauthy-hswMoS7B.d.mts → cauthy-C29HCvag.d.mts} +1 -1
- package/dist/cli.js +492 -25
- package/dist/client.d.mts +236 -0
- package/dist/client.d.ts +236 -0
- package/dist/client.js +331 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +297 -0
- package/dist/client.mjs.map +1 -0
- package/dist/express.d.mts +24 -0
- package/dist/express.d.ts +24 -0
- package/dist/express.js +102 -0
- package/dist/express.js.map +1 -0
- package/dist/express.mjs +76 -0
- package/dist/express.mjs.map +1 -0
- package/dist/fastify.d.mts +2 -2
- package/dist/fastify.d.ts +2 -2
- package/dist/fastify.js +3 -1
- package/dist/fastify.js.map +1 -1
- package/dist/fastify.mjs +3 -1
- package/dist/fastify.mjs.map +1 -1
- package/dist/hono.d.mts +23 -0
- package/dist/hono.d.ts +23 -0
- package/dist/hono.js +50 -0
- package/dist/hono.js.map +1 -0
- package/dist/hono.mjs +24 -0
- package/dist/hono.mjs.map +1 -0
- package/dist/index.d.mts +44 -6
- package/dist/index.d.ts +44 -6
- package/dist/index.js +672 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +660 -37
- package/dist/index.mjs.map +1 -1
- package/dist/next.d.mts +24 -0
- package/dist/next.d.ts +24 -0
- package/dist/next.js +77 -0
- package/dist/next.js.map +1 -0
- package/dist/next.mjs +52 -0
- package/dist/next.mjs.map +1 -0
- package/dist/react.d.mts +546 -7
- package/dist/react.d.ts +546 -7
- package/dist/react.js +925 -14
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +913 -10
- package/dist/react.mjs.map +1 -1
- package/dist/styles.css +271 -0
- package/dist/svelte.d.mts +26 -0
- package/dist/svelte.d.ts +26 -0
- package/dist/svelte.js +402 -0
- package/dist/svelte.js.map +1 -0
- package/dist/svelte.mjs +373 -0
- package/dist/svelte.mjs.map +1 -0
- package/dist/{system-DvcQjORr.d.ts → system-DhRr_WxR.d.ts} +139 -1
- package/dist/{system-CR8p4BjB.d.mts → system-ZD_Wa8Bm.d.mts} +139 -1
- package/dist/theme.d.mts +59 -1
- package/dist/theme.d.ts +59 -1
- package/dist/theme.js +185 -5
- package/dist/theme.js.map +1 -1
- package/dist/theme.mjs +180 -4
- package/dist/theme.mjs.map +1 -1
- package/dist/validate-B_iC-4y8.d.mts +19 -0
- package/dist/validate-B_iC-4y8.d.ts +19 -0
- package/dist/vue.d.mts +28 -0
- package/dist/vue.d.ts +28 -0
- package/dist/vue.js +420 -0
- package/dist/vue.js.map +1 -0
- package/dist/vue.mjs +391 -0
- package/dist/vue.mjs.map +1 -0
- package/package.json +68 -7
- package/dist/permissions-B-a3eN0h.d.mts +0 -52
- package/dist/permissions-B-a3eN0h.d.ts +0 -52
package/dist/cli.js
CHANGED
|
@@ -12,10 +12,10 @@ function sessionSecret() {
|
|
|
12
12
|
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
13
13
|
}
|
|
14
14
|
async function connectSystem(o) {
|
|
15
|
-
const
|
|
16
|
-
const issuer = (o.issuer ??
|
|
17
|
-
const tenant = o.tenant ??
|
|
18
|
-
const token = o.token ??
|
|
15
|
+
const env2 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
|
|
16
|
+
const issuer = (o.issuer ?? env2("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
17
|
+
const tenant = o.tenant ?? env2("CAUTHY_TENANT") ?? "";
|
|
18
|
+
const token = o.token ?? env2("CAUTHY_TOKEN") ?? "";
|
|
19
19
|
for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
|
|
20
20
|
if (v === "") throw new Error(`cauthy-auth: ${name} is required to connect a system`);
|
|
21
21
|
}
|
|
@@ -47,17 +47,67 @@ async function connectSystem(o) {
|
|
|
47
47
|
env: { ...d.env, CAUTHY_SECRET: sessionSecret() }
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
function envFile(
|
|
51
|
-
return Object.entries(
|
|
50
|
+
function envFile(env2) {
|
|
51
|
+
return Object.entries(env2).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// src/permissions.ts
|
|
55
|
+
var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
56
|
+
function policySource(system, roles) {
|
|
57
|
+
const text = (v) => JSON.stringify(v);
|
|
58
|
+
const key = (k) => IDENT.test(k) ? k : text(k);
|
|
59
|
+
const one = (r) => {
|
|
60
|
+
const parts = [`role: ${text(r.role)}`];
|
|
61
|
+
if (r.name) parts.push(`name: ${text(r.name)}`);
|
|
62
|
+
if (r.description) parts.push(`description: ${text(r.description)}`);
|
|
63
|
+
if (r.inherits?.length) parts.push(`inherits: [${r.inherits.map(text).join(", ")}]`);
|
|
64
|
+
if (r.permissions?.length) {
|
|
65
|
+
const list = r.permissions.map((p) => ` { ${[...p.kind && p.kind !== "*" ? [`${key("kind")}: ${text(p.kind)}`] : [], `resource: ${text(p.resource)}`, `action: ${text(p.action)}`].join(", ")} },`);
|
|
66
|
+
parts.push(`permissions: [
|
|
67
|
+
${list.join("\n")}
|
|
68
|
+
]`);
|
|
69
|
+
}
|
|
70
|
+
return ` {
|
|
71
|
+
${parts.join(",\n ")},
|
|
72
|
+
},`;
|
|
73
|
+
};
|
|
74
|
+
return `// The catalog of roles of ${text(system)}, as Cauthy has it.
|
|
75
|
+
// Written by \`cauthy-auth policy --pull\`: run it again after the catalog
|
|
76
|
+
// changes, and do not edit this file by hand.
|
|
77
|
+
|
|
78
|
+
import { definePolicy, type PermissionOf, type RoleOf } from 'cauthy-auth'
|
|
79
|
+
|
|
80
|
+
export const policy = definePolicy([
|
|
81
|
+
${roles.map(one).join("\n")}
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
export const { can, hasRole } = policy
|
|
85
|
+
export type Role = RoleOf<typeof policy>
|
|
86
|
+
export type Permission = PermissionOf<typeof policy>
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
async function pullPolicy(o = {}) {
|
|
90
|
+
const env2 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
|
|
91
|
+
const issuer = (o.issuer ?? env2("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
92
|
+
const system = o.system ?? env2("CAUTHY_SYSTEM") ?? "";
|
|
93
|
+
const tenant = o.tenant ?? env2("CAUTHY_TENANT") ?? "";
|
|
94
|
+
const token = o.token ?? env2("CAUTHY_TOKEN") ?? "";
|
|
95
|
+
for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
|
|
96
|
+
if (v === "") throw new Error(`cauthy-auth: ${name} is required to read a policy`);
|
|
97
|
+
}
|
|
98
|
+
const res = await (o.fetch ?? fetch)(`${issuer}/v1/systems/${encodeURIComponent(system)}/policy`, {
|
|
99
|
+
headers: { authorization: `Bearer ${token}`, "x-tenant-key": tenant, accept: "application/json" }
|
|
100
|
+
});
|
|
101
|
+
const body = await res.json().catch(() => null);
|
|
102
|
+
if (!res.ok) throw new Error(`cauthy-auth: Cauthy refused to give the policy (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`);
|
|
103
|
+
return body?.data?.roles ?? [];
|
|
104
|
+
}
|
|
55
105
|
async function pushPolicy(o) {
|
|
56
|
-
const
|
|
57
|
-
const issuer = (o.issuer ??
|
|
58
|
-
const system = o.system ??
|
|
59
|
-
const tenant = o.tenant ??
|
|
60
|
-
const token = o.token ??
|
|
106
|
+
const env2 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
|
|
107
|
+
const issuer = (o.issuer ?? env2("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
108
|
+
const system = o.system ?? env2("CAUTHY_SYSTEM") ?? "";
|
|
109
|
+
const tenant = o.tenant ?? env2("CAUTHY_TENANT") ?? "";
|
|
110
|
+
const token = o.token ?? env2("CAUTHY_TOKEN") ?? "";
|
|
61
111
|
for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
|
|
62
112
|
if (v === "") throw new Error(`cauthy-auth: ${name} is required to push a policy`);
|
|
63
113
|
}
|
|
@@ -72,6 +122,9 @@ async function pushPolicy(o) {
|
|
|
72
122
|
}
|
|
73
123
|
}
|
|
74
124
|
|
|
125
|
+
// src/system.ts
|
|
126
|
+
var import_jose = require("jose");
|
|
127
|
+
|
|
75
128
|
// src/theme.ts
|
|
76
129
|
var MAX_RADIUS = 40;
|
|
77
130
|
function declarations(css) {
|
|
@@ -124,10 +177,92 @@ function applies(d, o) {
|
|
|
124
177
|
if (o.mode === "dark" && LIGHT.test(scope)) return false;
|
|
125
178
|
const named = NAMED.exec(scope);
|
|
126
179
|
if (named && !/^(dark|light)$/i.test(named[1]) && named[1] !== o.theme) return false;
|
|
127
|
-
if (!scope.startsWith("@") && !/:root|^html\b|^body\b|\[data-|^\*$|:host/.test(scope)) return false;
|
|
180
|
+
if (!scope.startsWith("@") && !/:root|^html\b|^body\b|\[data-|^\*$|:host|^\.(dark|light)\b/.test(scope)) return false;
|
|
128
181
|
}
|
|
129
182
|
return true;
|
|
130
183
|
}
|
|
184
|
+
var TOKEN_CHARS = /^[A-Za-z0-9 #%.,()/'"_+*-]+$/;
|
|
185
|
+
var TOKEN_FUNCS = /* @__PURE__ */ new Set([
|
|
186
|
+
"var",
|
|
187
|
+
"calc",
|
|
188
|
+
"min",
|
|
189
|
+
"max",
|
|
190
|
+
"clamp",
|
|
191
|
+
"rgb",
|
|
192
|
+
"rgba",
|
|
193
|
+
"hsl",
|
|
194
|
+
"hsla",
|
|
195
|
+
"hwb",
|
|
196
|
+
"lab",
|
|
197
|
+
"lch",
|
|
198
|
+
"oklab",
|
|
199
|
+
"oklch",
|
|
200
|
+
"color",
|
|
201
|
+
"color-mix",
|
|
202
|
+
"light-dark",
|
|
203
|
+
"cubic-bezier",
|
|
204
|
+
"steps",
|
|
205
|
+
"linear",
|
|
206
|
+
"linear-gradient",
|
|
207
|
+
"radial-gradient",
|
|
208
|
+
"conic-gradient",
|
|
209
|
+
"repeating-linear-gradient",
|
|
210
|
+
"repeating-radial-gradient",
|
|
211
|
+
"drop-shadow",
|
|
212
|
+
"blur",
|
|
213
|
+
"translate",
|
|
214
|
+
"translatex",
|
|
215
|
+
"translatey",
|
|
216
|
+
"scale",
|
|
217
|
+
"rotate"
|
|
218
|
+
]);
|
|
219
|
+
function tokenValue(value) {
|
|
220
|
+
const v = value.replace(/\s+/g, " ").trim();
|
|
221
|
+
if (v === "" || v.length > 400 || !TOKEN_CHARS.test(v)) return null;
|
|
222
|
+
for (const m of v.matchAll(/([A-Za-z-]+)\(/g)) if (!TOKEN_FUNCS.has(m[1].toLowerCase())) return null;
|
|
223
|
+
if (/^\(|[ ,(]\(/.test(v)) return null;
|
|
224
|
+
return v;
|
|
225
|
+
}
|
|
226
|
+
var THEME_SCOPE = /^(?:\s*(?::root|html|body|\[data-(?:theme|mode|color-scheme)(?:=["']?[\w-]+["']?)?\])\s*,?)+$/i;
|
|
227
|
+
function tokensFromStackmyth(css, options = {}) {
|
|
228
|
+
const mode = options.mode ?? "light";
|
|
229
|
+
const all = (Array.isArray(css) ? css : [css]).flatMap(declarations);
|
|
230
|
+
const named = options.theme ?? all.map((d) => d.within.map((w) => NAMED.exec(w)?.[1]).find((n) => n && !/^(dark|light)$/i.test(n))).find(Boolean);
|
|
231
|
+
const vars = /* @__PURE__ */ new Map();
|
|
232
|
+
for (const d of all) {
|
|
233
|
+
if (!applies(d, { mode, theme: named })) continue;
|
|
234
|
+
if (!d.within.every((w) => w.startsWith("@") || THEME_SCOPE.test(w))) continue;
|
|
235
|
+
vars.set(d.name, d.value);
|
|
236
|
+
}
|
|
237
|
+
const inline = (value, depth = 0) => {
|
|
238
|
+
if (depth > 16) return null;
|
|
239
|
+
let failed = false;
|
|
240
|
+
let out2 = value;
|
|
241
|
+
for (let guard = 0; guard < 64; guard++) {
|
|
242
|
+
const next = out2.replace(/var\(\s*(--(?!sm-)[\w-]+)\s*(?:,\s*([^()]*(?:\([^()]*\)[^()]*)*))?\)/, (_m, name, fallback) => {
|
|
243
|
+
const known = vars.get(name);
|
|
244
|
+
const got = known !== void 0 ? inline(known, depth + 1) : fallback !== void 0 ? inline(fallback.trim(), depth + 1) : null;
|
|
245
|
+
if (got === null) failed = true;
|
|
246
|
+
return got ?? "";
|
|
247
|
+
});
|
|
248
|
+
if (failed) return null;
|
|
249
|
+
if (next === out2) break;
|
|
250
|
+
out2 = next;
|
|
251
|
+
}
|
|
252
|
+
return out2;
|
|
253
|
+
};
|
|
254
|
+
const tokens = {};
|
|
255
|
+
const warnings = [];
|
|
256
|
+
for (const [name, raw] of vars) {
|
|
257
|
+
if (!/^--sm-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) continue;
|
|
258
|
+
const written = inline(raw);
|
|
259
|
+
const value = written === null ? null : tokenValue(written);
|
|
260
|
+
if (value === null) warnings.push(`${name}: "${raw.length > 60 ? raw.slice(0, 57) + "\u2026" : raw}" is not a value Cauthy takes`);
|
|
261
|
+
else tokens[name] = value;
|
|
262
|
+
}
|
|
263
|
+
if (Object.keys(tokens).length === 0) warnings.push("no --sm-* token was found in these stylesheets");
|
|
264
|
+
return { tokens, warnings };
|
|
265
|
+
}
|
|
131
266
|
function resolve(value, vars, seen = 0) {
|
|
132
267
|
if (seen > 16) return null;
|
|
133
268
|
let unresolved = false;
|
|
@@ -249,6 +384,63 @@ function themeFromStackmyth(css, options = {}) {
|
|
|
249
384
|
else if (![...vars.keys()].some((k) => k.startsWith("--sm-"))) warnings.push("no --sm-* token was found in these stylesheets");
|
|
250
385
|
return { theme, warnings };
|
|
251
386
|
}
|
|
387
|
+
var RADII = ["radius", "buttonRadius"];
|
|
388
|
+
var SHADCN = [
|
|
389
|
+
["background", ["--background", "--color-background"]],
|
|
390
|
+
["surface", ["--card", "--color-card", "--popover", "--color-popover"]],
|
|
391
|
+
["text", ["--foreground", "--color-foreground"]],
|
|
392
|
+
["textMuted", ["--muted-foreground", "--color-muted-foreground"]],
|
|
393
|
+
["border", ["--border", "--color-border"]],
|
|
394
|
+
["inputBorder", ["--input", "--color-input"]],
|
|
395
|
+
["primary", ["--primary", "--color-primary"]],
|
|
396
|
+
["primaryText", ["--primary-foreground", "--color-primary-foreground"]],
|
|
397
|
+
["accent", ["--ring", "--color-ring", "--primary", "--color-primary"]],
|
|
398
|
+
["error", ["--destructive", "--color-destructive"]],
|
|
399
|
+
["radius", ["--radius"]],
|
|
400
|
+
["buttonRadius", ["--radius"]],
|
|
401
|
+
["fontFamily", ["--font-sans", "--font-family"]]
|
|
402
|
+
];
|
|
403
|
+
function shadcnColor(value) {
|
|
404
|
+
return /^[\d.]+(?:deg)?\s+[\d.]+%\s+[\d.]+%(?:\s*\/\s*[\d.]+%?)?$/.test(value.trim()) ? toColor(`hsl(${value.trim()})`) : toColor(value);
|
|
405
|
+
}
|
|
406
|
+
function pageVariables(css, mode, theme) {
|
|
407
|
+
const vars = /* @__PURE__ */ new Map();
|
|
408
|
+
for (const sheet of Array.isArray(css) ? css : [css]) {
|
|
409
|
+
for (const d of declarations(sheet)) if (applies(d, { mode, theme })) vars.set(d.name, d.value);
|
|
410
|
+
}
|
|
411
|
+
return vars;
|
|
412
|
+
}
|
|
413
|
+
function themeFromCss(css, options = {}) {
|
|
414
|
+
const mode = options.mode ?? "light";
|
|
415
|
+
const vars = pageVariables(css, mode, options.theme);
|
|
416
|
+
const names = [...vars.keys()];
|
|
417
|
+
let format;
|
|
418
|
+
if (options.format && options.format !== "auto") format = options.format;
|
|
419
|
+
else if (options.map && Object.keys(options.map).length > 0) format = "custom";
|
|
420
|
+
else if (names.some((n) => n.startsWith("--sm-"))) format = "stackmyth";
|
|
421
|
+
else if (names.some((n) => /^--(color-)?(primary|background|foreground)$/.test(n))) format = "shadcn";
|
|
422
|
+
else format = "custom";
|
|
423
|
+
if (format === "stackmyth") return { ...themeFromStackmyth(css, { mode, theme: options.theme }), format };
|
|
424
|
+
const spec = format === "shadcn" ? SHADCN : Object.entries(options.map ?? {}).filter(([, v]) => v).map(([k, v]) => [k, [String(v).startsWith("--") ? String(v) : `--${v}`]]);
|
|
425
|
+
const theme = {};
|
|
426
|
+
const warnings = [];
|
|
427
|
+
for (const [field, tokens] of spec) {
|
|
428
|
+
for (const token of tokens) {
|
|
429
|
+
const raw = vars.get(token);
|
|
430
|
+
if (raw === void 0) continue;
|
|
431
|
+
const value = resolve(raw, vars);
|
|
432
|
+
const got = value === null ? null : RADII.includes(field) ? toRadius(value) : field === "fontFamily" ? toFont(value) : format === "shadcn" ? shadcnColor(value) : toColor(value);
|
|
433
|
+
if (got !== null) {
|
|
434
|
+
theme[field] = got;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
warnings.push(`${token}: "${raw.length > 60 ? raw.slice(0, 57) + "\u2026" : raw}" is not ${RADII.includes(field) ? "a length" : field === "fontFamily" ? "a font list" : "a colour"} Cauthy can take`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (vars.size === 0) warnings.push("no custom properties were found in these stylesheets");
|
|
441
|
+
else if (format === "custom" && spec.length === 0) warnings.push("these stylesheets use names of their own: say which variable each value is read from");
|
|
442
|
+
return { theme, warnings, format };
|
|
443
|
+
}
|
|
252
444
|
function toWire(theme) {
|
|
253
445
|
const names = {
|
|
254
446
|
background: "background",
|
|
@@ -272,7 +464,10 @@ function toWire(theme) {
|
|
|
272
464
|
panelColors: "panel_colors",
|
|
273
465
|
panelText: "panel_text",
|
|
274
466
|
panelIconUrl: "panel_icon_url",
|
|
275
|
-
texts: "texts"
|
|
467
|
+
texts: "texts",
|
|
468
|
+
inputBackground: "input_background",
|
|
469
|
+
inputBorder: "input_border",
|
|
470
|
+
tokens: "tokens"
|
|
276
471
|
};
|
|
277
472
|
const wire = {};
|
|
278
473
|
for (const [k, v] of Object.entries(theme)) {
|
|
@@ -281,12 +476,44 @@ function toWire(theme) {
|
|
|
281
476
|
}
|
|
282
477
|
return wire;
|
|
283
478
|
}
|
|
479
|
+
function fromWire(wire) {
|
|
480
|
+
const names = {
|
|
481
|
+
background: "background",
|
|
482
|
+
surface: "surface",
|
|
483
|
+
text: "text",
|
|
484
|
+
text_muted: "textMuted",
|
|
485
|
+
border: "border",
|
|
486
|
+
primary: "primary",
|
|
487
|
+
primary_text: "primaryText",
|
|
488
|
+
accent: "accent",
|
|
489
|
+
error: "error",
|
|
490
|
+
error_background: "errorBackground",
|
|
491
|
+
success: "success",
|
|
492
|
+
success_background: "successBackground",
|
|
493
|
+
radius: "radius",
|
|
494
|
+
button_radius: "buttonRadius",
|
|
495
|
+
font_family: "fontFamily",
|
|
496
|
+
logo_url: "logoUrl",
|
|
497
|
+
fonts: "fonts",
|
|
498
|
+
layout: "layout",
|
|
499
|
+
panel_colors: "panelColors",
|
|
500
|
+
panel_text: "panelText",
|
|
501
|
+
panel_icon_url: "panelIconUrl",
|
|
502
|
+
texts: "texts",
|
|
503
|
+
input_background: "inputBackground",
|
|
504
|
+
input_border: "inputBorder",
|
|
505
|
+
tokens: "tokens"
|
|
506
|
+
};
|
|
507
|
+
const theme = {};
|
|
508
|
+
for (const [k, v] of Object.entries(wire ?? {})) if (names[k] && v !== void 0 && v !== null && v !== "") theme[names[k]] = v;
|
|
509
|
+
return theme;
|
|
510
|
+
}
|
|
284
511
|
async function pushTheme(o) {
|
|
285
|
-
const
|
|
286
|
-
const issuer = (o.issuer ??
|
|
287
|
-
const clientId = o.clientId ??
|
|
288
|
-
const tenant = o.tenant ??
|
|
289
|
-
const token = o.token ??
|
|
512
|
+
const env2 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
|
|
513
|
+
const issuer = (o.issuer ?? env2("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
514
|
+
const clientId = o.clientId ?? env2("CAUTHY_CLIENT_ID") ?? "";
|
|
515
|
+
const tenant = o.tenant ?? env2("CAUTHY_TENANT") ?? "";
|
|
516
|
+
const token = o.token ?? env2("CAUTHY_TOKEN") ?? "";
|
|
290
517
|
for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_CLIENT_ID: clientId, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
|
|
291
518
|
if (v === "") throw new Error(`cauthy-auth: ${name} is required to push a theme`);
|
|
292
519
|
}
|
|
@@ -301,6 +528,184 @@ async function pushTheme(o) {
|
|
|
301
528
|
}
|
|
302
529
|
}
|
|
303
530
|
|
|
531
|
+
// src/types.ts
|
|
532
|
+
var CauthyConfigError = class extends Error {
|
|
533
|
+
constructor(message) {
|
|
534
|
+
super(`cauthy-auth: ${message}`);
|
|
535
|
+
this.name = "CauthyConfigError";
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// src/system.ts
|
|
540
|
+
var ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
541
|
+
var SKEW_MS = 3e4;
|
|
542
|
+
var CauthyApiError = class extends Error {
|
|
543
|
+
constructor(status, code, message) {
|
|
544
|
+
super(`cauthy-auth: ${message}`);
|
|
545
|
+
this.status = status;
|
|
546
|
+
this.code = code;
|
|
547
|
+
this.name = "CauthyApiError";
|
|
548
|
+
}
|
|
549
|
+
status;
|
|
550
|
+
code;
|
|
551
|
+
};
|
|
552
|
+
function env(name) {
|
|
553
|
+
return typeof process !== "undefined" ? process.env?.[name] : void 0;
|
|
554
|
+
}
|
|
555
|
+
function toOrganization(o) {
|
|
556
|
+
return {
|
|
557
|
+
id: o.id,
|
|
558
|
+
system: o.system,
|
|
559
|
+
name: o.name,
|
|
560
|
+
kind: o.kind,
|
|
561
|
+
externalId: o.external_id,
|
|
562
|
+
status: o.status,
|
|
563
|
+
metadata: o.metadata ?? {},
|
|
564
|
+
members: o.members,
|
|
565
|
+
createdAt: o.created_at,
|
|
566
|
+
updatedAt: o.updated_at
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function toMember(m) {
|
|
570
|
+
return { userId: m.user_id, email: m.email, name: m.name, status: m.status, role: m.role, ...m.invited === void 0 ? {} : { invited: m.invited } };
|
|
571
|
+
}
|
|
572
|
+
function asJwk(k) {
|
|
573
|
+
if (typeof k.privateKey !== "string") return k.privateKey;
|
|
574
|
+
return k.privateKey.includes("BEGIN") ? void 0 : JSON.parse(k.privateKey);
|
|
575
|
+
}
|
|
576
|
+
async function loadKey(k) {
|
|
577
|
+
const jwk = asJwk(k);
|
|
578
|
+
return jwk ? (0, import_jose.importJWK)(jwk, "RS256") : (0, import_jose.importPKCS8)(k.privateKey, "RS256", { extractable: true });
|
|
579
|
+
}
|
|
580
|
+
function createSystem(given = {}) {
|
|
581
|
+
const issuer = (given.issuer ?? env("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
582
|
+
const clientId = given.clientId ?? env("CAUTHY_CLIENT_ID") ?? "";
|
|
583
|
+
const clientSecret = given.clientSecret ?? env("CAUTHY_CLIENT_SECRET") ?? "";
|
|
584
|
+
const envKey = env("CAUTHY_SYSTEM_KEY");
|
|
585
|
+
const key = given.key ?? (envKey ? { privateKey: envKey, keyId: env("CAUTHY_SYSTEM_KEY_ID") ?? "" } : void 0);
|
|
586
|
+
const doFetch = (...args) => (given.fetch ?? fetch)(...args);
|
|
587
|
+
const tokenEndpoint = `${issuer}/v1/oauth/token`;
|
|
588
|
+
function configured() {
|
|
589
|
+
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address");
|
|
590
|
+
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
591
|
+
if (key ? key.keyId === "" : clientSecret === "") {
|
|
592
|
+
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)");
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
let imported;
|
|
596
|
+
const signingKey = () => imported ??= loadKey(key);
|
|
597
|
+
async function fail2(res, what) {
|
|
598
|
+
const body = await res.json().catch(() => null);
|
|
599
|
+
const first = body?.errors?.[0];
|
|
600
|
+
throw new CauthyApiError(res.status, first?.code ?? "error", `Cauthy refused ${what} (${res.status}): ${first?.message ?? res.statusText}`);
|
|
601
|
+
}
|
|
602
|
+
let held;
|
|
603
|
+
let asking;
|
|
604
|
+
async function ask() {
|
|
605
|
+
configured();
|
|
606
|
+
const form = new URLSearchParams({ grant_type: "client_credentials", client_id: clientId });
|
|
607
|
+
if (key) {
|
|
608
|
+
const assertion = await new import_jose.SignJWT({}).setProtectedHeader({ alg: "RS256", kid: key.keyId }).setIssuer(clientId).setSubject(clientId).setAudience(tokenEndpoint).setJti(crypto.randomUUID()).setIssuedAt().setExpirationTime("2m").sign(await signingKey());
|
|
609
|
+
form.set("client_assertion_type", ASSERTION_TYPE);
|
|
610
|
+
form.set("client_assertion", assertion);
|
|
611
|
+
} else {
|
|
612
|
+
form.set("client_secret", clientSecret);
|
|
613
|
+
}
|
|
614
|
+
const res = await doFetch(tokenEndpoint, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form });
|
|
615
|
+
if (!res.ok) await fail2(res, "the application's own token");
|
|
616
|
+
const t = await res.json();
|
|
617
|
+
held = { value: t.access_token, until: Date.now() + t.expires_in * 1e3 - SKEW_MS };
|
|
618
|
+
return t.access_token;
|
|
619
|
+
}
|
|
620
|
+
function token() {
|
|
621
|
+
if (held && Date.now() < held.until) return Promise.resolve(held.value);
|
|
622
|
+
return asking ??= ask().finally(() => {
|
|
623
|
+
asking = void 0;
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
async function call(method, path, body) {
|
|
627
|
+
const send = async () => doFetch(`${issuer}${path}`, {
|
|
628
|
+
method,
|
|
629
|
+
headers: { authorization: `Bearer ${await token()}`, ...body === void 0 ? {} : { "content-type": "application/json" } },
|
|
630
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
631
|
+
});
|
|
632
|
+
let res = await send();
|
|
633
|
+
if (res.status === 401 && held) {
|
|
634
|
+
held = void 0;
|
|
635
|
+
res = await send();
|
|
636
|
+
}
|
|
637
|
+
if (!res.ok) await fail2(res, `${method} ${path}`);
|
|
638
|
+
return res.status === 204 ? void 0 : res.json();
|
|
639
|
+
}
|
|
640
|
+
const data = async (method, path, body) => (await call(method, path, body))?.data;
|
|
641
|
+
const id = encodeURIComponent;
|
|
642
|
+
return {
|
|
643
|
+
token,
|
|
644
|
+
request: data,
|
|
645
|
+
organizations: {
|
|
646
|
+
async list(query = {}) {
|
|
647
|
+
const q = new URLSearchParams();
|
|
648
|
+
for (const [k, v] of Object.entries(query)) if (v !== void 0) q.set(k, String(v));
|
|
649
|
+
const out2 = await call("GET", `/v1/organizations${q.size ? `?${q}` : ""}`);
|
|
650
|
+
return { items: out2.data.map(toOrganization), total: out2.total_count };
|
|
651
|
+
},
|
|
652
|
+
async create(input) {
|
|
653
|
+
return toOrganization(await data(
|
|
654
|
+
"POST",
|
|
655
|
+
"/v1/organizations",
|
|
656
|
+
{ name: input.name, kind: input.kind, external_id: input.externalId, metadata: input.metadata }
|
|
657
|
+
));
|
|
658
|
+
},
|
|
659
|
+
async get(orgId) {
|
|
660
|
+
return toOrganization(await data("GET", `/v1/organizations/${id(orgId)}`));
|
|
661
|
+
},
|
|
662
|
+
async update(orgId, patch) {
|
|
663
|
+
return toOrganization(await data("PATCH", `/v1/organizations/${id(orgId)}`, patch));
|
|
664
|
+
},
|
|
665
|
+
async remove(orgId) {
|
|
666
|
+
await call("DELETE", `/v1/organizations/${id(orgId)}`);
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
members: {
|
|
670
|
+
async list(orgId) {
|
|
671
|
+
return (await data("GET", `/v1/organizations/${id(orgId)}/members`)).map(toMember);
|
|
672
|
+
},
|
|
673
|
+
async add(orgId, m) {
|
|
674
|
+
return toMember(await data(
|
|
675
|
+
"POST",
|
|
676
|
+
`/v1/organizations/${id(orgId)}/members`,
|
|
677
|
+
{ role: m.role, user_id: m.userId, email: m.email, name: m.name, locale: m.locale, client_id: clientId }
|
|
678
|
+
));
|
|
679
|
+
},
|
|
680
|
+
async setRole(orgId, userId, role) {
|
|
681
|
+
return toMember(await data("PUT", `/v1/organizations/${id(orgId)}/members/${id(userId)}`, { role }));
|
|
682
|
+
},
|
|
683
|
+
async remove(orgId, userId) {
|
|
684
|
+
await call("DELETE", `/v1/organizations/${id(orgId)}/members/${id(userId)}`);
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
async invite(person) {
|
|
688
|
+
const u = await data("POST", "/v1/users", { ...person, client_id: clientId });
|
|
689
|
+
return { id: u.id, email: u.email };
|
|
690
|
+
},
|
|
691
|
+
theme: {
|
|
692
|
+
async get(of = clientId) {
|
|
693
|
+
return fromWire(await data("GET", `/v1/oauth/clients/${id(of)}/theme`));
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
policy: {
|
|
697
|
+
async get(system) {
|
|
698
|
+
return (await data("GET", `/v1/systems/${id(system)}/policy`)).roles ?? [];
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
async publicJwks() {
|
|
702
|
+
if (!key) return { keys: [] };
|
|
703
|
+
const { kty, n, e } = asJwk(key) ?? await (0, import_jose.exportJWK)(await signingKey());
|
|
704
|
+
return { keys: [{ kty, n, e, kid: key.keyId, alg: "RS256", use: "sig" }] };
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
304
709
|
// src/cli.ts
|
|
305
710
|
var INIT_HELP = `cauthy-auth init --system <name> --app-url <address> [options]
|
|
306
711
|
|
|
@@ -373,7 +778,23 @@ Then, in the application:
|
|
|
373
778
|
and serve auth.handler(request) under ${(given["--callback-path"] ?? "/api/auth/callback").replace(/\/callback$/, "")}/*.
|
|
374
779
|
`);
|
|
375
780
|
}
|
|
376
|
-
var POLICY_HELP = `cauthy-auth policy --
|
|
781
|
+
var POLICY_HELP = `cauthy-auth policy --pull [--out <file.ts>] [--system <name>]
|
|
782
|
+
cauthy-auth policy --file <roles.json> [--system <name>] [--push]
|
|
783
|
+
|
|
784
|
+
--pull writes the catalog Cauthy has as a TypeScript module, so the
|
|
785
|
+
application's roles and permissions are typed from it:
|
|
786
|
+
|
|
787
|
+
import { can, hasRole } from './policy'
|
|
788
|
+
can(organization, 'ledger:write')
|
|
789
|
+
|
|
790
|
+
--pull Read the catalog from Cauthy.
|
|
791
|
+
--out <file> Write the module there. Without it, it is printed.
|
|
792
|
+
|
|
793
|
+
To pull: CAUTHY_ISSUER, CAUTHY_SYSTEM, and either the application's own
|
|
794
|
+
identity (CAUTHY_CLIENT_ID with CAUTHY_CLIENT_SECRET) or CAUTHY_TENANT with
|
|
795
|
+
CAUTHY_TOKEN (a personal access token with organizations:manage).
|
|
796
|
+
|
|
797
|
+
--file does the opposite, once:
|
|
377
798
|
|
|
378
799
|
Loads a system's catalog of roles into Cauthy from a file:
|
|
379
800
|
|
|
@@ -400,16 +821,34 @@ async function policy(argv) {
|
|
|
400
821
|
let file;
|
|
401
822
|
let system;
|
|
402
823
|
let push = false;
|
|
824
|
+
let pull = false;
|
|
825
|
+
let out2;
|
|
403
826
|
for (let i = 0; i < argv.length; i++) {
|
|
404
827
|
const arg = argv[i];
|
|
405
828
|
const value = () => argv[++i] ?? fail(`${arg} needs a value`);
|
|
406
829
|
if (arg === "--file") file = value();
|
|
407
830
|
else if (arg === "--system") system = value();
|
|
408
831
|
else if (arg === "--push") push = true;
|
|
832
|
+
else if (arg === "--pull") pull = true;
|
|
833
|
+
else if (arg === "--out") out2 = value();
|
|
409
834
|
else fail(`unknown option ${arg}
|
|
410
835
|
|
|
411
836
|
${POLICY_HELP}`);
|
|
412
837
|
}
|
|
838
|
+
if (pull) {
|
|
839
|
+
const of = system ?? process.env.CAUTHY_SYSTEM ?? fail("name the system with --system or CAUTHY_SYSTEM");
|
|
840
|
+
const read = process.env.CAUTHY_TOKEN ? await pullPolicy({ system: of }) : await createSystem().policy.get(of);
|
|
841
|
+
if (read.length === 0) fail(`Cauthy has no catalog of roles for "${of}"`);
|
|
842
|
+
const source = policySource(of, read);
|
|
843
|
+
if (out2) {
|
|
844
|
+
(0, import_node_fs.writeFileSync)(out2, source);
|
|
845
|
+
process.stdout.write(`${read.length} roles written to ${out2}: ${read.map((r) => r.role).join(", ")}
|
|
846
|
+
`);
|
|
847
|
+
} else {
|
|
848
|
+
process.stdout.write(source);
|
|
849
|
+
}
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
413
852
|
if (!file) fail("give the catalog with --file\n\n" + POLICY_HELP);
|
|
414
853
|
let roles;
|
|
415
854
|
try {
|
|
@@ -434,11 +873,20 @@ var HELP = `cauthy-auth init \u2026 connect an application to Cauthy (cauthy
|
|
|
434
873
|
cauthy-auth policy \u2026 load a system's catalog of roles (cauthy-auth policy --help)
|
|
435
874
|
cauthy-auth theme [options]
|
|
436
875
|
|
|
437
|
-
Reads the application's
|
|
876
|
+
Reads the application's look out of its stylesheets and gives it to Cauthy:
|
|
877
|
+
its hosted pages, and cauthy-auth's ready-made forms. The dashboard (Sign-in
|
|
878
|
+
Page Look) does the same with a preview.
|
|
438
879
|
|
|
439
|
-
--css <file> A stylesheet that
|
|
440
|
-
order the application loads them
|
|
441
|
-
|
|
880
|
+
--css <file> A stylesheet that sets the application's colours. Repeat it,
|
|
881
|
+
in the order the application loads them.
|
|
882
|
+
--format <name> auto (default), shadcn (shadcn/ui and Tailwind), stackmyth,
|
|
883
|
+
or custom with --map.
|
|
884
|
+
--map <v>=<var> For custom: which variable a value is read from, e.g.
|
|
885
|
+
--map primary=--brand --map radius=--corner. Values:
|
|
886
|
+
primary, primaryText, accent, background, surface, text,
|
|
887
|
+
textMuted, border, inputBackground, inputBorder, error,
|
|
888
|
+
errorBackground, success, successBackground, radius,
|
|
889
|
+
buttonRadius, fontFamily.
|
|
442
890
|
--theme <name> The named theme to read, for a sheet with [data-theme=\u2026].
|
|
443
891
|
--mode <mode> light (default) or dark.
|
|
444
892
|
--logo <url> An https image shown where "Cauthy" is.
|
|
@@ -466,6 +914,8 @@ async function main(argv) {
|
|
|
466
914
|
let logoUrl;
|
|
467
915
|
let push = false;
|
|
468
916
|
let clear = false;
|
|
917
|
+
let format = "auto";
|
|
918
|
+
const map = {};
|
|
469
919
|
for (let i = 1; i < argv.length; i++) {
|
|
470
920
|
const arg = argv[i];
|
|
471
921
|
const value = () => argv[++i] ?? fail(`${arg} needs a value`);
|
|
@@ -475,6 +925,14 @@ async function main(argv) {
|
|
|
475
925
|
const m = value();
|
|
476
926
|
if (m !== "light" && m !== "dark") fail("--mode is light or dark");
|
|
477
927
|
mode = m;
|
|
928
|
+
} else if (arg === "--format") {
|
|
929
|
+
const f = value();
|
|
930
|
+
if (!["auto", "shadcn", "stackmyth", "custom"].includes(f)) fail("--format is auto, shadcn, stackmyth or custom");
|
|
931
|
+
format = f;
|
|
932
|
+
} else if (arg === "--map") {
|
|
933
|
+
const m = /^(\w+)=(.+)$/.exec(value());
|
|
934
|
+
if (!m) fail("--map is <value>=<variable>, e.g. primary=--brand");
|
|
935
|
+
map[m[1]] = m[2];
|
|
478
936
|
} else if (arg === "--logo") logoUrl = value();
|
|
479
937
|
else if (arg === "--push") push = true;
|
|
480
938
|
else if (arg === "--clear") clear = true;
|
|
@@ -495,8 +953,17 @@ ${HELP}`);
|
|
|
495
953
|
return fail(`cannot read ${f}`);
|
|
496
954
|
}
|
|
497
955
|
});
|
|
498
|
-
const imported =
|
|
499
|
-
|
|
956
|
+
const imported = themeFromCss(sheets, { format, theme, mode, map: Object.keys(map).length > 0 ? map : void 0 });
|
|
957
|
+
if (logoUrl) imported.theme.logoUrl = logoUrl;
|
|
958
|
+
const warnings = [...imported.warnings];
|
|
959
|
+
if (imported.format === "stackmyth") {
|
|
960
|
+
const read = tokensFromStackmyth(sheets, { theme, mode });
|
|
961
|
+
if (Object.keys(read.tokens).length > 0) imported.theme.tokens = read.tokens;
|
|
962
|
+
warnings.push(...read.warnings);
|
|
963
|
+
}
|
|
964
|
+
process.stderr.write(`read as: ${imported.format}
|
|
965
|
+
`);
|
|
966
|
+
for (const w of warnings) process.stderr.write(`warning: ${w}
|
|
500
967
|
`);
|
|
501
968
|
if (Object.keys(imported.theme).length === 0) fail("no theme could be read from these stylesheets");
|
|
502
969
|
process.stdout.write(JSON.stringify(toWire(imported.theme), null, 2) + "\n");
|