@fuzdev/fuz_app 0.62.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 +139 -24
- 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/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/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +1 -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 +124 -39
- package/dist/auth/account_action_specs.d.ts +7 -1
- 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 +13 -0
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +40 -5
- 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 +63 -12
- package/dist/auth/account_schema.d.ts +5 -5
- 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/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/all_action_spec_registries.d.ts +2 -2
- package/dist/auth/all_action_spec_registries.js +2 -2
- 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_routes.d.ts +1 -1
- package/dist/auth/audit_log_routes.js +1 -1
- package/dist/auth/audit_log_schema.d.ts +30 -3
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +21 -3
- package/dist/auth/bootstrap_routes.d.ts +1 -1
- package/dist/auth/invite_schema.d.ts +2 -2
- package/dist/auth/request_context.d.ts +1 -1
- 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/env/update_env_variable.js +1 -1
- package/dist/http/CLAUDE.md +42 -26
- 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 +91 -71
- 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 +20 -6
- 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/CLAUDE.md +13 -18
- package/dist/ui/SurfaceExplorer.svelte +161 -2
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- 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
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* @module
|
|
29
29
|
*/
|
|
30
30
|
import { type RpcAction } from '../actions/action_rpc.js';
|
|
31
|
+
import type { ConnectionCloser } from '../actions/connection_closer.js';
|
|
31
32
|
import { type RoleSchemaResult } from './role_schema.js';
|
|
32
33
|
import { type AppSettings } from './app_settings_schema.js';
|
|
33
34
|
import type { RouteFactoryDeps } from './deps.js';
|
|
@@ -49,6 +50,16 @@ export interface AdminActionOptions {
|
|
|
49
50
|
* handler and RPC dispatch returns `method_not_found`.
|
|
50
51
|
*/
|
|
51
52
|
app_settings?: AppSettings;
|
|
53
|
+
/**
|
|
54
|
+
* Live-connection closer — when set, `admin_session_revoke_all` and
|
|
55
|
+
* `admin_token_revoke_all` handlers eagerly close affected WebSocket
|
|
56
|
+
* sockets for the target account BEFORE emitting the corresponding
|
|
57
|
+
* audit event. Mirrors the self-service surface (see
|
|
58
|
+
* `AccountActionOptions.connection_closer`). `BackendWebsocketTransport`
|
|
59
|
+
* satisfies this interface structurally. When absent, only the
|
|
60
|
+
* listener-based close (`transports_ws_auth_guard`) runs.
|
|
61
|
+
*/
|
|
62
|
+
connection_closer?: ConnectionCloser | null;
|
|
52
63
|
}
|
|
53
64
|
/**
|
|
54
65
|
* Create the admin-only RPC actions.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/admin_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAsC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"admin_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/admin_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAsC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAC7F,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iCAAiC,CAAC;AAEtE,OAAO,EAGN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AAuB1B,OAAO,EAAC,KAAK,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAK1D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AA6ChD,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IAClC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC5C;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,EAC7C,UAAS,kBAAuB,KAC9B,KAAK,CAAC,SAAS,CAmRjB,CAAC"}
|
|
@@ -56,6 +56,7 @@ import { admin_account_list_action_spec, admin_session_list_action_spec, admin_s
|
|
|
56
56
|
export const create_admin_actions = (deps, options = {}) => {
|
|
57
57
|
const role_specs = options.roles?.role_specs ?? builtin_role_specs_by_name;
|
|
58
58
|
const grantable_roles = list_roles_with_grant_path(role_specs, GRANT_PATH_ADMIN);
|
|
59
|
+
const connection_closer = options.connection_closer ?? null;
|
|
59
60
|
const account_list_handler = async (input, ctx) => {
|
|
60
61
|
const accounts = await query_admin_account_list(ctx, {
|
|
61
62
|
limit: input.limit,
|
|
@@ -88,6 +89,23 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
88
89
|
throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
|
|
89
90
|
}
|
|
90
91
|
const count = await query_session_revoke_all_for_account(ctx, input.account_id);
|
|
92
|
+
// Handler-side belt+suspenders — close the target account's live WS
|
|
93
|
+
// sockets BEFORE the audit emit so revocation lands even if the audit
|
|
94
|
+
// INSERT fails. Listener-based close (`transports_ws_auth_guard` on
|
|
95
|
+
// `audit.on_event_chain`) stays as a fail-safe for out-of-band emit
|
|
96
|
+
// sites. Idempotent — see `account_actions.ts::session_revoke_handler`.
|
|
97
|
+
if (connection_closer) {
|
|
98
|
+
connection_closer.close_sockets_for_account(input.account_id);
|
|
99
|
+
}
|
|
100
|
+
// TOCTOU window — admin B hard-deletes `input.account_id` between the
|
|
101
|
+
// pre-check above and this emit; the FK rejects the row, the audit
|
|
102
|
+
// emitter logs + swallows, and the operation goes unaudited. Bounded
|
|
103
|
+
// by the audit emitter's failure logging (operator-visible) and by
|
|
104
|
+
// the rarity of concurrent admin hard-deletes. Not switching to the
|
|
105
|
+
// failure-shape (`target_account_id: null + metadata.attempted_account_id`)
|
|
106
|
+
// because the FK linkage powers the username-join in
|
|
107
|
+
// `audit_log_list_with_usernames`; losing it on every success row
|
|
108
|
+
// to harden a corner case isn't worth the query-shape change.
|
|
91
109
|
deps.audit.emit(ctx, {
|
|
92
110
|
event_type: 'session_revoke_all',
|
|
93
111
|
account_id: auth.account.id,
|
|
@@ -117,6 +135,13 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
117
135
|
throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
|
|
118
136
|
}
|
|
119
137
|
const count = await query_revoke_all_api_tokens_for_account(ctx, input.account_id);
|
|
138
|
+
// Handler-side belt+suspenders — see `session_revoke_all_handler`.
|
|
139
|
+
if (connection_closer) {
|
|
140
|
+
connection_closer.close_sockets_for_account(input.account_id);
|
|
141
|
+
}
|
|
142
|
+
// TOCTOU window — see `session_revoke_all_handler` for the rationale on
|
|
143
|
+
// keeping `target_account_id` populated rather than switching to the
|
|
144
|
+
// failure-shape.
|
|
120
145
|
deps.audit.emit(ctx, {
|
|
121
146
|
event_type: 'token_revoke_all',
|
|
122
147
|
account_id: auth.account.id,
|
|
@@ -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
|
|
@@ -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
|
};
|
|
@@ -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>;
|
|
@@ -311,9 +336,11 @@ export interface CreateAuditLogConfigOptions {
|
|
|
311
336
|
* Throws when an `extra_events` key collides with a builtin event type, or
|
|
312
337
|
* fails `AuditEventTypeName` format validation.
|
|
313
338
|
*
|
|
314
|
-
* Call once at startup; pass the result
|
|
315
|
-
*
|
|
316
|
-
*
|
|
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`.
|
|
317
344
|
*
|
|
318
345
|
* @throws Error when an `extra_events` key collides with a builtin event type or fails `AuditEventTypeName` format validation
|
|
319
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":"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.
|
|
@@ -275,9 +291,11 @@ export const builtin_audit_log_config = Object.freeze({
|
|
|
275
291
|
* Throws when an `extra_events` key collides with a builtin event type, or
|
|
276
292
|
* fails `AuditEventTypeName` format validation.
|
|
277
293
|
*
|
|
278
|
-
* Call once at startup; pass the result
|
|
279
|
-
*
|
|
280
|
-
*
|
|
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`.
|
|
281
299
|
*
|
|
282
300
|
* @throws Error when an `extra_events` key collides with a builtin event type or fails `AuditEventTypeName` format validation
|
|
283
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;
|
|
@@ -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 = {
|
|
@@ -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
|
*
|
|
@@ -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)
|