@jeffusion/bungee-core 5.26.0 → 6.0.2

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 +455 -223
  2. package/dist/main.js +803 -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
@@ -1,715 +0,0 @@
1
- // @bun
2
- // plugins/local-accounts/server/control.ts
3
- import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "crypto";
4
-
5
- // packages/core/src/plugin-logger.ts
6
- var sinkKey = Symbol.for("@jeffusion/bungee/plugin-log-sink");
7
- function createLogger(bindings = {}) {
8
- const method = (level) => (value, message) => {
9
- const sink = globalThis[sinkKey];
10
- if (sink) {
11
- if (typeof value === "string")
12
- sink[level](bindings, value);
13
- else
14
- sink[level]({ ...bindings, ...value }, message);
15
- } else if (level !== "debug" || process.env.LOG_LEVEL === "debug") {
16
- const write = level === "fatal" ? console.error : console[level];
17
- write(message ?? (typeof value === "string" ? value : ""), typeof value === "string" ? bindings : { ...bindings, ...value });
18
- }
19
- };
20
- return {
21
- info: method("info"),
22
- warn: method("warn"),
23
- error: method("error"),
24
- debug: method("debug"),
25
- fatal: method("fatal"),
26
- child: (extra) => createLogger({ ...bindings, ...extra })
27
- };
28
- }
29
- var logger = createLogger();
30
-
31
- // packages/core/src/hooks/impl.ts
32
- var WATERFALL_VIEW_TRANSFORM = Symbol("waterfall-view-transform");
33
- // packages/core/src/plugin-extensions.ts
34
- var ANONYMOUS_PRINCIPAL = Object.freeze({ domain: "anonymous", keyId: "", credentialVersion: 0 });
35
- // packages/core/src/plugin-durable-state.ts
36
- function isDurableStateConflictError(error) {
37
- return error instanceof Error && "code" in error && error.code === "durable_state_conflict";
38
- }
39
- var MAX_BYTES = 1024 * 1024;
40
- // packages/core/src/plugin-services/host-rpc.ts
41
- import { AsyncLocalStorage } from "async_hooks";
42
-
43
- // packages/core/src/plugin-services/wire-contract.ts
44
- var RPC_JSON_MAX_BYTES = 64 * 1024;
45
- var RPC_CONTRACT_MAX_BYTES = 256 * 1024;
46
- var RPC_CALL_PURPOSES = new Set(["bootstrap", "background", "management", "request", "attempt"]);
47
- var RPC_COMMAND_DEDUPLICATION = new Set(["local-transaction", "external-contract", "none"]);
48
- var RPC_COMMAND_POLICY_KEYS = new Set(["deduplication", "resultRetentionMs", "quotaBytes", "maxResultBytes"]);
49
-
50
- // packages/core/src/plugin-services/rpc-runtime.ts
51
- var PROCESSES = new Set(["control", "worker", "ingress"]);
52
- var SCOPES = new Set(["global", "binding"]);
53
- var PURPOSES = new Set(["bootstrap", "background", "management", "request", "attempt"]);
54
- var ENDPOINTS = new WeakMap;
55
- var REVOKED = new WeakMap;
56
-
57
- // packages/core/src/plugin-services/host-rpc.ts
58
- var SERVICE_ERROR_MESSAGES = {
59
- unauthorized: "RPC call is not authorized",
60
- not_ready: "RPC endpoint is not ready",
61
- retired: "RPC endpoint is retiring",
62
- revoked: "RPC endpoint generation was revoked",
63
- unsupported_method: "RPC method is not published",
64
- wrong_purpose: "RPC method is not published for this purpose",
65
- invalid_input: "RPC input is not valid",
66
- invalid_output: "RPC output is not valid",
67
- invalid_operation_id: "RPC command operation id is invalid",
68
- overloaded: "RPC runtime is at capacity",
69
- cancelled: "RPC call was cancelled",
70
- timeout: "RPC call deadline exceeded",
71
- unknown: "RPC command outcome is unknown",
72
- capability_unavailable: "RPC command execution is unavailable",
73
- closed: "RPC runtime is closed",
74
- failed: "RPC call failed",
75
- conflict: "RPC command identity conflicts with an existing record",
76
- expired: "RPC command result has expired",
77
- pending: "RPC command is still pending",
78
- rejected: "RPC command was rejected",
79
- missing: "RPC command has no stored result",
80
- storage_failure: "RPC command storage failed",
81
- deadlock: "RPC call chain would deadlock",
82
- call_depth_exceeded: "RPC call depth exceeded",
83
- undeclared: "RPC service is not declared by the manifest",
84
- unavailable: "RPC service is unavailable",
85
- ambiguous_placement: "RPC service placement is ambiguous",
86
- invalid_registration: "RPC registration is invalid"
87
- };
88
- var SERVICE_ERROR_CODES = new Set(Object.keys(SERVICE_ERROR_MESSAGES));
89
- var EMPTY_OPTIONS = Object.freeze({});
90
- var FRAME_STORE = new AsyncLocalStorage;
91
- var CALLEE_INFO = new WeakMap;
92
- // packages/core/src/plugin-services/peer-channel-protocol.ts
93
- var CHANNEL_REQUEST_MAX_BYTES = 4 * 1024;
94
- var CHANNEL_RESULT_MAX_BYTES = 60 * 1024;
95
- var CHANNEL_MAX_CHUNK_BYTES = 60 * 1024;
96
- var PLUGIN_CHANNEL_LANES = Object.freeze(["event", "snapshot", "stream"]);
97
- var ERROR_CODES = new Set([
98
- "unavailable",
99
- "ambiguous",
100
- "unauthorized",
101
- "invalid",
102
- "overloaded",
103
- "expired",
104
- "conflict",
105
- "gap",
106
- "truncated",
107
- "rejected",
108
- "cancelled",
109
- "closed",
110
- "unknown",
111
- "failed"
112
- ]);
113
- var RPC_CALL_PURPOSES2 = new Set(["bootstrap", "background", "management", "request", "attempt"]);
114
-
115
- // packages/core/src/plugin-services/peer-channel-hub.ts
116
- var CHANNEL_MAX_TRANSFER_BUFFER_BYTES = 4 * CHANNEL_MAX_CHUNK_BYTES;
117
- var CHANNEL_MAX_TOTAL_BUFFER_BYTES = 16 * CHANNEL_MAX_CHUNK_BYTES;
118
- var CHANNEL_STREAM_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
119
- var CHANNEL_STREAM_MAX_CREDIT_BYTES = 8 * CHANNEL_MAX_CHUNK_BYTES;
120
- var CHANNEL_STREAM_DEFAULT_CREDIT_BYTES = 4 * CHANNEL_MAX_CHUNK_BYTES;
121
- var CHANNEL_SNAPSHOT_MAX_BYTES = 64 * 1024 * 1024;
122
- var CHANNEL_EVENT_MAX_PAYLOAD_BYTES = 60 * 1024;
123
- var EMPTY = new Uint8Array(0);
124
- var HANDLE_REGISTRATIONS = new WeakMap;
125
-
126
- // packages/core/src/plugin-services/snapshot-store.ts
127
- var DEFAULT_CHUNK_BYTES = 60 * 1024;
128
- var SNAPSHOT_FAMILY_REFS = new Map;
129
-
130
- // packages/core/src/plugin-services.ts
131
- var CHANNEL_KINDS = new Set(["events", "snapshot", "stream"]);
132
- // packages/core/src/gateway/retry-action.ts
133
- var retryActionBrand = Symbol.for("@jeffusion/bungee/request-retry-action");
134
- // packages/core/src/token-stats-window.ts
135
- var HOUR = 3600000;
136
- var TOKEN_STATS_RETENTION_MS = 31 * 24 * HOUR;
137
- // plugins/local-accounts/server/control.ts
138
- var PROVIDER = "local-accounts";
139
- var COOKIE = "bungee_local_session";
140
- var DEFAULT_SESSION_POLICY = Object.freeze({ idleTimeoutMinutes: 30, absoluteTimeoutMinutes: 480 });
141
- var COOKIE_LEASE_SECONDS = 400 * 24 * 60 * 60;
142
- var MAX_TIMEOUT_MINUTES = Math.floor(Number.MAX_SAFE_INTEGER / 60000);
143
- 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"];
144
-
145
- class AccountError extends Error {
146
- code;
147
- status;
148
- constructor(code, status = 400) {
149
- super(code);
150
- this.code = code;
151
- this.status = status;
152
- }
153
- }
154
- function fail(code, status = 400) {
155
- throw new AccountError(code, status);
156
- }
157
- function object(v) {
158
- if (!v || typeof v !== "object" || Array.isArray(v))
159
- fail("invalid_input");
160
- return v;
161
- }
162
- function exact(v, fields) {
163
- if (Object.keys(v).some((k) => !fields.includes(k)))
164
- fail("invalid_input");
165
- }
166
- function username(v) {
167
- if (typeof v !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.@-]{0,63}$/.test(v))
168
- fail("invalid_input");
169
- return v.toLowerCase();
170
- }
171
- function password(v) {
172
- if (typeof v !== "string" || [...v].length < 6 || [...v].length > 64)
173
- fail("invalid_password");
174
- return v;
175
- }
176
- function digest(v) {
177
- return createHash("sha256").update(v).digest("hex");
178
- }
179
- function csrf(token) {
180
- return createHmac("sha256", token).update("local-accounts/csrf/v1").digest("base64url");
181
- }
182
- function constant(a, b) {
183
- const aa = Buffer.from(a), bb = Buffer.from(b);
184
- return aa.length === bb.length && timingSafeEqual(aa, bb);
185
- }
186
- function dto(m) {
187
- return { id: m.id, username: m.username, requiresPasswordChange: m.temporary };
188
- }
189
- function json(v, status = 200, headers = {}) {
190
- return Response.json(v, { status, headers: { "cache-control": "no-store", ...headers } });
191
- }
192
- function safe(error) {
193
- return error instanceof AccountError ? json({ error: error.code }, error.status) : json({ error: "provider_unavailable" }, 503);
194
- }
195
- async function body(request) {
196
- const reader = request.body?.getReader();
197
- if (!reader)
198
- fail("invalid_input");
199
- let size = 0;
200
- const chunks = [];
201
- try {
202
- for (;; ) {
203
- const r = await reader.read();
204
- if (r.done)
205
- break;
206
- size += r.value.length;
207
- if (size > 8192) {
208
- await reader.cancel();
209
- fail("body_limit", 413);
210
- }
211
- chunks.push(r.value);
212
- }
213
- } finally {
214
- reader.releaseLock();
215
- }
216
- try {
217
- return object(JSON.parse(Buffer.concat(chunks).toString("utf8")));
218
- } catch (e) {
219
- if (e instanceof AccountError)
220
- throw e;
221
- return fail("invalid_input");
222
- }
223
- }
224
- function credentials(request) {
225
- const auth = request.headers.get("authorization");
226
- if (auth !== null) {
227
- const m = /^Bearer ([A-Za-z0-9_-]{43})$/.exec(auth);
228
- return m ? { token: m[1], transport: "bearer" } : null;
229
- }
230
- const tokens = (request.headers.get("cookie") ?? "").split(";").map((x) => x.trim()).filter((x) => x.startsWith(COOKIE + "="));
231
- if (tokens.length !== 1)
232
- return null;
233
- const token = tokens[0].slice(COOKIE.length + 1);
234
- return /^[A-Za-z0-9_-]{43}$/.test(token) ? { token, transport: "cookie" } : null;
235
- }
236
- function origin(request, expected) {
237
- if (request.headers.get("origin") !== (expected ?? new URL(request.url).origin))
238
- fail("invalid_origin", 403);
239
- }
240
- function cookie(request, token, maxAge, managementOrigin) {
241
- return `${COOKIE}=${token}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${maxAge}${new URL(managementOrigin ?? request.url).protocol === "https:" ? "; Secure" : ""}`;
242
- }
243
- function validateSessionPolicy(value, code = "invalid_session_policy", status = 400) {
244
- if (!value || typeof value !== "object" || Array.isArray(value))
245
- fail(code, status);
246
- const p = value;
247
- 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))
248
- fail(code, status);
249
- return { idleTimeoutMinutes: p.idleTimeoutMinutes, absoluteTimeoutMinutes: p.absoluteTimeoutMinutes };
250
- }
251
- var ACCOUNT_RECORD_SCHEMA = 3;
252
- var MAX_SESSIONS = 512;
253
- var MAX_FAILURES = 1024;
254
- var asJson = (value) => value;
255
- function validateAdministrator(m) {
256
- const a = m;
257
- if (!a || typeof a.id !== "string" || !a.id || typeof a.username !== "string" || !/^[a-z0-9][a-z0-9_.@-]{0,63}$/.test(a.username) || typeof a.disabled !== "boolean" || typeof a.temporary !== "boolean" || typeof a.passwordHash !== "string" || !a.passwordHash.startsWith("$argon2id$") || !Number.isSafeInteger(a.generation) || a.generation < 1 || !Number.isSafeInteger(a.failureEpoch) || a.failureEpoch < 1)
258
- fail("corrupt_state", 503);
259
- }
260
- function envelope(value, field) {
261
- if (!value || typeof value !== "object" || Array.isArray(value))
262
- fail("corrupt_state", 503);
263
- const v = value;
264
- if (v.schema !== ACCOUNT_RECORD_SCHEMA || !Object.hasOwn(v, field) || Object.keys(v).some((k) => k !== "schema" && k !== field))
265
- fail("corrupt_state", 503);
266
- return v[field];
267
- }
268
- function administratorValue(record) {
269
- if (!record)
270
- return null;
271
- const a = envelope(record.value, "administrator");
272
- if (a !== null)
273
- validateAdministrator(a);
274
- return a;
275
- }
276
- function validateSession(value) {
277
- if (value === null)
278
- return null;
279
- const s = value;
280
- if (!s || !/^[a-f0-9]{64}$/.test(s.digest) || typeof s.administratorId !== "string" || !s.administratorId || !Number.isSafeInteger(s.generation) || s.generation < 1 || !Number.isFinite(s.created) || !Number.isFinite(s.touched) || s.touched < s.created || !["cookie", "bearer"].includes(s.transport))
281
- fail("corrupt_state", 503);
282
- if (s.policy !== undefined)
283
- validateSessionPolicy(s.policy, "corrupt_state", 503);
284
- return s;
285
- }
286
- function validateFailure(value) {
287
- if (value === null)
288
- return null;
289
- const f = value;
290
- if (!f || typeof f.key !== "string" || !/^(account|source):[a-f0-9]{64}$/.test(f.key) || !Number.isSafeInteger(f.count) || f.count < 0 || !Number.isFinite(f.until) || !Number.isSafeInteger(f.epoch) || f.epoch < 1)
291
- fail("corrupt_state", 503);
292
- return f;
293
- }
294
- function slots(records, kind, limit, validate) {
295
- const result = Array.from({ length: limit }, (_, i) => ({ key: `${kind}:${i}`, version: 0, value: null }));
296
- const seen = new Set;
297
- for (const r of records.filter((r) => r.key.startsWith(kind + ":"))) {
298
- const index = Number(r.key.slice(kind.length + 1));
299
- if (!Number.isInteger(index) || index < 0 || index >= limit || r.key !== `${kind}:${index}`)
300
- fail("corrupt_state", 503);
301
- const value = validate(envelope(r.value, kind));
302
- const identity = value && (kind === "session" ? value.digest : value.key);
303
- if (identity && seen.has(identity))
304
- fail("corrupt_state", 503);
305
- if (identity)
306
- seen.add(identity);
307
- result[index] = { key: r.key, version: r.version, value };
308
- }
309
- return result;
310
- }
311
- var mutation = (slot, field, value) => ({ key: slot.key, expectedVersion: slot.version, value: asJson({ schema: ACCOUNT_RECORD_SCHEMA, [field]: value }) });
312
- function next(value) {
313
- if (!Number.isSafeInteger(value + 1))
314
- fail("generation_overflow");
315
- return value + 1;
316
- }
317
-
318
- class LocalAccountsControl {
319
- host;
320
- dependencies;
321
- management = this;
322
- rpc = [];
323
- api;
324
- state;
325
- now;
326
- subjects = new WeakMap;
327
- sessionSlots = new Map;
328
- dummy = "";
329
- disposed = false;
330
- constructor(host, dependencies = {}) {
331
- this.host = host;
332
- this.dependencies = dependencies;
333
- if (!host.durableState)
334
- fail("durable_state_required", 503);
335
- this.state = host.durableState;
336
- this.now = dependencies.now ?? Date.now;
337
- const invoke = (fn) => async (c) => {
338
- try {
339
- this.alive();
340
- if (c.requestSignal.aborted)
341
- fail("cancelled");
342
- return await fn(c);
343
- } catch (e) {
344
- return safe(e);
345
- }
346
- };
347
- this.api = [
348
- { path: "/password", methods: ["POST"], handler: "changePassword", invoke: invoke(async (c) => {
349
- await this.requireCapability(c, "self.password", true);
350
- const b = await body(c.request);
351
- exact(b, ["currentPassword", "password", "passwordConfirmation"]);
352
- await this.changePassword(c, b);
353
- return json({ ok: true }, 200, { "set-cookie": cookie(c.request, "", 0, this.host.managementOrigin) });
354
- }) },
355
- { path: "/logout", methods: ["POST"], handler: "logout", invoke: invoke(async (c) => {
356
- await this.requireCapability(c, "self.logout", true);
357
- return this.logout(c.request);
358
- }) },
359
- { path: "/self", methods: ["GET"], handler: "self", invoke: invoke(async (c) => {
360
- await this.requireCapability(c, "self.password");
361
- const m = (await this.readAdministrator()).value;
362
- const creds = credentials(c.request);
363
- return json({ administrator: dto(m), ...creds?.transport === "cookie" ? { csrfToken: csrf(creds.token) } : {} });
364
- }) },
365
- { path: "/session-policy", methods: ["GET", "PUT"], handler: "sessionPolicy", invoke: invoke(async (c) => {
366
- await this.requireCapability(c, "config.write", c.request.method === "PUT");
367
- if (c.request.method === "GET")
368
- return json(await this.readSessionPolicy());
369
- const b = await body(c.request);
370
- exact(b, ["version", "policy"]);
371
- if (!Number.isSafeInteger(b.version) || b.version < 0)
372
- fail("invalid_session_policy");
373
- const policy = validateSessionPolicy(b.policy);
374
- await this.requireCapability(c, "config.write", true);
375
- try {
376
- await this.state.transact([{ key: "session-policy", expectedVersion: b.version, value: asJson(policy) }]);
377
- } catch (e) {
378
- if (isDurableStateConflictError(e))
379
- fail("version_conflict", 409);
380
- throw e;
381
- }
382
- return json(await this.readSessionPolicy());
383
- }) }
384
- ];
385
- }
386
- alive() {
387
- if (this.disposed || this.host.signal.aborted)
388
- fail("provider_unavailable", 503);
389
- }
390
- async readAdministrator() {
391
- this.alive();
392
- const record = await this.state.get("administrator");
393
- return { key: "administrator", version: record?.version ?? 0, value: administratorValue(record) };
394
- }
395
- async records() {
396
- this.alive();
397
- const records = await this.state.list();
398
- for (const r of records)
399
- if (!["administrator", "session-policy", "recovery-summary"].includes(r.key) && !r.key.startsWith("session:") && !r.key.startsWith("failure:"))
400
- fail("account_migration_required", 503);
401
- return records;
402
- }
403
- async retry(plan) {
404
- for (let i = 0;i < 64; i++) {
405
- const { mutations, result } = await plan();
406
- if (!mutations.length)
407
- return result;
408
- this.alive();
409
- try {
410
- await this.state.transact(mutations);
411
- return result;
412
- } catch (e) {
413
- if (!isDurableStateConflictError(e))
414
- throw e;
415
- }
416
- }
417
- return fail("version_conflict", 409);
418
- }
419
- async readSessionPolicy() {
420
- this.alive();
421
- const record = await this.state.get("session-policy");
422
- return { version: record?.version ?? 0, policy: record ? validateSessionPolicy(record.value, "corrupt_state", 503) : { ...DEFAULT_SESSION_POLICY } };
423
- }
424
- async start() {
425
- await this.readAdministrator();
426
- await this.readSessionPolicy();
427
- const records = await this.records();
428
- slots(records, "session", MAX_SESSIONS, validateSession);
429
- slots(records, "failure", MAX_FAILURES, validateFailure);
430
- this.dummy = await this.hash(randomBytes(32).toString("base64url"));
431
- }
432
- dispose() {
433
- this.disposed = true;
434
- }
435
- hash(p) {
436
- return Bun.password.hash(p, { algorithm: "argon2id", memoryCost: 65536, timeCost: 3 });
437
- }
438
- async hasIdentity() {
439
- const a = (await this.readAdministrator()).value;
440
- return !!a && !a.disabled;
441
- }
442
- async revokeSessions() {
443
- await this.retry(async () => {
444
- const a = await this.readAdministrator();
445
- return { mutations: a.value ? [mutation(a, "administrator", { ...a.value, generation: next(a.value.generation) })] : [], result: undefined };
446
- });
447
- }
448
- async bootstrap(input) {
449
- const b = object(input);
450
- exact(b, ["username", "password", "passwordConfirmation"]);
451
- const name = username(b.username), p = password(b.password);
452
- if (b.passwordConfirmation !== p)
453
- fail("password_confirmation");
454
- await this.records();
455
- const prior = (await this.readAdministrator()).value;
456
- if (prior) {
457
- if (prior.username !== name || prior.disabled || !await Bun.password.verify(p, prior.passwordHash))
458
- fail("invalid_credentials", 401);
459
- const latest = (await this.readAdministrator()).value;
460
- if (!latest || latest.generation !== prior.generation || latest.disabled)
461
- fail("invalid_credentials", 401);
462
- return;
463
- }
464
- const hashed = await this.hash(p);
465
- await this.retry(async () => {
466
- const a = await this.readAdministrator();
467
- if (a.value)
468
- fail("bootstrap_conflict", 409);
469
- return { mutations: [mutation(a, "administrator", { id: randomUUID(), username: name, disabled: false, temporary: false, passwordHash: hashed, generation: 1, failureEpoch: 1 })], result: undefined };
470
- });
471
- }
472
- live(m, session) {
473
- const now = this.now(), policy = session.policy ?? DEFAULT_SESSION_POLICY;
474
- if (now < session.touched || now < session.created || policy.idleTimeoutMinutes > 0 && now - session.touched >= policy.idleTimeoutMinutes * 60000 || policy.absoluteTimeoutMinutes > 0 && now - session.created >= policy.absoluteTimeoutMinutes * 60000)
475
- return;
476
- return m && m.id === session.administratorId && !m.disabled && m.generation === session.generation ? m : undefined;
477
- }
478
- async findSession(d) {
479
- this.alive();
480
- const key = this.sessionSlots.get(d);
481
- if (key) {
482
- const record = await this.state.get(key), value = record && validateSession(envelope(record.value, "session"));
483
- if (record && value?.digest === d)
484
- return { key, version: record.version, value };
485
- this.sessionSlots.delete(d);
486
- }
487
- const found = slots(await this.records(), "session", MAX_SESSIONS, validateSession).find((x) => x.value?.digest === d);
488
- if (found) {
489
- if (this.sessionSlots.size >= MAX_SESSIONS)
490
- this.sessionSlots.clear();
491
- this.sessionSlots.set(d, found.key);
492
- }
493
- return found;
494
- }
495
- cookieAge(policy, elapsedMs = 0) {
496
- return policy.absoluteTimeoutMinutes === 0 ? COOKIE_LEASE_SECONDS : Math.max(0, Math.min(COOKIE_LEASE_SECONDS, Math.floor((policy.absoluteTimeoutMinutes * 60000 - elapsedMs) / 1000)));
497
- }
498
- async sessionCookie(request) {
499
- this.alive();
500
- const creds = credentials(request);
501
- if (creds?.transport !== "cookie")
502
- return;
503
- const slot = await this.findSession(digest(creds.token)), m = (await this.readAdministrator()).value, session = slot?.value;
504
- if (!session || session.transport !== "cookie" || !this.live(m, session))
505
- return;
506
- return cookie(request, creds.token, this.cookieAge(session.policy ?? DEFAULT_SESSION_POLICY, this.now() - session.created), this.host.managementOrigin);
507
- }
508
- async authenticate(request) {
509
- this.alive();
510
- const c = credentials(request);
511
- if (!c)
512
- return null;
513
- const authenticated = await this.retry(async () => {
514
- const slot = await this.findSession(digest(c.token)), m = (await this.readAdministrator()).value, session = slot?.value;
515
- const administrator = session && session.transport === c.transport && this.live(m, session);
516
- if (!slot || !session || !administrator)
517
- return { mutations: [], result: null };
518
- return { mutations: [mutation(slot, "session", { ...session, touched: this.now() })], result: { administrator, digest: session.digest } };
519
- });
520
- if (!authenticated)
521
- return null;
522
- const m = (await this.readAdministrator()).value;
523
- if (!m || m.disabled || m.generation !== authenticated.administrator.generation || m.id !== authenticated.administrator.id)
524
- return null;
525
- const subject = Object.freeze({ id: m.id, provider: PROVIDER, capabilities: Object.freeze([...ALL]), requiresPasswordChange: m.temporary });
526
- this.subjects.set(subject, { generation: m.generation, digest: authenticated.digest });
527
- return subject;
528
- }
529
- async authorize(subject, _capability) {
530
- this.alive();
531
- const issued = this.subjects.get(subject);
532
- if (!issued || subject.provider !== PROVIDER)
533
- return false;
534
- const slot = await this.findSession(issued.digest), session = slot?.value, m = session && this.live((await this.readAdministrator()).value, session);
535
- return !!m && m.id === subject.id && m.generation === issued.generation;
536
- }
537
- async requireCapability(c, capability, write = false) {
538
- if (!c.subject || !await this.authorize(c.subject, capability))
539
- fail("forbidden", 403);
540
- if (write)
541
- this.checkWrite(c.request);
542
- }
543
- validateWrite(request) {
544
- this.checkWrite(request);
545
- }
546
- csrfToken(request) {
547
- const c = credentials(request);
548
- return c?.transport === "cookie" ? csrf(c.token) : undefined;
549
- }
550
- checkWrite(request) {
551
- const c = credentials(request);
552
- if (!c)
553
- fail("unauthorized", 401);
554
- if (c.transport === "cookie") {
555
- origin(request, this.host.managementOrigin);
556
- if (!constant(request.headers.get("x-csrf-token") ?? "", csrf(c.token)))
557
- fail("invalid_csrf", 403);
558
- }
559
- }
560
- async login(request) {
561
- try {
562
- this.alive();
563
- if (request.method !== "POST")
564
- fail("method_not_allowed", 405);
565
- const b = await body(request);
566
- exact(b, ["username", "password", "transport"]);
567
- const name = username(b.username), p = typeof b.password === "string" ? b.password : "";
568
- const transport = b.transport ?? "cookie";
569
- if (transport !== "cookie" && transport !== "bearer")
570
- fail("invalid_input");
571
- if (transport === "cookie" || request.headers.has("origin"))
572
- origin(request, this.host.managementOrigin);
573
- const source = (this.dependencies.trustedSource ?? this.host.trustedSource)?.(request) ?? "unknown";
574
- const keys = ["account:" + digest(name), "source:" + digest(source)];
575
- const reservation = await this.retry(async () => {
576
- const a = (await this.readAdministrator()).value, epoch = a?.failureEpoch ?? 1, now = this.now();
577
- const failures = slots(await this.records(), "failure", MAX_FAILURES, validateFailure), selected = [];
578
- for (const key of keys) {
579
- let slot = failures.find((x) => !selected.includes(x) && x.value?.key === key);
580
- const active = slot?.value && slot.value.epoch === epoch && slot.value.until > now;
581
- if (active && slot.value.count >= 10)
582
- fail("login_limited", 429);
583
- slot ??= failures.find((x) => !selected.includes(x) && (!x.value || x.value.epoch !== epoch || x.value.until <= now));
584
- if (!slot)
585
- fail("login_limited", 429);
586
- selected.push(slot);
587
- }
588
- return { mutations: selected.map((slot, i) => {
589
- const prior = slot.value;
590
- 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 });
591
- }), result: epoch };
592
- });
593
- const candidate = (await this.readAdministrator()).value, administrator = candidate?.username === name ? candidate : null;
594
- const valid = await Bun.password.verify(p, administrator?.passwordHash ?? (this.dummy || await this.hash("dummy-invalid-password")));
595
- if (!valid || !administrator || administrator.disabled)
596
- fail("invalid_credentials", 401);
597
- const token = randomBytes(32).toString("base64url"), policy = (await this.readSessionPolicy()).policy, created = this.now();
598
- await this.retry(async () => {
599
- const m = (await this.readAdministrator()).value;
600
- if (!m || m.id !== administrator.id || m.disabled || m.generation !== administrator.generation || m.failureEpoch !== reservation)
601
- fail("invalid_credentials", 401);
602
- const records = await this.records(), sessions = slots(records, "session", MAX_SESSIONS, validateSession), slot = sessions.find((x) => !x.value || !this.live(m, x.value));
603
- if (!slot)
604
- fail("session_limit", 429);
605
- const changes = [mutation(slot, "session", { digest: digest(token), administratorId: m.id, generation: m.generation, created, touched: created, transport, policy })];
606
- const failures = slots(records, "failure", MAX_FAILURES, validateFailure);
607
- const account = failures.find((x) => x.value?.key === keys[0] && x.value.epoch === reservation);
608
- if (account)
609
- changes.push(mutation(account, "failure", null));
610
- const source = failures.find((x) => x.value?.key === keys[1] && x.value.epoch === reservation);
611
- if (source)
612
- changes.push(mutation(source, "failure", { ...source.value, count: Math.max(0, source.value.count - 1) }));
613
- return { mutations: changes, result: undefined };
614
- });
615
- const latest = (await this.readAdministrator()).value;
616
- if (!latest || latest.disabled || latest.generation !== administrator.generation || latest.id !== administrator.id)
617
- fail("invalid_credentials", 401);
618
- return transport === "cookie" ? json({ administrator: dto(administrator), csrfToken: csrf(token) }, 200, { "set-cookie": cookie(request, token, this.cookieAge(policy, this.now() - created), this.host.managementOrigin) }) : json({ administrator: dto(administrator), token, expiresIn: policy.absoluteTimeoutMinutes === 0 ? null : policy.absoluteTimeoutMinutes * 60 });
619
- } catch (e) {
620
- return safe(e);
621
- }
622
- }
623
- async logout(request) {
624
- try {
625
- this.alive();
626
- if (request.method !== "POST")
627
- fail("method_not_allowed", 405);
628
- this.checkWrite(request);
629
- const c = credentials(request);
630
- await this.retry(async () => {
631
- const slot = await this.findSession(digest(c.token));
632
- return { mutations: slot?.value ? [mutation(slot, "session", null)] : [], result: undefined };
633
- });
634
- return json({ ok: true }, 200, { "set-cookie": cookie(request, "", 0, this.host.managementOrigin) });
635
- } catch (e) {
636
- return safe(e);
637
- }
638
- }
639
- async changePassword(c, b) {
640
- const p = password(b.password);
641
- if (b.passwordConfirmation !== p)
642
- fail("password_confirmation");
643
- const m = (await this.readAdministrator()).value, id = c.subject.id;
644
- if (!m || m.id !== id || typeof b.currentPassword !== "string" || !await Bun.password.verify(b.currentPassword, m.passwordHash))
645
- fail("invalid_credentials", 401);
646
- if (constant(p, b.currentPassword))
647
- fail("password_unchanged");
648
- const hashed = await this.hash(p);
649
- await this.retry(async () => {
650
- await this.requireCapability(c, "self.password", true);
651
- const a = await this.readAdministrator(), current = a.value;
652
- if (!current || current.id !== id || current.disabled || current.generation !== m.generation)
653
- fail("version_conflict", 409);
654
- const issued = this.subjects.get(c.subject), slot = await this.findSession(issued.digest);
655
- if (!slot?.value || !this.live(current, slot.value))
656
- fail("forbidden", 403);
657
- return { mutations: [mutation(a, "administrator", { ...current, passwordHash: hashed, temporary: false, generation: next(current.generation) }), mutation(slot, "session", null)], result: undefined };
658
- });
659
- }
660
- }
661
- function createControl(host, dependencies = {}) {
662
- return new LocalAccountsControl(host, dependencies);
663
- }
664
- var api = [
665
- { path: "/password", methods: ["POST"], handler: "changePassword" },
666
- { path: "/logout", methods: ["POST"], handler: "logout" },
667
- { path: "/self", methods: ["GET"], handler: "self" },
668
- { path: "/session-policy", methods: ["GET", "PUT"], handler: "sessionPolicy" }
669
- ];
670
- var rpc = [];
671
- var controlApi = api;
672
- var controlRpc = rpc;
673
- async function recoverIdentity(input, context) {
674
- const b = object(input);
675
- exact(b, ["username", "newUsername", "password", "reason"]);
676
- const name = b.username === undefined ? undefined : username(b.username), p = password(b.password), newName = b.newUsername === undefined ? undefined : username(b.newUsername);
677
- if (typeof b.reason !== "string" || !b.reason.trim() || b.reason.length > 512)
678
- fail("invalid_reason");
679
- const state = context.durableState;
680
- await readManagementSetup(state);
681
- const record = await state.get("administrator"), prior = administratorValue(record);
682
- if (prior && name !== undefined && prior.username !== name)
683
- fail("recovery_requires_existing_administrator");
684
- const passwordHash = await Bun.password.hash(p, { algorithm: "argon2id", memoryCost: 65536, timeCost: 3 });
685
- const administrator = prior ? { ...prior, username: newName ?? prior.username, passwordHash, disabled: false, temporary: false, generation: next(prior.generation), failureEpoch: next(prior.failureEpoch) } : { id: randomUUID(), username: newName ?? name ?? fail("invalid_input"), disabled: false, temporary: false, passwordHash, generation: 1, failureEpoch: 1 };
686
- const summary = await state.get("recovery-summary");
687
- await state.transact([
688
- { key: "administrator", expectedVersion: record?.version ?? 0, value: asJson({ schema: ACCOUNT_RECORD_SCHEMA, administrator }) },
689
- { key: "recovery-summary", expectedVersion: summary?.version ?? 0, value: { schema: ACCOUNT_RECORD_SCHEMA, administratorId: administrator.id, reason: b.reason, at: Date.now() } }
690
- ]);
691
- return dto(administrator);
692
- }
693
- async function readManagementSetup(state) {
694
- for (const r of await state.list())
695
- if (!["administrator", "session-policy", "recovery-summary"].includes(r.key) && !r.key.startsWith("session:") && !r.key.startsWith("failure:"))
696
- fail("account_migration_required", 503);
697
- return { initialized: !!administratorValue(await state.get("administrator")) };
698
- }
699
- var control_default = { createControl, readManagementSetup, offlineRecovery: { kind: "identity", recover: recoverIdentity } };
700
- export {
701
- ACCOUNT_RECORD_SCHEMA,
702
- LocalAccountsControl,
703
- MAX_FAILURES,
704
- MAX_SESSIONS,
705
- api,
706
- controlApi,
707
- controlRpc,
708
- createControl,
709
- control_default as default,
710
- readManagementSetup,
711
- recoverIdentity,
712
- rpc
713
- };
714
-
715
- //# debugId=C01750CC9993B9E264756E2164756E21