@fuzdev/fuz_app 0.87.0 → 0.89.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/action_rpc.js +1 -1
- package/dist/actions/register_action_ws.js +1 -1
- package/dist/auth/CLAUDE.md +15 -0
- package/dist/auth/account_actions.js +1 -1
- package/dist/auth/account_route_schema.d.ts +152 -0
- package/dist/auth/account_route_schema.d.ts.map +1 -0
- package/dist/auth/account_route_schema.js +147 -0
- package/dist/auth/account_routes.d.ts +18 -83
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +28 -115
- package/dist/auth/audit_log_route_schema.d.ts +32 -0
- package/dist/auth/audit_log_route_schema.d.ts.map +1 -0
- package/dist/auth/audit_log_route_schema.js +36 -0
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +2 -12
- package/dist/auth/bearer_auth.js +1 -1
- package/dist/auth/bootstrap_route_schema.d.ts +85 -0
- package/dist/auth/bootstrap_route_schema.d.ts.map +1 -0
- package/dist/auth/bootstrap_route_schema.js +56 -0
- package/dist/auth/bootstrap_routes.d.ts +0 -20
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +4 -35
- package/dist/auth/signup_route_schema.d.ts +53 -0
- package/dist/auth/signup_route_schema.d.ts.map +1 -0
- package/dist/auth/signup_route_schema.js +59 -0
- package/dist/auth/signup_routes.d.ts +0 -26
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +8 -40
- package/dist/http/CLAUDE.md +2 -1
- package/dist/http/client_ip.d.ts +15 -0
- package/dist/http/client_ip.d.ts.map +1 -0
- package/dist/http/client_ip.js +13 -0
- package/dist/http/proxy.d.ts +0 -7
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +0 -7
- package/dist/realtime/sse.d.ts +0 -2
- package/dist/realtime/sse.d.ts.map +1 -1
- package/dist/realtime/sse.js +1 -2
- package/dist/realtime/sse_constants.d.ts +17 -0
- package/dist/realtime/sse_constants.d.ts.map +1 -0
- package/dist/realtime/sse_constants.js +16 -0
- package/dist/testing/CLAUDE.md +9 -1
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +1 -1
- package/dist/testing/app_server.d.ts +0 -15
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +1 -15
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/cross_backend/body_size_smuggling.d.ts +12 -0
- package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size_smuggling.js +68 -41
- package/dist/testing/cross_backend/capabilities.d.ts +29 -0
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +15 -0
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +13 -0
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +1 -0
- package/dist/testing/cross_backend/in_process_setup.d.ts +143 -0
- package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -0
- package/dist/testing/cross_backend/in_process_setup.js +166 -0
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +13 -6
- package/dist/testing/cross_backend/setup.d.ts +31 -140
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +11 -171
- package/dist/testing/cross_backend/sse_round_trip.js +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +2 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +16 -1
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +15 -18
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +2 -1
- package/dist/testing/sse_round_trip.d.ts +1 -1
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +1 -1
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +7 -9
- package/dist/testing/test_credentials.d.ts +23 -0
- package/dist/testing/test_credentials.d.ts.map +1 -0
- package/dist/testing/test_credentials.js +22 -0
- package/package.json +4 -4
|
@@ -21,41 +21,18 @@
|
|
|
21
21
|
*
|
|
22
22
|
* @module
|
|
23
23
|
*/
|
|
24
|
-
import { z } from 'zod';
|
|
25
24
|
import { clear_session_cookie, create_session_and_set_cookie } from './session_middleware.js';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
25
|
+
import { RoleGrantSummaryJson, to_session_account } from './account_schema.js';
|
|
26
|
+
import { account_status_route_shape, create_account_route_shapes, DEFAULT_MAX_SESSIONS, } from './account_route_schema.js';
|
|
28
27
|
import { hash_session_token, query_session_revoke_all_for_account, query_session_revoke_by_hash_unscoped, } from './session_queries.js';
|
|
29
28
|
import { query_account_by_username_or_email, query_update_account_password, } from './account_queries.js';
|
|
30
29
|
import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
|
|
31
30
|
import { build_account_context, build_request_context, get_request_context, require_request_context, resolve_acting_actor, } from './request_context.js';
|
|
32
31
|
import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
|
|
33
32
|
import { get_route_input } from '../http/route_spec.js';
|
|
34
|
-
import { get_client_ip } from '../http/
|
|
33
|
+
import { get_client_ip } from '../http/client_ip.js';
|
|
35
34
|
import { rate_limit_exceeded_response } from '../rate_limiter.js';
|
|
36
|
-
import {
|
|
37
|
-
import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INVALID_CREDENTIALS, ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, } from '../http/error_schemas.js';
|
|
38
|
-
/** Input for `GET /api/account/status`. No parameters — caller is the subject. */
|
|
39
|
-
export const AccountStatusInput = z.null();
|
|
40
|
-
/**
|
|
41
|
-
* Output for `GET /api/account/status` on the authenticated path.
|
|
42
|
-
*
|
|
43
|
-
* `account` is always populated for authenticated callers. `actor` and
|
|
44
|
-
* `role_grants` are populated when the caller's account has a unique actor or
|
|
45
|
-
* the request supplies `?acting=<actor_id>`; on multi-actor accounts
|
|
46
|
-
* without an `acting` query, `actor` is `null` and `role_grants` is empty so
|
|
47
|
-
* the frontend can show a persona picker without a separate roundtrip.
|
|
48
|
-
*/
|
|
49
|
-
export const AccountStatusOutput = z.strictObject({
|
|
50
|
-
account: SessionAccountJson,
|
|
51
|
-
actor: ActorSummaryJson.nullable(),
|
|
52
|
-
role_grants: z.array(RoleGrantSummaryJson),
|
|
53
|
-
});
|
|
54
|
-
/** Error body for `GET /api/account/status` on the unauthenticated path. */
|
|
55
|
-
export const AccountStatusUnauthenticatedError = z.looseObject({
|
|
56
|
-
error: z.literal(ERROR_AUTHENTICATION_REQUIRED),
|
|
57
|
-
bootstrap_available: z.boolean().optional(),
|
|
58
|
-
});
|
|
35
|
+
import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INVALID_CREDENTIALS } from '../http/error_schemas.js';
|
|
59
36
|
/**
|
|
60
37
|
* Create the account status route spec.
|
|
61
38
|
*
|
|
@@ -70,15 +47,8 @@ export const AccountStatusUnauthenticatedError = z.looseObject({
|
|
|
70
47
|
* @returns a single account status route spec
|
|
71
48
|
*/
|
|
72
49
|
export const create_account_status_route_spec = (options) => ({
|
|
73
|
-
|
|
74
|
-
path: options?.path ??
|
|
75
|
-
auth: { account: 'none', actor: 'none' },
|
|
76
|
-
description: 'Current account info (unauthenticated: 401 with bootstrap status)',
|
|
77
|
-
input: AccountStatusInput,
|
|
78
|
-
output: AccountStatusOutput,
|
|
79
|
-
errors: {
|
|
80
|
-
401: AccountStatusUnauthenticatedError,
|
|
81
|
-
},
|
|
50
|
+
...account_status_route_shape,
|
|
51
|
+
path: options?.path ?? account_status_route_shape.path,
|
|
82
52
|
handler: async (c, route) => {
|
|
83
53
|
const account_id = c.get(ACCOUNT_ID_KEY) ?? null;
|
|
84
54
|
if (!account_id) {
|
|
@@ -147,10 +117,6 @@ export const create_account_status_route_spec = (options) => ({
|
|
|
147
117
|
});
|
|
148
118
|
},
|
|
149
119
|
});
|
|
150
|
-
/** Default maximum sessions per account. */
|
|
151
|
-
export const DEFAULT_MAX_SESSIONS = 5;
|
|
152
|
-
/** Default maximum API tokens per account. */
|
|
153
|
-
export const DEFAULT_MAX_TOKENS = 10;
|
|
154
120
|
/**
|
|
155
121
|
* Default minimum wall-clock time (ms) for a login failure (401) response.
|
|
156
122
|
*
|
|
@@ -176,75 +142,43 @@ const login_fail_delay = (floor_ms, jitter_ms) => {
|
|
|
176
142
|
const jitter = jitter_ms > 0 ? Math.floor(Math.random() * (jitter_ms * 2 + 1)) - jitter_ms : 0;
|
|
177
143
|
return new Promise((resolve) => setTimeout(resolve, floor_ms + jitter));
|
|
178
144
|
};
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
export const LoginInput = z.strictObject({
|
|
182
|
-
username: UsernameProvided,
|
|
183
|
-
password: PasswordProvided,
|
|
184
|
-
});
|
|
185
|
-
/** Output for `POST /login`. The signed session cookie is the operative side effect. */
|
|
186
|
-
export const LoginOutput = z.strictObject({
|
|
187
|
-
ok: z.literal(true),
|
|
188
|
-
});
|
|
189
|
-
/** Input for `POST /logout`. Session identity flows through the cookie. */
|
|
190
|
-
export const LogoutInput = z.null();
|
|
191
|
-
/** Output for `POST /logout`. Includes the revoked account's username for UI redraw. */
|
|
192
|
-
export const LogoutOutput = z.strictObject({
|
|
193
|
-
ok: z.literal(true),
|
|
194
|
-
username: z.string(),
|
|
195
|
-
});
|
|
196
|
-
/** Input for `POST /password`. `current_password` is minimally validated; `new_password` enforces the full policy. */
|
|
197
|
-
export const PasswordChangeInput = z.strictObject({
|
|
198
|
-
current_password: PasswordProvided,
|
|
199
|
-
new_password: Password,
|
|
200
|
-
});
|
|
201
|
-
/** Output for `POST /password`. Counts are returned so the UI can summarize the revoke-all cascade. */
|
|
202
|
-
export const PasswordChangeOutput = z.strictObject({
|
|
203
|
-
ok: z.literal(true),
|
|
204
|
-
sessions_revoked: z.number(),
|
|
205
|
-
tokens_revoked: z.number(),
|
|
206
|
-
});
|
|
145
|
+
// `create_account_route_specs` spreads each shape and attaches the live
|
|
146
|
+
// handler below.
|
|
207
147
|
/**
|
|
208
148
|
* Create account route specs for session-based auth.
|
|
209
149
|
*
|
|
210
|
-
* The returned specs cover the
|
|
211
|
-
*
|
|
212
|
-
*
|
|
150
|
+
* The returned specs cover the REST flows that stay after the RPC migration:
|
|
151
|
+
* `/status` (account info + bootstrap availability), `/verify` (nginx
|
|
152
|
+
* `auth_request` shim), `/login`, `/logout`, `/password`. `/status` is bundled
|
|
153
|
+
* here (relative path, prefixed to `/api/account/status` by the caller) so
|
|
154
|
+
* every account surface serves it, matching the Rust `account_router`.
|
|
155
|
+
* Self-service session/token management is on `auth/account_actions.ts`.
|
|
213
156
|
*
|
|
214
157
|
* @param deps - stateless capabilities (keyring, password, log)
|
|
215
|
-
* @param options - per-factory configuration (session_options, ip_rate_limiter, login_account_rate_limiter)
|
|
158
|
+
* @param options - per-factory configuration (session_options, ip_rate_limiter, login_account_rate_limiter, bootstrap_status)
|
|
216
159
|
* @returns route specs (not yet applied to Hono)
|
|
217
160
|
*/
|
|
218
161
|
export const create_account_route_specs = (deps, options) => {
|
|
219
162
|
const { keyring, password } = deps;
|
|
220
|
-
const { session_options, ip_rate_limiter, login_account_rate_limiter, max_sessions = DEFAULT_MAX_SESSIONS, login_fail_floor_ms = DEFAULT_LOGIN_FAIL_FLOOR_MS, login_fail_jitter_ms = DEFAULT_LOGIN_FAIL_JITTER_MS, connection_closer = null, } = options;
|
|
163
|
+
const { session_options, ip_rate_limiter, login_account_rate_limiter, max_sessions = DEFAULT_MAX_SESSIONS, login_fail_floor_ms = DEFAULT_LOGIN_FAIL_FLOOR_MS, login_fail_jitter_ms = DEFAULT_LOGIN_FAIL_JITTER_MS, connection_closer = null, bootstrap_status, } = options;
|
|
164
|
+
const [verify_shape, login_shape, logout_shape, password_shape] = create_account_route_shapes({
|
|
165
|
+
login_account_rate_limited: login_account_rate_limiter !== null,
|
|
166
|
+
});
|
|
221
167
|
return [
|
|
168
|
+
// `/status` is bundled into the account family (relative path, prefixed
|
|
169
|
+
// to `/api/account/status` by the caller) so every account surface serves
|
|
170
|
+
// it — matching the Rust `account_router`. The standalone
|
|
171
|
+
// `create_account_status_route_spec` stays the building block.
|
|
172
|
+
create_account_status_route_spec({ bootstrap_status }),
|
|
222
173
|
{
|
|
223
|
-
|
|
224
|
-
path: '/verify',
|
|
225
|
-
auth: { account: 'required', actor: 'none' },
|
|
226
|
-
description: 'Session-validity probe for nginx auth_request (empty body, 200 or 401)',
|
|
227
|
-
input: z.null(),
|
|
228
|
-
output: z.null(),
|
|
174
|
+
...verify_shape,
|
|
229
175
|
handler: (c) => {
|
|
230
176
|
require_request_context(c);
|
|
231
177
|
return c.body(null, 200);
|
|
232
178
|
},
|
|
233
179
|
},
|
|
234
180
|
{
|
|
235
|
-
|
|
236
|
-
path: '/login',
|
|
237
|
-
auth: { account: 'none', actor: 'none' },
|
|
238
|
-
description: 'Exchange credentials for session',
|
|
239
|
-
input: LoginInput,
|
|
240
|
-
output: LoginOutput,
|
|
241
|
-
rate_limit: 'both',
|
|
242
|
-
errors: {
|
|
243
|
-
400: z.looseObject({
|
|
244
|
-
error: z.enum([ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY]),
|
|
245
|
-
}),
|
|
246
|
-
401: z.looseObject({ error: z.literal(ERROR_INVALID_CREDENTIALS) }),
|
|
247
|
-
},
|
|
181
|
+
...login_shape,
|
|
248
182
|
handler: async (c, route) => {
|
|
249
183
|
// Per-IP rate limit check (before any DB/password work)
|
|
250
184
|
const ip = ip_rate_limiter ? get_client_ip(c) : null;
|
|
@@ -331,16 +265,7 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
331
265
|
},
|
|
332
266
|
},
|
|
333
267
|
{
|
|
334
|
-
|
|
335
|
-
path: '/logout',
|
|
336
|
-
// `credential_types: ['session']` — see `docs/security.md` §Credential-channel gating.
|
|
337
|
-
// Logout is a session-bound operation; a bearer / daemon token holds no session
|
|
338
|
-
// to end, so the dispatcher rejects it (403 `credential_type_required`) rather than
|
|
339
|
-
// returning a misleading 200 + a phantom `logout` audit row for a no-op.
|
|
340
|
-
auth: { account: 'required', actor: 'none', credential_types: ['session'] },
|
|
341
|
-
description: 'Revoke current session and clear cookie',
|
|
342
|
-
input: LogoutInput,
|
|
343
|
-
output: LogoutOutput,
|
|
268
|
+
...logout_shape,
|
|
344
269
|
handler: async (c, route) => {
|
|
345
270
|
const ctx = require_request_context(c);
|
|
346
271
|
const session_token = c.get(session_options.context_key) ?? null;
|
|
@@ -377,19 +302,7 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
377
302
|
},
|
|
378
303
|
},
|
|
379
304
|
{
|
|
380
|
-
|
|
381
|
-
path: '/password',
|
|
382
|
-
// `credential_types: ['session']` — see `docs/security.md` §Credential-channel gating.
|
|
383
|
-
auth: { account: 'required', actor: 'none', credential_types: ['session'] },
|
|
384
|
-
description: 'Change password (revokes all sessions and API tokens)',
|
|
385
|
-
input: PasswordChangeInput,
|
|
386
|
-
output: PasswordChangeOutput,
|
|
387
|
-
rate_limit: login_account_rate_limiter ? 'both' : 'ip',
|
|
388
|
-
errors: {
|
|
389
|
-
400: z.looseObject({
|
|
390
|
-
error: z.enum([ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY]),
|
|
391
|
-
}),
|
|
392
|
-
},
|
|
305
|
+
...password_shape,
|
|
393
306
|
handler: async (c, route) => {
|
|
394
307
|
// per-IP rate limit check (before argon2 work)
|
|
395
308
|
const ip = ip_rate_limiter ? get_client_ip(c) : null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono-free wire schemas + route shape for the audit-log SSE stream.
|
|
3
|
+
*
|
|
4
|
+
* Split from `audit_log_routes.ts` (whose handler pulls `hono/streaming` via
|
|
5
|
+
* `realtime/sse`) so cross-process test suites can build the audit-stream
|
|
6
|
+
* route shape without dragging the in-process SSE handler, and its optional
|
|
7
|
+
* `hono` peer, onto a backend-spawning consumer. `audit_log_routes.ts` imports
|
|
8
|
+
* these back and attaches the live SSE handler; single source of truth for the
|
|
9
|
+
* wire shape.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import type { RouteSpec } from '../http/route_spec.js';
|
|
15
|
+
/** Query schema for the audit-log SSE route — multi-actor admins pass `?acting=<uuid>`. */
|
|
16
|
+
export declare const AuditStreamQuery: z.ZodObject<{
|
|
17
|
+
acting: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
18
|
+
}, z.core.$strict>;
|
|
19
|
+
export type AuditStreamQuery = z.infer<typeof AuditStreamQuery>;
|
|
20
|
+
/** Default role required to access the audit-log SSE route. */
|
|
21
|
+
export declare const DEFAULT_AUDIT_STREAM_ROLE = "admin";
|
|
22
|
+
/**
|
|
23
|
+
* The `GET /audit/stream` SSE route shape minus its handler — pure hono-free
|
|
24
|
+
* data. `create_audit_log_route_specs` spreads this and attaches the live SSE
|
|
25
|
+
* handler; cross-process surface builders spread it with a stub handler. The
|
|
26
|
+
* output is `z.null()` because SSE streams have no JSON response body.
|
|
27
|
+
*
|
|
28
|
+
* @param required_role - role gating the stream (default `DEFAULT_AUDIT_STREAM_ROLE`)
|
|
29
|
+
* @returns the SSE route shape minus its handler
|
|
30
|
+
*/
|
|
31
|
+
export declare const create_audit_log_route_shape: (required_role?: string) => Omit<RouteSpec, "handler">;
|
|
32
|
+
//# sourceMappingURL=audit_log_route_schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit_log_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAGrD,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB;;kBAAwC,CAAC;AACtE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,+DAA+D;AAC/D,eAAO,MAAM,yBAAyB,UAAU,CAAC;AAEjD;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GACxC,gBAAe,MAAkC,KAC/C,IAAI,CAAC,SAAS,EAAE,SAAS,CAQ1B,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono-free wire schemas + route shape for the audit-log SSE stream.
|
|
3
|
+
*
|
|
4
|
+
* Split from `audit_log_routes.ts` (whose handler pulls `hono/streaming` via
|
|
5
|
+
* `realtime/sse`) so cross-process test suites can build the audit-stream
|
|
6
|
+
* route shape without dragging the in-process SSE handler, and its optional
|
|
7
|
+
* `hono` peer, onto a backend-spawning consumer. `audit_log_routes.ts` imports
|
|
8
|
+
* these back and attaches the live SSE handler; single source of truth for the
|
|
9
|
+
* wire shape.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { ActingActor } from '../http/auth_shape.js';
|
|
15
|
+
/** Query schema for the audit-log SSE route — multi-actor admins pass `?acting=<uuid>`. */
|
|
16
|
+
export const AuditStreamQuery = z.strictObject({ acting: ActingActor });
|
|
17
|
+
/** Default role required to access the audit-log SSE route. */
|
|
18
|
+
export const DEFAULT_AUDIT_STREAM_ROLE = 'admin';
|
|
19
|
+
/**
|
|
20
|
+
* The `GET /audit/stream` SSE route shape minus its handler — pure hono-free
|
|
21
|
+
* data. `create_audit_log_route_specs` spreads this and attaches the live SSE
|
|
22
|
+
* handler; cross-process surface builders spread it with a stub handler. The
|
|
23
|
+
* output is `z.null()` because SSE streams have no JSON response body.
|
|
24
|
+
*
|
|
25
|
+
* @param required_role - role gating the stream (default `DEFAULT_AUDIT_STREAM_ROLE`)
|
|
26
|
+
* @returns the SSE route shape minus its handler
|
|
27
|
+
*/
|
|
28
|
+
export const create_audit_log_route_shape = (required_role = DEFAULT_AUDIT_STREAM_ROLE) => ({
|
|
29
|
+
method: 'GET',
|
|
30
|
+
path: '/audit/stream',
|
|
31
|
+
auth: { account: 'required', actor: 'required', roles: [required_role] },
|
|
32
|
+
description: 'Subscribe to realtime audit log events',
|
|
33
|
+
query: AuditStreamQuery,
|
|
34
|
+
input: z.null(),
|
|
35
|
+
output: z.null(), // SSE — no JSON response
|
|
36
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_log_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"audit_log_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAErD,OAAO,EAAsB,KAAK,SAAS,EAAE,KAAK,eAAe,EAAC,MAAM,oBAAoB,CAAC;AAC7F,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oCAAoC,CAAC;AAIzE,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACpC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,MAAM,CAAC,EAAE;QACR,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;QAC1F,GAAG,EAAE,MAAM,CAAC;KACZ,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GAAI,UAAU,oBAAoB,KAAG,KAAK,CAAC,SAAS,CAyB5F,CAAC"}
|
|
@@ -11,13 +11,10 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
14
|
+
import { create_audit_log_route_shape } from './audit_log_route_schema.js';
|
|
15
15
|
import { create_sse_response } from '../realtime/sse.js';
|
|
16
16
|
import { AUTH_SESSION_TOKEN_HASH_KEY, require_request_context } from './request_context.js';
|
|
17
17
|
import { AUDIT_LOG_CHANNEL } from '../realtime/sse_auth_guard.js';
|
|
18
|
-
import { ActingActor } from '../http/auth_shape.js';
|
|
19
|
-
/** Query schema for the audit-log SSE route — multi-actor admins pass `?acting=<uuid>`. */
|
|
20
|
-
const AuditStreamQuery = z.strictObject({ acting: ActingActor });
|
|
21
18
|
/**
|
|
22
19
|
* Create the optional audit-log SSE route spec.
|
|
23
20
|
*
|
|
@@ -28,19 +25,12 @@ const AuditStreamQuery = z.strictObject({ acting: ActingActor });
|
|
|
28
25
|
* @returns the SSE route spec (when `options.stream` is provided) or an empty array
|
|
29
26
|
*/
|
|
30
27
|
export const create_audit_log_route_specs = (options) => {
|
|
31
|
-
const role = options?.required_role ?? 'admin';
|
|
32
28
|
if (!options?.stream)
|
|
33
29
|
return [];
|
|
34
30
|
const { subscribe, log } = options.stream;
|
|
35
31
|
return [
|
|
36
32
|
{
|
|
37
|
-
|
|
38
|
-
path: '/audit/stream',
|
|
39
|
-
auth: { account: 'required', actor: 'required', roles: [role] },
|
|
40
|
-
description: 'Subscribe to realtime audit log events',
|
|
41
|
-
query: AuditStreamQuery,
|
|
42
|
-
input: z.null(),
|
|
43
|
-
output: z.null(), // SSE — no JSON response
|
|
33
|
+
...create_audit_log_route_shape(options.required_role),
|
|
44
34
|
handler: (c) => {
|
|
45
35
|
const ctx = require_request_context(c);
|
|
46
36
|
// scope = session hash (capped → tabs-per-session limit and
|
package/dist/auth/bearer_auth.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { DEV } from 'esm-env';
|
|
18
18
|
import { AUTH_API_TOKEN_ID_KEY, ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
|
|
19
19
|
import { query_validate_api_token } from './api_token_queries.js';
|
|
20
|
-
import { get_client_ip } from '../http/
|
|
20
|
+
import { get_client_ip } from '../http/client_ip.js';
|
|
21
21
|
import { rate_limit_exceeded_response } from '../rate_limiter.js';
|
|
22
22
|
/**
|
|
23
23
|
* Create middleware that authenticates via bearer token.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono-free wire shape + schemas for `POST /bootstrap`.
|
|
3
|
+
*
|
|
4
|
+
* Split from `bootstrap_routes.ts` so the route's declared shape (method,
|
|
5
|
+
* path, auth, input/output/error schemas) is importable **without** the
|
|
6
|
+
* hono-coupled handler (which sets a session cookie and reads the client
|
|
7
|
+
* IP off the Hono context). `create_bootstrap_route_specs` spreads
|
|
8
|
+
* `bootstrap_route_shape` and attaches the live handler; the test surface
|
|
9
|
+
* builder (`create_test_app_surface_spec`) spreads it with a stub handler so
|
|
10
|
+
* attack-surface generation reads the real shape without pulling the
|
|
11
|
+
* in-process Hono app onto cross-process consumers. Single source of truth —
|
|
12
|
+
* the shape can't drift between the live route and the surface.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
/** Input for `POST /bootstrap`. `token` is the one-shot token file contents. */
|
|
18
|
+
export declare const BootstrapInput: z.ZodObject<{
|
|
19
|
+
token: z.ZodString;
|
|
20
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
21
|
+
password: z.ZodString;
|
|
22
|
+
}, z.core.$strict>;
|
|
23
|
+
export type BootstrapInput = z.infer<typeof BootstrapInput>;
|
|
24
|
+
/** Output for `POST /bootstrap`. Session cookie is the operative side effect. */
|
|
25
|
+
export declare const BootstrapOutput: z.ZodObject<{
|
|
26
|
+
ok: z.ZodLiteral<true>;
|
|
27
|
+
account: z.ZodObject<{
|
|
28
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
29
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
30
|
+
}, z.core.$strict>;
|
|
31
|
+
actor: z.ZodObject<{
|
|
32
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
33
|
+
}, z.core.$strict>;
|
|
34
|
+
}, z.core.$strict>;
|
|
35
|
+
export type BootstrapOutput = z.infer<typeof BootstrapOutput>;
|
|
36
|
+
/**
|
|
37
|
+
* The `POST /bootstrap` route shape minus its handler — pure hono-free data.
|
|
38
|
+
* `create_bootstrap_route_specs` spreads this and attaches the live handler;
|
|
39
|
+
* surface generation spreads it with a stub handler (handlers are never run
|
|
40
|
+
* during surface assembly, only the shape is read).
|
|
41
|
+
*/
|
|
42
|
+
export declare const bootstrap_route_shape: {
|
|
43
|
+
method: "POST";
|
|
44
|
+
path: string;
|
|
45
|
+
auth: {
|
|
46
|
+
account: "none";
|
|
47
|
+
actor: "none";
|
|
48
|
+
};
|
|
49
|
+
description: string;
|
|
50
|
+
transaction: false;
|
|
51
|
+
input: z.ZodObject<{
|
|
52
|
+
token: z.ZodString;
|
|
53
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
54
|
+
password: z.ZodString;
|
|
55
|
+
}, z.core.$strict>;
|
|
56
|
+
output: z.ZodObject<{
|
|
57
|
+
ok: z.ZodLiteral<true>;
|
|
58
|
+
account: z.ZodObject<{
|
|
59
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
60
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
61
|
+
}, z.core.$strict>;
|
|
62
|
+
actor: z.ZodObject<{
|
|
63
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
64
|
+
}, z.core.$strict>;
|
|
65
|
+
}, z.core.$strict>;
|
|
66
|
+
rate_limit: "ip";
|
|
67
|
+
errors: {
|
|
68
|
+
400: z.ZodObject<{
|
|
69
|
+
error: z.ZodEnum<{
|
|
70
|
+
invalid_request_body: "invalid_request_body";
|
|
71
|
+
invalid_json_body: "invalid_json_body";
|
|
72
|
+
}>;
|
|
73
|
+
}, z.core.$loose>;
|
|
74
|
+
401: z.ZodObject<{
|
|
75
|
+
error: z.ZodLiteral<"invalid_token">;
|
|
76
|
+
}, z.core.$loose>;
|
|
77
|
+
403: z.ZodObject<{
|
|
78
|
+
error: z.ZodLiteral<"already_bootstrapped">;
|
|
79
|
+
}, z.core.$loose>;
|
|
80
|
+
404: z.ZodObject<{
|
|
81
|
+
error: z.ZodLiteral<"token_file_missing">;
|
|
82
|
+
}, z.core.$loose>;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=bootstrap_route_schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AActB,gFAAgF;AAChF,eAAO,MAAM,cAAc;;;;kBAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,iFAAiF;AACjF,eAAO,MAAM,eAAe;;;;;;;;;kBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiBI,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono-free wire shape + schemas for `POST /bootstrap`.
|
|
3
|
+
*
|
|
4
|
+
* Split from `bootstrap_routes.ts` so the route's declared shape (method,
|
|
5
|
+
* path, auth, input/output/error schemas) is importable **without** the
|
|
6
|
+
* hono-coupled handler (which sets a session cookie and reads the client
|
|
7
|
+
* IP off the Hono context). `create_bootstrap_route_specs` spreads
|
|
8
|
+
* `bootstrap_route_shape` and attaches the live handler; the test surface
|
|
9
|
+
* builder (`create_test_app_surface_spec`) spreads it with a stub handler so
|
|
10
|
+
* attack-surface generation reads the real shape without pulling the
|
|
11
|
+
* in-process Hono app onto cross-process consumers. Single source of truth —
|
|
12
|
+
* the shape can't drift between the live route and the surface.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
18
|
+
import { Username } from '../primitive_schemas.js';
|
|
19
|
+
import { Password } from './password.js';
|
|
20
|
+
import { ERROR_INVALID_TOKEN, ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING, ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, } from '../http/error_schemas.js';
|
|
21
|
+
/** Input for `POST /bootstrap`. `token` is the one-shot token file contents. */
|
|
22
|
+
export const BootstrapInput = z.strictObject({
|
|
23
|
+
token: z.string().min(1).meta({ sensitivity: 'secret' }),
|
|
24
|
+
username: Username,
|
|
25
|
+
password: Password,
|
|
26
|
+
});
|
|
27
|
+
/** Output for `POST /bootstrap`. Session cookie is the operative side effect. */
|
|
28
|
+
export const BootstrapOutput = z.strictObject({
|
|
29
|
+
ok: z.literal(true),
|
|
30
|
+
account: z.strictObject({ id: Uuid, username: Username }),
|
|
31
|
+
actor: z.strictObject({ id: Uuid }),
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* The `POST /bootstrap` route shape minus its handler — pure hono-free data.
|
|
35
|
+
* `create_bootstrap_route_specs` spreads this and attaches the live handler;
|
|
36
|
+
* surface generation spreads it with a stub handler (handlers are never run
|
|
37
|
+
* during surface assembly, only the shape is read).
|
|
38
|
+
*/
|
|
39
|
+
export const bootstrap_route_shape = {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
path: '/bootstrap',
|
|
42
|
+
auth: { account: 'none', actor: 'none' },
|
|
43
|
+
description: 'Create initial keeper account (one-shot)',
|
|
44
|
+
transaction: false, // bootstrap_account manages its own transaction
|
|
45
|
+
input: BootstrapInput,
|
|
46
|
+
output: BootstrapOutput,
|
|
47
|
+
rate_limit: 'ip',
|
|
48
|
+
errors: {
|
|
49
|
+
400: z.looseObject({
|
|
50
|
+
error: z.enum([ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY]),
|
|
51
|
+
}),
|
|
52
|
+
401: z.looseObject({ error: z.literal(ERROR_INVALID_TOKEN) }),
|
|
53
|
+
403: z.looseObject({ error: z.literal(ERROR_ALREADY_BOOTSTRAPPED) }),
|
|
54
|
+
404: z.looseObject({ error: z.literal(ERROR_TOKEN_FILE_MISSING) }),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import { z } from 'zod';
|
|
10
9
|
import type { Context } from 'hono';
|
|
11
10
|
import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
12
11
|
import type { SessionOptions } from './session_cookie.js';
|
|
@@ -16,25 +15,6 @@ import { type RouteSpec } from '../http/route_spec.js';
|
|
|
16
15
|
import { type RateLimiter } from '../rate_limiter.js';
|
|
17
16
|
import type { RouteFactoryDeps } from './deps.js';
|
|
18
17
|
import type { StatResult } from '../runtime/deps.js';
|
|
19
|
-
/** Input for `POST /bootstrap`. `token` is the one-shot token file contents. */
|
|
20
|
-
export declare const BootstrapInput: z.ZodObject<{
|
|
21
|
-
token: z.ZodString;
|
|
22
|
-
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
23
|
-
password: z.ZodString;
|
|
24
|
-
}, z.core.$strict>;
|
|
25
|
-
export type BootstrapInput = z.infer<typeof BootstrapInput>;
|
|
26
|
-
/** Output for `POST /bootstrap`. Session cookie is the operative side effect. */
|
|
27
|
-
export declare const BootstrapOutput: z.ZodObject<{
|
|
28
|
-
ok: z.ZodLiteral<true>;
|
|
29
|
-
account: z.ZodObject<{
|
|
30
|
-
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
31
|
-
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
32
|
-
}, z.core.$strict>;
|
|
33
|
-
actor: z.ZodObject<{
|
|
34
|
-
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
35
|
-
}, z.core.$strict>;
|
|
36
|
-
}, z.core.$strict>;
|
|
37
|
-
export type BootstrapOutput = z.infer<typeof BootstrapOutput>;
|
|
38
18
|
/**
|
|
39
19
|
* Bootstrap status — runtime state computed once at startup.
|
|
40
20
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAClC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAoB,KAAK,uBAAuB,EAAC,MAAM,wBAAwB,CAAC;AAEvF,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAGnD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,8EAA8E;IAC9E,gBAAgB,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,4EAA4E;IAC5E,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,EAAE,EAAE,EAAE,CAAC;IACP,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,wBAAwB,EAC9B,SAAS;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,KAClC,OAAO,CAAC,eAAe,CAwBzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,gBAAgB,EACtB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAmGjB,CAAC"}
|
|
@@ -6,29 +6,13 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import { z } from 'zod';
|
|
10
|
-
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
11
9
|
import { create_session_and_set_cookie } from './session_middleware.js';
|
|
12
10
|
import { bootstrap_account } from './bootstrap_account.js';
|
|
13
|
-
import {
|
|
14
|
-
import { Password } from './password.js';
|
|
11
|
+
import { bootstrap_route_shape } from './bootstrap_route_schema.js';
|
|
15
12
|
import { get_route_input } from '../http/route_spec.js';
|
|
16
|
-
import { get_client_ip } from '../http/
|
|
13
|
+
import { get_client_ip } from '../http/client_ip.js';
|
|
17
14
|
import { rate_limit_exceeded_response } from '../rate_limiter.js';
|
|
18
|
-
import {
|
|
19
|
-
// -- Input/output schemas ---------------------------------------------------
|
|
20
|
-
/** Input for `POST /bootstrap`. `token` is the one-shot token file contents. */
|
|
21
|
-
export const BootstrapInput = z.strictObject({
|
|
22
|
-
token: z.string().min(1).meta({ sensitivity: 'secret' }),
|
|
23
|
-
username: Username,
|
|
24
|
-
password: Password,
|
|
25
|
-
});
|
|
26
|
-
/** Output for `POST /bootstrap`. Session cookie is the operative side effect. */
|
|
27
|
-
export const BootstrapOutput = z.strictObject({
|
|
28
|
-
ok: z.literal(true),
|
|
29
|
-
account: z.strictObject({ id: Uuid, username: Username }),
|
|
30
|
-
actor: z.strictObject({ id: Uuid }),
|
|
31
|
-
});
|
|
15
|
+
import { ERROR_ALREADY_BOOTSTRAPPED } from '../http/error_schemas.js';
|
|
32
16
|
/**
|
|
33
17
|
* Check bootstrap availability at startup.
|
|
34
18
|
*
|
|
@@ -73,22 +57,7 @@ export const create_bootstrap_route_specs = (deps, options) => {
|
|
|
73
57
|
const { token_path } = bootstrap_status;
|
|
74
58
|
return [
|
|
75
59
|
{
|
|
76
|
-
|
|
77
|
-
path: '/bootstrap',
|
|
78
|
-
auth: { account: 'none', actor: 'none' },
|
|
79
|
-
description: 'Create initial keeper account (one-shot)',
|
|
80
|
-
transaction: false, // bootstrap_account manages its own transaction
|
|
81
|
-
input: BootstrapInput,
|
|
82
|
-
output: BootstrapOutput,
|
|
83
|
-
rate_limit: 'ip',
|
|
84
|
-
errors: {
|
|
85
|
-
400: z.looseObject({
|
|
86
|
-
error: z.enum([ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY]),
|
|
87
|
-
}),
|
|
88
|
-
401: z.looseObject({ error: z.literal(ERROR_INVALID_TOKEN) }),
|
|
89
|
-
403: z.looseObject({ error: z.literal(ERROR_ALREADY_BOOTSTRAPPED) }),
|
|
90
|
-
404: z.looseObject({ error: z.literal(ERROR_TOKEN_FILE_MISSING) }),
|
|
91
|
-
},
|
|
60
|
+
...bootstrap_route_shape,
|
|
92
61
|
handler: async (c, route) => {
|
|
93
62
|
// Short-circuit if bootstrap already completed or surface-only mounted.
|
|
94
63
|
// In 'surface_only' mode `bootstrap_status.token_path === null` and
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono-free wire schemas + route shape for `POST /signup`.
|
|
3
|
+
*
|
|
4
|
+
* Split from `signup_routes.ts` (whose handler pulls `hono/cookie` via
|
|
5
|
+
* `session_middleware` to set the new session cookie) so cross-process test
|
|
6
|
+
* suites can build the signup route shape — and assert on the response shape
|
|
7
|
+
* — without dragging the in-process Hono session handler, and its optional
|
|
8
|
+
* `hono` peer, onto a backend-spawning consumer. `signup_routes.ts` imports
|
|
9
|
+
* these back and attaches the live handler; single source of truth for the
|
|
10
|
+
* wire shape.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import type { RouteSpec } from '../http/route_spec.js';
|
|
16
|
+
/** Input for `POST /signup`. `email` is optional and must match any referenced invite. */
|
|
17
|
+
export declare const SignupInput: z.ZodObject<{
|
|
18
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
19
|
+
password: z.ZodString;
|
|
20
|
+
email: z.ZodOptional<z.ZodEmail>;
|
|
21
|
+
}, z.core.$strict>;
|
|
22
|
+
export type SignupInput = z.infer<typeof SignupInput>;
|
|
23
|
+
/**
|
|
24
|
+
* Output for `POST /signup`.
|
|
25
|
+
*
|
|
26
|
+
* Session cookie is the operative side effect. The returned `account` and
|
|
27
|
+
* `actor` mirror `BootstrapOutput` so cross-process per-test setup can read
|
|
28
|
+
* the per-test identity straight off the signup response.
|
|
29
|
+
*/
|
|
30
|
+
export declare const SignupOutput: z.ZodObject<{
|
|
31
|
+
ok: z.ZodLiteral<true>;
|
|
32
|
+
account: z.ZodObject<{
|
|
33
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
34
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
35
|
+
}, z.core.$strict>;
|
|
36
|
+
actor: z.ZodObject<{
|
|
37
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
38
|
+
}, z.core.$strict>;
|
|
39
|
+
}, z.core.$strict>;
|
|
40
|
+
export type SignupOutput = z.infer<typeof SignupOutput>;
|
|
41
|
+
/** Option inputs that shape the signup route metadata (not its handler). */
|
|
42
|
+
export interface SignupRouteShapeOptions {
|
|
43
|
+
/** Whether a per-account signup rate limiter is wired — toggles `rate_limit`. */
|
|
44
|
+
signup_account_rate_limited: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The `POST /signup` route shape minus its handler — pure hono-free data.
|
|
48
|
+
* `create_signup_route_specs` spreads this and attaches the live handler;
|
|
49
|
+
* cross-process surface builders spread it with a stub handler. Single source
|
|
50
|
+
* of truth — the shape can't drift between the live route and the surface.
|
|
51
|
+
*/
|
|
52
|
+
export declare const create_signup_route_shape: (options: SignupRouteShapeOptions) => Omit<RouteSpec, "handler">;
|
|
53
|
+
//# sourceMappingURL=signup_route_schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signup_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/signup_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAKtB,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAQrD,0FAA0F;AAC1F,eAAO,MAAM,WAAW;;;;kBAItB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;;;;;;;;;kBAIvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,4EAA4E;AAC5E,MAAM,WAAW,uBAAuB;IACvC,iFAAiF;IACjF,2BAA2B,EAAE,OAAO,CAAC;CACrC;AAED;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GACrC,SAAS,uBAAuB,KAC9B,IAAI,CAAC,SAAS,EAAE,SAAS,CAgB1B,CAAC"}
|