@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
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { z } from 'zod';
|
|
15
15
|
import { DEV } from 'esm-env';
|
|
16
16
|
import {} from '../http/route_spec.js';
|
|
17
|
-
import { get_client_ip } from '../http/
|
|
17
|
+
import { get_client_ip } from '../http/client_ip.js';
|
|
18
18
|
import { get_request_context, } from '../auth/request_context.js';
|
|
19
19
|
import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY, } from '../hono_context.js';
|
|
20
20
|
import { compile_action_registry } from './compile_action_registry.js';
|
|
@@ -30,7 +30,7 @@ import { wait } from '@fuzdev/fuz_util/async.js';
|
|
|
30
30
|
import { Logger } from '@fuzdev/fuz_util/log.js';
|
|
31
31
|
import { get_request_context, require_request_context, } from '../auth/request_context.js';
|
|
32
32
|
import { hash_session_token } from '../auth/session_queries.js';
|
|
33
|
-
import { get_client_ip } from '../http/
|
|
33
|
+
import { get_client_ip } from '../http/client_ip.js';
|
|
34
34
|
import { flush_pending_effects, flush_post_commit_effects } from '../http/pending_effects.js';
|
|
35
35
|
import { jsonrpc_error_messages } from '../http/jsonrpc_errors.js';
|
|
36
36
|
import { create_jsonrpc_error_response, create_jsonrpc_notification, to_jsonrpc_message_id, to_jsonrpc_params, is_jsonrpc_request, } from '../http/jsonrpc_helpers.js';
|
package/dist/auth/CLAUDE.md
CHANGED
|
@@ -121,6 +121,21 @@ they track the same config. Sample via `get_*`; `reset_*` are test-only.
|
|
|
121
121
|
- `auth/audit_log_routes.ts` — optional `GET /audit/stream` (SSE); list/history are on the RPC surface.
|
|
122
122
|
- `auth/auth_guard_resolver.ts` — `fuz_auth_guard_resolver` injected into `apply_route_specs` so the framework stays auth-agnostic.
|
|
123
123
|
|
|
124
|
+
**Hono-free route shapes.** Each cookie/SSE-coupled route module has a sibling
|
|
125
|
+
`*_route_schema.ts` (`account_route_schema.ts`, `signup_route_schema.ts`,
|
|
126
|
+
`audit_log_route_schema.ts`, `bootstrap_route_schema.ts`) holding the I/O
|
|
127
|
+
schemas **and** the route _shapes_ (`Omit<RouteSpec, 'handler'>` —
|
|
128
|
+
method/path/auth/io/errors, via `create_*_route_shapes(options)` or a static
|
|
129
|
+
`*_route_shape` const). The `create_*_route_specs` factories spread a shape and
|
|
130
|
+
attach the live (hono-coupled) handler; a cross-process surface builder spreads
|
|
131
|
+
the same shape with a stub handler (surface generation never runs handlers).
|
|
132
|
+
Single source of truth — the shape can't drift between the live route and the
|
|
133
|
+
attack surface — and a backend-spawning consumer assembling its surface imports
|
|
134
|
+
the shapes without dragging `hono/cookie` / `hono/streaming` (and the optional
|
|
135
|
+
`hono` peer) onto a Rust-only cross-process suite. Shared route-limit constants
|
|
136
|
+
(`DEFAULT_MAX_SESSIONS` / `_TOKENS`) live in `account_route_schema.ts` for the
|
|
137
|
+
same reason (the RPC `account_actions` reads `DEFAULT_MAX_TOKENS`).
|
|
138
|
+
|
|
124
139
|
**`POST /login` timing floor.** Login 401s are floored to
|
|
125
140
|
`DEFAULT_LOGIN_FAIL_FLOOR_MS` (250ms) + uniform jitter (±25ms) via
|
|
126
141
|
`Promise.all(work, setTimeout)` so observed time is `max(work, delay)` and
|
|
@@ -27,7 +27,7 @@ import { to_session_account } from './account_schema.js';
|
|
|
27
27
|
import { query_session_list_for_account, query_session_revoke_for_account, query_session_revoke_all_for_account, } from './session_queries.js';
|
|
28
28
|
import { query_api_token_enforce_limit, query_api_token_list_for_account, query_create_api_token, query_revoke_api_token_for_account, } from './api_token_queries.js';
|
|
29
29
|
import { generate_api_token } from './api_token.js';
|
|
30
|
-
import { DEFAULT_MAX_TOKENS } from './
|
|
30
|
+
import { DEFAULT_MAX_TOKENS } from './account_route_schema.js';
|
|
31
31
|
import { account_verify_action_spec, account_session_list_action_spec, account_session_revoke_action_spec, account_session_revoke_all_action_spec, account_token_create_action_spec, account_token_list_action_spec, account_token_revoke_action_spec, } from './account_action_specs.js';
|
|
32
32
|
/**
|
|
33
33
|
* Create the self-service account RPC actions.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono-free wire schemas + route shapes for the account REST routes.
|
|
3
|
+
*
|
|
4
|
+
* Split from `account_routes.ts` (whose handlers pull `hono/cookie` via
|
|
5
|
+
* `session_middleware`) so cross-process test suites can build the account
|
|
6
|
+
* route shapes — and assert on the `POST /login` / `GET /api/account/status`
|
|
7
|
+
* response shapes — without dragging the in-process Hono session handler, and
|
|
8
|
+
* its optional `hono` peer, onto a backend-spawning consumer. `account_routes.ts`
|
|
9
|
+
* imports these back and attaches the live handlers; single source of truth
|
|
10
|
+
* for the wire shape.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import type { RouteSpec } from '../http/route_spec.js';
|
|
16
|
+
/** Input for `GET /api/account/status`. No parameters — caller is the subject. */
|
|
17
|
+
export declare const AccountStatusInput: z.ZodNull;
|
|
18
|
+
export type AccountStatusInput = z.infer<typeof AccountStatusInput>;
|
|
19
|
+
/** Output for `GET /api/account/status`. */
|
|
20
|
+
export declare const AccountStatusOutput: z.ZodObject<{
|
|
21
|
+
account: z.ZodObject<{
|
|
22
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
23
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
24
|
+
email: z.ZodNullable<z.ZodEmail>;
|
|
25
|
+
email_verified: z.ZodBoolean;
|
|
26
|
+
created_at: z.ZodString;
|
|
27
|
+
}, z.core.$strict>;
|
|
28
|
+
actor: z.ZodNullable<z.ZodObject<{
|
|
29
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
30
|
+
name: z.ZodString;
|
|
31
|
+
}, z.core.$strict>>;
|
|
32
|
+
role_grants: z.ZodArray<z.ZodObject<{
|
|
33
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
34
|
+
role: z.ZodString;
|
|
35
|
+
scope_kind: z.ZodNullable<z.ZodString>;
|
|
36
|
+
scope_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
37
|
+
created_at: z.ZodString;
|
|
38
|
+
expires_at: z.ZodNullable<z.ZodString>;
|
|
39
|
+
granted_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
40
|
+
}, z.core.$strict>>;
|
|
41
|
+
}, z.core.$strict>;
|
|
42
|
+
export type AccountStatusOutput = z.infer<typeof AccountStatusOutput>;
|
|
43
|
+
/** Error body for `GET /api/account/status` on the unauthenticated path. */
|
|
44
|
+
export declare const AccountStatusUnauthenticatedError: z.ZodObject<{
|
|
45
|
+
error: z.ZodLiteral<"authentication_required">;
|
|
46
|
+
bootstrap_available: z.ZodOptional<z.ZodBoolean>;
|
|
47
|
+
}, z.core.$loose>;
|
|
48
|
+
export type AccountStatusUnauthenticatedError = z.infer<typeof AccountStatusUnauthenticatedError>;
|
|
49
|
+
/** Input for `POST /login`. Accepts a username or email in the `username` field. */
|
|
50
|
+
export declare const LoginInput: z.ZodObject<{
|
|
51
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
52
|
+
password: z.ZodString;
|
|
53
|
+
}, z.core.$strict>;
|
|
54
|
+
export type LoginInput = z.infer<typeof LoginInput>;
|
|
55
|
+
/** Output for `POST /login`. Session cookie is the operative side effect. */
|
|
56
|
+
export declare const LoginOutput: z.ZodObject<{
|
|
57
|
+
ok: z.ZodLiteral<true>;
|
|
58
|
+
}, z.core.$strict>;
|
|
59
|
+
export type LoginOutput = z.infer<typeof LoginOutput>;
|
|
60
|
+
/** Input for `POST /logout`. Session identity flows through the cookie. */
|
|
61
|
+
export declare const LogoutInput: z.ZodNull;
|
|
62
|
+
export type LogoutInput = z.infer<typeof LogoutInput>;
|
|
63
|
+
/** Output for `POST /logout`. Includes the revoked account's username for UI redraw. */
|
|
64
|
+
export declare const LogoutOutput: z.ZodObject<{
|
|
65
|
+
ok: z.ZodLiteral<true>;
|
|
66
|
+
username: z.ZodString;
|
|
67
|
+
}, z.core.$strict>;
|
|
68
|
+
export type LogoutOutput = z.infer<typeof LogoutOutput>;
|
|
69
|
+
/** Input for `POST /password`. `current_password` is minimally validated; `new_password` enforces the full policy. */
|
|
70
|
+
export declare const PasswordChangeInput: z.ZodObject<{
|
|
71
|
+
current_password: z.ZodString;
|
|
72
|
+
new_password: z.ZodString;
|
|
73
|
+
}, z.core.$strict>;
|
|
74
|
+
export type PasswordChangeInput = z.infer<typeof PasswordChangeInput>;
|
|
75
|
+
/** Output for `POST /password`. Counts are returned so the UI can summarize the revoke-all cascade. */
|
|
76
|
+
export declare const PasswordChangeOutput: z.ZodObject<{
|
|
77
|
+
ok: z.ZodLiteral<true>;
|
|
78
|
+
sessions_revoked: z.ZodNumber;
|
|
79
|
+
tokens_revoked: z.ZodNumber;
|
|
80
|
+
}, z.core.$strict>;
|
|
81
|
+
export type PasswordChangeOutput = z.infer<typeof PasswordChangeOutput>;
|
|
82
|
+
/** Default maximum sessions per account. */
|
|
83
|
+
export declare const DEFAULT_MAX_SESSIONS = 5;
|
|
84
|
+
/** Default maximum API tokens per account. */
|
|
85
|
+
export declare const DEFAULT_MAX_TOKENS = 10;
|
|
86
|
+
/**
|
|
87
|
+
* The `GET /status` route shape minus its handler — pure hono-free data.
|
|
88
|
+
* `create_account_status_route_spec` spreads this and attaches the live handler
|
|
89
|
+
* (which reads the account id off the request context); surface generation
|
|
90
|
+
* spreads it with a stub handler.
|
|
91
|
+
*
|
|
92
|
+
* The path is **relative** like the sibling account shapes (`/login`,
|
|
93
|
+
* `/verify`), so it composes under `prefix_route_specs('/api/account', …)` into
|
|
94
|
+
* `/api/account/status`. `create_account_route_specs` bundles it (so every
|
|
95
|
+
* account surface serves `/status`, matching the Rust `account_router`);
|
|
96
|
+
* mirror Rust by mounting it as part of the account family, not separately.
|
|
97
|
+
*/
|
|
98
|
+
export declare const account_status_route_shape: {
|
|
99
|
+
method: "GET";
|
|
100
|
+
path: string;
|
|
101
|
+
auth: {
|
|
102
|
+
account: "none";
|
|
103
|
+
actor: "none";
|
|
104
|
+
};
|
|
105
|
+
description: string;
|
|
106
|
+
input: z.ZodNull;
|
|
107
|
+
output: z.ZodObject<{
|
|
108
|
+
account: z.ZodObject<{
|
|
109
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
110
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
111
|
+
email: z.ZodNullable<z.ZodEmail>;
|
|
112
|
+
email_verified: z.ZodBoolean;
|
|
113
|
+
created_at: z.ZodString;
|
|
114
|
+
}, z.core.$strict>;
|
|
115
|
+
actor: z.ZodNullable<z.ZodObject<{
|
|
116
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
117
|
+
name: z.ZodString;
|
|
118
|
+
}, z.core.$strict>>;
|
|
119
|
+
role_grants: z.ZodArray<z.ZodObject<{
|
|
120
|
+
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
121
|
+
role: z.ZodString;
|
|
122
|
+
scope_kind: z.ZodNullable<z.ZodString>;
|
|
123
|
+
scope_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
124
|
+
created_at: z.ZodString;
|
|
125
|
+
expires_at: z.ZodNullable<z.ZodString>;
|
|
126
|
+
granted_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
127
|
+
}, z.core.$strict>>;
|
|
128
|
+
}, z.core.$strict>;
|
|
129
|
+
errors: {
|
|
130
|
+
401: z.ZodObject<{
|
|
131
|
+
error: z.ZodLiteral<"authentication_required">;
|
|
132
|
+
bootstrap_available: z.ZodOptional<z.ZodBoolean>;
|
|
133
|
+
}, z.core.$loose>;
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
/** Option inputs that shape the account route metadata (not its handlers). */
|
|
137
|
+
export interface AccountRouteShapeOptions {
|
|
138
|
+
/** Whether a per-account login rate limiter is wired — toggles `/password`'s `rate_limit`. */
|
|
139
|
+
login_account_rate_limited: boolean;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The four account route shapes (`/verify`, `/login`, `/logout`, `/password`)
|
|
143
|
+
* minus their handlers — pure hono-free data. `create_account_route_specs`
|
|
144
|
+
* spreads each and attaches the live handler; cross-process surface builders
|
|
145
|
+
* spread them with stub handlers. Single source of truth — the shapes can't
|
|
146
|
+
* drift between the live routes and the surface.
|
|
147
|
+
*
|
|
148
|
+
* Returns a fixed 4-tuple `[verify, login, logout, password]` so destructuring
|
|
149
|
+
* yields non-optional shapes under `noUncheckedIndexedAccess`.
|
|
150
|
+
*/
|
|
151
|
+
export declare const create_account_route_shapes: (options: AccountRouteShapeOptions) => [Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">, Omit<RouteSpec, "handler">];
|
|
152
|
+
//# sourceMappingURL=account_route_schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_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,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,4CAA4C;AAC5C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;kBAI9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,4EAA4E;AAC5E,eAAO,MAAM,iCAAiC;;;iBAG5C,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAElG,oFAAoF;AACpF,eAAO,MAAM,UAAU;;;kBAGrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,eAAO,MAAM,WAAW;;kBAEtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,2EAA2E;AAC3E,eAAO,MAAM,WAAW,WAAW,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,wFAAwF;AACxF,eAAO,MAAM,YAAY;;;kBAGvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,sHAAsH;AACtH,eAAO,MAAM,mBAAmB;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,uGAAuG;AACvG,eAAO,MAAM,oBAAoB;;;;kBAI/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,4CAA4C;AAC5C,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAUD,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACxC,8FAA8F;IAC9F,0BAA0B,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,wBAAwB,KAC/B,CACF,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAoD1B,CAAC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hono-free wire schemas + route shapes for the account REST routes.
|
|
3
|
+
*
|
|
4
|
+
* Split from `account_routes.ts` (whose handlers pull `hono/cookie` via
|
|
5
|
+
* `session_middleware`) so cross-process test suites can build the account
|
|
6
|
+
* route shapes — and assert on the `POST /login` / `GET /api/account/status`
|
|
7
|
+
* response shapes — without dragging the in-process Hono session handler, and
|
|
8
|
+
* its optional `hono` peer, onto a backend-spawning consumer. `account_routes.ts`
|
|
9
|
+
* imports these back and attaches the live handlers; single source of truth
|
|
10
|
+
* for the wire shape.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { ActorSummaryJson, RoleGrantSummaryJson, SessionAccountJson } from './account_schema.js';
|
|
16
|
+
import { UsernameProvided } from '../primitive_schemas.js';
|
|
17
|
+
import { Password, PasswordProvided } from './password.js';
|
|
18
|
+
import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INVALID_CREDENTIALS, ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, } from '../http/error_schemas.js';
|
|
19
|
+
/** Input for `GET /api/account/status`. No parameters — caller is the subject. */
|
|
20
|
+
export const AccountStatusInput = z.null();
|
|
21
|
+
/** Output for `GET /api/account/status`. */
|
|
22
|
+
export const AccountStatusOutput = z.strictObject({
|
|
23
|
+
account: SessionAccountJson,
|
|
24
|
+
actor: ActorSummaryJson.nullable(),
|
|
25
|
+
role_grants: z.array(RoleGrantSummaryJson),
|
|
26
|
+
});
|
|
27
|
+
/** Error body for `GET /api/account/status` on the unauthenticated path. */
|
|
28
|
+
export const AccountStatusUnauthenticatedError = z.looseObject({
|
|
29
|
+
error: z.literal(ERROR_AUTHENTICATION_REQUIRED),
|
|
30
|
+
bootstrap_available: z.boolean().optional(),
|
|
31
|
+
});
|
|
32
|
+
/** Input for `POST /login`. Accepts a username or email in the `username` field. */
|
|
33
|
+
export const LoginInput = z.strictObject({
|
|
34
|
+
username: UsernameProvided,
|
|
35
|
+
password: PasswordProvided,
|
|
36
|
+
});
|
|
37
|
+
/** Output for `POST /login`. Session cookie is the operative side effect. */
|
|
38
|
+
export const LoginOutput = z.strictObject({
|
|
39
|
+
ok: z.literal(true),
|
|
40
|
+
});
|
|
41
|
+
/** Input for `POST /logout`. Session identity flows through the cookie. */
|
|
42
|
+
export const LogoutInput = z.null();
|
|
43
|
+
/** Output for `POST /logout`. Includes the revoked account's username for UI redraw. */
|
|
44
|
+
export const LogoutOutput = z.strictObject({
|
|
45
|
+
ok: z.literal(true),
|
|
46
|
+
username: z.string(),
|
|
47
|
+
});
|
|
48
|
+
/** Input for `POST /password`. `current_password` is minimally validated; `new_password` enforces the full policy. */
|
|
49
|
+
export const PasswordChangeInput = z.strictObject({
|
|
50
|
+
current_password: PasswordProvided,
|
|
51
|
+
new_password: Password,
|
|
52
|
+
});
|
|
53
|
+
/** Output for `POST /password`. Counts are returned so the UI can summarize the revoke-all cascade. */
|
|
54
|
+
export const PasswordChangeOutput = z.strictObject({
|
|
55
|
+
ok: z.literal(true),
|
|
56
|
+
sessions_revoked: z.number(),
|
|
57
|
+
tokens_revoked: z.number(),
|
|
58
|
+
});
|
|
59
|
+
/** Default maximum sessions per account. */
|
|
60
|
+
export const DEFAULT_MAX_SESSIONS = 5;
|
|
61
|
+
/** Default maximum API tokens per account. */
|
|
62
|
+
export const DEFAULT_MAX_TOKENS = 10;
|
|
63
|
+
/**
|
|
64
|
+
* The `GET /status` route shape minus its handler — pure hono-free data.
|
|
65
|
+
* `create_account_status_route_spec` spreads this and attaches the live handler
|
|
66
|
+
* (which reads the account id off the request context); surface generation
|
|
67
|
+
* spreads it with a stub handler.
|
|
68
|
+
*
|
|
69
|
+
* The path is **relative** like the sibling account shapes (`/login`,
|
|
70
|
+
* `/verify`), so it composes under `prefix_route_specs('/api/account', …)` into
|
|
71
|
+
* `/api/account/status`. `create_account_route_specs` bundles it (so every
|
|
72
|
+
* account surface serves `/status`, matching the Rust `account_router`);
|
|
73
|
+
* mirror Rust by mounting it as part of the account family, not separately.
|
|
74
|
+
*/
|
|
75
|
+
export const account_status_route_shape = {
|
|
76
|
+
method: 'GET',
|
|
77
|
+
path: '/status',
|
|
78
|
+
auth: { account: 'none', actor: 'none' },
|
|
79
|
+
description: 'Current account info (unauthenticated: 401 with bootstrap status)',
|
|
80
|
+
input: AccountStatusInput,
|
|
81
|
+
output: AccountStatusOutput,
|
|
82
|
+
errors: {
|
|
83
|
+
401: AccountStatusUnauthenticatedError,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* The four account route shapes (`/verify`, `/login`, `/logout`, `/password`)
|
|
88
|
+
* minus their handlers — pure hono-free data. `create_account_route_specs`
|
|
89
|
+
* spreads each and attaches the live handler; cross-process surface builders
|
|
90
|
+
* spread them with stub handlers. Single source of truth — the shapes can't
|
|
91
|
+
* drift between the live routes and the surface.
|
|
92
|
+
*
|
|
93
|
+
* Returns a fixed 4-tuple `[verify, login, logout, password]` so destructuring
|
|
94
|
+
* yields non-optional shapes under `noUncheckedIndexedAccess`.
|
|
95
|
+
*/
|
|
96
|
+
export const create_account_route_shapes = (options) => [
|
|
97
|
+
{
|
|
98
|
+
method: 'GET',
|
|
99
|
+
path: '/verify',
|
|
100
|
+
auth: { account: 'required', actor: 'none' },
|
|
101
|
+
description: 'Session-validity probe for nginx auth_request (empty body, 200 or 401)',
|
|
102
|
+
input: z.null(),
|
|
103
|
+
output: z.null(),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
method: 'POST',
|
|
107
|
+
path: '/login',
|
|
108
|
+
auth: { account: 'none', actor: 'none' },
|
|
109
|
+
description: 'Exchange credentials for session',
|
|
110
|
+
input: LoginInput,
|
|
111
|
+
output: LoginOutput,
|
|
112
|
+
rate_limit: 'both',
|
|
113
|
+
errors: {
|
|
114
|
+
400: z.looseObject({
|
|
115
|
+
error: z.enum([ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY]),
|
|
116
|
+
}),
|
|
117
|
+
401: z.looseObject({ error: z.literal(ERROR_INVALID_CREDENTIALS) }),
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
method: 'POST',
|
|
122
|
+
path: '/logout',
|
|
123
|
+
// `credential_types: ['session']` — see `docs/security.md` §Credential-channel gating.
|
|
124
|
+
// Logout is a session-bound operation; a bearer / daemon token holds no session
|
|
125
|
+
// to end, so the dispatcher rejects it (403 `credential_type_required`) rather than
|
|
126
|
+
// returning a misleading 200 + a phantom `logout` audit row for a no-op.
|
|
127
|
+
auth: { account: 'required', actor: 'none', credential_types: ['session'] },
|
|
128
|
+
description: 'Revoke current session and clear cookie',
|
|
129
|
+
input: LogoutInput,
|
|
130
|
+
output: LogoutOutput,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
method: 'POST',
|
|
134
|
+
path: '/password',
|
|
135
|
+
// `credential_types: ['session']` — see `docs/security.md` §Credential-channel gating.
|
|
136
|
+
auth: { account: 'required', actor: 'none', credential_types: ['session'] },
|
|
137
|
+
description: 'Change password (revokes all sessions and API tokens)',
|
|
138
|
+
input: PasswordChangeInput,
|
|
139
|
+
output: PasswordChangeOutput,
|
|
140
|
+
rate_limit: options.login_account_rate_limited ? 'both' : 'ip',
|
|
141
|
+
errors: {
|
|
142
|
+
400: z.looseObject({
|
|
143
|
+
error: z.enum([ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY]),
|
|
144
|
+
}),
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
];
|
|
@@ -21,53 +21,11 @@
|
|
|
21
21
|
*
|
|
22
22
|
* @module
|
|
23
23
|
*/
|
|
24
|
-
import { z } from 'zod';
|
|
25
24
|
import type { SessionOptions } from './session_cookie.js';
|
|
26
25
|
import { type RouteSpec } from '../http/route_spec.js';
|
|
27
26
|
import { type RateLimiter } from '../rate_limiter.js';
|
|
28
27
|
import type { RouteFactoryDeps } from './deps.js';
|
|
29
28
|
import type { ConnectionCloser } from '../actions/connection_closer.js';
|
|
30
|
-
/** Input for `GET /api/account/status`. No parameters — caller is the subject. */
|
|
31
|
-
export declare const AccountStatusInput: z.ZodNull;
|
|
32
|
-
export type AccountStatusInput = z.infer<typeof AccountStatusInput>;
|
|
33
|
-
/**
|
|
34
|
-
* Output for `GET /api/account/status` on the authenticated path.
|
|
35
|
-
*
|
|
36
|
-
* `account` is always populated for authenticated callers. `actor` and
|
|
37
|
-
* `role_grants` are populated when the caller's account has a unique actor or
|
|
38
|
-
* the request supplies `?acting=<actor_id>`; on multi-actor accounts
|
|
39
|
-
* without an `acting` query, `actor` is `null` and `role_grants` is empty so
|
|
40
|
-
* the frontend can show a persona picker without a separate roundtrip.
|
|
41
|
-
*/
|
|
42
|
-
export declare const AccountStatusOutput: z.ZodObject<{
|
|
43
|
-
account: z.ZodObject<{
|
|
44
|
-
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
45
|
-
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
46
|
-
email: z.ZodNullable<z.ZodEmail>;
|
|
47
|
-
email_verified: z.ZodBoolean;
|
|
48
|
-
created_at: z.ZodString;
|
|
49
|
-
}, z.core.$strict>;
|
|
50
|
-
actor: z.ZodNullable<z.ZodObject<{
|
|
51
|
-
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
52
|
-
name: z.ZodString;
|
|
53
|
-
}, z.core.$strict>>;
|
|
54
|
-
role_grants: z.ZodArray<z.ZodObject<{
|
|
55
|
-
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
56
|
-
role: z.ZodString;
|
|
57
|
-
scope_kind: z.ZodNullable<z.ZodString>;
|
|
58
|
-
scope_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
59
|
-
created_at: z.ZodString;
|
|
60
|
-
expires_at: z.ZodNullable<z.ZodString>;
|
|
61
|
-
granted_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
62
|
-
}, z.core.$strict>>;
|
|
63
|
-
}, z.core.$strict>;
|
|
64
|
-
export type AccountStatusOutput = z.infer<typeof AccountStatusOutput>;
|
|
65
|
-
/** Error body for `GET /api/account/status` on the unauthenticated path. */
|
|
66
|
-
export declare const AccountStatusUnauthenticatedError: z.ZodObject<{
|
|
67
|
-
error: z.ZodLiteral<"authentication_required">;
|
|
68
|
-
bootstrap_available: z.ZodOptional<z.ZodBoolean>;
|
|
69
|
-
}, z.core.$loose>;
|
|
70
|
-
export type AccountStatusUnauthenticatedError = z.infer<typeof AccountStatusUnauthenticatedError>;
|
|
71
29
|
/**
|
|
72
30
|
* Create the account status route spec.
|
|
73
31
|
*
|
|
@@ -91,10 +49,6 @@ export interface AccountStatusOptions {
|
|
|
91
49
|
available: boolean;
|
|
92
50
|
};
|
|
93
51
|
}
|
|
94
|
-
/** Default maximum sessions per account. */
|
|
95
|
-
export declare const DEFAULT_MAX_SESSIONS = 5;
|
|
96
|
-
/** Default maximum API tokens per account. */
|
|
97
|
-
export declare const DEFAULT_MAX_TOKENS = 10;
|
|
98
52
|
/**
|
|
99
53
|
* Default minimum wall-clock time (ms) for a login failure (401) response.
|
|
100
54
|
*
|
|
@@ -153,49 +107,30 @@ export interface AccountRouteOptions extends AuthSessionRouteOptions {
|
|
|
153
107
|
* `audit.on_event_chain`) runs.
|
|
154
108
|
*/
|
|
155
109
|
connection_closer?: ConnectionCloser | null;
|
|
110
|
+
/**
|
|
111
|
+
* Runtime bootstrap status for the bundled `GET /status` route — when
|
|
112
|
+
* `available`, its unauthenticated 401 carries `bootstrap_available: true`
|
|
113
|
+
* so a fresh frontend can route to the bootstrap flow. Pass
|
|
114
|
+
* `ctx.bootstrap_status` (the live `BootstrapStatus` ref) so the flag tracks
|
|
115
|
+
* the one-shot bootstrap completing. Omit when no bootstrap flow is wired —
|
|
116
|
+
* `/status` is still served, just without the flag.
|
|
117
|
+
*/
|
|
118
|
+
bootstrap_status?: {
|
|
119
|
+
available: boolean;
|
|
120
|
+
};
|
|
156
121
|
}
|
|
157
|
-
/** Input for `POST /login`. Accepts a username or email in the `username` field. */
|
|
158
|
-
export declare const LoginInput: z.ZodObject<{
|
|
159
|
-
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
160
|
-
password: z.ZodString;
|
|
161
|
-
}, z.core.$strict>;
|
|
162
|
-
export type LoginInput = z.infer<typeof LoginInput>;
|
|
163
|
-
/** Output for `POST /login`. The signed session cookie is the operative side effect. */
|
|
164
|
-
export declare const LoginOutput: z.ZodObject<{
|
|
165
|
-
ok: z.ZodLiteral<true>;
|
|
166
|
-
}, z.core.$strict>;
|
|
167
|
-
export type LoginOutput = z.infer<typeof LoginOutput>;
|
|
168
|
-
/** Input for `POST /logout`. Session identity flows through the cookie. */
|
|
169
|
-
export declare const LogoutInput: z.ZodNull;
|
|
170
|
-
export type LogoutInput = z.infer<typeof LogoutInput>;
|
|
171
|
-
/** Output for `POST /logout`. Includes the revoked account's username for UI redraw. */
|
|
172
|
-
export declare const LogoutOutput: z.ZodObject<{
|
|
173
|
-
ok: z.ZodLiteral<true>;
|
|
174
|
-
username: z.ZodString;
|
|
175
|
-
}, z.core.$strict>;
|
|
176
|
-
export type LogoutOutput = z.infer<typeof LogoutOutput>;
|
|
177
|
-
/** Input for `POST /password`. `current_password` is minimally validated; `new_password` enforces the full policy. */
|
|
178
|
-
export declare const PasswordChangeInput: z.ZodObject<{
|
|
179
|
-
current_password: z.ZodString;
|
|
180
|
-
new_password: z.ZodString;
|
|
181
|
-
}, z.core.$strict>;
|
|
182
|
-
export type PasswordChangeInput = z.infer<typeof PasswordChangeInput>;
|
|
183
|
-
/** Output for `POST /password`. Counts are returned so the UI can summarize the revoke-all cascade. */
|
|
184
|
-
export declare const PasswordChangeOutput: z.ZodObject<{
|
|
185
|
-
ok: z.ZodLiteral<true>;
|
|
186
|
-
sessions_revoked: z.ZodNumber;
|
|
187
|
-
tokens_revoked: z.ZodNumber;
|
|
188
|
-
}, z.core.$strict>;
|
|
189
|
-
export type PasswordChangeOutput = z.infer<typeof PasswordChangeOutput>;
|
|
190
122
|
/**
|
|
191
123
|
* Create account route specs for session-based auth.
|
|
192
124
|
*
|
|
193
|
-
* The returned specs cover the
|
|
194
|
-
*
|
|
195
|
-
*
|
|
125
|
+
* The returned specs cover the REST flows that stay after the RPC migration:
|
|
126
|
+
* `/status` (account info + bootstrap availability), `/verify` (nginx
|
|
127
|
+
* `auth_request` shim), `/login`, `/logout`, `/password`. `/status` is bundled
|
|
128
|
+
* here (relative path, prefixed to `/api/account/status` by the caller) so
|
|
129
|
+
* every account surface serves it, matching the Rust `account_router`.
|
|
130
|
+
* Self-service session/token management is on `auth/account_actions.ts`.
|
|
196
131
|
*
|
|
197
132
|
* @param deps - stateless capabilities (keyring, password, log)
|
|
198
|
-
* @param options - per-factory configuration (session_options, ip_rate_limiter, login_account_rate_limiter)
|
|
133
|
+
* @param options - per-factory configuration (session_options, ip_rate_limiter, login_account_rate_limiter, bootstrap_status)
|
|
199
134
|
* @returns route specs (not yet applied to Hono)
|
|
200
135
|
*/
|
|
201
136
|
export declare const create_account_route_specs: (deps: RouteFactoryDeps, options: AccountRouteOptions) => Array<RouteSpec>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AA4BxD,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,gBAAgB,EAAC,MAAM,iCAAiC,CAAC;AAGtE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gCAAgC,GAAI,UAAU,oBAAoB,KAAG,SA4EhF,CAAC;AAEH,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACpC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE;QAAC,SAAS,EAAE,OAAO,CAAA;KAAC,CAAC;CACxC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAQ/C;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACvC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kFAAkF;IAClF,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,uBAAuB;IACnE,4FAA4F;IAC5F,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC5C;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE;QAAC,SAAS,EAAE,OAAO,CAAA;KAAC,CAAC;CACxC;AAID;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,gBAAgB,EACtB,SAAS,mBAAmB,KAC1B,KAAK,CAAC,SAAS,CAwRjB,CAAC"}
|