@fuzdev/fuz_app 0.87.0 → 0.88.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.
Files changed (69) hide show
  1. package/dist/actions/action_rpc.js +1 -1
  2. package/dist/actions/register_action_ws.js +1 -1
  3. package/dist/auth/CLAUDE.md +15 -0
  4. package/dist/auth/account_actions.js +1 -1
  5. package/dist/auth/account_route_schema.d.ts +146 -0
  6. package/dist/auth/account_route_schema.d.ts.map +1 -0
  7. package/dist/auth/account_route_schema.js +141 -0
  8. package/dist/auth/account_routes.d.ts +0 -79
  9. package/dist/auth/account_routes.d.ts.map +1 -1
  10. package/dist/auth/account_routes.js +15 -110
  11. package/dist/auth/audit_log_route_schema.d.ts +32 -0
  12. package/dist/auth/audit_log_route_schema.d.ts.map +1 -0
  13. package/dist/auth/audit_log_route_schema.js +36 -0
  14. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  15. package/dist/auth/audit_log_routes.js +2 -12
  16. package/dist/auth/bearer_auth.js +1 -1
  17. package/dist/auth/bootstrap_route_schema.d.ts +85 -0
  18. package/dist/auth/bootstrap_route_schema.d.ts.map +1 -0
  19. package/dist/auth/bootstrap_route_schema.js +56 -0
  20. package/dist/auth/bootstrap_routes.d.ts +0 -20
  21. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  22. package/dist/auth/bootstrap_routes.js +4 -35
  23. package/dist/auth/signup_route_schema.d.ts +53 -0
  24. package/dist/auth/signup_route_schema.d.ts.map +1 -0
  25. package/dist/auth/signup_route_schema.js +59 -0
  26. package/dist/auth/signup_routes.d.ts +0 -26
  27. package/dist/auth/signup_routes.d.ts.map +1 -1
  28. package/dist/auth/signup_routes.js +8 -40
  29. package/dist/http/CLAUDE.md +2 -1
  30. package/dist/http/client_ip.d.ts +15 -0
  31. package/dist/http/client_ip.d.ts.map +1 -0
  32. package/dist/http/client_ip.js +13 -0
  33. package/dist/http/proxy.d.ts +0 -7
  34. package/dist/http/proxy.d.ts.map +1 -1
  35. package/dist/http/proxy.js +0 -7
  36. package/dist/realtime/sse.d.ts +0 -2
  37. package/dist/realtime/sse.d.ts.map +1 -1
  38. package/dist/realtime/sse.js +1 -2
  39. package/dist/realtime/sse_constants.d.ts +17 -0
  40. package/dist/realtime/sse_constants.d.ts.map +1 -0
  41. package/dist/realtime/sse_constants.js +16 -0
  42. package/dist/testing/admin_integration.d.ts.map +1 -1
  43. package/dist/testing/admin_integration.js +1 -1
  44. package/dist/testing/app_server.d.ts +0 -15
  45. package/dist/testing/app_server.d.ts.map +1 -1
  46. package/dist/testing/app_server.js +1 -15
  47. package/dist/testing/audit_completeness.d.ts.map +1 -1
  48. package/dist/testing/audit_completeness.js +1 -1
  49. package/dist/testing/cross_backend/in_process_setup.d.ts +143 -0
  50. package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -0
  51. package/dist/testing/cross_backend/in_process_setup.js +166 -0
  52. package/dist/testing/cross_backend/setup.d.ts +31 -140
  53. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  54. package/dist/testing/cross_backend/setup.js +11 -171
  55. package/dist/testing/cross_backend/sse_round_trip.js +1 -1
  56. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  57. package/dist/testing/cross_backend/testing_reset_actions.js +2 -1
  58. package/dist/testing/integration.js +2 -2
  59. package/dist/testing/middleware.d.ts.map +1 -1
  60. package/dist/testing/middleware.js +2 -1
  61. package/dist/testing/sse_round_trip.d.ts +1 -1
  62. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  63. package/dist/testing/sse_round_trip.js +1 -1
  64. package/dist/testing/stubs.d.ts.map +1 -1
  65. package/dist/testing/stubs.js +7 -9
  66. package/dist/testing/test_credentials.d.ts +23 -0
  67. package/dist/testing/test_credentials.d.ts.map +1 -0
  68. package/dist/testing/test_credentials.js +22 -0
  69. package/package.json +2 -2
@@ -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;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAsB,KAAK,SAAS,EAAE,KAAK,eAAe,EAAC,MAAM,oBAAoB,CAAC;AAC7F,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oCAAoC,CAAC;AAQzE,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,CAiC5F,CAAC"}
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 { z } from 'zod';
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
- method: 'GET',
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
@@ -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/proxy.js';
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,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAClC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAoB,KAAK,uBAAuB,EAAC,MAAM,wBAAwB,CAAC;AAGvF,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;AAWnD,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;;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,CAkHjB,CAAC"}
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 { Username } from '../primitive_schemas.js';
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/proxy.js';
13
+ import { get_client_ip } from '../http/client_ip.js';
17
14
  import { rate_limit_exceeded_response } from '../rate_limiter.js';
18
- import { ERROR_INVALID_TOKEN, ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING, ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, } from '../http/error_schemas.js';
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
- method: 'POST',
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"}
@@ -0,0 +1,59 @@
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 { Uuid } from '@fuzdev/fuz_util/id.js';
16
+ import { Username, Email } from '../primitive_schemas.js';
17
+ import { Password } from './password.js';
18
+ import { ERROR_NO_MATCHING_INVITE, ERROR_SIGNUP_CONFLICT, ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, } from '../http/error_schemas.js';
19
+ /** Input for `POST /signup`. `email` is optional and must match any referenced invite. */
20
+ export const SignupInput = z.strictObject({
21
+ username: Username,
22
+ password: Password,
23
+ email: Email.optional(),
24
+ });
25
+ /**
26
+ * Output for `POST /signup`.
27
+ *
28
+ * Session cookie is the operative side effect. The returned `account` and
29
+ * `actor` mirror `BootstrapOutput` so cross-process per-test setup can read
30
+ * the per-test identity straight off the signup response.
31
+ */
32
+ export const SignupOutput = z.strictObject({
33
+ ok: z.literal(true),
34
+ account: z.strictObject({ id: Uuid, username: Username }),
35
+ actor: z.strictObject({ id: Uuid }),
36
+ });
37
+ /**
38
+ * The `POST /signup` route shape minus its handler — pure hono-free data.
39
+ * `create_signup_route_specs` spreads this and attaches the live handler;
40
+ * cross-process surface builders spread it with a stub handler. Single source
41
+ * of truth — the shape can't drift between the live route and the surface.
42
+ */
43
+ export const create_signup_route_shape = (options) => ({
44
+ method: 'POST',
45
+ path: '/signup',
46
+ auth: { account: 'none', actor: 'none' },
47
+ description: 'Create account (invite-gated or open signup)',
48
+ transaction: false, // manages its own transaction for TOCTOU safety
49
+ input: SignupInput,
50
+ output: SignupOutput,
51
+ rate_limit: options.signup_account_rate_limited ? 'both' : 'ip',
52
+ errors: {
53
+ 400: z.looseObject({
54
+ error: z.enum([ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY]),
55
+ }),
56
+ 403: z.looseObject({ error: z.literal(ERROR_NO_MATCHING_INVITE) }),
57
+ 409: z.looseObject({ error: z.literal(ERROR_SIGNUP_CONFLICT) }),
58
+ },
59
+ });
@@ -7,7 +7,6 @@
7
7
  *
8
8
  * @module
9
9
  */
10
- import { z } from 'zod';
11
10
  import { type RouteSpec } from '../http/route_spec.js';
12
11
  import { type RateLimiter } from '../rate_limiter.js';
13
12
  import type { RouteFactoryDeps } from './deps.js';
@@ -52,31 +51,6 @@ export interface SignupRouteOptions extends AuthSessionRouteOptions {
52
51
  */
53
52
  signup_fail_jitter_ms?: number;
54
53
  }
55
- /** Input for `POST /signup`. `email` is optional and must match any referenced invite. */
56
- export declare const SignupInput: z.ZodObject<{
57
- username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
58
- password: z.ZodString;
59
- email: z.ZodOptional<z.ZodEmail>;
60
- }, z.core.$strict>;
61
- export type SignupInput = z.infer<typeof SignupInput>;
62
- /**
63
- * Output for `POST /signup`.
64
- *
65
- * Session cookie is the operative side effect. The returned `account` and
66
- * `actor` mirror `BootstrapOutput` so cross-process per-test setup can read
67
- * the per-test identity straight off the signup response.
68
- */
69
- export declare const SignupOutput: z.ZodObject<{
70
- ok: z.ZodLiteral<true>;
71
- account: z.ZodObject<{
72
- id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
73
- username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
74
- }, z.core.$strict>;
75
- actor: z.ZodObject<{
76
- id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
77
- }, z.core.$strict>;
78
- }, z.core.$strict>;
79
- export type SignupOutput = z.infer<typeof SignupOutput>;
80
54
  /**
81
55
  * Create signup route specs for account creation.
82
56
  *
@@ -1 +1 @@
1
- {"version":3,"file":"signup_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/signup_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AActB,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;AAQhD,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,qBAAqB,CAAC;AAEjE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAQhD;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IAClE,6FAA6F;IAC7F,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAID,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;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,gBAAgB,EACtB,SAAS,kBAAkB,KACzB,KAAK,CAAC,SAAS,CAwLjB,CAAC"}
1
+ {"version":3,"file":"signup_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/signup_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,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;AAGhD,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,qBAAqB,CAAC;AAEjE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAQhD;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IAClE,6FAA6F;IAC7F,2BAA2B,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAID;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,gBAAgB,EACtB,SAAS,kBAAkB,KACzB,KAAK,CAAC,SAAS,CA4KjB,CAAC"}
@@ -7,18 +7,15 @@
7
7
  *
8
8
  * @module
9
9
  */
10
- import { z } from 'zod';
11
- import { Uuid } from '@fuzdev/fuz_util/id.js';
12
10
  import { create_session_and_set_cookie } from './session_middleware.js';
13
11
  import { query_create_account_with_actor } from './account_queries.js';
14
12
  import { query_app_settings_load } from './app_settings_queries.js';
15
13
  import { query_invite_find_unclaimed_match_for_update, query_invite_claim_unscoped, } from './invite_queries.js';
16
- import { Username, Email } from '../primitive_schemas.js';
17
- import { Password } from './password.js';
14
+ import { create_signup_route_shape } from './signup_route_schema.js';
18
15
  import { get_route_input } from '../http/route_spec.js';
19
- import { get_client_ip } from '../http/proxy.js';
16
+ import { get_client_ip } from '../http/client_ip.js';
20
17
  import { rate_limit_exceeded_response } from '../rate_limiter.js';
21
- import { ERROR_NO_MATCHING_INVITE, ERROR_SIGNUP_CONFLICT, ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, } from '../http/error_schemas.js';
18
+ import { ERROR_NO_MATCHING_INVITE, ERROR_SIGNUP_CONFLICT } from '../http/error_schemas.js';
22
19
  import { is_pg_unique_violation } from '../db/pg_error.js';
23
20
  /**
24
21
  * Default minimum wall-clock time (ms) for a signup denial (403 / 409) response.
@@ -47,25 +44,8 @@ const signup_fail_delay = (floor_ms, jitter_ms) => {
47
44
  const jitter = jitter_ms > 0 ? Math.floor(Math.random() * (jitter_ms * 2 + 1)) - jitter_ms : 0;
48
45
  return new Promise((resolve) => setTimeout(resolve, floor_ms + jitter));
49
46
  };
50
- // -- Input/output schemas ---------------------------------------------------
51
- /** Input for `POST /signup`. `email` is optional and must match any referenced invite. */
52
- export const SignupInput = z.strictObject({
53
- username: Username,
54
- password: Password,
55
- email: Email.optional(),
56
- });
57
- /**
58
- * Output for `POST /signup`.
59
- *
60
- * Session cookie is the operative side effect. The returned `account` and
61
- * `actor` mirror `BootstrapOutput` so cross-process per-test setup can read
62
- * the per-test identity straight off the signup response.
63
- */
64
- export const SignupOutput = z.strictObject({
65
- ok: z.literal(true),
66
- account: z.strictObject({ id: Uuid, username: Username }),
67
- actor: z.strictObject({ id: Uuid }),
68
- });
47
+ // `create_signup_route_specs` spreads the shape and attaches the live handler
48
+ // below.
69
49
  /**
70
50
  * Create signup route specs for account creation.
71
51
  *
@@ -78,21 +58,9 @@ export const create_signup_route_specs = (deps, options) => {
78
58
  const { session_options, ip_rate_limiter, signup_account_rate_limiter, signup_fail_floor_ms = DEFAULT_SIGNUP_FAIL_FLOOR_MS, signup_fail_jitter_ms = DEFAULT_SIGNUP_FAIL_JITTER_MS, } = options;
79
59
  return [
80
60
  {
81
- method: 'POST',
82
- path: '/signup',
83
- auth: { account: 'none', actor: 'none' },
84
- description: 'Create account (invite-gated or open signup)',
85
- transaction: false, // manages its own transaction for TOCTOU safety
86
- input: SignupInput,
87
- output: SignupOutput,
88
- rate_limit: signup_account_rate_limiter ? 'both' : 'ip',
89
- errors: {
90
- 400: z.looseObject({
91
- error: z.enum([ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY]),
92
- }),
93
- 403: z.looseObject({ error: z.literal(ERROR_NO_MATCHING_INVITE) }),
94
- 409: z.looseObject({ error: z.literal(ERROR_SIGNUP_CONFLICT) }),
95
- },
61
+ ...create_signup_route_shape({
62
+ signup_account_rate_limited: signup_account_rate_limiter !== null,
63
+ }),
96
64
  handler: async (c, route) => {
97
65
  // Per-IP rate limit check (before any work). 429 stays fast.
98
66
  const ip = ip_rate_limiter ? get_client_ip(c) : null;
@@ -21,7 +21,8 @@ effects, see ../../../docs/architecture.md.
21
21
  - `http/middleware_spec.ts` — `MiddlewareSpec` interface.
22
22
  - `http/surface.ts` — `AppSurface`, `AppSurfaceSpec`, `generate_app_surface`, diagnostics.
23
23
  - `http/surface_query.ts` — pure filters/groupings over `AppSurface`.
24
- - `http/proxy.ts` — trusted-proxy middleware, CIDR parsing, rightmost-first XFF resolution.
24
+ - `http/proxy.ts` — trusted-proxy middleware, CIDR parsing, rightmost-first XFF resolution. Pulls `hono/utils/ipaddr` for CIDR matching.
25
+ - `http/client_ip.ts` — `get_client_ip(c)` — reads the resolved IP off the context (set by `proxy.ts`'s middleware). Split out of `proxy.ts` so it's hono-free: dispatch + route modules that only _read_ the IP don't pull `hono/utils/ipaddr`, keeping cross-process test surfaces free of the optional `hono` peer.
25
26
  - `http/ip_canonical.ts` — RFC 5952 IPv6 canonicalization + IPv4-mapped collapse; `IP_LITERAL_CHARS` regex.
26
27
  - `http/origin.ts` — origin allowlist middleware with wildcard patterns (Origin-only).
27
28
  - `http/jsonrpc.ts` — JSON-RPC 2.0 envelope schemas (MCP superset), `JsonrpcErrorCode`, `_meta`.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Read the resolved client IP off the Hono context.
3
+ *
4
+ * Split from `proxy.ts` (which pulls `hono/utils/ipaddr` for trusted-proxy
5
+ * CIDR matching) so dispatch + route modules that only need to *read* the IP
6
+ * stay free of that value import — and the cross-process test surface that
7
+ * imports them stays free of the optional `hono` peer. The IP itself is set on
8
+ * the context by the trusted-proxy middleware in `proxy.ts`.
9
+ *
10
+ * @module
11
+ */
12
+ import type { Context } from 'hono';
13
+ /** Client IP resolved by the trusted-proxy middleware, or `'unknown'` if unset. */
14
+ export declare const get_client_ip: (c: Context) => string;
15
+ //# sourceMappingURL=client_ip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client_ip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/client_ip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAElC,mFAAmF;AACnF,eAAO,MAAM,aAAa,GAAI,GAAG,OAAO,KAAG,MAAyC,CAAC"}