@fuzdev/fuz_app 0.61.0 → 0.63.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/CLAUDE.md +43 -35
- package/dist/actions/action_rpc.d.ts +10 -0
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +1 -1
- package/dist/actions/action_spec.d.ts +1 -1
- package/dist/actions/action_spec.js +1 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +1 -0
- package/dist/auth/CLAUDE.md +47 -26
- package/dist/auth/account_action_specs.d.ts +6 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +11 -4
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +9 -4
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +8 -4
- package/dist/auth/account_schema.d.ts +2 -2
- package/dist/auth/account_schema.js +2 -2
- package/dist/auth/actor_lookup_actions.d.ts +1 -1
- package/dist/auth/actor_lookup_actions.js +1 -1
- package/dist/auth/actor_lookup_queries.d.ts +1 -1
- package/dist/auth/actor_lookup_queries.js +1 -1
- package/dist/auth/actor_search_action_specs.d.ts +1 -1
- package/dist/auth/actor_search_action_specs.js +1 -1
- package/dist/auth/actor_search_actions.d.ts +1 -1
- package/dist/auth/actor_search_actions.js +1 -1
- package/dist/auth/actor_search_queries.d.ts +1 -1
- package/dist/auth/actor_search_queries.js +1 -1
- package/dist/auth/all_action_spec_registries.d.ts +2 -2
- package/dist/auth/all_action_spec_registries.js +2 -2
- package/dist/auth/audit_log_routes.d.ts +1 -1
- package/dist/auth/audit_log_routes.js +1 -1
- package/dist/auth/audit_log_schema.d.ts +25 -0
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +16 -0
- package/dist/auth/request_context.d.ts +1 -1
- package/dist/env/update_env_variable.js +1 -1
- package/dist/http/CLAUDE.md +15 -15
- package/dist/server/app_server.d.ts +54 -6
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +32 -4
- package/dist/testing/CLAUDE.md +34 -50
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +17 -1
- package/dist/ui/CLAUDE.md +13 -18
- package/dist/ui/keyed_async_slot.svelte.d.ts +1 -1
- package/dist/ui/keyed_async_slot.svelte.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,7 +29,7 @@ import { hash_session_token, query_session_revoke_all_for_account, query_session
|
|
|
29
29
|
import { query_account_by_username_or_email, query_update_account_password, } from './account_queries.js';
|
|
30
30
|
import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
|
|
31
31
|
import { build_account_context, build_request_context, get_request_context, require_request_context, resolve_acting_actor, } from './request_context.js';
|
|
32
|
-
import { ACCOUNT_ID_KEY } from '../hono_context.js';
|
|
32
|
+
import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY } from '../hono_context.js';
|
|
33
33
|
import { get_route_input } from '../http/route_spec.js';
|
|
34
34
|
import { get_client_ip } from '../http/proxy.js';
|
|
35
35
|
import { rate_limit_exceeded_response } from '../rate_limiter.js';
|
|
@@ -355,7 +355,8 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
355
355
|
{
|
|
356
356
|
method: 'POST',
|
|
357
357
|
path: '/password',
|
|
358
|
-
|
|
358
|
+
// `credential_types: ['session']` — see `docs/security.md` §Credential-channel gating.
|
|
359
|
+
auth: { account: 'required', actor: 'none', credential_types: ['session'] },
|
|
359
360
|
description: 'Change password (revokes all sessions and API tokens)',
|
|
360
361
|
input: PasswordChangeInput,
|
|
361
362
|
output: PasswordChangeOutput,
|
|
@@ -376,6 +377,8 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
376
377
|
}
|
|
377
378
|
const ctx = require_request_context(c);
|
|
378
379
|
const { current_password, new_password } = get_route_input(c);
|
|
380
|
+
// Defense in depth — see `docs/security.md` §Credential-channel gating.
|
|
381
|
+
const credential_type = c.get(CREDENTIAL_TYPE_KEY) ?? undefined;
|
|
379
382
|
// per-account rate limit check (after context resolution, before argon2 work)
|
|
380
383
|
if (login_account_rate_limiter) {
|
|
381
384
|
const check = login_account_rate_limiter.check(ctx.account.id);
|
|
@@ -394,6 +397,7 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
394
397
|
outcome: 'failure',
|
|
395
398
|
account_id: ctx.account.id,
|
|
396
399
|
ip: get_client_ip(c),
|
|
400
|
+
metadata: { credential_type },
|
|
397
401
|
});
|
|
398
402
|
return c.json({ error: ERROR_INVALID_CREDENTIALS }, 401);
|
|
399
403
|
}
|
|
@@ -426,7 +430,7 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
426
430
|
outcome: 'failure',
|
|
427
431
|
account_id: ctx.account.id,
|
|
428
432
|
ip: get_client_ip(c),
|
|
429
|
-
metadata: { reason: 'concurrent_change' },
|
|
433
|
+
metadata: { reason: 'concurrent_change', credential_type },
|
|
430
434
|
});
|
|
431
435
|
return c.json({ error: ERROR_INVALID_CREDENTIALS }, 401);
|
|
432
436
|
}
|
|
@@ -442,7 +446,7 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
442
446
|
event_type: 'password_change',
|
|
443
447
|
account_id: ctx.account.id,
|
|
444
448
|
ip: get_client_ip(c),
|
|
445
|
-
metadata: { sessions_revoked, tokens_revoked },
|
|
449
|
+
metadata: { sessions_revoked, tokens_revoked, credential_type },
|
|
446
450
|
});
|
|
447
451
|
return c.json({ ok: true, sessions_revoked, tokens_revoked });
|
|
448
452
|
},
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* `Account`, `Actor`, `RoleGrant`, `AuthSession`, and `ApiToken`.
|
|
6
6
|
*
|
|
7
7
|
* Identifier primitives (`Username`, `UsernameProvided`, `Email`) live
|
|
8
|
-
* in
|
|
8
|
+
* in `primitive_schemas.ts` — they're general validator shapes that
|
|
9
9
|
* don't depend on the auth domain. The auth-shape request-contract
|
|
10
|
-
* primitive `ActingActor` lives in
|
|
10
|
+
* primitive `ActingActor` lives in `http/auth_shape.ts` next to
|
|
11
11
|
* `RouteAuth` (the two pair: `auth.actor !== 'none'` ⟺ input declares
|
|
12
12
|
* `acting?: ActingActor`).
|
|
13
13
|
*
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* `Account`, `Actor`, `RoleGrant`, `AuthSession`, and `ApiToken`.
|
|
6
6
|
*
|
|
7
7
|
* Identifier primitives (`Username`, `UsernameProvided`, `Email`) live
|
|
8
|
-
* in
|
|
8
|
+
* in `primitive_schemas.ts` — they're general validator shapes that
|
|
9
9
|
* don't depend on the auth domain. The auth-shape request-contract
|
|
10
|
-
* primitive `ActingActor` lives in
|
|
10
|
+
* primitive `ActingActor` lives in `http/auth_shape.ts` next to
|
|
11
11
|
* `RouteAuth` (the two pair: `auth.actor !== 'none'` ⟺ input declares
|
|
12
12
|
* `acting?: ActingActor`).
|
|
13
13
|
*
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Pure read — no audit, no side effects. Auth (`account: 'required'`) +
|
|
5
5
|
* rate-limit (`account`-grain) enforced at the spec layer; see
|
|
6
|
-
*
|
|
6
|
+
* `auth/actor_lookup_action_specs.ts` for the info-leak audit.
|
|
7
7
|
*
|
|
8
8
|
* `display_name` is omitted (not `null`) when `actor.name` is blank,
|
|
9
9
|
* matching the wire shape `display_name?` so the typed client sees an
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Pure read — no audit, no side effects. Auth (`account: 'required'`) +
|
|
5
5
|
* rate-limit (`account`-grain) enforced at the spec layer; see
|
|
6
|
-
*
|
|
6
|
+
* `auth/actor_lookup_action_specs.ts` for the info-leak audit.
|
|
7
7
|
*
|
|
8
8
|
* `display_name` is omitted (not `null`) when `actor.name` is blank,
|
|
9
9
|
* matching the wire shape `display_name?` so the typed client sees an
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* The inner join still resolves one row per actor — `actor.account_id`
|
|
11
11
|
* is `NOT NULL` so every actor has exactly one account.
|
|
12
12
|
*
|
|
13
|
-
* Info-leak posture (see `actor_lookup_action_specs.ts` §
|
|
13
|
+
* Info-leak posture (see `actor_lookup_action_specs.ts` §audit):
|
|
14
14
|
*
|
|
15
15
|
* - Row shape **omits** `account_id` — the join is control-plane,
|
|
16
16
|
* not wire-visible.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* The inner join still resolves one row per actor — `actor.account_id`
|
|
11
11
|
* is `NOT NULL` so every actor has exactly one account.
|
|
12
12
|
*
|
|
13
|
-
* Info-leak posture (see `actor_lookup_action_specs.ts` §
|
|
13
|
+
* Info-leak posture (see `actor_lookup_action_specs.ts` §audit):
|
|
14
14
|
*
|
|
15
15
|
* - Row shape **omits** `account_id` — the join is control-plane,
|
|
16
16
|
* not wire-visible.
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
* ## Wire shape — info-leak audit
|
|
46
46
|
*
|
|
47
47
|
* Output `{actors: [{id, username, display_name?}]}` is identical to
|
|
48
|
-
* `actor_lookup`'s — see
|
|
48
|
+
* `actor_lookup`'s — see `auth/actor_lookup_action_specs.ts` for the full
|
|
49
49
|
* field-by-field audit. Same omissions (`account_id`, email,
|
|
50
50
|
* timestamps, role / role_grants / session state), same `display_name`
|
|
51
51
|
* omitted-not-null contract, same response-order-unspecified rule.
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
* ## Wire shape — info-leak audit
|
|
46
46
|
*
|
|
47
47
|
* Output `{actors: [{id, username, display_name?}]}` is identical to
|
|
48
|
-
* `actor_lookup`'s — see
|
|
48
|
+
* `actor_lookup`'s — see `auth/actor_lookup_action_specs.ts` for the full
|
|
49
49
|
* field-by-field audit. Same omissions (`account_id`, email,
|
|
50
50
|
* timestamps, role / role_grants / session state), same `display_name`
|
|
51
51
|
* omitted-not-null contract, same response-order-unspecified rule.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Pure read — no audit, no side effects. Auth (`account: 'required'`,
|
|
5
5
|
* `actor: 'none'`) + rate-limit (`account`-grain) enforced at the spec
|
|
6
|
-
* layer; see
|
|
6
|
+
* layer; see `auth/actor_search_action_specs.ts` for the info-leak audit
|
|
7
7
|
* and threat model.
|
|
8
8
|
*
|
|
9
9
|
* The handler adds two checks the spec layer can't express:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Pure read — no audit, no side effects. Auth (`account: 'required'`,
|
|
5
5
|
* `actor: 'none'`) + rate-limit (`account`-grain) enforced at the spec
|
|
6
|
-
* layer; see
|
|
6
|
+
* layer; see `auth/actor_search_action_specs.ts` for the info-leak audit
|
|
7
7
|
* and threat model.
|
|
8
8
|
*
|
|
9
9
|
* The handler adds two checks the spec layer can't express:
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* gates), no role_grant join — every actor with a matching prefix is
|
|
30
30
|
* returned.
|
|
31
31
|
*
|
|
32
|
-
* ## Info-leak posture (see `actor_search_action_specs.ts` §
|
|
32
|
+
* ## Info-leak posture (see `actor_search_action_specs.ts` §audit)
|
|
33
33
|
*
|
|
34
34
|
* - Row shape **omits** `account_id` — the join is control-plane, not
|
|
35
35
|
* wire-visible. Identical to `actor_lookup_queries.ts`.
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* gates), no role_grant join — every actor with a matching prefix is
|
|
30
30
|
* returned.
|
|
31
31
|
*
|
|
32
|
-
* ## Info-leak posture (see `actor_search_action_specs.ts` §
|
|
32
|
+
* ## Info-leak posture (see `actor_search_action_specs.ts` §audit)
|
|
33
33
|
*
|
|
34
34
|
* - Row shape **omits** `account_id` — the join is control-plane, not
|
|
35
35
|
* wire-visible. Identical to `actor_lookup_queries.ts`.
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
* - Codegen that needs to see every fuz_auth surface at once
|
|
23
23
|
* (typed-client filters, attack-surface reports). For typed-client
|
|
24
24
|
* wiring of the standard surface, prefer `all_standard_action_specs`
|
|
25
|
-
* in
|
|
25
|
+
* in `auth/standard_action_specs.ts` — it mirrors the
|
|
26
26
|
* `create_standard_rpc_actions` mount and stays narrower than this
|
|
27
27
|
* registry-of-registries (no opt-in bundles).
|
|
28
28
|
*
|
|
29
29
|
* `protocol_action_specs` (heartbeat / cancel) is **not** included —
|
|
30
30
|
* those are transport-level wire-protocol concerns shipped by fuz_app
|
|
31
31
|
* and spread by every consumer at registration via `protocol_actions`
|
|
32
|
-
* from
|
|
32
|
+
* from `actions/protocol.ts`. Walker tests that need protocol
|
|
33
33
|
* coverage spread `protocol_action_specs` separately.
|
|
34
34
|
*
|
|
35
35
|
* @module
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
* - Codegen that needs to see every fuz_auth surface at once
|
|
23
23
|
* (typed-client filters, attack-surface reports). For typed-client
|
|
24
24
|
* wiring of the standard surface, prefer `all_standard_action_specs`
|
|
25
|
-
* in
|
|
25
|
+
* in `auth/standard_action_specs.ts` — it mirrors the
|
|
26
26
|
* `create_standard_rpc_actions` mount and stays narrower than this
|
|
27
27
|
* registry-of-registries (no opt-in bundles).
|
|
28
28
|
*
|
|
29
29
|
* `protocol_action_specs` (heartbeat / cancel) is **not** included —
|
|
30
30
|
* those are transport-level wire-protocol concerns shipped by fuz_app
|
|
31
31
|
* and spread by every consumer at registration via `protocol_actions`
|
|
32
|
-
* from
|
|
32
|
+
* from `actions/protocol.ts`. Walker tests that need protocol
|
|
33
33
|
* coverage spread `protocol_action_specs` separately.
|
|
34
34
|
*
|
|
35
35
|
* @module
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `GET /audit/stream` SSE route — streams aren't an action-kind, so they
|
|
8
8
|
* stay on REST. The event payload broadcast on the stream surfaces via
|
|
9
9
|
* `audit_log_event_specs` (one `EventSpec` per audit event type) declared
|
|
10
|
-
* alongside the broadcaster in
|
|
10
|
+
* alongside the broadcaster in `realtime/sse_auth_guard.ts`.
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `GET /audit/stream` SSE route — streams aren't an action-kind, so they
|
|
8
8
|
* stay on REST. The event payload broadcast on the stream surfaces via
|
|
9
9
|
* `audit_log_event_specs` (one `EventSpec` per audit event type) declared
|
|
10
|
-
* alongside the broadcaster in
|
|
10
|
+
* alongside the broadcaster in `realtime/sse_auth_guard.ts`.
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
@@ -85,21 +85,46 @@ export declare const audit_metadata_schemas: Readonly<{
|
|
|
85
85
|
reason: z.ZodOptional<z.ZodEnum<{
|
|
86
86
|
concurrent_change: "concurrent_change";
|
|
87
87
|
}>>;
|
|
88
|
+
credential_type: z.ZodOptional<z.ZodEnum<{
|
|
89
|
+
daemon_token: "daemon_token";
|
|
90
|
+
session: "session";
|
|
91
|
+
api_token: "api_token";
|
|
92
|
+
}>>;
|
|
88
93
|
}, z.core.$loose>>;
|
|
89
94
|
session_revoke: z.ZodObject<{
|
|
90
95
|
session_id: z.ZodString;
|
|
96
|
+
credential_type: z.ZodOptional<z.ZodEnum<{
|
|
97
|
+
daemon_token: "daemon_token";
|
|
98
|
+
session: "session";
|
|
99
|
+
api_token: "api_token";
|
|
100
|
+
}>>;
|
|
91
101
|
}, z.core.$loose>;
|
|
92
102
|
session_revoke_all: z.ZodObject<{
|
|
93
103
|
count: z.ZodOptional<z.ZodNumber>;
|
|
94
104
|
reason: z.ZodOptional<z.ZodString>;
|
|
95
105
|
attempted_account_id: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
106
|
+
credential_type: z.ZodOptional<z.ZodEnum<{
|
|
107
|
+
daemon_token: "daemon_token";
|
|
108
|
+
session: "session";
|
|
109
|
+
api_token: "api_token";
|
|
110
|
+
}>>;
|
|
96
111
|
}, z.core.$loose>;
|
|
97
112
|
token_create: z.ZodObject<{
|
|
98
113
|
token_id: z.ZodString;
|
|
99
114
|
name: z.ZodString;
|
|
115
|
+
credential_type: z.ZodOptional<z.ZodEnum<{
|
|
116
|
+
daemon_token: "daemon_token";
|
|
117
|
+
session: "session";
|
|
118
|
+
api_token: "api_token";
|
|
119
|
+
}>>;
|
|
100
120
|
}, z.core.$loose>;
|
|
101
121
|
token_revoke: z.ZodObject<{
|
|
102
122
|
token_id: z.ZodString;
|
|
123
|
+
credential_type: z.ZodOptional<z.ZodEnum<{
|
|
124
|
+
daemon_token: "daemon_token";
|
|
125
|
+
session: "session";
|
|
126
|
+
api_token: "api_token";
|
|
127
|
+
}>>;
|
|
103
128
|
}, z.core.$loose>;
|
|
104
129
|
token_revoke_all: z.ZodObject<{
|
|
105
130
|
count: z.ZodOptional<z.ZodNumber>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,17 @@ import { Blake3Hash } from '@fuzdev/fuz_util/hash_blake3.js';
|
|
|
14
14
|
import { AuthSessionJson } from './account_schema.js';
|
|
15
15
|
import { Email } from '../primitive_schemas.js';
|
|
16
16
|
import { ApiTokenId } from './api_token.js';
|
|
17
|
+
import { BuiltinCredentialType } from './credential_type_schema.js';
|
|
18
|
+
/**
|
|
19
|
+
* Defense-in-depth audit field — records the credential channel
|
|
20
|
+
* (`session` / `api_token` / `daemon_token`) the request arrived on.
|
|
21
|
+
* Present on events whose specs declare `credential_types: ['session']`
|
|
22
|
+
* so forensics survive a future loosening or bypass of the spec gate.
|
|
23
|
+
* See `docs/security.md` §Credential-channel gating.
|
|
24
|
+
*/
|
|
25
|
+
const credential_type_meta = BuiltinCredentialType.optional().meta({
|
|
26
|
+
description: 'Credential channel the request arrived on. Defense in depth — the spec gate restricts to `session`, but the row preserves what actually authenticated the request in case the gate is loosened or bypassed in a future refactor.',
|
|
27
|
+
});
|
|
17
28
|
/**
|
|
18
29
|
* All tracked auth event types. Frozen to convert accidental in-process
|
|
19
30
|
* mutation (test cross-contamination, cast escapes) into loud TypeErrors.
|
|
@@ -102,10 +113,12 @@ export const audit_metadata_schemas = Object.freeze({
|
|
|
102
113
|
reason: z.enum(['concurrent_change']).optional().meta({
|
|
103
114
|
description: 'Failure category. `concurrent_change` indicates another password change committed first against the same starting hash (verify-write race loser). Absent for typed-wrong-password failures.',
|
|
104
115
|
}),
|
|
116
|
+
credential_type: credential_type_meta,
|
|
105
117
|
})
|
|
106
118
|
.nullable(),
|
|
107
119
|
session_revoke: z.looseObject({
|
|
108
120
|
session_id: Blake3Hash.meta({ description: 'Blake3 hash identifying the revoked session row.' }),
|
|
121
|
+
credential_type: credential_type_meta,
|
|
109
122
|
}),
|
|
110
123
|
session_revoke_all: z.looseObject({
|
|
111
124
|
// Omitted on `outcome='failure'` (no revocation attempted — e.g. target
|
|
@@ -122,13 +135,16 @@ export const audit_metadata_schemas = Object.freeze({
|
|
|
122
135
|
attempted_account_id: Uuid.optional().meta({
|
|
123
136
|
description: 'Probed account id when the target lookup missed (FK constraint forces `target_account_id` to null).',
|
|
124
137
|
}),
|
|
138
|
+
credential_type: credential_type_meta,
|
|
125
139
|
}),
|
|
126
140
|
token_create: z.looseObject({
|
|
127
141
|
token_id: ApiTokenId.meta({ description: 'Public id of the created API token (`tok_…`).' }),
|
|
128
142
|
name: z.string().meta({ description: 'Operator-supplied label for the token.' }),
|
|
143
|
+
credential_type: credential_type_meta,
|
|
129
144
|
}),
|
|
130
145
|
token_revoke: z.looseObject({
|
|
131
146
|
token_id: ApiTokenId.meta({ description: 'Public id of the revoked API token (`tok_…`).' }),
|
|
147
|
+
credential_type: credential_type_meta,
|
|
132
148
|
}),
|
|
133
149
|
token_revoke_all: z.looseObject({
|
|
134
150
|
// Same shape as `session_revoke_all` for failures.
|
|
@@ -340,7 +340,7 @@ export declare const build_account_context: (deps: QueryDeps, account_id: string
|
|
|
340
340
|
*
|
|
341
341
|
* The auth phase deliberately stops short of constructing a `Response` so
|
|
342
342
|
* the same failure flows through every transport without the auth-domain
|
|
343
|
-
* code knowing about JSON-RPC. See
|
|
343
|
+
* code knowing about JSON-RPC. See `../../../CLAUDE.md` §Cleanest
|
|
344
344
|
* architecture takes priority for the rationale.
|
|
345
345
|
*/
|
|
346
346
|
export type AuthorizationFailureBody = {
|
|
@@ -70,7 +70,7 @@ export const update_env_variable = async (key, value, options) => {
|
|
|
70
70
|
const updated_content = updated_lines.join('\n') + (has_trailing_newline ? '\n' : '');
|
|
71
71
|
await write_file(file_path, updated_content, 'utf-8');
|
|
72
72
|
};
|
|
73
|
-
// Keep this tokenization aligned with `parse_dotenv` in
|
|
73
|
+
// Keep this tokenization aligned with `parse_dotenv` in `env/dotenv.ts`:
|
|
74
74
|
// trim, skip empties/comments, split on the first `=`.
|
|
75
75
|
const find_last_key_line_index = (lines, key) => {
|
|
76
76
|
if (!key)
|
package/dist/http/CLAUDE.md
CHANGED
|
@@ -5,12 +5,12 @@ surface introspection, JSON-RPC envelope + error taxonomy, proxy/origin
|
|
|
5
5
|
middleware primitives, post-commit effect helper, generic admin route specs.
|
|
6
6
|
|
|
7
7
|
**Nothing in this directory is auth-specific.** Auth middleware, routes, and
|
|
8
|
-
guards live in
|
|
8
|
+
guards live in `auth/` and consume these primitives. Routes and actions in
|
|
9
9
|
other domains should do the same — extend, don't special-case.
|
|
10
10
|
|
|
11
11
|
For the design rationale behind declarative routes, DEV-only output
|
|
12
12
|
validation, the three-layer error-schema merge, and fire-and-forget effects,
|
|
13
|
-
see
|
|
13
|
+
see ../../docs/architecture.md.
|
|
14
14
|
|
|
15
15
|
## Module Map
|
|
16
16
|
|
|
@@ -42,7 +42,7 @@ by `generate_app_surface`. Same-shaped data, different consumers.
|
|
|
42
42
|
|
|
43
43
|
- `method` — `'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'`
|
|
44
44
|
- `path` — Hono path (supports `:param` segments)
|
|
45
|
-
- `auth: RouteAuth` — flat record `{account, actor, roles?, credential_types?}` from `auth_shape.ts`. Each axis is `'none' | 'optional' | 'required'`. Same shape governs `ActionSpec.auth` (see
|
|
45
|
+
- `auth: RouteAuth` — flat record `{account, actor, roles?, credential_types?}` from `auth_shape.ts`. Each axis is `'none' | 'optional' | 'required'`. Same shape governs `ActionSpec.auth` (see `actions/CLAUDE.md`).
|
|
46
46
|
- `handler: RouteHandler` — `(c: Context, route: RouteContext) => Response | Promise<Response>`
|
|
47
47
|
- `description` — free-text, surfaced in `AppSurface`
|
|
48
48
|
- `params?: z.ZodObject` — strict-object schema for URL path params
|
|
@@ -84,7 +84,7 @@ interface RouteContext {
|
|
|
84
84
|
`db.transaction`.
|
|
85
85
|
|
|
86
86
|
Pool-level fire-and-forget writes (audit logs, etc.) run through the bound
|
|
87
|
-
`AppDeps.audit` capability — see
|
|
87
|
+
`AppDeps.audit` capability — see `auth/CLAUDE.md` §Deps. Handlers that
|
|
88
88
|
need rollback-resilient writes call `deps.audit.emit(route, input)`, which
|
|
89
89
|
captures the pool inside the bound emitter so the row lands even when
|
|
90
90
|
the handler's transaction rolls back.
|
|
@@ -98,7 +98,7 @@ the handler's transaction rolls back.
|
|
|
98
98
|
|
|
99
99
|
Override explicitly when a mutation route must manage its own transactions
|
|
100
100
|
(e.g. signup, which does a multi-step flow that can't live inside a single
|
|
101
|
-
wrapper). See
|
|
101
|
+
wrapper). See `auth/signup_routes.ts`.
|
|
102
102
|
|
|
103
103
|
### Validation pipeline (per-route middleware order)
|
|
104
104
|
|
|
@@ -114,7 +114,7 @@ wrapper). See `../auth/signup_routes.ts`.
|
|
|
114
114
|
or `auth.actor === 'required'`. Fires before any body parsing so
|
|
115
115
|
unauthenticated callers never see route-shape information from
|
|
116
116
|
input parse failures. The `AuthGuardResolver` (e.g.
|
|
117
|
-
`fuz_auth_guard_resolver` from
|
|
117
|
+
`fuz_auth_guard_resolver` from `auth/auth_guard_resolver.ts`) returns
|
|
118
118
|
this set as `pre_validation: Array<MiddlewareHandler>`.
|
|
119
119
|
4. **Input validation** — JSON body parsed + validated; mismatch returns
|
|
120
120
|
400 `ERROR_INVALID_JSON_BODY` (not JSON) or `ERROR_INVALID_REQUEST_BODY`
|
|
@@ -191,7 +191,7 @@ are the contract with external callers.
|
|
|
191
191
|
|
|
192
192
|
The production behavior short-circuits to the unwrapped handler — no
|
|
193
193
|
parse work on the hot path. Uniform across all three action-handler
|
|
194
|
-
surfaces (REST, RPC, WS); see
|
|
194
|
+
surfaces (REST, RPC, WS); see ../../docs/architecture.md §DEV-only
|
|
195
195
|
Output Validation.
|
|
196
196
|
|
|
197
197
|
### Helpers
|
|
@@ -205,8 +205,8 @@ Output Validation.
|
|
|
205
205
|
|
|
206
206
|
`error_schemas.ts` is the **declarative** error surface:
|
|
207
207
|
|
|
208
|
-
- `ERROR_*`
|
|
209
|
-
`.literal(
|
|
208
|
+
- `ERROR_*` `snake_case` string constants — single source of truth; use
|
|
209
|
+
`.literal(ERROR_*)` in Zod schemas and inline checks in handlers
|
|
210
210
|
- `ApiError`, `ValidationError`, `PermissionError`,
|
|
211
211
|
`CredentialTypeRequiredError`, `RateLimitError`, `PayloadTooLargeError`,
|
|
212
212
|
`ForeignKeyError` — standard shapes
|
|
@@ -413,7 +413,7 @@ connection is from a configured trusted proxy. Without this middleware,
|
|
|
413
413
|
`get_client_ip(c)` returns `'unknown'`.
|
|
414
414
|
|
|
415
415
|
Must run **before** auth and rate-limiting middleware. See the root
|
|
416
|
-
|
|
416
|
+
../../CLAUDE.md §Middleware Ordering.
|
|
417
417
|
|
|
418
418
|
- `normalize_ip(ip)` — idempotent: lowercase + strip `::ffff:` prefix on
|
|
419
419
|
IPv4-mapped IPv6 addresses; safe on non-IP strings (`'unknown'` → `'unknown'`).
|
|
@@ -465,7 +465,7 @@ don't include ports — bounded by operator configuration in practice.
|
|
|
465
465
|
|
|
466
466
|
Origin allowlisting for locally-running services — **not** the CSRF
|
|
467
467
|
layer. CSRF is handled by `SameSite: strict` on session cookies (see
|
|
468
|
-
|
|
468
|
+
`auth/session_middleware.ts`).
|
|
469
469
|
|
|
470
470
|
- `parse_allowed_origins(env_value)` — comma-separated patterns → `Array<RegExp>`
|
|
471
471
|
- `should_allow_origin(origin, patterns)` — case-insensitive match
|
|
@@ -664,7 +664,7 @@ emit_after_commit(ctx, () => notification_sender.send_to_account(account_id, msg
|
|
|
664
664
|
```
|
|
665
665
|
|
|
666
666
|
Used for WS sends (`NotificationSender.send_to_account` for
|
|
667
|
-
role-grant-offer notifications — see
|
|
667
|
+
role-grant-offer notifications — see `auth/CLAUDE.md` §WS notifications)
|
|
668
668
|
and any side effect that must run only after the transaction commits.
|
|
669
669
|
|
|
670
670
|
### Key properties
|
|
@@ -712,7 +712,7 @@ auth-domain dependencies:
|
|
|
712
712
|
surface data reveals API structure (schemas, auth, routes)
|
|
713
713
|
|
|
714
714
|
Auth-aware variants (account status, bootstrap status) live in
|
|
715
|
-
|
|
715
|
+
`auth/` — `common_routes.ts` stays generic.
|
|
716
716
|
|
|
717
717
|
## DB Routes (Generic Browser)
|
|
718
718
|
|
|
@@ -743,7 +743,7 @@ Interfaces exported for consumer use: `TableInfo`, `TableWithCount`,
|
|
|
743
743
|
## Cross-Module Notes
|
|
744
744
|
|
|
745
745
|
- **Middleware ordering** is assembled by `create_app_server` — see the
|
|
746
|
-
root
|
|
746
|
+
root ../../CLAUDE.md §Middleware Ordering. The invariants `http/`
|
|
747
747
|
needs consumers to uphold: trusted-proxy runs before auth/rate-limit;
|
|
748
748
|
origin verification runs before session parsing; `client_ip` must be
|
|
749
749
|
set before any handler or rate limiter reads it
|
|
@@ -753,7 +753,7 @@ Interfaces exported for consumer use: `TableInfo`, `TableWithCount`,
|
|
|
753
753
|
- **Input/output schemas align with SAES.** When wiring RPC via
|
|
754
754
|
`actions/action_rpc.ts` or bridging to `RouteSpec` via
|
|
755
755
|
`actions/action_bridge.ts`, the same Zod types flow through unchanged
|
|
756
|
-
(see
|
|
756
|
+
(see `actions/CLAUDE.md` §Single JSON-RPC 2.0 endpoint and §HTTP bridge)
|
|
757
757
|
- **Error modules are complementary, not redundant.** `error_schemas.ts`
|
|
758
758
|
is Zod-first (for routes and surface); `jsonrpc_errors.ts` is
|
|
759
759
|
throw-first (for handlers and the catch layer). A single `ERROR_*`
|
|
@@ -137,7 +137,10 @@ export interface AppServerOptions {
|
|
|
137
137
|
* listener to `backend.deps.audit.on_event_chain` (composing with the
|
|
138
138
|
* consumer's `on_audit_event` callback rather than rebuilding `AppDeps`), and
|
|
139
139
|
* auto-includes `audit_log_event_specs` in the surface. The result is exposed
|
|
140
|
-
* on `AppServerContext` (for route factories) and `AppServer` (for the caller)
|
|
140
|
+
* on `AppServerContext` (for route factories) and `AppServer` (for the caller),
|
|
141
|
+
* always typed as `AuditLogSse | null` — when this option is set, the field
|
|
142
|
+
* is non-null. Use `require_audit_sse(ctx)` to assert the invariant in
|
|
143
|
+
* route factories that depend on it.
|
|
141
144
|
*
|
|
142
145
|
* Pass `true` for defaults (admin role), or `{role: 'custom'}` for a custom role.
|
|
143
146
|
* Omit to wire audit SSE manually.
|
|
@@ -160,14 +163,27 @@ export interface AppServerOptions {
|
|
|
160
163
|
* `create_standard_rpc_actions(ctx.deps, {app_settings: ctx.app_settings})`.
|
|
161
164
|
*/
|
|
162
165
|
rpc_endpoints?: Array<RpcEndpointSpec> | ((context: AppServerContext) => Array<RpcEndpointSpec>);
|
|
163
|
-
/**
|
|
164
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Env schema for surface generation. Defaults to `BaseServerEnv` —
|
|
168
|
+
* pass an extended schema (typically `BaseServerEnv.extend({...})`)
|
|
169
|
+
* when the consumer adds app-specific env vars.
|
|
170
|
+
*/
|
|
171
|
+
env_schema?: z.ZodObject;
|
|
165
172
|
/** Middleware applied after routes, before static serving. Included in surface. */
|
|
166
173
|
post_route_middleware?: Array<MiddlewareSpec>;
|
|
167
174
|
/** Static file serving. Omit if not serving static files. */
|
|
168
175
|
static_serving?: {
|
|
169
176
|
serve_static: ServeStaticFactory;
|
|
177
|
+
/** Root directory for static files. Default `'./build'`. */
|
|
178
|
+
root?: string;
|
|
179
|
+
/** Optional SPA fallback path served for client-side routes. */
|
|
170
180
|
spa_fallback?: string;
|
|
181
|
+
/**
|
|
182
|
+
* Predicate deciding which paths receive the SPA fallback.
|
|
183
|
+
* Default: every path that is not under `/api/`. Only consulted
|
|
184
|
+
* when `spa_fallback` is set.
|
|
185
|
+
*/
|
|
186
|
+
is_spa_route?: (path: string) => boolean;
|
|
171
187
|
};
|
|
172
188
|
/**
|
|
173
189
|
* Await all pending fire-and-forget effects before returning the response.
|
|
@@ -202,7 +218,11 @@ export interface AppServerContext {
|
|
|
202
218
|
action_account_rate_limiter: RateLimiter | null;
|
|
203
219
|
/** Global app settings (mutable ref — mutated by settings admin route). */
|
|
204
220
|
app_settings: AppSettings;
|
|
205
|
-
/**
|
|
221
|
+
/**
|
|
222
|
+
* Factory-managed audit log SSE. Non-null when the `audit_log_sse`
|
|
223
|
+
* option was passed to `create_app_server`, `null` when omitted.
|
|
224
|
+
* Use `require_audit_sse(ctx)` to assert the invariant.
|
|
225
|
+
*/
|
|
206
226
|
audit_sse: AuditLogSse | null;
|
|
207
227
|
}
|
|
208
228
|
/** Result of `create_app_server()`. */
|
|
@@ -215,11 +235,35 @@ export interface AppServer {
|
|
|
215
235
|
app_settings: AppSettings;
|
|
216
236
|
/** Migration results from `create_app_backend` (auth + any `migration_namespaces` passed there). */
|
|
217
237
|
migration_results: ReadonlyArray<MigrationResult>;
|
|
218
|
-
/**
|
|
238
|
+
/**
|
|
239
|
+
* Factory-managed audit log SSE. Non-null when the `audit_log_sse`
|
|
240
|
+
* option was passed to `create_app_server`, `null` when omitted.
|
|
241
|
+
* Use `require_audit_sse(server)` to assert the invariant.
|
|
242
|
+
*/
|
|
219
243
|
audit_sse: AuditLogSse | null;
|
|
220
244
|
/** Close the database connection. Propagated from `AppBackend`. */
|
|
221
245
|
close: () => Promise<void>;
|
|
222
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Assert that `audit_sse` was wired by `create_app_server` and return it
|
|
249
|
+
* as a non-null `AuditLogSse`. Throws a labelled error when the
|
|
250
|
+
* `audit_log_sse` option was not passed to `create_app_server`.
|
|
251
|
+
*
|
|
252
|
+
* Use in route factories that depend on factory-managed audit SSE:
|
|
253
|
+
*
|
|
254
|
+
* ```ts
|
|
255
|
+
* create_route_specs: (ctx) => create_audit_log_route_specs({
|
|
256
|
+
* stream: require_audit_sse(ctx),
|
|
257
|
+
* }),
|
|
258
|
+
* ```
|
|
259
|
+
*
|
|
260
|
+
* Preferred over `ctx.audit_sse!` — `!` lies to the type system and
|
|
261
|
+
* produces a downstream cannot-read-property crash if a consumer wires
|
|
262
|
+
* the route without enabling the option.
|
|
263
|
+
*/
|
|
264
|
+
export declare const require_audit_sse: (source: {
|
|
265
|
+
audit_sse: AuditLogSse | null;
|
|
266
|
+
}) => AuditLogSse;
|
|
223
267
|
/** Default maximum request body size: 1 MiB. */
|
|
224
268
|
export declare const DEFAULT_MAX_BODY_SIZE: number;
|
|
225
269
|
/**
|
|
@@ -231,7 +275,11 @@ export declare const DEFAULT_MAX_BODY_SIZE: number;
|
|
|
231
275
|
* pass `migration_namespaces` to `create_app_backend`.
|
|
232
276
|
*
|
|
233
277
|
* When `audit_log_sse` is set, the SSE registry's listener is appended to
|
|
234
|
-
* `backend.deps.audit.on_event_chain` — no shallow-copy of `AppDeps`.
|
|
278
|
+
* `backend.deps.audit.on_event_chain` — no shallow-copy of `AppDeps`. The
|
|
279
|
+
* `audit_sse` field on the returned `AppServer` (and the
|
|
280
|
+
* `AppServerContext` passed to `create_route_specs`) is non-null in that
|
|
281
|
+
* case; consumers can call `require_audit_sse(ctx)` / `require_audit_sse(server)`
|
|
282
|
+
* to assert the invariant.
|
|
235
283
|
*
|
|
236
284
|
* @returns assembled Hono app, backend, surface build, and bootstrap status
|
|
237
285
|
*/
|