@jeffusion/bungee-core 5.26.0 → 6.0.1

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.
Files changed (71) hide show
  1. package/dist/config-storage-worker.js +376 -223
  2. package/dist/main.js +799 -437
  3. package/dist/observability-worker.js +0 -10
  4. package/dist/plugins/anthropic-request-sanitizer/index.js.map +2 -2
  5. package/dist/plugins/anthropic-request-sanitizer/manifest.json +1 -1
  6. package/dist/plugins/anthropic-tool-name-transformer/index.js.map +2 -2
  7. package/dist/plugins/anthropic-tool-name-transformer/manifest.json +1 -1
  8. package/dist/plugins/chatgpt-oauth/control.js.map +1 -1
  9. package/dist/plugins/chatgpt-oauth/index.js.map +2 -2
  10. package/dist/plugins/chatgpt-oauth/manifest.json +1 -1
  11. package/dist/plugins/codex-router/control.js.map +1 -1
  12. package/dist/plugins/codex-router/index.js +2 -2
  13. package/dist/plugins/codex-router/index.js.map +3 -3
  14. package/dist/plugins/codex-router/manifest.json +1 -1
  15. package/dist/plugins/deepseek-reasoning-fix/index.js.map +2 -2
  16. package/dist/plugins/deepseek-reasoning-fix/manifest.json +1 -1
  17. package/dist/plugins/key-access/control.js.map +1 -1
  18. package/dist/plugins/key-access/index.js.map +2 -2
  19. package/dist/plugins/key-access/ingress.js.map +1 -1
  20. package/dist/plugins/key-access/manifest.json +1 -1
  21. package/dist/plugins/key-rate-limit/index.js.map +2 -2
  22. package/dist/plugins/key-rate-limit/manifest.json +1 -1
  23. package/dist/plugins/llm-protocol-adapter/control.js +2 -2
  24. package/dist/plugins/llm-protocol-adapter/control.js.map +4 -4
  25. package/dist/plugins/llm-protocol-adapter/index.js +2 -2
  26. package/dist/plugins/llm-protocol-adapter/index.js.map +3 -3
  27. package/dist/plugins/llm-protocol-adapter/manifest.json +1 -1
  28. package/dist/plugins/model-mapping/index.js.map +2 -2
  29. package/dist/plugins/model-mapping/manifest.json +1 -1
  30. package/dist/plugins/models-dev/index.js.map +2 -2
  31. package/dist/plugins/models-dev/manifest.json +1 -1
  32. package/dist/plugins/signature-repair/index.js.map +2 -2
  33. package/dist/plugins/signature-repair/manifest.json +1 -1
  34. package/dist/plugins/token-budget/control.js.map +1 -1
  35. package/dist/plugins/token-budget/index.js.map +2 -2
  36. package/dist/plugins/token-budget/manifest.json +1 -1
  37. package/dist/plugins/token-metering/index.js.map +2 -2
  38. package/dist/plugins/token-metering/manifest.json +1 -1
  39. package/dist/plugins/token-stats/control.js.map +1 -1
  40. package/dist/plugins/token-stats/index.js.map +2 -2
  41. package/dist/plugins/token-stats/manifest.json +1 -1
  42. package/dist/plugins/user-auth/control.js +3911 -0
  43. package/dist/plugins/{local-accounts → user-auth}/control.js.map +8 -5
  44. package/dist/plugins/{local-accounts → user-auth}/index.js +6 -6
  45. package/dist/plugins/{local-accounts → user-auth}/index.js.map +6 -6
  46. package/dist/plugins/user-auth/manifest.json +735 -0
  47. package/dist/plugins/user-auth/ui/ConnectionEditor.svelte +137 -0
  48. package/dist/plugins/user-auth/ui/Login.svelte +87 -0
  49. package/dist/plugins/user-auth/ui/LoginProviderIcon.svelte +19 -0
  50. package/dist/plugins/user-auth/ui/LoginSettings.svelte +141 -0
  51. package/dist/plugins/user-auth/ui/Profile.svelte +185 -0
  52. package/dist/plugins/{local-accounts → user-auth}/ui/RecoveryHelp.svelte +3 -3
  53. package/dist/plugins/user-auth/ui/SessionPolicy.svelte +104 -0
  54. package/dist/plugins/user-auth/ui/Settings.svelte +14 -0
  55. package/dist/plugins/user-auth/ui/SettingsSection.svelte +32 -0
  56. package/dist/plugins/user-auth/ui/Setup.svelte +38 -0
  57. package/dist/plugins/user-auth/ui/TurnstileChallenge.svelte +26 -0
  58. package/dist/plugins/user-auth/ui/TurnstileSettings.svelte +81 -0
  59. package/dist/plugins/user-auth/ui/Users.svelte +95 -0
  60. package/dist/plugins/user-auth/ui/assets/LICENSE.txt +30 -0
  61. package/dist/plugins/user-auth/ui/assets/github.svg +1 -0
  62. package/dist/plugins/user-auth/ui/assets/google.svg +1 -0
  63. package/dist/plugins/user-auth/ui/assets/microsoft.svg +1 -0
  64. package/dist/plugins/user-auth/ui/contracts.ts +16 -0
  65. package/dist/plugins/{local-accounts → user-auth}/ui/login-errors.ts +4 -1
  66. package/dist/plugins/user-auth/ui/turnstile.ts +21 -0
  67. package/package.json +2 -2
  68. package/dist/plugins/local-accounts/control.js +0 -715
  69. package/dist/plugins/local-accounts/manifest.json +0 -223
  70. package/dist/plugins/local-accounts/ui/Login.svelte +0 -47
  71. package/dist/plugins/local-accounts/ui/Settings.svelte +0 -117
@@ -0,0 +1,3911 @@
1
+ // @bun
2
+ // node_modules/oauth4webapi/build/index.js
3
+ var USER_AGENT;
4
+ if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) {
5
+ const NAME = "oauth4webapi";
6
+ const VERSION = "v3.8.8";
7
+ USER_AGENT = `${NAME}/${VERSION}`;
8
+ }
9
+ function looseInstanceOf(input, expected) {
10
+ if (input == null) {
11
+ return false;
12
+ }
13
+ try {
14
+ return input instanceof expected || Object.getPrototypeOf(input)[Symbol.toStringTag] === expected.prototype[Symbol.toStringTag];
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+ var ERR_INVALID_ARG_VALUE = "ERR_INVALID_ARG_VALUE";
20
+ var ERR_INVALID_ARG_TYPE = "ERR_INVALID_ARG_TYPE";
21
+ function CodedTypeError(message, code, cause) {
22
+ const err = new TypeError(message, { cause });
23
+ Object.assign(err, { code });
24
+ return err;
25
+ }
26
+ var allowInsecureRequests = Symbol();
27
+ var clockSkew = Symbol();
28
+ var clockTolerance = Symbol();
29
+ var customFetch = Symbol();
30
+ var modifyAssertion = Symbol();
31
+ var jweDecrypt = Symbol();
32
+ var jwksCache = Symbol();
33
+ var encoder = new TextEncoder;
34
+ var decoder = new TextDecoder;
35
+ function buf(input) {
36
+ if (typeof input === "string") {
37
+ return encoder.encode(input);
38
+ }
39
+ return decoder.decode(input);
40
+ }
41
+ var encodeBase64Url;
42
+ if (Uint8Array.prototype.toBase64) {
43
+ encodeBase64Url = (input) => {
44
+ if (input instanceof ArrayBuffer) {
45
+ input = new Uint8Array(input);
46
+ }
47
+ return input.toBase64({ alphabet: "base64url", omitPadding: true });
48
+ };
49
+ } else {
50
+ const CHUNK_SIZE = 32768;
51
+ encodeBase64Url = (input) => {
52
+ if (input instanceof ArrayBuffer) {
53
+ input = new Uint8Array(input);
54
+ }
55
+ const arr = [];
56
+ for (let i = 0;i < input.byteLength; i += CHUNK_SIZE) {
57
+ arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
58
+ }
59
+ return btoa(arr.join("")).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
60
+ };
61
+ }
62
+ var decodeBase64Url;
63
+ if (Uint8Array.fromBase64) {
64
+ decodeBase64Url = (input) => {
65
+ try {
66
+ return Uint8Array.fromBase64(input, { alphabet: "base64url" });
67
+ } catch (cause) {
68
+ throw CodedTypeError("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE, cause);
69
+ }
70
+ };
71
+ } else {
72
+ decodeBase64Url = (input) => {
73
+ try {
74
+ const binary = atob(input.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, ""));
75
+ const bytes = new Uint8Array(binary.length);
76
+ for (let i = 0;i < binary.length; i++) {
77
+ bytes[i] = binary.charCodeAt(i);
78
+ }
79
+ return bytes;
80
+ } catch (cause) {
81
+ throw CodedTypeError("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE, cause);
82
+ }
83
+ };
84
+ }
85
+ function b64u(input) {
86
+ if (typeof input === "string") {
87
+ return decodeBase64Url(input);
88
+ }
89
+ return encodeBase64Url(input);
90
+ }
91
+
92
+ class UnsupportedOperationError extends Error {
93
+ code;
94
+ constructor(message, options) {
95
+ super(message, options);
96
+ this.name = this.constructor.name;
97
+ this.code = UNSUPPORTED_OPERATION;
98
+ Error.captureStackTrace?.(this, this.constructor);
99
+ }
100
+ }
101
+
102
+ class OperationProcessingError extends Error {
103
+ code;
104
+ constructor(message, options) {
105
+ super(message, options);
106
+ this.name = this.constructor.name;
107
+ if (options?.code) {
108
+ this.code = options?.code;
109
+ }
110
+ Error.captureStackTrace?.(this, this.constructor);
111
+ }
112
+ }
113
+ function OPE(message, code, cause) {
114
+ return new OperationProcessingError(message, { code, cause });
115
+ }
116
+ function isJsonObject(input) {
117
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
118
+ return false;
119
+ }
120
+ return true;
121
+ }
122
+ function prepareHeaders(input) {
123
+ if (looseInstanceOf(input, Headers)) {
124
+ input = Object.fromEntries(input.entries());
125
+ }
126
+ const headers = new Headers(input ?? {});
127
+ if (USER_AGENT && !headers.has("user-agent")) {
128
+ headers.set("user-agent", USER_AGENT);
129
+ }
130
+ if (headers.has("authorization")) {
131
+ throw CodedTypeError('"options.headers" must not include the "authorization" header name', ERR_INVALID_ARG_VALUE);
132
+ }
133
+ return headers;
134
+ }
135
+ function signal(url, value) {
136
+ if (value !== undefined) {
137
+ if (typeof value === "function") {
138
+ value = value(url.href);
139
+ }
140
+ if (!(value instanceof AbortSignal)) {
141
+ throw CodedTypeError('"options.signal" must return or be an instance of AbortSignal', ERR_INVALID_ARG_TYPE);
142
+ }
143
+ return value;
144
+ }
145
+ return;
146
+ }
147
+ function replaceDoubleSlash(pathname) {
148
+ if (pathname.includes("//")) {
149
+ return pathname.replace("//", "/");
150
+ }
151
+ return pathname;
152
+ }
153
+ function prependWellKnown(url, wellKnown, allowTerminatingSlash = false) {
154
+ if (url.pathname === "/") {
155
+ url.pathname = wellKnown;
156
+ } else {
157
+ url.pathname = replaceDoubleSlash(`${wellKnown}/${allowTerminatingSlash ? url.pathname : url.pathname.replace(/(\/)$/, "")}`);
158
+ }
159
+ return url;
160
+ }
161
+ function appendWellKnown(url, wellKnown) {
162
+ url.pathname = replaceDoubleSlash(`${url.pathname}/${wellKnown}`);
163
+ return url;
164
+ }
165
+ async function performDiscovery(input, urlName, transform, options) {
166
+ if (!(input instanceof URL)) {
167
+ throw CodedTypeError(`"${urlName}" must be an instance of URL`, ERR_INVALID_ARG_TYPE);
168
+ }
169
+ checkProtocol(input, options?.[allowInsecureRequests] !== true);
170
+ const url = transform(new URL(input.href));
171
+ const headers = prepareHeaders(options?.headers);
172
+ headers.set("accept", "application/json");
173
+ return (options?.[customFetch] || fetch)(url.href, {
174
+ body: undefined,
175
+ headers: Object.fromEntries(headers.entries()),
176
+ method: "GET",
177
+ redirect: "manual",
178
+ signal: signal(url, options?.signal)
179
+ });
180
+ }
181
+ async function discoveryRequest(issuerIdentifier, options) {
182
+ return performDiscovery(issuerIdentifier, "issuerIdentifier", (url) => {
183
+ switch (options?.algorithm) {
184
+ case undefined:
185
+ case "oidc":
186
+ appendWellKnown(url, ".well-known/openid-configuration");
187
+ break;
188
+ case "oauth2":
189
+ prependWellKnown(url, ".well-known/oauth-authorization-server");
190
+ break;
191
+ default:
192
+ throw CodedTypeError('"options.algorithm" must be "oidc" (default), or "oauth2"', ERR_INVALID_ARG_VALUE);
193
+ }
194
+ return url;
195
+ }, options);
196
+ }
197
+ function assertNumber(input, allow0, it, code, cause) {
198
+ try {
199
+ if (typeof input !== "number" || !Number.isFinite(input)) {
200
+ throw CodedTypeError(`${it} must be a number`, ERR_INVALID_ARG_TYPE, cause);
201
+ }
202
+ if (input > 0)
203
+ return;
204
+ if (allow0) {
205
+ if (input !== 0) {
206
+ throw CodedTypeError(`${it} must be a non-negative number`, ERR_INVALID_ARG_VALUE, cause);
207
+ }
208
+ return;
209
+ }
210
+ throw CodedTypeError(`${it} must be a positive number`, ERR_INVALID_ARG_VALUE, cause);
211
+ } catch (err) {
212
+ if (code) {
213
+ throw OPE(err.message, code, cause);
214
+ }
215
+ throw err;
216
+ }
217
+ }
218
+ function assertString(input, it, code, cause) {
219
+ try {
220
+ if (typeof input !== "string") {
221
+ throw CodedTypeError(`${it} must be a string`, ERR_INVALID_ARG_TYPE, cause);
222
+ }
223
+ if (input.length === 0) {
224
+ throw CodedTypeError(`${it} must not be empty`, ERR_INVALID_ARG_VALUE, cause);
225
+ }
226
+ } catch (err) {
227
+ if (code) {
228
+ throw OPE(err.message, code, cause);
229
+ }
230
+ throw err;
231
+ }
232
+ }
233
+ async function processDiscoveryResponse(expectedIssuerIdentifier, response) {
234
+ const expected = expectedIssuerIdentifier;
235
+ if (!(expected instanceof URL) && expected !== _nodiscoverycheck) {
236
+ throw CodedTypeError('"expectedIssuerIdentifier" must be an instance of URL', ERR_INVALID_ARG_TYPE);
237
+ }
238
+ if (!looseInstanceOf(response, Response)) {
239
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
240
+ }
241
+ if (response.status !== 200) {
242
+ throw OPE('"response" is not a conform Authorization Server Metadata response (unexpected HTTP status code)', RESPONSE_IS_NOT_CONFORM, response);
243
+ }
244
+ assertReadableResponse(response);
245
+ const json = await getResponseJsonBody(response);
246
+ assertString(json.issuer, '"response" body "issuer" property', INVALID_RESPONSE, { body: json });
247
+ if (expected !== _nodiscoverycheck && new URL(json.issuer).href !== expected.href) {
248
+ throw OPE('"response" body "issuer" property does not match the expected value', JSON_ATTRIBUTE_COMPARISON, { expected: expected.href, body: json, attribute: "issuer" });
249
+ }
250
+ return json;
251
+ }
252
+ function assertApplicationJson(response) {
253
+ assertContentType(response, "application/json");
254
+ }
255
+ function notJson(response, ...types) {
256
+ let msg = '"response" content-type must be ';
257
+ if (types.length > 2) {
258
+ const last = types.pop();
259
+ msg += `${types.join(", ")}, or ${last}`;
260
+ } else if (types.length === 2) {
261
+ msg += `${types[0]} or ${types[1]}`;
262
+ } else {
263
+ msg += types[0];
264
+ }
265
+ return OPE(msg, RESPONSE_IS_NOT_JSON, response);
266
+ }
267
+ function assertContentTypes(response, ...types) {
268
+ if (!types.includes(getContentType(response))) {
269
+ throw notJson(response, ...types);
270
+ }
271
+ }
272
+ function assertContentType(response, contentType) {
273
+ if (getContentType(response) !== contentType) {
274
+ throw notJson(response, contentType);
275
+ }
276
+ }
277
+ function randomBytes() {
278
+ return b64u(crypto.getRandomValues(new Uint8Array(32)));
279
+ }
280
+ function generateRandomCodeVerifier() {
281
+ return randomBytes();
282
+ }
283
+ function generateRandomState() {
284
+ return randomBytes();
285
+ }
286
+ function generateRandomNonce() {
287
+ return randomBytes();
288
+ }
289
+ async function calculatePKCECodeChallenge(codeVerifier) {
290
+ assertString(codeVerifier, "codeVerifier");
291
+ return b64u(await crypto.subtle.digest("SHA-256", buf(codeVerifier)));
292
+ }
293
+ function getClockSkew(client) {
294
+ const skew = client?.[clockSkew];
295
+ return typeof skew === "number" && Number.isFinite(skew) ? skew : 0;
296
+ }
297
+ function getClockTolerance(client) {
298
+ const tolerance = client?.[clockTolerance];
299
+ return typeof tolerance === "number" && Number.isFinite(tolerance) && Math.sign(tolerance) !== -1 ? tolerance : 30;
300
+ }
301
+ function epochTime() {
302
+ return Math.floor(Date.now() / 1000);
303
+ }
304
+ function assertAs(as) {
305
+ if (typeof as !== "object" || as === null) {
306
+ throw CodedTypeError('"as" must be an object', ERR_INVALID_ARG_TYPE);
307
+ }
308
+ assertString(as.issuer, '"as.issuer"');
309
+ }
310
+ function assertClient(client) {
311
+ if (typeof client !== "object" || client === null) {
312
+ throw CodedTypeError('"client" must be an object', ERR_INVALID_ARG_TYPE);
313
+ }
314
+ assertString(client.client_id, '"client.client_id"');
315
+ }
316
+ function formUrlEncode(token) {
317
+ return encodeURIComponent(token).replace(/(?:[-_.!~*'()]|%20)/g, (substring) => {
318
+ switch (substring) {
319
+ case "-":
320
+ case "_":
321
+ case ".":
322
+ case "!":
323
+ case "~":
324
+ case "*":
325
+ case "'":
326
+ case "(":
327
+ case ")":
328
+ return `%${substring.charCodeAt(0).toString(16).toUpperCase()}`;
329
+ case "%20":
330
+ return "+";
331
+ default:
332
+ throw new Error;
333
+ }
334
+ });
335
+ }
336
+ function ClientSecretPost(clientSecret) {
337
+ assertString(clientSecret, '"clientSecret"');
338
+ return (_as, client, body, _headers) => {
339
+ body.set("client_id", client.client_id);
340
+ body.set("client_secret", clientSecret);
341
+ };
342
+ }
343
+ function ClientSecretBasic(clientSecret) {
344
+ assertString(clientSecret, '"clientSecret"');
345
+ return (_as, client, _body, headers) => {
346
+ const username = formUrlEncode(client.client_id);
347
+ const password = formUrlEncode(clientSecret);
348
+ const credentials = btoa(`${username}:${password}`);
349
+ headers.set("authorization", `Basic ${credentials}`);
350
+ };
351
+ }
352
+ function None() {
353
+ return (_as, client, body, _headers) => {
354
+ body.set("client_id", client.client_id);
355
+ };
356
+ }
357
+ var URLParse = URL.parse ? (url, base) => URL.parse(url, base) : (url, base) => {
358
+ try {
359
+ return new URL(url, base);
360
+ } catch {
361
+ return null;
362
+ }
363
+ };
364
+ function checkProtocol(url, enforceHttps) {
365
+ if (enforceHttps && url.protocol !== "https:") {
366
+ throw OPE("only requests to HTTPS are allowed", HTTP_REQUEST_FORBIDDEN, url);
367
+ }
368
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
369
+ throw OPE("only HTTP and HTTPS requests are allowed", REQUEST_PROTOCOL_FORBIDDEN, url);
370
+ }
371
+ }
372
+ function validateEndpoint(value, endpoint, useMtlsAlias, enforceHttps) {
373
+ let url;
374
+ if (typeof value !== "string" || !(url = URLParse(value))) {
375
+ throw OPE(`authorization server metadata does not contain a valid ${useMtlsAlias ? `"as.mtls_endpoint_aliases.${endpoint}"` : `"as.${endpoint}"`}`, value === undefined ? MISSING_SERVER_METADATA : INVALID_SERVER_METADATA, { attribute: useMtlsAlias ? `mtls_endpoint_aliases.${endpoint}` : endpoint });
376
+ }
377
+ checkProtocol(url, enforceHttps);
378
+ return url;
379
+ }
380
+ function resolveEndpoint(as, endpoint, useMtlsAlias, enforceHttps) {
381
+ if (useMtlsAlias && as.mtls_endpoint_aliases && endpoint in as.mtls_endpoint_aliases) {
382
+ return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, useMtlsAlias, enforceHttps);
383
+ }
384
+ return validateEndpoint(as[endpoint], endpoint, useMtlsAlias, enforceHttps);
385
+ }
386
+ function isDPoPNonceError(err) {
387
+ if (err instanceof WWWAuthenticateChallengeError) {
388
+ return err.cause.some((challenge) => challenge.scheme === "dpop" && challenge.parameters.error === "use_dpop_nonce");
389
+ }
390
+ if (err instanceof ResponseBodyError) {
391
+ return err.error === "use_dpop_nonce";
392
+ }
393
+ return false;
394
+ }
395
+ class ResponseBodyError extends Error {
396
+ cause;
397
+ code;
398
+ error;
399
+ status;
400
+ error_description;
401
+ response;
402
+ constructor(message, options) {
403
+ super(message, options);
404
+ this.name = this.constructor.name;
405
+ this.code = RESPONSE_BODY_ERROR;
406
+ this.cause = options.cause;
407
+ this.error = options.cause.error;
408
+ this.status = options.response.status;
409
+ this.error_description = options.cause.error_description;
410
+ Object.defineProperty(this, "response", { enumerable: false, value: options.response });
411
+ Error.captureStackTrace?.(this, this.constructor);
412
+ }
413
+ }
414
+
415
+ class AuthorizationResponseError extends Error {
416
+ cause;
417
+ code;
418
+ error;
419
+ error_description;
420
+ constructor(message, options) {
421
+ super(message, options);
422
+ this.name = this.constructor.name;
423
+ this.code = AUTHORIZATION_RESPONSE_ERROR;
424
+ this.cause = options.cause;
425
+ this.error = options.cause.get("error");
426
+ this.error_description = options.cause.get("error_description") ?? undefined;
427
+ Error.captureStackTrace?.(this, this.constructor);
428
+ }
429
+ }
430
+
431
+ class WWWAuthenticateChallengeError extends Error {
432
+ cause;
433
+ code;
434
+ response;
435
+ status;
436
+ constructor(message, options) {
437
+ super(message, options);
438
+ this.name = this.constructor.name;
439
+ this.code = WWW_AUTHENTICATE_CHALLENGE;
440
+ this.cause = options.cause;
441
+ this.status = options.response.status;
442
+ this.response = options.response;
443
+ Object.defineProperty(this, "response", { enumerable: false });
444
+ Error.captureStackTrace?.(this, this.constructor);
445
+ }
446
+ }
447
+ var tokenMatch = "[a-zA-Z0-9!#$%&\\'\\*\\+\\-\\.\\^_`\\|~]+";
448
+ var token68Match = "[a-zA-Z0-9\\-\\._\\~\\+\\/]+={0,2}";
449
+ var quotedMatch = '"((?:[^"\\\\]|\\\\[\\s\\S])*)"';
450
+ var quotedParamMatcher = "(" + tokenMatch + ")\\s*=\\s*" + quotedMatch;
451
+ var paramMatcher = "(" + tokenMatch + ")\\s*=\\s*(" + tokenMatch + ")";
452
+ var schemeRE = new RegExp("^[,\\s]*(" + tokenMatch + ")");
453
+ var quotedParamRE = new RegExp("^[,\\s]*" + quotedParamMatcher + "[,\\s]*(.*)");
454
+ var unquotedParamRE = new RegExp("^[,\\s]*" + paramMatcher + "[,\\s]*(.*)");
455
+ var token68ParamRE = new RegExp("^(" + token68Match + ")(?:$|[,\\s])(.*)");
456
+ function parseWwwAuthenticateChallenges(response) {
457
+ if (!looseInstanceOf(response, Response)) {
458
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
459
+ }
460
+ const header = response.headers.get("www-authenticate");
461
+ if (header === null) {
462
+ return;
463
+ }
464
+ const challenges = [];
465
+ let rest = header;
466
+ while (rest) {
467
+ let match = rest.match(schemeRE);
468
+ const scheme = match?.["1"].toLowerCase();
469
+ if (!scheme) {
470
+ return;
471
+ }
472
+ const afterScheme = rest.substring(match[0].length);
473
+ if (afterScheme && !afterScheme.match(/^[\s,]/)) {
474
+ return;
475
+ }
476
+ const spaceMatch = afterScheme.match(/^\s+(.*)$/);
477
+ const hasParameters = !!spaceMatch;
478
+ rest = spaceMatch ? spaceMatch[1] : undefined;
479
+ const parameters = {};
480
+ let token68;
481
+ if (hasParameters) {
482
+ while (rest) {
483
+ let key;
484
+ let value;
485
+ if (match = rest.match(quotedParamRE)) {
486
+ [, key, value, rest] = match;
487
+ if (value.includes("\\")) {
488
+ value = value.replace(/\\([\s\S])/g, "$1");
489
+ }
490
+ parameters[key.toLowerCase()] = value;
491
+ continue;
492
+ }
493
+ if (match = rest.match(unquotedParamRE)) {
494
+ [, key, value, rest] = match;
495
+ parameters[key.toLowerCase()] = value;
496
+ continue;
497
+ }
498
+ if (match = rest.match(token68ParamRE)) {
499
+ if (Object.keys(parameters).length) {
500
+ break;
501
+ }
502
+ [, token68, rest] = match;
503
+ break;
504
+ }
505
+ return;
506
+ }
507
+ } else {
508
+ rest = afterScheme || undefined;
509
+ }
510
+ const challenge = { scheme, parameters };
511
+ if (token68) {
512
+ challenge.token68 = token68;
513
+ }
514
+ challenges.push(challenge);
515
+ }
516
+ if (!challenges.length) {
517
+ return;
518
+ }
519
+ return challenges;
520
+ }
521
+ async function parseOAuthResponseErrorBody(response) {
522
+ if (response.status > 399 && response.status < 500) {
523
+ assertReadableResponse(response);
524
+ assertApplicationJson(response);
525
+ try {
526
+ const json = await response.clone().json();
527
+ if (isJsonObject(json) && typeof json.error === "string" && json.error.length) {
528
+ return json;
529
+ }
530
+ } catch {}
531
+ }
532
+ return;
533
+ }
534
+ async function checkOAuthBodyError(response, expected, label) {
535
+ if (response.status !== expected) {
536
+ checkAuthenticationChallenges(response);
537
+ let err;
538
+ if (err = await parseOAuthResponseErrorBody(response)) {
539
+ await response.body?.cancel();
540
+ throw new ResponseBodyError("server responded with an error in the response body", {
541
+ cause: err,
542
+ response
543
+ });
544
+ }
545
+ throw OPE(`"response" is not a conform ${label} response (unexpected HTTP status code)`, RESPONSE_IS_NOT_CONFORM, response);
546
+ }
547
+ }
548
+ function assertDPoP(option) {
549
+ if (!branded.has(option)) {
550
+ throw CodedTypeError('"options.DPoP" is not a valid DPoPHandle', ERR_INVALID_ARG_VALUE);
551
+ }
552
+ }
553
+ async function resourceRequest(accessToken, method, url, headers, body, options) {
554
+ assertString(accessToken, '"accessToken"');
555
+ if (!(url instanceof URL)) {
556
+ throw CodedTypeError('"url" must be an instance of URL', ERR_INVALID_ARG_TYPE);
557
+ }
558
+ checkProtocol(url, options?.[allowInsecureRequests] !== true);
559
+ headers = prepareHeaders(headers);
560
+ if (options?.DPoP) {
561
+ assertDPoP(options.DPoP);
562
+ await options.DPoP.addProof(url, headers, method.toUpperCase(), accessToken);
563
+ }
564
+ headers.set("authorization", `${headers.has("dpop") ? "DPoP" : "Bearer"} ${accessToken}`);
565
+ const response = await (options?.[customFetch] || fetch)(url.href, {
566
+ duplex: looseInstanceOf(body, ReadableStream) ? "half" : undefined,
567
+ body,
568
+ headers: Object.fromEntries(headers.entries()),
569
+ method,
570
+ redirect: "manual",
571
+ signal: signal(url, options?.signal)
572
+ });
573
+ options?.DPoP?.cacheNonce(response, url);
574
+ return response;
575
+ }
576
+ async function protectedResourceRequest(accessToken, method, url, headers, body, options) {
577
+ const response = await resourceRequest(accessToken, method, url, headers, body, options);
578
+ checkAuthenticationChallenges(response);
579
+ return response;
580
+ }
581
+ var jwksMap;
582
+ var jwksRequests;
583
+ var jwksKeys;
584
+ function setJwksCache(as, jwks, uat, cache) {
585
+ jwksMap ||= new WeakMap;
586
+ jwksMap.set(as, {
587
+ jwks,
588
+ uat,
589
+ get age() {
590
+ return epochTime() - this.uat;
591
+ }
592
+ });
593
+ if (cache) {
594
+ Object.assign(cache, { jwks: structuredClone(jwks), uat });
595
+ }
596
+ }
597
+ function isFreshJwksCache(input) {
598
+ if (typeof input !== "object" || input === null) {
599
+ return false;
600
+ }
601
+ if (!("uat" in input) || typeof input.uat !== "number" || epochTime() - input.uat >= 300) {
602
+ return false;
603
+ }
604
+ if (!("jwks" in input) || !isJsonObject(input.jwks) || !Array.isArray(input.jwks.keys) || !Array.prototype.every.call(input.jwks.keys, isJsonObject)) {
605
+ return false;
606
+ }
607
+ return true;
608
+ }
609
+ function clearJwksCache(as, cache) {
610
+ jwksMap?.delete(as);
611
+ delete cache?.jwks;
612
+ delete cache?.uat;
613
+ }
614
+ async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
615
+ const { alg, kid } = header;
616
+ checkSupportedJwsAlg(header);
617
+ if (!jwksMap?.has(as) && isFreshJwksCache(options?.[jwksCache])) {
618
+ setJwksCache(as, options?.[jwksCache].jwks, options?.[jwksCache].uat);
619
+ }
620
+ let jwks;
621
+ let age;
622
+ if (jwksMap?.has(as)) {
623
+ ({ jwks, age } = jwksMap.get(as));
624
+ if (age >= 300) {
625
+ clearJwksCache(as, options?.[jwksCache]);
626
+ return getPublicSigKeyFromIssuerJwksUri(as, options, header);
627
+ }
628
+ } else {
629
+ const downloaded = await jwksRequest(as, options);
630
+ jwks = downloaded.jwks;
631
+ age = epochTime() - downloaded.uat;
632
+ setJwksCache(as, jwks, downloaded.uat, options?.[jwksCache]);
633
+ }
634
+ let kty;
635
+ switch (alg.slice(0, 2)) {
636
+ case "RS":
637
+ case "PS":
638
+ kty = "RSA";
639
+ break;
640
+ case "ES":
641
+ kty = "EC";
642
+ break;
643
+ case "Ed":
644
+ kty = "OKP";
645
+ break;
646
+ case "ML":
647
+ kty = "AKP";
648
+ break;
649
+ default:
650
+ throw new UnsupportedOperationError("unsupported JWS algorithm", { cause: { alg } });
651
+ }
652
+ const candidates = jwks.keys.filter((jwk) => {
653
+ if (jwk.kty !== kty) {
654
+ return false;
655
+ }
656
+ if (kid !== undefined && kid !== jwk.kid) {
657
+ return false;
658
+ }
659
+ if (jwk.alg !== undefined && alg !== jwk.alg) {
660
+ return false;
661
+ }
662
+ if (jwk.use !== undefined && jwk.use !== "sig") {
663
+ return false;
664
+ }
665
+ if (jwk.key_ops?.includes("verify") === false) {
666
+ return false;
667
+ }
668
+ switch (true) {
669
+ case (alg === "ES256" && jwk.crv !== "P-256"):
670
+ case (alg === "ES384" && jwk.crv !== "P-384"):
671
+ case (alg === "ES512" && jwk.crv !== "P-521"):
672
+ case (alg === "Ed25519" && jwk.crv !== "Ed25519"):
673
+ case (alg === "EdDSA" && jwk.crv !== "Ed25519"):
674
+ return false;
675
+ }
676
+ return true;
677
+ });
678
+ const { 0: jwk, length } = candidates;
679
+ if (!length) {
680
+ if (age >= 60) {
681
+ clearJwksCache(as, options?.[jwksCache]);
682
+ return getPublicSigKeyFromIssuerJwksUri(as, options, header);
683
+ }
684
+ throw OPE("error when selecting a JWT verification key, no applicable keys found", KEY_SELECTION, { header, candidates, jwks_uri: new URL(as.jwks_uri) });
685
+ }
686
+ if (length !== 1) {
687
+ throw OPE('error when selecting a JWT verification key, multiple applicable keys found, a "kid" JWT Header Parameter is required', KEY_SELECTION, { header, candidates, jwks_uri: new URL(as.jwks_uri) });
688
+ }
689
+ jwksKeys ||= new WeakMap;
690
+ let keys = jwksKeys.get(jwk);
691
+ if (!keys) {
692
+ keys = new Map;
693
+ jwksKeys.set(jwk, keys);
694
+ }
695
+ let key = keys.get(alg);
696
+ if (!key) {
697
+ key = importJwk(alg, jwk).catch((cause) => {
698
+ keys.delete(alg);
699
+ throw cause;
700
+ });
701
+ keys.set(alg, key);
702
+ }
703
+ return key;
704
+ }
705
+ var skipSubjectCheck = Symbol();
706
+ function getContentType(input) {
707
+ return input.headers.get("content-type")?.split(";")[0];
708
+ }
709
+ async function authenticatedRequest(as, client, clientAuthentication, url, body, headers, options) {
710
+ await clientAuthentication(as, client, body, headers);
711
+ headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
712
+ return (options?.[customFetch] || fetch)(url.href, {
713
+ body,
714
+ headers: Object.fromEntries(headers.entries()),
715
+ method: "POST",
716
+ redirect: "manual",
717
+ signal: signal(url, options?.signal)
718
+ });
719
+ }
720
+ async function tokenEndpointRequest(as, client, clientAuthentication, grantType, parameters, options) {
721
+ const url = resolveEndpoint(as, "token_endpoint", client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
722
+ parameters.set("grant_type", grantType);
723
+ const headers = prepareHeaders(options?.headers);
724
+ headers.set("accept", "application/json");
725
+ if (options?.DPoP !== undefined) {
726
+ assertDPoP(options.DPoP);
727
+ await options.DPoP.addProof(url, headers, "POST");
728
+ }
729
+ const response = await authenticatedRequest(as, client, clientAuthentication, url, parameters, headers, options);
730
+ options?.DPoP?.cacheNonce(response, url);
731
+ return response;
732
+ }
733
+ var idTokenClaims = new WeakMap;
734
+ var jwtRefs = new WeakMap;
735
+ function getValidatedIdTokenClaims(ref) {
736
+ if (!ref.id_token) {
737
+ return;
738
+ }
739
+ const claims = idTokenClaims.get(ref);
740
+ if (!claims) {
741
+ throw CodedTypeError('"ref" was already garbage collected or did not resolve from the proper sources', ERR_INVALID_ARG_VALUE);
742
+ }
743
+ return claims;
744
+ }
745
+ async function validateApplicationLevelSignature(as, ref, options) {
746
+ assertAs(as);
747
+ if (!jwtRefs.has(ref)) {
748
+ throw CodedTypeError('"ref" does not contain a processed JWT Response to verify the signature of', ERR_INVALID_ARG_VALUE);
749
+ }
750
+ const { 0: protectedHeader, 1: payload, 2: encodedSignature } = jwtRefs.get(ref).split(".");
751
+ const header = JSON.parse(buf(b64u(protectedHeader)));
752
+ if (header.alg.startsWith("HS")) {
753
+ throw new UnsupportedOperationError("unsupported JWS algorithm", { cause: { alg: header.alg } });
754
+ }
755
+ let key;
756
+ key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
757
+ await validateJwsSignature(protectedHeader, payload, key, b64u(encodedSignature));
758
+ }
759
+ async function processGenericAccessTokenResponse(as, client, response, additionalRequiredIdTokenClaims, decryptFn, recognizedTokenTypes) {
760
+ assertAs(as);
761
+ assertClient(client);
762
+ if (!looseInstanceOf(response, Response)) {
763
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
764
+ }
765
+ await checkOAuthBodyError(response, 200, "Token Endpoint");
766
+ assertReadableResponse(response);
767
+ const json = await getResponseJsonBody(response);
768
+ assertString(json.access_token, '"response" body "access_token" property', INVALID_RESPONSE, {
769
+ body: json
770
+ });
771
+ assertString(json.token_type, '"response" body "token_type" property', INVALID_RESPONSE, {
772
+ body: json
773
+ });
774
+ json.token_type = json.token_type.toLowerCase();
775
+ if (json.expires_in !== undefined) {
776
+ let expiresIn = typeof json.expires_in !== "number" ? parseFloat(json.expires_in) : json.expires_in;
777
+ assertNumber(expiresIn, true, '"response" body "expires_in" property', INVALID_RESPONSE, {
778
+ body: json
779
+ });
780
+ json.expires_in = expiresIn;
781
+ }
782
+ if (json.refresh_token !== undefined) {
783
+ assertString(json.refresh_token, '"response" body "refresh_token" property', INVALID_RESPONSE, {
784
+ body: json
785
+ });
786
+ }
787
+ if (json.scope !== undefined && typeof json.scope !== "string") {
788
+ throw OPE('"response" body "scope" property must be a string', INVALID_RESPONSE, { body: json });
789
+ }
790
+ if (json.id_token !== undefined) {
791
+ assertString(json.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
792
+ body: json
793
+ });
794
+ const requiredClaims = [];
795
+ if (client.require_auth_time === true) {
796
+ requiredClaims.push("auth_time");
797
+ }
798
+ if (client.default_max_age !== undefined) {
799
+ assertNumber(client.default_max_age, true, '"client.default_max_age"');
800
+ requiredClaims.push("auth_time");
801
+ }
802
+ if (additionalRequiredIdTokenClaims?.length) {
803
+ requiredClaims.push(...additionalRequiredIdTokenClaims);
804
+ }
805
+ const { claims, jwt } = await validateIdTokenClaims(as, client, json.id_token, requiredClaims, decryptFn);
806
+ validateIdTokenAuthorizedParty(client, claims);
807
+ validateIdTokenAuthTimeClaim(claims);
808
+ jwtRefs.set(response, jwt);
809
+ idTokenClaims.set(json, claims);
810
+ }
811
+ if (recognizedTokenTypes?.[json.token_type] !== undefined) {
812
+ recognizedTokenTypes[json.token_type](response, json);
813
+ } else if (json.token_type !== "dpop" && json.token_type !== "bearer") {
814
+ throw new UnsupportedOperationError("unsupported `token_type` value", { cause: { body: json } });
815
+ }
816
+ return json;
817
+ }
818
+ function checkAuthenticationChallenges(response) {
819
+ let challenges;
820
+ if (challenges = parseWwwAuthenticateChallenges(response)) {
821
+ throw new WWWAuthenticateChallengeError("server responded with a challenge in the WWW-Authenticate HTTP Header", { cause: challenges, response });
822
+ }
823
+ }
824
+ function validateAudience(expected, result) {
825
+ if (Array.isArray(result.claims.aud)) {
826
+ if (!result.claims.aud.includes(expected)) {
827
+ throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
828
+ expected,
829
+ claims: result.claims,
830
+ claim: "aud"
831
+ });
832
+ }
833
+ } else if (result.claims.aud !== expected) {
834
+ throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
835
+ expected,
836
+ claims: result.claims,
837
+ claim: "aud"
838
+ });
839
+ }
840
+ return result;
841
+ }
842
+ function validateIssuer(as, result) {
843
+ const expected = as[_expectedIssuer]?.(result) ?? as.issuer;
844
+ if (result.claims.iss !== expected) {
845
+ throw OPE('unexpected JWT "iss" (issuer) claim value', JWT_CLAIM_COMPARISON, {
846
+ expected,
847
+ claims: result.claims,
848
+ claim: "iss"
849
+ });
850
+ }
851
+ return result;
852
+ }
853
+ var branded = new WeakSet;
854
+ function brand(searchParams) {
855
+ branded.add(searchParams);
856
+ return searchParams;
857
+ }
858
+ var nopkce = Symbol();
859
+ async function authorizationCodeGrantRequest(as, client, clientAuthentication, callbackParameters, redirectUri, codeVerifier, options) {
860
+ assertAs(as);
861
+ assertClient(client);
862
+ if (!branded.has(callbackParameters)) {
863
+ throw CodedTypeError('"callbackParameters" must be an instance of URLSearchParams obtained from "validateAuthResponse()", or "validateJwtAuthResponse()', ERR_INVALID_ARG_VALUE);
864
+ }
865
+ assertString(redirectUri, '"redirectUri"');
866
+ const code = getURLSearchParameter(callbackParameters, "code");
867
+ if (!code) {
868
+ throw OPE('no authorization code in "callbackParameters"', INVALID_RESPONSE);
869
+ }
870
+ const parameters = new URLSearchParams(options?.additionalParameters);
871
+ parameters.set("redirect_uri", redirectUri);
872
+ parameters.set("code", code);
873
+ if (codeVerifier !== nopkce) {
874
+ assertString(codeVerifier, '"codeVerifier"');
875
+ parameters.set("code_verifier", codeVerifier);
876
+ }
877
+ return tokenEndpointRequest(as, client, clientAuthentication, "authorization_code", parameters, options);
878
+ }
879
+ var jwtClaimNames = {
880
+ aud: "audience",
881
+ c_hash: "code hash",
882
+ client_id: "client id",
883
+ exp: "expiration time",
884
+ iat: "issued at",
885
+ iss: "issuer",
886
+ jti: "jwt id",
887
+ nonce: "nonce",
888
+ s_hash: "state hash",
889
+ sub: "subject",
890
+ ath: "access token hash",
891
+ htm: "http method",
892
+ htu: "http uri",
893
+ cnf: "confirmation",
894
+ auth_time: "authentication time"
895
+ };
896
+ function validatePresence(required, result) {
897
+ for (const claim of required) {
898
+ if (result.claims[claim] === undefined) {
899
+ throw OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`, INVALID_RESPONSE, {
900
+ claims: result.claims
901
+ });
902
+ }
903
+ }
904
+ return result;
905
+ }
906
+ function validateStringClaim(claim, result) {
907
+ if (typeof result.claims[claim] !== "string") {
908
+ throw OPE(`unexpected JWT "${claim}" (${jwtClaimNames[claim]}) claim type`, INVALID_RESPONSE, {
909
+ claims: result.claims
910
+ });
911
+ }
912
+ return result;
913
+ }
914
+ function validateIdTokenClaims(as, client, idToken, requiredClaims, decryptFn) {
915
+ return validateJwt(idToken, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported, "RS256"), getClockSkew(client), getClockTolerance(client), decryptFn).then(validatePresence.bind(undefined, ["aud", "exp", "iat", "iss", "sub", ...requiredClaims])).then(validateIssuer.bind(undefined, as)).then(validateAudience.bind(undefined, client.client_id)).then(validateStringClaim.bind(undefined, "sub"));
916
+ }
917
+ function resolveIdTokenMaxAge(client, maxAge) {
918
+ if (maxAge === skipAuthTimeCheck) {
919
+ return maxAge;
920
+ }
921
+ const fromClient = maxAge === undefined;
922
+ if (fromClient) {
923
+ maxAge = client.default_max_age;
924
+ }
925
+ if (maxAge === undefined) {
926
+ return skipAuthTimeCheck;
927
+ }
928
+ assertNumber(maxAge, true, fromClient ? '"client.default_max_age"' : '"maxAge" argument');
929
+ return maxAge;
930
+ }
931
+ function validateIdTokenAuthTimeClaim(claims) {
932
+ if (claims.auth_time !== undefined) {
933
+ assertNumber(claims.auth_time, true, 'ID Token "auth_time" (authentication time)', INVALID_RESPONSE, { claims });
934
+ }
935
+ }
936
+ function validateIdTokenAuthTime(client, claims, maxAge) {
937
+ if (maxAge === skipAuthTimeCheck) {
938
+ return;
939
+ }
940
+ const now = epochTime() + getClockSkew(client);
941
+ const tolerance = getClockTolerance(client);
942
+ if (claims.auth_time + maxAge < now - tolerance) {
943
+ throw OPE("too much time has elapsed since the last End-User authentication", JWT_TIMESTAMP_CHECK, { claims, now, tolerance, claim: "auth_time" });
944
+ }
945
+ }
946
+ function validateIdTokenNonce(claims, expectedNonce) {
947
+ const expected = expectedNonce === expectNoNonce ? undefined : expectedNonce;
948
+ if (claims.nonce !== expected) {
949
+ throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
950
+ expected,
951
+ claims,
952
+ claim: "nonce"
953
+ });
954
+ }
955
+ }
956
+ function validateIdTokenAuthorizedParty(client, claims) {
957
+ if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
958
+ if (claims.azp === undefined) {
959
+ throw OPE('ID Token "aud" (audience) claim includes additional untrusted audiences', JWT_CLAIM_COMPARISON, { claims, claim: "aud" });
960
+ }
961
+ if (claims.azp !== client.client_id) {
962
+ throw OPE('unexpected ID Token "azp" (authorized party) claim value', JWT_CLAIM_COMPARISON, {
963
+ expected: client.client_id,
964
+ claims,
965
+ claim: "azp"
966
+ });
967
+ }
968
+ }
969
+ }
970
+ var expectNoNonce = Symbol();
971
+ var skipAuthTimeCheck = Symbol();
972
+ async function processAuthorizationCodeResponse(as, client, response, options) {
973
+ if (typeof options?.expectedNonce === "string" || typeof options?.maxAge === "number" || options?.requireIdToken) {
974
+ return processAuthorizationCodeOpenIDResponse(as, client, response, options.expectedNonce, options.maxAge, options[jweDecrypt], options.recognizedTokenTypes);
975
+ }
976
+ return processAuthorizationCodeOAuth2Response(as, client, response, options?.maxAge, options?.[jweDecrypt], options?.recognizedTokenTypes);
977
+ }
978
+ async function processAuthorizationCodeOpenIDResponse(as, client, response, expectedNonce, maxAge, decryptFn, recognizedTokenTypes) {
979
+ const additionalRequiredClaims = [];
980
+ switch (expectedNonce) {
981
+ case undefined:
982
+ expectedNonce = expectNoNonce;
983
+ break;
984
+ case expectNoNonce:
985
+ break;
986
+ default:
987
+ assertString(expectedNonce, '"expectedNonce" argument');
988
+ additionalRequiredClaims.push("nonce");
989
+ }
990
+ maxAge = resolveIdTokenMaxAge(client, maxAge);
991
+ if (maxAge !== skipAuthTimeCheck) {
992
+ additionalRequiredClaims.push("auth_time");
993
+ }
994
+ const result = await processGenericAccessTokenResponse(as, client, response, additionalRequiredClaims, decryptFn, recognizedTokenTypes);
995
+ assertString(result.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
996
+ body: result
997
+ });
998
+ const claims = getValidatedIdTokenClaims(result);
999
+ validateIdTokenAuthTime(client, claims, maxAge);
1000
+ validateIdTokenNonce(claims, expectedNonce);
1001
+ return result;
1002
+ }
1003
+ async function processAuthorizationCodeOAuth2Response(as, client, response, maxAge, decryptFn, recognizedTokenTypes) {
1004
+ const result = await processGenericAccessTokenResponse(as, client, response, undefined, decryptFn, recognizedTokenTypes);
1005
+ const claims = getValidatedIdTokenClaims(result);
1006
+ if (claims) {
1007
+ validateIdTokenAuthTime(client, claims, resolveIdTokenMaxAge(client, maxAge));
1008
+ validateIdTokenNonce(claims, expectNoNonce);
1009
+ }
1010
+ return result;
1011
+ }
1012
+ var WWW_AUTHENTICATE_CHALLENGE = "OAUTH_WWW_AUTHENTICATE_CHALLENGE";
1013
+ var RESPONSE_BODY_ERROR = "OAUTH_RESPONSE_BODY_ERROR";
1014
+ var UNSUPPORTED_OPERATION = "OAUTH_UNSUPPORTED_OPERATION";
1015
+ var AUTHORIZATION_RESPONSE_ERROR = "OAUTH_AUTHORIZATION_RESPONSE_ERROR";
1016
+ var PARSE_ERROR = "OAUTH_PARSE_ERROR";
1017
+ var INVALID_RESPONSE = "OAUTH_INVALID_RESPONSE";
1018
+ var RESPONSE_IS_NOT_JSON = "OAUTH_RESPONSE_IS_NOT_JSON";
1019
+ var RESPONSE_IS_NOT_CONFORM = "OAUTH_RESPONSE_IS_NOT_CONFORM";
1020
+ var HTTP_REQUEST_FORBIDDEN = "OAUTH_HTTP_REQUEST_FORBIDDEN";
1021
+ var REQUEST_PROTOCOL_FORBIDDEN = "OAUTH_REQUEST_PROTOCOL_FORBIDDEN";
1022
+ var JWT_TIMESTAMP_CHECK = "OAUTH_JWT_TIMESTAMP_CHECK_FAILED";
1023
+ var JWT_CLAIM_COMPARISON = "OAUTH_JWT_CLAIM_COMPARISON_FAILED";
1024
+ var JSON_ATTRIBUTE_COMPARISON = "OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED";
1025
+ var KEY_SELECTION = "OAUTH_KEY_SELECTION_FAILED";
1026
+ var MISSING_SERVER_METADATA = "OAUTH_MISSING_SERVER_METADATA";
1027
+ var INVALID_SERVER_METADATA = "OAUTH_INVALID_SERVER_METADATA";
1028
+ function assertReadableResponse(response) {
1029
+ if (response.bodyUsed) {
1030
+ throw CodedTypeError('"response" body has been used already', ERR_INVALID_ARG_VALUE);
1031
+ }
1032
+ }
1033
+ async function jwksRequest(as, options) {
1034
+ assertAs(as);
1035
+ const url = resolveEndpoint(as, "jwks_uri", false, options?.[allowInsecureRequests] !== true);
1036
+ const headers = prepareHeaders(options?.headers);
1037
+ headers.set("accept", "application/json");
1038
+ headers.append("accept", "application/jwk-set+json");
1039
+ const requestHeaders = Object.fromEntries(headers.entries());
1040
+ const headersKey = JSON.stringify(requestHeaders);
1041
+ const fetcher = options?.[customFetch] || fetch;
1042
+ const requestSignal = signal(url, options?.signal);
1043
+ jwksRequests ||= new WeakMap;
1044
+ const requests = jwksRequests.get(as) ?? new Set;
1045
+ for (const pending of requests) {
1046
+ if (pending.url === url.href && pending.headers === headersKey && pending.fetch === fetcher && pending.signal === requestSignal) {
1047
+ return pending.promise;
1048
+ }
1049
+ }
1050
+ const pending = {
1051
+ url: url.href,
1052
+ headers: headersKey,
1053
+ fetch: fetcher,
1054
+ signal: requestSignal,
1055
+ promise: Promise.resolve().then(() => fetcher(url.href, {
1056
+ body: undefined,
1057
+ headers: requestHeaders,
1058
+ method: "GET",
1059
+ redirect: "manual",
1060
+ signal: requestSignal
1061
+ })).then(processJwksResponse).then((jwks) => {
1062
+ const uat = epochTime();
1063
+ setJwksCache(as, jwks, uat);
1064
+ return { jwks, uat };
1065
+ }).finally(() => {
1066
+ requests.delete(pending);
1067
+ if (!requests.size) {
1068
+ jwksRequests.delete(as);
1069
+ }
1070
+ })
1071
+ };
1072
+ requests.add(pending);
1073
+ jwksRequests.set(as, requests);
1074
+ return pending.promise;
1075
+ }
1076
+ async function processJwksResponse(response) {
1077
+ if (!looseInstanceOf(response, Response)) {
1078
+ throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
1079
+ }
1080
+ if (response.status !== 200) {
1081
+ throw OPE('"response" is not a conform JSON Web Key Set response (unexpected HTTP status code)', RESPONSE_IS_NOT_CONFORM, response);
1082
+ }
1083
+ assertReadableResponse(response);
1084
+ const json = await getResponseJsonBody(response, (response) => assertContentTypes(response, "application/json", "application/jwk-set+json"));
1085
+ if (!Array.isArray(json.keys)) {
1086
+ throw OPE('"response" body "keys" property must be an array', INVALID_RESPONSE, { body: json });
1087
+ }
1088
+ if (!Array.prototype.every.call(json.keys, isJsonObject)) {
1089
+ throw OPE('"response" body "keys" property members must be JWK formatted objects', INVALID_RESPONSE, { body: json });
1090
+ }
1091
+ return json;
1092
+ }
1093
+ function supported(alg) {
1094
+ switch (alg) {
1095
+ case "PS256":
1096
+ case "ES256":
1097
+ case "RS256":
1098
+ case "PS384":
1099
+ case "ES384":
1100
+ case "RS384":
1101
+ case "PS512":
1102
+ case "ES512":
1103
+ case "RS512":
1104
+ case "Ed25519":
1105
+ case "EdDSA":
1106
+ case "ML-DSA-44":
1107
+ case "ML-DSA-65":
1108
+ case "ML-DSA-87":
1109
+ return true;
1110
+ default:
1111
+ return false;
1112
+ }
1113
+ }
1114
+ function checkSupportedJwsAlg(header) {
1115
+ if (!supported(header.alg)) {
1116
+ throw new UnsupportedOperationError('unsupported JWS "alg" identifier', {
1117
+ cause: { alg: header.alg }
1118
+ });
1119
+ }
1120
+ }
1121
+ function checkRsaKeyAlgorithm(key) {
1122
+ const { algorithm } = key;
1123
+ if (typeof algorithm.modulusLength !== "number" || algorithm.modulusLength < 2048) {
1124
+ throw new UnsupportedOperationError(`unsupported ${algorithm.name} modulusLength`, {
1125
+ cause: key
1126
+ });
1127
+ }
1128
+ }
1129
+ function ecdsaHashName(key) {
1130
+ const { algorithm } = key;
1131
+ switch (algorithm.namedCurve) {
1132
+ case "P-256":
1133
+ return "SHA-256";
1134
+ case "P-384":
1135
+ return "SHA-384";
1136
+ case "P-521":
1137
+ return "SHA-512";
1138
+ default:
1139
+ throw new UnsupportedOperationError("unsupported ECDSA namedCurve", { cause: key });
1140
+ }
1141
+ }
1142
+ function keyToSubtle(key) {
1143
+ switch (key.algorithm.name) {
1144
+ case "ECDSA":
1145
+ return {
1146
+ name: key.algorithm.name,
1147
+ hash: ecdsaHashName(key)
1148
+ };
1149
+ case "RSA-PSS": {
1150
+ checkRsaKeyAlgorithm(key);
1151
+ switch (key.algorithm.hash.name) {
1152
+ case "SHA-256":
1153
+ case "SHA-384":
1154
+ case "SHA-512":
1155
+ return {
1156
+ name: key.algorithm.name,
1157
+ saltLength: parseInt(key.algorithm.hash.name.slice(-3), 10) >> 3
1158
+ };
1159
+ default:
1160
+ throw new UnsupportedOperationError("unsupported RSA-PSS hash name", { cause: key });
1161
+ }
1162
+ }
1163
+ case "RSASSA-PKCS1-v1_5":
1164
+ checkRsaKeyAlgorithm(key);
1165
+ return key.algorithm.name;
1166
+ case "ML-DSA-44":
1167
+ case "ML-DSA-65":
1168
+ case "ML-DSA-87":
1169
+ case "Ed25519":
1170
+ return key.algorithm.name;
1171
+ }
1172
+ throw new UnsupportedOperationError("unsupported CryptoKey algorithm name", { cause: key });
1173
+ }
1174
+ async function validateJwsSignature(protectedHeader, payload, key, signature) {
1175
+ const data = buf(`${protectedHeader}.${payload}`);
1176
+ const algorithm = keyToSubtle(key);
1177
+ const verified = await crypto.subtle.verify(algorithm, key, signature, data);
1178
+ if (!verified) {
1179
+ throw OPE("JWT signature verification failed", INVALID_RESPONSE, {
1180
+ key,
1181
+ data,
1182
+ signature,
1183
+ algorithm
1184
+ });
1185
+ }
1186
+ }
1187
+ async function validateJwt(jws, checkAlg, clockSkew, clockTolerance, decryptJwt) {
1188
+ let { 0: protectedHeader, 1: payload, length } = jws.split(".");
1189
+ if (length === 5) {
1190
+ if (decryptJwt !== undefined) {
1191
+ jws = await decryptJwt(jws);
1192
+ ({ 0: protectedHeader, 1: payload, length } = jws.split("."));
1193
+ } else {
1194
+ throw new UnsupportedOperationError("JWE decryption is not configured", { cause: jws });
1195
+ }
1196
+ }
1197
+ if (length !== 3) {
1198
+ throw OPE("Invalid JWT", INVALID_RESPONSE, jws);
1199
+ }
1200
+ let header;
1201
+ try {
1202
+ header = JSON.parse(buf(b64u(protectedHeader)));
1203
+ } catch (cause) {
1204
+ throw OPE("failed to parse JWT Header body as base64url encoded JSON", PARSE_ERROR, cause);
1205
+ }
1206
+ if (!isJsonObject(header)) {
1207
+ throw OPE("JWT Header must be a top level object", INVALID_RESPONSE, jws);
1208
+ }
1209
+ checkAlg(header);
1210
+ if (header.crit !== undefined) {
1211
+ throw new UnsupportedOperationError('no JWT "crit" header parameter extensions are supported', {
1212
+ cause: { header }
1213
+ });
1214
+ }
1215
+ let claims;
1216
+ try {
1217
+ claims = JSON.parse(buf(b64u(payload)));
1218
+ } catch (cause) {
1219
+ throw OPE("failed to parse JWT Payload body as base64url encoded JSON", PARSE_ERROR, cause);
1220
+ }
1221
+ if (!isJsonObject(claims)) {
1222
+ throw OPE("JWT Payload must be a top level object", INVALID_RESPONSE, jws);
1223
+ }
1224
+ const now = epochTime() + clockSkew;
1225
+ if (claims.exp !== undefined) {
1226
+ if (typeof claims.exp !== "number") {
1227
+ throw OPE('unexpected JWT "exp" (expiration time) claim type', INVALID_RESPONSE, { claims });
1228
+ }
1229
+ if (claims.exp <= now - clockTolerance) {
1230
+ throw OPE('unexpected JWT "exp" (expiration time) claim value, expiration is past current timestamp', JWT_TIMESTAMP_CHECK, { claims, now, tolerance: clockTolerance, claim: "exp" });
1231
+ }
1232
+ }
1233
+ if (claims.iat !== undefined) {
1234
+ if (typeof claims.iat !== "number") {
1235
+ throw OPE('unexpected JWT "iat" (issued at) claim type', INVALID_RESPONSE, { claims });
1236
+ }
1237
+ }
1238
+ if (claims.iss !== undefined) {
1239
+ if (typeof claims.iss !== "string") {
1240
+ throw OPE('unexpected JWT "iss" (issuer) claim type', INVALID_RESPONSE, { claims });
1241
+ }
1242
+ }
1243
+ if (claims.nbf !== undefined) {
1244
+ if (typeof claims.nbf !== "number") {
1245
+ throw OPE('unexpected JWT "nbf" (not before) claim type', INVALID_RESPONSE, { claims });
1246
+ }
1247
+ if (claims.nbf > now + clockTolerance) {
1248
+ throw OPE('unexpected JWT "nbf" (not before) claim value', JWT_TIMESTAMP_CHECK, {
1249
+ claims,
1250
+ now,
1251
+ tolerance: clockTolerance,
1252
+ claim: "nbf"
1253
+ });
1254
+ }
1255
+ }
1256
+ if (claims.aud !== undefined) {
1257
+ if (typeof claims.aud !== "string" && !Array.isArray(claims.aud)) {
1258
+ throw OPE('unexpected JWT "aud" (audience) claim type', INVALID_RESPONSE, { claims });
1259
+ }
1260
+ }
1261
+ return { header, claims, jwt: jws };
1262
+ }
1263
+ async function consumeStream(request) {
1264
+ if (request.bodyUsed) {
1265
+ throw CodedTypeError("form_post Request instances must contain a readable body", ERR_INVALID_ARG_VALUE, { cause: request });
1266
+ }
1267
+ return request.text();
1268
+ }
1269
+ async function formPostResponse(request) {
1270
+ if (request.method !== "POST") {
1271
+ throw CodedTypeError("form_post responses are expected to use the POST method", ERR_INVALID_ARG_VALUE, { cause: request });
1272
+ }
1273
+ if (getContentType(request) !== "application/x-www-form-urlencoded") {
1274
+ throw CodedTypeError("form_post responses are expected to use the application/x-www-form-urlencoded content-type", ERR_INVALID_ARG_VALUE, { cause: request });
1275
+ }
1276
+ return consumeStream(request);
1277
+ }
1278
+ function checkSigningAlgorithm(client, issuer, fallback, header) {
1279
+ if (client !== undefined) {
1280
+ if (typeof client === "string" ? header.alg !== client : !client.includes(header.alg)) {
1281
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
1282
+ header,
1283
+ expected: client,
1284
+ reason: "client configuration"
1285
+ });
1286
+ }
1287
+ return;
1288
+ }
1289
+ if (Array.isArray(issuer)) {
1290
+ if (!issuer.includes(header.alg)) {
1291
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
1292
+ header,
1293
+ expected: issuer,
1294
+ reason: "authorization server metadata"
1295
+ });
1296
+ }
1297
+ return;
1298
+ }
1299
+ if (fallback !== undefined) {
1300
+ if (typeof fallback === "string" ? header.alg !== fallback : typeof fallback === "function" ? !fallback(header.alg) : !fallback.includes(header.alg)) {
1301
+ throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
1302
+ header,
1303
+ expected: fallback,
1304
+ reason: "default value"
1305
+ });
1306
+ }
1307
+ return;
1308
+ }
1309
+ throw OPE('missing client or server configuration to verify used JWT "alg" header parameter', undefined, { client, issuer, fallback });
1310
+ }
1311
+ function getURLSearchParameter(parameters, name) {
1312
+ const { 0: value, length } = parameters.getAll(name);
1313
+ if (length > 1) {
1314
+ throw OPE(`"${name}" parameter must be provided only once`, INVALID_RESPONSE);
1315
+ }
1316
+ return value;
1317
+ }
1318
+ var skipStateCheck = Symbol();
1319
+ var expectNoState = Symbol();
1320
+ function validateAuthResponse(as, client, parameters, expectedState) {
1321
+ assertAs(as);
1322
+ assertClient(client);
1323
+ if (parameters instanceof URL) {
1324
+ parameters = parameters.searchParams;
1325
+ }
1326
+ if (!(parameters instanceof URLSearchParams)) {
1327
+ throw CodedTypeError('"parameters" must be an instance of URLSearchParams, or URL', ERR_INVALID_ARG_TYPE);
1328
+ }
1329
+ if (getURLSearchParameter(parameters, "response")) {
1330
+ throw OPE('"parameters" contains a JARM response, use validateJwtAuthResponse() instead of validateAuthResponse()', INVALID_RESPONSE, { parameters });
1331
+ }
1332
+ const iss = getURLSearchParameter(parameters, "iss");
1333
+ const state = getURLSearchParameter(parameters, "state");
1334
+ if (!iss && as.authorization_response_iss_parameter_supported) {
1335
+ throw OPE('response parameter "iss" (issuer) missing', INVALID_RESPONSE, { parameters });
1336
+ }
1337
+ if (iss && iss !== as.issuer) {
1338
+ throw OPE('unexpected "iss" (issuer) response parameter value', INVALID_RESPONSE, {
1339
+ expected: as.issuer,
1340
+ parameters
1341
+ });
1342
+ }
1343
+ switch (expectedState) {
1344
+ case undefined:
1345
+ case expectNoState:
1346
+ if (state !== undefined) {
1347
+ throw OPE('unexpected "state" response parameter encountered', INVALID_RESPONSE, {
1348
+ expected: undefined,
1349
+ parameters
1350
+ });
1351
+ }
1352
+ break;
1353
+ case skipStateCheck:
1354
+ break;
1355
+ default:
1356
+ assertString(expectedState, '"expectedState" argument');
1357
+ if (state !== expectedState) {
1358
+ throw OPE(state === undefined ? 'response parameter "state" missing' : 'unexpected "state" response parameter value', INVALID_RESPONSE, { expected: expectedState, parameters });
1359
+ }
1360
+ }
1361
+ const error = getURLSearchParameter(parameters, "error");
1362
+ if (error) {
1363
+ throw new AuthorizationResponseError("authorization response from the server is an error", {
1364
+ cause: parameters
1365
+ });
1366
+ }
1367
+ const id_token = getURLSearchParameter(parameters, "id_token");
1368
+ const token = getURLSearchParameter(parameters, "token");
1369
+ if (id_token !== undefined || token !== undefined) {
1370
+ throw new UnsupportedOperationError("implicit and hybrid flows are not supported");
1371
+ }
1372
+ return brand(new URLSearchParams(parameters));
1373
+ }
1374
+ function algToSubtle(alg) {
1375
+ switch (alg) {
1376
+ case "PS256":
1377
+ case "PS384":
1378
+ case "PS512":
1379
+ return { name: "RSA-PSS", hash: `SHA-${alg.slice(-3)}` };
1380
+ case "RS256":
1381
+ case "RS384":
1382
+ case "RS512":
1383
+ return { name: "RSASSA-PKCS1-v1_5", hash: `SHA-${alg.slice(-3)}` };
1384
+ case "ES256":
1385
+ case "ES384":
1386
+ return { name: "ECDSA", namedCurve: `P-${alg.slice(-3)}` };
1387
+ case "ES512":
1388
+ return { name: "ECDSA", namedCurve: "P-521" };
1389
+ case "EdDSA":
1390
+ return "Ed25519";
1391
+ case "Ed25519":
1392
+ case "ML-DSA-44":
1393
+ case "ML-DSA-65":
1394
+ case "ML-DSA-87":
1395
+ return alg;
1396
+ default:
1397
+ throw new UnsupportedOperationError("unsupported JWS algorithm", { cause: { alg } });
1398
+ }
1399
+ }
1400
+ async function importJwk(alg, jwk) {
1401
+ const { ext, key_ops, use, ...key } = jwk;
1402
+ return crypto.subtle.importKey("jwk", key, algToSubtle(alg), true, ["verify"]);
1403
+ }
1404
+ async function getResponseJsonBody(response, check = assertApplicationJson) {
1405
+ let json;
1406
+ try {
1407
+ json = await response.json();
1408
+ } catch (cause) {
1409
+ check(response);
1410
+ throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
1411
+ }
1412
+ if (!isJsonObject(json)) {
1413
+ throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
1414
+ }
1415
+ return json;
1416
+ }
1417
+ var _nodiscoverycheck = Symbol();
1418
+ var _expectedIssuer = Symbol();
1419
+
1420
+ // node_modules/openid-client/build/index.js
1421
+ var headers;
1422
+ var USER_AGENT2;
1423
+ if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) {
1424
+ const NAME = "openid-client";
1425
+ const VERSION = "v6.8.4";
1426
+ USER_AGENT2 = `${NAME}/${VERSION}`;
1427
+ headers = { "user-agent": USER_AGENT2 };
1428
+ }
1429
+ var int = (config) => {
1430
+ return props.get(config);
1431
+ };
1432
+ var props;
1433
+ var tbi;
1434
+ function ClientSecretPost2(clientSecret) {
1435
+ if (clientSecret !== undefined) {
1436
+ return ClientSecretPost(clientSecret);
1437
+ }
1438
+ tbi ||= new WeakMap;
1439
+ return (as, client, body, headers) => {
1440
+ let auth;
1441
+ if (!(auth = tbi.get(client))) {
1442
+ assertString2(client.client_secret, '"metadata.client_secret"');
1443
+ auth = ClientSecretPost(client.client_secret);
1444
+ tbi.set(client, auth);
1445
+ }
1446
+ return auth(as, client, body, headers);
1447
+ };
1448
+ }
1449
+ function assertString2(input, it) {
1450
+ if (typeof input !== "string") {
1451
+ throw CodedTypeError2(`${it} must be a string`, ERR_INVALID_ARG_TYPE2);
1452
+ }
1453
+ if (input.length === 0) {
1454
+ throw CodedTypeError2(`${it} must not be empty`, ERR_INVALID_ARG_VALUE2);
1455
+ }
1456
+ }
1457
+ function ClientSecretBasic2(clientSecret) {
1458
+ if (clientSecret !== undefined) {
1459
+ return ClientSecretBasic(clientSecret);
1460
+ }
1461
+ tbi ||= new WeakMap;
1462
+ return (as, client, body, headers) => {
1463
+ let auth;
1464
+ if (!(auth = tbi.get(client))) {
1465
+ assertString2(client.client_secret, '"metadata.client_secret"');
1466
+ auth = ClientSecretBasic(client.client_secret);
1467
+ tbi.set(client, auth);
1468
+ }
1469
+ return auth(as, client, body, headers);
1470
+ };
1471
+ }
1472
+ function None2() {
1473
+ return None();
1474
+ }
1475
+ var customFetch2 = customFetch;
1476
+ var ERR_INVALID_ARG_VALUE2 = "ERR_INVALID_ARG_VALUE";
1477
+ var ERR_INVALID_ARG_TYPE2 = "ERR_INVALID_ARG_TYPE";
1478
+ function CodedTypeError2(message, code, cause) {
1479
+ const err = new TypeError(message, { cause });
1480
+ Object.assign(err, { code });
1481
+ return err;
1482
+ }
1483
+ function calculatePKCECodeChallenge2(codeVerifier) {
1484
+ return calculatePKCECodeChallenge(codeVerifier);
1485
+ }
1486
+ function randomPKCECodeVerifier() {
1487
+ return generateRandomCodeVerifier();
1488
+ }
1489
+ function randomNonce() {
1490
+ return generateRandomNonce();
1491
+ }
1492
+ function randomState() {
1493
+ return generateRandomState();
1494
+ }
1495
+
1496
+ class ClientError extends Error {
1497
+ code;
1498
+ constructor(message, options) {
1499
+ super(message, options);
1500
+ this.name = this.constructor.name;
1501
+ this.code = options?.code;
1502
+ Error.captureStackTrace?.(this, this.constructor);
1503
+ }
1504
+ }
1505
+ var decoder2 = new TextDecoder;
1506
+ function e(msg, cause, code) {
1507
+ return new ClientError(msg, { cause, code });
1508
+ }
1509
+ function errorHandler(err) {
1510
+ if (err instanceof TypeError || err instanceof ClientError || err instanceof ResponseBodyError || err instanceof AuthorizationResponseError || err instanceof WWWAuthenticateChallengeError) {
1511
+ throw err;
1512
+ }
1513
+ if (err instanceof OperationProcessingError) {
1514
+ switch (err.code) {
1515
+ case HTTP_REQUEST_FORBIDDEN:
1516
+ throw e("only requests to HTTPS are allowed", err, err.code);
1517
+ case REQUEST_PROTOCOL_FORBIDDEN:
1518
+ throw e("only requests to HTTP or HTTPS are allowed", err, err.code);
1519
+ case RESPONSE_IS_NOT_CONFORM:
1520
+ throw e("unexpected HTTP response status code", err.cause, err.code);
1521
+ case RESPONSE_IS_NOT_JSON:
1522
+ throw e("unexpected response content-type", err.cause, err.code);
1523
+ case PARSE_ERROR:
1524
+ throw e("parsing error occured", err, err.code);
1525
+ case INVALID_RESPONSE:
1526
+ throw e("invalid response encountered", err, err.code);
1527
+ case JWT_CLAIM_COMPARISON:
1528
+ throw e("unexpected JWT claim value encountered", err, err.code);
1529
+ case JSON_ATTRIBUTE_COMPARISON:
1530
+ throw e("unexpected JSON attribute value encountered", err, err.code);
1531
+ case JWT_TIMESTAMP_CHECK:
1532
+ throw e("JWT timestamp claim value failed validation", err, err.code);
1533
+ default:
1534
+ throw e(err.message, err, err.code);
1535
+ }
1536
+ }
1537
+ if (err instanceof UnsupportedOperationError) {
1538
+ throw e("unsupported operation", err, err.code);
1539
+ }
1540
+ if (err instanceof DOMException) {
1541
+ switch (err.name) {
1542
+ case "OperationError":
1543
+ throw e("runtime operation error", err, UNSUPPORTED_OPERATION);
1544
+ case "NotSupportedError":
1545
+ throw e("runtime unsupported operation", err, UNSUPPORTED_OPERATION);
1546
+ case "TimeoutError":
1547
+ throw e("operation timed out", err, "OAUTH_TIMEOUT");
1548
+ case "AbortError":
1549
+ throw e("operation aborted", err, "OAUTH_ABORT");
1550
+ }
1551
+ }
1552
+ throw new ClientError("something went wrong", { cause: err });
1553
+ }
1554
+ function handleEntraId(server, as, options) {
1555
+ if (server.origin === "https://login.microsoftonline.com" && (!options?.algorithm || options.algorithm === "oidc")) {
1556
+ as[kEntraId] = true;
1557
+ return true;
1558
+ }
1559
+ return false;
1560
+ }
1561
+ function handleB2Clogin(server, options) {
1562
+ if (server.hostname.endsWith(".b2clogin.com") && (!options?.algorithm || options.algorithm === "oidc")) {
1563
+ return true;
1564
+ }
1565
+ return false;
1566
+ }
1567
+ async function discovery(server, clientId, metadata, clientAuthentication, options) {
1568
+ const as = await performDiscovery2(server, options);
1569
+ const instance = new Configuration(as, clientId, metadata, clientAuthentication);
1570
+ let internals = int(instance);
1571
+ if (options?.[customFetch2]) {
1572
+ internals.fetch = options[customFetch2];
1573
+ }
1574
+ if (options?.timeout) {
1575
+ internals.timeout = options.timeout;
1576
+ }
1577
+ if (options?.execute) {
1578
+ for (const extension of options.execute) {
1579
+ extension(instance);
1580
+ }
1581
+ }
1582
+ return instance;
1583
+ }
1584
+ async function performDiscovery2(server, options) {
1585
+ if (!(server instanceof URL)) {
1586
+ throw CodedTypeError2('"server" must be an instance of URL', ERR_INVALID_ARG_TYPE2);
1587
+ }
1588
+ const resolve = !server.href.includes("/.well-known/");
1589
+ const timeout = options?.timeout ?? 30;
1590
+ const signal = AbortSignal.timeout(timeout * 1000);
1591
+ const as = await (resolve ? discoveryRequest(server, {
1592
+ algorithm: options?.algorithm,
1593
+ [customFetch]: options?.[customFetch2],
1594
+ [allowInsecureRequests]: options?.execute?.includes(allowInsecureRequests2),
1595
+ signal,
1596
+ headers: new Headers(headers)
1597
+ }) : (options?.[customFetch2] || fetch)((() => {
1598
+ checkProtocol(server, options?.execute?.includes(allowInsecureRequests2) ? false : true);
1599
+ return server.href;
1600
+ })(), {
1601
+ headers: Object.fromEntries(new Headers({ accept: "application/json", ...headers }).entries()),
1602
+ body: undefined,
1603
+ method: "GET",
1604
+ redirect: "manual",
1605
+ signal
1606
+ })).then((response) => processDiscoveryResponse(_nodiscoverycheck, response)).catch(errorHandler);
1607
+ if (resolve && new URL(as.issuer).href !== server.href) {
1608
+ handleEntraId(server, as, options) || handleB2Clogin(server, options) || (() => {
1609
+ throw new ClientError("discovered metadata issuer does not match the expected issuer", {
1610
+ code: JSON_ATTRIBUTE_COMPARISON,
1611
+ cause: {
1612
+ expected: server.href,
1613
+ body: as,
1614
+ attribute: "issuer"
1615
+ }
1616
+ });
1617
+ })();
1618
+ }
1619
+ return as;
1620
+ }
1621
+ function getServerHelpers(metadata) {
1622
+ return {
1623
+ supportsPKCE: {
1624
+ __proto__: null,
1625
+ value(method = "S256") {
1626
+ return metadata.code_challenge_methods_supported?.includes(method) === true;
1627
+ }
1628
+ }
1629
+ };
1630
+ }
1631
+ function addServerHelpers(metadata) {
1632
+ Object.defineProperties(metadata, getServerHelpers(metadata));
1633
+ }
1634
+ var kEntraId = Symbol();
1635
+
1636
+ class Configuration {
1637
+ constructor(server, clientId, metadata, clientAuthentication) {
1638
+ if (typeof clientId !== "string" || !clientId.length) {
1639
+ throw CodedTypeError2('"clientId" must be a non-empty string', ERR_INVALID_ARG_TYPE2);
1640
+ }
1641
+ if (typeof metadata === "string") {
1642
+ metadata = { client_secret: metadata };
1643
+ }
1644
+ if (metadata?.client_id !== undefined && clientId !== metadata.client_id) {
1645
+ throw CodedTypeError2('"clientId" and "metadata.client_id" must be the same', ERR_INVALID_ARG_VALUE2);
1646
+ }
1647
+ const client = {
1648
+ ...structuredClone(metadata),
1649
+ client_id: clientId
1650
+ };
1651
+ client[clockSkew] = metadata?.[clockSkew] ?? 0;
1652
+ client[clockTolerance] = metadata?.[clockTolerance] ?? 30;
1653
+ let auth;
1654
+ if (clientAuthentication) {
1655
+ auth = clientAuthentication;
1656
+ } else {
1657
+ if (typeof client.client_secret === "string" && client.client_secret.length) {
1658
+ auth = ClientSecretPost2(client.client_secret);
1659
+ } else {
1660
+ auth = None2();
1661
+ }
1662
+ }
1663
+ let c = Object.freeze(client);
1664
+ const clone = structuredClone(server);
1665
+ if (kEntraId in server) {
1666
+ clone[_expectedIssuer] = ({ claims: { tid } }) => server.issuer.replace("{tenantid}", tid);
1667
+ }
1668
+ let as = Object.freeze(clone);
1669
+ props ||= new WeakMap;
1670
+ props.set(this, {
1671
+ __proto__: null,
1672
+ as,
1673
+ c,
1674
+ auth,
1675
+ tlsOnly: true,
1676
+ jwksCache: {}
1677
+ });
1678
+ }
1679
+ serverMetadata() {
1680
+ const metadata = structuredClone(int(this).as);
1681
+ addServerHelpers(metadata);
1682
+ return metadata;
1683
+ }
1684
+ clientMetadata() {
1685
+ const metadata = structuredClone(int(this).c);
1686
+ return metadata;
1687
+ }
1688
+ get timeout() {
1689
+ return int(this).timeout;
1690
+ }
1691
+ set timeout(value) {
1692
+ int(this).timeout = value;
1693
+ }
1694
+ get [customFetch2]() {
1695
+ return int(this).fetch;
1696
+ }
1697
+ set [customFetch2](value) {
1698
+ int(this).fetch = value;
1699
+ }
1700
+ }
1701
+ Object.freeze(Configuration.prototype);
1702
+ function getHelpers(response) {
1703
+ let exp = undefined;
1704
+ if (response.expires_in !== undefined) {
1705
+ const now = new Date;
1706
+ now.setSeconds(now.getSeconds() + response.expires_in);
1707
+ exp = now.getTime();
1708
+ }
1709
+ return {
1710
+ expiresIn: {
1711
+ __proto__: null,
1712
+ value() {
1713
+ if (exp) {
1714
+ const now = Date.now();
1715
+ if (exp > now) {
1716
+ return Math.floor((exp - now) / 1000);
1717
+ }
1718
+ return 0;
1719
+ }
1720
+ return;
1721
+ }
1722
+ },
1723
+ claims: {
1724
+ __proto__: null,
1725
+ value() {
1726
+ try {
1727
+ return getValidatedIdTokenClaims(this);
1728
+ } catch {
1729
+ return;
1730
+ }
1731
+ }
1732
+ }
1733
+ };
1734
+ }
1735
+ function addHelpers(response) {
1736
+ Object.defineProperties(response, getHelpers(response));
1737
+ }
1738
+ function allowInsecureRequests2(config) {
1739
+ int(config).tlsOnly = false;
1740
+ }
1741
+ function enableNonRepudiationChecks(config) {
1742
+ checkConfig(config);
1743
+ int(config).nonRepudiation = (response) => {
1744
+ const { as, fetch: fetch2, tlsOnly, timeout, jwksCache: jwksCache2 } = int(config);
1745
+ return validateApplicationLevelSignature(as, response, {
1746
+ [customFetch]: fetch2,
1747
+ [allowInsecureRequests]: !tlsOnly,
1748
+ headers: new Headers(headers),
1749
+ signal: signal2(timeout),
1750
+ [jwksCache]: jwksCache2
1751
+ }).catch(errorHandler);
1752
+ };
1753
+ }
1754
+ function stripParams(url) {
1755
+ url = new URL(url);
1756
+ url.search = "";
1757
+ url.hash = "";
1758
+ return url.href;
1759
+ }
1760
+ function webInstanceOf(input, toStringTag) {
1761
+ try {
1762
+ return Object.getPrototypeOf(input)[Symbol.toStringTag] === toStringTag;
1763
+ } catch {
1764
+ return false;
1765
+ }
1766
+ }
1767
+ async function authorizationCodeGrant(config, currentUrl, checks, tokenEndpointParameters, options) {
1768
+ checkConfig(config);
1769
+ if (options?.flag !== retry && !(currentUrl instanceof URL) && !webInstanceOf(currentUrl, "Request")) {
1770
+ throw CodedTypeError2('"currentUrl" must be an instance of URL, or Request', ERR_INVALID_ARG_TYPE2);
1771
+ }
1772
+ let authResponse;
1773
+ let redirectUri;
1774
+ const { as, c, auth, fetch: fetch2, tlsOnly, jarm, hybrid, nonRepudiation, timeout, decrypt, implicit } = int(config);
1775
+ if (options?.flag === retry) {
1776
+ authResponse = options.authResponse;
1777
+ redirectUri = options.redirectUri;
1778
+ } else {
1779
+ if (!(currentUrl instanceof URL)) {
1780
+ const request = currentUrl;
1781
+ currentUrl = new URL(currentUrl.url);
1782
+ switch (request.method) {
1783
+ case "GET":
1784
+ break;
1785
+ case "POST":
1786
+ const params = new URLSearchParams(await formPostResponse(request));
1787
+ if (hybrid) {
1788
+ currentUrl.hash = params.toString();
1789
+ } else {
1790
+ for (const [k, v] of params.entries()) {
1791
+ currentUrl.searchParams.append(k, v);
1792
+ }
1793
+ }
1794
+ break;
1795
+ default:
1796
+ throw CodedTypeError2("unexpected Request HTTP method", ERR_INVALID_ARG_VALUE2);
1797
+ }
1798
+ }
1799
+ redirectUri = stripParams(currentUrl);
1800
+ switch (true) {
1801
+ case !!jarm:
1802
+ authResponse = await jarm(currentUrl, checks?.expectedState);
1803
+ break;
1804
+ case !!hybrid:
1805
+ authResponse = await hybrid(currentUrl, checks?.expectedNonce, checks?.expectedState, checks?.maxAge);
1806
+ break;
1807
+ case !!implicit:
1808
+ throw new TypeError("authorizationCodeGrant() cannot be used by response_type=id_token clients");
1809
+ default:
1810
+ try {
1811
+ authResponse = validateAuthResponse(as, c, currentUrl.searchParams, checks?.expectedState);
1812
+ } catch (err) {
1813
+ errorHandler(err);
1814
+ }
1815
+ }
1816
+ }
1817
+ const response = await authorizationCodeGrantRequest(as, c, auth, authResponse, redirectUri, checks?.pkceCodeVerifier || nopkce, {
1818
+ additionalParameters: tokenEndpointParameters,
1819
+ [customFetch]: fetch2,
1820
+ [allowInsecureRequests]: !tlsOnly,
1821
+ DPoP: options?.DPoP,
1822
+ headers: new Headers(headers),
1823
+ signal: signal2(timeout)
1824
+ }).catch(errorHandler);
1825
+ if (typeof checks?.expectedNonce === "string" || typeof checks?.maxAge === "number") {
1826
+ checks.idTokenExpected = true;
1827
+ }
1828
+ const p = processAuthorizationCodeResponse(as, c, response, {
1829
+ expectedNonce: checks?.expectedNonce,
1830
+ maxAge: checks?.maxAge,
1831
+ requireIdToken: checks?.idTokenExpected,
1832
+ [jweDecrypt]: decrypt
1833
+ });
1834
+ let result;
1835
+ try {
1836
+ result = await p;
1837
+ } catch (err) {
1838
+ if (retryable(err, options)) {
1839
+ return authorizationCodeGrant(config, undefined, checks, tokenEndpointParameters, {
1840
+ ...options,
1841
+ flag: retry,
1842
+ authResponse,
1843
+ redirectUri
1844
+ });
1845
+ }
1846
+ errorHandler(err);
1847
+ }
1848
+ result.id_token && await nonRepudiation?.(response);
1849
+ addHelpers(result);
1850
+ return result;
1851
+ }
1852
+ function buildAuthorizationUrl(config, parameters) {
1853
+ checkConfig(config);
1854
+ const { as, c, tlsOnly, hybrid, jarm, implicit } = int(config);
1855
+ const authorizationEndpoint = resolveEndpoint(as, "authorization_endpoint", false, tlsOnly);
1856
+ parameters = new URLSearchParams(parameters);
1857
+ if (!parameters.has("client_id")) {
1858
+ parameters.set("client_id", c.client_id);
1859
+ }
1860
+ if (!parameters.has("request_uri") && !parameters.has("request")) {
1861
+ if (!parameters.has("response_type")) {
1862
+ parameters.set("response_type", hybrid ? "code id_token" : implicit ? "id_token" : "code");
1863
+ }
1864
+ if (implicit && !parameters.has("nonce")) {
1865
+ throw CodedTypeError2("response_type=id_token clients must provide a nonce parameter in their authorization request parameters", ERR_INVALID_ARG_VALUE2);
1866
+ }
1867
+ if (jarm) {
1868
+ parameters.set("response_mode", "jwt");
1869
+ }
1870
+ }
1871
+ for (const [k, v] of parameters.entries()) {
1872
+ authorizationEndpoint.searchParams.append(k, v);
1873
+ }
1874
+ return authorizationEndpoint;
1875
+ }
1876
+ function checkConfig(input) {
1877
+ if (!(input instanceof Configuration)) {
1878
+ throw CodedTypeError2('"config" must be an instance of Configuration', ERR_INVALID_ARG_TYPE2);
1879
+ }
1880
+ if (Object.getPrototypeOf(input) !== Configuration.prototype) {
1881
+ throw CodedTypeError2("subclassing Configuration is not allowed", ERR_INVALID_ARG_VALUE2);
1882
+ }
1883
+ }
1884
+ function signal2(timeout) {
1885
+ return timeout ? AbortSignal.timeout(timeout * 1000) : undefined;
1886
+ }
1887
+ function retryable(err, options) {
1888
+ if (options?.DPoP && options.flag !== retry) {
1889
+ return isDPoPNonceError(err);
1890
+ }
1891
+ return false;
1892
+ }
1893
+ var retry = Symbol();
1894
+ async function fetchProtectedResource(config, accessToken, url, method, body, headers, options) {
1895
+ checkConfig(config);
1896
+ headers ||= new Headers;
1897
+ if (!headers.has("user-agent")) {
1898
+ headers.set("user-agent", USER_AGENT2);
1899
+ }
1900
+ const { fetch: fetch2, tlsOnly, timeout } = int(config);
1901
+ const exec = protectedResourceRequest(accessToken, method, url, headers, body, {
1902
+ [customFetch]: fetch2,
1903
+ [allowInsecureRequests]: !tlsOnly,
1904
+ DPoP: options?.DPoP,
1905
+ signal: signal2(timeout)
1906
+ });
1907
+ let result;
1908
+ try {
1909
+ result = await exec;
1910
+ } catch (err) {
1911
+ if (retryable(err, options)) {
1912
+ return fetchProtectedResource(config, accessToken, url, method, body, headers, {
1913
+ ...options,
1914
+ flag: retry
1915
+ });
1916
+ }
1917
+ errorHandler(err);
1918
+ }
1919
+ return result;
1920
+ }
1921
+
1922
+ // plugins/user-auth/server/control.ts
1923
+ import { isDeepStrictEqual } from "util";
1924
+
1925
+ // plugins/user-auth/server/errors.ts
1926
+ class AccountError extends Error {
1927
+ code;
1928
+ status;
1929
+ constructor(code, status = 400) {
1930
+ super(code);
1931
+ this.code = code;
1932
+ this.status = status;
1933
+ }
1934
+ }
1935
+ function fail(code, status = 400) {
1936
+ throw new AccountError(code, status);
1937
+ }
1938
+
1939
+ // plugins/user-auth/server/control.ts
1940
+ import { createHash, createHmac, randomBytes as randomBytes2, randomUUID, timingSafeEqual } from "crypto";
1941
+
1942
+ // packages/core/src/plugin-logger.ts
1943
+ var sinkKey = Symbol.for("@jeffusion/bungee/plugin-log-sink");
1944
+ function createLogger(bindings = {}) {
1945
+ const method = (level) => (value, message) => {
1946
+ const sink = globalThis[sinkKey];
1947
+ if (sink) {
1948
+ if (typeof value === "string")
1949
+ sink[level](bindings, value);
1950
+ else
1951
+ sink[level]({ ...bindings, ...value }, message);
1952
+ } else if (level !== "debug" || process.env.LOG_LEVEL === "debug") {
1953
+ const write = level === "fatal" ? console.error : console[level];
1954
+ write(message ?? (typeof value === "string" ? value : ""), typeof value === "string" ? bindings : { ...bindings, ...value });
1955
+ }
1956
+ };
1957
+ return {
1958
+ info: method("info"),
1959
+ warn: method("warn"),
1960
+ error: method("error"),
1961
+ debug: method("debug"),
1962
+ fatal: method("fatal"),
1963
+ child: (extra) => createLogger({ ...bindings, ...extra })
1964
+ };
1965
+ }
1966
+ var logger = createLogger();
1967
+
1968
+ // packages/core/src/hooks/impl.ts
1969
+ var WATERFALL_VIEW_TRANSFORM = Symbol("waterfall-view-transform");
1970
+ // packages/core/src/plugin-extensions.ts
1971
+ var ANONYMOUS_PRINCIPAL = Object.freeze({ domain: "anonymous", keyId: "", credentialVersion: 0 });
1972
+ // packages/core/src/plugin-durable-state.ts
1973
+ function isDurableStateConflictError(error) {
1974
+ return error instanceof Error && "code" in error && error.code === "durable_state_conflict";
1975
+ }
1976
+ var MAX_BYTES = 1024 * 1024;
1977
+ // packages/core/src/plugin-services/host-rpc.ts
1978
+ import { AsyncLocalStorage } from "async_hooks";
1979
+
1980
+ // packages/core/src/plugin-services/wire-contract.ts
1981
+ var RPC_JSON_MAX_BYTES = 64 * 1024;
1982
+ var RPC_CONTRACT_MAX_BYTES = 256 * 1024;
1983
+ var RPC_CALL_PURPOSES = new Set(["bootstrap", "background", "management", "request", "attempt"]);
1984
+ var RPC_COMMAND_DEDUPLICATION = new Set(["local-transaction", "external-contract", "none"]);
1985
+ var RPC_COMMAND_POLICY_KEYS = new Set(["deduplication", "resultRetentionMs", "quotaBytes", "maxResultBytes"]);
1986
+
1987
+ // packages/core/src/plugin-services/rpc-runtime.ts
1988
+ var PROCESSES = new Set(["control", "worker", "ingress"]);
1989
+ var SCOPES = new Set(["global", "binding"]);
1990
+ var PURPOSES = new Set(["bootstrap", "background", "management", "request", "attempt"]);
1991
+ var ENDPOINTS = new WeakMap;
1992
+ var REVOKED = new WeakMap;
1993
+
1994
+ // packages/core/src/plugin-services/host-rpc.ts
1995
+ var SERVICE_ERROR_MESSAGES = {
1996
+ unauthorized: "RPC call is not authorized",
1997
+ not_ready: "RPC endpoint is not ready",
1998
+ retired: "RPC endpoint is retiring",
1999
+ revoked: "RPC endpoint generation was revoked",
2000
+ unsupported_method: "RPC method is not published",
2001
+ wrong_purpose: "RPC method is not published for this purpose",
2002
+ invalid_input: "RPC input is not valid",
2003
+ invalid_output: "RPC output is not valid",
2004
+ invalid_operation_id: "RPC command operation id is invalid",
2005
+ overloaded: "RPC runtime is at capacity",
2006
+ cancelled: "RPC call was cancelled",
2007
+ timeout: "RPC call deadline exceeded",
2008
+ unknown: "RPC command outcome is unknown",
2009
+ capability_unavailable: "RPC command execution is unavailable",
2010
+ closed: "RPC runtime is closed",
2011
+ failed: "RPC call failed",
2012
+ conflict: "RPC command identity conflicts with an existing record",
2013
+ expired: "RPC command result has expired",
2014
+ pending: "RPC command is still pending",
2015
+ rejected: "RPC command was rejected",
2016
+ missing: "RPC command has no stored result",
2017
+ storage_failure: "RPC command storage failed",
2018
+ deadlock: "RPC call chain would deadlock",
2019
+ call_depth_exceeded: "RPC call depth exceeded",
2020
+ undeclared: "RPC service is not declared by the manifest",
2021
+ unavailable: "RPC service is unavailable",
2022
+ ambiguous_placement: "RPC service placement is ambiguous",
2023
+ invalid_registration: "RPC registration is invalid"
2024
+ };
2025
+ var SERVICE_ERROR_CODES = new Set(Object.keys(SERVICE_ERROR_MESSAGES));
2026
+ var EMPTY_OPTIONS = Object.freeze({});
2027
+ var FRAME_STORE = new AsyncLocalStorage;
2028
+ var CALLEE_INFO = new WeakMap;
2029
+ // packages/core/src/plugin-services/peer-channel-protocol.ts
2030
+ var CHANNEL_REQUEST_MAX_BYTES = 4 * 1024;
2031
+ var CHANNEL_RESULT_MAX_BYTES = 60 * 1024;
2032
+ var CHANNEL_MAX_CHUNK_BYTES = 60 * 1024;
2033
+ var PLUGIN_CHANNEL_LANES = Object.freeze(["event", "snapshot", "stream"]);
2034
+ var ERROR_CODES = new Set([
2035
+ "unavailable",
2036
+ "ambiguous",
2037
+ "unauthorized",
2038
+ "invalid",
2039
+ "overloaded",
2040
+ "expired",
2041
+ "conflict",
2042
+ "gap",
2043
+ "truncated",
2044
+ "rejected",
2045
+ "cancelled",
2046
+ "closed",
2047
+ "unknown",
2048
+ "failed"
2049
+ ]);
2050
+ var RPC_CALL_PURPOSES2 = new Set(["bootstrap", "background", "management", "request", "attempt"]);
2051
+
2052
+ // packages/core/src/plugin-services/peer-channel-hub.ts
2053
+ var CHANNEL_MAX_TRANSFER_BUFFER_BYTES = 4 * CHANNEL_MAX_CHUNK_BYTES;
2054
+ var CHANNEL_MAX_TOTAL_BUFFER_BYTES = 16 * CHANNEL_MAX_CHUNK_BYTES;
2055
+ var CHANNEL_STREAM_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
2056
+ var CHANNEL_STREAM_MAX_CREDIT_BYTES = 8 * CHANNEL_MAX_CHUNK_BYTES;
2057
+ var CHANNEL_STREAM_DEFAULT_CREDIT_BYTES = 4 * CHANNEL_MAX_CHUNK_BYTES;
2058
+ var CHANNEL_SNAPSHOT_MAX_BYTES = 64 * 1024 * 1024;
2059
+ var CHANNEL_EVENT_MAX_PAYLOAD_BYTES = 60 * 1024;
2060
+ var EMPTY = new Uint8Array(0);
2061
+ var HANDLE_REGISTRATIONS = new WeakMap;
2062
+
2063
+ // packages/core/src/plugin-services/snapshot-store.ts
2064
+ var DEFAULT_CHUNK_BYTES = 60 * 1024;
2065
+ var SNAPSHOT_FAMILY_REFS = new Map;
2066
+
2067
+ // packages/core/src/plugin-services.ts
2068
+ var CHANNEL_KINDS = new Set(["events", "snapshot", "stream"]);
2069
+ // packages/core/src/gateway/retry-action.ts
2070
+ var retryActionBrand = Symbol.for("@jeffusion/bungee/request-retry-action");
2071
+ // packages/core/src/token-stats-window.ts
2072
+ var HOUR = 3600000;
2073
+ var TOKEN_STATS_RETENTION_MS = 31 * 24 * HOUR;
2074
+ // plugins/user-auth/server/control.ts
2075
+ var GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2076
+ var MICROSOFT = "https://login.microsoftonline.com/";
2077
+ var CONSUMERS = "9188040d-6c67-4c5b-b112-36a304b66dad";
2078
+ var ISSUER_TEMPLATE = MICROSOFT + "{tenantid}/v2.0";
2079
+ var LIMIT = 1024 * 1024;
2080
+ function providerFields(value, fields) {
2081
+ if (Object.keys(value).some((key) => !fields.includes(key)))
2082
+ fail("invalid_input");
2083
+ }
2084
+ function providerRecord(value) {
2085
+ if (!value || typeof value !== "object" || Array.isArray(value))
2086
+ fail("invalid_input");
2087
+ return value;
2088
+ }
2089
+ function providerUrl(value) {
2090
+ let url;
2091
+ try {
2092
+ if (typeof value !== "string" || !value || value.length > 2048)
2093
+ fail("invalid_oidc_url");
2094
+ url = new URL(value);
2095
+ } catch {
2096
+ return fail("invalid_oidc_url");
2097
+ }
2098
+ if (url.username || url.password || url.hash || typeof value === "string" && (value.includes("#") || /^https?:\/\/[^/?#]*@/i.test(value)) || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
2099
+ fail("invalid_oidc_url");
2100
+ return url;
2101
+ }
2102
+ function parseProvider(provider, input, scopesInput) {
2103
+ if (!["google", "microsoft", "github", "oidc"].includes(provider))
2104
+ fail("invalid_provider");
2105
+ const p = provider, c = providerRecord(input);
2106
+ let issuer, configuration;
2107
+ if (p === "google" || p === "github") {
2108
+ providerFields(c, []);
2109
+ issuer = p === "google" ? "https://accounts.google.com" : "https://github.com";
2110
+ configuration = {};
2111
+ } else if (p === "microsoft") {
2112
+ providerFields(c, ["accountType", "tenantId"]);
2113
+ if (!["tenant", "organizations", "consumers", "common"].includes(c.accountType))
2114
+ fail("invalid_microsoft_configuration");
2115
+ if (c.accountType === "tenant" ? typeof c.tenantId !== "string" || !GUID.test(c.tenantId) : Object.hasOwn(c, "tenantId"))
2116
+ fail("invalid_microsoft_configuration");
2117
+ configuration = { accountType: c.accountType, ...c.accountType === "tenant" ? { tenantId: c.tenantId.toLowerCase() } : {} };
2118
+ issuer = MICROSOFT + (configuration.tenantId ?? configuration.accountType) + "/v2.0";
2119
+ } else {
2120
+ if (!["issuer", "discovery", "manual"].includes(c.mode))
2121
+ fail("invalid_oidc_configuration");
2122
+ const fields = c.mode === "issuer" ? ["mode", "issuer"] : c.mode === "discovery" ? ["mode", "issuer", "discoveryUrl"] : ["mode", "issuer", "authorizationEndpoint", "tokenEndpoint", "jwksUri", "tokenEndpointAuthMethod", "idTokenSigningAlg"];
2123
+ providerFields(c, fields);
2124
+ providerUrl(c.issuer);
2125
+ issuer = c.issuer;
2126
+ if (new URL(issuer).search)
2127
+ fail("invalid_oidc_url");
2128
+ if (c.mode === "discovery")
2129
+ providerUrl(c.discoveryUrl);
2130
+ if (c.mode === "manual") {
2131
+ for (const field of ["authorizationEndpoint", "tokenEndpoint", "jwksUri"])
2132
+ providerUrl(c[field]);
2133
+ if (c.tokenEndpointAuthMethod !== undefined && !["client_secret_basic", "client_secret_post"].includes(c.tokenEndpointAuthMethod))
2134
+ fail("oidc_auth_method_unsupported");
2135
+ if (c.idTokenSigningAlg !== undefined && !["RS256", "PS256", "ES256"].includes(c.idTokenSigningAlg))
2136
+ fail("oidc_signing_alg_unsupported");
2137
+ }
2138
+ configuration = { ...c };
2139
+ }
2140
+ if (p !== "oidc" && scopesInput !== undefined)
2141
+ fail("preset_scopes_fixed");
2142
+ const scopes = p === "github" ? ["read:user"] : scopesInput ?? ["openid", "profile", "email"];
2143
+ if (!Array.isArray(scopes) || !scopes.length || scopes.length > 16 || scopes.some((s) => typeof s !== "string" || !s || s.length > 128 || /\s/.test(s)) || p !== "github" && !scopes.includes("openid"))
2144
+ fail("invalid_input");
2145
+ return { provider: p, issuer, configuration, scopes };
2146
+ }
2147
+ async function boundedResponse(response) {
2148
+ if (!response.body)
2149
+ return response;
2150
+ const reader = response.body.getReader();
2151
+ const chunks = [];
2152
+ let size = 0;
2153
+ try {
2154
+ for (;; ) {
2155
+ const chunk = await reader.read();
2156
+ if (chunk.done)
2157
+ break;
2158
+ size += chunk.value.length;
2159
+ if (size > LIMIT) {
2160
+ await reader.cancel();
2161
+ fail("oidc_metadata_limit");
2162
+ }
2163
+ chunks.push(chunk.value);
2164
+ }
2165
+ } finally {
2166
+ reader.releaseLock();
2167
+ }
2168
+ const headers = new Headers(response.headers);
2169
+ headers.delete("content-encoding");
2170
+ headers.delete("content-length");
2171
+ return new Response(Buffer.concat(chunks), { status: response.status, statusText: response.statusText, headers });
2172
+ }
2173
+ async function limitedJson(response) {
2174
+ if (!response.ok)
2175
+ fail("oidc_metadata_failed", 502);
2176
+ const reader = response.body?.getReader();
2177
+ if (!reader)
2178
+ fail("oidc_metadata_invalid");
2179
+ let size = 0;
2180
+ const chunks = [];
2181
+ try {
2182
+ for (;; ) {
2183
+ const r = await reader.read();
2184
+ if (r.done)
2185
+ break;
2186
+ size += r.value.length;
2187
+ if (size > LIMIT) {
2188
+ await reader.cancel();
2189
+ fail("oidc_metadata_limit");
2190
+ }
2191
+ chunks.push(r.value);
2192
+ }
2193
+ } catch (error) {
2194
+ if (error instanceof AccountError)
2195
+ throw error;
2196
+ fail("oidc_network_failed", 502);
2197
+ } finally {
2198
+ reader.releaseLock();
2199
+ }
2200
+ try {
2201
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
2202
+ } catch {
2203
+ return fail("oidc_metadata_invalid");
2204
+ }
2205
+ }
2206
+ function discoveryUrl(issuer) {
2207
+ const url = providerUrl(issuer);
2208
+ url.pathname = url.pathname.replace(/\/$/, "") + "/.well-known/openid-configuration";
2209
+ return url.href;
2210
+ }
2211
+ function metadataCheck(value, expected) {
2212
+ const m = providerRecord(value);
2213
+ if (typeof m.issuer !== "string")
2214
+ fail("oidc_metadata_invalid");
2215
+ if (providerUrl(m.issuer).search)
2216
+ fail("invalid_oidc_url");
2217
+ if (expected !== undefined && m.issuer !== expected)
2218
+ fail("oidc_issuer_mismatch");
2219
+ for (const key of ["authorization_endpoint", "token_endpoint", "jwks_uri"])
2220
+ providerUrl(m[key]);
2221
+ for (const key of ["token_endpoint_auth_methods_supported", "id_token_signing_alg_values_supported"])
2222
+ if (m[key] !== undefined && (!Array.isArray(m[key]) || m[key].some((v) => typeof v !== "string")))
2223
+ fail("oidc_metadata_invalid");
2224
+ const methods = m.token_endpoint_auth_methods_supported;
2225
+ if (methods && !methods.some((m) => ["client_secret_basic", "client_secret_post"].includes(m)))
2226
+ fail("oidc_auth_method_unsupported");
2227
+ const algs = m.id_token_signing_alg_values_supported;
2228
+ if (!algs?.some((a) => ["RS256", "PS256", "ES256"].includes(a)))
2229
+ fail("oidc_signing_alg_unsupported");
2230
+ if (m.code_challenge_methods_supported !== undefined && (!Array.isArray(m.code_challenge_methods_supported) || !m.code_challenge_methods_supported.includes("S256")))
2231
+ fail("oidc_pkce_unsupported");
2232
+ return m;
2233
+ }
2234
+
2235
+ class LoginProviders {
2236
+ fetcher;
2237
+ signal;
2238
+ constructor(fetcher, signal) {
2239
+ this.fetcher = fetcher;
2240
+ this.signal = signal;
2241
+ }
2242
+ async network(input, init = {}) {
2243
+ providerUrl(input instanceof Request ? input.url : String(input));
2244
+ try {
2245
+ return await boundedResponse(await this.fetcher(input, { ...init, redirect: "error", signal: AbortSignal.any([this.signal, AbortSignal.timeout(5000), ...init.signal ? [init.signal] : []]) }));
2246
+ } catch (error) {
2247
+ if (error instanceof AccountError)
2248
+ throw error;
2249
+ fail("oidc_network_failed", 502);
2250
+ }
2251
+ }
2252
+ async discover(mode, value) {
2253
+ if (mode !== "issuer" && mode !== "discovery")
2254
+ fail("invalid_input");
2255
+ const url = providerUrl(value);
2256
+ if (mode === "issuer" && url.search)
2257
+ fail("invalid_oidc_url");
2258
+ const endpoint = mode === "issuer" ? discoveryUrl(value) : url.href;
2259
+ const m = metadataCheck(await limitedJson(await this.network(endpoint)), mode === "issuer" ? value : undefined);
2260
+ return { issuer: m.issuer, discoveryUrl: endpoint, authorizationEndpoint: m.authorization_endpoint, tokenEndpoint: m.token_endpoint, jwksUri: m.jwks_uri, tokenEndpointAuthMethods: m.token_endpoint_auth_methods_supported ?? ["client_secret_basic"], idTokenSigningAlgs: m.id_token_signing_alg_values_supported };
2261
+ }
2262
+ async metadata(c) {
2263
+ if (c.provider === "github")
2264
+ return { issuer: "https://github.com/login/oauth", authorization_response_iss_parameter_supported: true, authorization_endpoint: "https://github.com/login/oauth/authorize", token_endpoint: "https://github.com/login/oauth/access_token", token_endpoint_auth_methods_supported: ["client_secret_post"] };
2265
+ const configuration = c.configuration;
2266
+ if (c.provider === "oidc" && configuration.mode === "manual")
2267
+ return metadataCheck({ issuer: c.issuer, authorization_endpoint: configuration.authorizationEndpoint, token_endpoint: configuration.tokenEndpoint, jwks_uri: configuration.jwksUri, token_endpoint_auth_methods_supported: [configuration.tokenEndpointAuthMethod ?? "client_secret_basic"], id_token_signing_alg_values_supported: [configuration.idTokenSigningAlg ?? "RS256"] }, c.issuer);
2268
+ const endpoint = c.provider === "oidc" && configuration.mode === "discovery" ? configuration.discoveryUrl : discoveryUrl(c.issuer);
2269
+ const raw = await limitedJson(await this.network(endpoint));
2270
+ const ms = c.configuration;
2271
+ const expected = c.provider === "microsoft" && ms.accountType !== "tenant" ? ms.accountType === "consumers" ? MICROSOFT + CONSUMERS + "/v2.0" : ISSUER_TEMPLATE : c.issuer;
2272
+ return metadataCheck(raw, expected);
2273
+ }
2274
+ async validate(c) {
2275
+ await this.metadata(c);
2276
+ }
2277
+ microsoftIssuer(connection, claims) {
2278
+ const c = connection.configuration, tid = claims.tid;
2279
+ if (typeof tid !== "string" || !GUID.test(tid))
2280
+ fail("invalid_microsoft_tenant", 401);
2281
+ const normalized = tid.toLowerCase();
2282
+ if (claims.iss !== MICROSOFT + tid + "/v2.0" || c.accountType === "tenant" && normalized !== c.tenantId || c.accountType === "organizations" && normalized === CONSUMERS || c.accountType === "consumers" && normalized !== CONSUMERS)
2283
+ fail("microsoft_account_scope", 401);
2284
+ return claims.iss;
2285
+ }
2286
+ async configuration(c, secret) {
2287
+ const metadata = await this.metadata(c);
2288
+ const custom = c.configuration;
2289
+ const methods = metadata.token_endpoint_auth_methods_supported ?? ["client_secret_basic"];
2290
+ const method = c.provider === "oidc" && custom.mode === "manual" ? custom.tokenEndpointAuthMethod ?? "client_secret_basic" : methods.includes("client_secret_post") ? "client_secret_post" : "client_secret_basic";
2291
+ const auth = method === "client_secret_post" ? ClientSecretPost2(secret) : ClientSecretBasic2(secret);
2292
+ let actualIssuer;
2293
+ const customFetch = async (input, init) => {
2294
+ const { body, ...options } = init;
2295
+ const response = await this.network(input, { ...options, body: body instanceof Uint8Array ? new Uint8Array(body).buffer : body });
2296
+ if (c.provider === "github" && String(input) === metadata.token_endpoint) {
2297
+ let value;
2298
+ try {
2299
+ value = await response.clone().json();
2300
+ } catch {
2301
+ fail("oauth_token_failed", 502);
2302
+ }
2303
+ if (value && typeof value === "object" && "error" in value) {
2304
+ switch (value.error) {
2305
+ case "incorrect_client_credentials":
2306
+ fail("oauth_client_rejected", 502);
2307
+ case "redirect_uri_mismatch":
2308
+ fail("oauth_callback_mismatch", 502);
2309
+ case "bad_verification_code":
2310
+ fail("oauth_code_rejected", 401);
2311
+ case "unverified_user_email":
2312
+ fail("oauth_email_unverified", 401);
2313
+ default:
2314
+ fail("oauth_token_failed", 502);
2315
+ }
2316
+ }
2317
+ }
2318
+ if (c.provider !== "microsoft")
2319
+ return response;
2320
+ if (String(input) === metadata.token_endpoint && response.ok) {
2321
+ const value = await limitedJson(response.clone());
2322
+ try {
2323
+ if (typeof value.id_token !== "string")
2324
+ fail("invalid_oidc_identity", 401);
2325
+ const claims = providerRecord(JSON.parse(Buffer.from(value.id_token.split(".")[1], "base64url").toString()));
2326
+ actualIssuer = this.microsoftIssuer(c, claims);
2327
+ } catch (error) {
2328
+ if (error instanceof AccountError)
2329
+ throw error;
2330
+ fail("invalid_oidc_identity", 401);
2331
+ }
2332
+ }
2333
+ if (String(input) === metadata.jwks_uri) {
2334
+ if (!actualIssuer)
2335
+ fail("invalid_oidc_identity", 401);
2336
+ const value = providerRecord(await limitedJson(response));
2337
+ if (!Array.isArray(value.keys))
2338
+ fail("oidc_metadata_invalid");
2339
+ const keys = value.keys.filter((key) => key && typeof key === "object" && typeof key.issuer === "string" && key.issuer.replace("{tenantid}", actualIssuer.slice(MICROSOFT.length, -"/v2.0".length)) === actualIssuer);
2340
+ if (!keys.length)
2341
+ fail("microsoft_signing_key_issuer", 401);
2342
+ return Response.json({ keys });
2343
+ }
2344
+ return response;
2345
+ };
2346
+ const signingAlg = metadata.id_token_signing_alg_values_supported?.includes("RS256") ? "RS256" : metadata.id_token_signing_alg_values_supported?.find((alg) => ["PS256", "ES256"].includes(alg));
2347
+ let config;
2348
+ if (c.provider === "microsoft") {
2349
+ config = await discovery(new URL(c.issuer), c.clientId, { client_secret: secret, id_token_signed_response_alg: "RS256" }, auth, { timeout: 5, [customFetch2]: async () => Response.json(metadata) });
2350
+ } else
2351
+ config = new Configuration(metadata, c.clientId, { client_secret: secret, ...c.provider !== "github" ? { id_token_signed_response_alg: signingAlg } : {} }, auth);
2352
+ config[customFetch2] = customFetch;
2353
+ config.timeout = 5;
2354
+ if ([c.issuer, metadata.token_endpoint, metadata.jwks_uri].some((url) => url?.startsWith("http:")))
2355
+ allowInsecureRequests2(config);
2356
+ if (c.provider !== "github")
2357
+ enableNonRepudiationChecks(config);
2358
+ return config;
2359
+ }
2360
+ async exchange(c, config, callback, checks) {
2361
+ const tokens = await authorizationCodeGrant(config, callback, c.provider === "github" ? { pkceCodeVerifier: checks.pkceCodeVerifier, expectedState: checks.expectedState } : { ...checks, idTokenExpected: true }).catch((error) => {
2362
+ if (error instanceof AccountError)
2363
+ throw error;
2364
+ if (error instanceof ClientError && error.cause instanceof AccountError)
2365
+ throw error.cause;
2366
+ if (c.provider !== "github")
2367
+ throw error;
2368
+ fail("oauth_token_failed", 502);
2369
+ });
2370
+ if (c.provider === "github") {
2371
+ const response = await fetchProtectedResource(config, tokens.access_token, new URL("https://api.github.com/user"), "GET", undefined, new Headers({ accept: "application/vnd.github+json", "x-github-api-version": "2022-11-28", "user-agent": "Bungee" }));
2372
+ const user = providerRecord(await limitedJson(response));
2373
+ if (!Number.isSafeInteger(user.id) || user.id <= 0)
2374
+ fail("invalid_oauth_identity", 401);
2375
+ return { protocol: "oauth2", issuer: "https://github.com", subject: String(user.id), displayName: typeof user.name === "string" ? [...user.name].slice(0, 128).join("") : "GitHub user" };
2376
+ }
2377
+ const claims = tokens.claims();
2378
+ if (!claims || typeof claims.sub !== "string" || !claims.sub || claims.sub.length > 255 || typeof claims.iss !== "string")
2379
+ fail("invalid_oidc_identity", 401);
2380
+ if (c.provider === "microsoft")
2381
+ this.microsoftIssuer(c, claims);
2382
+ return { protocol: "oidc", issuer: claims.iss, subject: claims.sub, displayName: typeof claims.name === "string" ? [...claims.name].slice(0, 128).join("") : "SSO user" };
2383
+ }
2384
+ }
2385
+ var MAX_USERS = 128;
2386
+ var MAX_CONNECTIONS = 32;
2387
+ var MAX_IDENTITIES = 512;
2388
+ var MAX_FLOWS = 128;
2389
+ var MAX_AUDIT = 128;
2390
+ var MAX_SECRETS = 512;
2391
+ var FLOW_COOKIE = "bungee_user_flow";
2392
+ var EMPTY_SETTINGS = { autoCreateUsers: false, sessionEpoch: 1, failureEpoch: 1 };
2393
+ var EMPTY_TURNSTILE = { enabled: false, siteKey: "", secretRef: "", hostnames: [] };
2394
+ var PROVIDER = "user-auth";
2395
+ var COOKIE = "bungee_user_session";
2396
+ var DEFAULT_SESSION_POLICY = Object.freeze({ idleTimeoutMinutes: 30, absoluteTimeoutMinutes: 480 });
2397
+ var COOKIE_LEASE_SECONDS = 400 * 24 * 60 * 60;
2398
+ var MAX_TIMEOUT_MINUTES = 43200;
2399
+ var ALL = ["config.read", "config.write", "logs.read", "logs.body", "keys.read", "keys.write", "plugins.read", "plugins.toggle", "plugins.code", "auth.mode", "self.password", "self.logout"];
2400
+ function object(v) {
2401
+ if (!v || typeof v !== "object" || Array.isArray(v))
2402
+ fail("invalid_input");
2403
+ return v;
2404
+ }
2405
+ function exact(v, fields) {
2406
+ if (Object.keys(v).some((k) => !fields.includes(k)))
2407
+ fail("invalid_input");
2408
+ }
2409
+ function username(v) {
2410
+ if (typeof v !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.@-]{0,63}$/.test(v))
2411
+ fail("invalid_input");
2412
+ return v.toLowerCase();
2413
+ }
2414
+ function password(v) {
2415
+ if (typeof v !== "string" || [...v].length < 5 || [...v].length > 32)
2416
+ fail("invalid_password");
2417
+ return v;
2418
+ }
2419
+ function digest(v) {
2420
+ return createHash("sha256").update(v).digest("hex");
2421
+ }
2422
+ function csrf(token) {
2423
+ return createHmac("sha256", token).update("user-auth/csrf/v1").digest("base64url");
2424
+ }
2425
+ function constant(a, b) {
2426
+ const aa = Buffer.from(a), bb = Buffer.from(b);
2427
+ return aa.length === bb.length && timingSafeEqual(aa, bb);
2428
+ }
2429
+ function dto(m) {
2430
+ return { id: m.id, username: m.username, displayName: m.displayName, disabled: m.disabled, hasLocalPassword: !!m.passwordHash, requiresPasswordChange: m.temporary };
2431
+ }
2432
+ function json(v, status = 200, headers = {}) {
2433
+ return Response.json(v, { status, headers: { "cache-control": "no-store", ...headers } });
2434
+ }
2435
+ function safe(error) {
2436
+ return error instanceof AccountError ? json({ error: error.code }, error.status) : json({ error: "provider_unavailable" }, 503);
2437
+ }
2438
+ async function body(request) {
2439
+ const reader = request.body?.getReader();
2440
+ if (!reader)
2441
+ fail("invalid_input");
2442
+ let size = 0;
2443
+ const chunks = [];
2444
+ try {
2445
+ for (;; ) {
2446
+ const r = await reader.read();
2447
+ if (r.done)
2448
+ break;
2449
+ size += r.value.length;
2450
+ if (size > 8192) {
2451
+ await reader.cancel();
2452
+ fail("body_limit", 413);
2453
+ }
2454
+ chunks.push(r.value);
2455
+ }
2456
+ } finally {
2457
+ reader.releaseLock();
2458
+ }
2459
+ try {
2460
+ return object(JSON.parse(Buffer.concat(chunks).toString("utf8")));
2461
+ } catch (e) {
2462
+ if (e instanceof AccountError)
2463
+ throw e;
2464
+ return fail("invalid_input");
2465
+ }
2466
+ }
2467
+ function credentials(request) {
2468
+ const auth = request.headers.get("authorization");
2469
+ if (auth !== null) {
2470
+ const m = /^Bearer ([A-Za-z0-9_-]{43})$/.exec(auth);
2471
+ return m ? { token: m[1], transport: "bearer" } : null;
2472
+ }
2473
+ const tokens = (request.headers.get("cookie") ?? "").split(";").map((x) => x.trim()).filter((x) => x.startsWith(COOKIE + "="));
2474
+ if (tokens.length !== 1)
2475
+ return null;
2476
+ const token = tokens[0].slice(COOKIE.length + 1);
2477
+ return /^[A-Za-z0-9_-]{43}$/.test(token) ? { token, transport: "cookie" } : null;
2478
+ }
2479
+ function origin(request, expected) {
2480
+ if (request.headers.get("origin") !== (expected ?? new URL(request.url).origin))
2481
+ fail("invalid_origin", 403);
2482
+ }
2483
+ function cookie(request, token, maxAge, managementOrigin) {
2484
+ return `${COOKIE}=${token}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${maxAge}${new URL(managementOrigin ?? request.url).protocol === "https:" ? "; Secure" : ""}`;
2485
+ }
2486
+ function validateSessionPolicy(value, code = "invalid_session_policy", status = 400) {
2487
+ if (!value || typeof value !== "object" || Array.isArray(value))
2488
+ fail(code, status);
2489
+ const p = value;
2490
+ if (Object.keys(p).some((key) => !["idleTimeoutMinutes", "absoluteTimeoutMinutes"].includes(key)) || ![p.idleTimeoutMinutes, p.absoluteTimeoutMinutes].every((n) => Number.isSafeInteger(n) && n >= 0 && n <= MAX_TIMEOUT_MINUTES) || p.absoluteTimeoutMinutes === 0)
2491
+ fail(code, status);
2492
+ return { idleTimeoutMinutes: p.idleTimeoutMinutes, absoluteTimeoutMinutes: p.absoluteTimeoutMinutes };
2493
+ }
2494
+ var ACCOUNT_RECORD_SCHEMA = 3;
2495
+ var MAX_SESSIONS = 512;
2496
+ var MAX_FAILURES = 1024;
2497
+ var asJson = (value) => value;
2498
+ function validateUser(value) {
2499
+ const u = value;
2500
+ if (!u || Object.keys(u).some((k) => !["id", "username", "displayName", "disabled", "generation"].includes(k)) || typeof u.id !== "string" || !/^[a-f0-9-]{36}$/.test(u.id) || typeof u.username !== "string" || !/^[a-z0-9][a-z0-9_.@-]{0,63}$/.test(u.username) || typeof u.displayName !== "string" || [...u.displayName].length > 128 || typeof u.disabled !== "boolean" || !Number.isSafeInteger(u.generation) || u.generation < 1)
2501
+ fail("corrupt_state", 503);
2502
+ return u;
2503
+ }
2504
+ function localValue(record) {
2505
+ if (!record)
2506
+ return null;
2507
+ const c = envelope(record.value, "credential");
2508
+ if (!c || Object.keys(c).some((k) => !["passwordHash", "temporary"].includes(k)) || typeof c.passwordHash !== "string" || !c.passwordHash.startsWith("$argon2id$") || typeof c.temporary !== "boolean")
2509
+ fail("corrupt_state", 503);
2510
+ return c;
2511
+ }
2512
+ function entry(records, key, field, fallback) {
2513
+ const r = records.find((r) => r.key === key);
2514
+ return { key, version: r?.version ?? 0, value: r ? envelope(r.value, field) : fallback };
2515
+ }
2516
+ function users(records) {
2517
+ const result = records.filter((r) => r.key.startsWith("user:")).map((r) => {
2518
+ const u = validateUser(envelope(r.value, "user"));
2519
+ if (r.key !== "user:" + u.id)
2520
+ fail("corrupt_state", 503);
2521
+ const c = localValue(records.find((r) => r.key === "credential:" + u.id));
2522
+ return { ...u, passwordHash: c?.passwordHash ?? "", temporary: c?.temporary ?? false };
2523
+ });
2524
+ if (result.length > MAX_USERS || new Set(result.map((u) => u.username)).size !== result.length)
2525
+ fail("corrupt_state", 503);
2526
+ return result;
2527
+ }
2528
+ function settings(records) {
2529
+ const s = entry(records, "login-settings", "settings", EMPTY_SETTINGS), v = s.value;
2530
+ requireReadyRecord(records, s.version);
2531
+ if (!v || Object.keys(v).some((k) => !["autoCreateUsers", "sessionEpoch", "failureEpoch"].includes(k)) || typeof v.autoCreateUsers !== "boolean" || ![v.sessionEpoch, v.failureEpoch].every((n) => Number.isSafeInteger(n) && n > 0))
2532
+ fail("corrupt_state", 503);
2533
+ return s;
2534
+ }
2535
+ function turnstile(records) {
2536
+ const s = entry(records, "turnstile", "turnstile", EMPTY_TURNSTILE), v = s.value;
2537
+ requireReadyRecord(records, s.version);
2538
+ if (!v || Object.keys(v).some((k) => !["enabled", "siteKey", "secretRef", "hostnames"].includes(k)) || typeof v.enabled !== "boolean" || typeof v.siteKey !== "string" || typeof v.secretRef !== "string" || !Array.isArray(v.hostnames) || v.hostnames.some((h) => typeof h !== "string") || v.enabled && (!v.siteKey || !v.secretRef || !v.hostnames.length))
2539
+ fail("corrupt_state", 503);
2540
+ return s;
2541
+ }
2542
+ function requireReadyRecord(records, version) {
2543
+ const setup = records.find((r) => r.key === "setup");
2544
+ if (!version && setup && envelope(setup.value, "setup").phase === "ready")
2545
+ fail("corrupt_state", 503);
2546
+ }
2547
+ function connectionSlots(records) {
2548
+ const ids = new Set;
2549
+ const result = records.filter((r) => r.key.startsWith("connection:")).map((r) => {
2550
+ const c = envelope(r.value, "connection");
2551
+ if (c === null)
2552
+ return { key: r.key, version: r.version, value: null };
2553
+ if (!c || c.deleting !== undefined && (!c.deleting || c.enabled || Object.keys(c.deleting).some((k) => k !== "secretRefs") || !Array.isArray(c.deleting.secretRefs) || c.deleting.secretRefs.length > MAX_FLOWS + 1 || c.deleting.secretRefs.some((ref) => typeof ref !== "string" || !/^auth-secret:(\d+):([1-9]\d*)$/.test(ref))) || Object.keys(c).some((k) => !["id", "label", "enabled", "issuer", "provider", "configuration", "clientId", "secretRef", "scopes", "generation", "deleting"].includes(k)) || typeof c.id !== "string" || !/^[a-f0-9-]{36}$/.test(c.id) || ids.has(c.id) || typeof c.label !== "string" || typeof c.enabled !== "boolean" || typeof c.issuer !== "string" || typeof c.clientId !== "string" || typeof c.secretRef !== "string" || !Array.isArray(c.scopes) || c.provider !== "github" && !c.scopes.includes("openid") || c.scopes.some((s) => typeof s !== "string") || !Number.isSafeInteger(c.generation) || c.generation < 1)
2554
+ fail("corrupt_state", 503);
2555
+ try {
2556
+ const parsed = parseProvider(c.provider, c.configuration, c.provider === "oidc" ? c.scopes : undefined);
2557
+ if (JSON.stringify(parsed) !== JSON.stringify({ provider: c.provider, issuer: c.issuer, configuration: c.configuration, scopes: c.scopes }))
2558
+ fail("corrupt_state", 503);
2559
+ } catch {
2560
+ fail("corrupt_state", 503);
2561
+ }
2562
+ ids.add(c.id);
2563
+ return { key: r.key, version: r.version, value: c };
2564
+ });
2565
+ if (result.length > MAX_CONNECTIONS)
2566
+ fail("corrupt_state", 503);
2567
+ return result;
2568
+ }
2569
+ function connections(records) {
2570
+ return connectionSlots(records).flatMap((slot) => slot.value && !slot.value.deleting ? [slot.value] : []);
2571
+ }
2572
+ function identities(records) {
2573
+ const result = records.filter((r) => r.key.startsWith("identity:")).flatMap((r) => {
2574
+ const i = envelope(r.value, "identity");
2575
+ if (i === null)
2576
+ return [];
2577
+ if (!i || Object.keys(i).some((k) => !["id", "userId", "connectionId", "issuer", "subject", "protocol"].includes(k)) || typeof i.userId !== "string" || typeof i.connectionId !== "string" || typeof i.issuer !== "string" || !["oidc", "oauth2"].includes(i.protocol) || typeof i.subject !== "string" || !i.subject || r.key !== "identity:" + i.id || i.id !== digest(i.issuer + "\x00" + i.subject))
2578
+ fail("corrupt_state", 503);
2579
+ return [i];
2580
+ });
2581
+ if (result.length > MAX_IDENTITIES)
2582
+ fail("corrupt_state", 503);
2583
+ return result;
2584
+ }
2585
+ function envelope(value, field) {
2586
+ if (!value || typeof value !== "object" || Array.isArray(value))
2587
+ fail("corrupt_state", 503);
2588
+ const v = value;
2589
+ if (v.schema !== ACCOUNT_RECORD_SCHEMA || !Object.hasOwn(v, field) || Object.keys(v).some((k) => k !== "schema" && k !== field))
2590
+ fail("corrupt_state", 503);
2591
+ return v[field];
2592
+ }
2593
+ function validateSession(value) {
2594
+ if (value === null)
2595
+ return null;
2596
+ const s = value;
2597
+ if (!s || Object.keys(s).some((k) => !["digest", "administratorId", "generation", "created", "touched", "pendingTouch", "transport", "policy", "epoch", "connectionId", "connectionGeneration"].includes(k)) || !/^[a-f0-9]{64}$/.test(s.digest) || typeof s.administratorId !== "string" || !s.administratorId || !Number.isSafeInteger(s.generation) || s.generation < 1 || !Number.isSafeInteger(s.created) || s.created < 0 || !Number.isSafeInteger(s.touched) || s.touched < s.created || s.pendingTouch !== undefined && (!Number.isSafeInteger(s.pendingTouch) || s.pendingTouch < s.touched) || !Number.isSafeInteger(s.epoch) || s.epoch < 1 || s.connectionId !== undefined && (typeof s.connectionId !== "string" || !Number.isSafeInteger(s.connectionGeneration)) || !["cookie", "bearer"].includes(s.transport))
2598
+ fail("corrupt_state", 503);
2599
+ validateSessionPolicy(s.policy, "corrupt_state", 503);
2600
+ return s;
2601
+ }
2602
+ function validateFailure(value) {
2603
+ if (value === null)
2604
+ return null;
2605
+ const f = value;
2606
+ if (!f || Object.keys(f).some((k) => !["key", "count", "until", "epoch"].includes(k)) || typeof f.key !== "string" || !/^(account|source|challenge):[a-f0-9]{64}$/.test(f.key) || !Number.isSafeInteger(f.count) || f.count < 0 || !Number.isSafeInteger(f.until) || f.until < 0 || !Number.isSafeInteger(f.epoch) || f.epoch < 1)
2607
+ fail("corrupt_state", 503);
2608
+ return f;
2609
+ }
2610
+ function slots(records, kind, limit, validate) {
2611
+ const result = Array.from({ length: limit }, (_, i) => ({ key: `${kind}:${i}`, version: 0, value: null }));
2612
+ const seen = new Set;
2613
+ for (const r of records.filter((r) => r.key.startsWith(kind + ":"))) {
2614
+ const index = Number(r.key.slice(kind.length + 1));
2615
+ if (!Number.isInteger(index) || index < 0 || index >= limit || r.key !== `${kind}:${index}`)
2616
+ fail("corrupt_state", 503);
2617
+ const value = validate(envelope(r.value, kind));
2618
+ const identity = value && (kind === "session" ? value.digest : kind === "failure" ? value.key : value.id);
2619
+ if (identity && seen.has(identity))
2620
+ fail("corrupt_state", 503);
2621
+ if (identity)
2622
+ seen.add(identity);
2623
+ result[index] = { key: r.key, version: r.version, value };
2624
+ }
2625
+ return result;
2626
+ }
2627
+ var mutation = (slot, field, value) => ({ key: slot.key, expectedVersion: slot.version, value: asJson({ schema: ACCOUNT_RECORD_SCHEMA, [field]: value }) });
2628
+ function next(value) {
2629
+ if (!Number.isSafeInteger(value + 1))
2630
+ fail("generation_overflow");
2631
+ return value + 1;
2632
+ }
2633
+ function validateKeys(records) {
2634
+ for (const r of records)
2635
+ if (!["setup", "clock", "session-policy", "recovery-summary", "recovery-anchor", "login-settings", "turnstile"].includes(r.key) && !["session:", "failure:", "user:", "credential:", "identity:", "connection:", "flow:", "audit:"].some((p) => r.key.startsWith(p)))
2636
+ fail("corrupt_state", 503);
2637
+ }
2638
+ function validateAudit(records) {
2639
+ for (const record of records.filter((r) => r.key.startsWith("audit:"))) {
2640
+ const index = Number(record.key.slice(6));
2641
+ const value = envelope(record.value, "audit");
2642
+ if (!Number.isInteger(index) || index < 0 || index >= MAX_AUDIT || record.key !== "audit:" + index || !value || Object.keys(value).some((k) => !["actor", "action", "result", "at"].includes(k)) || [value.actor, value.action, value.result].some((v) => typeof v !== "string" || !v || v.length > 128) || !Number.isSafeInteger(value.at) || value.at < 0)
2643
+ fail("corrupt_state", 503);
2644
+ }
2645
+ }
2646
+ function clockValue(record) {
2647
+ if (!record)
2648
+ fail("corrupt_state", 503);
2649
+ const value = envelope(record.value, "observedThrough");
2650
+ if (!Number.isSafeInteger(value) || value < 0)
2651
+ fail("corrupt_state", 503);
2652
+ return value;
2653
+ }
2654
+ function wallTime(now) {
2655
+ const value = now();
2656
+ if (!Number.isSafeInteger(value) || value < 0)
2657
+ fail("invalid_clock", 503);
2658
+ return value;
2659
+ }
2660
+ function clockMutation(clock) {
2661
+ return { key: "clock", expectedVersion: clock.version, value: { schema: ACCOUNT_RECORD_SCHEMA, observedThrough: clock.value } };
2662
+ }
2663
+ async function observeClock(state, now) {
2664
+ for (let i = 0;i < 64; i++) {
2665
+ const record = await state.get("clock"), prior = clockValue(record), value = Math.max(prior, wallTime(now));
2666
+ if (value === prior)
2667
+ return { version: record.version, value, readVersion: record.version };
2668
+ try {
2669
+ const [saved] = await state.transact([clockMutation({ version: record.version, value })]);
2670
+ return { version: saved.version, value, readVersion: record.version };
2671
+ } catch (e) {
2672
+ if (!isDurableStateConflictError(e))
2673
+ throw e;
2674
+ }
2675
+ }
2676
+ return fail("version_conflict", 409);
2677
+ }
2678
+ function setupRecord(records) {
2679
+ validateKeys(records);
2680
+ const record = records.find((r) => r.key === "setup");
2681
+ if (!record)
2682
+ fail("corrupt_state", 503);
2683
+ const value = envelope(record.value, "setup");
2684
+ if (!value || !["uninitialized", "ready"].includes(value.phase) || Object.keys(value).some((k) => !["phase", "code"].includes(k)))
2685
+ fail("corrupt_state", 503);
2686
+ if (value.code !== undefined && (!value.code || !/^[a-f0-9]{64}$/.test(value.code.digest) || !Number.isSafeInteger(value.code.expiresAt) || value.code.expiresAt < 0 || Object.keys(value.code).some((k) => !["digest", "expiresAt"].includes(k))))
2687
+ fail("corrupt_state", 503);
2688
+ if (value.phase === "ready" && value.code !== undefined)
2689
+ fail("corrupt_state", 503);
2690
+ const all = users(records), anchor = records.find((r) => r.key === "recovery-anchor");
2691
+ if (value.phase === "ready" !== !!all.length || value.phase === "ready" && (!anchor || typeof envelope(anchor.value, "userId") !== "string"))
2692
+ fail("corrupt_state", 503);
2693
+ validateAudit(records);
2694
+ settings(records);
2695
+ turnstile(records);
2696
+ connections(records);
2697
+ identities(records);
2698
+ clockValue(records.find((r) => r.key === "clock"));
2699
+ return { key: "setup", version: record.version, value };
2700
+ }
2701
+
2702
+ class UserAuthControl {
2703
+ host;
2704
+ dependencies;
2705
+ management = this;
2706
+ rpc = [];
2707
+ api;
2708
+ state;
2709
+ now;
2710
+ subjects = new WeakMap;
2711
+ sessionSlots = new Map;
2712
+ dummy = "";
2713
+ disposed = false;
2714
+ stateTail = Promise.resolve();
2715
+ pendingStateOperations = 0;
2716
+ constructor(host, dependencies = {}) {
2717
+ this.host = host;
2718
+ this.dependencies = dependencies;
2719
+ if (!host.durableState)
2720
+ fail("durable_state_required", 503);
2721
+ this.state = host.durableState;
2722
+ this.now = dependencies.now ?? Date.now;
2723
+ const invoke = (fn) => async (c) => {
2724
+ try {
2725
+ this.alive();
2726
+ if (c.requestSignal.aborted)
2727
+ fail("cancelled");
2728
+ return await fn(c);
2729
+ } catch (e) {
2730
+ return safe(e);
2731
+ }
2732
+ };
2733
+ this.api = [
2734
+ { path: "/password", methods: ["POST"], handler: "changePassword", invoke: invoke(async (c) => {
2735
+ await this.requireCapability(c, "self.password", true);
2736
+ const b = await body(c.request);
2737
+ exact(b, ["currentPassword", "password", "passwordConfirmation"]);
2738
+ await this.changePassword(c, b);
2739
+ return json({ ok: true }, 200, { "set-cookie": cookie(c.request, "", 0, this.host.managementOrigin) });
2740
+ }) },
2741
+ { path: "/logout", methods: ["POST"], handler: "logout", invoke: invoke(async (c) => {
2742
+ await this.requireCapability(c, "self.logout", true);
2743
+ return this.logout(c.request);
2744
+ }) },
2745
+ { path: "/self", methods: ["GET"], handler: "self", invoke: invoke(async (c) => {
2746
+ await this.requireCapability(c, "self.password");
2747
+ return this.retry(async (time) => {
2748
+ const { user } = await this.guard(c, time), creds = credentials(c.request);
2749
+ return { mutations: [], result: json({ administrator: dto(user), ...creds?.transport === "cookie" ? { csrfToken: csrf(creds.token) } : {} }) };
2750
+ });
2751
+ }) },
2752
+ { path: "/session-policy", methods: ["GET", "PUT"], handler: "sessionPolicy", invoke: invoke(async (c) => {
2753
+ await this.requireCapability(c, "config.write", c.request.method === "PUT");
2754
+ if (c.request.method === "GET")
2755
+ return this.retry(async (time) => {
2756
+ const policy = await this.readSessionPolicy();
2757
+ await this.guard(c, time);
2758
+ return { mutations: [], result: json(policy) };
2759
+ });
2760
+ const b = await body(c.request);
2761
+ exact(b, ["version", "policy"]);
2762
+ if (!Number.isSafeInteger(b.version) || b.version < 0)
2763
+ fail("invalid_session_policy");
2764
+ const policy = validateSessionPolicy(b.policy);
2765
+ await this.requireCapability(c, "config.write", true);
2766
+ try {
2767
+ await this.retry(async (time) => {
2768
+ await this.guard(c, time);
2769
+ return { mutations: [{ key: "session-policy", expectedVersion: b.version, value: asJson({ schema: ACCOUNT_RECORD_SCHEMA, policy }) }], result: undefined };
2770
+ });
2771
+ } catch (e) {
2772
+ if (isDurableStateConflictError(e))
2773
+ fail("version_conflict", 409);
2774
+ throw e;
2775
+ }
2776
+ return json(await this.readSessionPolicy());
2777
+ }) },
2778
+ ...this.featureApi(invoke)
2779
+ ];
2780
+ }
2781
+ alive() {
2782
+ if (this.disposed || this.host.signal.aborted)
2783
+ fail("provider_unavailable", 503);
2784
+ }
2785
+ async readAdministrator(id) {
2786
+ this.alive();
2787
+ if (!id) {
2788
+ const all = users(await this.records());
2789
+ id = all[0]?.id;
2790
+ }
2791
+ if (!id)
2792
+ return { key: "user:missing", version: 0, value: null };
2793
+ const record = await this.state.get("user:" + id);
2794
+ if (!record)
2795
+ return { key: "user:" + id, version: 0, value: null };
2796
+ const user = validateUser(envelope(record.value, "user")), credential = localValue(await this.state.get("credential:" + id));
2797
+ if (user.id !== id)
2798
+ fail("corrupt_state", 503);
2799
+ return { key: record.key, version: record.version, value: { ...user, passwordHash: credential?.passwordHash ?? "", temporary: credential?.temporary ?? false } };
2800
+ }
2801
+ userMutation(slot, value) {
2802
+ const { passwordHash, temporary, ...user } = value;
2803
+ return mutation(slot, "user", user);
2804
+ }
2805
+ credentialMutation(records, id, hash) {
2806
+ const r = records.find((r) => r.key === "credential:" + id);
2807
+ return { key: "credential:" + id, expectedVersion: r?.version ?? 0, value: asJson({ schema: ACCOUNT_RECORD_SCHEMA, credential: { passwordHash: hash, temporary: false } }) };
2808
+ }
2809
+ async records() {
2810
+ this.alive();
2811
+ const records = await this.state.list();
2812
+ setupRecord(records);
2813
+ return records;
2814
+ }
2815
+ async retry(plan) {
2816
+ if (this.pendingStateOperations >= 256)
2817
+ fail("provider_busy", 503);
2818
+ this.pendingStateOperations++;
2819
+ const task = this.stateTail.then(async () => {
2820
+ await this.cleanupDeletedConnections();
2821
+ const result = await this.retryState(plan);
2822
+ await this.cleanupDeletedConnections();
2823
+ return result;
2824
+ });
2825
+ this.stateTail = task.then(() => {}, () => {});
2826
+ try {
2827
+ return await task;
2828
+ } finally {
2829
+ this.pendingStateOperations--;
2830
+ }
2831
+ }
2832
+ async retryState(plan) {
2833
+ for (let i = 0;i < 64; i++) {
2834
+ const before = await this.state.get("clock");
2835
+ clockValue(before);
2836
+ let clock;
2837
+ const time = async () => {
2838
+ const prior = clock, observed = await observeClock(this.state, this.now);
2839
+ clock = prior && observed.readVersion === prior.version ? { ...observed, readVersion: prior.readVersion } : observed;
2840
+ return clock.value;
2841
+ };
2842
+ let planned;
2843
+ try {
2844
+ planned = await plan(time);
2845
+ } catch (error) {
2846
+ await time();
2847
+ throw error;
2848
+ }
2849
+ if (!clock)
2850
+ await time();
2851
+ if (clock.readVersion !== before.version)
2852
+ continue;
2853
+ if (!planned.mutations.length)
2854
+ return planned.result;
2855
+ this.alive();
2856
+ try {
2857
+ await this.state.transact([...planned.mutations, clockMutation(clock)]);
2858
+ return planned.result;
2859
+ } catch (e) {
2860
+ if (!isDurableStateConflictError(e))
2861
+ throw e;
2862
+ }
2863
+ }
2864
+ return fail("version_conflict", 409);
2865
+ }
2866
+ async cleanupDeletedConnections() {
2867
+ if (!connectionSlots(await this.state.list()).some((slot) => slot.value?.deleting))
2868
+ return;
2869
+ for (;; ) {
2870
+ const pending = await this.retryState(async () => {
2871
+ const records = await this.records();
2872
+ const slot = connectionSlots(records).find((s) => s.value?.deleting);
2873
+ if (!slot)
2874
+ return { mutations: [], result: { done: true, connection: null } };
2875
+ const id = slot.value.id;
2876
+ const bindings = identities(records).filter((i) => i.connectionId === id);
2877
+ const flows = this.flows(records).filter((f) => f.value?.connectionId === id);
2878
+ const changes = [
2879
+ ...bindings.map((i) => mutation(entry(records, "identity:" + i.id, "identity", null), "identity", null)),
2880
+ ...flows.map((f) => mutation(f, "flow", null))
2881
+ ];
2882
+ return { mutations: changes.slice(0, 255), result: { done: false, connection: changes.length ? null : slot.value } };
2883
+ });
2884
+ if (pending.done)
2885
+ return;
2886
+ if (!pending.connection)
2887
+ continue;
2888
+ for (const ref of pending.connection.deleting.secretRefs) {
2889
+ const match = /^auth-secret:(\d+):([1-9]\d*)$/.exec(ref);
2890
+ const key = "auth-secret:" + match[1], version = Number(match[2]);
2891
+ const saved = await this.host.secretStore.get(key);
2892
+ if (saved?.version === version)
2893
+ await this.host.secretStore.delete(key, version);
2894
+ }
2895
+ await this.retryState(async () => {
2896
+ const records = await this.records();
2897
+ const slot = connectionSlots(records).find((s) => s.value?.id === pending.connection.id && s.value.deleting);
2898
+ return { mutations: slot ? [mutation(slot, "connection", null)] : [], result: undefined };
2899
+ });
2900
+ }
2901
+ }
2902
+ async readSessionPolicy() {
2903
+ this.alive();
2904
+ const record = await this.state.get("session-policy");
2905
+ return { version: record?.version ?? 0, policy: record ? validateSessionPolicy(envelope(record.value, "policy"), "corrupt_state", 503) : { ...DEFAULT_SESSION_POLICY } };
2906
+ }
2907
+ async start() {
2908
+ await readManagementSetup(this.state);
2909
+ await observeClock(this.state, this.now);
2910
+ await this.readSessionPolicy();
2911
+ const records = await this.records();
2912
+ slots(records, "session", MAX_SESSIONS, validateSession);
2913
+ slots(records, "failure", MAX_FAILURES, validateFailure);
2914
+ this.flows(records);
2915
+ for (const r of records.filter((r) => r.key.startsWith("credential:"))) {
2916
+ localValue(r);
2917
+ if (!users(records).some((u) => r.key === "credential:" + u.id))
2918
+ fail("corrupt_state", 503);
2919
+ }
2920
+ await this.cleanupDeletedConnections();
2921
+ this.dummy = await this.hash(randomBytes2(32).toString("base64url"));
2922
+ }
2923
+ dispose() {
2924
+ this.disposed = true;
2925
+ }
2926
+ hash(p) {
2927
+ return Bun.password.hash(p, { algorithm: "argon2id", memoryCost: 65536, timeCost: 3 });
2928
+ }
2929
+ async setupStatus() {
2930
+ return (await readManagementSetup(this.state)).initialized ? "ready" : "uninitialized";
2931
+ }
2932
+ async hasIdentity() {
2933
+ return await this.setupStatus() === "ready" && users(await this.records()).some((u) => !u.disabled);
2934
+ }
2935
+ async revokeSessions() {
2936
+ await this.retry(async () => {
2937
+ const s = settings(await this.records());
2938
+ return { mutations: [mutation(s, "settings", { ...s.value, sessionEpoch: next(s.value.sessionEpoch) })], result: undefined };
2939
+ });
2940
+ }
2941
+ async bootstrap(input) {
2942
+ const b = object(input);
2943
+ exact(b, ["username", "password"]);
2944
+ const name = username(b.username), p = password(b.password);
2945
+ if (await this.setupStatus() !== "ready")
2946
+ fail("initialization_required", 409);
2947
+ const prior = users(await this.records()).find((u) => u.username === name);
2948
+ if (!prior?.passwordHash)
2949
+ fail("invalid_credentials", 401);
2950
+ if (prior.username !== name || prior.disabled || !await Bun.password.verify(p, prior.passwordHash))
2951
+ fail("invalid_credentials", 401);
2952
+ const latest = (await this.readAdministrator(prior.id)).value;
2953
+ if (!latest || latest.id !== prior.id || latest.generation !== prior.generation || latest.username !== prior.username || latest.disabled)
2954
+ fail("invalid_credentials", 401);
2955
+ }
2956
+ source(request) {
2957
+ return (this.dependencies.trustedSource ?? this.host.trustedSource)?.(request) ?? "unknown";
2958
+ }
2959
+ async reserve(keys) {
2960
+ return this.retry(async (time) => {
2961
+ const epoch = settings(await this.records()).value.failureEpoch;
2962
+ const failures = slots(await this.records(), "failure", MAX_FAILURES, validateFailure), selected = [], now = await time();
2963
+ for (const key of keys) {
2964
+ let slot = failures.find((x) => !selected.includes(x) && x.value?.key === key);
2965
+ const active = slot?.value && slot.value.epoch === epoch && slot.value.until > now;
2966
+ if (active && slot.value.count >= 10)
2967
+ fail("login_limited", 429);
2968
+ slot ??= failures.find((x) => !selected.includes(x) && (!x.value || x.value.epoch !== epoch || x.value.until <= now));
2969
+ if (!slot)
2970
+ fail("login_limited", 429);
2971
+ selected.push(slot);
2972
+ }
2973
+ return { mutations: selected.map((slot, i) => {
2974
+ const prior = slot.value;
2975
+ return mutation(slot, "failure", { key: keys[i], count: prior && prior.key === keys[i] && prior.epoch === epoch && prior.until > now ? prior.count + 1 : 1, until: prior && prior.key === keys[i] && prior.epoch === epoch && prior.until > now ? prior.until : now + 15 * 60000, epoch });
2976
+ }), result: epoch };
2977
+ });
2978
+ }
2979
+ async initialize(request) {
2980
+ try {
2981
+ this.alive();
2982
+ if (request.method !== "POST")
2983
+ fail("method_not_allowed", 405);
2984
+ origin(request, this.host.managementOrigin);
2985
+ const b = await body(request);
2986
+ exact(b, ["setupCode", "username", "password", "passwordConfirmation"]);
2987
+ await this.reserve(["source:" + digest(this.source(request))]);
2988
+ const check = async (time) => {
2989
+ const records = await this.records(), setup = setupRecord(records), now = await time();
2990
+ if (setup.value.phase !== "uninitialized")
2991
+ fail("already_initialized", 409);
2992
+ if (!setup.value.code || now >= setup.value.code.expiresAt || typeof b.setupCode !== "string" || !constant(digest(b.setupCode), setup.value.code.digest))
2993
+ fail("invalid_setup_code", 401);
2994
+ return { setup, records };
2995
+ };
2996
+ const initial = await this.retry(async (time) => ({ mutations: [], result: await check(time) }));
2997
+ const name = username(b.username), p = password(b.password);
2998
+ if (b.passwordConfirmation !== p)
2999
+ fail("password_confirmation");
3000
+ const passwordHash = await this.hash(p);
3001
+ await this.retry(async (time) => {
3002
+ const { setup, records } = await check(time);
3003
+ if (setup.version !== initial.setup.version)
3004
+ fail("invalid_setup_code", 401);
3005
+ const account = { id: randomUUID(), username: name, displayName: name, disabled: false, generation: 1 };
3006
+ return { mutations: [mutation(setup, "setup", { phase: "ready" }), mutation(settings(records), "settings", EMPTY_SETTINGS), mutation(turnstile(records), "turnstile", EMPTY_TURNSTILE), { key: "user:" + account.id, expectedVersion: 0, value: asJson({ schema: ACCOUNT_RECORD_SCHEMA, user: account }) }, this.credentialMutation(records, account.id, passwordHash), { key: "recovery-anchor", expectedVersion: 0, value: { schema: ACCOUNT_RECORD_SCHEMA, userId: account.id } }], result: undefined };
3007
+ });
3008
+ return json({ initialized: true });
3009
+ } catch (e) {
3010
+ return safe(e);
3011
+ }
3012
+ }
3013
+ live(m, session, now, records) {
3014
+ if (session.epoch !== settings(records).value.sessionEpoch)
3015
+ return;
3016
+ if (session.connectionId) {
3017
+ const c = connections(records).find((c) => c.id === session.connectionId);
3018
+ if (!c?.enabled || c.generation !== session.connectionGeneration)
3019
+ return;
3020
+ }
3021
+ const policy = session.policy;
3022
+ if (now < session.touched || now < session.created || policy.idleTimeoutMinutes > 0 && now - session.touched >= policy.idleTimeoutMinutes * 60000 || now - session.created >= policy.absoluteTimeoutMinutes * 60000)
3023
+ return;
3024
+ return m && m.id === session.administratorId && !m.disabled && m.generation === session.generation ? m : undefined;
3025
+ }
3026
+ async findSession(d) {
3027
+ this.alive();
3028
+ const key = this.sessionSlots.get(d);
3029
+ if (key) {
3030
+ const record = await this.state.get(key), value = record && validateSession(envelope(record.value, "session"));
3031
+ if (record && value?.digest === d)
3032
+ return { key, version: record.version, value };
3033
+ this.sessionSlots.delete(d);
3034
+ }
3035
+ const found = slots(await this.records(), "session", MAX_SESSIONS, validateSession).find((x) => x.value?.digest === d);
3036
+ if (found) {
3037
+ if (this.sessionSlots.size >= MAX_SESSIONS)
3038
+ this.sessionSlots.clear();
3039
+ this.sessionSlots.set(d, found.key);
3040
+ }
3041
+ return found;
3042
+ }
3043
+ cookieAge(policy, elapsedMs = 0) {
3044
+ return Math.max(0, Math.min(COOKIE_LEASE_SECONDS, Math.floor((policy.absoluteTimeoutMinutes * 60000 - elapsedMs) / 1000)));
3045
+ }
3046
+ async sessionCookie(request) {
3047
+ this.alive();
3048
+ const creds = credentials(request);
3049
+ if (creds?.transport !== "cookie")
3050
+ return;
3051
+ return this.retry(async (time) => {
3052
+ const slot = await this.findSession(digest(creds.token)), session = slot?.value, m = (await this.readAdministrator(session?.administratorId)).value, records = await this.records(), now = await time();
3053
+ return { mutations: [], result: session && session.transport === "cookie" && this.live(m, session, now, records) ? cookie(request, creds.token, this.cookieAge(session.policy, now - session.created), this.host.managementOrigin) : undefined };
3054
+ });
3055
+ }
3056
+ async authenticate(request) {
3057
+ this.alive();
3058
+ const c = credentials(request);
3059
+ if (!c)
3060
+ return null;
3061
+ const d = digest(c.token);
3062
+ const prepared = await this.retry(async (time) => {
3063
+ const slot = await this.findSession(d), session = slot?.value, m = (await this.readAdministrator(session?.administratorId)).value, records = await this.records(), now = await time();
3064
+ const administrator = session && session.transport === c.transport && this.live(m, session, now, records);
3065
+ if (!slot || !session || !administrator)
3066
+ return { mutations: [], result: false };
3067
+ return { mutations: session.pendingTouch === undefined ? [mutation(slot, "session", { ...session, pendingTouch: now })] : [], result: true };
3068
+ }).catch(async (error) => {
3069
+ try {
3070
+ await observeClock(this.state, this.now);
3071
+ } catch {}
3072
+ throw error;
3073
+ });
3074
+ if (!prepared)
3075
+ return null;
3076
+ const authenticated = await this.retry(async (time) => {
3077
+ const slot = await this.findSession(d), session = slot?.value, m = (await this.readAdministrator(session?.administratorId)).value, records = await this.records(), now = await time();
3078
+ const administrator = session && session.transport === c.transport && this.live(m, session, now, records);
3079
+ if (!slot || !session || !administrator)
3080
+ return { mutations: [], result: null };
3081
+ const { pendingTouch, ...confirmed } = session;
3082
+ return { mutations: pendingTouch === undefined ? [] : [mutation(slot, "session", { ...confirmed, touched: now })], result: administrator };
3083
+ });
3084
+ if (!authenticated)
3085
+ return null;
3086
+ const subject = Object.freeze({ id: authenticated.id, provider: PROVIDER, capabilities: Object.freeze([...ALL]), requiresPasswordChange: authenticated.temporary });
3087
+ this.subjects.set(subject, { generation: authenticated.generation, digest: d });
3088
+ return await this.authorize(subject, "config.read") ? subject : null;
3089
+ }
3090
+ async authorize(subject, _capability) {
3091
+ this.alive();
3092
+ const issued = this.subjects.get(subject);
3093
+ if (!issued || subject.provider !== PROVIDER)
3094
+ return false;
3095
+ return this.retry(async (time) => {
3096
+ const slot = await this.findSession(issued.digest), session = slot?.value, account = (await this.readAdministrator(session?.administratorId)).value, records = await this.records(), now = await time(), m = session && this.live(account, session, now, records);
3097
+ return { mutations: [], result: !!m && m.id === subject.id && m.generation === issued.generation };
3098
+ });
3099
+ }
3100
+ async requireCapability(c, capability, write = false) {
3101
+ if (!c.subject || !credentials(c.request) || this.subjects.get(c.subject)?.digest !== digest(credentials(c.request).token) || !await this.authorize(c.subject, capability))
3102
+ fail("forbidden", 403);
3103
+ if (write)
3104
+ this.checkWrite(c.request);
3105
+ }
3106
+ validateWrite(request) {
3107
+ this.checkWrite(request);
3108
+ }
3109
+ csrfToken(request) {
3110
+ const c = credentials(request);
3111
+ return c?.transport === "cookie" ? csrf(c.token) : undefined;
3112
+ }
3113
+ checkWrite(request) {
3114
+ const c = credentials(request);
3115
+ if (!c)
3116
+ fail("unauthorized", 401);
3117
+ if (c.transport === "cookie") {
3118
+ origin(request, this.host.managementOrigin);
3119
+ if (!constant(request.headers.get("x-csrf-token") ?? "", csrf(c.token)))
3120
+ fail("invalid_csrf", 403);
3121
+ }
3122
+ }
3123
+ async login(request) {
3124
+ try {
3125
+ this.alive();
3126
+ if (request.method !== "POST")
3127
+ fail("method_not_allowed", 405);
3128
+ const b = await body(request);
3129
+ exact(b, ["username", "password", "transport", "turnstileToken"]);
3130
+ const name = username(b.username), p = typeof b.password === "string" ? b.password : "";
3131
+ const transport = b.transport ?? "cookie";
3132
+ if (transport !== "cookie" && transport !== "bearer")
3133
+ fail("invalid_input");
3134
+ if (transport === "cookie" || request.headers.has("origin"))
3135
+ origin(request, this.host.managementOrigin);
3136
+ const keys = ["account:" + digest(name), "source:" + digest(this.source(request))];
3137
+ const reservation = await this.reserve(keys);
3138
+ if (await this.setupStatus() !== "ready")
3139
+ fail("invalid_credentials", 401);
3140
+ const turn = turnstile(await this.records());
3141
+ await this.verifyTurnstile(b.turnstileToken, turn.value, request);
3142
+ const administrator = users(await this.records()).find((u) => u.username === name);
3143
+ const valid = await Bun.password.verify(p, administrator?.passwordHash || this.dummy || await this.hash("dummy-invalid-password"));
3144
+ if (!valid || !administrator || !administrator.passwordHash || administrator.disabled)
3145
+ fail("invalid_credentials", 401);
3146
+ const token = randomBytes2(32).toString("base64url");
3147
+ let policy = { ...DEFAULT_SESSION_POLICY };
3148
+ const created = await this.retry(async (time) => {
3149
+ const m = (await this.readAdministrator(administrator.id)).value;
3150
+ if (!m || m.id !== administrator.id || m.username !== administrator.username || m.disabled || m.generation !== administrator.generation)
3151
+ fail("invalid_credentials", 401);
3152
+ const records = await this.records();
3153
+ policy = (await this.readSessionPolicy()).policy;
3154
+ if (settings(records).value.failureEpoch !== reservation || turnstile(records).version !== turn.version)
3155
+ fail("version_conflict", 409);
3156
+ const now = await time(), sessions = slots(records, "session", MAX_SESSIONS, validateSession), slot = sessions.find((x) => !x.value || !this.live(users(records).find((u) => u.id === x.value.administratorId) ?? null, x.value, now, records));
3157
+ if (!slot)
3158
+ fail("session_limit", 429);
3159
+ const changes = [mutation(slot, "session", { digest: digest(token), administratorId: m.id, generation: m.generation, created: now, touched: now, transport, policy, epoch: settings(records).value.sessionEpoch }), this.audit(records, m.id, "local.login", now)];
3160
+ const failures = slots(records, "failure", MAX_FAILURES, validateFailure);
3161
+ const account = failures.find((x) => x.value?.key === keys[0] && x.value.epoch === reservation);
3162
+ if (account)
3163
+ changes.push(mutation(account, "failure", null));
3164
+ const source = failures.find((x) => x.value?.key === keys[1] && x.value.epoch === reservation);
3165
+ if (source)
3166
+ changes.push(mutation(source, "failure", { ...source.value, count: Math.max(0, source.value.count - 1) }));
3167
+ return { mutations: changes, result: now };
3168
+ });
3169
+ const latest = (await this.readAdministrator(administrator.id)).value;
3170
+ if (!latest || latest.disabled || latest.generation !== administrator.generation || latest.id !== administrator.id)
3171
+ fail("invalid_credentials", 401);
3172
+ return transport === "cookie" ? json({ administrator: dto(administrator), csrfToken: csrf(token) }, 200, { "set-cookie": cookie(request, token, this.cookieAge(policy, (await observeClock(this.state, this.now)).value - created), this.host.managementOrigin) }) : json({ administrator: dto(administrator), token, expiresIn: policy.absoluteTimeoutMinutes * 60 });
3173
+ } catch (e) {
3174
+ await this.recordFailure("local.login", e);
3175
+ return safe(e);
3176
+ }
3177
+ }
3178
+ async logout(request) {
3179
+ try {
3180
+ this.alive();
3181
+ if (request.method !== "POST")
3182
+ fail("method_not_allowed", 405);
3183
+ this.checkWrite(request);
3184
+ const c = credentials(request);
3185
+ await this.retry(async () => {
3186
+ const slot = await this.findSession(digest(c.token));
3187
+ return { mutations: slot?.value ? [mutation(slot, "session", null)] : [], result: undefined };
3188
+ });
3189
+ return json({ ok: true }, 200, { "set-cookie": cookie(request, "", 0, this.host.managementOrigin) });
3190
+ } catch (e) {
3191
+ return safe(e);
3192
+ }
3193
+ }
3194
+ async changePassword(c, b) {
3195
+ const p = password(b.password);
3196
+ if (b.passwordConfirmation !== p)
3197
+ fail("password_confirmation");
3198
+ const id = c.subject.id, m = (await this.readAdministrator(id)).value;
3199
+ if (!m || !m.passwordHash || m.id !== id || typeof b.currentPassword !== "string" || !await Bun.password.verify(b.currentPassword, m.passwordHash))
3200
+ fail("invalid_credentials", 401);
3201
+ if (constant(p, b.currentPassword))
3202
+ fail("password_unchanged");
3203
+ const hashed = await this.hash(p);
3204
+ await this.retry(async (time) => {
3205
+ this.checkWrite(c.request);
3206
+ const a = await this.readAdministrator(id), current = a.value, records = await this.records();
3207
+ if (!current || current.id !== id || current.disabled || current.generation !== m.generation)
3208
+ fail("version_conflict", 409);
3209
+ const issued = this.subjects.get(c.subject), slot = await this.findSession(issued.digest), now = await time();
3210
+ if (!slot?.value || !this.live(current, slot.value, now, records))
3211
+ fail("forbidden", 403);
3212
+ return { mutations: [this.userMutation(a, { ...current, generation: next(current.generation) }), this.credentialMutation(records, id, hashed), mutation(slot, "session", null), this.audit(records, id, "user.password.change", now)], result: undefined };
3213
+ });
3214
+ }
3215
+ async guard(c, time) {
3216
+ this.checkWriteIfNeeded(c.request);
3217
+ const issued = c.subject && this.subjects.get(c.subject), creds = credentials(c.request);
3218
+ if (!issued || !creds || issued.digest !== digest(creds.token))
3219
+ fail("forbidden", 403);
3220
+ const slot = await this.findSession(issued.digest), records = await this.records(), user = users(records).find((u) => u.id === c.subject.id), now = await time();
3221
+ if (!slot?.value || slot.value.transport !== creds.transport || !user || user.generation !== issued.generation || !this.live(user, slot.value, now, records))
3222
+ fail("forbidden", 403);
3223
+ return { user, records };
3224
+ }
3225
+ checkWriteIfNeeded(request) {
3226
+ if (!["GET", "HEAD"].includes(request.method))
3227
+ this.checkWrite(request);
3228
+ }
3229
+ audit(records, actor, action, at, result = "success") {
3230
+ const recordsBySlot = Array.from({ length: MAX_AUDIT }, (_, i) => records.find((r) => r.key === "audit:" + i));
3231
+ const index = recordsBySlot.findIndex((r) => !r);
3232
+ const slot = index >= 0 ? index : recordsBySlot.reduce((best, r, i) => envelope(r.value, "audit").at < envelope(recordsBySlot[best].value, "audit").at ? i : best, 0);
3233
+ return { key: "audit:" + slot, expectedVersion: recordsBySlot[slot]?.version ?? 0, value: asJson({ schema: ACCOUNT_RECORD_SCHEMA, audit: { actor, action, result, at } }) };
3234
+ }
3235
+ async recordFailure(action, error) {
3236
+ try {
3237
+ await this.retry(async (time) => {
3238
+ const records = await this.records(), at = await time();
3239
+ return { mutations: [this.audit(records, "anonymous", action, at, error instanceof AccountError ? error.code : "provider_unavailable")], result: undefined };
3240
+ });
3241
+ } catch {}
3242
+ }
3243
+ featureApi(invoke) {
3244
+ const route = (path, methods, handler, fn) => ({ path, methods, handler, invoke: invoke(fn) });
3245
+ return [
3246
+ route("/users", ["GET", "POST"], "users", async (c) => {
3247
+ await this.requireCapability(c, "config.write", c.request.method === "POST");
3248
+ if (c.request.method === "GET")
3249
+ return this.retry(async (time) => {
3250
+ const { records } = await this.guard(c, time);
3251
+ return { mutations: [], result: json({ users: users(records).map(dto) }) };
3252
+ });
3253
+ const b = await body(c.request);
3254
+ exact(b, ["username", "displayName", "password", "passwordConfirmation"]);
3255
+ const name = username(b.username), displayName = this.displayName(b.displayName ?? name);
3256
+ const hash = b.password === undefined ? "" : await this.hash(this.confirmPassword(b)), id = randomUUID();
3257
+ return this.retry(async (time) => {
3258
+ const { records, user } = await this.guard(c, time), all = users(records);
3259
+ if (all.length >= MAX_USERS)
3260
+ fail("user_limit", 409);
3261
+ if (all.some((u) => u.username === name))
3262
+ fail("username_exists", 409);
3263
+ const u = { id, username: name, displayName, disabled: false, generation: 1, passwordHash: hash, temporary: false };
3264
+ return { mutations: [this.userMutation({ key: "user:" + id, version: 0, value: null }, u), ...hash ? [this.credentialMutation(records, id, hash)] : [], this.audit(records, user.id, "user.create", await time())], result: json({ user: dto(u) }, 201) };
3265
+ });
3266
+ }),
3267
+ route("/users/update", ["PUT"], "updateUser", async (c) => {
3268
+ await this.requireCapability(c, "config.write", true);
3269
+ const b = await body(c.request);
3270
+ exact(b, ["id", "username", "displayName", "disabled"]);
3271
+ const id = this.text(b.id, 64), name = username(b.username), displayName = this.displayName(b.displayName);
3272
+ if (typeof b.disabled !== "boolean")
3273
+ fail("invalid_input");
3274
+ return this.retry(async (time) => {
3275
+ const { records, user } = await this.guard(c, time), all = users(records), u = all.find((u) => u.id === id);
3276
+ if (!u)
3277
+ fail("not_found", 404);
3278
+ if (all.some((u) => u.id !== id && u.username === name))
3279
+ fail("username_exists", 409);
3280
+ if (b.disabled && !u.disabled && u.passwordHash && !all.some((x) => x.id !== id && !x.disabled && x.passwordHash))
3281
+ fail("last_local_user", 409);
3282
+ const updated = { ...u, username: name, displayName, disabled: b.disabled, generation: b.disabled !== u.disabled ? next(u.generation) : u.generation };
3283
+ const r = records.find((r) => r.key === "user:" + id);
3284
+ return { mutations: [this.userMutation({ key: r.key, version: r.version, value: u }, updated), this.audit(records, user.id, "user.update", await time())], result: json({ user: dto(updated) }) };
3285
+ });
3286
+ }),
3287
+ route("/users/password", ["POST"], "resetUserPassword", async (c) => {
3288
+ await this.requireCapability(c, "config.write", true);
3289
+ const b = await body(c.request);
3290
+ exact(b, ["id", "password", "passwordConfirmation"]);
3291
+ const id = this.text(b.id, 64), hash = await this.hash(this.confirmPassword(b));
3292
+ return this.retry(async (time) => {
3293
+ const { records, user } = await this.guard(c, time), u = users(records).find((u) => u.id === id);
3294
+ if (!u)
3295
+ fail("not_found", 404);
3296
+ const r = records.find((r) => r.key === "user:" + id);
3297
+ return { mutations: [this.userMutation({ key: r.key, version: r.version, value: u }, { ...u, generation: next(u.generation) }), this.credentialMutation(records, id, hash), this.audit(records, user.id, "user.password.reset", await time())], result: json({ ok: true }) };
3298
+ });
3299
+ }),
3300
+ route("/identities", ["GET", "POST", "DELETE"], "identities", async (c) => {
3301
+ await this.requireCapability(c, "config.write", c.request.method !== "GET");
3302
+ const b = c.request.method === "GET" ? {} : await body(c.request);
3303
+ exact(b, c.request.method === "POST" ? ["userId", "connectionId", "subject"] : ["id"]);
3304
+ return this.retry(async (time) => {
3305
+ const { records, user } = await this.guard(c, time), all = identities(records);
3306
+ if (c.request.method === "GET") {
3307
+ const id = new URL(c.request.url).searchParams.get("userId");
3308
+ return { mutations: [], result: json({ identities: all.filter((i) => !id || i.userId === id) }) };
3309
+ }
3310
+ let identity;
3311
+ if (c.request.method === "POST") {
3312
+ const connection = connections(records).find((x) => x.id === b.connectionId);
3313
+ if (!connection)
3314
+ fail("not_found", 404);
3315
+ const ms = connection.configuration;
3316
+ if (connection.provider === "microsoft" && ["common", "organizations"].includes(ms.accountType))
3317
+ fail("verified_identity_required");
3318
+ const identityIssuer = connection.provider === "microsoft" && ms.accountType === "consumers" ? "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0" : connection.issuer;
3319
+ const sub = this.text(b.subject, 255), id = digest(identityIssuer + "\x00" + sub);
3320
+ if (connection.provider === "github" && (!/^[1-9][0-9]*$/.test(sub) || !Number.isSafeInteger(Number(sub))))
3321
+ fail("invalid_oauth_identity");
3322
+ if (all.some((x) => x.id === id))
3323
+ fail("identity_exists", 409);
3324
+ if (all.length >= MAX_IDENTITIES)
3325
+ fail("identity_limit", 409);
3326
+ identity = { id, userId: this.text(b.userId, 64), connectionId: connection.id, issuer: identityIssuer, subject: sub, protocol: connection.provider === "github" ? "oauth2" : "oidc" };
3327
+ } else {
3328
+ const found = all.find((i) => i.id === b.id);
3329
+ if (!found)
3330
+ fail("not_found", 404);
3331
+ identity = found;
3332
+ }
3333
+ const target = users(records).find((u) => u.id === identity.userId);
3334
+ if (!target)
3335
+ fail("not_found", 404);
3336
+ const r = records.find((r) => r.key === "user:" + target.id), prior = entry(records, "identity:" + identity.id, "identity", null);
3337
+ return { mutations: [mutation(prior, "identity", c.request.method === "POST" ? identity : null), this.userMutation({ key: r.key, version: r.version, value: target }, { ...target, generation: next(target.generation) }), this.audit(records, user.id, c.request.method === "POST" ? "identity.bind" : "identity.unbind", await time())], result: json({ ok: true }) };
3338
+ });
3339
+ }),
3340
+ route("/login-connections", ["GET", "POST", "PUT", "DELETE"], "loginConnections", async (c) => {
3341
+ await this.requireCapability(c, "config.write", c.request.method !== "GET");
3342
+ if (c.request.method === "GET")
3343
+ return this.retry(async (time) => {
3344
+ const { records } = await this.guard(c, time);
3345
+ return { mutations: [], result: json({ publicOrigin: this.host.managementOrigin ? this.publicOrigin() : null, connections: connectionSlots(records).flatMap((slot) => slot.value && !slot.value.deleting ? [{ ...this.connectionDto(slot.value), version: slot.version }] : []) }) };
3346
+ });
3347
+ const b = await body(c.request);
3348
+ if (c.request.method === "DELETE") {
3349
+ exact(b, ["id", "version"]);
3350
+ const id = this.text(b.id, 64);
3351
+ if (!Number.isSafeInteger(b.version) || b.version < 1)
3352
+ fail("invalid_input");
3353
+ return this.retry(async (time) => {
3354
+ const { records, user } = await this.guard(c, time);
3355
+ const slot = connectionSlots(records).find((s) => s.value?.id === id && !s.value.deleting);
3356
+ if (!slot || slot.version !== b.version)
3357
+ fail("version_conflict", 409);
3358
+ const affectedIds = new Set(identities(records).filter((i) => i.connectionId === id).map((i) => i.userId));
3359
+ const affected = users(records).filter((u) => affectedIds.has(u.id));
3360
+ const issued = this.subjects.get(c.subject), session = await this.findSession(issued.digest);
3361
+ return {
3362
+ mutations: [
3363
+ mutation(slot, "connection", { ...slot.value, enabled: false, deleting: { secretRefs: [...new Set([slot.value.secretRef, ...this.flows(records).filter((f) => f.value?.connectionId === id).map((f) => f.value.verifierRef)])].filter(Boolean) } }),
3364
+ ...affected.map((u) => {
3365
+ const r = records.find((r) => r.key === "user:" + u.id);
3366
+ return this.userMutation({ key: r.key, version: r.version, value: u }, { ...u, generation: next(u.generation) });
3367
+ }),
3368
+ this.audit(records, user.id, "login.delete", await time())
3369
+ ],
3370
+ result: json({ ok: true, sessionRevoked: affectedIds.has(user.id) || session?.value?.connectionId === id })
3371
+ };
3372
+ });
3373
+ }
3374
+ exact(b, ["id", "version", "label", "enabled", "provider", "configuration", "clientId", "clientSecret", "scopes"]);
3375
+ const id = this.text(b.id, 36), label = this.text(b.label, 128), clientId = this.text(b.clientId, 512);
3376
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(id) || b.enabled !== undefined && typeof b.enabled !== "boolean" || c.request.method === "POST" && b.version !== undefined || c.request.method === "PUT" && (!Number.isSafeInteger(b.version) || b.version < 1))
3377
+ fail("invalid_input");
3378
+ const parsed = parseProvider(b.provider, b.configuration, b.scopes);
3379
+ const previous = c.request.method === "PUT" ? await this.retry(async (time) => {
3380
+ const { records } = await this.guard(c, time);
3381
+ const slot = connectionSlots(records).find((slot) => slot.value?.id === id && !slot.value.deleting);
3382
+ if (!slot?.value || slot.version !== b.version)
3383
+ fail("version_conflict", 409);
3384
+ if (slot.value.provider !== parsed.provider)
3385
+ fail("invalid_input");
3386
+ return { mutations: [], result: slot.value };
3387
+ }) : undefined;
3388
+ const disableOnly = previous && b.enabled === false && b.clientSecret === undefined && previous.clientId === clientId && previous.issuer === parsed.issuer && isDeepStrictEqual(previous.configuration, parsed.configuration) && isDeepStrictEqual(previous.scopes, parsed.scopes);
3389
+ if (!disableOnly)
3390
+ await this.loginProviders().validate({ ...parsed, clientId });
3391
+ const secretRef = b.clientSecret === undefined ? undefined : await this.storeSecret(this.text(b.clientSecret, 8192));
3392
+ let replacedRef;
3393
+ try {
3394
+ return await this.retry(async (time) => {
3395
+ const { records, user } = await this.guard(c, time), slots = connectionSlots(records), record = c.request.method === "POST" ? slots.find((s) => s.value?.id === id) ?? slots.find((s) => s.key === "connection:" + id) ?? slots.find((s) => s.value === null) : slots.find((s) => s.value?.id === id && !s.value.deleting), old = record?.value;
3396
+ if (c.request.method === "PUT" && (!old || record?.version !== b.version))
3397
+ fail("version_conflict", 409);
3398
+ if (old && old.provider !== parsed.provider)
3399
+ fail("invalid_input");
3400
+ if (c.request.method === "POST" && old)
3401
+ fail("version_conflict", 409);
3402
+ if (c.request.method === "POST" && slots.filter((s) => s.value !== null).length >= MAX_CONNECTIONS)
3403
+ fail("connection_limit", 409);
3404
+ replacedRef = old?.secretRef;
3405
+ const connection = { id, label, enabled: b.enabled ?? false, ...parsed, clientId, secretRef: secretRef ?? old?.secretRef ?? "", generation: next(old?.generation ?? 0) };
3406
+ if (connection.enabled && !connection.secretRef)
3407
+ fail("oidc_secret_required");
3408
+ return { mutations: [{ key: record?.key ?? "connection:" + id, expectedVersion: record?.version ?? 0, value: asJson({ schema: ACCOUNT_RECORD_SCHEMA, connection }) }, this.audit(records, user.id, "login.configure", await time())], result: json({ connection: { ...this.connectionDto(connection), version: (record?.version ?? 0) + 1 } }, c.request.method === "POST" ? 201 : 200) };
3409
+ });
3410
+ } finally {
3411
+ if (secretRef)
3412
+ await this.discardUnpublishedSecret(secretRef);
3413
+ if (replacedRef)
3414
+ await this.discardUnpublishedSecret(replacedRef);
3415
+ }
3416
+ }),
3417
+ route("/login-connections/discover", ["POST"], "discoverLoginConnection", async (c) => {
3418
+ await this.requireCapability(c, "config.write", true);
3419
+ const b = await body(c.request);
3420
+ exact(b, ["mode", "url"]);
3421
+ const metadata = await this.loginProviders().discover(b.mode, b.url);
3422
+ return this.retry(async (time) => {
3423
+ await this.guard(c, time);
3424
+ return { mutations: [], result: json(metadata) };
3425
+ });
3426
+ }),
3427
+ route("/login-settings", ["GET", "PUT"], "loginSettings", async (c) => {
3428
+ await this.requireCapability(c, "config.write", c.request.method === "PUT");
3429
+ const b = c.request.method === "PUT" ? await body(c.request) : {};
3430
+ exact(b, ["version", "autoCreateUsers"]);
3431
+ return this.retry(async (time) => {
3432
+ const { records, user } = await this.guard(c, time), s = settings(records);
3433
+ if (c.request.method === "GET")
3434
+ return { mutations: [], result: json({ version: s.version, autoCreateUsers: s.value.autoCreateUsers }) };
3435
+ if (typeof b.autoCreateUsers !== "boolean")
3436
+ fail("invalid_input");
3437
+ if (b.version !== s.version)
3438
+ fail("version_conflict", 409);
3439
+ return { mutations: [mutation(s, "settings", { ...s.value, autoCreateUsers: b.autoCreateUsers }), this.audit(records, user.id, "login.configure", await time())], result: json({ version: s.version + 1, autoCreateUsers: b.autoCreateUsers }) };
3440
+ });
3441
+ }),
3442
+ route("/turnstile", ["GET", "PUT"], "turnstile", async (c) => {
3443
+ await this.requireCapability(c, "config.write", c.request.method === "PUT");
3444
+ if (c.request.method === "GET")
3445
+ return this.retry(async (time) => {
3446
+ const { records } = await this.guard(c, time), s = turnstile(records);
3447
+ return { mutations: [], result: json(this.turnstileDto(s)) };
3448
+ });
3449
+ const b = await body(c.request);
3450
+ exact(b, ["version", "enabled", "siteKey", "secret", "hostnames"]);
3451
+ if (typeof b.enabled !== "boolean" || typeof b.siteKey !== "string" || b.siteKey.length > 256 || !Array.isArray(b.hostnames) || b.hostnames.length > 32 || b.hostnames.some((h) => typeof h !== "string" || !h || h.length > 253 || !/^([a-z0-9-]+\.)*[a-z0-9-]+$/i.test(h)))
3452
+ fail("invalid_input");
3453
+ const ref = b.secret === undefined ? undefined : await this.storeSecret(this.text(b.secret, 8192));
3454
+ let replacedRef;
3455
+ try {
3456
+ return await this.retry(async (time) => {
3457
+ const { records, user } = await this.guard(c, time), s = turnstile(records);
3458
+ replacedRef = s.value.secretRef;
3459
+ if (s.version !== b.version)
3460
+ fail("version_conflict", 409);
3461
+ const v = { enabled: b.enabled, siteKey: b.siteKey, secretRef: ref ?? s.value.secretRef, hostnames: b.hostnames };
3462
+ if (v.enabled && (!v.siteKey || !v.secretRef || !v.hostnames.length))
3463
+ fail("turnstile_incomplete");
3464
+ return { mutations: [mutation(s, "turnstile", v), this.audit(records, user.id, "turnstile.configure", await time())], result: json(this.turnstileDto({ key: s.key, version: s.version + 1, value: v })) };
3465
+ });
3466
+ } finally {
3467
+ if (ref)
3468
+ await this.discardUnpublishedSecret(ref);
3469
+ if (replacedRef)
3470
+ await this.discardUnpublishedSecret(replacedRef);
3471
+ }
3472
+ }),
3473
+ route("/identities/link/start", ["POST"], "beginIdentityLink", async (c) => {
3474
+ await this.requireCapability(c, "config.write", true);
3475
+ const b = await body(c.request);
3476
+ exact(b, ["connectionId"]);
3477
+ return this.startFlow(c.request, this.text(b.connectionId, 64), "/#/extensions/user-auth/profile", c);
3478
+ }),
3479
+ route("/identities/link/finish", ["POST"], "finishIdentityLink", async (c) => {
3480
+ await this.requireCapability(c, "config.write", true);
3481
+ const b = await body(c.request);
3482
+ exact(b, ["flowId"]);
3483
+ return this.finishLink(c, this.text(b.flowId, 64));
3484
+ })
3485
+ ];
3486
+ }
3487
+ displayName(v) {
3488
+ if (typeof v !== "string" || [...v].length > 128)
3489
+ fail("invalid_input");
3490
+ return v;
3491
+ }
3492
+ text(v, max) {
3493
+ if (typeof v !== "string" || !v || v.length > max)
3494
+ fail("invalid_input");
3495
+ return v;
3496
+ }
3497
+ confirmPassword(b) {
3498
+ const p = password(b.password);
3499
+ if (p !== b.passwordConfirmation)
3500
+ fail("password_confirmation");
3501
+ return p;
3502
+ }
3503
+ connectionDto(v) {
3504
+ return { id: v.id, label: v.label, enabled: v.enabled, provider: v.provider, configuration: v.configuration, issuer: v.issuer, clientId: v.clientId, scopes: v.scopes, hasSecret: !!v.secretRef };
3505
+ }
3506
+ turnstileDto(s) {
3507
+ return { version: s.version, enabled: s.value.enabled, siteKey: s.value.siteKey, hostnames: s.value.hostnames, hasSecret: !!s.value.secretRef };
3508
+ }
3509
+ async storeSecret(secret) {
3510
+ const encoded = JSON.stringify({ owner: randomUUID(), value: secret });
3511
+ for (let index = 0;index < MAX_SECRETS; index++) {
3512
+ const key = "auth-secret:" + index;
3513
+ if (await this.host.secretStore.get(key))
3514
+ continue;
3515
+ try {
3516
+ const version = await this.host.secretStore.compareAndSet(key, null, encoded);
3517
+ return key + ":" + version;
3518
+ } catch (error) {
3519
+ const saved = await this.host.secretStore.get(key);
3520
+ if (saved && constant(saved.value, encoded))
3521
+ return key + ":" + saved.version;
3522
+ if (error && typeof error === "object" && "code" in error && error.code === "version_conflict")
3523
+ continue;
3524
+ throw error;
3525
+ }
3526
+ }
3527
+ return fail("secret_limit", 503);
3528
+ }
3529
+ async readSecret(ref) {
3530
+ const match = /^auth-secret:(\d+):([1-9]\d*)$/.exec(ref);
3531
+ if (!match || Number(match[1]) >= MAX_SECRETS)
3532
+ return null;
3533
+ const key = "auth-secret:" + match[1], saved = await this.host.secretStore.get(key);
3534
+ if (saved?.version !== Number(match[2]))
3535
+ return null;
3536
+ try {
3537
+ const value = JSON.parse(saved.value);
3538
+ return typeof value?.owner === "string" && typeof value.value === "string" ? { version: saved.version, value: value.value, key } : null;
3539
+ } catch {
3540
+ return null;
3541
+ }
3542
+ }
3543
+ async deleteSecret(ref) {
3544
+ const saved = await this.readSecret(ref);
3545
+ if (saved)
3546
+ await this.host.secretStore.delete(saved.key, saved.version);
3547
+ }
3548
+ async discardUnpublishedSecret(ref) {
3549
+ try {
3550
+ const records = await this.records();
3551
+ if (records.some((r) => JSON.stringify(r.value).includes(ref)))
3552
+ return;
3553
+ await this.deleteSecret(ref);
3554
+ } catch {}
3555
+ }
3556
+ async verifyTurnstile(token, config, request) {
3557
+ if (!config.enabled)
3558
+ return;
3559
+ if (typeof token !== "string" || !token || token.length > 2048)
3560
+ fail("turnstile_required", 401);
3561
+ await this.retry(async (time) => {
3562
+ const records = await this.records(), s = settings(records).value, all = slots(records, "failure", MAX_FAILURES, validateFailure), key = "challenge:" + digest(token), now = await time();
3563
+ if (all.some((f) => f.value?.key === key && f.value.until > now))
3564
+ fail("turnstile_failed", 401);
3565
+ const slot = all.find((f) => !f.value || f.value.until <= now);
3566
+ if (!slot)
3567
+ fail("login_limited", 429);
3568
+ return { mutations: [mutation(slot, "failure", { key, count: 1, until: now + 5 * 60000, epoch: s.failureEpoch })], result: undefined };
3569
+ });
3570
+ const secret = await this.readSecret(config.secretRef);
3571
+ if (!secret)
3572
+ fail("turnstile_failed", 401);
3573
+ try {
3574
+ const response = await (this.dependencies.fetch ?? fetch)("https://challenges.cloudflare.com/turnstile/v0/siteverify", { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ secret: secret.value, response: token }), signal: AbortSignal.any([request.signal, this.host.signal, AbortSignal.timeout(5000)]), redirect: "error" });
3575
+ const result = await response.json();
3576
+ if (!response.ok || result.success !== true || typeof result.hostname !== "string" || !config.hostnames.includes(result.hostname) || result.action !== "local-login")
3577
+ fail("turnstile_failed", 401);
3578
+ } catch (e) {
3579
+ if (e instanceof AccountError)
3580
+ throw e;
3581
+ fail("turnstile_failed", 401);
3582
+ }
3583
+ }
3584
+ publicOrigin() {
3585
+ const configured = this.host.managementOrigin;
3586
+ if (!configured)
3587
+ fail("management_origin_required", 503);
3588
+ let url;
3589
+ try {
3590
+ url = new URL(configured);
3591
+ } catch {
3592
+ fail("management_origin_required", 503);
3593
+ }
3594
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.pathname !== "/" || url.search || url.hash || url.origin !== configured)
3595
+ fail("management_origin_required", 503);
3596
+ return url.origin;
3597
+ }
3598
+ returnPath(value) {
3599
+ if (value === undefined)
3600
+ return "/#/";
3601
+ if (typeof value !== "string" || !value.startsWith("/") || value.startsWith("//") || value.includes("\\") || /[\x00-\x20]/.test(value) || value.length > 2048)
3602
+ fail("invalid_return_to");
3603
+ const u = new URL(value, this.publicOrigin());
3604
+ if (u.origin !== this.publicOrigin())
3605
+ fail("invalid_return_to");
3606
+ return u.pathname + u.search + u.hash;
3607
+ }
3608
+ flowCookie(token, maxAge = 600) {
3609
+ return `${FLOW_COOKIE}=${token}; Path=/api/auth/login/callback/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}${this.publicOrigin().startsWith("https:") ? "; Secure" : ""}`;
3610
+ }
3611
+ flows(records) {
3612
+ return slots(records, "flow", MAX_FLOWS, (v) => {
3613
+ if (v === null)
3614
+ return null;
3615
+ const f = v;
3616
+ if (!f || Object.keys(f).some((k) => !["id", "state", "nonce", "verifierRef", "connectionId", "generation", "created", "expiresAt", "epoch", "returnTo", "status", "initiator", "subject", "issuer", "protocol"].includes(k)) || typeof f.id !== "string" || typeof f.state !== "string" || typeof f.nonce !== "string" || typeof f.verifierRef !== "string" || typeof f.connectionId !== "string" || !Number.isSafeInteger(f.generation) || !Number.isSafeInteger(f.created) || !Number.isSafeInteger(f.expiresAt) || !Number.isSafeInteger(f.epoch) || !["pending", "exchanging", "verified", "consumed"].includes(f.status) || typeof f.returnTo !== "string" || f.subject !== undefined && typeof f.subject !== "string" || f.issuer !== undefined && typeof f.issuer !== "string" || f.protocol !== undefined && !["oidc", "oauth2"].includes(f.protocol) || f.status === "verified" && (!f.subject || !f.issuer || !f.protocol) || f.initiator !== undefined && (!f.initiator || typeof f.initiator.userId !== "string" || typeof f.initiator.sessionDigest !== "string" || !Number.isSafeInteger(f.initiator.generation)))
3617
+ fail("corrupt_state", 503);
3618
+ return f;
3619
+ });
3620
+ }
3621
+ loginProviders() {
3622
+ return new LoginProviders(this.dependencies.fetch ?? fetch, this.host.signal);
3623
+ }
3624
+ async oidcConfig(connection) {
3625
+ const secret = await this.readSecret(connection.secretRef);
3626
+ if (!secret)
3627
+ fail("oidc_unavailable", 503);
3628
+ return this.loginProviders().configuration(connection, secret.value);
3629
+ }
3630
+ async loginOptions(_request) {
3631
+ try {
3632
+ this.alive();
3633
+ const records = await this.records(), t = turnstile(records).value;
3634
+ return json({ local: { enabled: true, turnstile: { enabled: t.enabled, ...t.enabled ? { siteKey: t.siteKey } : {} } }, methods: connections(records).filter((c) => c.enabled).map((c) => ({ id: c.id, label: c.label, provider: c.provider })) });
3635
+ } catch (e) {
3636
+ return safe(e);
3637
+ }
3638
+ }
3639
+ async beginLogin(request) {
3640
+ try {
3641
+ this.alive();
3642
+ origin(request, this.host.managementOrigin);
3643
+ const b = await body(request);
3644
+ exact(b, ["methodId", "returnTo"]);
3645
+ return await this.startFlow(request, this.text(b.methodId, 64), this.returnPath(b.returnTo));
3646
+ } catch (e) {
3647
+ return safe(e);
3648
+ }
3649
+ }
3650
+ async startFlow(request, methodId, returnTo, c) {
3651
+ const publicOrigin = this.publicOrigin();
3652
+ await this.reserve(["source:" + digest(this.source(request))]);
3653
+ const connection = connections(await this.records()).find((c) => c.id === methodId && c.enabled);
3654
+ if (!connection)
3655
+ fail("oidc_unavailable", 401);
3656
+ const config = await this.oidcConfig(connection), state = randomState(), nonce = randomNonce(), verifier = randomPKCECodeVerifier(), challenge = await calculatePKCECodeChallenge2(verifier), verifierRef = await this.storeSecret(verifier), id = randomUUID();
3657
+ let replacedRef;
3658
+ try {
3659
+ await this.retry(async (time) => {
3660
+ const guarded = c ? await this.guard(c, time) : undefined, records = guarded?.records ?? await this.records(), current = connections(records).find((x) => x.id === methodId), now = await time();
3661
+ if (!current?.enabled || current.generation !== connection.generation)
3662
+ fail("version_conflict", 409);
3663
+ const slot = this.flows(records).find((s) => !s.value || s.value.expiresAt <= now || s.value.status === "consumed");
3664
+ if (!slot)
3665
+ fail("flow_limit", 429);
3666
+ replacedRef = slot.value?.verifierRef;
3667
+ const flow = { id, state, nonce, verifierRef, connectionId: methodId, generation: current.generation, created: now, expiresAt: now + 10 * 60000, epoch: settings(records).value.sessionEpoch, returnTo, status: "pending", ...guarded ? { initiator: { userId: guarded.user.id, generation: guarded.user.generation, sessionDigest: this.subjects.get(c.subject).digest } } : {} };
3668
+ return { mutations: [mutation(slot, "flow", flow)], result: undefined };
3669
+ });
3670
+ const authorizationUrl = buildAuthorizationUrl(config, { redirect_uri: publicOrigin + "/api/auth/login/callback/" + methodId, scope: connection.scopes.join(" "), response_type: "code", code_challenge: challenge, code_challenge_method: "S256", state, ...connection.provider === "github" ? {} : { nonce } }).href;
3671
+ return json({ authorizationUrl, flowId: id }, 200, { "set-cookie": this.flowCookie(state) });
3672
+ } finally {
3673
+ await this.discardUnpublishedSecret(verifierRef);
3674
+ if (replacedRef)
3675
+ await this.discardUnpublishedSecret(replacedRef);
3676
+ }
3677
+ }
3678
+ async completeLogin(request) {
3679
+ let verifierRef;
3680
+ try {
3681
+ this.alive();
3682
+ const publicOrigin = this.publicOrigin(), incoming = new URL(request.url), methodId = incoming.pathname.split("/").at(-1), state = incoming.searchParams.get("state");
3683
+ const cookies = (request.headers.get("cookie") ?? "").split(";").map((c) => c.trim()).filter((c) => c.startsWith(FLOW_COOKIE + "="));
3684
+ if (!state || cookies.length !== 1 || !constant(cookies[0].slice(FLOW_COOKIE.length + 1), state))
3685
+ fail("invalid_oidc_state", 401);
3686
+ const flow = await this.retry(async (time) => {
3687
+ const records = await this.records(), slot = this.flows(records).find((s) => s.value?.state === state), connection = connections(records).find((c) => c.id === methodId), now = await time(), f = slot?.value;
3688
+ if (!slot || !f || f.connectionId !== methodId || f.status !== "pending" || f.epoch !== settings(records).value.sessionEpoch || f.expiresAt <= now || !connection?.enabled || connection.generation !== f.generation)
3689
+ fail("invalid_oidc_flow", 401);
3690
+ return { mutations: [mutation(slot, "flow", { ...f, status: "exchanging" })], result: { flow: f, connection } };
3691
+ });
3692
+ verifierRef = flow.flow.verifierRef;
3693
+ const verifier = await this.readSecret(verifierRef);
3694
+ if (!verifier)
3695
+ fail("invalid_oidc_flow", 401);
3696
+ const config = await this.oidcConfig(flow.connection);
3697
+ const canonical = new URL(publicOrigin + "/api/auth/login/callback/" + methodId);
3698
+ canonical.search = incoming.search;
3699
+ const identityResult = await this.loginProviders().exchange(flow.connection, config, canonical, { pkceCodeVerifier: verifier.value, expectedState: state, expectedNonce: flow.flow.nonce });
3700
+ const token = randomBytes2(32).toString("base64url");
3701
+ const result = await this.retry(async (time) => {
3702
+ const records = await this.records(), slot = this.flows(records).find((s) => s.value?.id === flow.flow.id), f = slot?.value, current = connections(records).find((c) => c.id === methodId), policy = (await this.readSessionPolicy()).policy, initiatingSession = f?.initiator ? await this.findSession(f.initiator.sessionDigest) : undefined, now = await time();
3703
+ if (!slot || !f || f.status !== "exchanging" || f.epoch !== settings(records).value.sessionEpoch || f.expiresAt <= now || !current?.enabled || current.generation !== f.generation)
3704
+ fail("invalid_oidc_flow", 401);
3705
+ if (f.initiator) {
3706
+ const u = users(records).find((u) => u.id === f.initiator.userId), session = initiatingSession;
3707
+ if (!u || u.generation !== f.initiator.generation || !session?.value || !this.live(u, session.value, now, records))
3708
+ fail("forbidden", 403);
3709
+ return { mutations: [mutation(slot, "flow", { ...f, status: "verified", subject: identityResult.subject, issuer: identityResult.issuer, protocol: identityResult.protocol })], result: { link: true, returnTo: "/#/extensions/user-auth/profile?oidcLink=" + f.id } };
3710
+ }
3711
+ const id = digest(identityResult.issuer + "\x00" + identityResult.subject), binding = identities(records).find((i) => i.id === id), all = users(records);
3712
+ let u = binding && all.find((u) => u.id === binding.userId);
3713
+ const changes = [];
3714
+ if (binding && !u)
3715
+ fail("corrupt_state", 503);
3716
+ if (binding && (binding.connectionId !== current.id || binding.protocol !== identityResult.protocol))
3717
+ fail("identity_connection_mismatch", 401);
3718
+ if (!u) {
3719
+ if (!settings(records).value.autoCreateUsers)
3720
+ fail("unknown_identity", 401);
3721
+ if (all.length >= MAX_USERS || identities(records).length >= MAX_IDENTITIES)
3722
+ fail("user_limit", 409);
3723
+ const userId = randomUUID();
3724
+ u = { id: userId, username: "sso-" + userId, displayName: identityResult.displayName, disabled: false, generation: 1, passwordHash: "", temporary: false };
3725
+ changes.push(this.userMutation({ key: "user:" + userId, version: 0, value: null }, u));
3726
+ const identity = { id, userId, connectionId: current.id, issuer: identityResult.issuer, subject: identityResult.subject, protocol: identityResult.protocol };
3727
+ changes.push(mutation(entry(records, "identity:" + id, "identity", null), "identity", identity));
3728
+ }
3729
+ if (u.disabled)
3730
+ fail("disabled_user", 401);
3731
+ const sessions = slots(records, "session", MAX_SESSIONS, validateSession), session = sessions.find((s) => !s.value || !this.live(all.find((u) => u.id === s.value.administratorId) ?? null, s.value, now, records));
3732
+ if (!session)
3733
+ fail("session_limit", 429);
3734
+ changes.push(mutation(session, "session", { digest: digest(token), administratorId: u.id, generation: u.generation, created: now, touched: now, transport: "cookie", policy, epoch: settings(records).value.sessionEpoch, connectionId: current.id, connectionGeneration: current.generation }), mutation(slot, "flow", { ...f, status: "consumed" }), this.audit(records, u.id, "oidc.login", now));
3735
+ return { mutations: changes, result: { link: false, returnTo: f.returnTo, policy } };
3736
+ });
3737
+ const headers = new Headers({ location: new URL(result.returnTo, publicOrigin).href, "cache-control": "no-store" });
3738
+ headers.append("set-cookie", this.flowCookie("", 0));
3739
+ if (!result.link && result.policy)
3740
+ headers.append("set-cookie", cookie(request, token, this.cookieAge(result.policy), publicOrigin));
3741
+ return new Response(null, { status: 302, headers });
3742
+ } catch (error) {
3743
+ const code = error instanceof AccountError ? error.code : "oidc_failed";
3744
+ await this.recordFailure("oidc.login", error);
3745
+ try {
3746
+ return new Response(null, { status: 302, headers: { location: this.publicOrigin() + "/#/login?oidcError=" + encodeURIComponent(code), "cache-control": "no-store", "set-cookie": this.flowCookie("", 0) } });
3747
+ } catch {
3748
+ return safe(error);
3749
+ }
3750
+ } finally {
3751
+ if (verifierRef) {
3752
+ try {
3753
+ await this.deleteSecret(verifierRef);
3754
+ } catch {}
3755
+ }
3756
+ }
3757
+ }
3758
+ async finishLink(c, id) {
3759
+ return this.retry(async (time) => {
3760
+ const { records, user } = await this.guard(c, time), slot = this.flows(records).find((s) => s.value?.id === id), f = slot?.value, now = await time(), connection = f && connections(records).find((x) => x.id === f.connectionId), issued = this.subjects.get(c.subject);
3761
+ if (!slot || !f || f.status !== "verified" || f.epoch !== settings(records).value.sessionEpoch || f.expiresAt <= now || !f.subject || !f.issuer || !f.protocol || !f.initiator || f.initiator.userId !== user.id || f.initiator.generation !== user.generation || f.initiator.sessionDigest !== issued.digest || !connection?.enabled || connection.generation !== f.generation)
3762
+ fail("invalid_oidc_flow", 401);
3763
+ const identity = { id: digest(f.issuer + "\x00" + f.subject), userId: user.id, connectionId: connection.id, issuer: f.issuer, subject: f.subject, protocol: f.protocol };
3764
+ if (identities(records).some((i) => i.id === identity.id))
3765
+ fail("identity_exists", 409);
3766
+ if (identities(records).length >= MAX_IDENTITIES)
3767
+ fail("identity_limit", 409);
3768
+ const u = records.find((r) => r.key === "user:" + user.id);
3769
+ return { mutations: [mutation(entry(records, "identity:" + identity.id, "identity", null), "identity", identity), this.userMutation({ key: u.key, version: u.version, value: user }, { ...user, generation: next(user.generation) }), mutation(slot, "flow", { ...f, status: "consumed" }), this.audit(records, user.id, "identity.link", now)], result: json({ ok: true }) };
3770
+ });
3771
+ }
3772
+ }
3773
+ function createControl(host, dependencies = {}) {
3774
+ return new UserAuthControl(host, dependencies);
3775
+ }
3776
+ var api = [
3777
+ { path: "/password", methods: ["POST"], handler: "changePassword" },
3778
+ { path: "/logout", methods: ["POST"], handler: "logout" },
3779
+ { path: "/self", methods: ["GET"], handler: "self" },
3780
+ { path: "/session-policy", methods: ["GET", "PUT"], handler: "sessionPolicy" },
3781
+ ...[["/users", ["GET", "POST"], "users"], ["/users/update", ["PUT"], "updateUser"], ["/users/password", ["POST"], "resetUserPassword"], ["/identities", ["GET", "POST", "DELETE"], "identities"], ["/login-connections", ["GET", "POST", "PUT", "DELETE"], "loginConnections"], ["/login-connections/discover", ["POST"], "discoverLoginConnection"], ["/login-settings", ["GET", "PUT"], "loginSettings"], ["/turnstile", ["GET", "PUT"], "turnstile"], ["/identities/link/start", ["POST"], "beginIdentityLink"], ["/identities/link/finish", ["POST"], "finishIdentityLink"]].map(([path, methods, handler]) => ({ path, methods, handler }))
3782
+ ];
3783
+ var rpc = [];
3784
+ var controlApi = api;
3785
+ var controlRpc = rpc;
3786
+ async function readManagementSetup(state) {
3787
+ const records = await state.list(), setup = setupRecord(records);
3788
+ slots(records, "session", MAX_SESSIONS, validateSession);
3789
+ slots(records, "failure", MAX_FAILURES, validateFailure);
3790
+ const policy = records.find((r) => r.key === "session-policy");
3791
+ if (policy)
3792
+ validateSessionPolicy(envelope(policy.value, "policy"), "corrupt_state", 503);
3793
+ return { initialized: setup.value.phase === "ready" };
3794
+ }
3795
+ async function prepareInitialization(context) {
3796
+ const state = context.durableState, now = context.now ?? Date.now;
3797
+ for (let i = 0;i < 64; i++) {
3798
+ const records = await state.list(), fresh = !records.length;
3799
+ if (!fresh)
3800
+ await readManagementSetup(state);
3801
+ const setup = fresh ? undefined : setupRecord(records);
3802
+ if (setup?.value.phase === "ready")
3803
+ fail("already_initialized", 409);
3804
+ const clock = fresh ? { version: 0, value: wallTime(now) } : await observeClock(state, now), code = randomBytes2(32).toString("base64url"), expiresAt = clock.value + 30 * 60000;
3805
+ try {
3806
+ await state.transact([{ key: "setup", expectedVersion: setup?.version ?? 0, value: asJson({ schema: ACCOUNT_RECORD_SCHEMA, setup: { phase: "uninitialized", code: { digest: digest(code), expiresAt } } }) }, clockMutation(clock)]);
3807
+ return { code, expiresAt };
3808
+ } catch (e) {
3809
+ if (!isDurableStateConflictError(e))
3810
+ throw e;
3811
+ }
3812
+ }
3813
+ return fail("version_conflict", 409);
3814
+ }
3815
+ async function recoverIdentity(input, context) {
3816
+ const b = object(input);
3817
+ exact(b, ["operation", "username", "password", "passwordConfirmation", "reason", "disableTurnstile"]);
3818
+ if (b.operation !== "reset" || b.disableTurnstile !== undefined && !["", "yes", "no"].includes(b.disableTurnstile))
3819
+ fail("invalid_input");
3820
+ const name = username(b.username), p = password(b.password);
3821
+ if (b.passwordConfirmation !== p)
3822
+ fail("password_confirmation");
3823
+ if (typeof b.reason !== "string" || !b.reason.trim() || b.reason.length > 512)
3824
+ fail("invalid_reason");
3825
+ const state = context.durableState, now = context.now ?? Date.now, hash = await Bun.password.hash(p, { algorithm: "argon2id", memoryCost: 65536, timeCost: 3 });
3826
+ for (let i = 0;i < 64; i++) {
3827
+ const records = await state.list();
3828
+ validateKeys(records);
3829
+ const anchor = records.find((r) => r.key === "recovery-anchor");
3830
+ let anchorId;
3831
+ if (anchor) {
3832
+ const id = envelope(anchor.value, "userId");
3833
+ if (typeof id !== "string" || !/^[a-f0-9-]{36}$/.test(id))
3834
+ fail("corrupt_state", 503);
3835
+ anchorId = id;
3836
+ }
3837
+ for (const r of records)
3838
+ if (r.value?.schema !== ACCOUNT_RECORD_SCHEMA)
3839
+ fail("corrupt_state", 503);
3840
+ const id = anchorId ?? randomUUID(), record = records.find((r) => r.key === "user:" + id);
3841
+ let prior;
3842
+ if (record) {
3843
+ try {
3844
+ prior = validateUser(envelope(record.value, "user"));
3845
+ } catch (error) {
3846
+ if (!(error instanceof AccountError))
3847
+ throw error;
3848
+ }
3849
+ }
3850
+ const other = records.filter((r) => r.key.startsWith("user:") && r.key !== "user:" + id).map((r) => validateUser(envelope(r.value, "user")));
3851
+ if (other.some((u) => u.username === name))
3852
+ fail("username_exists", 409);
3853
+ const s = settings(records), turn = turnstile(records), clockRecord = records.find((r) => r.key === "clock");
3854
+ let observed = 0;
3855
+ try {
3856
+ observed = clockValue(clockRecord);
3857
+ } catch (error) {
3858
+ if (!(error instanceof AccountError))
3859
+ throw error;
3860
+ }
3861
+ const at = Math.max(observed, wallTime(now)), user = { id, username: name, displayName: prior?.displayName ?? name, disabled: false, generation: next(prior?.generation ?? 0) }, credential = records.find((r) => r.key === "credential:" + id);
3862
+ const failures = slots(records, "failure", MAX_FAILURES, validateFailure);
3863
+ slots(records, "session", MAX_SESSIONS, validateSession);
3864
+ connections(records);
3865
+ identities(records);
3866
+ const changes = [
3867
+ { key: "user:" + id, expectedVersion: record?.version ?? 0, value: asJson({ schema: ACCOUNT_RECORD_SCHEMA, user }) },
3868
+ { key: "credential:" + id, expectedVersion: credential?.version ?? 0, value: { schema: ACCOUNT_RECORD_SCHEMA, credential: { passwordHash: hash, temporary: false } } },
3869
+ { key: "recovery-anchor", expectedVersion: anchor?.version ?? 0, value: { schema: ACCOUNT_RECORD_SCHEMA, userId: id } },
3870
+ { key: "setup", expectedVersion: records.find((r) => r.key === "setup")?.version ?? 0, value: { schema: ACCOUNT_RECORD_SCHEMA, setup: { phase: "ready" } } },
3871
+ { key: "clock", expectedVersion: clockRecord?.version ?? 0, value: { schema: ACCOUNT_RECORD_SCHEMA, observedThrough: at } },
3872
+ mutation(s, "settings", { ...s.value, sessionEpoch: next(s.value.sessionEpoch), failureEpoch: next(Math.max(s.value.failureEpoch, ...failures.map((f) => f.value?.epoch ?? 0))) }),
3873
+ { key: "recovery-summary", expectedVersion: records.find((r) => r.key === "recovery-summary")?.version ?? 0, value: { schema: ACCOUNT_RECORD_SCHEMA, userId: id, reason: b.reason, at } },
3874
+ mutation(turn, "turnstile", { ...turn.value, ...b.disableTurnstile === "yes" ? { enabled: false } : {} })
3875
+ ];
3876
+ try {
3877
+ await state.transact(changes);
3878
+ return dto({ ...user, passwordHash: hash, temporary: false });
3879
+ } catch (e) {
3880
+ if (!isDurableStateConflictError(e))
3881
+ throw e;
3882
+ }
3883
+ }
3884
+ return fail("version_conflict", 409);
3885
+ }
3886
+ var control_default = { createControl, readManagementSetup, offlineRecovery: {
3887
+ kind: "identity",
3888
+ prepareInitialization,
3889
+ prompts: [{ name: "username", label: "Recovery username", secret: false }, { name: "password", label: "New password", secret: true }, { name: "passwordConfirmation", label: "Confirm password", secret: true }, { name: "reason", label: "Recovery reason", secret: false }, { name: "disableTurnstile", label: "Disable Turnstile? Type yes to disable; default no", secret: false }],
3890
+ recover: recoverIdentity
3891
+ } };
3892
+ export {
3893
+ ACCOUNT_RECORD_SCHEMA,
3894
+ LoginProviders,
3895
+ MAX_FAILURES,
3896
+ MAX_SESSIONS,
3897
+ UserAuthControl,
3898
+ api,
3899
+ controlApi,
3900
+ controlRpc,
3901
+ createControl,
3902
+ control_default as default,
3903
+ parseProvider,
3904
+ prepareInitialization,
3905
+ providerUrl,
3906
+ readManagementSetup,
3907
+ recoverIdentity,
3908
+ rpc
3909
+ };
3910
+
3911
+ //# debugId=5FEFB6DCD014AEAE64756E2164756E21