@fuzdev/fuz_app 0.37.0 → 0.38.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 +19 -0
- package/dist/actions/transports_ws_auth_guard.d.ts +31 -0
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +45 -0
- package/dist/auth/CLAUDE.md +27 -5
- package/dist/auth/account_routes.js +2 -2
- package/dist/auth/audit_log_queries.d.ts +9 -2
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +30 -5
- package/dist/auth/permit_offer_actions.d.ts.map +1 -1
- package/dist/auth/permit_offer_actions.js +3 -0
- package/dist/auth/session_queries.d.ts +9 -7
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +9 -7
- package/dist/testing/app_server.d.ts +11 -0
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +4 -0
- package/dist/testing/rpc_helpers.d.ts +9 -6
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +29 -9
- package/package.json +1 -1
package/dist/actions/CLAUDE.md
CHANGED
|
@@ -280,6 +280,25 @@ Event dispatch:
|
|
|
280
280
|
attacker-controlled identifiers. Reacting to them would let an authenticated
|
|
281
281
|
caller close another user's socket by guessing a session hash or token id.
|
|
282
282
|
|
|
283
|
+
`create_ws_logout_closer(transport, log)` is the sibling helper for
|
|
284
|
+
user-initiated `logout` events — kept separate because
|
|
285
|
+
`WS_DISCONNECT_EVENT_TYPES` deliberately omits `logout` (admin-initiated
|
|
286
|
+
revocations use `session_revoke`, while `logout` is the user-initiated
|
|
287
|
+
case). Compose the two on `on_audit_event`:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
const ws_guard = create_ws_auth_guard(transport, log);
|
|
291
|
+
const ws_logout_closer = create_ws_logout_closer(transport, log);
|
|
292
|
+
const on_audit_event = (event: AuditLogEvent): void => {
|
|
293
|
+
ws_guard(event);
|
|
294
|
+
ws_logout_closer(event);
|
|
295
|
+
};
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Same `outcome === 'failure'` guard as `create_ws_auth_guard`. Closes via
|
|
299
|
+
`close_sockets_for_account(event.account_id)` — `logout` is always
|
|
300
|
+
self-service, so there is no `target_account_id` to fall back on.
|
|
301
|
+
|
|
283
302
|
## WebSocket dispatch
|
|
284
303
|
|
|
285
304
|
Two layered entry points:
|
|
@@ -40,4 +40,35 @@ export declare const WS_DISCONNECT_EVENT_TYPES: ReadonlySet<string>;
|
|
|
40
40
|
* @returns an `on_audit_event` callback suitable for `CreateAppBackendOptions`
|
|
41
41
|
*/
|
|
42
42
|
export declare const create_ws_auth_guard: (transport: BackendWebsocketTransport, log: Logger) => ((event: AuditLogEvent) => void);
|
|
43
|
+
/**
|
|
44
|
+
* Create an audit event handler that closes WebSocket connections on
|
|
45
|
+
* user-initiated logout.
|
|
46
|
+
*
|
|
47
|
+
* Sibling helper to `create_ws_auth_guard` — kept separate because
|
|
48
|
+
* `WS_DISCONNECT_EVENT_TYPES` deliberately omits `logout` (admin-initiated
|
|
49
|
+
* revocations use `session_revoke`, while `logout` is the user-initiated
|
|
50
|
+
* case). Three consumers (tx, undying, zzz) hand-rolled this same branch
|
|
51
|
+
* before extraction.
|
|
52
|
+
*
|
|
53
|
+
* Compose with `create_ws_auth_guard` to handle both kinds of disconnect:
|
|
54
|
+
*
|
|
55
|
+
* ```ts
|
|
56
|
+
* const ws_guard = create_ws_auth_guard(transport, log);
|
|
57
|
+
* const ws_logout_closer = create_ws_logout_closer(transport, log);
|
|
58
|
+
* const on_audit_event = (event: AuditLogEvent): void => {
|
|
59
|
+
* ws_guard(event);
|
|
60
|
+
* ws_logout_closer(event);
|
|
61
|
+
* };
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* Ignores `outcome === 'failure'` events — failed logouts carry
|
|
65
|
+
* unauthenticated identifiers (no session to close anyway), and reacting
|
|
66
|
+
* to them would let an unauthenticated probe close the targeted account's
|
|
67
|
+
* sockets by submitting a logout for an arbitrary `account_id`.
|
|
68
|
+
*
|
|
69
|
+
* @param transport - the backend WebSocket transport to guard
|
|
70
|
+
* @param log - logger for disconnect events (info level on non-zero closures)
|
|
71
|
+
* @returns an `on_audit_event` callback wireable alongside `create_ws_auth_guard`
|
|
72
|
+
*/
|
|
73
|
+
export declare const create_ws_logout_closer: (transport: BackendWebsocketTransport, log: Logger) => ((event: AuditLogEvent) => void);
|
|
43
74
|
//# sourceMappingURL=transports_ws_auth_guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transports_ws_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AAE1E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAMxD,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CA6CjC,CAAC"}
|
|
1
|
+
{"version":3,"file":"transports_ws_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AAE1E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAMxD,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CA6CjC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,uBAAuB,GACnC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAajC,CAAC"}
|
|
@@ -84,3 +84,48 @@ export const create_ws_auth_guard = (transport, log) => {
|
|
|
84
84
|
}
|
|
85
85
|
};
|
|
86
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Create an audit event handler that closes WebSocket connections on
|
|
89
|
+
* user-initiated logout.
|
|
90
|
+
*
|
|
91
|
+
* Sibling helper to `create_ws_auth_guard` — kept separate because
|
|
92
|
+
* `WS_DISCONNECT_EVENT_TYPES` deliberately omits `logout` (admin-initiated
|
|
93
|
+
* revocations use `session_revoke`, while `logout` is the user-initiated
|
|
94
|
+
* case). Three consumers (tx, undying, zzz) hand-rolled this same branch
|
|
95
|
+
* before extraction.
|
|
96
|
+
*
|
|
97
|
+
* Compose with `create_ws_auth_guard` to handle both kinds of disconnect:
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* const ws_guard = create_ws_auth_guard(transport, log);
|
|
101
|
+
* const ws_logout_closer = create_ws_logout_closer(transport, log);
|
|
102
|
+
* const on_audit_event = (event: AuditLogEvent): void => {
|
|
103
|
+
* ws_guard(event);
|
|
104
|
+
* ws_logout_closer(event);
|
|
105
|
+
* };
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* Ignores `outcome === 'failure'` events — failed logouts carry
|
|
109
|
+
* unauthenticated identifiers (no session to close anyway), and reacting
|
|
110
|
+
* to them would let an unauthenticated probe close the targeted account's
|
|
111
|
+
* sockets by submitting a logout for an arbitrary `account_id`.
|
|
112
|
+
*
|
|
113
|
+
* @param transport - the backend WebSocket transport to guard
|
|
114
|
+
* @param log - logger for disconnect events (info level on non-zero closures)
|
|
115
|
+
* @returns an `on_audit_event` callback wireable alongside `create_ws_auth_guard`
|
|
116
|
+
*/
|
|
117
|
+
export const create_ws_logout_closer = (transport, log) => {
|
|
118
|
+
return (event) => {
|
|
119
|
+
if (event.event_type !== 'logout')
|
|
120
|
+
return;
|
|
121
|
+
if (event.outcome === 'failure')
|
|
122
|
+
return;
|
|
123
|
+
const account_id = event.account_id;
|
|
124
|
+
if (!account_id)
|
|
125
|
+
return;
|
|
126
|
+
const closed = transport.close_sockets_for_account(account_id);
|
|
127
|
+
if (closed > 0) {
|
|
128
|
+
log.info(`WS logout closer: closed ${closed} socket(s) for account ${account_id} (logout)`);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
};
|
package/dist/auth/CLAUDE.md
CHANGED
|
@@ -364,9 +364,11 @@ Server-side sessions, keyed by blake3 hash of the session token:
|
|
|
364
364
|
- `query_session_touch` — updates `last_seen_at`; extends `expires_at` only
|
|
365
365
|
when less than `AUTH_SESSION_EXTEND_THRESHOLD_MS` remains (avoids a write
|
|
366
366
|
on every request).
|
|
367
|
-
- **`
|
|
368
|
-
|
|
369
|
-
|
|
367
|
+
- **`query_session_revoke_by_hash_unscoped`** — unscoped DELETE. The
|
|
368
|
+
`_unscoped` suffix is the safety signal — there is no `account_id`
|
|
369
|
+
constraint, so this is only safe from the authenticated session cookie
|
|
370
|
+
path (logout). For user-facing revocation by ID, use
|
|
371
|
+
`query_session_revoke_for_account`.
|
|
370
372
|
- `query_session_revoke_for_account(deps, hash, account_id)` — IDOR guarded.
|
|
371
373
|
- `query_session_revoke_all_for_account` — returns count.
|
|
372
374
|
- `query_session_list_for_account`, `query_session_list_all_active` (admin).
|
|
@@ -420,10 +422,18 @@ run'` if the seed somehow missed (defensive — migrations always seed).
|
|
|
420
422
|
### `audit_log_queries.ts`
|
|
421
423
|
|
|
422
424
|
- `AUDIT_LOG_DEFAULT_LIMIT = 50`.
|
|
423
|
-
- `query_audit_log<T>(deps, input)` —
|
|
424
|
-
`AUDIT_METADATA_SCHEMAS[event_type]`
|
|
425
|
+
- `query_audit_log<T>(deps, input)` — validates metadata against
|
|
426
|
+
`AUDIT_METADATA_SCHEMAS[event_type]` in production + DEV both.
|
|
427
|
+
Mismatches `console.error` and increment
|
|
428
|
+
`audit_metadata_validation_failures` (sample via
|
|
429
|
+
`get_audit_metadata_validation_failures()`), but never throw — fail-open
|
|
430
|
+
by design, matching the rest of the fire-and-forget audit pipeline.
|
|
425
431
|
Returns the inserted row via `RETURNING *` (so callers get `id`, `seq`,
|
|
426
432
|
`created_at`).
|
|
433
|
+
- `get_audit_metadata_validation_failures()` / `reset_audit_metadata_validation_failures()` —
|
|
434
|
+
read / clear the in-process counter. Single-process scope (resets on
|
|
435
|
+
restart); operators thread it into a future `/metrics` surface or a
|
|
436
|
+
debug RPC handler when external observability is needed.
|
|
427
437
|
- `query_audit_log_list(deps, options?)` — supports `event_type`,
|
|
428
438
|
`event_type_in`, `account_id` (matches either `account_id` OR
|
|
429
439
|
`target_account_id`), `outcome`, `since_seq`, `limit`, `offset`.
|
|
@@ -755,6 +765,18 @@ Deps: `AdminActionDeps = Pick<RouteFactoryDeps, 'log' | 'on_audit_event'>`.
|
|
|
755
765
|
|
|
756
766
|
### `permit_offer_action_specs.ts` + `permit_offer_actions.ts` — seven RPC actions
|
|
757
767
|
|
|
768
|
+
> **Hazard — admin `permit_offer_create` does not auto-accept.** The action
|
|
769
|
+
> returns `{offer}` only — no `permit` is inserted. Acceptance is a separate
|
|
770
|
+
> RPC call (`permit_offer_accept`); admin-side tests that need to materialize
|
|
771
|
+
> a permit synchronously call `query_accept_offer` directly (see the
|
|
772
|
+
> `offer_and_accept` helper in `testing/admin_integration.ts`). The CHANGELOG
|
|
773
|
+
> v0.31 entry "admin grant_permit routes emit offers instead of direct
|
|
774
|
+
> grants" was the first signal of this two-step flow; consumers reading the
|
|
775
|
+
> standard admin suite assume auto-accept and have to redesign their tests
|
|
776
|
+
> when they discover otherwise. If you need direct grant for a programmatic
|
|
777
|
+
> path that already proves consent, reach for `query_grant_permit` rather
|
|
778
|
+
> than the RPC action.
|
|
779
|
+
|
|
758
780
|
Six offer-lifecycle methods plus `permit_revoke`. Authorization is a mix:
|
|
759
781
|
|
|
760
782
|
- `permit_offer_create` — `auth: 'authenticated'`. The **`web_grantable`
|
|
@@ -25,7 +25,7 @@ import { z } from 'zod';
|
|
|
25
25
|
import { clear_session_cookie } from './session_middleware.js';
|
|
26
26
|
import { create_session_and_set_cookie } from './session_lifecycle.js';
|
|
27
27
|
import { ActorSummaryJson, PermitSummaryJson, SessionAccountJson, to_session_account, UsernameProvided, } from './account_schema.js';
|
|
28
|
-
import { hash_session_token, query_session_revoke_all_for_account,
|
|
28
|
+
import { hash_session_token, query_session_revoke_all_for_account, query_session_revoke_by_hash_unscoped, } from './session_queries.js';
|
|
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 { audit_log_fire_and_forget } from './audit_log_queries.js';
|
|
@@ -296,7 +296,7 @@ export const create_account_route_specs = (deps, options) => {
|
|
|
296
296
|
const session_token = c.get(session_options.context_key) ?? null;
|
|
297
297
|
if (session_token) {
|
|
298
298
|
const token_hash = hash_session_token(session_token);
|
|
299
|
-
await
|
|
299
|
+
await query_session_revoke_by_hash_unscoped(route, token_hash);
|
|
300
300
|
}
|
|
301
301
|
clear_session_cookie(c, session_options);
|
|
302
302
|
void audit_log_fire_and_forget(route, {
|
|
@@ -17,14 +17,21 @@ import type { RouteContext } from '../http/route_spec.js';
|
|
|
17
17
|
import { type AuditEventType, type AuditLogEvent, type AuditLogInput, type AuditLogListOptions, type AuditLogEventWithUsernamesJson, type PermitHistoryEventJson } from './audit_log_schema.js';
|
|
18
18
|
/** Default limit for audit log listings. */
|
|
19
19
|
export declare const AUDIT_LOG_DEFAULT_LIMIT = 50;
|
|
20
|
+
/** Number of audit metadata validation failures observed since process start. */
|
|
21
|
+
export declare const get_audit_metadata_validation_failures: () => number;
|
|
22
|
+
/** Reset the counter — for tests only; production code should not call this. */
|
|
23
|
+
export declare const reset_audit_metadata_validation_failures: () => void;
|
|
20
24
|
/**
|
|
21
25
|
* Insert an audit log entry.
|
|
22
26
|
*
|
|
23
27
|
* Uses `RETURNING *` to return the full inserted row including
|
|
24
28
|
* DB-assigned fields (`id`, `seq`, `created_at`).
|
|
25
29
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
30
|
+
* Validates `metadata` against the per-event-type schema in production +
|
|
31
|
+
* DEV both. Mismatches log to `console.error` and increment
|
|
32
|
+
* `audit_metadata_validation_failures` (sampled via the exported getter)
|
|
33
|
+
* but never throw — the audit row is still written. Schema-vs-runtime
|
|
34
|
+
* drift is an operator signal, not a request-failing condition.
|
|
28
35
|
*
|
|
29
36
|
* @param deps - query dependencies
|
|
30
37
|
* @param input - the audit event to record
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_log_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"audit_log_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,MAAM,uBAAuB,CAAC;AAE/B,4CAA4C;AAC5C,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAmB1C,iFAAiF;AACjF,eAAO,MAAM,sCAAsC,QAAO,MACvB,CAAC;AAEpC,gFAAgF;AAChF,eAAO,MAAM,wCAAwC,QAAO,IAE3D,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,SAAS,cAAc,EAC7D,MAAM,SAAS,EACf,OAAO,aAAa,CAAC,CAAC,CAAC,KACrB,OAAO,CAAC,aAAa,CA2BvB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAwC9B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CA8C/C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAA+B,KAC7B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAO9B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,cAA+B,EAC/B,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAYvC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,QAAQ,IAAI,KACV,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,GAAI,CAAC,SAAS,cAAc,EACjE,OAAO,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,iBAAiB,CAAC,EAC9D,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,KAAK,MAAM,EACX,UAAU,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,KACtC,OAAO,CAAC,IAAI,CAcd,CAAC"}
|
|
@@ -11,30 +11,55 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
|
-
import { DEV } from 'esm-env';
|
|
15
14
|
import { assert_row } from '../db/assert_row.js';
|
|
16
15
|
import { AUDIT_METADATA_SCHEMAS, } from './audit_log_schema.js';
|
|
17
16
|
/** Default limit for audit log listings. */
|
|
18
17
|
export const AUDIT_LOG_DEFAULT_LIMIT = 50;
|
|
18
|
+
/**
|
|
19
|
+
* Process-wide counter for audit metadata validation failures.
|
|
20
|
+
*
|
|
21
|
+
* `query_audit_log` validates each `metadata` payload against the per-event
|
|
22
|
+
* schema in `AUDIT_METADATA_SCHEMAS` and increments this counter on
|
|
23
|
+
* mismatch. The audit row is still written — validation is fail-open by
|
|
24
|
+
* design, matching the rest of the fire-and-forget audit pipeline (a
|
|
25
|
+
* schema-vs-runtime drift should not break auth flows). Operators sample
|
|
26
|
+
* the counter via `get_audit_metadata_validation_failures()` (e.g. from
|
|
27
|
+
* a future `/metrics` surface or a debug RPC handler).
|
|
28
|
+
*
|
|
29
|
+
* Single-process scope: fuz_app's runtime state lives in-process (rate
|
|
30
|
+
* limiter, daemon token state); this counter follows the same pattern
|
|
31
|
+
* and resets on server restart.
|
|
32
|
+
*/
|
|
33
|
+
let audit_metadata_validation_failures = 0;
|
|
34
|
+
/** Number of audit metadata validation failures observed since process start. */
|
|
35
|
+
export const get_audit_metadata_validation_failures = () => audit_metadata_validation_failures;
|
|
36
|
+
/** Reset the counter — for tests only; production code should not call this. */
|
|
37
|
+
export const reset_audit_metadata_validation_failures = () => {
|
|
38
|
+
audit_metadata_validation_failures = 0;
|
|
39
|
+
};
|
|
19
40
|
/**
|
|
20
41
|
* Insert an audit log entry.
|
|
21
42
|
*
|
|
22
43
|
* Uses `RETURNING *` to return the full inserted row including
|
|
23
44
|
* DB-assigned fields (`id`, `seq`, `created_at`).
|
|
24
45
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
46
|
+
* Validates `metadata` against the per-event-type schema in production +
|
|
47
|
+
* DEV both. Mismatches log to `console.error` and increment
|
|
48
|
+
* `audit_metadata_validation_failures` (sampled via the exported getter)
|
|
49
|
+
* but never throw — the audit row is still written. Schema-vs-runtime
|
|
50
|
+
* drift is an operator signal, not a request-failing condition.
|
|
27
51
|
*
|
|
28
52
|
* @param deps - query dependencies
|
|
29
53
|
* @param input - the audit event to record
|
|
30
54
|
* @returns the inserted audit log row
|
|
31
55
|
*/
|
|
32
56
|
export const query_audit_log = async (deps, input) => {
|
|
33
|
-
if (
|
|
57
|
+
if (input.metadata != null) {
|
|
34
58
|
const schema = AUDIT_METADATA_SCHEMAS[input.event_type];
|
|
35
59
|
const result = schema.safeParse(input.metadata);
|
|
36
60
|
if (!result.success) {
|
|
37
|
-
|
|
61
|
+
audit_metadata_validation_failures++;
|
|
62
|
+
console.error(`[audit_log] metadata mismatch for '${input.event_type}':`, result.error.issues);
|
|
38
63
|
}
|
|
39
64
|
}
|
|
40
65
|
const rows = await deps.db.query(`INSERT INTO audit_log (event_type, outcome, actor_id, account_id, target_account_id, ip, metadata)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permit_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAa,KAAK,aAAa,EAAE,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAGxF,OAAO,EAAmC,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAsBzF,OAAO,EAAW,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,iCAAiC,CAAC;AAmCzC;;;;;;;;GAQG;AACH,MAAM,MAAM,0BAA0B,GAAG,CACxC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,EACrE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACnC,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,iDAAiD;AACjD,MAAM,WAAW,wBAAwB;IACxC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACvC;AAqCD;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,gBAAgB,CAAC;IAC9F,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,qBAAqB,EAC3B,UAAS,wBAA6B,KACpC,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"permit_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/permit_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAa,KAAK,aAAa,EAAE,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAGxF,OAAO,EAAmC,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAsBzF,OAAO,EAAW,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,iCAAiC,CAAC;AAmCzC;;;;;;;;GAQG;AACH,MAAM,MAAM,0BAA0B,GAAG,CACxC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,EACrE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACnC,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,iDAAiD;AACjD,MAAM,WAAW,wBAAwB;IACxC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACvC;AAqCD;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,gBAAgB,CAAC;IAC9F,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,qBAAqB,EAC3B,UAAS,wBAA6B,KACpC,KAAK,CAAC,SAAS,CA8djB,CAAC"}
|
|
@@ -106,6 +106,9 @@ export const create_permit_offer_actions = (deps, options = {}) => {
|
|
|
106
106
|
},
|
|
107
107
|
}, log, on_audit_event);
|
|
108
108
|
};
|
|
109
|
+
// Returns {offer} only — no auto-accept. Recipient must call
|
|
110
|
+
// permit_offer_accept; admin tests materialize permits via
|
|
111
|
+
// query_accept_offer (see testing/admin_integration.ts `offer_and_accept`).
|
|
109
112
|
const create_handler = async (input, ctx) => {
|
|
110
113
|
const auth = require_request_auth(ctx.auth);
|
|
111
114
|
// Role must be web_grantable — same gate as admin direct-grant.
|
|
@@ -52,14 +52,16 @@ export declare const query_session_get_valid: (deps: QueryDeps, token_hash: stri
|
|
|
52
52
|
*/
|
|
53
53
|
export declare const query_session_touch: (deps: QueryDeps, token_hash: string) => Promise<void>;
|
|
54
54
|
/**
|
|
55
|
-
* Revoke (delete) a session by its token hash.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
55
|
+
* Revoke (delete) a session by its token hash, with no account scoping.
|
|
56
|
+
*
|
|
57
|
+
* The `_unscoped` suffix is the safety signal — there is no `account_id`
|
|
58
|
+
* constraint, so callers must guarantee the hash came from a trusted
|
|
59
|
+
* source (the authenticated session cookie path is the only safe production
|
|
60
|
+
* caller — see `account_routes.ts` `/logout`). For user-facing revocation
|
|
61
|
+
* of a specific session by ID, use `query_session_revoke_for_account`
|
|
62
|
+
* (IDOR-guarded).
|
|
61
63
|
*/
|
|
62
|
-
export declare const
|
|
64
|
+
export declare const query_session_revoke_by_hash_unscoped: (deps: QueryDeps, token_hash: string) => Promise<void>;
|
|
63
65
|
/**
|
|
64
66
|
* Revoke a session only if it belongs to the specified account.
|
|
65
67
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAErD,kDAAkD;AAClD,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE,yEAAyE;AACzE,eAAO,MAAM,gCAAgC,QAAsB,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,MAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAAU,MAAM,SAAS,EAAE,YAAY,MAAM,KAAG,OAAO,CAAC,IAAI,CAY3F,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAErD,kDAAkD;AAClD,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE,yEAAyE;AACzE,eAAO,MAAM,gCAAgC,QAAsB,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,MAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAAU,MAAM,SAAS,EAAE,YAAY,MAAM,KAAG,OAAO,CAAC,IAAI,CAY3F,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAc,MAAM,KAClB,OAAO,CAAC,MAAM,CAYhB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,cAAW,KACT,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,CAAC,CASjD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,CAKnF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,iBAAiB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,EACjD,KAAK,MAAM,KACT,OAAO,CAAC,IAAI,CAMd,CAAC"}
|
|
@@ -72,14 +72,16 @@ export const query_session_touch = async (deps, token_hash) => {
|
|
|
72
72
|
WHERE id = $1`, [token_hash, new_expires.toISOString()]);
|
|
73
73
|
};
|
|
74
74
|
/**
|
|
75
|
-
* Revoke (delete) a session by its token hash.
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
75
|
+
* Revoke (delete) a session by its token hash, with no account scoping.
|
|
76
|
+
*
|
|
77
|
+
* The `_unscoped` suffix is the safety signal — there is no `account_id`
|
|
78
|
+
* constraint, so callers must guarantee the hash came from a trusted
|
|
79
|
+
* source (the authenticated session cookie path is the only safe production
|
|
80
|
+
* caller — see `account_routes.ts` `/logout`). For user-facing revocation
|
|
81
|
+
* of a specific session by ID, use `query_session_revoke_for_account`
|
|
82
|
+
* (IDOR-guarded).
|
|
81
83
|
*/
|
|
82
|
-
export const
|
|
84
|
+
export const query_session_revoke_by_hash_unscoped = async (deps, token_hash) => {
|
|
83
85
|
await deps.db.query(`DELETE FROM auth_session WHERE id = $1`, [token_hash]);
|
|
84
86
|
};
|
|
85
87
|
/**
|
|
@@ -23,6 +23,7 @@ import type { AppBackend } from '../server/app_backend.js';
|
|
|
23
23
|
import { type AppServerOptions, type AppServerContext } from '../server/app_server.js';
|
|
24
24
|
import type { AppSurface, AppSurfaceSpec } from '../http/surface.js';
|
|
25
25
|
import type { RouteSpec } from '../http/route_spec.js';
|
|
26
|
+
import type { RpcEndpointsSuiteOption } from './rpc_helpers.js';
|
|
26
27
|
/**
|
|
27
28
|
* Fast password stub for tests that don't exercise login/password flows.
|
|
28
29
|
*
|
|
@@ -118,6 +119,16 @@ export declare const create_test_app_server: (options: TestAppServerOptions) =>
|
|
|
118
119
|
export interface CreateTestAppOptions extends TestAppServerOptions {
|
|
119
120
|
/** Route spec factory — called with the assembled `AppServerContext`. */
|
|
120
121
|
create_route_specs: (context: AppServerContext) => Array<RouteSpec>;
|
|
122
|
+
/**
|
|
123
|
+
* RPC endpoints mounted by `create_app_server` — eager array or
|
|
124
|
+
* `(ctx: AppServerContext) => Array<RpcEndpointSpec>` factory. Symmetric
|
|
125
|
+
* with the suite-level `rpc_endpoints` option on
|
|
126
|
+
* `describe_standard_admin_integration_tests` etc., so callers wiring a
|
|
127
|
+
* full RPC stack don't have to switch shapes between low-level and
|
|
128
|
+
* suite-level helpers. Equivalent to `app_options.rpc_endpoints`; when
|
|
129
|
+
* both are set `app_options` wins and `console.warn` fires.
|
|
130
|
+
*/
|
|
131
|
+
rpc_endpoints?: RpcEndpointsSuiteOption;
|
|
121
132
|
/** Optional overrides for `AppServerOptions` (backend, session_options, and create_route_specs are managed). */
|
|
122
133
|
app_options?: Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs'>>;
|
|
123
134
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAK/B,OAAO,EAA2B,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAE1E,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAU1D,OAAO,EAA8B,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAG3F,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AACrC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAEN,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,UAAU,EAAE,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
|
|
@@ -183,6 +183,9 @@ export const create_test_app = async (options) => {
|
|
|
183
183
|
rotated_at: new Date(),
|
|
184
184
|
keeper_account_id: test_server.account.id,
|
|
185
185
|
};
|
|
186
|
+
if (options.rpc_endpoints !== undefined && options.app_options?.rpc_endpoints !== undefined) {
|
|
187
|
+
console.warn('create_test_app: both top-level `rpc_endpoints` and `app_options.rpc_endpoints` are set; preferring `app_options.rpc_endpoints` (back-compat).');
|
|
188
|
+
}
|
|
186
189
|
const result = await create_app_server({
|
|
187
190
|
backend: test_server,
|
|
188
191
|
session_options: options.session_options,
|
|
@@ -195,6 +198,7 @@ export const create_test_app = async (options) => {
|
|
|
195
198
|
bearer_ip_rate_limiter: null,
|
|
196
199
|
await_pending_effects: true,
|
|
197
200
|
daemon_token_state,
|
|
201
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
198
202
|
...options.app_options,
|
|
199
203
|
create_route_specs: options.create_route_specs,
|
|
200
204
|
});
|
|
@@ -20,17 +20,20 @@ export type RpcEndpointsSuiteOption = Array<RpcEndpointSpec> | ((ctx: AppServerC
|
|
|
20
20
|
* Resolve a suite's `rpc_endpoints` option to an array for setup-time
|
|
21
21
|
* inspection (path lookup, action presence checks).
|
|
22
22
|
*
|
|
23
|
-
* For the factory form this invokes the factory
|
|
24
|
-
* `AppServerContext`
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* For the factory form this invokes the factory twice with stub
|
|
24
|
+
* `AppServerContext`s and asserts that both invocations produce the same
|
|
25
|
+
* (path, method-list) shape — catching factories that close over mutable
|
|
26
|
+
* state or otherwise diverge across calls. The first array is returned;
|
|
27
|
+
* the second is discarded after the comparison. `create_app_server`
|
|
28
|
+
* invokes the factory again per-test with its real ctx, and those are
|
|
29
|
+
* the handlers that actually serve requests.
|
|
28
30
|
*
|
|
29
31
|
* Safe as long as the factory is pure with respect to the endpoint `path`
|
|
30
32
|
* and the action `spec.method` list — the canonical helpers
|
|
31
33
|
* (`create_standard_rpc_actions`, `create_admin_actions`, `create_account_actions`,
|
|
32
34
|
* etc.) are. Factories that return a different `path` based on `ctx` will
|
|
33
|
-
* produce a setup/runtime mismatch;
|
|
35
|
+
* produce a setup/runtime mismatch; the path-purity assert below surfaces
|
|
36
|
+
* that as a clear `gro check` error rather than a silent test/runtime drift.
|
|
34
37
|
*/
|
|
35
38
|
export declare const resolve_rpc_endpoints_for_setup: (rpc_endpoints: RpcEndpointsSuiteOption, session_options: SessionOptions<string>) => Array<RpcEndpointSpec>;
|
|
36
39
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAa7B,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,EAIN,KAAK,gBAAgB,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,2BAA2B,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AACxD,OAAO,KAAK,EAAC,qBAAqB,EAAE,mBAAmB,EAAE,eAAe,EAAC,MAAM,oBAAoB,CAAC;AACpG,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAG9D;;;;;;;;GAQG;AACH,MAAM,MAAM,uBAAuB,GAChC,KAAK,CAAC,eAAe,CAAC,GACtB,CAAC,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;AAEvD
|
|
1
|
+
{"version":3,"file":"rpc_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,21 +16,41 @@ import { create_stub_app_server_context } from './stubs.js';
|
|
|
16
16
|
* Resolve a suite's `rpc_endpoints` option to an array for setup-time
|
|
17
17
|
* inspection (path lookup, action presence checks).
|
|
18
18
|
*
|
|
19
|
-
* For the factory form this invokes the factory
|
|
20
|
-
* `AppServerContext`
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* For the factory form this invokes the factory twice with stub
|
|
20
|
+
* `AppServerContext`s and asserts that both invocations produce the same
|
|
21
|
+
* (path, method-list) shape — catching factories that close over mutable
|
|
22
|
+
* state or otherwise diverge across calls. The first array is returned;
|
|
23
|
+
* the second is discarded after the comparison. `create_app_server`
|
|
24
|
+
* invokes the factory again per-test with its real ctx, and those are
|
|
25
|
+
* the handlers that actually serve requests.
|
|
24
26
|
*
|
|
25
27
|
* Safe as long as the factory is pure with respect to the endpoint `path`
|
|
26
28
|
* and the action `spec.method` list — the canonical helpers
|
|
27
29
|
* (`create_standard_rpc_actions`, `create_admin_actions`, `create_account_actions`,
|
|
28
30
|
* etc.) are. Factories that return a different `path` based on `ctx` will
|
|
29
|
-
* produce a setup/runtime mismatch;
|
|
31
|
+
* produce a setup/runtime mismatch; the path-purity assert below surfaces
|
|
32
|
+
* that as a clear `gro check` error rather than a silent test/runtime drift.
|
|
30
33
|
*/
|
|
31
|
-
export const resolve_rpc_endpoints_for_setup = (rpc_endpoints, session_options) =>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
export const resolve_rpc_endpoints_for_setup = (rpc_endpoints, session_options) => {
|
|
35
|
+
if (typeof rpc_endpoints !== 'function')
|
|
36
|
+
return rpc_endpoints;
|
|
37
|
+
const first = rpc_endpoints(create_stub_app_server_context(session_options));
|
|
38
|
+
const second = rpc_endpoints(create_stub_app_server_context(session_options));
|
|
39
|
+
const summarize = (eps) => JSON.stringify(eps
|
|
40
|
+
.map((ep) => ({
|
|
41
|
+
path: ep.path,
|
|
42
|
+
methods: ep.actions.map((a) => a.spec.method).sort(),
|
|
43
|
+
}))
|
|
44
|
+
.sort((a, b) => a.path.localeCompare(b.path)));
|
|
45
|
+
const summary_a = summarize(first);
|
|
46
|
+
const summary_b = summarize(second);
|
|
47
|
+
if (summary_a !== summary_b) {
|
|
48
|
+
throw new Error('rpc_endpoints factory is not path-pure: two invocations with equivalent stub ctxs produced different (path, method) shapes. ' +
|
|
49
|
+
`The factory must be pure wrt endpoint path and action method list — see ../testing/rpc_helpers.ts. ` +
|
|
50
|
+
`first=${summary_a} second=${summary_b}`);
|
|
51
|
+
}
|
|
52
|
+
return first;
|
|
53
|
+
};
|
|
34
54
|
/**
|
|
35
55
|
* Create a `RequestInit` for a JSON-RPC POST request.
|
|
36
56
|
*
|