cauthy-auth 0.2.0 → 0.4.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 +12 -1
- package/dist/{cauthy-BrIMaoj1.d.mts → cauthy--iqtLWNa.d.ts} +7 -1
- package/dist/{cauthy-CCZc9w8r.d.ts → cauthy-Dg-DNIo9.d.mts} +7 -1
- package/dist/cli.js +129 -4
- package/dist/fastify.d.mts +3 -2
- package/dist/fastify.d.ts +3 -2
- package/dist/index.d.mts +47 -3
- package/dist/index.d.ts +47 -3
- package/dist/index.js +495 -244
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +489 -241
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +4 -2
- package/dist/react.d.ts +4 -2
- package/dist/system-CL3Q30FT.d.ts +243 -0
- package/dist/system-CdvktS7x.d.mts +243 -0
- package/dist/theme.d.mts +16 -3
- package/dist/theme.d.ts +16 -3
- package/dist/theme.js +32 -2
- package/dist/theme.js.map +1 -1
- package/dist/theme.mjs +31 -2
- package/dist/theme.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/types-CYmUVWzY.d.mts +0 -106
- package/dist/types-CYmUVWzY.d.ts +0 -106
package/dist/index.mjs
CHANGED
|
@@ -54,6 +54,434 @@ 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
|
+
// src/theme.ts
|
|
61
|
+
var MAX_RADIUS = 40;
|
|
62
|
+
function declarations(css) {
|
|
63
|
+
const text = css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
64
|
+
const out2 = [];
|
|
65
|
+
const within = [];
|
|
66
|
+
let chunk = "";
|
|
67
|
+
let quote = "";
|
|
68
|
+
let depth = 0;
|
|
69
|
+
const flush = () => {
|
|
70
|
+
const m = /^\s*(--[\w-]+)\s*:\s*([\s\S]+?)\s*$/.exec(chunk);
|
|
71
|
+
if (m) out2.push({ name: m[1], value: m[2].replace(/\s+/g, " ").replace(/\s*!important$/, ""), within: [...within] });
|
|
72
|
+
chunk = "";
|
|
73
|
+
};
|
|
74
|
+
for (const ch of text) {
|
|
75
|
+
if (quote) {
|
|
76
|
+
chunk += ch;
|
|
77
|
+
if (ch === quote) quote = "";
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (ch === '"' || ch === "'") {
|
|
81
|
+
quote = ch;
|
|
82
|
+
chunk += ch;
|
|
83
|
+
} else if (ch === "(") {
|
|
84
|
+
depth++;
|
|
85
|
+
chunk += ch;
|
|
86
|
+
} else if (ch === ")") {
|
|
87
|
+
depth = Math.max(0, depth - 1);
|
|
88
|
+
chunk += ch;
|
|
89
|
+
} else if (ch === "{" && depth === 0) {
|
|
90
|
+
within.push(chunk.trim().replace(/\s+/g, " "));
|
|
91
|
+
chunk = "";
|
|
92
|
+
} else if (ch === "}" && depth === 0) {
|
|
93
|
+
flush();
|
|
94
|
+
within.pop();
|
|
95
|
+
} else if (ch === ";" && depth === 0) {
|
|
96
|
+
flush();
|
|
97
|
+
} else {
|
|
98
|
+
chunk += ch;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return out2;
|
|
102
|
+
}
|
|
103
|
+
var DARK = /prefers-color-scheme:\s*dark|data-(mode|theme|color-scheme)=["']?dark|\.dark\b/i;
|
|
104
|
+
var LIGHT = /prefers-color-scheme:\s*light|data-(mode|theme|color-scheme)=["']?light|\.light\b/i;
|
|
105
|
+
var NAMED = /data-theme=["']?([\w-]+)/i;
|
|
106
|
+
function applies(d, o) {
|
|
107
|
+
for (const scope of d.within) {
|
|
108
|
+
if (o.mode === "light" && DARK.test(scope)) return false;
|
|
109
|
+
if (o.mode === "dark" && LIGHT.test(scope)) return false;
|
|
110
|
+
const named = NAMED.exec(scope);
|
|
111
|
+
if (named && !/^(dark|light)$/i.test(named[1]) && named[1] !== o.theme) return false;
|
|
112
|
+
if (!scope.startsWith("@") && !/:root|^html\b|^body\b|\[data-|^\*$|:host/.test(scope)) return false;
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
function resolve(value, vars, seen = 0) {
|
|
117
|
+
if (seen > 16) return null;
|
|
118
|
+
let unresolved = false;
|
|
119
|
+
let out2 = value;
|
|
120
|
+
for (let guard = 0; guard < 32 && /var\(/.test(out2); guard++) {
|
|
121
|
+
out2 = out2.replace(/var\(\s*(--[\w-]+)\s*(?:,\s*([^()]*(?:\([^()]*\)[^()]*)*))?\)/, (_m, name, fallback) => {
|
|
122
|
+
const known = vars.get(name);
|
|
123
|
+
const next = known !== void 0 ? resolve(known, vars, seen + 1) : fallback !== void 0 ? resolve(fallback.trim(), vars, seen + 1) : null;
|
|
124
|
+
if (next === null) unresolved = true;
|
|
125
|
+
return next ?? "";
|
|
126
|
+
});
|
|
127
|
+
if (unresolved) return null;
|
|
128
|
+
}
|
|
129
|
+
return /var\(/.test(out2) ? null : out2.trim();
|
|
130
|
+
}
|
|
131
|
+
var clamp01 = (n) => Math.min(1, Math.max(0, n));
|
|
132
|
+
var hex2 = (n) => Math.round(clamp01(n) * 255).toString(16).padStart(2, "0");
|
|
133
|
+
var gamma = (c) => c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
134
|
+
function out(r, g, b, a = 1) {
|
|
135
|
+
if (a >= 1) return `#${hex2(r)}${hex2(g)}${hex2(b)}`;
|
|
136
|
+
return `rgba(${Math.round(clamp01(r) * 255)}, ${Math.round(clamp01(g) * 255)}, ${Math.round(clamp01(b) * 255)}, ${Math.round(clamp01(a) * 1e3) / 1e3})`;
|
|
137
|
+
}
|
|
138
|
+
var alpha = (s) => s === void 0 ? 1 : s.endsWith("%") ? parseFloat(s) / 100 : parseFloat(s);
|
|
139
|
+
var KEYWORDS = { white: "#ffffff", black: "#000000" };
|
|
140
|
+
function toColor(value) {
|
|
141
|
+
const v = value.trim().toLowerCase();
|
|
142
|
+
if (KEYWORDS[v]) return KEYWORDS[v];
|
|
143
|
+
if (/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(v)) return v;
|
|
144
|
+
let m = /^rgba?\(\s*([\d.]+%?)[\s,]+([\d.]+%?)[\s,]+([\d.]+%?)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/.exec(v);
|
|
145
|
+
if (m) {
|
|
146
|
+
const ch = (s) => s.endsWith("%") ? parseFloat(s) / 100 : parseFloat(s) / 255;
|
|
147
|
+
return out(ch(m[1]), ch(m[2]), ch(m[3]), alpha(m[4]));
|
|
148
|
+
}
|
|
149
|
+
m = /^hsla?\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%[\s,]+([\d.]+)%(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/.exec(v);
|
|
150
|
+
if (m) {
|
|
151
|
+
const h = (parseFloat(m[1]) % 360 + 360) % 360;
|
|
152
|
+
const s = parseFloat(m[2]) / 100;
|
|
153
|
+
const l = parseFloat(m[3]) / 100;
|
|
154
|
+
const f = (n) => {
|
|
155
|
+
const k = (n + h / 30) % 12;
|
|
156
|
+
return l - s * Math.min(l, 1 - l) * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
157
|
+
};
|
|
158
|
+
return out(f(0), f(8), f(4), alpha(m[4]));
|
|
159
|
+
}
|
|
160
|
+
m = /^oklch\(\s*([\d.]+%?)\s+([\d.]+%?)\s+([\d.]+)(?:deg)?(?:\s*\/\s*([\d.]+%?))?\s*\)$/.exec(v);
|
|
161
|
+
if (m) {
|
|
162
|
+
const L = m[1].endsWith("%") ? parseFloat(m[1]) / 100 : parseFloat(m[1]);
|
|
163
|
+
const C = m[2].endsWith("%") ? parseFloat(m[2]) / 100 * 0.4 : parseFloat(m[2]);
|
|
164
|
+
const H = parseFloat(m[3]) * Math.PI / 180;
|
|
165
|
+
const a = C * Math.cos(H);
|
|
166
|
+
const b = C * Math.sin(H);
|
|
167
|
+
const l = Math.pow(L + 0.3963377774 * a + 0.2158037573 * b, 3);
|
|
168
|
+
const mm = Math.pow(L - 0.1055613458 * a - 0.0638541728 * b, 3);
|
|
169
|
+
const s = Math.pow(L - 0.0894841775 * a - 1.291485548 * b, 3);
|
|
170
|
+
return out(
|
|
171
|
+
gamma(4.0767416621 * l - 3.3077115913 * mm + 0.2309699292 * s),
|
|
172
|
+
gamma(-1.2684380046 * l + 2.6097574011 * mm - 0.3413193965 * s),
|
|
173
|
+
gamma(-0.0041960863 * l - 0.7034186147 * mm + 1.707614701 * s),
|
|
174
|
+
alpha(m[4])
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
function toRadius(value) {
|
|
180
|
+
const m = /^([\d.]+)(px|rem|em)?$/.exec(value.trim());
|
|
181
|
+
if (!m) return null;
|
|
182
|
+
const px = parseFloat(m[1]) * (m[2] === "rem" || m[2] === "em" ? 16 : 1);
|
|
183
|
+
return Number.isFinite(px) ? Math.min(MAX_RADIUS, Math.max(0, Math.round(px))) : null;
|
|
184
|
+
}
|
|
185
|
+
function toFont(value) {
|
|
186
|
+
const v = value.trim();
|
|
187
|
+
return /^[A-Za-z0-9 ,'"_-]{1,200}$/.test(v) && (v.match(/"/g)?.length ?? 0) % 2 === 0 && (v.match(/'/g)?.length ?? 0) % 2 === 0 ? v : null;
|
|
188
|
+
}
|
|
189
|
+
var COLORS = [
|
|
190
|
+
["background", ["--sm-bg-surface", "--sm-surface"]],
|
|
191
|
+
["surface", ["--sm-bg-surface-raised", "--sm-surface-raised"]],
|
|
192
|
+
["text", ["--sm-text-primary", "--sm-surface-text"]],
|
|
193
|
+
["textMuted", ["--sm-text-secondary"]],
|
|
194
|
+
["border", ["--sm-border-default", "--sm-surface-border"]],
|
|
195
|
+
["primary", ["--sm-primary", "--sm-bg-interactive"]],
|
|
196
|
+
["primaryText", ["--sm-primary-text", "--sm-text-inverse"]],
|
|
197
|
+
["accent", ["--sm-text-brand", "--sm-border-focus", "--sm-accent"]],
|
|
198
|
+
["error", ["--sm-error-soft-text", "--sm-error"]],
|
|
199
|
+
["errorBackground", ["--sm-error-soft"]],
|
|
200
|
+
["success", ["--sm-success-soft-text", "--sm-success"]],
|
|
201
|
+
["successBackground", ["--sm-success-soft"]]
|
|
202
|
+
];
|
|
203
|
+
function themeFromStackmyth(css, options = {}) {
|
|
204
|
+
const o = { mode: options.mode ?? "light", theme: options.theme };
|
|
205
|
+
const vars = /* @__PURE__ */ new Map();
|
|
206
|
+
for (const sheet of Array.isArray(css) ? css : [css]) {
|
|
207
|
+
for (const d of declarations(sheet)) if (applies(d, o)) vars.set(d.name, d.value);
|
|
208
|
+
}
|
|
209
|
+
const theme = {};
|
|
210
|
+
const warnings = [];
|
|
211
|
+
const read = (tokens, to, what) => {
|
|
212
|
+
for (const token of tokens) {
|
|
213
|
+
const raw = vars.get(token);
|
|
214
|
+
if (raw === void 0) continue;
|
|
215
|
+
const value = resolve(raw, vars);
|
|
216
|
+
const got = value === null ? null : to(value);
|
|
217
|
+
if (got !== null) return got;
|
|
218
|
+
warnings.push(`${token}: "${raw}" is not ${what} Cauthy can take`);
|
|
219
|
+
}
|
|
220
|
+
return void 0;
|
|
221
|
+
};
|
|
222
|
+
for (const [field, tokens] of COLORS) {
|
|
223
|
+
const c = read(tokens, toColor, "a colour");
|
|
224
|
+
if (c !== void 0) theme[field] = c;
|
|
225
|
+
}
|
|
226
|
+
const radius = read(["--sm-radius-container", "--sm-radius-md"], toRadius, "a length");
|
|
227
|
+
if (radius !== void 0) theme.radius = radius;
|
|
228
|
+
const button = read(["--sm-radius-interactive", "--sm-radius-sm"], toRadius, "a length");
|
|
229
|
+
if (button !== void 0) theme.buttonRadius = button;
|
|
230
|
+
const font = read(["--sm-font-family"], toFont, "a font list");
|
|
231
|
+
if (font !== void 0) theme.fontFamily = font;
|
|
232
|
+
if (options.logoUrl) theme.logoUrl = options.logoUrl;
|
|
233
|
+
if (vars.size === 0) warnings.push("no custom properties were found: is this the stylesheet that declares the --sm-* tokens?");
|
|
234
|
+
else if (![...vars.keys()].some((k) => k.startsWith("--sm-"))) warnings.push("no --sm-* token was found in these stylesheets");
|
|
235
|
+
return { theme, warnings };
|
|
236
|
+
}
|
|
237
|
+
function toWire(theme) {
|
|
238
|
+
const names = {
|
|
239
|
+
background: "background",
|
|
240
|
+
surface: "surface",
|
|
241
|
+
text: "text",
|
|
242
|
+
textMuted: "text_muted",
|
|
243
|
+
border: "border",
|
|
244
|
+
primary: "primary",
|
|
245
|
+
primaryText: "primary_text",
|
|
246
|
+
accent: "accent",
|
|
247
|
+
error: "error",
|
|
248
|
+
errorBackground: "error_background",
|
|
249
|
+
success: "success",
|
|
250
|
+
successBackground: "success_background",
|
|
251
|
+
radius: "radius",
|
|
252
|
+
buttonRadius: "button_radius",
|
|
253
|
+
fontFamily: "font_family",
|
|
254
|
+
logoUrl: "logo_url",
|
|
255
|
+
fonts: "fonts"
|
|
256
|
+
};
|
|
257
|
+
const wire = {};
|
|
258
|
+
for (const [k, v] of Object.entries(theme)) {
|
|
259
|
+
if (v === void 0 || v === "" || Array.isArray(v) && v.length === 0) continue;
|
|
260
|
+
wire[names[k]] = v;
|
|
261
|
+
}
|
|
262
|
+
return wire;
|
|
263
|
+
}
|
|
264
|
+
function fromWire(wire) {
|
|
265
|
+
const names = {
|
|
266
|
+
background: "background",
|
|
267
|
+
surface: "surface",
|
|
268
|
+
text: "text",
|
|
269
|
+
text_muted: "textMuted",
|
|
270
|
+
border: "border",
|
|
271
|
+
primary: "primary",
|
|
272
|
+
primary_text: "primaryText",
|
|
273
|
+
accent: "accent",
|
|
274
|
+
error: "error",
|
|
275
|
+
error_background: "errorBackground",
|
|
276
|
+
success: "success",
|
|
277
|
+
success_background: "successBackground",
|
|
278
|
+
radius: "radius",
|
|
279
|
+
button_radius: "buttonRadius",
|
|
280
|
+
font_family: "fontFamily",
|
|
281
|
+
logo_url: "logoUrl",
|
|
282
|
+
fonts: "fonts"
|
|
283
|
+
};
|
|
284
|
+
const theme = {};
|
|
285
|
+
for (const [k, v] of Object.entries(wire ?? {})) if (names[k] && v !== void 0 && v !== null && v !== "") theme[names[k]] = v;
|
|
286
|
+
return theme;
|
|
287
|
+
}
|
|
288
|
+
async function pushTheme(o) {
|
|
289
|
+
const env3 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
|
|
290
|
+
const issuer = (o.issuer ?? env3("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
291
|
+
const clientId = o.clientId ?? env3("CAUTHY_CLIENT_ID") ?? "";
|
|
292
|
+
const tenant = o.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
293
|
+
const token = o.token ?? env3("CAUTHY_TOKEN") ?? "";
|
|
294
|
+
for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_CLIENT_ID: clientId, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
|
|
295
|
+
if (v === "") throw new Error(`cauthy-auth: ${name} is required to push a theme`);
|
|
296
|
+
}
|
|
297
|
+
const res = await (o.fetch ?? fetch)(`${issuer}/v1/oauth/clients/${encodeURIComponent(clientId)}/theme`, {
|
|
298
|
+
method: "PUT",
|
|
299
|
+
headers: { authorization: `Bearer ${token}`, "x-tenant-key": tenant, "content-type": "application/json" },
|
|
300
|
+
body: JSON.stringify(toWire(o.theme))
|
|
301
|
+
});
|
|
302
|
+
if (!res.ok) {
|
|
303
|
+
const body = await res.json().catch(() => null);
|
|
304
|
+
throw new Error(`cauthy-auth: Cauthy refused the theme (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/types.ts
|
|
309
|
+
var CauthyConfigError = class extends Error {
|
|
310
|
+
constructor(message) {
|
|
311
|
+
super(`cauthy-auth: ${message}`);
|
|
312
|
+
this.name = "CauthyConfigError";
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// src/system.ts
|
|
317
|
+
var ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
318
|
+
var SKEW_MS = 3e4;
|
|
319
|
+
var CauthyApiError = class extends Error {
|
|
320
|
+
constructor(status, code, message) {
|
|
321
|
+
super(`cauthy-auth: ${message}`);
|
|
322
|
+
this.status = status;
|
|
323
|
+
this.code = code;
|
|
324
|
+
this.name = "CauthyApiError";
|
|
325
|
+
}
|
|
326
|
+
status;
|
|
327
|
+
code;
|
|
328
|
+
};
|
|
329
|
+
function env(name) {
|
|
330
|
+
return typeof process !== "undefined" ? process.env?.[name] : void 0;
|
|
331
|
+
}
|
|
332
|
+
function toOrganization(o) {
|
|
333
|
+
return {
|
|
334
|
+
id: o.id,
|
|
335
|
+
system: o.system,
|
|
336
|
+
name: o.name,
|
|
337
|
+
kind: o.kind,
|
|
338
|
+
externalId: o.external_id,
|
|
339
|
+
status: o.status,
|
|
340
|
+
metadata: o.metadata ?? {},
|
|
341
|
+
members: o.members,
|
|
342
|
+
createdAt: o.created_at,
|
|
343
|
+
updatedAt: o.updated_at
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function toMember(m) {
|
|
347
|
+
return { userId: m.user_id, email: m.email, name: m.name, status: m.status, role: m.role, ...m.invited === void 0 ? {} : { invited: m.invited } };
|
|
348
|
+
}
|
|
349
|
+
function asJwk(k) {
|
|
350
|
+
if (typeof k.privateKey !== "string") return k.privateKey;
|
|
351
|
+
return k.privateKey.includes("BEGIN") ? void 0 : JSON.parse(k.privateKey);
|
|
352
|
+
}
|
|
353
|
+
async function loadKey(k) {
|
|
354
|
+
const jwk = asJwk(k);
|
|
355
|
+
return jwk ? importJWK(jwk, "RS256") : importPKCS8(k.privateKey, "RS256", { extractable: true });
|
|
356
|
+
}
|
|
357
|
+
function createSystem(given = {}) {
|
|
358
|
+
const issuer = (given.issuer ?? env("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
359
|
+
const clientId = given.clientId ?? env("CAUTHY_CLIENT_ID") ?? "";
|
|
360
|
+
const clientSecret = given.clientSecret ?? env("CAUTHY_CLIENT_SECRET") ?? "";
|
|
361
|
+
const envKey = env("CAUTHY_SYSTEM_KEY");
|
|
362
|
+
const key = given.key ?? (envKey ? { privateKey: envKey, keyId: env("CAUTHY_SYSTEM_KEY_ID") ?? "" } : void 0);
|
|
363
|
+
const doFetch = (...args) => (given.fetch ?? fetch)(...args);
|
|
364
|
+
const tokenEndpoint = `${issuer}/v1/oauth/token`;
|
|
365
|
+
function configured() {
|
|
366
|
+
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address");
|
|
367
|
+
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
368
|
+
if (key ? key.keyId === "" : clientSecret === "") {
|
|
369
|
+
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)");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
let imported;
|
|
373
|
+
const signingKey = () => imported ??= loadKey(key);
|
|
374
|
+
async function fail(res, what) {
|
|
375
|
+
const body = await res.json().catch(() => null);
|
|
376
|
+
const first = body?.errors?.[0];
|
|
377
|
+
throw new CauthyApiError(res.status, first?.code ?? "error", `Cauthy refused ${what} (${res.status}): ${first?.message ?? res.statusText}`);
|
|
378
|
+
}
|
|
379
|
+
let held;
|
|
380
|
+
let asking;
|
|
381
|
+
async function ask() {
|
|
382
|
+
configured();
|
|
383
|
+
const form = new URLSearchParams({ grant_type: "client_credentials", client_id: clientId });
|
|
384
|
+
if (key) {
|
|
385
|
+
const assertion = await new SignJWT({}).setProtectedHeader({ alg: "RS256", kid: key.keyId }).setIssuer(clientId).setSubject(clientId).setAudience(tokenEndpoint).setJti(crypto.randomUUID()).setIssuedAt().setExpirationTime("2m").sign(await signingKey());
|
|
386
|
+
form.set("client_assertion_type", ASSERTION_TYPE);
|
|
387
|
+
form.set("client_assertion", assertion);
|
|
388
|
+
} else {
|
|
389
|
+
form.set("client_secret", clientSecret);
|
|
390
|
+
}
|
|
391
|
+
const res = await doFetch(tokenEndpoint, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form });
|
|
392
|
+
if (!res.ok) await fail(res, "the application's own token");
|
|
393
|
+
const t = await res.json();
|
|
394
|
+
held = { value: t.access_token, until: Date.now() + t.expires_in * 1e3 - SKEW_MS };
|
|
395
|
+
return t.access_token;
|
|
396
|
+
}
|
|
397
|
+
function token() {
|
|
398
|
+
if (held && Date.now() < held.until) return Promise.resolve(held.value);
|
|
399
|
+
return asking ??= ask().finally(() => {
|
|
400
|
+
asking = void 0;
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
async function call(method, path, body) {
|
|
404
|
+
const send = async () => doFetch(`${issuer}${path}`, {
|
|
405
|
+
method,
|
|
406
|
+
headers: { authorization: `Bearer ${await token()}`, ...body === void 0 ? {} : { "content-type": "application/json" } },
|
|
407
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
408
|
+
});
|
|
409
|
+
let res = await send();
|
|
410
|
+
if (res.status === 401 && held) {
|
|
411
|
+
held = void 0;
|
|
412
|
+
res = await send();
|
|
413
|
+
}
|
|
414
|
+
if (!res.ok) await fail(res, `${method} ${path}`);
|
|
415
|
+
return res.status === 204 ? void 0 : res.json();
|
|
416
|
+
}
|
|
417
|
+
const data = async (method, path, body) => (await call(method, path, body))?.data;
|
|
418
|
+
const id = encodeURIComponent;
|
|
419
|
+
return {
|
|
420
|
+
token,
|
|
421
|
+
request: data,
|
|
422
|
+
organizations: {
|
|
423
|
+
async list(query = {}) {
|
|
424
|
+
const q = new URLSearchParams();
|
|
425
|
+
for (const [k, v] of Object.entries(query)) if (v !== void 0) q.set(k, String(v));
|
|
426
|
+
const out2 = await call("GET", `/v1/organizations${q.size ? `?${q}` : ""}`);
|
|
427
|
+
return { items: out2.data.map(toOrganization), total: out2.total_count };
|
|
428
|
+
},
|
|
429
|
+
async create(input) {
|
|
430
|
+
return toOrganization(await data(
|
|
431
|
+
"POST",
|
|
432
|
+
"/v1/organizations",
|
|
433
|
+
{ name: input.name, kind: input.kind, external_id: input.externalId, metadata: input.metadata }
|
|
434
|
+
));
|
|
435
|
+
},
|
|
436
|
+
async get(orgId) {
|
|
437
|
+
return toOrganization(await data("GET", `/v1/organizations/${id(orgId)}`));
|
|
438
|
+
},
|
|
439
|
+
async update(orgId, patch) {
|
|
440
|
+
return toOrganization(await data("PATCH", `/v1/organizations/${id(orgId)}`, patch));
|
|
441
|
+
},
|
|
442
|
+
async remove(orgId) {
|
|
443
|
+
await call("DELETE", `/v1/organizations/${id(orgId)}`);
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
members: {
|
|
447
|
+
async list(orgId) {
|
|
448
|
+
return (await data("GET", `/v1/organizations/${id(orgId)}/members`)).map(toMember);
|
|
449
|
+
},
|
|
450
|
+
async add(orgId, m) {
|
|
451
|
+
return toMember(await data(
|
|
452
|
+
"POST",
|
|
453
|
+
`/v1/organizations/${id(orgId)}/members`,
|
|
454
|
+
{ role: m.role, user_id: m.userId, email: m.email, name: m.name, locale: m.locale, client_id: clientId }
|
|
455
|
+
));
|
|
456
|
+
},
|
|
457
|
+
async setRole(orgId, userId, role) {
|
|
458
|
+
return toMember(await data("PUT", `/v1/organizations/${id(orgId)}/members/${id(userId)}`, { role }));
|
|
459
|
+
},
|
|
460
|
+
async remove(orgId, userId) {
|
|
461
|
+
await call("DELETE", `/v1/organizations/${id(orgId)}/members/${id(userId)}`);
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
async invite(person) {
|
|
465
|
+
const u = await data("POST", "/v1/users", { ...person, client_id: clientId });
|
|
466
|
+
return { id: u.id, email: u.email };
|
|
467
|
+
},
|
|
468
|
+
theme: {
|
|
469
|
+
async get(of = clientId) {
|
|
470
|
+
return fromWire(await data("GET", `/v1/oauth/clients/${id(of)}/theme`));
|
|
471
|
+
},
|
|
472
|
+
async set(theme, of = clientId) {
|
|
473
|
+
if (theme === null) await call("DELETE", `/v1/oauth/clients/${id(of)}/theme`);
|
|
474
|
+
else await call("PUT", `/v1/oauth/clients/${id(of)}/theme`, toWire(theme));
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
async publicJwks() {
|
|
478
|
+
if (!key) return { keys: [] };
|
|
479
|
+
const { kty, n, e } = asJwk(key) ?? await exportJWK(await signingKey());
|
|
480
|
+
return { keys: [{ kty, n, e, kid: key.keyId, alg: "RS256", use: "sig" }] };
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
57
485
|
// src/webhook.ts
|
|
58
486
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
59
487
|
var CauthyWebhookError = class extends Error {
|
|
@@ -75,8 +503,8 @@ function keysOf(issuer) {
|
|
|
75
503
|
}
|
|
76
504
|
var hex = (s) => typeof s === "string" && s !== "" ? s : void 0;
|
|
77
505
|
async function verifyWebhook(request, options = {}) {
|
|
78
|
-
const
|
|
79
|
-
const issuer = (options.issuer ??
|
|
506
|
+
const env3 = typeof process !== "undefined" ? process.env?.CAUTHY_ISSUER : void 0;
|
|
507
|
+
const issuer = (options.issuer ?? env3 ?? "").replace(/\/+$/, "");
|
|
80
508
|
if (issuer === "") throw new CauthyWebhookError("issuer (CAUTHY_ISSUER) is not set");
|
|
81
509
|
const signature = request.headers.get("x-cauthy-signature-jwt");
|
|
82
510
|
if (!signature) throw new CauthyWebhookError("no signature");
|
|
@@ -125,20 +553,12 @@ async function verifyWebhook(request, options = {}) {
|
|
|
125
553
|
};
|
|
126
554
|
}
|
|
127
555
|
|
|
128
|
-
// src/types.ts
|
|
129
|
-
var CauthyConfigError = class extends Error {
|
|
130
|
-
constructor(message) {
|
|
131
|
-
super(`cauthy-auth: ${message}`);
|
|
132
|
-
this.name = "CauthyConfigError";
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
|
|
136
556
|
// src/cauthy.ts
|
|
137
557
|
var SESSION_DAYS = 30;
|
|
138
558
|
var FLOW_SECONDS = 600;
|
|
139
559
|
var SKEW_SECONDS = 30;
|
|
140
560
|
var ORGANIZATIONS_TTL_MS = 6e4;
|
|
141
|
-
function
|
|
561
|
+
function env2(name) {
|
|
142
562
|
return typeof process !== "undefined" ? process.env?.[name] : void 0;
|
|
143
563
|
}
|
|
144
564
|
function b64url(bytes) {
|
|
@@ -165,11 +585,11 @@ function redirect(location, cookies = []) {
|
|
|
165
585
|
return new Response(null, { status: 302, headers: h });
|
|
166
586
|
}
|
|
167
587
|
function createCauthy(given = {}) {
|
|
168
|
-
const issuer = (given.issuer ??
|
|
169
|
-
const clientId = given.clientId ??
|
|
170
|
-
const clientSecret = given.clientSecret ??
|
|
171
|
-
const secret = given.secret ??
|
|
172
|
-
const system = given.system ??
|
|
588
|
+
const issuer = (given.issuer ?? env2("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
589
|
+
const clientId = given.clientId ?? env2("CAUTHY_CLIENT_ID") ?? "";
|
|
590
|
+
const clientSecret = given.clientSecret ?? env2("CAUTHY_CLIENT_SECRET");
|
|
591
|
+
const secret = given.secret ?? env2("CAUTHY_SECRET") ?? "";
|
|
592
|
+
const system = given.system ?? env2("CAUTHY_SYSTEM") ?? "";
|
|
173
593
|
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address, e.g. https://id.example.com");
|
|
174
594
|
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
175
595
|
if (secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
|
|
@@ -438,6 +858,7 @@ function createCauthy(given = {}) {
|
|
|
438
858
|
return {
|
|
439
859
|
handler,
|
|
440
860
|
webhook,
|
|
861
|
+
system: createSystem({ issuer, clientId, clientSecret, key: given.systemKey, fetch: given.fetch }),
|
|
441
862
|
getSession,
|
|
442
863
|
status,
|
|
443
864
|
resolve: resolve2,
|
|
@@ -449,226 +870,6 @@ function createCauthy(given = {}) {
|
|
|
449
870
|
};
|
|
450
871
|
}
|
|
451
872
|
|
|
452
|
-
// src/theme.ts
|
|
453
|
-
var MAX_RADIUS = 40;
|
|
454
|
-
function declarations(css) {
|
|
455
|
-
const text = css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
456
|
-
const out2 = [];
|
|
457
|
-
const within = [];
|
|
458
|
-
let chunk = "";
|
|
459
|
-
let quote = "";
|
|
460
|
-
let depth = 0;
|
|
461
|
-
const flush = () => {
|
|
462
|
-
const m = /^\s*(--[\w-]+)\s*:\s*([\s\S]+?)\s*$/.exec(chunk);
|
|
463
|
-
if (m) out2.push({ name: m[1], value: m[2].replace(/\s+/g, " ").replace(/\s*!important$/, ""), within: [...within] });
|
|
464
|
-
chunk = "";
|
|
465
|
-
};
|
|
466
|
-
for (const ch of text) {
|
|
467
|
-
if (quote) {
|
|
468
|
-
chunk += ch;
|
|
469
|
-
if (ch === quote) quote = "";
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
|
-
if (ch === '"' || ch === "'") {
|
|
473
|
-
quote = ch;
|
|
474
|
-
chunk += ch;
|
|
475
|
-
} else if (ch === "(") {
|
|
476
|
-
depth++;
|
|
477
|
-
chunk += ch;
|
|
478
|
-
} else if (ch === ")") {
|
|
479
|
-
depth = Math.max(0, depth - 1);
|
|
480
|
-
chunk += ch;
|
|
481
|
-
} else if (ch === "{" && depth === 0) {
|
|
482
|
-
within.push(chunk.trim().replace(/\s+/g, " "));
|
|
483
|
-
chunk = "";
|
|
484
|
-
} else if (ch === "}" && depth === 0) {
|
|
485
|
-
flush();
|
|
486
|
-
within.pop();
|
|
487
|
-
} else if (ch === ";" && depth === 0) {
|
|
488
|
-
flush();
|
|
489
|
-
} else {
|
|
490
|
-
chunk += ch;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return out2;
|
|
494
|
-
}
|
|
495
|
-
var DARK = /prefers-color-scheme:\s*dark|data-(mode|theme|color-scheme)=["']?dark|\.dark\b/i;
|
|
496
|
-
var LIGHT = /prefers-color-scheme:\s*light|data-(mode|theme|color-scheme)=["']?light|\.light\b/i;
|
|
497
|
-
var NAMED = /data-theme=["']?([\w-]+)/i;
|
|
498
|
-
function applies(d, o) {
|
|
499
|
-
for (const scope of d.within) {
|
|
500
|
-
if (o.mode === "light" && DARK.test(scope)) return false;
|
|
501
|
-
if (o.mode === "dark" && LIGHT.test(scope)) return false;
|
|
502
|
-
const named = NAMED.exec(scope);
|
|
503
|
-
if (named && !/^(dark|light)$/i.test(named[1]) && named[1] !== o.theme) return false;
|
|
504
|
-
if (!scope.startsWith("@") && !/:root|^html\b|^body\b|\[data-|^\*$|:host/.test(scope)) return false;
|
|
505
|
-
}
|
|
506
|
-
return true;
|
|
507
|
-
}
|
|
508
|
-
function resolve(value, vars, seen = 0) {
|
|
509
|
-
if (seen > 16) return null;
|
|
510
|
-
let unresolved = false;
|
|
511
|
-
let out2 = value;
|
|
512
|
-
for (let guard = 0; guard < 32 && /var\(/.test(out2); guard++) {
|
|
513
|
-
out2 = out2.replace(/var\(\s*(--[\w-]+)\s*(?:,\s*([^()]*(?:\([^()]*\)[^()]*)*))?\)/, (_m, name, fallback) => {
|
|
514
|
-
const known = vars.get(name);
|
|
515
|
-
const next = known !== void 0 ? resolve(known, vars, seen + 1) : fallback !== void 0 ? resolve(fallback.trim(), vars, seen + 1) : null;
|
|
516
|
-
if (next === null) unresolved = true;
|
|
517
|
-
return next ?? "";
|
|
518
|
-
});
|
|
519
|
-
if (unresolved) return null;
|
|
520
|
-
}
|
|
521
|
-
return /var\(/.test(out2) ? null : out2.trim();
|
|
522
|
-
}
|
|
523
|
-
var clamp01 = (n) => Math.min(1, Math.max(0, n));
|
|
524
|
-
var hex2 = (n) => Math.round(clamp01(n) * 255).toString(16).padStart(2, "0");
|
|
525
|
-
var gamma = (c) => c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
526
|
-
function out(r, g, b, a = 1) {
|
|
527
|
-
if (a >= 1) return `#${hex2(r)}${hex2(g)}${hex2(b)}`;
|
|
528
|
-
return `rgba(${Math.round(clamp01(r) * 255)}, ${Math.round(clamp01(g) * 255)}, ${Math.round(clamp01(b) * 255)}, ${Math.round(clamp01(a) * 1e3) / 1e3})`;
|
|
529
|
-
}
|
|
530
|
-
var alpha = (s) => s === void 0 ? 1 : s.endsWith("%") ? parseFloat(s) / 100 : parseFloat(s);
|
|
531
|
-
var KEYWORDS = { white: "#ffffff", black: "#000000" };
|
|
532
|
-
function toColor(value) {
|
|
533
|
-
const v = value.trim().toLowerCase();
|
|
534
|
-
if (KEYWORDS[v]) return KEYWORDS[v];
|
|
535
|
-
if (/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(v)) return v;
|
|
536
|
-
let m = /^rgba?\(\s*([\d.]+%?)[\s,]+([\d.]+%?)[\s,]+([\d.]+%?)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/.exec(v);
|
|
537
|
-
if (m) {
|
|
538
|
-
const ch = (s) => s.endsWith("%") ? parseFloat(s) / 100 : parseFloat(s) / 255;
|
|
539
|
-
return out(ch(m[1]), ch(m[2]), ch(m[3]), alpha(m[4]));
|
|
540
|
-
}
|
|
541
|
-
m = /^hsla?\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%[\s,]+([\d.]+)%(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/.exec(v);
|
|
542
|
-
if (m) {
|
|
543
|
-
const h = (parseFloat(m[1]) % 360 + 360) % 360;
|
|
544
|
-
const s = parseFloat(m[2]) / 100;
|
|
545
|
-
const l = parseFloat(m[3]) / 100;
|
|
546
|
-
const f = (n) => {
|
|
547
|
-
const k = (n + h / 30) % 12;
|
|
548
|
-
return l - s * Math.min(l, 1 - l) * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
549
|
-
};
|
|
550
|
-
return out(f(0), f(8), f(4), alpha(m[4]));
|
|
551
|
-
}
|
|
552
|
-
m = /^oklch\(\s*([\d.]+%?)\s+([\d.]+%?)\s+([\d.]+)(?:deg)?(?:\s*\/\s*([\d.]+%?))?\s*\)$/.exec(v);
|
|
553
|
-
if (m) {
|
|
554
|
-
const L = m[1].endsWith("%") ? parseFloat(m[1]) / 100 : parseFloat(m[1]);
|
|
555
|
-
const C = m[2].endsWith("%") ? parseFloat(m[2]) / 100 * 0.4 : parseFloat(m[2]);
|
|
556
|
-
const H = parseFloat(m[3]) * Math.PI / 180;
|
|
557
|
-
const a = C * Math.cos(H);
|
|
558
|
-
const b = C * Math.sin(H);
|
|
559
|
-
const l = Math.pow(L + 0.3963377774 * a + 0.2158037573 * b, 3);
|
|
560
|
-
const mm = Math.pow(L - 0.1055613458 * a - 0.0638541728 * b, 3);
|
|
561
|
-
const s = Math.pow(L - 0.0894841775 * a - 1.291485548 * b, 3);
|
|
562
|
-
return out(
|
|
563
|
-
gamma(4.0767416621 * l - 3.3077115913 * mm + 0.2309699292 * s),
|
|
564
|
-
gamma(-1.2684380046 * l + 2.6097574011 * mm - 0.3413193965 * s),
|
|
565
|
-
gamma(-0.0041960863 * l - 0.7034186147 * mm + 1.707614701 * s),
|
|
566
|
-
alpha(m[4])
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
return null;
|
|
570
|
-
}
|
|
571
|
-
function toRadius(value) {
|
|
572
|
-
const m = /^([\d.]+)(px|rem|em)?$/.exec(value.trim());
|
|
573
|
-
if (!m) return null;
|
|
574
|
-
const px = parseFloat(m[1]) * (m[2] === "rem" || m[2] === "em" ? 16 : 1);
|
|
575
|
-
return Number.isFinite(px) ? Math.min(MAX_RADIUS, Math.max(0, Math.round(px))) : null;
|
|
576
|
-
}
|
|
577
|
-
function toFont(value) {
|
|
578
|
-
const v = value.trim();
|
|
579
|
-
return /^[A-Za-z0-9 ,'"_-]{1,200}$/.test(v) && (v.match(/"/g)?.length ?? 0) % 2 === 0 && (v.match(/'/g)?.length ?? 0) % 2 === 0 ? v : null;
|
|
580
|
-
}
|
|
581
|
-
var COLORS = [
|
|
582
|
-
["background", ["--sm-bg-surface", "--sm-surface"]],
|
|
583
|
-
["surface", ["--sm-bg-surface-raised", "--sm-surface-raised"]],
|
|
584
|
-
["text", ["--sm-text-primary", "--sm-surface-text"]],
|
|
585
|
-
["textMuted", ["--sm-text-secondary"]],
|
|
586
|
-
["border", ["--sm-border-default", "--sm-surface-border"]],
|
|
587
|
-
["primary", ["--sm-primary", "--sm-bg-interactive"]],
|
|
588
|
-
["primaryText", ["--sm-primary-text", "--sm-text-inverse"]],
|
|
589
|
-
["accent", ["--sm-text-brand", "--sm-border-focus", "--sm-accent"]],
|
|
590
|
-
["error", ["--sm-error-soft-text", "--sm-error"]],
|
|
591
|
-
["errorBackground", ["--sm-error-soft"]],
|
|
592
|
-
["success", ["--sm-success-soft-text", "--sm-success"]],
|
|
593
|
-
["successBackground", ["--sm-success-soft"]]
|
|
594
|
-
];
|
|
595
|
-
function themeFromStackmyth(css, options = {}) {
|
|
596
|
-
const o = { mode: options.mode ?? "light", theme: options.theme };
|
|
597
|
-
const vars = /* @__PURE__ */ new Map();
|
|
598
|
-
for (const sheet of Array.isArray(css) ? css : [css]) {
|
|
599
|
-
for (const d of declarations(sheet)) if (applies(d, o)) vars.set(d.name, d.value);
|
|
600
|
-
}
|
|
601
|
-
const theme = {};
|
|
602
|
-
const warnings = [];
|
|
603
|
-
const read = (tokens, to, what) => {
|
|
604
|
-
for (const token of tokens) {
|
|
605
|
-
const raw = vars.get(token);
|
|
606
|
-
if (raw === void 0) continue;
|
|
607
|
-
const value = resolve(raw, vars);
|
|
608
|
-
const got = value === null ? null : to(value);
|
|
609
|
-
if (got !== null) return got;
|
|
610
|
-
warnings.push(`${token}: "${raw}" is not ${what} Cauthy can take`);
|
|
611
|
-
}
|
|
612
|
-
return void 0;
|
|
613
|
-
};
|
|
614
|
-
for (const [field, tokens] of COLORS) {
|
|
615
|
-
const c = read(tokens, toColor, "a colour");
|
|
616
|
-
if (c !== void 0) theme[field] = c;
|
|
617
|
-
}
|
|
618
|
-
const radius = read(["--sm-radius-container", "--sm-radius-md"], toRadius, "a length");
|
|
619
|
-
if (radius !== void 0) theme.radius = radius;
|
|
620
|
-
const button = read(["--sm-radius-interactive", "--sm-radius-sm"], toRadius, "a length");
|
|
621
|
-
if (button !== void 0) theme.buttonRadius = button;
|
|
622
|
-
const font = read(["--sm-font-family"], toFont, "a font list");
|
|
623
|
-
if (font !== void 0) theme.fontFamily = font;
|
|
624
|
-
if (options.logoUrl) theme.logoUrl = options.logoUrl;
|
|
625
|
-
if (vars.size === 0) warnings.push("no custom properties were found: is this the stylesheet that declares the --sm-* tokens?");
|
|
626
|
-
else if (![...vars.keys()].some((k) => k.startsWith("--sm-"))) warnings.push("no --sm-* token was found in these stylesheets");
|
|
627
|
-
return { theme, warnings };
|
|
628
|
-
}
|
|
629
|
-
function toWire(theme) {
|
|
630
|
-
const names = {
|
|
631
|
-
background: "background",
|
|
632
|
-
surface: "surface",
|
|
633
|
-
text: "text",
|
|
634
|
-
textMuted: "text_muted",
|
|
635
|
-
border: "border",
|
|
636
|
-
primary: "primary",
|
|
637
|
-
primaryText: "primary_text",
|
|
638
|
-
accent: "accent",
|
|
639
|
-
error: "error",
|
|
640
|
-
errorBackground: "error_background",
|
|
641
|
-
success: "success",
|
|
642
|
-
successBackground: "success_background",
|
|
643
|
-
radius: "radius",
|
|
644
|
-
buttonRadius: "button_radius",
|
|
645
|
-
fontFamily: "font_family",
|
|
646
|
-
logoUrl: "logo_url"
|
|
647
|
-
};
|
|
648
|
-
const wire = {};
|
|
649
|
-
for (const [k, v] of Object.entries(theme)) if (v !== void 0 && v !== "") wire[names[k]] = v;
|
|
650
|
-
return wire;
|
|
651
|
-
}
|
|
652
|
-
async function pushTheme(o) {
|
|
653
|
-
const env2 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
|
|
654
|
-
const issuer = (o.issuer ?? env2("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
655
|
-
const clientId = o.clientId ?? env2("CAUTHY_CLIENT_ID") ?? "";
|
|
656
|
-
const tenant = o.tenant ?? env2("CAUTHY_TENANT") ?? "";
|
|
657
|
-
const token = o.token ?? env2("CAUTHY_TOKEN") ?? "";
|
|
658
|
-
for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_CLIENT_ID: clientId, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
|
|
659
|
-
if (v === "") throw new Error(`cauthy-auth: ${name} is required to push a theme`);
|
|
660
|
-
}
|
|
661
|
-
const res = await (o.fetch ?? fetch)(`${issuer}/v1/oauth/clients/${encodeURIComponent(clientId)}/theme`, {
|
|
662
|
-
method: "PUT",
|
|
663
|
-
headers: { authorization: `Bearer ${token}`, "x-tenant-key": tenant, "content-type": "application/json" },
|
|
664
|
-
body: JSON.stringify(toWire(o.theme))
|
|
665
|
-
});
|
|
666
|
-
if (!res.ok) {
|
|
667
|
-
const body = await res.json().catch(() => null);
|
|
668
|
-
throw new Error(`cauthy-auth: Cauthy refused the theme (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
873
|
// src/permissions.ts
|
|
673
874
|
function matches(entry, resource, action) {
|
|
674
875
|
const i = entry.lastIndexOf(":");
|
|
@@ -683,11 +884,11 @@ function can(organization, resource, action) {
|
|
|
683
884
|
return organization.permissions.some((p) => matches(p, resource, action));
|
|
684
885
|
}
|
|
685
886
|
async function pushPolicy(o) {
|
|
686
|
-
const
|
|
687
|
-
const issuer = (o.issuer ??
|
|
688
|
-
const system = o.system ??
|
|
689
|
-
const tenant = o.tenant ??
|
|
690
|
-
const token = o.token ??
|
|
887
|
+
const env3 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
|
|
888
|
+
const issuer = (o.issuer ?? env3("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
889
|
+
const system = o.system ?? env3("CAUTHY_SYSTEM") ?? "";
|
|
890
|
+
const tenant = o.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
891
|
+
const token = o.token ?? env3("CAUTHY_TOKEN") ?? "";
|
|
691
892
|
for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
|
|
692
893
|
if (v === "") throw new Error(`cauthy-auth: ${name} is required to push a policy`);
|
|
693
894
|
}
|
|
@@ -701,11 +902,58 @@ async function pushPolicy(o) {
|
|
|
701
902
|
throw new Error(`cauthy-auth: Cauthy refused the policy (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`);
|
|
702
903
|
}
|
|
703
904
|
}
|
|
905
|
+
|
|
906
|
+
// src/connect.ts
|
|
907
|
+
function sessionSecret() {
|
|
908
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
909
|
+
let s = "";
|
|
910
|
+
for (const b of bytes) s += String.fromCharCode(b);
|
|
911
|
+
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
912
|
+
}
|
|
913
|
+
async function connectSystem(o) {
|
|
914
|
+
const env3 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
|
|
915
|
+
const issuer = (o.issuer ?? env3("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
916
|
+
const tenant = o.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
917
|
+
const token = o.token ?? env3("CAUTHY_TOKEN") ?? "";
|
|
918
|
+
for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
|
|
919
|
+
if (v === "") throw new Error(`cauthy-auth: ${name} is required to connect a system`);
|
|
920
|
+
}
|
|
921
|
+
const res = await (o.fetch ?? fetch)(`${issuer}/v1/connect`, {
|
|
922
|
+
method: "POST",
|
|
923
|
+
headers: { authorization: `Bearer ${token}`, "x-tenant-key": tenant, "content-type": "application/json" },
|
|
924
|
+
body: JSON.stringify({
|
|
925
|
+
system: o.system,
|
|
926
|
+
app_url: o.appUrl,
|
|
927
|
+
name: o.name,
|
|
928
|
+
callback_path: o.callbackPath,
|
|
929
|
+
events_url: o.eventsUrl,
|
|
930
|
+
events: o.events,
|
|
931
|
+
jwks_uri: o.jwksUri
|
|
932
|
+
})
|
|
933
|
+
});
|
|
934
|
+
const body = await res.json().catch(() => null);
|
|
935
|
+
if (!res.ok || !body?.data) {
|
|
936
|
+
const why = (body?.errors ?? []).map((e) => e.message).filter(Boolean).join("; ") || res.statusText;
|
|
937
|
+
throw new Error(`cauthy-auth: Cauthy did not connect the system (${res.status}): ${why}`);
|
|
938
|
+
}
|
|
939
|
+
const d = body.data;
|
|
940
|
+
return {
|
|
941
|
+
system: d.system,
|
|
942
|
+
clientId: d.client.client_id,
|
|
943
|
+
clientSecret: d.client_secret,
|
|
944
|
+
redirectUris: d.client.redirect_uris,
|
|
945
|
+
webhook: d.webhook,
|
|
946
|
+
env: { ...d.env, CAUTHY_SECRET: sessionSecret() }
|
|
947
|
+
};
|
|
948
|
+
}
|
|
704
949
|
export {
|
|
950
|
+
CauthyApiError,
|
|
705
951
|
CauthyConfigError,
|
|
706
952
|
CauthyWebhookError,
|
|
707
953
|
can,
|
|
954
|
+
connectSystem,
|
|
708
955
|
createCauthy,
|
|
956
|
+
createSystem,
|
|
709
957
|
pushPolicy,
|
|
710
958
|
pushTheme,
|
|
711
959
|
themeFromStackmyth,
|