@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.
- 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 +146 -0
- package/dist/auth/account_route_schema.d.ts.map +1 -0
- package/dist/auth/account_route_schema.js +141 -0
- package/dist/auth/account_routes.d.ts +0 -79
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +15 -110
- 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/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/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/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/integration.js +2 -2
- 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 +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;
|
|
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"}
|
|
@@ -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;
|
|
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 {
|
|
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/
|
|
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
|
|
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
|
-
//
|
|
51
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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;
|
package/dist/http/CLAUDE.md
CHANGED
|
@@ -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"}
|