@fuzdev/fuz_app 0.63.0 → 0.64.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 +124 -11
- package/dist/actions/connection_closer.d.ts +68 -0
- package/dist/actions/connection_closer.d.ts.map +1 -0
- package/dist/actions/connection_closer.js +41 -0
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +23 -2
- package/dist/actions/register_ws_endpoint.d.ts +11 -9
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +5 -5
- package/dist/actions/transports_ws_auth_guard.d.ts +24 -8
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +23 -7
- package/dist/actions/ws_endpoint_spec.d.ts +119 -0
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
- package/dist/actions/ws_endpoint_spec.js +13 -0
- package/dist/auth/CLAUDE.md +79 -15
- package/dist/auth/account_action_specs.d.ts +1 -1
- package/dist/auth/account_actions.d.ts +13 -0
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +31 -1
- package/dist/auth/account_routes.d.ts +12 -2
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +55 -8
- package/dist/auth/account_schema.d.ts +3 -3
- package/dist/auth/admin_action_specs.d.ts +8 -8
- package/dist/auth/admin_actions.d.ts +11 -0
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +25 -0
- package/dist/auth/audit_emitter.d.ts +56 -12
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +38 -12
- package/dist/auth/audit_log_schema.d.ts +5 -3
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +5 -3
- package/dist/auth/bootstrap_routes.d.ts +1 -1
- package/dist/auth/invite_schema.d.ts +2 -2
- package/dist/auth/signup_routes.d.ts +1 -1
- package/dist/auth/standard_rpc_actions.d.ts +1 -0
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +1 -0
- package/dist/http/CLAUDE.md +26 -10
- package/dist/http/ip_canonical.d.ts +99 -0
- package/dist/http/ip_canonical.d.ts.map +1 -0
- package/dist/http/ip_canonical.js +191 -0
- package/dist/http/origin.d.ts +13 -5
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +13 -31
- package/dist/http/pending_effects.d.ts +1 -1
- package/dist/http/pending_effects.js +1 -1
- package/dist/http/proxy.d.ts +13 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +15 -23
- package/dist/http/surface.d.ts +50 -0
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +27 -1
- package/dist/primitive_schemas.d.ts +20 -4
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +25 -4
- package/dist/realtime/sse_auth_guard.d.ts +16 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +15 -3
- package/dist/server/app_backend.d.ts +66 -19
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +57 -34
- package/dist/server/app_server.d.ts +60 -0
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +95 -2
- package/dist/server/startup.d.ts.map +1 -1
- package/dist/server/startup.js +12 -0
- package/dist/testing/CLAUDE.md +64 -28
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +4 -5
- package/dist/testing/adversarial_headers.d.ts +6 -0
- package/dist/testing/adversarial_headers.d.ts.map +1 -1
- package/dist/testing/adversarial_headers.js +13 -5
- package/dist/testing/app_server.d.ts +33 -32
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +4 -13
- package/dist/testing/attack_surface.d.ts +8 -7
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +12 -8
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +3 -5
- package/dist/testing/audit_drift_guard.d.ts +116 -0
- package/dist/testing/audit_drift_guard.d.ts.map +1 -0
- package/dist/testing/audit_drift_guard.js +134 -0
- package/dist/testing/connection_closer_helpers.d.ts +44 -0
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
- package/dist/testing/connection_closer_helpers.js +48 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +7 -9
- package/dist/testing/rate_limiting.js +4 -4
- package/dist/testing/rpc_helpers.d.ts +2 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +6 -8
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +12 -6
- package/dist/testing/stubs.d.ts +11 -0
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +4 -0
- package/dist/testing/surface_invariants.d.ts +66 -1
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +103 -1
- package/dist/ui/SurfaceExplorer.svelte +161 -2
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
* Bound audit-emit capability.
|
|
3
3
|
*
|
|
4
4
|
* `AuditEmitter` closes over the pool-level `Db`, the `on_audit_event`
|
|
5
|
-
* subscriber chain, and the optional `AuditLogConfig
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* subscriber chain, and the optional `AuditLogConfig`. Built by the
|
|
6
|
+
* consumer's `audit_factory` callback on `CreateAppBackendOptions` —
|
|
7
|
+
* `create_app_backend` invokes the factory once with its constructed
|
|
8
|
+
* `{db, log}` and lands the result on `AppDeps.audit`. Consumers reach
|
|
9
|
+
* for `deps.audit.emit(ctx, input)` and never see the pool — handlers
|
|
10
|
+
* cannot accidentally emit an audit event against the request's
|
|
11
|
+
* transactional `db` (which would be rolled back with the parent on a
|
|
12
|
+
* handler throw).
|
|
10
13
|
*
|
|
11
14
|
* Four methods cover every fan-out shape the auth domain needs:
|
|
12
15
|
*
|
|
@@ -26,9 +29,13 @@
|
|
|
26
29
|
* the query layer). Runs every listener on the chain; per-listener throws
|
|
27
30
|
* are isolated.
|
|
28
31
|
*
|
|
29
|
-
* The chain is
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
+
* The chain is a documented mutable seam — `create_app_server` appends
|
|
33
|
+
* additional listeners after the backend is built (the factory-managed
|
|
34
|
+
* audit-log SSE, per-endpoint WS auth guards and logout closers, any
|
|
35
|
+
* `extra_audit_handlers` on a `WsEndpointSpec`) before the first request
|
|
36
|
+
* runs. Consumers can also append listeners directly on the emitter
|
|
37
|
+
* they return from `audit_factory` for setups that don't pass through
|
|
38
|
+
* `create_app_server`.
|
|
32
39
|
*
|
|
33
40
|
* @module
|
|
34
41
|
*/
|
|
@@ -126,12 +133,36 @@ export interface AuditEmitter {
|
|
|
126
133
|
*/
|
|
127
134
|
notify(event: AuditLogEvent): void;
|
|
128
135
|
/**
|
|
129
|
-
* Mutable subscriber chain.
|
|
130
|
-
* factory-managed audit-log SSE
|
|
131
|
-
*
|
|
136
|
+
* Mutable subscriber chain. `create_app_server` appends the
|
|
137
|
+
* factory-managed audit-log SSE listener and per-endpoint WS auth
|
|
138
|
+
* guards / logout closers here so SSE + WS fan-out compose on top of
|
|
139
|
+
* the consumer's `on_audit_event` callback without shallow-copying
|
|
140
|
+
* `AppDeps`. Consumers can also append listeners directly for setups
|
|
141
|
+
* that don't run through `create_app_server`.
|
|
132
142
|
*/
|
|
133
143
|
readonly on_event_chain: Array<(event: AuditLogEvent) => void>;
|
|
134
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Signature of `AuditEmitter.emit` — captured by the inner closure so
|
|
147
|
+
* `emit_role_grant_target` reaches the decorated function rather than
|
|
148
|
+
* a `this.emit` lookup. Exposed as a type so `EmitDecorator` can name
|
|
149
|
+
* the inner / outer slot.
|
|
150
|
+
*/
|
|
151
|
+
export type AuditEmitFn = <T extends string>(ctx: AuditEmitterContext, input: AuditLogInput<T>) => void;
|
|
152
|
+
/**
|
|
153
|
+
* Wrap the bound `emit` before it gets captured by `emit_role_grant_target`'s
|
|
154
|
+
* closure and exposed on the returned `AuditEmitter`. Test instrumentation
|
|
155
|
+
* uses this to record `emit` invocation ordering against external markers
|
|
156
|
+
* (e.g. eager `ConnectionCloser` calls in `connection_closer.db.test.ts`)
|
|
157
|
+
* without paying the freeze-breaking footgun the pre-decorator
|
|
158
|
+
* `patch_audit_emit_capture` hot-patcher had.
|
|
159
|
+
*
|
|
160
|
+
* Because the inner closure captures the decorated function (not the
|
|
161
|
+
* outer slot reference), `emit_role_grant_target` also routes through
|
|
162
|
+
* the wrap — the close-vs-emit ordering helper sees role-grant-shape
|
|
163
|
+
* emissions, not just bare `emit` calls. Production never sets this.
|
|
164
|
+
*/
|
|
165
|
+
export type EmitDecorator = (inner: AuditEmitFn) => AuditEmitFn;
|
|
135
166
|
/** Options for `create_audit_emitter`. */
|
|
136
167
|
export interface CreateAuditEmitterOptions {
|
|
137
168
|
/** Pool-level `Db`. Captured by every emit call. */
|
|
@@ -149,9 +180,22 @@ export interface CreateAuditEmitterOptions {
|
|
|
149
180
|
* registered here once at backend assembly.
|
|
150
181
|
*/
|
|
151
182
|
audit_log_config?: AuditLogConfig;
|
|
183
|
+
/**
|
|
184
|
+
* Test-only hook to wrap `emit` at construction time. The decorated
|
|
185
|
+
* function is captured by `emit_role_grant_target`'s closure and is
|
|
186
|
+
* the function exposed on the returned `AuditEmitter`, so both call
|
|
187
|
+
* shapes route through it — see `EmitDecorator` for the rationale.
|
|
188
|
+
*
|
|
189
|
+
* Leave unset in production. The intended caller is
|
|
190
|
+
* `create_emit_ordering_audit_factory` in `testing/audit_drift_guard.ts`.
|
|
191
|
+
*/
|
|
192
|
+
emit_decorator?: EmitDecorator;
|
|
152
193
|
}
|
|
153
194
|
/**
|
|
154
|
-
* Build a bound `AuditEmitter`.
|
|
195
|
+
* Build a bound `AuditEmitter`. Typical caller is the consumer's
|
|
196
|
+
* `audit_factory` callback on `CreateAppBackendOptions` —
|
|
197
|
+
* `create_app_backend` invokes that callback with its constructed
|
|
198
|
+
* `{db, log}` and lands the result on `AppDeps.audit`.
|
|
155
199
|
*
|
|
156
200
|
* @param options - pool, logger, optional initial subscriber, optional config
|
|
157
201
|
* @returns the bound emitter; closes over the pool + config + listener chain
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,mBAAmB;IACnC,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACrE,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC5B;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,mBAAmB,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAChF;;;;;;;OAOG;IACH,sBAAsB,CAAC,CAAC,SAAS,MAAM,EACtC,GAAG,EAAE,yBAAyB,EAC9B,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE;QACN,UAAU,EAAE,CAAC,CAAC;QACd,iBAAiB,EAAE,IAAI,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;QAC7B,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACvC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;KAChC,GACC,IAAI,CAAC;IACR;;;;;;;;;;OAUG;IACH,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC;;;;;;;OAOG;IACH,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,CAAC;CAC/D;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,SAAS,MAAM,EAC1C,GAAG,EAAE,mBAAmB,EACxB,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,KACnB,IAAI,CAAC;AAEV;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,WAAW,CAAC;AAEhE,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACzC,oDAAoD;IACpD,EAAE,EAAE,EAAE,CAAC;IACP,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAC;IAClC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,yBAAyB,KAAG,YAoEzE,CAAC"}
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
* Bound audit-emit capability.
|
|
3
3
|
*
|
|
4
4
|
* `AuditEmitter` closes over the pool-level `Db`, the `on_audit_event`
|
|
5
|
-
* subscriber chain, and the optional `AuditLogConfig
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* subscriber chain, and the optional `AuditLogConfig`. Built by the
|
|
6
|
+
* consumer's `audit_factory` callback on `CreateAppBackendOptions` —
|
|
7
|
+
* `create_app_backend` invokes the factory once with its constructed
|
|
8
|
+
* `{db, log}` and lands the result on `AppDeps.audit`. Consumers reach
|
|
9
|
+
* for `deps.audit.emit(ctx, input)` and never see the pool — handlers
|
|
10
|
+
* cannot accidentally emit an audit event against the request's
|
|
11
|
+
* transactional `db` (which would be rolled back with the parent on a
|
|
12
|
+
* handler throw).
|
|
10
13
|
*
|
|
11
14
|
* Four methods cover every fan-out shape the auth domain needs:
|
|
12
15
|
*
|
|
@@ -26,22 +29,29 @@
|
|
|
26
29
|
* the query layer). Runs every listener on the chain; per-listener throws
|
|
27
30
|
* are isolated.
|
|
28
31
|
*
|
|
29
|
-
* The chain is
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
+
* The chain is a documented mutable seam — `create_app_server` appends
|
|
33
|
+
* additional listeners after the backend is built (the factory-managed
|
|
34
|
+
* audit-log SSE, per-endpoint WS auth guards and logout closers, any
|
|
35
|
+
* `extra_audit_handlers` on a `WsEndpointSpec`) before the first request
|
|
36
|
+
* runs. Consumers can also append listeners directly on the emitter
|
|
37
|
+
* they return from `audit_factory` for setups that don't pass through
|
|
38
|
+
* `create_app_server`.
|
|
32
39
|
*
|
|
33
40
|
* @module
|
|
34
41
|
*/
|
|
35
42
|
import { query_audit_log } from './audit_log_queries.js';
|
|
36
43
|
import { builtin_audit_log_config, } from './audit_log_schema.js';
|
|
37
44
|
/**
|
|
38
|
-
* Build a bound `AuditEmitter`.
|
|
45
|
+
* Build a bound `AuditEmitter`. Typical caller is the consumer's
|
|
46
|
+
* `audit_factory` callback on `CreateAppBackendOptions` —
|
|
47
|
+
* `create_app_backend` invokes that callback with its constructed
|
|
48
|
+
* `{db, log}` and lands the result on `AppDeps.audit`.
|
|
39
49
|
*
|
|
40
50
|
* @param options - pool, logger, optional initial subscriber, optional config
|
|
41
51
|
* @returns the bound emitter; closes over the pool + config + listener chain
|
|
42
52
|
*/
|
|
43
53
|
export const create_audit_emitter = (options) => {
|
|
44
|
-
const { db, log, audit_log_config = builtin_audit_log_config } = options;
|
|
54
|
+
const { db, log, audit_log_config = builtin_audit_log_config, emit_decorator } = options;
|
|
45
55
|
const on_event_chain = [];
|
|
46
56
|
if (options.on_audit_event)
|
|
47
57
|
on_event_chain.push(options.on_audit_event);
|
|
@@ -64,9 +74,14 @@ export const create_audit_emitter = (options) => {
|
|
|
64
74
|
log.error('Audit log write failed:', err);
|
|
65
75
|
}
|
|
66
76
|
};
|
|
67
|
-
const
|
|
77
|
+
const base_emit = (ctx, input) => {
|
|
68
78
|
ctx.pending_effects.push(emit_pool(input));
|
|
69
79
|
};
|
|
80
|
+
// The decorated `emit` is what `emit_role_grant_target` captures below
|
|
81
|
+
// and what gets exposed on the returned object — both call shapes
|
|
82
|
+
// route through any `emit_decorator` the caller supplied. Production
|
|
83
|
+
// passes no decorator, so this collapses to `base_emit`.
|
|
84
|
+
const emit = emit_decorator ? emit_decorator(base_emit) : base_emit;
|
|
70
85
|
const emit_role_grant_target = (ctx, auth, input) => {
|
|
71
86
|
emit(ctx, {
|
|
72
87
|
event_type: input.event_type,
|
|
@@ -79,5 +94,16 @@ export const create_audit_emitter = (options) => {
|
|
|
79
94
|
metadata: input.metadata,
|
|
80
95
|
});
|
|
81
96
|
};
|
|
82
|
-
|
|
97
|
+
// Freeze the slot layout so consumers cannot hot-patch `emit` /
|
|
98
|
+
// `emit_role_grant_target` / `emit_pool` / `notify` after construction.
|
|
99
|
+
// The previous test helper `patch_audit_emit_capture` did exactly this
|
|
100
|
+
// and only happened to work because the four slots were writable —
|
|
101
|
+
// `emit_role_grant_target` calls the closed-over inner `emit`, not
|
|
102
|
+
// `this.emit`, so the patch silently bypassed role-grant-shape emits.
|
|
103
|
+
// Tests that need instrumentation pass `emit_decorator` so the wrap
|
|
104
|
+
// is captured by the closure before the freeze (see
|
|
105
|
+
// `create_emit_ordering_audit_factory`). `on_event_chain` is a
|
|
106
|
+
// frozen reference but its array contents stay mutable —
|
|
107
|
+
// `create_app_server` appends to it post-assembly, by design.
|
|
108
|
+
return Object.freeze({ emit, emit_role_grant_target, emit_pool, notify, on_event_chain });
|
|
83
109
|
};
|
|
@@ -336,9 +336,11 @@ export interface CreateAuditLogConfigOptions {
|
|
|
336
336
|
* Throws when an `extra_events` key collides with a builtin event type, or
|
|
337
337
|
* fails `AuditEventTypeName` format validation.
|
|
338
338
|
*
|
|
339
|
-
* Call once at startup; pass the result
|
|
340
|
-
*
|
|
341
|
-
*
|
|
339
|
+
* Call once at startup; pass the result into the consumer's `audit_factory`
|
|
340
|
+
* body — typically `({db, log}) => create_audit_emitter({db, log,
|
|
341
|
+
* audit_log_config, ...})` — so it gets captured inside the bound
|
|
342
|
+
* `AppDeps.audit` emitter. Builtin handlers omit the `audit_log_config`
|
|
343
|
+
* slot and pick up `builtin_audit_log_config`.
|
|
342
344
|
*
|
|
343
345
|
* @throws Error when an `extra_events` key collides with a builtin event type or fails `AuditEventTypeName` format validation
|
|
344
346
|
*/
|
|
@@ -1 +1 @@
|
|
|
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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
|
|
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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"}
|
|
@@ -291,9 +291,11 @@ export const builtin_audit_log_config = Object.freeze({
|
|
|
291
291
|
* Throws when an `extra_events` key collides with a builtin event type, or
|
|
292
292
|
* fails `AuditEventTypeName` format validation.
|
|
293
293
|
*
|
|
294
|
-
* Call once at startup; pass the result
|
|
295
|
-
*
|
|
296
|
-
*
|
|
294
|
+
* Call once at startup; pass the result into the consumer's `audit_factory`
|
|
295
|
+
* body — typically `({db, log}) => create_audit_emitter({db, log,
|
|
296
|
+
* audit_log_config, ...})` — so it gets captured inside the bound
|
|
297
|
+
* `AppDeps.audit` emitter. Builtin handlers omit the `audit_log_config`
|
|
298
|
+
* slot and pick up `builtin_audit_log_config`.
|
|
297
299
|
*
|
|
298
300
|
* @throws Error when an `extra_events` key collides with a builtin event type or fails `AuditEventTypeName` format validation
|
|
299
301
|
*/
|
|
@@ -19,7 +19,7 @@ import type { StatResult } from '../runtime/deps.js';
|
|
|
19
19
|
/** Input for `POST /bootstrap`. `token` is the one-shot token file contents. */
|
|
20
20
|
export declare const BootstrapInput: z.ZodObject<{
|
|
21
21
|
token: z.ZodString;
|
|
22
|
-
username: z.ZodString
|
|
22
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
23
23
|
password: z.ZodString;
|
|
24
24
|
}, z.core.$strict>;
|
|
25
25
|
export type BootstrapInput = z.infer<typeof BootstrapInput>;
|
|
@@ -23,7 +23,7 @@ export interface Invite {
|
|
|
23
23
|
export declare const InviteJson: z.ZodObject<{
|
|
24
24
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
25
25
|
email: z.ZodNullable<z.ZodEmail>;
|
|
26
|
-
username: z.ZodNullable<z.ZodString
|
|
26
|
+
username: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
27
27
|
claimed_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
28
28
|
claimed_at: z.ZodNullable<z.ZodString>;
|
|
29
29
|
created_at: z.ZodString;
|
|
@@ -34,7 +34,7 @@ export type InviteJson = z.infer<typeof InviteJson>;
|
|
|
34
34
|
export declare const InviteWithUsernamesJson: z.ZodObject<{
|
|
35
35
|
id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
36
36
|
email: z.ZodNullable<z.ZodEmail>;
|
|
37
|
-
username: z.ZodNullable<z.ZodString
|
|
37
|
+
username: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
38
38
|
claimed_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
|
|
39
39
|
claimed_at: z.ZodNullable<z.ZodString>;
|
|
40
40
|
created_at: z.ZodString;
|
|
@@ -24,7 +24,7 @@ export interface SignupRouteOptions extends AuthSessionRouteOptions {
|
|
|
24
24
|
}
|
|
25
25
|
/** Input for `POST /signup`. `email` is optional and must match any referenced invite. */
|
|
26
26
|
export declare const SignupInput: z.ZodObject<{
|
|
27
|
-
username: z.ZodString
|
|
27
|
+
username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
28
28
|
password: z.ZodString;
|
|
29
29
|
email: z.ZodOptional<z.ZodEmail>;
|
|
30
30
|
}, z.core.$strict>;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Option routing: shared `roles` flows to both admin and role-grant-offer;
|
|
11
11
|
* `app_settings` goes to admin only; `default_ttl_ms` and `authorize` go
|
|
12
12
|
* to role-grant-offer only; `max_tokens` goes to account only;
|
|
13
|
+
* shared `connection_closer` flows to admin + account (role-grant-offer ignores);
|
|
13
14
|
* `notification_sender` reaches role-grant-offer transparently (admin + account
|
|
14
15
|
* ignore it).
|
|
15
16
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standard_rpc_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/standard_rpc_actions.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"standard_rpc_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/standard_rpc_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAuB,KAAK,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAEN,KAAK,2BAA2B,EAChC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAyB,KAAK,oBAAoB,EAAC,MAAM,sBAAsB,CAAC;AACvF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,qCAAqC,CAAC;AAC5E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExD;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAChB,SAAQ,kBAAkB,EAAE,2BAA2B,EAAE,oBAAoB;CAAG;AAEjF;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC;IACtF,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,sBAAsB,EAC5B,UAAS,yBAA8B,KACrC,KAAK,CAAC,SAAS,CAIjB,CAAC"}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Option routing: shared `roles` flows to both admin and role-grant-offer;
|
|
11
11
|
* `app_settings` goes to admin only; `default_ttl_ms` and `authorize` go
|
|
12
12
|
* to role-grant-offer only; `max_tokens` goes to account only;
|
|
13
|
+
* shared `connection_closer` flows to admin + account (role-grant-offer ignores);
|
|
13
14
|
* `notification_sender` reaches role-grant-offer transparently (admin + account
|
|
14
15
|
* ignore it).
|
|
15
16
|
*
|
package/dist/http/CLAUDE.md
CHANGED
|
@@ -24,7 +24,8 @@ see ../../docs/architecture.md.
|
|
|
24
24
|
| `surface.ts` | `AppSurface`, `AppSurfaceSpec`, `generate_app_surface`, diagnostics |
|
|
25
25
|
| `surface_query.ts` | Pure filters/groupings over `AppSurface` |
|
|
26
26
|
| `proxy.ts` | Trusted-proxy middleware, CIDR parsing, rightmost-first XFF resolution |
|
|
27
|
-
| `
|
|
27
|
+
| `ip_canonical.ts` | RFC 5952 IPv6 canonicalization + IPv4-mapped collapse; `IP_LITERAL_CHARS` regex |
|
|
28
|
+
| `origin.ts` | Origin allowlist middleware with wildcard patterns (Origin-only) |
|
|
28
29
|
| `jsonrpc.ts` | JSON-RPC 2.0 envelope schemas (MCP superset), `JsonrpcErrorCode`, `_meta` |
|
|
29
30
|
| `jsonrpc_errors.ts` | `ThrownJsonrpcError`, `jsonrpc_errors` throwers, HTTP-status mappings |
|
|
30
31
|
| `jsonrpc_helpers.ts` | Message builders, type guards, input/result normalizers |
|
|
@@ -280,7 +281,7 @@ invariant 2's throw) was the empirical confirmation.
|
|
|
280
281
|
- **Auth**: `ERROR_AUTHENTICATION_REQUIRED`, `ERROR_INSUFFICIENT_PERMISSIONS`,
|
|
281
282
|
`ERROR_CREDENTIAL_TYPE_REQUIRED`, `ERROR_RATE_LIMIT_EXCEEDED`,
|
|
282
283
|
`ERROR_INVALID_CREDENTIALS`, `ERROR_PAYLOAD_TOO_LARGE`
|
|
283
|
-
- **Origin + bearer**: `ERROR_FORBIDDEN_ORIGIN`, `ERROR_FORBIDDEN_REFERER
|
|
284
|
+
- **Origin + bearer**: `ERROR_FORBIDDEN_ORIGIN`, `ERROR_FORBIDDEN_REFERER` (retained for consumer compat; no longer emitted),
|
|
284
285
|
`ERROR_BEARER_REJECTED_BROWSER`, `ERROR_INVALID_TOKEN`, `ERROR_ACCOUNT_NOT_FOUND`
|
|
285
286
|
- **Keeper/daemon**: `ERROR_INVALID_DAEMON_TOKEN`,
|
|
286
287
|
`ERROR_KEEPER_ACCOUNT_NOT_CONFIGURED`, `ERROR_KEEPER_ACCOUNT_NOT_FOUND`
|
|
@@ -415,10 +416,15 @@ connection is from a configured trusted proxy. Without this middleware,
|
|
|
415
416
|
Must run **before** auth and rate-limiting middleware. See the root
|
|
416
417
|
../../CLAUDE.md §Middleware Ordering.
|
|
417
418
|
|
|
418
|
-
- `normalize_ip(ip)` —
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
419
|
+
- `normalize_ip(ip)` — delegates to `canonicalize_ip` from `ip_canonical.ts`:
|
|
420
|
+
RFC 5952 IPv6 canonicalization (lowercase hex, longest-zero-run
|
|
421
|
+
compression), IPv4-mapped IPv6 emitted in dotted form and stripped of
|
|
422
|
+
the `::ffff:` prefix so the bucket collapses to plain IPv4. Idempotent;
|
|
423
|
+
safe on non-IP strings (`'unknown'` → `'unknown'`); strict char-set
|
|
424
|
+
filter (`IP_LITERAL_CHARS`) preserves malformed forms unchanged so
|
|
425
|
+
downstream `validate_ip_strict` can still reject them. Pure IPv6 like
|
|
426
|
+
`::ffff:1` (group[5]=0, not 0xffff — NOT IPv4-mapped) stays preserved.
|
|
427
|
+
Mirrors `zzz_server::proxy::normalize_ip`
|
|
422
428
|
- `ProxyOptions` — `{trusted_proxies, get_connection_ip, log?}`
|
|
423
429
|
- `ParsedProxy` — `{type: 'ip'; address}` or `{type: 'cidr'; network; prefix; address_type}`
|
|
424
430
|
- `parse_proxy_entry(entry)` — accepts `'127.0.0.1'`, `'::1'`,
|
|
@@ -461,7 +467,7 @@ distinction in rate limiting and collapse to the proxy's connection
|
|
|
461
467
|
IP (one bucket for everyone behind that proxy). nginx + cloud LBs
|
|
462
468
|
don't include ports — bounded by operator configuration in practice.
|
|
463
469
|
|
|
464
|
-
### Origin
|
|
470
|
+
### Origin allowlist — `origin.ts`
|
|
465
471
|
|
|
466
472
|
Origin allowlisting for locally-running services — **not** the CSRF
|
|
467
473
|
layer. CSRF is handled by `SameSite: strict` on session cookies (see
|
|
@@ -471,9 +477,19 @@ layer. CSRF is handled by `SameSite: strict` on session cookies (see
|
|
|
471
477
|
- `should_allow_origin(origin, patterns)` — case-insensitive match
|
|
472
478
|
- `verify_request_source(allowed_patterns)` — Hono handler:
|
|
473
479
|
1. `Origin` header present → must match allowlist or 403 `ERROR_FORBIDDEN_ORIGIN`
|
|
474
|
-
2. No `Origin`
|
|
475
|
-
|
|
476
|
-
|
|
480
|
+
2. No `Origin` → allow through (curl, CLI, token auth is primary control)
|
|
481
|
+
|
|
482
|
+
**Origin-only by design.** Fetch spec mandates `Origin` on every unsafe
|
|
483
|
+
method, so a real browser request on any state-changing surface always
|
|
484
|
+
carries it. Non-browser clients (curl, server-to-server, CLI) don't
|
|
485
|
+
ship auto-attached session cookies, so CSRF isn't the relevant threat
|
|
486
|
+
there — auth (bearer / daemon token) is the actual control. A `Referer`
|
|
487
|
+
fallback would only widen the accepted-shape envelope without closing
|
|
488
|
+
a real CSRF hole. Mirrors `zzz_server::auth::is_request_origin_allowed`.
|
|
489
|
+
|
|
490
|
+
`ERROR_FORBIDDEN_REFERER` stays exported from `error_schemas.ts` for
|
|
491
|
+
consumers whose error-schema unions or test assertions still reference
|
|
492
|
+
it — the emit site is gone, the constant is not.
|
|
477
493
|
|
|
478
494
|
Pattern syntax:
|
|
479
495
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP address canonicalization — collapse equivalent string forms into a
|
|
3
|
+
* single key per RFC 5952 (IPv6) plus the dotted form for IPv4-mapped
|
|
4
|
+
* IPv6 addresses.
|
|
5
|
+
*
|
|
6
|
+
* **Why this exists.** Without canonicalization, the four representations
|
|
7
|
+
* `::1`, `::01`, `::0001`, and `0:0:0:0:0:0:0:1` are the same IPv6 address
|
|
8
|
+
* but produce four distinct strings — so an attacker rotating
|
|
9
|
+
* equivalent forms behind a trusted-passthrough proxy could defeat
|
|
10
|
+
* per-IP rate limiting (each form gets a fresh bucket) and pollute
|
|
11
|
+
* `audit_log.ip` forensics. The collision can extend to IPv4-mapped
|
|
12
|
+
* IPv6 forms (`::ffff:127.0.0.1` vs `0:0:0:0:0:ffff:7f00:1` vs the
|
|
13
|
+
* bare `127.0.0.1`) — three keys for one address.
|
|
14
|
+
*
|
|
15
|
+
* Canonicalization runs through {@link canonicalize_ip} which:
|
|
16
|
+
*
|
|
17
|
+
* 1. Lowercases and char-set filters (`IP_LITERAL_CHARS`) — non-IP
|
|
18
|
+
* strings (`'unknown'`, `'attacker:controlled'`, `'::1\n'`) pass
|
|
19
|
+
* through unchanged so downstream strict validators can still
|
|
20
|
+
* reject them.
|
|
21
|
+
* 2. Parses via Hono's `convertIPv*ToBinary` family.
|
|
22
|
+
* 3. Re-emits the canonical RFC 5952 string (lowercase hex,
|
|
23
|
+
* longest-zero-run compressed, IPv4-mapped emitted in the dotted
|
|
24
|
+
* form mandated by RFC 5952 §5).
|
|
25
|
+
* 4. Strips the `::ffff:` prefix from dotted IPv4-mapped forms so the
|
|
26
|
+
* bucket collapses to plain IPv4 — the strip moves AFTER
|
|
27
|
+
* canonicalization because the dotted form is the only form the
|
|
28
|
+
* strip can recognize symmetrically.
|
|
29
|
+
*
|
|
30
|
+
* Mirrors `zzz_server::proxy::normalize_ip` (landed 2026-05-16) which
|
|
31
|
+
* uses the same parse-then-canonicalize-then-strip ordering for the
|
|
32
|
+
* same rate-limit-key-poisoning surface. See
|
|
33
|
+
* `~/dev/grimoire/lore/fuz_app/TODO_PROXY.md` §IPv6 String
|
|
34
|
+
* Canonicalization for the cross-backend parity record.
|
|
35
|
+
*
|
|
36
|
+
* @module
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Allowed character set for a bare IP literal.
|
|
40
|
+
*
|
|
41
|
+
* Covers the union of IPv4 (digits + `.`), IPv6 (hex digits + `:`), and
|
|
42
|
+
* IPv4-mapped IPv6 forms (`::ffff:127.0.0.1`). Anything outside this
|
|
43
|
+
* set — brackets, whitespace, control bytes, letters g–z — disqualifies
|
|
44
|
+
* the input from parsing.
|
|
45
|
+
*
|
|
46
|
+
* Same regex `proxy.ts`'s `validate_ip_strict` uses; exported here so
|
|
47
|
+
* both modules can share one source of truth.
|
|
48
|
+
*/
|
|
49
|
+
export declare const IP_LITERAL_CHARS: RegExp;
|
|
50
|
+
/**
|
|
51
|
+
* Canonicalize an IP address string.
|
|
52
|
+
*
|
|
53
|
+
* Returns the RFC 5952 canonical form for parseable IPv4 or IPv6
|
|
54
|
+
* input. Returns the input unchanged (only lowercased) when the input
|
|
55
|
+
* is non-IP (`'unknown'`), malformed (`'attacker:controlled'`,
|
|
56
|
+
* `'::1\n'`), or any string the strict char-set filter rejects.
|
|
57
|
+
*
|
|
58
|
+
* **Idempotent.** `canonicalize_ip(canonicalize_ip(x)) === canonicalize_ip(x)`
|
|
59
|
+
* for every input.
|
|
60
|
+
*
|
|
61
|
+
* **Order-safe for IPv4-mapped IPv6.** The `::ffff:` prefix strip
|
|
62
|
+
* runs AFTER the canonical emit because the canonical form of an
|
|
63
|
+
* IPv4-mapped IPv6 address is the dotted form (`::ffff:127.0.0.1`,
|
|
64
|
+
* not `::ffff:7f00:1`). Stripping before canonicalize would miss the
|
|
65
|
+
* full-hex form. Closes the
|
|
66
|
+
* `normalize_ipv4_mapped_collapse_is_order_safe` test from the Rust
|
|
67
|
+
* port.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* canonicalize_ip('::0001') // → '::1'
|
|
71
|
+
* canonicalize_ip('0:0:0:0:0:0:0:1') // → '::1'
|
|
72
|
+
* canonicalize_ip('2001:0DB8::0001') // → '2001:db8::1'
|
|
73
|
+
* canonicalize_ip('::ffff:127.0.0.1') // → '127.0.0.1'
|
|
74
|
+
* canonicalize_ip('0:0:0:0:0:ffff:7f00:1') // → '127.0.0.1'
|
|
75
|
+
* canonicalize_ip('::ffff:1') // → '::ffff:1' (NOT IPv4-mapped — group[5] is 0, not ffff)
|
|
76
|
+
* canonicalize_ip('127.0.0.1') // → '127.0.0.1'
|
|
77
|
+
* canonicalize_ip('not-an-ip') // → 'not-an-ip' (passes through)
|
|
78
|
+
* canonicalize_ip('::1\n') // → '::1\n' (fails char-set; passes through)
|
|
79
|
+
* canonicalize_ip('203.0.113.1:8080') // → '203.0.113.1:8080' (passes through; validate_ip_strict rejects)
|
|
80
|
+
*/
|
|
81
|
+
export declare const canonicalize_ip: (ip: string) => string;
|
|
82
|
+
/**
|
|
83
|
+
* Convert a 128-bit IPv6 binary value into its RFC 5952 canonical string form.
|
|
84
|
+
*
|
|
85
|
+
* - IPv4-mapped (groups[0..5] = 0, groups[5] = 0xffff) emits the
|
|
86
|
+
* `::ffff:a.b.c.d` dotted form per RFC 5952 §5.
|
|
87
|
+
* - Otherwise: lowercase hex with no leading zeros per group (§4.1),
|
|
88
|
+
* the longest run of consecutive zero groups (≥ 2 groups) is
|
|
89
|
+
* replaced with `::` (§4.2.1, §4.2.3), and on equal-length runs the
|
|
90
|
+
* first one wins (§4.2.3). Single-zero groups stay as `0` (§4.2.2).
|
|
91
|
+
*
|
|
92
|
+
* Pure helper exported for the test suite to exercise the
|
|
93
|
+
* canonicalization invariants directly without a full
|
|
94
|
+
* `convertIPv6ToBinary` round-trip.
|
|
95
|
+
*
|
|
96
|
+
* @param bits - the 128-bit IPv6 value as `bigint` (only the low 128 bits are read)
|
|
97
|
+
*/
|
|
98
|
+
export declare const ipv6_bigint_to_canonical: (bits: bigint) => string;
|
|
99
|
+
//# sourceMappingURL=ip_canonical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip_canonical.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/ip_canonical.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAIH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,QAAqB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,eAAe,GAAI,IAAI,MAAM,KAAG,MAmC5C,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,MA6DvD,CAAC"}
|