theokit 0.5.3 → 0.6.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/{aws-lambda-SCRGTGF5.js → aws-lambda-WT4HRBNL.js} +3 -3
- package/dist/{build-R74EU6BP.js → build-ZZAQV2ES.js} +7 -7
- package/dist/{bun-FURRCQMS.js → bun-UTDVN6R4.js} +3 -3
- package/dist/{check-NXYPLM6M.js → check-F3UDYSZ4.js} +2 -2
- package/dist/{chunk-KJLECZWQ.js → chunk-3FYJ7R7N.js} +57 -14
- package/dist/chunk-3FYJ7R7N.js.map +1 -0
- package/dist/{chunk-JD7AQNJS.js → chunk-4NLIESWK.js} +18 -12
- package/dist/{chunk-JD7AQNJS.js.map → chunk-4NLIESWK.js.map} +1 -1
- package/dist/{chunk-H4J2LECY.js → chunk-6BUUZ2PK.js} +9 -9
- package/dist/chunk-6BUUZ2PK.js.map +1 -0
- package/dist/{chunk-FGOX37NB.js → chunk-BNGBG2SQ.js} +21 -2
- package/dist/chunk-BNGBG2SQ.js.map +1 -0
- package/dist/{chunk-X32KQH5C.js → chunk-CCVC6P6B.js} +3 -3
- package/dist/chunk-CCVC6P6B.js.map +1 -0
- package/dist/{chunk-S74FECKW.js → chunk-D4SV7JOG.js} +24 -20
- package/dist/chunk-D4SV7JOG.js.map +1 -0
- package/dist/{chunk-DNMSV3R3.js → chunk-D7ZH7DTG.js} +3 -3
- package/dist/chunk-D7ZH7DTG.js.map +1 -0
- package/dist/{chunk-MWYSRZI4.js → chunk-EQV67HZL.js} +2 -2
- package/dist/{chunk-CG563V5M.js → chunk-IEES3CHD.js} +39 -5
- package/dist/chunk-IEES3CHD.js.map +1 -0
- package/dist/{chunk-DUKXQNM7.js → chunk-IHMJV2OK.js} +2 -2
- package/dist/{chunk-WGHJD6I7.js → chunk-JAIKGP3Q.js} +76 -21
- package/dist/chunk-JAIKGP3Q.js.map +1 -0
- package/dist/{chunk-NZXH2LQQ.js → chunk-SJIRP73B.js} +18 -1
- package/dist/chunk-SJIRP73B.js.map +1 -0
- package/dist/{chunk-X3VNROZ7.js → chunk-SVSUUK4H.js} +73 -14
- package/dist/chunk-SVSUUK4H.js.map +1 -0
- package/dist/cli/index.js +25 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/{cloudflare-IZ6VJ2OU.js → cloudflare-U5GYYI47.js} +3 -3
- package/dist/db-ZPDW3UCU.js +78 -0
- package/dist/db-ZPDW3UCU.js.map +1 -0
- package/dist/{deno-deploy-O73NBXIW.js → deno-deploy-3QHJ3AJQ.js} +3 -3
- package/dist/{dev-ZU46C5AZ.js → dev-MHCQ5RD7.js} +16 -8
- package/dist/dev-MHCQ5RD7.js.map +1 -0
- package/dist/{dev-emit-66FCOS7V.js → dev-emit-4WG3B5BM.js} +2 -2
- package/dist/{dev-emit-JBVINGHU.js → dev-emit-6DJUK6DT.js} +76 -21
- package/dist/dev-emit-6DJUK6DT.js.map +1 -0
- package/dist/devtools/entry.js +2 -3
- package/dist/devtools/entry.js.map +1 -1
- package/dist/generate-ODDAYXNR.js +494 -0
- package/dist/generate-ODDAYXNR.js.map +1 -0
- package/dist/index.d.ts +32 -3
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/{info-CORLCPEJ.js → info-CSGWIAXA.js} +5 -5
- package/dist/{netlify-O7G3RLK4.js → netlify-DUB7BZ4E.js} +3 -3
- package/dist/{node-LXPQLMVB.js → node-ELE236WK.js} +3 -3
- package/dist/{openapi-TEOUOIJ2.js → openapi-KSY272CW.js} +5 -5
- package/dist/registry-B7FA6KMI.js +25 -0
- package/dist/{routes-GAXVWJUK.js → routes-NWJA5L5I.js} +4 -4
- package/dist/{scan-NQFI5S7P.js → scan-I5PT6TUX.js} +2 -2
- package/dist/{schema-D3v8VB7o.d.ts → schema-BpH6ivDY.d.ts} +2 -4
- package/dist/{schema-Z5VJ2I76.js → schema-NPI4NJEF.js} +3 -3
- package/dist/server/auth/index.js +3 -5
- package/dist/server/define/index.d.ts +7 -0
- package/dist/server/define/index.js +1 -1
- package/dist/server/http/index.js +1 -2
- package/dist/server/index.d.ts +34 -1
- package/dist/server/index.js +57 -48
- package/dist/server/index.js.map +1 -1
- package/dist/{services-typed-client-ASZL7FQV.js → services-typed-client-24VBMDOF.js} +2 -2
- package/dist/{services-typed-client-KK6RHRDG.js → services-typed-client-IETY2SAK.js} +2 -2
- package/dist/{start-HGUNNF5X.js → start-OAAAQ7Z4.js} +84 -69
- package/dist/start-OAAAQ7Z4.js.map +1 -0
- package/dist/{static-EO73I4C7.js → static-YWF2M6YT.js} +4 -4
- package/dist/{theo-cloud-HBCYEODQ.js → theo-cloud-EUK56I7O.js} +2 -2
- package/dist/{vercel-GSFDMF7K.js → vercel-LGDQWYZD.js} +3 -3
- package/dist/vite-plugin/index.d.ts +1 -1
- package/dist/vite-plugin/index.js +3 -4
- package/dist/{vite-plugin-TX5H4MB6.js → vite-plugin-IF2O6BJV.js} +7 -7
- package/package.json +4 -1
- package/dist/chunk-4HBI7DHP.js +0 -20
- package/dist/chunk-4HBI7DHP.js.map +0 -1
- package/dist/chunk-CG563V5M.js.map +0 -1
- package/dist/chunk-DNMSV3R3.js.map +0 -1
- package/dist/chunk-FGOX37NB.js.map +0 -1
- package/dist/chunk-H4J2LECY.js.map +0 -1
- package/dist/chunk-KJLECZWQ.js.map +0 -1
- package/dist/chunk-NZXH2LQQ.js.map +0 -1
- package/dist/chunk-S74FECKW.js.map +0 -1
- package/dist/chunk-WGHJD6I7.js.map +0 -1
- package/dist/chunk-X32KQH5C.js.map +0 -1
- package/dist/chunk-X3VNROZ7.js.map +0 -1
- package/dist/dev-ZU46C5AZ.js.map +0 -1
- package/dist/dev-emit-JBVINGHU.js.map +0 -1
- package/dist/generate-AZ2K6Z2Z.js +0 -281
- package/dist/generate-AZ2K6Z2Z.js.map +0 -1
- package/dist/registry-KW4F4D2L.js +0 -25
- package/dist/start-HGUNNF5X.js.map +0 -1
- /package/dist/{aws-lambda-SCRGTGF5.js.map → aws-lambda-WT4HRBNL.js.map} +0 -0
- /package/dist/{build-R74EU6BP.js.map → build-ZZAQV2ES.js.map} +0 -0
- /package/dist/{bun-FURRCQMS.js.map → bun-UTDVN6R4.js.map} +0 -0
- /package/dist/{check-NXYPLM6M.js.map → check-F3UDYSZ4.js.map} +0 -0
- /package/dist/{chunk-MWYSRZI4.js.map → chunk-EQV67HZL.js.map} +0 -0
- /package/dist/{chunk-DUKXQNM7.js.map → chunk-IHMJV2OK.js.map} +0 -0
- /package/dist/{cloudflare-IZ6VJ2OU.js.map → cloudflare-U5GYYI47.js.map} +0 -0
- /package/dist/{deno-deploy-O73NBXIW.js.map → deno-deploy-3QHJ3AJQ.js.map} +0 -0
- /package/dist/{dev-emit-66FCOS7V.js.map → dev-emit-4WG3B5BM.js.map} +0 -0
- /package/dist/{info-CORLCPEJ.js.map → info-CSGWIAXA.js.map} +0 -0
- /package/dist/{netlify-O7G3RLK4.js.map → netlify-DUB7BZ4E.js.map} +0 -0
- /package/dist/{node-LXPQLMVB.js.map → node-ELE236WK.js.map} +0 -0
- /package/dist/{openapi-TEOUOIJ2.js.map → openapi-KSY272CW.js.map} +0 -0
- /package/dist/{registry-KW4F4D2L.js.map → registry-B7FA6KMI.js.map} +0 -0
- /package/dist/{routes-GAXVWJUK.js.map → routes-NWJA5L5I.js.map} +0 -0
- /package/dist/{scan-NQFI5S7P.js.map → scan-I5PT6TUX.js.map} +0 -0
- /package/dist/{schema-Z5VJ2I76.js.map → schema-NPI4NJEF.js.map} +0 -0
- /package/dist/{services-typed-client-ASZL7FQV.js.map → services-typed-client-24VBMDOF.js.map} +0 -0
- /package/dist/{services-typed-client-KK6RHRDG.js.map → services-typed-client-IETY2SAK.js.map} +0 -0
- /package/dist/{static-EO73I4C7.js.map → static-YWF2M6YT.js.map} +0 -0
- /package/dist/{theo-cloud-HBCYEODQ.js.map → theo-cloud-EUK56I7O.js.map} +0 -0
- /package/dist/{vercel-GSFDMF7K.js.map → vercel-LGDQWYZD.js.map} +0 -0
- /package/dist/{vite-plugin-TX5H4MB6.js.map → vite-plugin-IF2O6BJV.js.map} +0 -0
|
@@ -50,19 +50,53 @@ function attachRouteFiles(node, dir) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
function parseSegment(name) {
|
|
54
|
+
if (name.startsWith("[[")) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Optional catch-all route segment is not supported yet: '${name}'. Use '[...param]' for catch-all, or split into explicit segments.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const catchAll = /^\[\.\.\.(.+)\]$/.exec(name);
|
|
60
|
+
if (catchAll) return checkedSegment("catchall", catchAll[1], name);
|
|
61
|
+
const dynamic = /^\[(.+)\]$/.exec(name);
|
|
62
|
+
if (dynamic) return checkedSegment("dynamic", dynamic[1], name);
|
|
63
|
+
return { kind: "static" };
|
|
64
|
+
}
|
|
65
|
+
function checkedSegment(kind, paramName, name) {
|
|
66
|
+
if (!/^\w+$/.test(paramName)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Invalid route param '${paramName}' in segment '${name}' \u2014 param names must match [A-Za-z0-9_] (react-router constraint).`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return { kind, paramName };
|
|
72
|
+
}
|
|
53
73
|
function nodeHasContent(node) {
|
|
54
74
|
return node.page !== void 0 || node.layout !== void 0 || node.error !== void 0 || node.loading !== void 0 || node.notFound !== void 0 || node.children.length > 0;
|
|
55
75
|
}
|
|
76
|
+
function buildChildPath(routePath, urlSegment) {
|
|
77
|
+
if (!urlSegment) return routePath;
|
|
78
|
+
return routePath === "/" ? `/${urlSegment}` : `${routePath}/${urlSegment}`;
|
|
79
|
+
}
|
|
80
|
+
function scanChild(parentDir, dirName, routePath) {
|
|
81
|
+
const isRouteGroup = dirName.startsWith("(") && dirName.endsWith(")");
|
|
82
|
+
const urlSegment = isRouteGroup ? "" : dirName;
|
|
83
|
+
const childPath = buildChildPath(routePath, urlSegment);
|
|
84
|
+
const child = scanDir(join(parentDir, dirName), isRouteGroup ? "" : dirName, childPath);
|
|
85
|
+
if (!isRouteGroup) {
|
|
86
|
+
const parsed = parseSegment(dirName);
|
|
87
|
+
if (parsed.kind !== "static" && parsed.paramName !== void 0) {
|
|
88
|
+
child.dynamic = { paramName: parsed.paramName, catchAll: parsed.kind === "catchall" };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return child;
|
|
92
|
+
}
|
|
56
93
|
function scanDir(dir, segment, routePath) {
|
|
57
94
|
const node = { segment, path: routePath, children: [] };
|
|
58
95
|
attachRouteFiles(node, dir);
|
|
59
96
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
60
97
|
if (!entry.isDirectory()) continue;
|
|
61
98
|
if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
|
|
62
|
-
const
|
|
63
|
-
const urlSegment = isRouteGroup ? "" : entry.name;
|
|
64
|
-
const childPath = urlSegment ? routePath === "/" ? `/${urlSegment}` : `${routePath}/${urlSegment}` : routePath;
|
|
65
|
-
const child = scanDir(join(dir, entry.name), isRouteGroup ? "" : entry.name, childPath);
|
|
99
|
+
const child = scanChild(dir, entry.name, routePath);
|
|
66
100
|
if (nodeHasContent(child)) {
|
|
67
101
|
node.children.push(child);
|
|
68
102
|
}
|
|
@@ -83,4 +117,4 @@ export {
|
|
|
83
117
|
isRouteFile,
|
|
84
118
|
scanRoutes
|
|
85
119
|
};
|
|
86
|
-
//# sourceMappingURL=chunk-
|
|
120
|
+
//# sourceMappingURL=chunk-IEES3CHD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/router/scan.ts","../src/router/types.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time file-system scanner. All paths originate from `appDir`,\n * which is itself derived from `process.cwd()`. No HTTP input flows here.\n */\nimport { readdirSync, statSync, existsSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\n\nimport type { RouteNode, RouteFileName } from './types.js'\nimport { ROUTE_FILE_NAMES, ROUTE_FILE_EXTENSIONS } from './types.js'\n\nfunction toNodeKey(name: RouteFileName): 'page' | 'layout' | 'error' | 'loading' | 'notFound' {\n if (name === 'not-found') return 'notFound'\n return name\n}\n\nfunction setRouteFile(\n node: RouteNode,\n key: 'page' | 'layout' | 'error' | 'loading' | 'notFound',\n value: string,\n): void {\n switch (key) {\n case 'page':\n node.page = value\n break\n case 'layout':\n node.layout = value\n break\n case 'error':\n node.error = value\n break\n case 'loading':\n node.loading = value\n break\n case 'notFound':\n node.notFound = value\n break\n }\n}\n\nfunction attachRouteFiles(node: RouteNode, dir: string): void {\n // Check route files with extension priority (.tsx > .ts > .jsx > .js)\n for (const name of ROUTE_FILE_NAMES) {\n const key = toNodeKey(name)\n if (node[key] !== undefined) continue\n for (const ext of ROUTE_FILE_EXTENSIONS) {\n const filename = `${name}${ext}`\n if (existsSync(join(dir, filename))) {\n setRouteFile(node, key, resolve(dir, filename))\n break\n }\n }\n }\n}\n\ninterface ParsedSegment {\n kind: 'static' | 'dynamic' | 'catchall'\n paramName?: string\n}\n\n/**\n * Classify a directory name as a static, dynamic (`[param]`), or catch-all\n * (`[...slug]`) route segment. Throws at build time on:\n * - optional catch-all `[[...slug]]` (EC-4 — not supported yet; see plan Q3)\n * - a param name outside `[A-Za-z0-9_]` (EC-5 — react-router would silently mis-match)\n * Order matters: catch-all is checked before single dynamic so `[...slug]` is\n * not misread as a dynamic param literally named `...slug`.\n */\nfunction parseSegment(name: string): ParsedSegment {\n if (name.startsWith('[[')) {\n throw new Error(\n `Optional catch-all route segment is not supported yet: '${name}'. ` +\n `Use '[...param]' for catch-all, or split into explicit segments.`,\n )\n }\n const catchAll = /^\\[\\.\\.\\.(.+)\\]$/.exec(name)\n if (catchAll) return checkedSegment('catchall', catchAll[1], name)\n const dynamic = /^\\[(.+)\\]$/.exec(name)\n if (dynamic) return checkedSegment('dynamic', dynamic[1], name)\n return { kind: 'static' }\n}\n\nfunction checkedSegment(\n kind: 'dynamic' | 'catchall',\n paramName: string,\n name: string,\n): ParsedSegment {\n if (!/^\\w+$/.test(paramName)) {\n throw new Error(\n `Invalid route param '${paramName}' in segment '${name}' — ` +\n `param names must match [A-Za-z0-9_] (react-router constraint).`,\n )\n }\n return { kind, paramName }\n}\n\nfunction nodeHasContent(node: RouteNode): boolean {\n return (\n node.page !== undefined ||\n node.layout !== undefined ||\n node.error !== undefined ||\n node.loading !== undefined ||\n node.notFound !== undefined ||\n node.children.length > 0\n )\n}\n\nfunction buildChildPath(routePath: string, urlSegment: string): string {\n if (!urlSegment) return routePath\n return routePath === '/' ? `/${urlSegment}` : `${routePath}/${urlSegment}`\n}\n\n/** Scan one subdirectory into a child RouteNode, attaching dynamic-segment metadata. */\nfunction scanChild(parentDir: string, dirName: string, routePath: string): RouteNode {\n const isRouteGroup = dirName.startsWith('(') && dirName.endsWith(')')\n const urlSegment = isRouteGroup ? '' : dirName\n const childPath = buildChildPath(routePath, urlSegment)\n const child = scanDir(join(parentDir, dirName), isRouteGroup ? '' : dirName, childPath)\n\n // Dynamic / catch-all segment metadata (throws on [[...]] or invalid charset).\n if (!isRouteGroup) {\n const parsed = parseSegment(dirName)\n if (parsed.kind !== 'static' && parsed.paramName !== undefined) {\n child.dynamic = { paramName: parsed.paramName, catchAll: parsed.kind === 'catchall' }\n }\n }\n return child\n}\n\nfunction scanDir(dir: string, segment: string, routePath: string): RouteNode {\n const node: RouteNode = { segment, path: routePath, children: [] }\n attachRouteFiles(node, dir)\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue\n if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue\n\n const child = scanChild(dir, entry.name, routePath)\n // Prune empty nodes — a directory with no route files AND no children is\n // irrelevant to the manifest.\n if (nodeHasContent(child)) {\n node.children.push(child)\n }\n }\n\n return node\n}\n\nexport function scanRoutes(appDir: string): RouteNode {\n if (!existsSync(appDir)) {\n throw new Error(`App directory does not exist: ${appDir}`)\n }\n if (!statSync(appDir).isDirectory()) {\n throw new Error(`App path is not a directory: ${appDir}`)\n }\n return scanDir(appDir, '', '/')\n}\n","// T2.2 (architecture-cleanup) — RouteNode moved to core/contracts/route-node.ts\n// (canonical home per ADR-0001 v3). Re-export keeps the local path stable.\nexport type { RouteNode } from '../core/contracts/route-node.js'\n\nexport const ROUTE_FILE_NAMES = ['page', 'layout', 'error', 'loading', 'not-found'] as const\n\nexport type RouteFileName = (typeof ROUTE_FILE_NAMES)[number]\n\nexport const ROUTE_FILE_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'] as const\n\nconst ROUTE_FILE_REGEX = /^(page|layout|error|loading|not-found)\\.(tsx|ts|jsx|js)$/\n\nexport function isRouteFile(filename: string): boolean {\n return ROUTE_FILE_REGEX.test(filename)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import "tsx/esm";
|
|
3
3
|
import {
|
|
4
4
|
theoConfigSchema
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-BNGBG2SQ.js";
|
|
6
6
|
|
|
7
7
|
// src/config/load-env.ts
|
|
8
8
|
import { lstatSync, readFileSync, realpathSync, statSync } from "fs";
|
|
@@ -231,4 +231,4 @@ export {
|
|
|
231
231
|
loadEnv,
|
|
232
232
|
loadConfig
|
|
233
233
|
};
|
|
234
|
-
//# sourceMappingURL=chunk-
|
|
234
|
+
//# sourceMappingURL=chunk-IHMJV2OK.js.map
|
|
@@ -7,6 +7,12 @@ import { join } from "path";
|
|
|
7
7
|
|
|
8
8
|
// src/vite-plugin/openapi-emit/zod-to-openapi.ts
|
|
9
9
|
import { z } from "zod";
|
|
10
|
+
var ZodToOpenApiError = class extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "ZodToOpenApiError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
10
16
|
function zodToOpenApiSchema(schema, options = {}) {
|
|
11
17
|
const ctx = options.ctx ?? { seen: /* @__PURE__ */ new Map(), components: {} };
|
|
12
18
|
const name = options.name;
|
|
@@ -22,42 +28,92 @@ function zodToOpenApiSchema(schema, options = {}) {
|
|
|
22
28
|
}
|
|
23
29
|
return convertSchema(schema, ctx);
|
|
24
30
|
}
|
|
31
|
+
function zodDef(schema) {
|
|
32
|
+
return schema.def;
|
|
33
|
+
}
|
|
25
34
|
function convertSchema(schema, ctx) {
|
|
26
35
|
const existing = ctx.seen.get(schema);
|
|
27
36
|
if (existing !== void 0) {
|
|
28
37
|
return { $ref: `#/components/schemas/${existing}` };
|
|
29
38
|
}
|
|
39
|
+
const def = zodDef(schema);
|
|
40
|
+
if (def?.type === "function") {
|
|
41
|
+
throw new ZodToOpenApiError(
|
|
42
|
+
"z.function() cannot be represented in an OpenAPI schema (functions are not serializable)."
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (def?.type === "pipe" && def.in !== void 0) {
|
|
46
|
+
return convertSchema(def.in, ctx);
|
|
47
|
+
}
|
|
30
48
|
try {
|
|
31
49
|
const jsonSchema = z.toJSONSchema(schema);
|
|
32
|
-
|
|
33
|
-
|
|
50
|
+
const out = toOpenApi3(jsonSchema);
|
|
51
|
+
if (def?.type === "union" && def.discriminator !== void 0 && out.oneOf !== void 0) {
|
|
52
|
+
out.discriminator = { propertyName: def.discriminator };
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (err instanceof ZodToOpenApiError) throw err;
|
|
34
57
|
return {};
|
|
35
58
|
}
|
|
36
59
|
}
|
|
60
|
+
var SAFE_INT_MIN = Number.MIN_SAFE_INTEGER;
|
|
61
|
+
var SAFE_INT_MAX = Number.MAX_SAFE_INTEGER;
|
|
62
|
+
function isRecord(v) {
|
|
63
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
64
|
+
}
|
|
65
|
+
function isNoiseKey(key, value, isInteger, hasFormat) {
|
|
66
|
+
if (key === "pattern" && hasFormat) return true;
|
|
67
|
+
if (isInteger && key === "minimum" && value === SAFE_INT_MIN) return true;
|
|
68
|
+
if (isInteger && key === "maximum" && value === SAFE_INT_MAX) return true;
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
function normalizeValue(key, value) {
|
|
72
|
+
if (key === "properties" && isRecord(value)) {
|
|
73
|
+
const props = {};
|
|
74
|
+
for (const [pk, pv] of Object.entries(value)) props[pk] = isRecord(pv) ? toOpenApi3(pv) : pv;
|
|
75
|
+
return props;
|
|
76
|
+
}
|
|
77
|
+
if (key === "items" && isRecord(value)) return toOpenApi3(value);
|
|
78
|
+
if ((key === "anyOf" || key === "oneOf" || key === "allOf") && Array.isArray(value)) {
|
|
79
|
+
const arr = value;
|
|
80
|
+
return arr.map((v) => isRecord(v) ? toOpenApi3(v) : v);
|
|
81
|
+
}
|
|
82
|
+
if (key === "additionalProperties" && isRecord(value)) return toOpenApi3(value);
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
function convertAnyOf(branches, rest) {
|
|
86
|
+
const records = branches.filter(isRecord);
|
|
87
|
+
const hasNull = records.some((b) => b.type === "null");
|
|
88
|
+
const nonNull = records.filter((b) => b.type !== "null");
|
|
89
|
+
if (hasNull && nonNull.length === 1) {
|
|
90
|
+
return { ...toOpenApi3(nonNull[0]), nullable: true };
|
|
91
|
+
}
|
|
92
|
+
const result = { oneOf: nonNull.map((b) => toOpenApi3(b)) };
|
|
93
|
+
if (hasNull) result.nullable = true;
|
|
94
|
+
for (const [k, v] of Object.entries(rest))
|
|
95
|
+
if (k !== "anyOf") result[k] = v;
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
37
98
|
function toOpenApi3(js) {
|
|
38
99
|
const { $schema: _, $defs: _defs, ...rest } = js;
|
|
100
|
+
if (Array.isArray(rest.anyOf)) return convertAnyOf(rest.anyOf, rest);
|
|
101
|
+
const isInteger = rest.type === "integer";
|
|
102
|
+
const hasFormat = typeof rest.format === "string";
|
|
39
103
|
const out = {};
|
|
40
104
|
for (const [key, value] of Object.entries(rest)) {
|
|
105
|
+
if (isNoiseKey(key, value, isInteger, hasFormat)) continue;
|
|
106
|
+
if (key === "const") {
|
|
107
|
+
out.enum = [value];
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
41
110
|
if (key === "type" && Array.isArray(value)) {
|
|
42
|
-
const types = value;
|
|
43
|
-
const hasNull = types.includes("null");
|
|
111
|
+
const types = value.filter((t) => typeof t === "string");
|
|
44
112
|
const nonNull = types.filter((t) => t !== "null");
|
|
45
113
|
out.type = nonNull.length === 1 ? nonNull[0] : nonNull;
|
|
46
|
-
if (
|
|
47
|
-
} else if (key === "properties" && value && typeof value === "object") {
|
|
48
|
-
const props = {};
|
|
49
|
-
for (const [pk, pv] of Object.entries(value)) {
|
|
50
|
-
props[pk] = pv && typeof pv === "object" ? toOpenApi3(pv) : pv;
|
|
51
|
-
}
|
|
52
|
-
out.properties = props;
|
|
53
|
-
} else if (key === "items" && value && typeof value === "object") {
|
|
54
|
-
out.items = toOpenApi3(value);
|
|
55
|
-
} else if ((key === "anyOf" || key === "oneOf" || key === "allOf") && Array.isArray(value)) {
|
|
56
|
-
out[key] = value.map((v) => toOpenApi3(v));
|
|
57
|
-
} else if (key === "additionalProperties" && value && typeof value === "object") {
|
|
58
|
-
out.additionalProperties = toOpenApi3(value);
|
|
114
|
+
if (types.includes("null")) out.nullable = true;
|
|
59
115
|
} else {
|
|
60
|
-
out[key] = value;
|
|
116
|
+
out[key] = normalizeValue(key, value);
|
|
61
117
|
}
|
|
62
118
|
}
|
|
63
119
|
return out;
|
|
@@ -90,8 +146,7 @@ function appendObjectAsParameters(schema, location, out, ctx) {
|
|
|
90
146
|
const shape = schema.shape;
|
|
91
147
|
if (shape === void 0) return;
|
|
92
148
|
for (const [name, child] of Object.entries(shape)) {
|
|
93
|
-
const
|
|
94
|
-
const optional = childDef.typeName === "ZodOptional" || childDef.typeName === "ZodDefault";
|
|
149
|
+
const optional = child.safeParse(void 0).success;
|
|
95
150
|
out.push({
|
|
96
151
|
name,
|
|
97
152
|
in: location,
|
|
@@ -252,4 +307,4 @@ export {
|
|
|
252
307
|
emitOpenApi,
|
|
253
308
|
loadRoutesForOpenApi
|
|
254
309
|
};
|
|
255
|
-
//# sourceMappingURL=chunk-
|
|
310
|
+
//# sourceMappingURL=chunk-JAIKGP3Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vite-plugin/openapi-emit/emit.ts","../src/vite-plugin/openapi-emit/zod-to-openapi.ts","../src/vite-plugin/openapi-emit/load-routes.ts"],"sourcesContent":["/**\n * In-house `emitOpenApi()` orchestrator (build-time only).\n *\n * Pure transformation: takes a route manifest (already hydrated with Zod\n * schemas extracted from `defineRoute()` config) + the openapi config\n * block, builds an OpenAPI 3.x document, writes `<outDir>/openapi.json`,\n * returns the document for downstream callers (CLI logs, tests).\n *\n * Per G2 plan v1.1 T2.1. Modelled on trpc-openapi's\n * `generateOpenApiDocument` + encore's `Generator.Generate()`.\n *\n * NEVER ship to runtime — devDep-shaped artifact.\n *\n * Path templating: TheoKit's `:param` syntax is converted to OpenAPI's\n * `{param}` per the spec. Env-var override: `THEOKIT_OPENAPI_SERVER_URL`\n * overrides `servers[0].url` without rebuilding the config.\n */\n/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time emit. outDir is a config-controlled relative path. The\n * caller already refused absolute paths via theoConfigSchema.distDir\n * pattern; the openapi.outDir field follows the same convention.\n */\nimport { mkdirSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport type { z } from 'zod'\n\nimport { zodToOpenApiSchema, type ConvertCtx, type OpenApiSchema } from './zod-to-openapi.js'\n\nexport interface OpenApiManifestRoute {\n /** TheoKit-style path with `:param` placeholders (`/users/:id`). */\n routePath: string\n /** Uppercase HTTP methods the route exports (`['GET','POST']`). */\n methods: string[]\n /** Optional `defineRoute({body})` schema. */\n body?: z.ZodType\n /** Optional `defineRoute({query})` schema. */\n query?: z.ZodType\n /** Optional `defineRoute({params})` schema. */\n params?: z.ZodType\n /** Optional `defineRoute({response})` schema. */\n response?: z.ZodType\n}\n\nexport interface OpenApiEmitConfig {\n servers: { url: string; description?: string }[]\n specVersion: '3.1.0' | '3.0.3'\n title: string\n version: string\n outDir: string\n}\n\nexport interface OpenApiOperation {\n operationId?: string\n parameters?: OpenApiParameter[]\n requestBody?: {\n required?: boolean\n content: Record<string, { schema: OpenApiSchema }>\n }\n responses: Record<\n string,\n { description: string; content?: Record<string, { schema: OpenApiSchema }> }\n >\n}\n\nexport interface OpenApiParameter {\n name: string\n in: 'path' | 'query' | 'header' | 'cookie'\n required: boolean\n schema: OpenApiSchema\n description?: string\n}\n\nexport type OpenApiPathItem = Partial<\n Record<'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options', OpenApiOperation>\n>\n\nexport interface OpenApiDocument {\n openapi: '3.1.0' | '3.0.3'\n info: { title: string; version: string }\n servers: { url: string; description?: string }[]\n paths: Record<string, OpenApiPathItem>\n components?: { schemas?: Record<string, OpenApiSchema> }\n}\n\nexport interface EmitOpenApiInput {\n manifest: OpenApiManifestRoute[]\n config: OpenApiEmitConfig\n}\n\nexport interface EmitOpenApiResult {\n document: OpenApiDocument\n path: string\n}\n\nconst METHOD_TO_KEY: Record<string, keyof OpenApiPathItem> = {\n GET: 'get',\n POST: 'post',\n PUT: 'put',\n PATCH: 'patch',\n DELETE: 'delete',\n HEAD: 'head',\n OPTIONS: 'options',\n}\n\n/** Convert `/users/:id/posts/:postId?` → `/users/{id}/posts/{postId}`. */\nexport function templatePath(routePath: string): string {\n // Bounded quantifier (\\w{0,64}) is OSS-safe — TheoKit route param names\n // never exceed 64 chars in practice; cap prevents super-linear backtracking.\n return routePath.replace(/:([A-Za-z_]\\w{0,64})\\??/g, '{$1}')\n}\n\nfunction buildParameters(\n paramsSchema: z.ZodType | undefined,\n querySchema: z.ZodType | undefined,\n ctx: ConvertCtx,\n): OpenApiParameter[] {\n const out: OpenApiParameter[] = []\n if (paramsSchema !== undefined) {\n appendObjectAsParameters(paramsSchema, 'path', out, ctx)\n }\n if (querySchema !== undefined) {\n appendObjectAsParameters(querySchema, 'query', out, ctx)\n }\n return out\n}\n\nfunction appendObjectAsParameters(\n schema: z.ZodType,\n location: 'path' | 'query',\n out: OpenApiParameter[],\n ctx: ConvertCtx,\n): void {\n // We expect an object schema; if it's not, skip silently (caller already\n // validated upstream when scanning defineRoute()).\n const shape = (schema as unknown as { shape?: Record<string, z.ZodType> }).shape\n if (shape === undefined) return\n for (const [name, child] of Object.entries(shape)) {\n // A field is optional iff the schema accepts `undefined` (covers\n // `.optional()` + `.default()`). Runtime check via safeParse — robust\n // across zod versions, unlike reading the changed `_def.typeName` internal\n // (zod 3) / `def.type` (zod 4).\n const optional = child.safeParse(undefined).success\n out.push({\n name,\n in: location,\n required: location === 'path' ? true : !optional,\n schema: zodToOpenApiSchema(child, { ctx }),\n })\n }\n}\n\nfunction buildOperation(\n route: OpenApiManifestRoute,\n ctx: ConvertCtx,\n method: string,\n): OpenApiOperation {\n // Bounded sanitizer: two single-pass replacements (no nested quantifiers).\n const sanitized = route.routePath.replace(/[/:\\-{}]/g, '_').replace(/_+/g, '_')\n const operationId = `${method.toLowerCase()}_${sanitized.replace(/^_|_$/g, '')}`\n const op: OpenApiOperation = {\n operationId,\n responses: {\n '200': { description: 'OK' },\n },\n }\n\n const parameters = buildParameters(route.params, route.query, ctx)\n if (parameters.length > 0) op.parameters = parameters\n\n // Body only meaningful for write methods; we still attach if defined so\n // the document is honest about the contract.\n if (route.body !== undefined) {\n op.requestBody = {\n required: true,\n content: {\n 'application/json': { schema: zodToOpenApiSchema(route.body, { ctx }) },\n },\n }\n }\n\n if (route.response !== undefined) {\n op.responses['200'] = {\n description: 'OK',\n content: {\n 'application/json': { schema: zodToOpenApiSchema(route.response, { ctx }) },\n },\n }\n }\n\n return op\n}\n\nexport function emitOpenApi(input: EmitOpenApiInput): EmitOpenApiResult {\n const { manifest, config } = input\n\n const ctx: ConvertCtx = { seen: new Map(), components: {} }\n\n const paths: Record<string, OpenApiPathItem> = {}\n for (const route of manifest) {\n const templatedPath = templatePath(route.routePath)\n const item: OpenApiPathItem = paths[templatedPath] ?? {}\n for (const method of route.methods) {\n // Defensive: manifest emitter normalizes to uppercase, but if a\n // future scanner produces a non-HTTP verb the key lookup yields\n // undefined and we skip silently. TS narrows the indexed type to\n // keyof OpenApiPathItem; the runtime undefined check is honest.\n const key = METHOD_TO_KEY[method.toUpperCase()] as keyof OpenApiPathItem | undefined\n if (key === undefined) continue\n item[key] = buildOperation(route, ctx, method)\n }\n paths[templatedPath] = item\n }\n\n const servers = applyServerOverride(config.servers)\n\n const document: OpenApiDocument = {\n openapi: config.specVersion,\n info: { title: config.title, version: config.version },\n servers,\n paths,\n }\n\n if (Object.keys(ctx.components).length > 0) {\n document.components = { schemas: ctx.components }\n }\n\n mkdirSync(config.outDir, { recursive: true })\n const outPath = join(config.outDir, 'openapi.json')\n writeFileSync(outPath, `${JSON.stringify(document, null, 2)}\\n`, 'utf8')\n\n return { document, path: outPath }\n}\n\nfunction applyServerOverride(\n servers: { url: string; description?: string }[],\n): { url: string; description?: string }[] {\n const override = process.env.THEOKIT_OPENAPI_SERVER_URL\n if (override === undefined || override === '') return servers\n if (servers.length === 0) return [{ url: override }]\n const [first, ...rest] = servers\n return [\n {\n url: override,\n ...(first.description !== undefined ? { description: first.description } : {}),\n },\n ...rest,\n ]\n}\n","/**\n * Zod → OpenAPI 3.0 Schema converter (build-time only).\n *\n * Uses Zod v4's native `z.toJSONSchema()` and post-processes\n * the JSON Schema output into OpenAPI 3.0 format:\n * - Removes `$schema` (invalid in OpenAPI 3.0)\n * - Converts `type: [\"string\", \"null\"]` → `{ type: \"string\", nullable: true }`\n * - Recursively normalizes nested schemas\n *\n * NEVER ship to runtime — devDep-shaped artifact.\n */\nimport { z } from 'zod'\n\nexport interface OpenApiSchema {\n type?: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null'\n format?: string\n items?: OpenApiSchema\n properties?: Record<string, OpenApiSchema>\n required?: string[]\n additionalProperties?: boolean | OpenApiSchema\n oneOf?: OpenApiSchema[]\n anyOf?: OpenApiSchema[]\n allOf?: OpenApiSchema[]\n discriminator?: { propertyName: string; mapping?: Record<string, string> }\n enum?: unknown[]\n nullable?: boolean\n description?: string\n default?: unknown\n $ref?: string\n}\n\nexport interface ConvertCtx {\n seen: Map<z.ZodType, string>\n components: Record<string, OpenApiSchema>\n}\n\nexport interface ConvertOptions {\n ctx?: ConvertCtx\n name?: string\n}\n\nexport class ZodToOpenApiError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ZodToOpenApiError'\n }\n}\n\nexport function zodToOpenApiSchema(schema: z.ZodType, options: ConvertOptions = {}): OpenApiSchema {\n const ctx: ConvertCtx = options.ctx ?? { seen: new Map(), components: {} }\n\n const name = options.name\n if (name !== undefined) {\n if (Object.prototype.hasOwnProperty.call(ctx.components, name)) {\n return { $ref: `#/components/schemas/${name}` }\n }\n ctx.seen.set(schema, name)\n ctx.components[name] = {}\n const body = convertSchema(schema, ctx)\n ctx.components[name] = body\n return { $ref: `#/components/schemas/${name}` }\n }\n\n return convertSchema(schema, ctx)\n}\n\ninterface ZodInternalDef {\n type?: string\n discriminator?: string\n in?: z.ZodType\n}\n\n/** Read zod v4's internal schema def (the `.def` getter) without a hard `any`. */\nfunction zodDef(schema: z.ZodType): ZodInternalDef | undefined {\n return (schema as unknown as { def?: ZodInternalDef }).def\n}\n\nfunction convertSchema(schema: z.ZodType, ctx: ConvertCtx): OpenApiSchema {\n const existing = ctx.seen.get(schema)\n if (existing !== undefined) {\n return { $ref: `#/components/schemas/${existing}` }\n }\n\n const def = zodDef(schema)\n // Fail loud on schema kinds that have no OpenAPI representation (rather than\n // swallowing the zod throw into `{}`). z.function() is the canonical case.\n if (def?.type === 'function') {\n throw new ZodToOpenApiError(\n 'z.function() cannot be represented in an OpenAPI schema (functions are not serializable).',\n )\n }\n\n // A transform / pipe (`z.string().transform(...)`) has no JSON-Schema of its\n // own — zod throws. The wire shape is the INPUT (transforms run post-parse),\n // so emit the input schema. This avoids the global `io: 'input'` mode, which\n // would also flip object `additionalProperties` semantics.\n if (def?.type === 'pipe' && def.in !== undefined) {\n return convertSchema(def.in, ctx)\n }\n\n try {\n const jsonSchema = z.toJSONSchema(schema) as Record<string, unknown>\n const out = toOpenApi3(jsonSchema)\n // zod v4 emits a discriminated union as `oneOf` but drops the discriminator\n // key; re-attach it from the zod def so OpenAPI consumers can branch.\n if (def?.type === 'union' && def.discriminator !== undefined && out.oneOf !== undefined) {\n out.discriminator = { propertyName: def.discriminator }\n }\n return out\n } catch (err) {\n if (err instanceof ZodToOpenApiError) throw err\n return {}\n }\n}\n\n/** JS safe-integer bounds zod v4 attaches to `.int()` — noise for OpenAPI, stripped. */\nconst SAFE_INT_MIN = Number.MIN_SAFE_INTEGER\nconst SAFE_INT_MAX = Number.MAX_SAFE_INTEGER\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === 'object' && !Array.isArray(v)\n}\n\n/** Drop zod-v4 noise keys: redundant `pattern` when `format` is set; JS safe-integer sentinel bounds. */\nfunction isNoiseKey(key: string, value: unknown, isInteger: boolean, hasFormat: boolean): boolean {\n if (key === 'pattern' && hasFormat) return true\n if (isInteger && key === 'minimum' && value === SAFE_INT_MIN) return true\n if (isInteger && key === 'maximum' && value === SAFE_INT_MAX) return true\n return false\n}\n\n/** Recursively normalize one JSON-Schema value (properties/items/*Of/additionalProperties). */\nfunction normalizeValue(key: string, value: unknown): unknown {\n if (key === 'properties' && isRecord(value)) {\n const props: Record<string, unknown> = {}\n for (const [pk, pv] of Object.entries(value)) props[pk] = isRecord(pv) ? toOpenApi3(pv) : pv\n return props\n }\n if (key === 'items' && isRecord(value)) return toOpenApi3(value)\n if ((key === 'anyOf' || key === 'oneOf' || key === 'allOf') && Array.isArray(value)) {\n const arr: unknown[] = value\n return arr.map((v) => (isRecord(v) ? toOpenApi3(v) : v))\n }\n if (key === 'additionalProperties' && isRecord(value)) return toOpenApi3(value)\n return value\n}\n\n/** Convert zod v4's `anyOf` (used for `.nullable()` + `z.union()`) to OpenAPI 3.0 shape. */\nfunction convertAnyOf(branches: unknown[], rest: Record<string, unknown>): OpenApiSchema {\n const records = branches.filter(isRecord)\n const hasNull = records.some((b) => b.type === 'null')\n const nonNull = records.filter((b) => b.type !== 'null')\n if (hasNull && nonNull.length === 1) {\n return { ...toOpenApi3(nonNull[0]), nullable: true }\n }\n const result: OpenApiSchema = { oneOf: nonNull.map((b) => toOpenApi3(b)) }\n if (hasNull) result.nullable = true\n for (const [k, v] of Object.entries(rest))\n if (k !== 'anyOf') (result as Record<string, unknown>)[k] = v\n return result\n}\n\n/**\n * Post-process JSON Schema (draft-2020-12) → OpenAPI 3.0:\n * - strip `$schema`/`$defs`\n * - `.nullable()`/union `anyOf` → `nullable: true` / `oneOf`\n * - `type: [\"string\",\"null\"]` → `{ type: \"string\", nullable: true }`\n * - drop zod-v4 noise (redundant pattern, safe-integer bounds)\n * - recurse into nested schemas\n */\nfunction toOpenApi3(js: Record<string, unknown>): OpenApiSchema {\n const { $schema: _, $defs: _defs, ...rest } = js\n if (Array.isArray(rest.anyOf)) return convertAnyOf(rest.anyOf, rest)\n\n const isInteger = rest.type === 'integer'\n const hasFormat = typeof rest.format === 'string'\n const out: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(rest)) {\n if (isNoiseKey(key, value, isInteger, hasFormat)) continue\n // `const` is JSON-Schema / OpenAPI-3.1 only; `enum: [x]` is the 3.0-and-3.1\n // compatible spelling. zod v4 emits `const` for literals (discriminator tags).\n if (key === 'const') {\n out.enum = [value]\n continue\n }\n if (key === 'type' && Array.isArray(value)) {\n const types = value.filter((t): t is string => typeof t === 'string')\n const nonNull = types.filter((t) => t !== 'null')\n out.type = nonNull.length === 1 ? nonNull[0] : nonNull\n if (types.includes('null')) out.nullable = true\n } else {\n out[key] = normalizeValue(key, value)\n }\n }\n\n return out\n}\n","/**\n * Build-time route loader for OpenAPI emit.\n *\n * Takes the G1 manifest (filePath + routePath + methods) and hydrates each\n * route's Zod schemas by dynamically importing the module. `defineRoute`\n * is an identity function, so the imported config exposes `body/query/\n * params/response` directly without any AST gymnastics.\n *\n * Supports both export shapes:\n * 1. `export const POST = defineRoute({...})` (per-method named exports)\n * 2. `export default defineRoute({...})` (single-export legacy shape)\n *\n * Per G2 plan v1.1 T2.2. Uses Vite's `ssrLoadModule` because route files\n * are TS at build time — Node's native loader doesn't transpile.\n */\nimport { resolve } from 'node:path'\n\nimport type { z } from 'zod'\n\nimport type { ManifestRoute } from '../../server/scan/manifest.js'\n\nimport type { OpenApiManifestRoute } from './emit.js'\n\ninterface RouteConfigShape {\n body?: z.ZodType\n query?: z.ZodType\n params?: z.ZodType\n response?: z.ZodType\n}\n\ninterface ViteLikeServer {\n ssrLoadModule: (id: string) => Promise<Record<string, unknown>>\n close: () => Promise<void>\n}\n\ninterface DefaultLoader {\n load: (absPath: string) => Promise<Record<string, unknown>>\n dispose: () => Promise<void>\n}\n\n/** Method names recognized as per-route named exports (UPPERCASE). */\nconst HTTP_METHOD_KEYS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const\n\nexport interface LoadRoutesOptions {\n serverDir: string\n routes: ManifestRoute[]\n /** Injectable for tests; defaults to a fresh Vite SSR dev server. */\n loadModule?: (absPath: string) => Promise<Record<string, unknown>>\n}\n\n/**\n * Hydrate manifest routes with Zod schemas extracted from their modules.\n *\n * Returns one `OpenApiManifestRoute` per (file × method) tuple. If a route\n * file declares multiple methods (`GET` + `POST`), each method gets its\n * own entry with the schemas from that method's `defineRoute()` call.\n *\n * Routes that fail to load are skipped with a console warning — the emit\n * should be best-effort, not a hard build failure.\n *\n * The default Vite loader is created once per call and disposed in the\n * `finally` block so the underlying server is released even when route\n * loading throws.\n */\nexport async function loadRoutesForOpenApi(\n options: LoadRoutesOptions,\n): Promise<OpenApiManifestRoute[]> {\n const { serverDir, routes } = options\n let loadModule = options.loadModule\n let dispose: (() => Promise<void>) | undefined\n if (loadModule === undefined) {\n const created = await createDefaultLoader(serverDir)\n loadModule = created.load\n dispose = created.dispose\n }\n\n const out: OpenApiManifestRoute[] = []\n try {\n for (const r of routes) {\n const abs = resolve(serverDir, r.filePath)\n let mod: Record<string, unknown>\n try {\n mod = await loadModule(abs)\n } catch (err) {\n console.warn(\n `[openapi-emit] Skipping ${r.filePath}: load failed (${(err as Error).message})`,\n )\n continue\n }\n\n const methods = r.methods ?? HTTP_METHOD_KEYS.filter((m) => mod[m] !== undefined)\n if (methods.length === 0) {\n // Fall back to default export when no method-named exports detected.\n const def = mod.default\n if (def !== undefined && def !== null) {\n out.push({ routePath: r.routePath, methods: ['GET'], ...extractSchemas(def) })\n }\n continue\n }\n\n for (const method of methods) {\n const handler = mod[method] ?? mod.default\n if (handler === undefined || handler === null) continue\n out.push({ routePath: r.routePath, methods: [method], ...extractSchemas(handler) })\n }\n }\n } finally {\n if (dispose !== undefined) {\n await dispose()\n }\n }\n return out\n}\n\nfunction extractSchemas(handlerExport: unknown): RouteConfigShape {\n if (typeof handlerExport !== 'object' || handlerExport === null) return {}\n const cfg = handlerExport as RouteConfigShape\n const out: RouteConfigShape = {}\n if (cfg.body !== undefined) out.body = cfg.body\n if (cfg.query !== undefined) out.query = cfg.query\n if (cfg.params !== undefined) out.params = cfg.params\n if (cfg.response !== undefined) out.response = cfg.response\n return out\n}\n\n/**\n * Default loader: spin up a minimal Vite SSR dev server (no HMR, no\n * middlewares) just to use its `ssrLoadModule`. Vite handles TS, ESM, and\n * import resolution for free.\n *\n * Returns `{load, dispose}` — the caller owns the lifecycle and MUST\n * call `dispose()` (typically in a `finally`) so the server is closed.\n */\nasync function createDefaultLoader(serverDir: string): Promise<DefaultLoader> {\n // Vite is a peerDep in build flows; import dynamically.\n const { createServer } = await import('vite')\n const server = (await createServer({\n root: serverDir,\n configFile: false,\n server: { middlewareMode: true },\n appType: 'custom',\n logLevel: 'silent',\n })) as unknown as ViteLikeServer\n\n const swallow = (): void => {\n /* eat close errors — best-effort cleanup */\n }\n return {\n load: async (absPath) => server.ssrLoadModule(absPath),\n dispose: async () => {\n await server.close().catch(swallow)\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -7,6 +7,21 @@ import {
|
|
|
7
7
|
setCookie
|
|
8
8
|
} from "./chunk-ZJQ4O76G.js";
|
|
9
9
|
|
|
10
|
+
// src/server/auth/auth.ts
|
|
11
|
+
var AuthRequiredError = class extends Error {
|
|
12
|
+
code = "AUTH_REQUIRED";
|
|
13
|
+
status = 401;
|
|
14
|
+
constructor(message = "Authentication required") {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "AuthRequiredError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
function requireAuth(session) {
|
|
20
|
+
if (session === null || session === void 0) {
|
|
21
|
+
throw new AuthRequiredError();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
// src/server/auth/crypto.ts
|
|
11
26
|
var encoder = new TextEncoder();
|
|
12
27
|
var decoder = new TextDecoder();
|
|
@@ -578,6 +593,8 @@ async function discoverOidcProvider(issuer) {
|
|
|
578
593
|
}
|
|
579
594
|
|
|
580
595
|
export {
|
|
596
|
+
AuthRequiredError,
|
|
597
|
+
requireAuth,
|
|
581
598
|
encrypt,
|
|
582
599
|
decrypt,
|
|
583
600
|
_resetKeyCacheForTests,
|
|
@@ -601,4 +618,4 @@ export {
|
|
|
601
618
|
clearOidcCache,
|
|
602
619
|
discoverOidcProvider
|
|
603
620
|
};
|
|
604
|
-
//# sourceMappingURL=chunk-
|
|
621
|
+
//# sourceMappingURL=chunk-SJIRP73B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/auth/auth.ts","../src/server/auth/crypto.ts","../src/server/auth/session.ts","../src/server/_internal/encoding.ts","../src/server/auth/auth-totp.ts","../src/server/auth/auth-backup-codes.ts","../src/server/auth/auth-throttle.ts","../src/server/auth/oauth-pkce.ts","../src/server/auth/oauth-state.ts","../src/server/auth/oidc-discovery.ts"],"sourcesContent":["export class AuthRequiredError extends Error {\n code = 'AUTH_REQUIRED' as const\n status = 401\n\n constructor(message = 'Authentication required') {\n super(message)\n this.name = 'AuthRequiredError'\n }\n}\n\nexport function requireAuth<T>(session: T | null | undefined): asserts session is T {\n if (session === null || session === undefined) {\n throw new AuthRequiredError()\n }\n}\n","// AES-GCM-256 token encryption with cached key derivation.\n//\n// CR-002 fixes:\n// - KDF: HKDF (RFC 5869) over the raw secret, not raw SHA-256.\n// HKDF binds the application context (\"theo-session-v1\") into the\n// derived key so the same secret used elsewhere cannot decrypt these\n// tokens. SHA-256 of the raw secret has neither domain separation nor\n// proper key-stretching guarantees.\n// - Cache: derived `CryptoKey` is memoized per (secret, info) tuple.\n// Pre-fix, every encrypt/decrypt re-ran SHA-256 + importKey, which (a)\n// burned CPU on every authenticated request and (b) made\n// `decryptWithFallback` leak rotation-array position via timing.\n// - Buffer typing: `Uint8Array` flows directly into Web Crypto\n// (`BufferSource`-compatible). The previous `as unknown as ArrayBuffer`\n// double-cast (CR-016) is removed.\n\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder()\n\nconst HKDF_INFO_DEFAULT = encoder.encode('theo-session-v1')\nconst HKDF_SALT_EMPTY = new Uint8Array(0)\n\n// Memoize derived keys. Bounded by the number of distinct secrets the app\n// ever uses (typically 1 in steady state, 2-5 during rotation). Map-based\n// so distinct secrets do not evict each other.\nconst keyCache = new Map<string, Promise<CryptoKey>>()\n\nasync function deriveKey(secret: string): Promise<CryptoKey> {\n const cached = keyCache.get(secret)\n if (cached) return cached\n\n const derive = (async () => {\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HKDF' },\n false,\n ['deriveKey'],\n )\n return crypto.subtle.deriveKey(\n { name: 'HKDF', hash: 'SHA-256', salt: HKDF_SALT_EMPTY, info: HKDF_INFO_DEFAULT },\n keyMaterial,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n )\n })()\n\n keyCache.set(secret, derive)\n // If derivation fails (invalid secret), evict so a retry can try fresh.\n derive.catch(() => {\n keyCache.delete(secret)\n })\n return derive\n}\n\nfunction toBase64Url(data: Uint8Array): string {\n return Buffer.from(data).toString('base64url')\n}\n\n/**\n * Decode a base64url string into a `Uint8Array<ArrayBuffer>`. The explicit\n * generic matters: Web Crypto APIs expect `BufferSource`, and TS 5.7+\n * tightened `Uint8Array` to `Uint8Array<ArrayBufferLike>` which includes\n * `SharedArrayBuffer`. Returning the narrower generic lets the value pass\n * `crypto.subtle.decrypt` without `as` casts (CR-016 follow-through).\n */\nfunction fromBase64Url(str: string): Uint8Array<ArrayBuffer> {\n const buf = Buffer.from(str, 'base64url')\n const copy = new Uint8Array(buf.length)\n copy.set(buf)\n return copy\n}\n\n// The `T` generic exists so call sites get type-safe round-tripping\n// (`encrypt<Session>(s, ...)` pairs with `decrypt<Session>(...)`); the\n// runtime body just stringifies `data`, hence T does not appear in the\n// signature beyond the argument.\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- T documents the round-trip contract with decrypt<T>\nexport async function encrypt<T>(data: T, secret: string): Promise<string> {\n const key = await deriveKey(secret)\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const plaintext = encoder.encode(JSON.stringify(data))\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext),\n )\n return `${toBase64Url(iv)}:${toBase64Url(ciphertext)}`\n}\n\nexport async function decrypt<T>(token: string, secret: string): Promise<T | null> {\n try {\n const parts = token.split(':')\n if (parts.length !== 2) return null\n\n const iv = fromBase64Url(parts[0])\n const ciphertext = fromBase64Url(parts[1])\n const key = await deriveKey(secret)\n\n const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext)\n return JSON.parse(decoder.decode(plaintext)) as T\n } catch {\n return null\n }\n}\n\n/**\n * Reset the derived-key cache. Test helper — never call from production code.\n * Used by Vitest to guarantee isolation between tests that use distinct\n * secrets and want to confirm the derivation path runs.\n */\nexport function _resetKeyCacheForTests(): void {\n keyCache.clear()\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\n\nimport {\n getCookie,\n setCookie,\n deleteCookie,\n getCookieFromRequest,\n appendCookieToHeaders,\n appendDeleteCookieToHeaders,\n} from '../http/cookies.js'\n\nimport { encrypt, decrypt } from './crypto.js'\n\nexport interface SessionConfig {\n /**\n * Session secret. Either a single string (legacy) or an array of strings\n * where index 0 is the newest. The array form enables dual-key rotation\n * (T3.1 / ADR D5): encrypt always uses `secrets[0]`, decrypt walks the\n * array, transparent re-encrypt on legacy hits (T3.2).\n *\n * EC-1: array length capped at 5 — enforced via throw at construction.\n */\n secret: string | string[]\n cookieName?: string\n maxAge?: number\n}\n\ninterface SessionEnvelope<T> {\n data: T\n exp: number\n}\n\nexport interface SessionMeta {\n /** Index of the secret that decrypted the session. 0 = newest; > 0 = legacy. */\n secretIndex: number\n /** True when the decrypt used a legacy secret and the cookie should be re-encrypted. */\n needsReencrypt: boolean\n}\n\nexport interface SessionManager<TSession> {\n getSession(req: IncomingMessage): Promise<TSession | null>\n /**\n * T3.2 — Variant of `getSession` that surfaces decrypt metadata.\n * Used by `api-middleware.ts` to wire transparent re-encrypt BEFORE\n * the handler runs (so streaming SSR routes don't miss the Set-Cookie\n * window — EC-4).\n */\n getSessionWithMeta(req: IncomingMessage): Promise<{ data: TSession | null; meta: SessionMeta }>\n createSession(res: ServerResponse, data: TSession): Promise<void>\n destroySession(res: ServerResponse): void\n /**\n * T3.3 — OWASP A07:2021 session fixation mitigation. Re-encrypts the\n * current session with a fresh IV and refreshed expiry. No-op when no\n * session is present.\n */\n rotateSession(req: IncomingMessage, res: ServerResponse): Promise<TSession | null>\n}\n\n/**\n * EC-1 — array length cap. Enforced via throw at construction. Silent\n * truncation would create false sense of rotation; silent acceptance\n * would create unbounded CPU on legacy decrypt walks.\n */\nconst MAX_SECRETS = 5\n\n/**\n * Validate + normalize the secret config into a non-empty array of\n * strings ≥ 32 chars each.\n */\nfunction normalizeSecrets(input: string | string[]): string[] {\n const arr = Array.isArray(input) ? input.slice() : [input]\n if (arr.length === 0) {\n throw new Error('Session secret must be non-empty (received an empty array)')\n }\n if (arr.length > MAX_SECRETS) {\n throw new Error(\n `Session secret array exceeds maximum of ${MAX_SECRETS} entries — drop the oldest before adding a new one (received ${arr.length})`,\n )\n }\n const wasArray = Array.isArray(input)\n for (let i = 0; i < arr.length; i++) {\n const s = arr[i]\n if (typeof s !== 'string' || s.length < 32) {\n const where = wasArray ? ` at index ${i}` : ''\n throw new Error(`Session secret${where} must be at least 32 characters for secure encryption`)\n }\n }\n return arr\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — module-level pure helpers extracted so\n * `createSessionManager` (IncomingMessage path) and\n * `createSessionManagerWeb` (Web path) share the same encryption +\n * dual-key rotation logic without code duplication (sonarjs\n * no-identical-functions). Both wrappers pass `secrets` + `maxAge`\n * captured from their respective factory scope.\n */\nasync function encryptSessionEnvelope(\n data: unknown,\n secrets: string[],\n maxAge: number,\n): Promise<string> {\n const envelope: SessionEnvelope<unknown> = {\n data,\n exp: Date.now() + maxAge * 1000,\n }\n return encrypt(envelope, secrets[0]) // newest\n}\n\n/**\n * CR-002 constant-time: try EVERY rotation entry in parallel, then pick\n * the newest match. Sequential early-exit leaked rotation position via\n * timing (success on entry 0 returned in ~3 ms; success on entry 4 in\n * ~15 ms). Running in parallel binds wall-clock to the slowest single\n * attempt, independent of which entry actually matched. With derived-key\n * cache (CR-002 in crypto.ts), the CPU multiplier per request is ~0\n * after the first request per secret.\n */\nasync function decryptSessionWithFallback<TSession>(\n raw: string,\n secrets: string[],\n): Promise<{ envelope: SessionEnvelope<TSession>; index: number } | null> {\n const results = await Promise.all(\n secrets.map(async (secret, i) => {\n const env = await decrypt<SessionEnvelope<TSession>>(raw, secret)\n return env ? { envelope: env, index: i } : null\n }),\n )\n for (const result of results) {\n if (result) return result\n }\n return null\n}\n\nexport function createSessionManager<TSession>(config: SessionConfig): SessionManager<TSession> {\n const secrets = normalizeSecrets(config.secret)\n const cookieName = config.cookieName ?? 'theo_session'\n const maxAge = config.maxAge ?? 604800 // 7 days\n\n function writeCookie(res: ServerResponse, token: string): void {\n setCookie(res, cookieName, token, {\n httpOnly: true,\n sameSite: 'lax',\n maxAge,\n path: '/',\n })\n }\n\n return {\n async getSession(req) {\n const { data } = await this.getSessionWithMeta(req)\n return data\n },\n\n async getSessionWithMeta(req) {\n const raw = getCookie(req, cookieName)\n if (!raw) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n const decoded = await decryptSessionWithFallback<TSession>(raw, secrets)\n if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n if (decoded.envelope.exp < Date.now()) {\n return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } }\n }\n return {\n data: decoded.envelope.data,\n meta: { secretIndex: decoded.index, needsReencrypt: decoded.index > 0 },\n }\n },\n\n async createSession(res, data) {\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookie(res, token)\n },\n\n destroySession(res) {\n deleteCookie(res, cookieName)\n },\n\n async rotateSession(req, res) {\n const { data } = await this.getSessionWithMeta(req)\n if (data === null) return null\n // Fresh IV per encrypt call (Web Crypto guarantee — see crypto.ts)\n // + refreshed expiry. Last write wins for the cookie value.\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookie(res, token)\n return data\n },\n }\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — Web-Standards session manager.\n *\n * Mirror of `SessionManager<TSession>` for the Web `Request` + `Headers`\n * shape. Same `SessionConfig`, same encryption (`encrypt`/`decrypt` from\n * `./crypto.js`), same dual-key rotation logic (CR-002 constant-time\n * decrypt fallback walk + transparent re-encrypt), same OWASP A07\n * `rotateSession` invariant.\n *\n * **Signature differences vs `SessionManager`:**\n * - Read methods take `Request` (instead of `IncomingMessage`).\n * - Write methods take a `Headers` instance to mutate (instead of\n * `ServerResponse`) — caller passes the headers they're building\n * for their `Response` constructor.\n *\n * Uses `getCookieFromRequest` / `appendCookieToHeaders` /\n * `appendDeleteCookieToHeaders` from Phase B slice 6/6.\n */\nexport interface SessionManagerWeb<TSession> {\n getSession(request: Request): Promise<TSession | null>\n getSessionWithMeta(request: Request): Promise<{ data: TSession | null; meta: SessionMeta }>\n createSession(target: Headers, data: TSession): Promise<void>\n destroySession(target: Headers): void\n rotateSession(request: Request, target: Headers): Promise<TSession | null>\n}\n\nexport function createSessionManagerWeb<TSession>(\n config: SessionConfig,\n): SessionManagerWeb<TSession> {\n const secrets = normalizeSecrets(config.secret)\n const cookieName = config.cookieName ?? 'theo_session'\n const maxAge = config.maxAge ?? 604800 // 7 days\n\n function writeCookieWeb(target: Headers, token: string): void {\n appendCookieToHeaders(target, cookieName, token, {\n httpOnly: true,\n sameSite: 'lax',\n maxAge,\n path: '/',\n })\n }\n\n return {\n async getSession(request) {\n const { data } = await this.getSessionWithMeta(request)\n return data\n },\n\n async getSessionWithMeta(request) {\n const raw = getCookieFromRequest(request, cookieName)\n if (raw === undefined) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n const decoded = await decryptSessionWithFallback<TSession>(raw, secrets)\n if (!decoded) return { data: null, meta: { secretIndex: -1, needsReencrypt: false } }\n if (decoded.envelope.exp < Date.now()) {\n return { data: null, meta: { secretIndex: decoded.index, needsReencrypt: false } }\n }\n return {\n data: decoded.envelope.data,\n meta: { secretIndex: decoded.index, needsReencrypt: decoded.index > 0 },\n }\n },\n\n async createSession(target, data) {\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookieWeb(target, token)\n },\n\n destroySession(target) {\n appendDeleteCookieToHeaders(target, cookieName)\n },\n\n async rotateSession(request, target) {\n const { data } = await this.getSessionWithMeta(request)\n if (data === null) return null\n const token = await encryptSessionEnvelope(data, secrets, maxAge)\n writeCookieWeb(target, token)\n return data\n },\n }\n}\n\n/**\n * T5a.2 Phase D slice 3/3 — Web-Standards transparent re-encrypt helper.\n *\n * Mirror of `rotateIfNeeded(sm, req, res)` for the Web Request + Headers\n * shape. Reads session via `getSessionWithMeta`; if the cookie was\n * decrypted with a legacy secret (index > 0), re-issues the cookie with\n * the newest secret via `createSession(target, data)`.\n *\n * **EC-4 honest framing:** unlike the IncomingMessage path where the\n * timing constraint is \"before `res.writeHead` fires\", the Web Request\n * path's constraint is \"before the `Response` object is constructed and\n * returned to the runtime\". Caller MUST invoke `rotateIfNeededWeb`\n * BEFORE building the final `Response(body, { headers: target })` so the\n * re-encrypted Set-Cookie lands on the wire.\n */\nexport async function rotateIfNeededWeb<TSession>(\n sm: SessionManagerWeb<TSession>,\n request: Request,\n target: Headers,\n): Promise<TSession | null> {\n const { data, meta } = await sm.getSessionWithMeta(request)\n if (data !== null && meta.needsReencrypt) {\n await sm.createSession(target, data)\n }\n return data\n}\n\n/**\n * T3.2 — Transparent re-encrypt helper (EC-4 timing-safe wrapper).\n *\n * Call this in your `createContext` (before any rendering / streaming\n * starts). It:\n * 1. Reads the session via `getSessionWithMeta`.\n * 2. If the cookie was decrypted with a legacy secret (index > 0), it\n * immediately re-issues the cookie with `secrets[0]` so the next\n * request lands on the newest key.\n * 3. Returns the session data (or null) for use downstream.\n *\n * EC-4: this MUST run before `renderToPipeableStream` / `res.writeHead`\n * fires. Calling it inside an SSR component or handler body that has\n * already streamed bytes makes the re-encrypt a silent no-op (Set-Cookie\n * is locked once headers commit), trapping users on the legacy secret\n * forever once it's removed from the array.\n */\nexport async function rotateIfNeeded<TSession>(\n sm: SessionManager<TSession>,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<TSession | null> {\n const { data, meta } = await sm.getSessionWithMeta(req)\n if (data !== null && meta.needsReencrypt) {\n await sm.createSession(res, data)\n }\n return data\n}\n\n/**\n * EC-2 — Production secret guard. Refuses to boot when `NODE_ENV === 'production'`\n * AND the secret is too short (< 32 chars) OR matches a known placeholder.\n *\n * Accepts a single secret OR an array. In array form, each entry is validated\n * independently — a single bad entry refuses boot with an index-qualified message.\n */\nconst PLACEHOLDER_PATTERN = /CHANGE_ME|demo[-_]|placeholder/i\n\nexport function assertProductionSecret(secret: string | string[]): void {\n const arr = Array.isArray(secret) ? secret : [secret]\n const isProd = process.env.NODE_ENV === 'production'\n\n for (let i = 0; i < arr.length; i++) {\n const s = arr[i]\n const isPlaceholder = PLACEHOLDER_PATTERN.test(s)\n const isTooShort = s.length < 32\n const prefix = arr.length > 1 ? `Session secret at index ${i}` : 'Session secret'\n\n if (isProd) {\n if (isTooShort) {\n throw new Error(\n `${prefix} too short for production (${s.length} chars; minimum 32). ` +\n `Set a 32+ random char secret in your env (e.g., \\`openssl rand -hex 32\\`).`,\n )\n }\n if (isPlaceholder) {\n throw new Error(\n `${prefix} looks like a placeholder (\"${s.slice(0, 16)}…\") and NODE_ENV is \"production\". ` +\n `Replace it with a 32+ random char secret (e.g., \\`openssl rand -hex 32\\`) before deploying.`,\n )\n }\n continue\n }\n if (isPlaceholder || isTooShort) {\n console.warn(\n `[theokit] WARNING: ${prefix.toLowerCase()} is a placeholder or too short. ` +\n `This is OK for dev, but the production server will REFUSE to boot until you replace it.`,\n )\n }\n }\n}\n","/**\n * Shared encoding utilities for crypto-adjacent code.\n *\n * CR-020 DRY: `base64urlEncode` was duplicated across `oauth-pkce.ts` and\n * `oauth-state.ts`. Duplication in cryptographic helpers is a bug class —\n * a future RFC-driven tweak (e.g., padding semantics) would diverge across\n * files. Centralizing the single canonical implementation eliminates that\n * vector.\n *\n * Module is intentionally under `_internal/` so it is **not** part of the\n * public API surface. Consumers outside `packages/theo/src/server` must\n * not import from here.\n */\n\n/**\n * RFC 4648 §5 — base64url-encode bytes without padding. Used wherever an\n * RFC requires URL-safe base64 (PKCE per RFC 7636 §4.1, JWT per RFC 7515,\n * etc.). Pure function — does not mutate `bytes`.\n */\nexport function base64urlEncode(bytes: Uint8Array): string {\n let s = ''\n for (const b of bytes) s += String.fromCharCode(b)\n // The `=+$` trailing-padding regex is bounded (at most 2 chars for\n // base64). sonarjs flags the `+` quantifier conservatively, but the\n // input is the output of `btoa()` — a string with at most 2 trailing\n // `=` characters by spec.\n // eslint-disable-next-line sonarjs/slow-regex -- bounded trailing-padding pattern (max 2 chars)\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n/**\n * Constant-time string equality.\n *\n * CR-012: prior implementations early-exited on length mismatch\n * (`if (a.length !== b.length) return false`). That early exit leaks\n * the comparison-target length via wall-clock timing — observable when\n * the secret length is itself sensitive (length-based bucket assignment,\n * variable-length nonces). This implementation always iterates\n * `max(a.length, b.length)` characters, OR-folding mismatches into a\n * single accumulator that is checked once at the end.\n *\n * Returns `false` for empty inputs as a defensive default — never accept\n * \"both empty\" as a match.\n */\nexport function constantTimeEquals(a: string, b: string): boolean {\n if (typeof a !== 'string' || typeof b !== 'string') return false\n if (a.length === 0 || b.length === 0) return false\n\n const maxLen = Math.max(a.length, b.length)\n let diff = a.length ^ b.length\n for (let i = 0; i < maxLen; i++) {\n // charCodeAt returns NaN for out-of-range indices; (NaN ^ x) is x, so we\n // explicitly substitute 0 to keep the OR-fold meaningful.\n const ca = i < a.length ? a.charCodeAt(i) : 0\n const cb = i < b.length ? b.charCodeAt(i) : 0\n diff |= ca ^ cb\n }\n return diff === 0\n}\n","/**\n * T6.2 — RFC 6238 TOTP primitive.\n *\n * Reference: https://datatracker.ietf.org/doc/html/rfc6238\n * HMAC-based one-time password (HOTP) base: https://datatracker.ietf.org/doc/html/rfc4226\n *\n * Pure-function, dependency-free implementation via Web Crypto. Secrets\n * may be passed as Uint8Array (preferred) or base32 string.\n *\n * SECURITY NOTES (documented per EC-13):\n * - TOTP secrets are equivalent to passwords. Encrypt at rest using a\n * separate KMS key from the session secret. If your DB leaks, ALL\n * 2FA codes are compromised — rotate by forcing all users to re-enroll.\n * - Use `verifyTotp` constant-time (we use crypto.timingSafeEqual internally).\n */\n\n// CR-012 fix: use the shared constant-time helper that does not early-exit\n// on length mismatch (the previous local version did, leaking the digit\n// count via timing).\nimport { constantTimeEquals } from '../_internal/encoding.js'\n\n/** Supported HMAC algorithms per RFC 6238 §1.2. Default SHA-1. */\nexport type TotpAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-512'\n\nexport interface TotpOptions {\n /** Shared secret — bytes (preferred) OR base32 string. */\n secret: Uint8Array | string\n /** Time step in seconds. Default 30 (RFC 6238 §5.2 recommended). */\n step?: number\n /** Code length: 6, 7, or 8 digits. Default 6. */\n digits?: 6 | 7 | 8\n /** HMAC algorithm. Default SHA-1 (RFC default). */\n algorithm?: TotpAlgorithm\n /** Time in ms since epoch. Default Date.now(). */\n time?: number\n}\n\nexport interface VerifyTotpOptions extends TotpOptions {\n /**\n * Drift tolerance in number of steps on each side. Default 1\n * (RFC 6238 §5.2 recommends ±1 step = 90s total window).\n */\n window?: number\n}\n\nconst DEFAULT_STEP = 30\nconst DEFAULT_DIGITS = 6\nconst DEFAULT_ALGORITHM: TotpAlgorithm = 'SHA-1'\n\n/** Decode base32 (RFC 4648, no padding) to bytes. Throws on invalid input. */\nfunction base32Decode(s: string): Uint8Array {\n const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\n // Trailing `=` is bounded by spec to 0..6 chars; `\\s+` collapses runs\n // of whitespace. Input length is the user's TOTP secret (10..50 chars\n // typical), so super-linear backtracking cannot escalate.\n // eslint-disable-next-line sonarjs/slow-regex -- bounded inputs (TOTP secrets are short)\n const clean = s.toUpperCase().replace(/=+$/, '').replace(/\\s+/g, '')\n if (!/^[A-Z2-7]+$/.test(clean)) {\n throw new Error('Invalid base32 secret')\n }\n const bits: number[] = []\n for (const c of clean) {\n const idx = alphabet.indexOf(c)\n for (let i = 4; i >= 0; i--) bits.push((idx >> i) & 1)\n }\n // Trim trailing partial byte\n const fullBytes = Math.floor(bits.length / 8)\n const out = new Uint8Array(fullBytes)\n for (let i = 0; i < fullBytes; i++) {\n let v = 0\n for (let j = 0; j < 8; j++) v = (v << 1) | bits[i * 8 + j]\n out[i] = v\n }\n return out\n}\n\n/** Encode bytes to base32 (RFC 4648, no padding). */\nfunction base32Encode(bytes: Uint8Array): string {\n const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'\n let out = ''\n let buffer = 0\n let bits = 0\n for (const b of bytes) {\n buffer = (buffer << 8) | b\n bits += 8\n while (bits >= 5) {\n out += alphabet[(buffer >> (bits - 5)) & 0x1f]\n bits -= 5\n }\n }\n if (bits > 0) out += alphabet[(buffer << (5 - bits)) & 0x1f]\n return out\n}\n\nfunction normalizeSecret(secret: Uint8Array | string): Uint8Array {\n if (typeof secret === 'string') return base32Decode(secret)\n return secret\n}\n\n/** Counter (8-byte big-endian) from time in ms / step in seconds. */\nfunction counterBytes(timeMs: number, stepSec: number): Uint8Array {\n const counter = Math.floor(timeMs / 1000 / stepSec)\n const out = new Uint8Array(8)\n // Two 32-bit halves (Number can safely represent up to 2^53; OK until year 285,000+)\n const hi = Math.floor(counter / 0x100000000)\n const lo = counter >>> 0\n out[0] = (hi >>> 24) & 0xff\n out[1] = (hi >>> 16) & 0xff\n out[2] = (hi >>> 8) & 0xff\n out[3] = hi & 0xff\n out[4] = (lo >>> 24) & 0xff\n out[5] = (lo >>> 16) & 0xff\n out[6] = (lo >>> 8) & 0xff\n out[7] = lo & 0xff\n return out\n}\n\nasync function hmac(\n secret: Uint8Array,\n message: Uint8Array,\n algorithm: TotpAlgorithm,\n): Promise<Uint8Array> {\n const key = await crypto.subtle.importKey(\n 'raw',\n secret as BufferSource,\n { name: 'HMAC', hash: algorithm },\n false,\n ['sign'],\n )\n const sig = await crypto.subtle.sign('HMAC', key, message as BufferSource)\n return new Uint8Array(sig)\n}\n\n/** RFC 4226 §5.3 dynamic truncation. */\nfunction truncate(mac: Uint8Array, digits: number): string {\n const offset = mac[mac.length - 1] & 0x0f\n const code =\n ((mac[offset] & 0x7f) << 24) |\n ((mac[offset + 1] & 0xff) << 16) |\n ((mac[offset + 2] & 0xff) << 8) |\n (mac[offset + 3] & 0xff)\n const mod = 10 ** digits\n return String(code % mod).padStart(digits, '0')\n}\n\n/** Generate a TOTP code for a given time. */\nexport async function generateTotp(opts: TotpOptions): Promise<string> {\n const secret = normalizeSecret(opts.secret)\n const step = opts.step ?? DEFAULT_STEP\n const digits = opts.digits ?? DEFAULT_DIGITS\n const algorithm = opts.algorithm ?? DEFAULT_ALGORITHM\n const time = opts.time ?? Date.now()\n const mac = await hmac(secret, counterBytes(time, step), algorithm)\n return truncate(mac, digits)\n}\n\n/**\n * Verify a TOTP code against the current window ± `window` steps.\n *\n * Returns `false` for malformed tokens (non-digit, wrong length) without\n * throwing — defensive against accidental user-input passthrough.\n */\nexport async function verifyTotp(token: string, opts: VerifyTotpOptions): Promise<boolean> {\n if (typeof token !== 'string') return false\n const digits = opts.digits ?? DEFAULT_DIGITS\n if (!/^\\d+$/.test(token) || token.length !== digits) return false\n\n const step = opts.step ?? DEFAULT_STEP\n const algorithm = opts.algorithm ?? DEFAULT_ALGORITHM\n const time = opts.time ?? Date.now()\n const drift = opts.window ?? 1\n const secret = normalizeSecret(opts.secret)\n\n // Walk the whole drift range constant-time-ish (no early exit).\n let matched = false\n for (let delta = -drift; delta <= drift; delta++) {\n const t = time + delta * step * 1000\n const code = truncate(await hmac(secret, counterBytes(t, step), algorithm), digits)\n if (constantTimeEquals(code, token)) matched = true\n }\n return matched\n}\n\n/** Generate a cryptographically random TOTP secret. Default 20 bytes (RFC minimum). */\nexport function generateTotpSecret(bytes = 20): Uint8Array {\n if (bytes < 16) {\n throw new Error('TOTP secret must be at least 16 bytes (RFC 6238 §3 minimum)')\n }\n const out = new Uint8Array(bytes)\n crypto.getRandomValues(out)\n return out\n}\n\n/**\n * Build an `otpauth://` URI for QR code enrollment. Format follows the\n * de facto Google Authenticator spec adopted by every major TOTP app.\n * otpauth://totp/<issuer>:<account>?secret=<base32>&issuer=<issuer>&algorithm=<alg>&digits=<n>&period=<sec>\n */\nexport interface TotpUriOptions {\n secret: Uint8Array\n issuer: string\n account: string\n algorithm?: TotpAlgorithm\n digits?: 6 | 7 | 8\n step?: number\n}\n\nexport function totpUri(opts: TotpUriOptions): string {\n const label = `${opts.issuer}:${opts.account}`\n const params = new URLSearchParams({\n secret: base32Encode(opts.secret),\n issuer: opts.issuer,\n algorithm: opts.algorithm ?? DEFAULT_ALGORITHM,\n digits: String(opts.digits ?? DEFAULT_DIGITS),\n period: String(opts.step ?? DEFAULT_STEP),\n })\n return `otpauth://totp/${encodeURIComponent(label)}?${params.toString()}`\n}\n","/**\n * T6.3 — Backup codes for 2FA recovery.\n *\n * generateBackupCodes returns N plaintexts (display once to user) + hashes\n * (store in DB). verifyBackupCode constant-time-walks all hashes and\n * returns the matchedHash so caller can delete the used code from storage\n * — replay protection lives at the caller (we can't see their database).\n *\n * SECURITY NOTES:\n * - Codes have ~40 bits entropy (8 chars × 5 bits per char from a\n * 32-char alphabet excluding I/L/O/0/1). Single-use → argon2id\n * overhead is unnecessary; SHA-256 of the normalized code suffices.\n * - The caller MUST atomically delete the matched hash on successful\n * verify. Without that, the code is replayable. We surface\n * matchedHash explicitly so this step is conspicuous.\n * - Pair this with throttleLoginAttempts (T6.1) on the verify endpoint\n * to prevent online brute-force against the 40-bit space.\n */\n\nexport interface BackupCodeOptions {\n /** How many codes to generate. Default 10. */\n count?: number\n /** Length in chars (excluding separator). Default 8. */\n length?: number\n /** Separator inserted at the midpoint. Default '-'; pass null to omit. */\n separator?: '-' | null\n /** Custom alphabet. Default excludes ambiguous chars (I, L, O, 0, 1). */\n alphabet?: string\n}\n\nexport interface BackupCode {\n plaintext: string\n hash: string\n}\n\nconst DEFAULT_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' // no I, O, 0, 1\nconst DEFAULT_COUNT = 10\nconst DEFAULT_LENGTH = 8\n\n/**\n * Generate cryptographically random backup codes + their SHA-256 hashes.\n *\n * Plaintext is uppercase alphanumeric (default alphabet excludes ambiguous\n * chars). Separator inserted at the midpoint (default `-`).\n */\nexport async function generateBackupCodes(opts: BackupCodeOptions = {}): Promise<BackupCode[]> {\n const count = opts.count ?? DEFAULT_COUNT\n const length = opts.length ?? DEFAULT_LENGTH\n const separator = opts.separator === undefined ? '-' : opts.separator\n const alphabet = opts.alphabet ?? DEFAULT_ALPHABET\n\n // CR-022 fix: bound count and length explicitly. Pathological inputs\n // (count=Infinity, length=0) would cause an infinite loop or wasteful\n // resource usage. Bounds are generous (count ≤ 1000, length ∈ [4, 64])\n // — well above any realistic 2FA backup-codes use case.\n if (!Number.isInteger(count) || count <= 0 || count > 1000) {\n throw new RangeError(\n `generateBackupCodes: count must be an integer in [1, 1000] (got ${String(count)})`,\n )\n }\n if (!Number.isInteger(length) || length < 4 || length > 64) {\n throw new RangeError(\n `generateBackupCodes: length must be an integer in [4, 64] (got ${String(length)})`,\n )\n }\n if (alphabet.length < 8) {\n throw new RangeError(\n `generateBackupCodes: alphabet must contain at least 8 distinct chars (got ${alphabet.length})`,\n )\n }\n\n const codes: BackupCode[] = []\n const seen = new Set<string>()\n while (codes.length < count) {\n const raw = randomFromAlphabet(length, alphabet)\n if (seen.has(raw)) continue\n seen.add(raw)\n const plaintext = formatWithSeparator(raw, separator)\n const hash = await sha256Hex(normalizeCode(plaintext))\n codes.push({ plaintext, hash })\n }\n return codes\n}\n\n/**\n * Verify a code against a list of stored hashes. Walks all hashes\n * constant-time (no early exit). Returns `valid:true` + matchedHash on\n * hit; `valid:false` otherwise.\n *\n * `matchedHash` is the EXACT string the caller stored, ready to be passed\n * to a `DELETE FROM backup_codes WHERE hash = ?` query.\n */\nexport async function verifyBackupCode(\n code: string,\n hashes: readonly string[],\n): Promise<{ valid: boolean; matchedHash?: string }> {\n const candidate = await sha256Hex(normalizeCode(code))\n let matchedHash: string | undefined\n // Constant-time iteration over hashes: never short-circuit.\n for (const h of hashes) {\n if (constantTimeEquals(h, candidate)) {\n matchedHash = h\n }\n }\n return matchedHash ? { valid: true, matchedHash } : { valid: false }\n}\n\n/**\n * Normalize a code for hashing: uppercase + strip separator. This way\n * `abcd-efgh` and `ABCDEFGH` produce the same hash. Whitespace is also\n * stripped defensively.\n */\nfunction normalizeCode(input: string): string {\n return input.toUpperCase().replace(/[-\\s]+/g, '')\n}\n\nfunction formatWithSeparator(raw: string, separator: '-' | null): string {\n if (!separator || raw.length < 4) return raw\n const mid = Math.floor(raw.length / 2)\n return raw.slice(0, mid) + separator + raw.slice(mid)\n}\n\nfunction randomFromAlphabet(length: number, alphabet: string): string {\n const random = new Uint8Array(length)\n crypto.getRandomValues(random)\n let out = ''\n for (let i = 0; i < length; i++) {\n out += alphabet[random[i] % alphabet.length]\n }\n return out\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const bytes = new TextEncoder().encode(input)\n const hash = await crypto.subtle.digest('SHA-256', bytes)\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\nfunction constantTimeEquals(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i)\n return diff === 0\n}\n","import type { RateLimitStore } from '../rate-limit/rate-limit-store.js'\n\n/**\n * T6.1 — Login throttling primitive.\n *\n * Per-credential failure counter backed by any `RateLimitStore`. After\n * `maxAttempts` failures within `windowMs`, the identifier is locked\n * for `lockoutMs`. Successful login resets the counter.\n *\n * Usage in a login handler:\n *\n * const state = await checkThrottle({ store, identifier: hash(email) })\n * if (!state.allowed) return 429\n * const valid = await verifyPassword(creds)\n * await recordAttempt({ store, identifier: hash(email) }, valid)\n * if (!valid) return 401\n *\n * SECURITY:\n * - NEVER pass raw email/username as `identifier`. Hash or normalize\n * first (e.g. SHA-256 of the lowercased email). This prevents PII\n * from landing in the rate-limit store or audit logs.\n * - Combine with the per-route IP rate limit (T2.2) for layered defense:\n * IP-level limits stop scripted floods; credential-level limits stop\n * distributed brute force.\n */\n\nexport interface ThrottleOptions {\n store: RateLimitStore\n /**\n * Stable identifier per credential. Hash raw emails/usernames before\n * passing in — never leak PII to the rate-limit key space.\n */\n identifier: string\n /** Failures before lockout. Default 5. */\n maxAttempts?: number\n /** Sliding window for counting failures. Default 15 minutes. */\n windowMs?: number\n /** Lockout duration after maxAttempts hit. Default 1 hour. */\n lockoutMs?: number\n}\n\nexport interface ThrottleState {\n allowed: boolean\n remainingAttempts: number\n /** Set when `allowed === false`. Absolute timestamp when the lockout expires. */\n lockedUntil?: Date\n}\n\nconst DEFAULT_MAX_ATTEMPTS = 5\nconst DEFAULT_LOCKOUT_MS = 60 * 60_000\n\n/**\n * Read the current throttle state for an identifier WITHOUT modifying\n * the counter. Call before validating credentials so locked attempts\n * are rejected fast.\n */\nexport async function checkThrottle(opts: ThrottleOptions): Promise<ThrottleState> {\n const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS\n const state = await opts.store.get(opts.identifier)\n if (!state) {\n return { allowed: true, remainingAttempts: maxAttempts }\n }\n if (state.count >= maxAttempts) {\n return {\n allowed: false,\n remainingAttempts: 0,\n // Lockout expires when the store TTL fires.\n lockedUntil: new Date(state.resetAt),\n }\n }\n return {\n allowed: true,\n remainingAttempts: Math.max(0, maxAttempts - state.count),\n }\n}\n\n/**\n * Record an attempt outcome. On success → reset the counter. On failure\n * → increment within the sliding window. Returns the new throttle state.\n *\n * Storage model: the store entry's TTL is `lockoutMs` (which doubles as\n * the failure-accumulation window). When `lockoutMs` is short — common\n * in tests or for soft lockouts — the entry auto-expires and the next\n * attempt starts fresh.\n */\nexport async function recordAttempt(\n opts: ThrottleOptions,\n success: boolean,\n): Promise<ThrottleState> {\n const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS\n if (success) {\n await opts.store.reset(opts.identifier)\n return {\n allowed: true,\n remainingAttempts: maxAttempts,\n }\n }\n const lockoutMs = opts.lockoutMs ?? DEFAULT_LOCKOUT_MS\n const state = await opts.store.incr(opts.identifier, lockoutMs)\n\n if (state.count >= maxAttempts) {\n return {\n allowed: false,\n remainingAttempts: 0,\n lockedUntil: new Date(state.resetAt),\n }\n }\n return {\n allowed: true,\n remainingAttempts: Math.max(0, maxAttempts - state.count),\n }\n}\n","/**\n * T7.3 — RFC 7636 PKCE primitive.\n *\n * Reference: https://datatracker.ietf.org/doc/html/rfc7636\n *\n * Pure-function, dependency-free implementation using Web Crypto.\n *\n * Usage in an OAuth authorization-code flow:\n *\n * const { codeVerifier, codeChallenge, codeChallengeMethod } = await generatePkceChallenge()\n * // Store codeVerifier in the user's session\n * // Send codeChallenge + codeChallengeMethod in the /authorize redirect\n * // ... user authenticates with provider ...\n * // On /callback, send codeVerifier with the code → token exchange\n */\n\nexport interface PkceChallenge {\n codeVerifier: string\n codeChallenge: string\n codeChallengeMethod: 'S256'\n}\n\n// CR-020 DRY: single canonical base64url encoder lives in _internal/encoding.\nimport { base64urlEncode } from '../_internal/encoding.js'\n\n/**\n * Compute the code_challenge from a code_verifier per RFC 7636 §4.2.\n * Exported separately to enable RFC 7636 Appendix B vector testing.\n */\nexport async function pkceChallengeFromVerifier(verifier: string): Promise<string> {\n const bytes = new TextEncoder().encode(verifier)\n const hash = await crypto.subtle.digest('SHA-256', bytes)\n return base64urlEncode(new Uint8Array(hash))\n}\n\n/**\n * Generate a fresh PKCE challenge pair.\n *\n * Verifier: 32 cryptographically random bytes → 43-char base64url string.\n * RFC 7636 §4.1 allows 43–128 chars; we ship the minimum-safe default.\n *\n * Method: 'S256' only — no 'plain' fallback. RFC 7636 §7.2 strongly\n * discourages plain and modern providers reject it.\n */\nexport async function generatePkceChallenge(): Promise<PkceChallenge> {\n const random = new Uint8Array(32)\n crypto.getRandomValues(random)\n const codeVerifier = base64urlEncode(random)\n const codeChallenge = await pkceChallengeFromVerifier(codeVerifier)\n return { codeVerifier, codeChallenge, codeChallengeMethod: 'S256' }\n}\n","/**\n * T7.4 — OAuth `state` parameter helpers (RFC 6749 §10.12).\n *\n * `state` is a cryptographically random anti-CSRF token. The client\n * stores it (in session/cookie/db) before redirecting to the\n * authorization endpoint and verifies it on the callback. Without it,\n * an attacker can authorize on the user's account using a captured code.\n */\n\n// CR-020 DRY + CR-012 constant-time: shared helpers in _internal/encoding.\nimport { base64urlEncode, constantTimeEquals } from '../_internal/encoding.js'\n\n/**\n * Generate a cryptographically random state token. Default 32 bytes\n * (~43 base64url chars). Increase for higher entropy if your provider\n * supports it.\n */\nexport function generateOAuthState(opts: { bytes?: number } = {}): string {\n const len = opts.bytes ?? 32\n const buf = new Uint8Array(len)\n crypto.getRandomValues(buf)\n return base64urlEncode(buf)\n}\n\n/**\n * Verify a state token. Constant-time compare. Empty inputs always fail\n * (defensive — never accept empty as \"they match\").\n *\n * CR-012 fix: `constantTimeEquals` does not early-exit on length mismatch,\n * so the comparison time is independent of how the input was forged.\n */\nexport function verifyOAuthState(provided: string, stored: string): boolean {\n return constantTimeEquals(provided, stored)\n}\n","/**\n * T7.4 — OpenID Connect Discovery 1.0 metadata fetcher.\n *\n * Reference: https://openid.net/specs/openid-connect-discovery-1_0.html\n *\n * Caches metadata in module scope. Cache key is the exact issuer string\n * (trailing-slash sensitive per RFC 8414 §3). Failures are NOT cached —\n * subsequent calls retry the fetch.\n *\n * EC-7 — HTTPS enforced. RFC 8414 §3 requires HTTPS for OIDC issuers.\n * Localhost (loopback) is allowed for development.\n */\n\nexport interface OidcMetadata {\n issuer: string\n authorization_endpoint: string\n token_endpoint: string\n jwks_uri?: string\n userinfo_endpoint?: string\n end_session_endpoint?: string\n scopes_supported?: string[]\n response_types_supported?: string[]\n grant_types_supported?: string[]\n id_token_signing_alg_values_supported?: string[]\n [key: string]: unknown // OIDC providers ship many optional fields\n}\n\nconst cache = new Map<string, Promise<OidcMetadata>>()\n\n/** Clear the discovery cache. Used by tests. */\nexport function clearOidcCache(): void {\n cache.clear()\n}\n\nfunction isLoopback(host: string): boolean {\n return host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host === '::1'\n}\n\n/**\n * Fetch (or return cached) OIDC provider metadata.\n *\n * Throws when:\n * - Issuer URL is malformed\n * - Issuer is HTTP and not a loopback host (EC-7)\n * - HTTP response is non-OK\n * - Metadata lacks `authorization_endpoint` (sanity check)\n */\nexport async function discoverOidcProvider(issuer: string | URL): Promise<OidcMetadata> {\n const issuerStr = typeof issuer === 'string' ? issuer : issuer.toString()\n // Validate protocol BEFORE checking cache — invalid issuers should never poison.\n const url = new URL(issuerStr)\n if (url.protocol !== 'https:' && !isLoopback(url.hostname)) {\n throw new Error(\n `OIDC issuer must use HTTPS (received \"${url.protocol}//${url.hostname}…\"). ` +\n `RFC 8414 §3 forbids plain HTTP except for loopback hosts.`,\n )\n }\n\n const cached = cache.get(issuerStr)\n if (cached) return cached\n\n const promise = (async () => {\n const wellKnown = new URL(\n '.well-known/openid-configuration',\n issuerStr.endsWith('/') ? issuerStr : issuerStr + '/',\n )\n const res = await fetch(wellKnown)\n if (!res.ok) {\n throw new Error(`OIDC discovery failed: ${res.status} ${res.statusText} at ${wellKnown}`)\n }\n const metadata = (await res.json()) as OidcMetadata\n if (!metadata.authorization_endpoint) {\n throw new Error('OIDC metadata missing authorization_endpoint — provider response is invalid')\n }\n return metadata\n })()\n // Cache the promise; on rejection, remove so the next call retries.\n cache.set(issuerStr, promise)\n promise.catch(() => {\n if (cache.get(issuerStr) === promise) cache.delete(issuerStr)\n })\n return promise\n}\n"],"mappings":"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","names":["constantTimeEquals"]}
|