cauthy-auth 0.0.0-stage → 0.2.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/dist/cli.js ADDED
@@ -0,0 +1,380 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/cli.ts
5
+ var import_node_fs = require("fs");
6
+
7
+ // src/permissions.ts
8
+ async function pushPolicy(o) {
9
+ const env = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
10
+ const issuer = (o.issuer ?? env("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
11
+ const system = o.system ?? env("CAUTHY_SYSTEM") ?? "";
12
+ const tenant = o.tenant ?? env("CAUTHY_TENANT") ?? "";
13
+ const token = o.token ?? env("CAUTHY_TOKEN") ?? "";
14
+ for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
15
+ if (v === "") throw new Error(`cauthy-auth: ${name} is required to push a policy`);
16
+ }
17
+ const res = await (o.fetch ?? fetch)(`${issuer}/v1/systems/${encodeURIComponent(system)}/policy`, {
18
+ method: "PUT",
19
+ headers: { authorization: `Bearer ${token}`, "x-tenant-key": tenant, "content-type": "application/json" },
20
+ body: JSON.stringify({ roles: o.roles })
21
+ });
22
+ if (!res.ok) {
23
+ const body = await res.json().catch(() => null);
24
+ throw new Error(`cauthy-auth: Cauthy refused the policy (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`);
25
+ }
26
+ }
27
+
28
+ // src/theme.ts
29
+ var MAX_RADIUS = 40;
30
+ function declarations(css) {
31
+ const text = css.replace(/\/\*[\s\S]*?\*\//g, "");
32
+ const out2 = [];
33
+ const within = [];
34
+ let chunk = "";
35
+ let quote = "";
36
+ let depth = 0;
37
+ const flush = () => {
38
+ const m = /^\s*(--[\w-]+)\s*:\s*([\s\S]+?)\s*$/.exec(chunk);
39
+ if (m) out2.push({ name: m[1], value: m[2].replace(/\s+/g, " ").replace(/\s*!important$/, ""), within: [...within] });
40
+ chunk = "";
41
+ };
42
+ for (const ch of text) {
43
+ if (quote) {
44
+ chunk += ch;
45
+ if (ch === quote) quote = "";
46
+ continue;
47
+ }
48
+ if (ch === '"' || ch === "'") {
49
+ quote = ch;
50
+ chunk += ch;
51
+ } else if (ch === "(") {
52
+ depth++;
53
+ chunk += ch;
54
+ } else if (ch === ")") {
55
+ depth = Math.max(0, depth - 1);
56
+ chunk += ch;
57
+ } else if (ch === "{" && depth === 0) {
58
+ within.push(chunk.trim().replace(/\s+/g, " "));
59
+ chunk = "";
60
+ } else if (ch === "}" && depth === 0) {
61
+ flush();
62
+ within.pop();
63
+ } else if (ch === ";" && depth === 0) {
64
+ flush();
65
+ } else {
66
+ chunk += ch;
67
+ }
68
+ }
69
+ return out2;
70
+ }
71
+ var DARK = /prefers-color-scheme:\s*dark|data-(mode|theme|color-scheme)=["']?dark|\.dark\b/i;
72
+ var LIGHT = /prefers-color-scheme:\s*light|data-(mode|theme|color-scheme)=["']?light|\.light\b/i;
73
+ var NAMED = /data-theme=["']?([\w-]+)/i;
74
+ function applies(d, o) {
75
+ for (const scope of d.within) {
76
+ if (o.mode === "light" && DARK.test(scope)) return false;
77
+ if (o.mode === "dark" && LIGHT.test(scope)) return false;
78
+ const named = NAMED.exec(scope);
79
+ if (named && !/^(dark|light)$/i.test(named[1]) && named[1] !== o.theme) return false;
80
+ if (!scope.startsWith("@") && !/:root|^html\b|^body\b|\[data-|^\*$|:host/.test(scope)) return false;
81
+ }
82
+ return true;
83
+ }
84
+ function resolve(value, vars, seen = 0) {
85
+ if (seen > 16) return null;
86
+ let unresolved = false;
87
+ let out2 = value;
88
+ for (let guard = 0; guard < 32 && /var\(/.test(out2); guard++) {
89
+ out2 = out2.replace(/var\(\s*(--[\w-]+)\s*(?:,\s*([^()]*(?:\([^()]*\)[^()]*)*))?\)/, (_m, name, fallback) => {
90
+ const known = vars.get(name);
91
+ const next = known !== void 0 ? resolve(known, vars, seen + 1) : fallback !== void 0 ? resolve(fallback.trim(), vars, seen + 1) : null;
92
+ if (next === null) unresolved = true;
93
+ return next ?? "";
94
+ });
95
+ if (unresolved) return null;
96
+ }
97
+ return /var\(/.test(out2) ? null : out2.trim();
98
+ }
99
+ var clamp01 = (n) => Math.min(1, Math.max(0, n));
100
+ var hex2 = (n) => Math.round(clamp01(n) * 255).toString(16).padStart(2, "0");
101
+ var gamma = (c) => c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
102
+ function out(r, g, b, a = 1) {
103
+ if (a >= 1) return `#${hex2(r)}${hex2(g)}${hex2(b)}`;
104
+ return `rgba(${Math.round(clamp01(r) * 255)}, ${Math.round(clamp01(g) * 255)}, ${Math.round(clamp01(b) * 255)}, ${Math.round(clamp01(a) * 1e3) / 1e3})`;
105
+ }
106
+ var alpha = (s) => s === void 0 ? 1 : s.endsWith("%") ? parseFloat(s) / 100 : parseFloat(s);
107
+ var KEYWORDS = { white: "#ffffff", black: "#000000" };
108
+ function toColor(value) {
109
+ const v = value.trim().toLowerCase();
110
+ if (KEYWORDS[v]) return KEYWORDS[v];
111
+ if (/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(v)) return v;
112
+ let m = /^rgba?\(\s*([\d.]+%?)[\s,]+([\d.]+%?)[\s,]+([\d.]+%?)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/.exec(v);
113
+ if (m) {
114
+ const ch = (s) => s.endsWith("%") ? parseFloat(s) / 100 : parseFloat(s) / 255;
115
+ return out(ch(m[1]), ch(m[2]), ch(m[3]), alpha(m[4]));
116
+ }
117
+ m = /^hsla?\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%[\s,]+([\d.]+)%(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/.exec(v);
118
+ if (m) {
119
+ const h = (parseFloat(m[1]) % 360 + 360) % 360;
120
+ const s = parseFloat(m[2]) / 100;
121
+ const l = parseFloat(m[3]) / 100;
122
+ const f = (n) => {
123
+ const k = (n + h / 30) % 12;
124
+ return l - s * Math.min(l, 1 - l) * Math.max(-1, Math.min(k - 3, 9 - k, 1));
125
+ };
126
+ return out(f(0), f(8), f(4), alpha(m[4]));
127
+ }
128
+ m = /^oklch\(\s*([\d.]+%?)\s+([\d.]+%?)\s+([\d.]+)(?:deg)?(?:\s*\/\s*([\d.]+%?))?\s*\)$/.exec(v);
129
+ if (m) {
130
+ const L = m[1].endsWith("%") ? parseFloat(m[1]) / 100 : parseFloat(m[1]);
131
+ const C = m[2].endsWith("%") ? parseFloat(m[2]) / 100 * 0.4 : parseFloat(m[2]);
132
+ const H = parseFloat(m[3]) * Math.PI / 180;
133
+ const a = C * Math.cos(H);
134
+ const b = C * Math.sin(H);
135
+ const l = Math.pow(L + 0.3963377774 * a + 0.2158037573 * b, 3);
136
+ const mm = Math.pow(L - 0.1055613458 * a - 0.0638541728 * b, 3);
137
+ const s = Math.pow(L - 0.0894841775 * a - 1.291485548 * b, 3);
138
+ return out(
139
+ gamma(4.0767416621 * l - 3.3077115913 * mm + 0.2309699292 * s),
140
+ gamma(-1.2684380046 * l + 2.6097574011 * mm - 0.3413193965 * s),
141
+ gamma(-0.0041960863 * l - 0.7034186147 * mm + 1.707614701 * s),
142
+ alpha(m[4])
143
+ );
144
+ }
145
+ return null;
146
+ }
147
+ function toRadius(value) {
148
+ const m = /^([\d.]+)(px|rem|em)?$/.exec(value.trim());
149
+ if (!m) return null;
150
+ const px = parseFloat(m[1]) * (m[2] === "rem" || m[2] === "em" ? 16 : 1);
151
+ return Number.isFinite(px) ? Math.min(MAX_RADIUS, Math.max(0, Math.round(px))) : null;
152
+ }
153
+ function toFont(value) {
154
+ const v = value.trim();
155
+ 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;
156
+ }
157
+ var COLORS = [
158
+ ["background", ["--sm-bg-surface", "--sm-surface"]],
159
+ ["surface", ["--sm-bg-surface-raised", "--sm-surface-raised"]],
160
+ ["text", ["--sm-text-primary", "--sm-surface-text"]],
161
+ ["textMuted", ["--sm-text-secondary"]],
162
+ ["border", ["--sm-border-default", "--sm-surface-border"]],
163
+ ["primary", ["--sm-primary", "--sm-bg-interactive"]],
164
+ ["primaryText", ["--sm-primary-text", "--sm-text-inverse"]],
165
+ ["accent", ["--sm-text-brand", "--sm-border-focus", "--sm-accent"]],
166
+ ["error", ["--sm-error-soft-text", "--sm-error"]],
167
+ ["errorBackground", ["--sm-error-soft"]],
168
+ ["success", ["--sm-success-soft-text", "--sm-success"]],
169
+ ["successBackground", ["--sm-success-soft"]]
170
+ ];
171
+ function themeFromStackmyth(css, options = {}) {
172
+ const o = { mode: options.mode ?? "light", theme: options.theme };
173
+ const vars = /* @__PURE__ */ new Map();
174
+ for (const sheet of Array.isArray(css) ? css : [css]) {
175
+ for (const d of declarations(sheet)) if (applies(d, o)) vars.set(d.name, d.value);
176
+ }
177
+ const theme = {};
178
+ const warnings = [];
179
+ const read = (tokens, to, what) => {
180
+ for (const token of tokens) {
181
+ const raw = vars.get(token);
182
+ if (raw === void 0) continue;
183
+ const value = resolve(raw, vars);
184
+ const got = value === null ? null : to(value);
185
+ if (got !== null) return got;
186
+ warnings.push(`${token}: "${raw}" is not ${what} Cauthy can take`);
187
+ }
188
+ return void 0;
189
+ };
190
+ for (const [field, tokens] of COLORS) {
191
+ const c = read(tokens, toColor, "a colour");
192
+ if (c !== void 0) theme[field] = c;
193
+ }
194
+ const radius = read(["--sm-radius-container", "--sm-radius-md"], toRadius, "a length");
195
+ if (radius !== void 0) theme.radius = radius;
196
+ const button = read(["--sm-radius-interactive", "--sm-radius-sm"], toRadius, "a length");
197
+ if (button !== void 0) theme.buttonRadius = button;
198
+ const font = read(["--sm-font-family"], toFont, "a font list");
199
+ if (font !== void 0) theme.fontFamily = font;
200
+ if (options.logoUrl) theme.logoUrl = options.logoUrl;
201
+ if (vars.size === 0) warnings.push("no custom properties were found: is this the stylesheet that declares the --sm-* tokens?");
202
+ else if (![...vars.keys()].some((k) => k.startsWith("--sm-"))) warnings.push("no --sm-* token was found in these stylesheets");
203
+ return { theme, warnings };
204
+ }
205
+ function toWire(theme) {
206
+ const names = {
207
+ background: "background",
208
+ surface: "surface",
209
+ text: "text",
210
+ textMuted: "text_muted",
211
+ border: "border",
212
+ primary: "primary",
213
+ primaryText: "primary_text",
214
+ accent: "accent",
215
+ error: "error",
216
+ errorBackground: "error_background",
217
+ success: "success",
218
+ successBackground: "success_background",
219
+ radius: "radius",
220
+ buttonRadius: "button_radius",
221
+ fontFamily: "font_family",
222
+ logoUrl: "logo_url"
223
+ };
224
+ const wire = {};
225
+ for (const [k, v] of Object.entries(theme)) if (v !== void 0 && v !== "") wire[names[k]] = v;
226
+ return wire;
227
+ }
228
+ async function pushTheme(o) {
229
+ const env = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
230
+ const issuer = (o.issuer ?? env("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
231
+ const clientId = o.clientId ?? env("CAUTHY_CLIENT_ID") ?? "";
232
+ const tenant = o.tenant ?? env("CAUTHY_TENANT") ?? "";
233
+ const token = o.token ?? env("CAUTHY_TOKEN") ?? "";
234
+ for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_CLIENT_ID: clientId, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
235
+ if (v === "") throw new Error(`cauthy-auth: ${name} is required to push a theme`);
236
+ }
237
+ const res = await (o.fetch ?? fetch)(`${issuer}/v1/oauth/clients/${encodeURIComponent(clientId)}/theme`, {
238
+ method: "PUT",
239
+ headers: { authorization: `Bearer ${token}`, "x-tenant-key": tenant, "content-type": "application/json" },
240
+ body: JSON.stringify(toWire(o.theme))
241
+ });
242
+ if (!res.ok) {
243
+ const body = await res.json().catch(() => null);
244
+ throw new Error(`cauthy-auth: Cauthy refused the theme (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`);
245
+ }
246
+ }
247
+
248
+ // src/cli.ts
249
+ var POLICY_HELP = `cauthy-auth policy --file <roles.json> [--system <name>] [--push]
250
+
251
+ Loads a system's catalog of roles into Cauthy from a file:
252
+
253
+ { "roles": [ { "role": "accountant", "name": "Accountant", "inherits": ["reader"],
254
+ "permissions": [ { "resource": "ledger", "action": "write" },
255
+ { "kind": "business", "resource": "books.close", "action": "write" } ] } ] }
256
+
257
+ --file <file> The catalog, as JSON.
258
+ --system <name> The system it is of. Default: CAUTHY_SYSTEM.
259
+ --push Send it to Cauthy, REPLACING the catalog it has. Without it
260
+ the catalog is only checked and shown.
261
+
262
+ Use it to load a first catalog. Afterwards the catalog is Cauthy's, edited in
263
+ its dashboard: pushing again overwrites what was edited there.
264
+
265
+ To push: CAUTHY_ISSUER, CAUTHY_TENANT (the workspace's slug) and CAUTHY_TOKEN
266
+ (a personal access token with organizations:manage).
267
+ `;
268
+ async function policy(argv) {
269
+ if (argv.includes("--help") || argv.includes("-h")) {
270
+ process.stdout.write(POLICY_HELP);
271
+ return;
272
+ }
273
+ let file;
274
+ let system;
275
+ let push = false;
276
+ for (let i = 0; i < argv.length; i++) {
277
+ const arg = argv[i];
278
+ const value = () => argv[++i] ?? fail(`${arg} needs a value`);
279
+ if (arg === "--file") file = value();
280
+ else if (arg === "--system") system = value();
281
+ else if (arg === "--push") push = true;
282
+ else fail(`unknown option ${arg}
283
+
284
+ ${POLICY_HELP}`);
285
+ }
286
+ if (!file) fail("give the catalog with --file\n\n" + POLICY_HELP);
287
+ let roles;
288
+ try {
289
+ const parsed = JSON.parse((0, import_node_fs.readFileSync)(file, "utf8"));
290
+ const list = Array.isArray(parsed) ? parsed : parsed.roles;
291
+ if (!Array.isArray(list)) throw new Error('no "roles" list');
292
+ roles = list;
293
+ } catch (err) {
294
+ return fail(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
295
+ }
296
+ const permissions = roles.reduce((n, r) => n + (r.permissions?.length ?? 0), 0);
297
+ process.stdout.write(`${roles.length} roles, ${permissions} permissions: ${roles.map((r) => r.role).join(", ")}
298
+ `);
299
+ if (push) {
300
+ await pushPolicy({ system, roles });
301
+ process.stdout.write("Sent to Cauthy: this is now the catalog of the system.\n");
302
+ } else {
303
+ process.stdout.write("Not sent. Add --push to give it to Cauthy.\n");
304
+ }
305
+ }
306
+ var HELP = `cauthy-auth theme [options]
307
+
308
+ Reads the application's Stackmyth theme and gives it to Cauthy's hosted pages.
309
+
310
+ --css <file> A stylesheet that declares --sm-* tokens. Repeat it, in the
311
+ order the application loads them: Stackmyth's own first,
312
+ the application's brand file after.
313
+ --theme <name> The named theme to read, for a sheet with [data-theme=\u2026].
314
+ --mode <mode> light (default) or dark.
315
+ --logo <url> An https image shown where "Cauthy" is.
316
+ --push Send the theme to Cauthy. Without it the theme is only shown.
317
+ --clear Take the application back to Cauthy's own look.
318
+
319
+ To push: CAUTHY_ISSUER, CAUTHY_CLIENT_ID, CAUTHY_TENANT (the workspace's slug)
320
+ and CAUTHY_TOKEN (a personal access token with oauth_clients:manage).
321
+ `;
322
+ function fail(message) {
323
+ process.stderr.write(`cauthy-auth: ${message}
324
+ `);
325
+ process.exit(1);
326
+ }
327
+ async function main(argv) {
328
+ if (argv[0] === "policy") return policy(argv.slice(1));
329
+ if (argv[0] !== "theme" || argv.includes("--help") || argv.includes("-h")) {
330
+ process.stdout.write(HELP);
331
+ process.exit(argv[0] === "theme" || argv.length === 0 || argv.includes("--help") || argv.includes("-h") ? 0 : 1);
332
+ }
333
+ const files = [];
334
+ let theme;
335
+ let mode = "light";
336
+ let logoUrl;
337
+ let push = false;
338
+ let clear = false;
339
+ for (let i = 1; i < argv.length; i++) {
340
+ const arg = argv[i];
341
+ const value = () => argv[++i] ?? fail(`${arg} needs a value`);
342
+ if (arg === "--css") files.push(value());
343
+ else if (arg === "--theme") theme = value();
344
+ else if (arg === "--mode") {
345
+ const m = value();
346
+ if (m !== "light" && m !== "dark") fail("--mode is light or dark");
347
+ mode = m;
348
+ } else if (arg === "--logo") logoUrl = value();
349
+ else if (arg === "--push") push = true;
350
+ else if (arg === "--clear") clear = true;
351
+ else fail(`unknown option ${arg}
352
+
353
+ ${HELP}`);
354
+ }
355
+ if (clear) {
356
+ await pushTheme({ theme: {} });
357
+ process.stdout.write("The application is back to Cauthy's own look.\n");
358
+ return;
359
+ }
360
+ if (files.length === 0) fail("give at least one --css file\n\n" + HELP);
361
+ const sheets = files.map((f) => {
362
+ try {
363
+ return (0, import_node_fs.readFileSync)(f, "utf8");
364
+ } catch {
365
+ return fail(`cannot read ${f}`);
366
+ }
367
+ });
368
+ const imported = themeFromStackmyth(sheets, { theme, mode, logoUrl });
369
+ for (const w of imported.warnings) process.stderr.write(`warning: ${w}
370
+ `);
371
+ if (Object.keys(imported.theme).length === 0) fail("no theme could be read from these stylesheets");
372
+ process.stdout.write(JSON.stringify(toWire(imported.theme), null, 2) + "\n");
373
+ if (push) {
374
+ await pushTheme({ theme: imported.theme });
375
+ process.stdout.write("Sent to Cauthy: its pages now take this look for the application.\n");
376
+ } else {
377
+ process.stdout.write("Not sent. Add --push to give it to Cauthy.\n");
378
+ }
379
+ }
380
+ main(process.argv.slice(2)).catch((err) => fail(err instanceof Error ? err.message.replace(/^@cauthy\/auth: /, "") : String(err)));
@@ -0,0 +1,22 @@
1
+ import { FastifyReply, FastifyPluginAsync } from 'fastify';
2
+ import { C as Cauthy } from './cauthy-BrIMaoj1.mjs';
3
+ import { S as Session } from './types-CYmUVWzY.mjs';
4
+ import 'jose';
5
+
6
+ declare module 'fastify' {
7
+ interface FastifyRequest {
8
+ /** Who is signed in, or null. Asked once per request. */
9
+ cauthy(): Promise<Session | null>;
10
+ }
11
+ interface FastifyInstance {
12
+ /** A preHandler that answers 401 unless somebody is signed in. */
13
+ requireSession(request: FastifyRequest, reply: FastifyReply): Promise<void>;
14
+ }
15
+ }
16
+ interface CauthyFastifyOptions {
17
+ auth: Cauthy;
18
+ }
19
+ declare const plugin: FastifyPluginAsync<CauthyFastifyOptions>;
20
+ declare const cauthyFastify: FastifyPluginAsync<CauthyFastifyOptions>;
21
+
22
+ export { type CauthyFastifyOptions, cauthyFastify, plugin as default };
@@ -0,0 +1,22 @@
1
+ import { FastifyReply, FastifyPluginAsync } from 'fastify';
2
+ import { C as Cauthy } from './cauthy-CCZc9w8r.js';
3
+ import { S as Session } from './types-CYmUVWzY.js';
4
+ import 'jose';
5
+
6
+ declare module 'fastify' {
7
+ interface FastifyRequest {
8
+ /** Who is signed in, or null. Asked once per request. */
9
+ cauthy(): Promise<Session | null>;
10
+ }
11
+ interface FastifyInstance {
12
+ /** A preHandler that answers 401 unless somebody is signed in. */
13
+ requireSession(request: FastifyRequest, reply: FastifyReply): Promise<void>;
14
+ }
15
+ }
16
+ interface CauthyFastifyOptions {
17
+ auth: Cauthy;
18
+ }
19
+ declare const plugin: FastifyPluginAsync<CauthyFastifyOptions>;
20
+ declare const cauthyFastify: FastifyPluginAsync<CauthyFastifyOptions>;
21
+
22
+ export { type CauthyFastifyOptions, cauthyFastify, plugin as default };
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/fastify.ts
21
+ var fastify_exports = {};
22
+ __export(fastify_exports, {
23
+ cauthyFastify: () => cauthyFastify,
24
+ default: () => fastify_default
25
+ });
26
+ module.exports = __toCommonJS(fastify_exports);
27
+ function originOf(req) {
28
+ return `${req.protocol}://${req.headers.host ?? "localhost"}`;
29
+ }
30
+ function toWebRequest(req) {
31
+ const headers = new Headers();
32
+ for (const [k, v] of Object.entries(req.headers)) {
33
+ if (Array.isArray(v)) for (const one of v) headers.append(k, one);
34
+ else if (v !== void 0) headers.set(k, v);
35
+ }
36
+ return new Request(new URL(req.url, originOf(req)), { method: req.method, headers });
37
+ }
38
+ var kSession = /* @__PURE__ */ Symbol("cauthy.session");
39
+ var kReply = /* @__PURE__ */ Symbol("cauthy.reply");
40
+ var plugin = async (app, { auth }) => {
41
+ const base = auth.options.basePath;
42
+ app.decorateRequest("cauthy", async function() {
43
+ const self = this;
44
+ self[kSession] ??= (async () => {
45
+ const resolved = await auth.resolve(toWebRequest(this));
46
+ const reply = this[kReply];
47
+ if (resolved.cookies.length > 0 && reply) reply.header("set-cookie", resolved.cookies);
48
+ return resolved.session;
49
+ })();
50
+ return self[kSession];
51
+ });
52
+ app.addHook("onRequest", async (req, reply) => {
53
+ ;
54
+ req[kReply] = reply;
55
+ });
56
+ app.decorate("requireSession", async function(request, reply) {
57
+ if (await request.cauthy() === null) {
58
+ await reply.code(401).header("x-cauthy-session", "none").send({ error: "unauthenticated" });
59
+ }
60
+ });
61
+ const serve = async (req, reply) => {
62
+ const res = await auth.handler(toWebRequest(req));
63
+ reply.code(res.status);
64
+ res.headers.forEach((value, name) => {
65
+ if (name !== "set-cookie") reply.header(name, value);
66
+ });
67
+ const cookies = res.headers.getSetCookie();
68
+ if (cookies.length > 0) reply.header("set-cookie", cookies);
69
+ return reply.send(res.body === null ? null : Buffer.from(await res.arrayBuffer()));
70
+ };
71
+ app.route({ method: ["GET", "POST"], url: `${base}/*`, handler: serve });
72
+ };
73
+ plugin[/* @__PURE__ */ Symbol.for("skip-override")] = true;
74
+ plugin[/* @__PURE__ */ Symbol.for("fastify.display-name")] = "cauthy-auth";
75
+ var cauthyFastify = plugin;
76
+ var fastify_default = plugin;
77
+ // Annotate the CommonJS export names for ESM import in node:
78
+ 0 && (module.exports = {
79
+ cauthyFastify
80
+ });
81
+ //# sourceMappingURL=fastify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fastify.ts"],"sourcesContent":["// Fastify: the routes, and who is signed in on every request.\n//\n// await app.register(cauthyFastify, { auth })\n//\n// app.get('/api/me', async (req, reply) => {\n// const session = await req.cauthy()\n// if (!session) return reply.code(401).send({ error: 'unauthenticated' })\n// return session.user\n// })\n//\n// app.get('/api/books', { preHandler: app.requireSession }, async (req) => { … })\n//\n// Here a session that needs renewing is renewed on the spot: Fastify has the\n// reply in hand, so the new cookie always goes back.\n\nimport type { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify'\nimport type { Cauthy } from './cauthy'\nimport type { Session } from './types'\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n /** Who is signed in, or null. Asked once per request. */\n cauthy(): Promise<Session | null>\n }\n interface FastifyInstance {\n /** A preHandler that answers 401 unless somebody is signed in. */\n requireSession(request: FastifyRequest, reply: FastifyReply): Promise<void>\n }\n}\n\nexport interface CauthyFastifyOptions {\n auth: Cauthy\n}\n\nfunction originOf(req: FastifyRequest): string {\n return `${req.protocol}://${req.headers.host ?? 'localhost'}`\n}\n\nfunction toWebRequest(req: FastifyRequest): Request {\n const headers = new Headers()\n for (const [k, v] of Object.entries(req.headers)) {\n if (Array.isArray(v)) for (const one of v) headers.append(k, one)\n else if (v !== undefined) headers.set(k, v)\n }\n // The routes read no body: what they need is in the URL and the cookies.\n return new Request(new URL(req.url, originOf(req)), { method: req.method, headers })\n}\n\nconst kSession = Symbol('cauthy.session')\nconst kReply = Symbol('cauthy.reply')\n\nconst plugin: FastifyPluginAsync<CauthyFastifyOptions> = async (app: FastifyInstance, { auth }) => {\n const base = auth.options.basePath\n\n app.decorateRequest('cauthy', async function (this: FastifyRequest): Promise<Session | null> {\n const self = this as FastifyRequest & { [kSession]?: Promise<Session | null> }\n self[kSession] ??= (async () => {\n const resolved = await auth.resolve(toWebRequest(this))\n // Set through the raw response so the cookie leaves whatever the route\n // answers with, even an error.\n const reply = (this as FastifyRequest & { [kReply]?: FastifyReply })[kReply]\n if (resolved.cookies.length > 0 && reply) reply.header('set-cookie', resolved.cookies)\n return resolved.session\n })()\n return self[kSession]\n })\n\n app.addHook('onRequest', async (req, reply) => {\n ;(req as FastifyRequest & { [kReply]?: FastifyReply })[kReply] = reply\n })\n\n app.decorate('requireSession', async function (request: FastifyRequest, reply: FastifyReply): Promise<void> {\n if ((await request.cauthy()) === null) {\n await reply.code(401).header('x-cauthy-session', 'none').send({ error: 'unauthenticated' })\n }\n })\n\n const serve = async (req: FastifyRequest, reply: FastifyReply) => {\n const res = await auth.handler(toWebRequest(req))\n reply.code(res.status)\n res.headers.forEach((value, name) => {\n if (name !== 'set-cookie') reply.header(name, value)\n })\n const cookies = res.headers.getSetCookie()\n if (cookies.length > 0) reply.header('set-cookie', cookies)\n return reply.send(res.body === null ? null : Buffer.from(await res.arrayBuffer()))\n }\n app.route({ method: ['GET', 'POST'], url: `${base}/*`, handler: serve })\n}\n\n\n// Outside encapsulation: `req.cauthy()` and `requireSession` are for the whole\n// application, not only for routes registered beside this plugin.\n;(plugin as unknown as Record<symbol, boolean>)[Symbol.for('skip-override')] = true\n;(plugin as unknown as Record<symbol, unknown>)[Symbol.for('fastify.display-name')] = 'cauthy-auth'\n\nexport const cauthyFastify = plugin\nexport default plugin\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkCA,SAAS,SAAS,KAA6B;AAC7C,SAAO,GAAG,IAAI,QAAQ,MAAM,IAAI,QAAQ,QAAQ,WAAW;AAC7D;AAEA,SAAS,aAAa,KAA8B;AAClD,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AAChD,QAAI,MAAM,QAAQ,CAAC,EAAG,YAAW,OAAO,EAAG,SAAQ,OAAO,GAAG,GAAG;AAAA,aACvD,MAAM,OAAW,SAAQ,IAAI,GAAG,CAAC;AAAA,EAC5C;AAEA,SAAO,IAAI,QAAQ,IAAI,IAAI,IAAI,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,QAAQ,IAAI,QAAQ,QAAQ,CAAC;AACrF;AAEA,IAAM,WAAW,uBAAO,gBAAgB;AACxC,IAAM,SAAS,uBAAO,cAAc;AAEpC,IAAM,SAAmD,OAAO,KAAsB,EAAE,KAAK,MAAM;AACjG,QAAM,OAAO,KAAK,QAAQ;AAE1B,MAAI,gBAAgB,UAAU,iBAA+D;AAC3F,UAAM,OAAO;AACb,SAAK,QAAQ,OAAO,YAAY;AAC9B,YAAM,WAAW,MAAM,KAAK,QAAQ,aAAa,IAAI,CAAC;AAGtD,YAAM,QAAS,KAAsD,MAAM;AAC3E,UAAI,SAAS,QAAQ,SAAS,KAAK,MAAO,OAAM,OAAO,cAAc,SAAS,OAAO;AACrF,aAAO,SAAS;AAAA,IAClB,GAAG;AACH,WAAO,KAAK,QAAQ;AAAA,EACtB,CAAC;AAED,MAAI,QAAQ,aAAa,OAAO,KAAK,UAAU;AAC7C;AAAC,IAAC,IAAqD,MAAM,IAAI;AAAA,EACnE,CAAC;AAED,MAAI,SAAS,kBAAkB,eAAgB,SAAyB,OAAoC;AAC1G,QAAK,MAAM,QAAQ,OAAO,MAAO,MAAM;AACrC,YAAM,MAAM,KAAK,GAAG,EAAE,OAAO,oBAAoB,MAAM,EAAE,KAAK,EAAE,OAAO,kBAAkB,CAAC;AAAA,IAC5F;AAAA,EACF,CAAC;AAED,QAAM,QAAQ,OAAO,KAAqB,UAAwB;AAChE,UAAM,MAAM,MAAM,KAAK,QAAQ,aAAa,GAAG,CAAC;AAChD,UAAM,KAAK,IAAI,MAAM;AACrB,QAAI,QAAQ,QAAQ,CAAC,OAAO,SAAS;AACnC,UAAI,SAAS,aAAc,OAAM,OAAO,MAAM,KAAK;AAAA,IACrD,CAAC;AACD,UAAM,UAAU,IAAI,QAAQ,aAAa;AACzC,QAAI,QAAQ,SAAS,EAAG,OAAM,OAAO,cAAc,OAAO;AAC1D,WAAO,MAAM,KAAK,IAAI,SAAS,OAAO,OAAO,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC;AAAA,EACnF;AACA,MAAI,MAAM,EAAE,QAAQ,CAAC,OAAO,MAAM,GAAG,KAAK,GAAG,IAAI,MAAM,SAAS,MAAM,CAAC;AACzE;AAKE,OAA8C,uBAAO,IAAI,eAAe,CAAC,IAAI;AAC7E,OAA8C,uBAAO,IAAI,sBAAsB,CAAC,IAAI;AAE/E,IAAM,gBAAgB;AAC7B,IAAO,kBAAQ;","names":[]}
@@ -0,0 +1,56 @@
1
+ // src/fastify.ts
2
+ function originOf(req) {
3
+ return `${req.protocol}://${req.headers.host ?? "localhost"}`;
4
+ }
5
+ function toWebRequest(req) {
6
+ const headers = new Headers();
7
+ for (const [k, v] of Object.entries(req.headers)) {
8
+ if (Array.isArray(v)) for (const one of v) headers.append(k, one);
9
+ else if (v !== void 0) headers.set(k, v);
10
+ }
11
+ return new Request(new URL(req.url, originOf(req)), { method: req.method, headers });
12
+ }
13
+ var kSession = /* @__PURE__ */ Symbol("cauthy.session");
14
+ var kReply = /* @__PURE__ */ Symbol("cauthy.reply");
15
+ var plugin = async (app, { auth }) => {
16
+ const base = auth.options.basePath;
17
+ app.decorateRequest("cauthy", async function() {
18
+ const self = this;
19
+ self[kSession] ??= (async () => {
20
+ const resolved = await auth.resolve(toWebRequest(this));
21
+ const reply = this[kReply];
22
+ if (resolved.cookies.length > 0 && reply) reply.header("set-cookie", resolved.cookies);
23
+ return resolved.session;
24
+ })();
25
+ return self[kSession];
26
+ });
27
+ app.addHook("onRequest", async (req, reply) => {
28
+ ;
29
+ req[kReply] = reply;
30
+ });
31
+ app.decorate("requireSession", async function(request, reply) {
32
+ if (await request.cauthy() === null) {
33
+ await reply.code(401).header("x-cauthy-session", "none").send({ error: "unauthenticated" });
34
+ }
35
+ });
36
+ const serve = async (req, reply) => {
37
+ const res = await auth.handler(toWebRequest(req));
38
+ reply.code(res.status);
39
+ res.headers.forEach((value, name) => {
40
+ if (name !== "set-cookie") reply.header(name, value);
41
+ });
42
+ const cookies = res.headers.getSetCookie();
43
+ if (cookies.length > 0) reply.header("set-cookie", cookies);
44
+ return reply.send(res.body === null ? null : Buffer.from(await res.arrayBuffer()));
45
+ };
46
+ app.route({ method: ["GET", "POST"], url: `${base}/*`, handler: serve });
47
+ };
48
+ plugin[/* @__PURE__ */ Symbol.for("skip-override")] = true;
49
+ plugin[/* @__PURE__ */ Symbol.for("fastify.display-name")] = "cauthy-auth";
50
+ var cauthyFastify = plugin;
51
+ var fastify_default = plugin;
52
+ export {
53
+ cauthyFastify,
54
+ fastify_default as default
55
+ };
56
+ //# sourceMappingURL=fastify.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fastify.ts"],"sourcesContent":["// Fastify: the routes, and who is signed in on every request.\n//\n// await app.register(cauthyFastify, { auth })\n//\n// app.get('/api/me', async (req, reply) => {\n// const session = await req.cauthy()\n// if (!session) return reply.code(401).send({ error: 'unauthenticated' })\n// return session.user\n// })\n//\n// app.get('/api/books', { preHandler: app.requireSession }, async (req) => { … })\n//\n// Here a session that needs renewing is renewed on the spot: Fastify has the\n// reply in hand, so the new cookie always goes back.\n\nimport type { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify'\nimport type { Cauthy } from './cauthy'\nimport type { Session } from './types'\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n /** Who is signed in, or null. Asked once per request. */\n cauthy(): Promise<Session | null>\n }\n interface FastifyInstance {\n /** A preHandler that answers 401 unless somebody is signed in. */\n requireSession(request: FastifyRequest, reply: FastifyReply): Promise<void>\n }\n}\n\nexport interface CauthyFastifyOptions {\n auth: Cauthy\n}\n\nfunction originOf(req: FastifyRequest): string {\n return `${req.protocol}://${req.headers.host ?? 'localhost'}`\n}\n\nfunction toWebRequest(req: FastifyRequest): Request {\n const headers = new Headers()\n for (const [k, v] of Object.entries(req.headers)) {\n if (Array.isArray(v)) for (const one of v) headers.append(k, one)\n else if (v !== undefined) headers.set(k, v)\n }\n // The routes read no body: what they need is in the URL and the cookies.\n return new Request(new URL(req.url, originOf(req)), { method: req.method, headers })\n}\n\nconst kSession = Symbol('cauthy.session')\nconst kReply = Symbol('cauthy.reply')\n\nconst plugin: FastifyPluginAsync<CauthyFastifyOptions> = async (app: FastifyInstance, { auth }) => {\n const base = auth.options.basePath\n\n app.decorateRequest('cauthy', async function (this: FastifyRequest): Promise<Session | null> {\n const self = this as FastifyRequest & { [kSession]?: Promise<Session | null> }\n self[kSession] ??= (async () => {\n const resolved = await auth.resolve(toWebRequest(this))\n // Set through the raw response so the cookie leaves whatever the route\n // answers with, even an error.\n const reply = (this as FastifyRequest & { [kReply]?: FastifyReply })[kReply]\n if (resolved.cookies.length > 0 && reply) reply.header('set-cookie', resolved.cookies)\n return resolved.session\n })()\n return self[kSession]\n })\n\n app.addHook('onRequest', async (req, reply) => {\n ;(req as FastifyRequest & { [kReply]?: FastifyReply })[kReply] = reply\n })\n\n app.decorate('requireSession', async function (request: FastifyRequest, reply: FastifyReply): Promise<void> {\n if ((await request.cauthy()) === null) {\n await reply.code(401).header('x-cauthy-session', 'none').send({ error: 'unauthenticated' })\n }\n })\n\n const serve = async (req: FastifyRequest, reply: FastifyReply) => {\n const res = await auth.handler(toWebRequest(req))\n reply.code(res.status)\n res.headers.forEach((value, name) => {\n if (name !== 'set-cookie') reply.header(name, value)\n })\n const cookies = res.headers.getSetCookie()\n if (cookies.length > 0) reply.header('set-cookie', cookies)\n return reply.send(res.body === null ? null : Buffer.from(await res.arrayBuffer()))\n }\n app.route({ method: ['GET', 'POST'], url: `${base}/*`, handler: serve })\n}\n\n\n// Outside encapsulation: `req.cauthy()` and `requireSession` are for the whole\n// application, not only for routes registered beside this plugin.\n;(plugin as unknown as Record<symbol, boolean>)[Symbol.for('skip-override')] = true\n;(plugin as unknown as Record<symbol, unknown>)[Symbol.for('fastify.display-name')] = 'cauthy-auth'\n\nexport const cauthyFastify = plugin\nexport default plugin\n"],"mappings":";AAkCA,SAAS,SAAS,KAA6B;AAC7C,SAAO,GAAG,IAAI,QAAQ,MAAM,IAAI,QAAQ,QAAQ,WAAW;AAC7D;AAEA,SAAS,aAAa,KAA8B;AAClD,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AAChD,QAAI,MAAM,QAAQ,CAAC,EAAG,YAAW,OAAO,EAAG,SAAQ,OAAO,GAAG,GAAG;AAAA,aACvD,MAAM,OAAW,SAAQ,IAAI,GAAG,CAAC;AAAA,EAC5C;AAEA,SAAO,IAAI,QAAQ,IAAI,IAAI,IAAI,KAAK,SAAS,GAAG,CAAC,GAAG,EAAE,QAAQ,IAAI,QAAQ,QAAQ,CAAC;AACrF;AAEA,IAAM,WAAW,uBAAO,gBAAgB;AACxC,IAAM,SAAS,uBAAO,cAAc;AAEpC,IAAM,SAAmD,OAAO,KAAsB,EAAE,KAAK,MAAM;AACjG,QAAM,OAAO,KAAK,QAAQ;AAE1B,MAAI,gBAAgB,UAAU,iBAA+D;AAC3F,UAAM,OAAO;AACb,SAAK,QAAQ,OAAO,YAAY;AAC9B,YAAM,WAAW,MAAM,KAAK,QAAQ,aAAa,IAAI,CAAC;AAGtD,YAAM,QAAS,KAAsD,MAAM;AAC3E,UAAI,SAAS,QAAQ,SAAS,KAAK,MAAO,OAAM,OAAO,cAAc,SAAS,OAAO;AACrF,aAAO,SAAS;AAAA,IAClB,GAAG;AACH,WAAO,KAAK,QAAQ;AAAA,EACtB,CAAC;AAED,MAAI,QAAQ,aAAa,OAAO,KAAK,UAAU;AAC7C;AAAC,IAAC,IAAqD,MAAM,IAAI;AAAA,EACnE,CAAC;AAED,MAAI,SAAS,kBAAkB,eAAgB,SAAyB,OAAoC;AAC1G,QAAK,MAAM,QAAQ,OAAO,MAAO,MAAM;AACrC,YAAM,MAAM,KAAK,GAAG,EAAE,OAAO,oBAAoB,MAAM,EAAE,KAAK,EAAE,OAAO,kBAAkB,CAAC;AAAA,IAC5F;AAAA,EACF,CAAC;AAED,QAAM,QAAQ,OAAO,KAAqB,UAAwB;AAChE,UAAM,MAAM,MAAM,KAAK,QAAQ,aAAa,GAAG,CAAC;AAChD,UAAM,KAAK,IAAI,MAAM;AACrB,QAAI,QAAQ,QAAQ,CAAC,OAAO,SAAS;AACnC,UAAI,SAAS,aAAc,OAAM,OAAO,MAAM,KAAK;AAAA,IACrD,CAAC;AACD,UAAM,UAAU,IAAI,QAAQ,aAAa;AACzC,QAAI,QAAQ,SAAS,EAAG,OAAM,OAAO,cAAc,OAAO;AAC1D,WAAO,MAAM,KAAK,IAAI,SAAS,OAAO,OAAO,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC,CAAC;AAAA,EACnF;AACA,MAAI,MAAM,EAAE,QAAQ,CAAC,OAAO,MAAM,GAAG,KAAK,GAAG,IAAI,MAAM,SAAS,MAAM,CAAC;AACzE;AAKE,OAA8C,uBAAO,IAAI,eAAe,CAAC,IAAI;AAC7E,OAA8C,uBAAO,IAAI,sBAAsB,CAAC,IAAI;AAE/E,IAAM,gBAAgB;AAC7B,IAAO,kBAAQ;","names":[]}
@@ -0,0 +1,5 @@
1
+ export { C as Cauthy, a as CauthyEvent, b as CauthyWebhookError, R as Resolved, V as VerifyWebhookOptions, c as createCauthy, v as verifyWebhook } from './cauthy-BrIMaoj1.mjs';
2
+ export { C as CauthyConfigError, a as CauthyOptions, J as JsonValue, P as PublicSession, S as Session, b as SessionOrganization, c as SessionStatus, d as SessionUser } from './types-CYmUVWzY.mjs';
3
+ export { CauthyTheme, ImportedTheme, PushThemeOptions, StackmythThemeOptions, pushTheme, themeFromStackmyth } from './theme.mjs';
4
+ export { P as Permitted, a as PolicyPermission, b as PolicyRole, c as PushPolicyOptions, d as can, p as pushPolicy } from './permissions-B-a3eN0h.mjs';
5
+ import 'jose';
@@ -0,0 +1,5 @@
1
+ export { C as Cauthy, a as CauthyEvent, b as CauthyWebhookError, R as Resolved, V as VerifyWebhookOptions, c as createCauthy, v as verifyWebhook } from './cauthy-CCZc9w8r.js';
2
+ export { C as CauthyConfigError, a as CauthyOptions, J as JsonValue, P as PublicSession, S as Session, b as SessionOrganization, c as SessionStatus, d as SessionUser } from './types-CYmUVWzY.js';
3
+ export { CauthyTheme, ImportedTheme, PushThemeOptions, StackmythThemeOptions, pushTheme, themeFromStackmyth } from './theme.js';
4
+ export { P as Permitted, a as PolicyPermission, b as PolicyRole, c as PushPolicyOptions, d as can, p as pushPolicy } from './permissions-B-a3eN0h.js';
5
+ import 'jose';