@data-club/ai-hub-server 0.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.
- package/dist/auth/argon2.d.ts +72 -0
- package/dist/auth/argon2.d.ts.map +1 -0
- package/dist/auth/argon2.js +145 -0
- package/dist/auth/argon2.js.map +1 -0
- package/dist/auth/encryption.d.ts +52 -0
- package/dist/auth/encryption.d.ts.map +1 -0
- package/dist/auth/encryption.js +98 -0
- package/dist/auth/encryption.js.map +1 -0
- package/dist/auth/jwt.d.ts +133 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/jwt.js +147 -0
- package/dist/auth/jwt.js.map +1 -0
- package/dist/auth/middleware.d.ts +101 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +161 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/passwordPolicy.d.ts +72 -0
- package/dist/auth/passwordPolicy.d.ts.map +1 -0
- package/dist/auth/passwordPolicy.js +88 -0
- package/dist/auth/passwordPolicy.js.map +1 -0
- package/dist/auth/roleRules.d.ts +200 -0
- package/dist/auth/roleRules.d.ts.map +1 -0
- package/dist/auth/roleRules.js +365 -0
- package/dist/auth/roleRules.js.map +1 -0
- package/dist/auth/userView.d.ts +55 -0
- package/dist/auth/userView.d.ts.map +1 -0
- package/dist/auth/userView.js +77 -0
- package/dist/auth/userView.js.map +1 -0
- package/dist/bootstrap.d.ts +104 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +160 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/db/connection.d.ts +39 -0
- package/dist/db/connection.d.ts.map +1 -0
- package/dist/db/connection.js +62 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/migrate.d.ts +31 -0
- package/dist/db/migrate.d.ts.map +1 -0
- package/dist/db/migrate.js +197 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/schema.d.ts +1902 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +662 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/seed.d.ts +92 -0
- package/dist/db/seed.d.ts.map +1 -0
- package/dist/db/seed.js +160 -0
- package/dist/db/seed.js.map +1 -0
- package/dist/handlers/fetch.d.ts +89 -0
- package/dist/handlers/fetch.d.ts.map +1 -0
- package/dist/handlers/fetch.js +173 -0
- package/dist/handlers/fetch.js.map +1 -0
- package/dist/handlers/nextjs.d.ts +40 -0
- package/dist/handlers/nextjs.d.ts.map +1 -0
- package/dist/handlers/nextjs.js +42 -0
- package/dist/handlers/nextjs.js.map +1 -0
- package/dist/handlers/responses.d.ts +46 -0
- package/dist/handlers/responses.d.ts.map +1 -0
- package/dist/handlers/responses.js +74 -0
- package/dist/handlers/responses.js.map +1 -0
- package/dist/handlers/routeTable.d.ts +21 -0
- package/dist/handlers/routeTable.d.ts.map +1 -0
- package/dist/handlers/routeTable.js +133 -0
- package/dist/handlers/routeTable.js.map +1 -0
- package/dist/handlers/router.d.ts +37 -0
- package/dist/handlers/router.d.ts.map +1 -0
- package/dist/handlers/router.js +82 -0
- package/dist/handlers/router.js.map +1 -0
- package/dist/handlers/sse.d.ts +38 -0
- package/dist/handlers/sse.d.ts.map +1 -0
- package/dist/handlers/sse.js +52 -0
- package/dist/handlers/sse.js.map +1 -0
- package/dist/handlers/types.d.ts +96 -0
- package/dist/handlers/types.d.ts.map +1 -0
- package/dist/handlers/types.js +14 -0
- package/dist/handlers/types.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/letta/client.d.ts +355 -0
- package/dist/letta/client.d.ts.map +1 -0
- package/dist/letta/client.js +529 -0
- package/dist/letta/client.js.map +1 -0
- package/dist/letta/eventTranslator.d.ts +166 -0
- package/dist/letta/eventTranslator.d.ts.map +1 -0
- package/dist/letta/eventTranslator.js +532 -0
- package/dist/letta/eventTranslator.js.map +1 -0
- package/dist/letta/index.d.ts +13 -0
- package/dist/letta/index.d.ts.map +1 -0
- package/dist/letta/index.js +12 -0
- package/dist/letta/index.js.map +1 -0
- package/dist/letta/modelSettings.d.ts +176 -0
- package/dist/letta/modelSettings.d.ts.map +1 -0
- package/dist/letta/modelSettings.js +314 -0
- package/dist/letta/modelSettings.js.map +1 -0
- package/dist/letta/promptWrapping.d.ts +141 -0
- package/dist/letta/promptWrapping.d.ts.map +1 -0
- package/dist/letta/promptWrapping.js +213 -0
- package/dist/letta/promptWrapping.js.map +1 -0
- package/dist/letta/sandbox.d.ts +100 -0
- package/dist/letta/sandbox.d.ts.map +1 -0
- package/dist/letta/sandbox.js +151 -0
- package/dist/letta/sandbox.js.map +1 -0
- package/dist/letta/settingsFileSeed.d.ts +146 -0
- package/dist/letta/settingsFileSeed.d.ts.map +1 -0
- package/dist/letta/settingsFileSeed.js +177 -0
- package/dist/letta/settingsFileSeed.js.map +1 -0
- package/dist/letta/subprocessThrottle.d.ts +151 -0
- package/dist/letta/subprocessThrottle.d.ts.map +1 -0
- package/dist/letta/subprocessThrottle.js +195 -0
- package/dist/letta/subprocessThrottle.js.map +1 -0
- package/dist/letta/tpmRecovery.d.ts +202 -0
- package/dist/letta/tpmRecovery.d.ts.map +1 -0
- package/dist/letta/tpmRecovery.js +314 -0
- package/dist/letta/tpmRecovery.js.map +1 -0
- package/dist/letta/types.d.ts +54 -0
- package/dist/letta/types.d.ts.map +1 -0
- package/dist/letta/types.js +16 -0
- package/dist/letta/types.js.map +1 -0
- package/dist/observability/logger.d.ts +48 -0
- package/dist/observability/logger.d.ts.map +1 -0
- package/dist/observability/logger.js +81 -0
- package/dist/observability/logger.js.map +1 -0
- package/dist/observability/metrics.d.ts +53 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/metrics.js +92 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/routes/admin/agents.d.ts +63 -0
- package/dist/routes/admin/agents.d.ts.map +1 -0
- package/dist/routes/admin/agents.js +461 -0
- package/dist/routes/admin/agents.js.map +1 -0
- package/dist/routes/admin/rotatingMessages.d.ts +51 -0
- package/dist/routes/admin/rotatingMessages.d.ts.map +1 -0
- package/dist/routes/admin/rotatingMessages.js +292 -0
- package/dist/routes/admin/rotatingMessages.js.map +1 -0
- package/dist/routes/admin/sessions.d.ts +61 -0
- package/dist/routes/admin/sessions.d.ts.map +1 -0
- package/dist/routes/admin/sessions.js +232 -0
- package/dist/routes/admin/sessions.js.map +1 -0
- package/dist/routes/admin/settings.d.ts +110 -0
- package/dist/routes/admin/settings.d.ts.map +1 -0
- package/dist/routes/admin/settings.js +401 -0
- package/dist/routes/admin/settings.js.map +1 -0
- package/dist/routes/admin/users.d.ts +115 -0
- package/dist/routes/admin/users.d.ts.map +1 -0
- package/dist/routes/admin/users.js +374 -0
- package/dist/routes/admin/users.js.map +1 -0
- package/dist/routes/agents.d.ts +30 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +55 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/auth.d.ts +121 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +422 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/chat.d.ts +97 -0
- package/dist/routes/chat.d.ts.map +1 -0
- package/dist/routes/chat.js +484 -0
- package/dist/routes/chat.js.map +1 -0
- package/dist/routes/featureToggles.d.ts +34 -0
- package/dist/routes/featureToggles.d.ts.map +1 -0
- package/dist/routes/featureToggles.js +47 -0
- package/dist/routes/featureToggles.js.map +1 -0
- package/dist/routes/health.d.ts +24 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +37 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/metrics.d.ts +35 -0
- package/dist/routes/metrics.d.ts.map +1 -0
- package/dist/routes/metrics.js +106 -0
- package/dist/routes/metrics.js.map +1 -0
- package/dist/routes/sessions.d.ts +161 -0
- package/dist/routes/sessions.d.ts.map +1 -0
- package/dist/routes/sessions.js +1501 -0
- package/dist/routes/sessions.js.map +1 -0
- package/dist/routes/stubs.d.ts +25 -0
- package/dist/routes/stubs.d.ts.map +1 -0
- package/dist/routes/stubs.js +25 -0
- package/dist/routes/stubs.js.map +1 -0
- package/dist/state/cron.d.ts +58 -0
- package/dist/state/cron.d.ts.map +1 -0
- package/dist/state/cron.js +160 -0
- package/dist/state/cron.js.map +1 -0
- package/dist/state/rotationRunner.d.ts +96 -0
- package/dist/state/rotationRunner.d.ts.map +1 -0
- package/dist/state/rotationRunner.js +246 -0
- package/dist/state/rotationRunner.js.map +1 -0
- package/dist/state/rotationScheduler.d.ts +76 -0
- package/dist/state/rotationScheduler.d.ts.map +1 -0
- package/dist/state/rotationScheduler.js +124 -0
- package/dist/state/rotationScheduler.js.map +1 -0
- package/dist/state/turnRegistry.d.ts +95 -0
- package/dist/state/turnRegistry.d.ts.map +1 -0
- package/dist/state/turnRegistry.js +149 -0
- package/dist/state/turnRegistry.js.map +1 -0
- package/dist/state/turnRunner.d.ts +134 -0
- package/dist/state/turnRunner.d.ts.map +1 -0
- package/dist/state/turnRunner.js +476 -0
- package/dist/state/turnRunner.js.map +1 -0
- package/package.json +47 -0
- package/src/auth/argon2.ts +152 -0
- package/src/auth/encryption.ts +110 -0
- package/src/auth/jwt.ts +217 -0
- package/src/auth/middleware.ts +250 -0
- package/src/auth/passwordPolicy.ts +99 -0
- package/src/auth/roleRules.ts +497 -0
- package/src/auth/userView.ts +88 -0
- package/src/bootstrap.ts +254 -0
- package/src/db/connection.ts +75 -0
- package/src/db/migrate.ts +212 -0
- package/src/db/schema.ts +750 -0
- package/src/db/seed.ts +236 -0
- package/src/handlers/fetch.ts +251 -0
- package/src/handlers/nextjs.ts +49 -0
- package/src/handlers/responses.ts +95 -0
- package/src/handlers/routeTable.ts +188 -0
- package/src/handlers/router.ts +96 -0
- package/src/handlers/sse.ts +55 -0
- package/src/handlers/types.ts +104 -0
- package/src/index.ts +60 -0
- package/src/letta/client.ts +860 -0
- package/src/letta/eventTranslator.ts +667 -0
- package/src/letta/index.ts +46 -0
- package/src/letta/lettaCliShim.mjs +40 -0
- package/src/letta/sandbox.ts +167 -0
- package/src/letta/settingsFileSeed.ts +254 -0
- package/src/letta/subprocessThrottle.ts +251 -0
- package/src/letta/types.ts +65 -0
- package/src/observability/logger.ts +103 -0
- package/src/observability/metrics.ts +114 -0
- package/src/routes/admin/agents.ts +574 -0
- package/src/routes/admin/rotatingMessages.ts +351 -0
- package/src/routes/admin/sessions.ts +283 -0
- package/src/routes/admin/settings.ts +463 -0
- package/src/routes/admin/users.ts +455 -0
- package/src/routes/agents.ts +66 -0
- package/src/routes/auth.ts +513 -0
- package/src/routes/chat.ts +534 -0
- package/src/routes/featureToggles.ts +52 -0
- package/src/routes/health.ts +44 -0
- package/src/routes/metrics.ts +111 -0
- package/src/routes/sessions.ts +1878 -0
- package/src/state/cron.ts +179 -0
- package/src/state/rotationRunner.ts +326 -0
- package/src/state/rotationScheduler.ts +164 -0
- package/src/state/turnRegistry.ts +210 -0
- package/src/state/turnRunner.ts +588 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/admin/settings/*` — super_admin-only deployment-wide configuration.
|
|
3
|
+
*
|
|
4
|
+
* Five endpoints:
|
|
5
|
+
*
|
|
6
|
+
* - `GET /admin/settings` — Letta connection (api key **masked**), theme,
|
|
7
|
+
* metrics + signing-key rotation timestamps. Encrypted Letta API key
|
|
8
|
+
* surfaces only as `letta_api_key_masked` (`••••••••<last4>`); plaintext
|
|
9
|
+
* never leaves the server outside the `Reveal once` flow (Step 12).
|
|
10
|
+
* - `PATCH /admin/settings` — body `{ letta_base_url?, letta_ade_url?, letta_api_key?, theme?: { default?, switcher_enabled? } }`.
|
|
11
|
+
* API key, when supplied, is encrypted under `AI_HUB_ENCRYPTION_KEY`. Theme
|
|
12
|
+
* fields live as **separate columns**, not in `feature_toggles`.
|
|
13
|
+
* - `GET /admin/settings/feature-toggles` — flat JSON blob from §8.4.
|
|
14
|
+
* - `PATCH /admin/settings/feature-toggles` — merge-patch the same blob.
|
|
15
|
+
* Unknown keys are accepted (forward-compat for future toggles).
|
|
16
|
+
* - `POST /admin/settings/rotate-signing-key` — replace
|
|
17
|
+
* `service_token_signing_key` with random 64 bytes; bump
|
|
18
|
+
* `signing_key_rotated_at`. Returns nothing — the key never leaves the
|
|
19
|
+
* server. After rotation every outstanding JWT fails signature
|
|
20
|
+
* verification (no grace period in v1; the calling super-admin is logged
|
|
21
|
+
* out alongside everyone else).
|
|
22
|
+
* - `POST /admin/settings/rotate-metrics-token` — generate a new 32-byte
|
|
23
|
+
* hex token, bump `metrics_scrape_token_rotated_at`, return
|
|
24
|
+
* `{ token: "<new>" }` exactly once in the response. Same one-time-reveal
|
|
25
|
+
* pattern as user password reset; subsequent `GET /admin/settings` calls
|
|
26
|
+
* do not echo it back.
|
|
27
|
+
*
|
|
28
|
+
* @see SPECIFICATIONS.md §6.3.4 — settings endpoints
|
|
29
|
+
* @see SPECIFICATIONS.md §6.7 — signing key rotation
|
|
30
|
+
* @see SPECIFICATIONS.md §6.10 — metrics token rotation
|
|
31
|
+
* @see ui-sketches/08-admin-settings.md
|
|
32
|
+
*/
|
|
33
|
+
import type { RouteHandler } from "../../handlers/types.js";
|
|
34
|
+
/**
|
|
35
|
+
* `GET /admin/settings` — super_admin-only deployment configuration.
|
|
36
|
+
*
|
|
37
|
+
* Encrypted Letta API key surfaces only as the masked placeholder
|
|
38
|
+
* (`letta_api_key_masked: "••••••••"`) + the boolean `letta_api_key_set`;
|
|
39
|
+
* plaintext never leaves the server through this endpoint. Step 12 wires the
|
|
40
|
+
* `Reveal once` flow as a separate UI affordance.
|
|
41
|
+
*
|
|
42
|
+
* @see SPECIFICATIONS.md §6.3.4
|
|
43
|
+
* @see ui-sketches/08-admin-settings.md
|
|
44
|
+
*/
|
|
45
|
+
export declare const handleGetSettings: RouteHandler;
|
|
46
|
+
/**
|
|
47
|
+
* `PATCH /admin/settings` — partial update of deployment configuration.
|
|
48
|
+
*
|
|
49
|
+
* The Letta API key, when supplied, is encrypted under
|
|
50
|
+
* `AI_HUB_ENCRYPTION_KEY` (env-only) before write. Theme fields live as
|
|
51
|
+
* separate columns (`theme_default`, `theme_switcher_enabled`) per §8.4.
|
|
52
|
+
*
|
|
53
|
+
* @see SPECIFICATIONS.md §6.3.4
|
|
54
|
+
* @see SPECIFICATIONS.md §8.4 — theme settings are explicit columns
|
|
55
|
+
*/
|
|
56
|
+
export declare const handlePatchSettings: RouteHandler;
|
|
57
|
+
/**
|
|
58
|
+
* `GET /admin/settings/feature-toggles` — the §8.4 flat JSON blob.
|
|
59
|
+
*
|
|
60
|
+
* Missing keys fall back to the React side's built-in defaults at render
|
|
61
|
+
* time; the server doesn't merge defaults in here (so the admin UI can show
|
|
62
|
+
* "default" vs. "explicitly set").
|
|
63
|
+
*
|
|
64
|
+
* @see SPECIFICATIONS.md §8.4
|
|
65
|
+
*/
|
|
66
|
+
export declare const handleGetFeatureToggles: RouteHandler;
|
|
67
|
+
/**
|
|
68
|
+
* `PATCH /admin/settings/feature-toggles` — merge-patch the §8.4 blob.
|
|
69
|
+
*
|
|
70
|
+
* Unknown keys → 400. Each successful PATCH replaces the entire stored blob
|
|
71
|
+
* with the merge of (old state) + (incoming patch). Setting a key to `false`
|
|
72
|
+
* makes it explicit-false; omitting a key leaves the current value.
|
|
73
|
+
*
|
|
74
|
+
* @see SPECIFICATIONS.md §8.4
|
|
75
|
+
*/
|
|
76
|
+
export declare const handlePatchFeatureToggles: RouteHandler;
|
|
77
|
+
/**
|
|
78
|
+
* `POST /admin/settings/rotate-signing-key` — replace the JWT signing key.
|
|
79
|
+
*
|
|
80
|
+
* Generates 64 new random bytes (matches seed bytes), writes to
|
|
81
|
+
* `admin_setting.service_token_signing_key`, bumps `signing_key_rotated_at`.
|
|
82
|
+
* Returns 204 No Content — the key never leaves the server.
|
|
83
|
+
*
|
|
84
|
+
* **Every outstanding JWT fails signature verification immediately after this
|
|
85
|
+
* call** (no grace period in v1). The calling super-admin's own token is
|
|
86
|
+
* invalidated alongside everyone else's; the React UI handles the 401 on the
|
|
87
|
+
* next request by routing back to login.
|
|
88
|
+
*
|
|
89
|
+
* @see SPECIFICATIONS.md §6.7 — signing key
|
|
90
|
+
* @see ui-sketches/08-admin-settings.md
|
|
91
|
+
*/
|
|
92
|
+
export declare const handleRotateSigningKey: RouteHandler;
|
|
93
|
+
/**
|
|
94
|
+
* `POST /admin/settings/rotate-metrics-token` — replace the `/metrics` bearer
|
|
95
|
+
* token.
|
|
96
|
+
*
|
|
97
|
+
* Generates a fresh 32-byte hex token, writes it, bumps
|
|
98
|
+
* `metrics_scrape_token_rotated_at`, and returns `{ token: "<new>" }` exactly
|
|
99
|
+
* once. The plaintext never appears in subsequent `GET /admin/settings`
|
|
100
|
+
* responses (same one-time-reveal pattern as user password resets).
|
|
101
|
+
*
|
|
102
|
+
* Rotating does **not** invalidate the calling super-admin's JWT — only the
|
|
103
|
+
* metrics-scraper bearer changes. Operators paste the new value into their
|
|
104
|
+
* Prometheus / Alloy config; old scrapers start failing immediately.
|
|
105
|
+
*
|
|
106
|
+
* @see SPECIFICATIONS.md §6.10
|
|
107
|
+
* @see ui-sketches/08-admin-settings.md
|
|
108
|
+
*/
|
|
109
|
+
export declare const handleRotateMetricsToken: RouteHandler;
|
|
110
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/routes/admin/settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAgBH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AA2H5D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,EAAE,YAO/B,CAAC;AA6BF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,EAAE,YAgDjC,CAAC;AAMF;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,YAOrC,CAAC;AA8BF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,EAAE,YAuCvC,CAAC;AAMF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,EAAE,YAsBpC,CAAC;AAMF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,wBAAwB,EAAE,YAsBtC,CAAC"}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/admin/settings/*` — super_admin-only deployment-wide configuration.
|
|
3
|
+
*
|
|
4
|
+
* Five endpoints:
|
|
5
|
+
*
|
|
6
|
+
* - `GET /admin/settings` — Letta connection (api key **masked**), theme,
|
|
7
|
+
* metrics + signing-key rotation timestamps. Encrypted Letta API key
|
|
8
|
+
* surfaces only as `letta_api_key_masked` (`••••••••<last4>`); plaintext
|
|
9
|
+
* never leaves the server outside the `Reveal once` flow (Step 12).
|
|
10
|
+
* - `PATCH /admin/settings` — body `{ letta_base_url?, letta_ade_url?, letta_api_key?, theme?: { default?, switcher_enabled? } }`.
|
|
11
|
+
* API key, when supplied, is encrypted under `AI_HUB_ENCRYPTION_KEY`. Theme
|
|
12
|
+
* fields live as **separate columns**, not in `feature_toggles`.
|
|
13
|
+
* - `GET /admin/settings/feature-toggles` — flat JSON blob from §8.4.
|
|
14
|
+
* - `PATCH /admin/settings/feature-toggles` — merge-patch the same blob.
|
|
15
|
+
* Unknown keys are accepted (forward-compat for future toggles).
|
|
16
|
+
* - `POST /admin/settings/rotate-signing-key` — replace
|
|
17
|
+
* `service_token_signing_key` with random 64 bytes; bump
|
|
18
|
+
* `signing_key_rotated_at`. Returns nothing — the key never leaves the
|
|
19
|
+
* server. After rotation every outstanding JWT fails signature
|
|
20
|
+
* verification (no grace period in v1; the calling super-admin is logged
|
|
21
|
+
* out alongside everyone else).
|
|
22
|
+
* - `POST /admin/settings/rotate-metrics-token` — generate a new 32-byte
|
|
23
|
+
* hex token, bump `metrics_scrape_token_rotated_at`, return
|
|
24
|
+
* `{ token: "<new>" }` exactly once in the response. Same one-time-reveal
|
|
25
|
+
* pattern as user password reset; subsequent `GET /admin/settings` calls
|
|
26
|
+
* do not echo it back.
|
|
27
|
+
*
|
|
28
|
+
* @see SPECIFICATIONS.md §6.3.4 — settings endpoints
|
|
29
|
+
* @see SPECIFICATIONS.md §6.7 — signing key rotation
|
|
30
|
+
* @see SPECIFICATIONS.md §6.10 — metrics token rotation
|
|
31
|
+
* @see ui-sketches/08-admin-settings.md
|
|
32
|
+
*/
|
|
33
|
+
import { randomBytes } from "node:crypto";
|
|
34
|
+
import { eq } from "drizzle-orm";
|
|
35
|
+
import { z } from "zod";
|
|
36
|
+
import { encryptString } from "../../auth/encryption.js";
|
|
37
|
+
import { protectedRoute } from "../../auth/middleware.js";
|
|
38
|
+
import { adminSetting } from "../../db/schema.js";
|
|
39
|
+
import { internalError, jsonResponse, validationError, } from "../../handlers/responses.js";
|
|
40
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
41
|
+
/* Helpers */
|
|
42
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
43
|
+
/** ISO-8601 timestamp with millisecond precision. */
|
|
44
|
+
function nowIso() {
|
|
45
|
+
return new Date().toISOString();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Computes a privacy-preserving display of the encrypted Letta API key.
|
|
49
|
+
*
|
|
50
|
+
* Per [08-admin-settings.md], the UI shows `••••••••` while the value is in
|
|
51
|
+
* the field — operators reveal-once if they need to copy. The server's
|
|
52
|
+
* `GET /admin/settings` never returns plaintext.
|
|
53
|
+
*
|
|
54
|
+
* @param row - the admin_setting row.
|
|
55
|
+
* @returns a masked string, or `null` when no key is stored.
|
|
56
|
+
*/
|
|
57
|
+
function maskLettaApiKey(row) {
|
|
58
|
+
if (!row.lettaApiKeyEncrypted)
|
|
59
|
+
return null;
|
|
60
|
+
// We can't decrypt without the key, and decrypting just to show "<last 4>"
|
|
61
|
+
// would defeat the purpose of encrypting at rest. Use a constant placeholder
|
|
62
|
+
// so the UI knows a key is configured without revealing length / contents.
|
|
63
|
+
return "••••••••";
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Determines the *effective* source of the Letta API key at this moment.
|
|
67
|
+
* Mirrors `letta/client.ts:resolveApiKey` (DB → env → throw) so the admin
|
|
68
|
+
* UI can show the truth about which source the runtime will use, not just
|
|
69
|
+
* what's stored in the DB.
|
|
70
|
+
*
|
|
71
|
+
* Returning the SOURCE — not the value — keeps plaintext off the wire.
|
|
72
|
+
*
|
|
73
|
+
* @param row - the admin_setting row (may be missing).
|
|
74
|
+
* @param envSource - env-var record from the server context. Lets tests
|
|
75
|
+
* exercise the source-resolution without touching the real `process.env`.
|
|
76
|
+
* @returns `"db"` when DB column has a non-empty value (the runtime will
|
|
77
|
+
* use it); `"env"` when DB is empty AND `LETTA_API_KEY` in `envSource`
|
|
78
|
+
* is set and non-empty; `"none"` otherwise.
|
|
79
|
+
*/
|
|
80
|
+
function resolveApiKeySource(row, envSource) {
|
|
81
|
+
// We deliberately can't tell here whether the DB-encrypted bytes decrypt
|
|
82
|
+
// to a non-whitespace string without doing the decrypt — but the rule is
|
|
83
|
+
// "non-empty bytes ⇒ candidate." The runtime resolver does the actual
|
|
84
|
+
// whitespace check; the UI source is just "is there a DB value worth
|
|
85
|
+
// showing?" The mismatch (DB has whitespace-only after decrypt) is rare
|
|
86
|
+
// enough that we don't pay the cost of decrypting here just to detect
|
|
87
|
+
// it; `letta_api_key_set` aligned with `lettaApiKeyEncrypted IS NOT NULL`
|
|
88
|
+
// matches the existing PATCH semantics (we never write whitespace).
|
|
89
|
+
if (row?.lettaApiKeyEncrypted)
|
|
90
|
+
return "db";
|
|
91
|
+
const envKey = envSource.LETTA_API_KEY;
|
|
92
|
+
if (envKey && envKey.trim().length > 0)
|
|
93
|
+
return "env";
|
|
94
|
+
return "none";
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Reads the singleton admin-settings row. Throws if the row hasn't been
|
|
98
|
+
* seeded — should not happen after first-boot.
|
|
99
|
+
*
|
|
100
|
+
* @param ctx - minimal server-context slice carrying the Drizzle client.
|
|
101
|
+
* @param ctx.db - the Drizzle client used for the read.
|
|
102
|
+
* @returns the row, or `null` if absent.
|
|
103
|
+
*/
|
|
104
|
+
function readSettings(ctx) {
|
|
105
|
+
return ctx.db.select().from(adminSetting).where(eq(adminSetting.id, 1)).get() ?? null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Wire-format view of the settings row for `GET /admin/settings`.
|
|
109
|
+
*
|
|
110
|
+
* @param row - the admin_setting row.
|
|
111
|
+
* @returns the JSON-safe response shape.
|
|
112
|
+
*/
|
|
113
|
+
function settingsView(row, envSource) {
|
|
114
|
+
const source = resolveApiKeySource(row, envSource);
|
|
115
|
+
return {
|
|
116
|
+
letta_base_url: row.lettaBaseUrl ?? null,
|
|
117
|
+
letta_ade_url: row.lettaAdeUrl ?? null,
|
|
118
|
+
letta_api_key_masked: maskLettaApiKey(row),
|
|
119
|
+
// True when EITHER the DB column is populated OR env.LETTA_API_KEY is
|
|
120
|
+
// set — matches the runtime resolver's "is the chat path going to
|
|
121
|
+
// work?" answer. The admin UI uses this to decide whether to render a
|
|
122
|
+
// "configured" badge vs. a "missing" warning, regardless of which
|
|
123
|
+
// source supplies the value.
|
|
124
|
+
letta_api_key_set: source !== "none",
|
|
125
|
+
// Source the runtime is *currently* using (per resolveApiKey precedence
|
|
126
|
+
// in letta/client.ts). Drives the admin UI's source-indicator text —
|
|
127
|
+
// "(from env)" vs "(from admin panel)" vs "(not configured)" — so
|
|
128
|
+
// operators don't get confused when the DB row is empty but chat
|
|
129
|
+
// still works (env fallback) or vice versa.
|
|
130
|
+
letta_api_key_source: source,
|
|
131
|
+
theme: {
|
|
132
|
+
default: row.themeDefault,
|
|
133
|
+
switcher_enabled: row.themeSwitcherEnabled,
|
|
134
|
+
},
|
|
135
|
+
feature_toggles: row.featureToggles,
|
|
136
|
+
signing_key_rotated_at: row.signingKeyRotatedAt ?? null,
|
|
137
|
+
metrics_scrape_token_rotated_at: row.metricsScrapeTokenRotatedAt ?? null,
|
|
138
|
+
updated_at: row.updatedAt,
|
|
139
|
+
// `metrics_scrape_token` and `service_token_signing_key` are deliberately
|
|
140
|
+
// omitted — see file-level comment.
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
144
|
+
/* GET /admin/settings */
|
|
145
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
146
|
+
/**
|
|
147
|
+
* `GET /admin/settings` — super_admin-only deployment configuration.
|
|
148
|
+
*
|
|
149
|
+
* Encrypted Letta API key surfaces only as the masked placeholder
|
|
150
|
+
* (`letta_api_key_masked: "••••••••"`) + the boolean `letta_api_key_set`;
|
|
151
|
+
* plaintext never leaves the server through this endpoint. Step 12 wires the
|
|
152
|
+
* `Reveal once` flow as a separate UI affordance.
|
|
153
|
+
*
|
|
154
|
+
* @see SPECIFICATIONS.md §6.3.4
|
|
155
|
+
* @see ui-sketches/08-admin-settings.md
|
|
156
|
+
*/
|
|
157
|
+
export const handleGetSettings = protectedRoute({ requiredRole: "super_admin" }, async (_req, ctx) => {
|
|
158
|
+
const row = readSettings(ctx);
|
|
159
|
+
if (!row)
|
|
160
|
+
return internalError("Admin settings row is missing — server misconfigured.");
|
|
161
|
+
return jsonResponse(200, settingsView(row, ctx.envSource));
|
|
162
|
+
});
|
|
163
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
164
|
+
/* PATCH /admin/settings */
|
|
165
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
166
|
+
/** Body for `PATCH /admin/settings`. Every field optional; empty body is a no-op. */
|
|
167
|
+
const patchSettingsSchema = z
|
|
168
|
+
.object({
|
|
169
|
+
letta_base_url: z.string().url().nullable().optional(),
|
|
170
|
+
letta_ade_url: z.string().url().nullable().optional(),
|
|
171
|
+
// The API key accepts:
|
|
172
|
+
// - a non-empty string → encrypt + store; runtime will use this DB
|
|
173
|
+
// value over env on the next request.
|
|
174
|
+
// - an empty/whitespace string → clear the DB column to NULL; the
|
|
175
|
+
// runtime then falls back to env.LETTA_API_KEY (the operator-spec'd
|
|
176
|
+
// "if admin panel is empty, use env" precedence).
|
|
177
|
+
// - omitted → no change.
|
|
178
|
+
letta_api_key: z.string().max(2048).optional(),
|
|
179
|
+
theme: z
|
|
180
|
+
.object({
|
|
181
|
+
default: z.enum(["light", "dark"]).optional(),
|
|
182
|
+
switcher_enabled: z.boolean().optional(),
|
|
183
|
+
})
|
|
184
|
+
.strict()
|
|
185
|
+
.optional(),
|
|
186
|
+
})
|
|
187
|
+
.strict();
|
|
188
|
+
/**
|
|
189
|
+
* `PATCH /admin/settings` — partial update of deployment configuration.
|
|
190
|
+
*
|
|
191
|
+
* The Letta API key, when supplied, is encrypted under
|
|
192
|
+
* `AI_HUB_ENCRYPTION_KEY` (env-only) before write. Theme fields live as
|
|
193
|
+
* separate columns (`theme_default`, `theme_switcher_enabled`) per §8.4.
|
|
194
|
+
*
|
|
195
|
+
* @see SPECIFICATIONS.md §6.3.4
|
|
196
|
+
* @see SPECIFICATIONS.md §8.4 — theme settings are explicit columns
|
|
197
|
+
*/
|
|
198
|
+
export const handlePatchSettings = protectedRoute({ requiredRole: "super_admin" }, async (req, ctx, auth) => {
|
|
199
|
+
let raw;
|
|
200
|
+
try {
|
|
201
|
+
raw = await req.json();
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return validationError("Request body must be valid JSON.");
|
|
205
|
+
}
|
|
206
|
+
const parsed = patchSettingsSchema.safeParse(raw);
|
|
207
|
+
if (!parsed.success) {
|
|
208
|
+
return validationError(parsed.error.issues[0]?.message ?? "Invalid request body.");
|
|
209
|
+
}
|
|
210
|
+
const body = parsed.data;
|
|
211
|
+
const update = { updatedAt: nowIso() };
|
|
212
|
+
if (body.letta_base_url !== undefined)
|
|
213
|
+
update.lettaBaseUrl = body.letta_base_url;
|
|
214
|
+
if (body.letta_ade_url !== undefined)
|
|
215
|
+
update.lettaAdeUrl = body.letta_ade_url;
|
|
216
|
+
if (body.letta_api_key !== undefined) {
|
|
217
|
+
// Empty / whitespace-only string clears the DB column to NULL. The
|
|
218
|
+
// runtime then falls back to env.LETTA_API_KEY (see
|
|
219
|
+
// letta/client.ts:resolveApiKey). Non-empty values are encrypted +
|
|
220
|
+
// stored as the operator's override of env.
|
|
221
|
+
const trimmed = body.letta_api_key.trim();
|
|
222
|
+
update.lettaApiKeyEncrypted =
|
|
223
|
+
trimmed.length === 0 ? null : encryptString(ctx.encryptionKey, body.letta_api_key);
|
|
224
|
+
}
|
|
225
|
+
if (body.theme?.default !== undefined)
|
|
226
|
+
update.themeDefault = body.theme.default;
|
|
227
|
+
if (body.theme?.switcher_enabled !== undefined)
|
|
228
|
+
update.themeSwitcherEnabled = body.theme.switcher_enabled;
|
|
229
|
+
if (Object.keys(update).length > 1) {
|
|
230
|
+
// More than just `updatedAt` — write.
|
|
231
|
+
ctx.db.update(adminSetting).set(update).where(eq(adminSetting.id, 1)).run();
|
|
232
|
+
ctx.logger.info({
|
|
233
|
+
actor_id: auth.user.id,
|
|
234
|
+
patch_keys: Object.keys(body),
|
|
235
|
+
event: "admin_settings_patched",
|
|
236
|
+
}, "Admin updated deployment settings.");
|
|
237
|
+
}
|
|
238
|
+
const fresh = readSettings(ctx);
|
|
239
|
+
if (!fresh)
|
|
240
|
+
return internalError();
|
|
241
|
+
return jsonResponse(200, settingsView(fresh, ctx.envSource));
|
|
242
|
+
});
|
|
243
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
244
|
+
/* GET /admin/settings/feature-toggles */
|
|
245
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
246
|
+
/**
|
|
247
|
+
* `GET /admin/settings/feature-toggles` — the §8.4 flat JSON blob.
|
|
248
|
+
*
|
|
249
|
+
* Missing keys fall back to the React side's built-in defaults at render
|
|
250
|
+
* time; the server doesn't merge defaults in here (so the admin UI can show
|
|
251
|
+
* "default" vs. "explicitly set").
|
|
252
|
+
*
|
|
253
|
+
* @see SPECIFICATIONS.md §8.4
|
|
254
|
+
*/
|
|
255
|
+
export const handleGetFeatureToggles = protectedRoute({ requiredRole: "super_admin" }, async (_req, ctx) => {
|
|
256
|
+
const row = readSettings(ctx);
|
|
257
|
+
if (!row)
|
|
258
|
+
return internalError();
|
|
259
|
+
return jsonResponse(200, { feature_toggles: row.featureToggles ?? {} });
|
|
260
|
+
});
|
|
261
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
262
|
+
/* PATCH /admin/settings/feature-toggles */
|
|
263
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
264
|
+
/**
|
|
265
|
+
* `PATCH /admin/settings/feature-toggles` body. Strict — only known toggles
|
|
266
|
+
* are accepted. **Adding a new toggle requires bumping this schema** so
|
|
267
|
+
* accidental typos in the UI surface as 400s instead of silently being saved
|
|
268
|
+
* into the blob.
|
|
269
|
+
*/
|
|
270
|
+
const featureTogglesSchema = z
|
|
271
|
+
.object({
|
|
272
|
+
sessions_panel_enabled: z.boolean().optional(),
|
|
273
|
+
sessions_panel_resizable: z.boolean().optional(),
|
|
274
|
+
sessions_panel_collapsible: z.boolean().optional(),
|
|
275
|
+
create_session_button_enabled: z.boolean().optional(),
|
|
276
|
+
user_input_enabled: z.boolean().optional(),
|
|
277
|
+
stop_button_enabled: z.boolean().optional(),
|
|
278
|
+
tool_calls_visible: z.boolean().optional(),
|
|
279
|
+
reasoning_visible: z.boolean().optional(),
|
|
280
|
+
system_messages_visible: z.boolean().optional(),
|
|
281
|
+
tpm_notice_visible: z.boolean().optional(),
|
|
282
|
+
effort_control_visible: z.boolean().optional(),
|
|
283
|
+
scroll_to_present_visible: z.boolean().optional(),
|
|
284
|
+
turn_status_visible: z.boolean().optional(),
|
|
285
|
+
})
|
|
286
|
+
.strict();
|
|
287
|
+
/**
|
|
288
|
+
* `PATCH /admin/settings/feature-toggles` — merge-patch the §8.4 blob.
|
|
289
|
+
*
|
|
290
|
+
* Unknown keys → 400. Each successful PATCH replaces the entire stored blob
|
|
291
|
+
* with the merge of (old state) + (incoming patch). Setting a key to `false`
|
|
292
|
+
* makes it explicit-false; omitting a key leaves the current value.
|
|
293
|
+
*
|
|
294
|
+
* @see SPECIFICATIONS.md §8.4
|
|
295
|
+
*/
|
|
296
|
+
export const handlePatchFeatureToggles = protectedRoute({ requiredRole: "super_admin" }, async (req, ctx, auth) => {
|
|
297
|
+
let raw;
|
|
298
|
+
try {
|
|
299
|
+
raw = await req.json();
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
return validationError("Request body must be valid JSON.");
|
|
303
|
+
}
|
|
304
|
+
const parsed = featureTogglesSchema.safeParse(raw);
|
|
305
|
+
if (!parsed.success) {
|
|
306
|
+
return validationError(parsed.error.issues[0]?.message ?? "Invalid request body.");
|
|
307
|
+
}
|
|
308
|
+
const body = parsed.data;
|
|
309
|
+
const row = readSettings(ctx);
|
|
310
|
+
if (!row)
|
|
311
|
+
return internalError();
|
|
312
|
+
const current = (row.featureToggles ?? {});
|
|
313
|
+
const next = { ...current };
|
|
314
|
+
for (const [k, v] of Object.entries(body)) {
|
|
315
|
+
if (v !== undefined)
|
|
316
|
+
next[k] = v;
|
|
317
|
+
}
|
|
318
|
+
ctx.db
|
|
319
|
+
.update(adminSetting)
|
|
320
|
+
.set({ featureToggles: next, updatedAt: nowIso() })
|
|
321
|
+
.where(eq(adminSetting.id, 1))
|
|
322
|
+
.run();
|
|
323
|
+
ctx.logger.info({
|
|
324
|
+
actor_id: auth.user.id,
|
|
325
|
+
patch_keys: Object.keys(body),
|
|
326
|
+
event: "admin_feature_toggles_patched",
|
|
327
|
+
}, "Admin updated feature toggles.");
|
|
328
|
+
return jsonResponse(200, { feature_toggles: next });
|
|
329
|
+
});
|
|
330
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
331
|
+
/* POST /admin/settings/rotate-signing-key */
|
|
332
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
333
|
+
/**
|
|
334
|
+
* `POST /admin/settings/rotate-signing-key` — replace the JWT signing key.
|
|
335
|
+
*
|
|
336
|
+
* Generates 64 new random bytes (matches seed bytes), writes to
|
|
337
|
+
* `admin_setting.service_token_signing_key`, bumps `signing_key_rotated_at`.
|
|
338
|
+
* Returns 204 No Content — the key never leaves the server.
|
|
339
|
+
*
|
|
340
|
+
* **Every outstanding JWT fails signature verification immediately after this
|
|
341
|
+
* call** (no grace period in v1). The calling super-admin's own token is
|
|
342
|
+
* invalidated alongside everyone else's; the React UI handles the 401 on the
|
|
343
|
+
* next request by routing back to login.
|
|
344
|
+
*
|
|
345
|
+
* @see SPECIFICATIONS.md §6.7 — signing key
|
|
346
|
+
* @see ui-sketches/08-admin-settings.md
|
|
347
|
+
*/
|
|
348
|
+
export const handleRotateSigningKey = protectedRoute({ requiredRole: "super_admin" }, async (_req, ctx, auth) => {
|
|
349
|
+
const newKey = randomBytes(64);
|
|
350
|
+
ctx.db
|
|
351
|
+
.update(adminSetting)
|
|
352
|
+
.set({
|
|
353
|
+
serviceTokenSigningKey: newKey,
|
|
354
|
+
signingKeyRotatedAt: nowIso(),
|
|
355
|
+
updatedAt: nowIso(),
|
|
356
|
+
})
|
|
357
|
+
.where(eq(adminSetting.id, 1))
|
|
358
|
+
.run();
|
|
359
|
+
ctx.logger.info({
|
|
360
|
+
actor_id: auth.user.id,
|
|
361
|
+
event: "admin_signing_key_rotated",
|
|
362
|
+
}, "Admin rotated the JWT signing key. All outstanding tokens are now invalid.");
|
|
363
|
+
return new Response(null, { status: 204 });
|
|
364
|
+
});
|
|
365
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
366
|
+
/* POST /admin/settings/rotate-metrics-token */
|
|
367
|
+
/* ────────────────────────────────────────────────────────────────────────── */
|
|
368
|
+
/**
|
|
369
|
+
* `POST /admin/settings/rotate-metrics-token` — replace the `/metrics` bearer
|
|
370
|
+
* token.
|
|
371
|
+
*
|
|
372
|
+
* Generates a fresh 32-byte hex token, writes it, bumps
|
|
373
|
+
* `metrics_scrape_token_rotated_at`, and returns `{ token: "<new>" }` exactly
|
|
374
|
+
* once. The plaintext never appears in subsequent `GET /admin/settings`
|
|
375
|
+
* responses (same one-time-reveal pattern as user password resets).
|
|
376
|
+
*
|
|
377
|
+
* Rotating does **not** invalidate the calling super-admin's JWT — only the
|
|
378
|
+
* metrics-scraper bearer changes. Operators paste the new value into their
|
|
379
|
+
* Prometheus / Alloy config; old scrapers start failing immediately.
|
|
380
|
+
*
|
|
381
|
+
* @see SPECIFICATIONS.md §6.10
|
|
382
|
+
* @see ui-sketches/08-admin-settings.md
|
|
383
|
+
*/
|
|
384
|
+
export const handleRotateMetricsToken = protectedRoute({ requiredRole: "super_admin" }, async (_req, ctx, auth) => {
|
|
385
|
+
const newToken = randomBytes(32).toString("hex");
|
|
386
|
+
ctx.db
|
|
387
|
+
.update(adminSetting)
|
|
388
|
+
.set({
|
|
389
|
+
metricsScrapeToken: newToken,
|
|
390
|
+
metricsScrapeTokenRotatedAt: nowIso(),
|
|
391
|
+
updatedAt: nowIso(),
|
|
392
|
+
})
|
|
393
|
+
.where(eq(adminSetting.id, 1))
|
|
394
|
+
.run();
|
|
395
|
+
ctx.logger.info({
|
|
396
|
+
actor_id: auth.user.id,
|
|
397
|
+
event: "admin_metrics_token_rotated",
|
|
398
|
+
}, "Admin rotated the /metrics scrape token.");
|
|
399
|
+
return jsonResponse(200, { token: newToken });
|
|
400
|
+
});
|
|
401
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../src/routes/admin/settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/admin/users/*` — admin + super_admin user management.
|
|
3
|
+
*
|
|
4
|
+
* Five endpoints:
|
|
5
|
+
*
|
|
6
|
+
* - `GET /admin/users` — list every user in the deployment.
|
|
7
|
+
* - `POST /admin/users` — create a user; the server generates a 16-char
|
|
8
|
+
* alphanumeric password, hashes it, and returns the **plaintext exactly
|
|
9
|
+
* once** in the response. The caller delivers it to the user out-of-band.
|
|
10
|
+
* Sets `is_initial_password = true` so the user is forced to change on
|
|
11
|
+
* first login (§8.5).
|
|
12
|
+
* - `PATCH /admin/users/:id/role` — change a user's role. Uses
|
|
13
|
+
* `applyUserRoleChangeInTx` so the shield + min-count rules are enforced
|
|
14
|
+
* inside the transaction (no race possible).
|
|
15
|
+
* - `POST /admin/users/:id/reset-password` — regenerate a 16-char password
|
|
16
|
+
* for an existing user. Same return-once semantics; sets the initial-
|
|
17
|
+
* password flag back to true. Uses `assertPasswordResetInTx` to validate
|
|
18
|
+
* the shield + min-count rules, then performs the async Argon2 hash + write
|
|
19
|
+
* outside the transaction (better-sqlite3 transactions are synchronous so
|
|
20
|
+
* an Argon2 hash cannot live inside one). The narrow window between assert
|
|
21
|
+
* and write is documented in the helper's JSDoc and is acceptable in v1.
|
|
22
|
+
* - `DELETE /admin/users/:id` — hard-delete the user. Uses
|
|
23
|
+
* `applyUserDeletionInTx`; cascades to `session` via the FK.
|
|
24
|
+
*
|
|
25
|
+
* **Admin shield + min-count rules** are enforced by the helpers in
|
|
26
|
+
* `auth/roleRules.ts`; the handlers here just translate the helper's
|
|
27
|
+
* `TxResult` into a Response.
|
|
28
|
+
*
|
|
29
|
+
* **Admin cannot create / promote to `super_admin`.** The role enum on the
|
|
30
|
+
* POST body is filtered by caller role (admins see user / power_user /
|
|
31
|
+
* admin; super_admin sees all four). This mirrors the [05-admin-users.md]
|
|
32
|
+
* sketch's "Create User" modal.
|
|
33
|
+
*
|
|
34
|
+
* @see SPECIFICATIONS.md §6.3.4 — admin users endpoints
|
|
35
|
+
* @see SPECIFICATIONS.md §8.1 — shield + min-count rules
|
|
36
|
+
* @see SPECIFICATIONS.md §8.5 — password reset semantics
|
|
37
|
+
* @see ui-sketches/05-admin-users.md
|
|
38
|
+
*/
|
|
39
|
+
import type { RouteHandler } from "../../handlers/types.js";
|
|
40
|
+
/**
|
|
41
|
+
* `GET /admin/users` — list every user in the deployment.
|
|
42
|
+
*
|
|
43
|
+
* `admin` and `super_admin` only; gated by `protectedRoute({ requiredRole: "admin" })`.
|
|
44
|
+
* Sorted alphabetically by username for stable rendering in the admin table.
|
|
45
|
+
*
|
|
46
|
+
* @see SPECIFICATIONS.md §6.3.4
|
|
47
|
+
* @see ui-sketches/05-admin-users.md
|
|
48
|
+
*/
|
|
49
|
+
export declare const handleListUsers: RouteHandler;
|
|
50
|
+
/**
|
|
51
|
+
* `POST /admin/users` — create a new user with a server-generated password.
|
|
52
|
+
*
|
|
53
|
+
* The 16-char alphanumeric plaintext is returned **exactly once** in the
|
|
54
|
+
* response (`{ user, generated_password }`); the caller (admin) is expected
|
|
55
|
+
* to deliver it out-of-band per §8.5. The plaintext never appears in logs
|
|
56
|
+
* (pino's redaction policy plus we never log the field).
|
|
57
|
+
*
|
|
58
|
+
* Caller-role gates:
|
|
59
|
+
*
|
|
60
|
+
* - `admin` can create role `user` / `power_user` / `admin`.
|
|
61
|
+
* - `super_admin` can additionally create `super_admin`.
|
|
62
|
+
*
|
|
63
|
+
* On username collision returns 409 `username_taken` (not 400) — the request
|
|
64
|
+
* itself was well-formed; the conflict is with existing data.
|
|
65
|
+
*
|
|
66
|
+
* @see SPECIFICATIONS.md §6.3.4 / §8.5
|
|
67
|
+
* @see ui-sketches/05-admin-users.md
|
|
68
|
+
*/
|
|
69
|
+
export declare const handleCreateUser: RouteHandler;
|
|
70
|
+
/**
|
|
71
|
+
* `PATCH /admin/users/:id/role` — change a user's role.
|
|
72
|
+
*
|
|
73
|
+
* Delegates the shield + min-count check to `applyUserRoleChangeInTx`, which
|
|
74
|
+
* re-reads the target row + super-admin count inside one transaction so a
|
|
75
|
+
* concurrent demote cannot bypass the invariants.
|
|
76
|
+
*
|
|
77
|
+
* Additional caller-role gate on top of the helper: an `admin` cannot grant
|
|
78
|
+
* the `super_admin` role on a target they could otherwise mutate (e.g.
|
|
79
|
+
* promoting a `power_user → super_admin`) — only super-admins can.
|
|
80
|
+
*
|
|
81
|
+
* @see SPECIFICATIONS.md §6.3.4 / §8.1
|
|
82
|
+
*/
|
|
83
|
+
export declare const handlePatchUserRole: RouteHandler;
|
|
84
|
+
/**
|
|
85
|
+
* `POST /admin/users/:id/reset-password` — regenerate the user's password.
|
|
86
|
+
*
|
|
87
|
+
* Uses `assertPasswordResetInTx` to validate shield + min-count inside a
|
|
88
|
+
* transaction, then performs the async Argon2 hash + UPDATE *outside* the
|
|
89
|
+
* transaction (better-sqlite3 transactions are synchronous). The narrow
|
|
90
|
+
* window between assertion and write is acceptable in v1 — a concurrent
|
|
91
|
+
* role-mutation could in theory bypass the rule, but the worst-case is a
|
|
92
|
+
* single password reset on a user the admin shouldn't have been able to
|
|
93
|
+
* touch; the admin already had to authenticate and the new password is
|
|
94
|
+
* displayed only to them. Mitigation if this ever bites: re-verify the
|
|
95
|
+
* caller's role hasn't dropped between assertion and write.
|
|
96
|
+
*
|
|
97
|
+
* Returns `{ generated_password }` exactly once. Sets `is_initial_password =
|
|
98
|
+
* true` so the user is forced to change on their next login.
|
|
99
|
+
*
|
|
100
|
+
* @see SPECIFICATIONS.md §6.3.4 / §8.5
|
|
101
|
+
* @see ui-sketches/05-admin-users.md
|
|
102
|
+
*/
|
|
103
|
+
export declare const handleResetUserPassword: RouteHandler;
|
|
104
|
+
/**
|
|
105
|
+
* `DELETE /admin/users/:id` — hard-delete the user.
|
|
106
|
+
*
|
|
107
|
+
* Uses `applyUserDeletionInTx`; the FK on `session.user_id` cascades, so
|
|
108
|
+
* deleting a user removes all their sessions atomically. The shield + min-
|
|
109
|
+
* count invariants apply (an admin cannot delete another admin or super
|
|
110
|
+
* admin; nobody can delete the only super admin).
|
|
111
|
+
*
|
|
112
|
+
* @see SPECIFICATIONS.md §6.3.4 / §8.1
|
|
113
|
+
*/
|
|
114
|
+
export declare const handleDeleteUser: RouteHandler;
|
|
115
|
+
//# sourceMappingURL=users.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/routes/admin/users.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AA0BH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAgF5D;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,YAQ7B,CAAC;AAiBF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,EAAE,YAyE9B,CAAC;AAaF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,EAAE,YA4CjC,CAAC;AAMF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,uBAAuB,EAAE,YA6CrC,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,EAAE,YA6B9B,CAAC"}
|