@fuzdev/fuz_app 0.148.0 → 0.150.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 +11 -9
- package/dist/actions/action_bridge.d.ts +7 -0
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +10 -0
- package/dist/actions/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +14 -1
- package/dist/actions/action_event_types.d.ts +1 -1
- package/dist/actions/action_event_types.d.ts.map +1 -1
- package/dist/actions/perform_action.d.ts +17 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +30 -7
- package/dist/actions/register_action_ws.d.ts +8 -2
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +24 -4
- package/dist/actions/transports_ws_auth_guard.d.ts +6 -4
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +9 -6
- package/dist/actions/ws_endpoint_spec.d.ts +1 -1
- package/dist/auth/CLAUDE.md +72 -33
- package/dist/auth/account_action_specs.d.ts +12 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +16 -2
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +7 -4
- package/dist/auth/actor_lookup_queries.d.ts +3 -2
- package/dist/auth/actor_lookup_queries.d.ts.map +1 -1
- package/dist/auth/actor_lookup_queries.js +6 -3
- package/dist/auth/actor_search_queries.d.ts +5 -2
- package/dist/auth/actor_search_queries.d.ts.map +1 -1
- package/dist/auth/actor_search_queries.js +11 -4
- package/dist/auth/admin_action_specs.d.ts +4 -4
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +33 -12
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +35 -6
- package/dist/auth/all_cell_actions.d.ts +11 -2
- package/dist/auth/all_cell_actions.d.ts.map +1 -1
- package/dist/auth/all_cell_actions.js +2 -1
- package/dist/auth/audit_emitter.d.ts +23 -3
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +14 -4
- package/dist/auth/audit_log_schema.d.ts +1 -0
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +9 -5
- package/dist/auth/bootstrap_account.d.ts +25 -1
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +37 -4
- package/dist/auth/bootstrap_routes.d.ts +3 -2
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +5 -4
- package/dist/auth/cell_action_specs.d.ts +215 -15
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +103 -16
- package/dist/auth/cell_actions.d.ts +40 -18
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +126 -19
- package/dist/auth/cell_audit_events.d.ts +1 -1
- package/dist/auth/cell_audit_events.d.ts.map +1 -1
- package/dist/auth/cell_audit_events.js +3 -2
- package/dist/auth/cell_audit_metadata.d.ts +11 -0
- package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
- package/dist/auth/cell_audit_metadata.js +10 -0
- package/dist/auth/cell_grant_action_specs.d.ts +7 -0
- package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_grant_action_specs.js +7 -0
- package/dist/auth/cell_grant_actions.d.ts +36 -4
- package/dist/auth/cell_grant_actions.d.ts.map +1 -1
- package/dist/auth/cell_grant_actions.js +20 -3
- package/dist/auth/credential_type_schema.d.ts +3 -2
- package/dist/auth/credential_type_schema.d.ts.map +1 -1
- package/dist/auth/credential_type_schema.js +3 -2
- package/dist/auth/role_grant_offer_action_specs.d.ts +18 -2
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +30 -6
- package/dist/auth/role_grant_offer_actions.d.ts +9 -4
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +58 -7
- package/dist/auth/role_grant_offer_notifications.d.ts +3 -2
- package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_notifications.js +3 -2
- package/dist/auth/role_grant_offer_queries.d.ts +20 -0
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +35 -0
- package/dist/auth/role_grant_queries.d.ts +27 -9
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +40 -13
- package/dist/auth/role_schema.d.ts +14 -8
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +6 -3
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +20 -0
- package/dist/db/CLAUDE.md +9 -2
- package/dist/db/cell_ddl.d.ts +9 -1
- package/dist/db/cell_ddl.d.ts.map +1 -1
- package/dist/db/cell_ddl.js +11 -1
- package/dist/db/cell_queries.d.ts +53 -7
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +61 -7
- package/dist/http/CLAUDE.md +14 -2
- package/dist/http/body_limit.d.ts +3 -1
- package/dist/http/body_limit.d.ts.map +1 -1
- package/dist/http/body_limit.js +6 -1
- package/dist/http/error_schemas.d.ts +8 -0
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +12 -0
- package/dist/http/jsonrpc.d.ts +9 -0
- package/dist/http/jsonrpc.d.ts.map +1 -1
- package/dist/http/jsonrpc.js +17 -0
- package/dist/http/jsonrpc_errors.d.ts +40 -15
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +99 -35
- package/dist/http/proxy.d.ts +4 -2
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +7 -2
- package/dist/http/surface.d.ts +3 -1
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +7 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +3 -2
- package/dist/schema_meta.d.ts +92 -1
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +117 -0
- package/dist/server/app_server.d.ts +13 -5
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +32 -8
- package/dist/testing/CLAUDE.md +196 -82
- package/dist/testing/adversarial_input_cases.d.ts +67 -7
- package/dist/testing/adversarial_input_cases.d.ts.map +1 -1
- package/dist/testing/adversarial_input_cases.js +210 -165
- package/dist/testing/app_server.d.ts +3 -1
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.d.ts +3 -2
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +5 -3
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_lookup.js +27 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts +6 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -1
- package/dist/testing/cross_backend/audit_cross_helpers.js +11 -0
- package/dist/testing/cross_backend/capabilities.d.ts +29 -5
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +10 -6
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +117 -2
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.js +109 -13
- package/dist/testing/cross_backend/cell_moderate.d.ts +7 -1
- package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_moderate.js +68 -4
- package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_ref_admission.js +18 -9
- package/dist/testing/cross_backend/conformance_table.d.ts +5 -0
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +12 -10
- package/dist/testing/cross_backend/cross_rpc.d.ts +3 -1
- package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -1
- package/dist/testing/cross_backend/cross_rpc.js +2 -2
- package/dist/testing/cross_backend/default_backend_configs.d.ts +9 -7
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +3 -3
- package/dist/testing/cross_backend/differential.d.ts +7 -0
- package/dist/testing/cross_backend/differential.d.ts.map +1 -1
- package/dist/testing/cross_backend/differential.js +17 -0
- package/dist/testing/cross_backend/expected_schema.json +1 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +5 -4
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +74 -2
- package/dist/testing/cross_backend/rpc_adversarial.d.ts +9 -0
- package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -1
- package/dist/testing/cross_backend/rpc_adversarial.js +16 -6
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
- package/dist/testing/cross_backend/setup.d.ts +2 -2
- package/dist/testing/cross_backend/spine_suites.d.ts +85 -5
- package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_suites.js +291 -20
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +15 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +24 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +18 -19
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +66 -42
- package/dist/testing/cross_backend/testing_backdoor.d.ts +27 -4
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +136 -41
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -15
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +52 -35
- package/dist/testing/cross_backend/transport_composition.js +3 -3
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +6 -3
- package/dist/testing/rpc_attack_surface.d.ts +10 -0
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +15 -3
- package/dist/testing/rpc_envelope_cases.d.ts +5 -2
- package/dist/testing/rpc_envelope_cases.d.ts.map +1 -1
- package/dist/testing/rpc_envelope_cases.js +4 -1
- package/dist/testing/rpc_round_trip.js +3 -3
- package/dist/testing/schema_generators.d.ts +99 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +244 -76
- package/dist/testing/stubs.d.ts +4 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +6 -4
- package/dist/zod.d.ts +9 -0
- package/dist/zod.d.ts.map +1 -1
- package/dist/zod.js +20 -0
- package/package.json +1 -1
|
@@ -34,8 +34,9 @@ import { credential_type_required_error } from '../actions/perform_action.js';
|
|
|
34
34
|
import { jsonrpc_errors, ThrownJsonrpcError } from '../http/jsonrpc_errors.js';
|
|
35
35
|
import { builtin_role_specs_by_name, list_roles_with_grant_path, ROLE_ADMIN, ROLE_KEEPER } from './role_schema.js';
|
|
36
36
|
import { has_scoped_role } from './request_context.js';
|
|
37
|
-
import { query_account_has_global_role, query_account_has_active_global_role,
|
|
37
|
+
import { query_account_has_global_role, query_account_has_active_global_role, query_count_active_accounts_with_global_role_for_update } from './role_grant_queries.js';
|
|
38
38
|
import { GRANT_PATH_ADMIN } from './grant_path_schema.js';
|
|
39
|
+
import { query_role_grant_offer_supersede_outgoing } from './role_grant_offer_queries.js';
|
|
39
40
|
import { query_account_by_email, query_account_by_id, query_account_by_username, query_account_soft_delete, query_account_undelete, query_actor_soft_delete, query_actor_undelete, query_actors_by_account, query_admin_account_list, query_purge_account } from './account_queries.js';
|
|
40
41
|
import { query_session_list_all_active, query_session_revoke_all_for_account } from './session_queries.js';
|
|
41
42
|
import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
|
|
@@ -245,9 +246,10 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
245
246
|
// keeper/daemon auth with no recovery (keeper role is non-web-
|
|
246
247
|
// revocable and purge itself needs keeper auth). Out-of-band only.
|
|
247
248
|
// 2. **Last admin** — the sole remaining *active* admin is protected
|
|
248
|
-
// (soft-deleted admins can't log in, so they don't count).
|
|
249
|
-
// the
|
|
250
|
-
//
|
|
249
|
+
// (soft-deleted admins can't log in, so they don't count). The count
|
|
250
|
+
// locks the rows it counted, so two removals racing for the last two
|
|
251
|
+
// admins serialize and the second is refused — no web path re-grants
|
|
252
|
+
// a lost admin. `role_grant_revoke` guards the admin grant the same way.
|
|
251
253
|
// A missing account holds neither role here and falls through to the
|
|
252
254
|
// handler's own not-found path. `event_type` selects the audit event so
|
|
253
255
|
// the failure row matches the operation in flight.
|
|
@@ -275,15 +277,38 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
275
277
|
// admin while another active admin exists. Keeper branch above stays
|
|
276
278
|
// unconditional; the last-admin branch fires only for an *active* target.
|
|
277
279
|
if ((await query_account_has_active_global_role(ctx, target_account_id, ROLE_ADMIN)) &&
|
|
278
|
-
(await
|
|
280
|
+
(await query_count_active_accounts_with_global_role_for_update(ctx, ROLE_ADMIN)) <= 1) {
|
|
279
281
|
deny(ERROR_CANNOT_DELETE_LAST_ADMIN, `cannot ${verb} the last admin account`);
|
|
280
282
|
}
|
|
281
283
|
};
|
|
284
|
+
// Supersede every pending offer the removed account's actors made, audited
|
|
285
|
+
// — the cascade a revoke runs on the revokee's offers. An offer records no
|
|
286
|
+
// authority, so one outliving its grantor's account would still confer on
|
|
287
|
+
// accept. No supersede notification: a cascade notifies the grantor, and the
|
|
288
|
+
// grantor's connections close with its account. `cause_id` is the account.
|
|
289
|
+
const cancel_outgoing_offers = async (ctx, target_account_id, actors) => {
|
|
290
|
+
const superseded = await query_role_grant_offer_supersede_outgoing(ctx, actors.map((a) => a.id));
|
|
291
|
+
for (const offer of superseded) {
|
|
292
|
+
deps.audit.emit_role_grant_target(ctx, ctx.auth, {
|
|
293
|
+
event_type: 'role_grant_offer_supersede',
|
|
294
|
+
target_account_id: offer.to_account_id,
|
|
295
|
+
target_actor_id: offer.to_actor_id,
|
|
296
|
+
metadata: {
|
|
297
|
+
offer_id: offer.id,
|
|
298
|
+
role: offer.role,
|
|
299
|
+
scope_id: offer.scope_id,
|
|
300
|
+
reason: 'account_deleted',
|
|
301
|
+
cause_id: target_account_id
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
};
|
|
282
306
|
// Soft-delete an account (reversible tombstone). Self-or-admin:
|
|
283
307
|
// deleting another account requires the admin role. A self-delete is
|
|
284
308
|
// session-only — enforced here, not on the spec, whose gate stays open so
|
|
285
309
|
// an admin deleting another account keeps any credential. Tombstones the
|
|
286
|
-
// account + its actor(s),
|
|
310
|
+
// account + its actor(s), supersedes the pending offers its actors made,
|
|
311
|
+
// revokes sessions/tokens, closes sockets, and
|
|
287
312
|
// emits `account_delete` + one `actor_delete` per soft-deleted actor —
|
|
288
313
|
// each carrying its identity snapshot. `delete` = soft.
|
|
289
314
|
const account_delete_handler = async (input, ctx) => {
|
|
@@ -322,6 +347,7 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
322
347
|
});
|
|
323
348
|
throw jsonrpc_errors.not_found('account', { reason: ERROR_ACCOUNT_NOT_FOUND });
|
|
324
349
|
}
|
|
350
|
+
await cancel_outgoing_offers(ctx, target_account_id, actors);
|
|
325
351
|
const soft_deleted_actors = [];
|
|
326
352
|
for (const actor of actors) {
|
|
327
353
|
if (await query_actor_soft_delete(ctx, actor.id, auth.actor.id)) {
|
|
@@ -374,6 +400,9 @@ export const create_admin_actions = (deps, options = {}) => {
|
|
|
374
400
|
await assert_account_removable(ctx, input.account_id, 'account_purge');
|
|
375
401
|
// Snapshot actors before the cascade removes them.
|
|
376
402
|
const actors = await query_actors_by_account(ctx, input.account_id);
|
|
403
|
+
// Before the cascade, whose `ON DELETE CASCADE` would take the offers
|
|
404
|
+
// with no audit row.
|
|
405
|
+
await cancel_outgoing_offers(ctx, input.account_id, actors);
|
|
377
406
|
const snapshot = await query_purge_account(ctx, input.account_id);
|
|
378
407
|
if (!snapshot) {
|
|
379
408
|
deps.audit.emit(ctx, {
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { RpcAction } from '../actions/action_rpc.js';
|
|
2
2
|
import { type CellActionDeps } from './cell_actions.js';
|
|
3
|
+
import { type CellGrantCreateAuthorize } from './cell_grant_actions.js';
|
|
3
4
|
import type { RoleSchemaResult } from './role_schema.js';
|
|
5
|
+
/**
|
|
6
|
+
* Dependencies for `create_all_cell_actions` — the CRUD factory's
|
|
7
|
+
* `CellActionDeps` plus the grant factory's optional `authorize_grant_create`.
|
|
8
|
+
*/
|
|
9
|
+
export type AllCellActionDeps = CellActionDeps & {
|
|
10
|
+
authorize_grant_create?: CellGrantCreateAuthorize;
|
|
11
|
+
};
|
|
4
12
|
/**
|
|
5
13
|
* Options for `create_all_cell_actions`.
|
|
6
14
|
*
|
|
@@ -27,9 +35,10 @@ export interface AllCellActionsOptions {
|
|
|
27
35
|
* Distinct from `create_cell_actions` (the CRUD-only factory this bundles) —
|
|
28
36
|
* reach for this whenever a backend mounts the complete cell layer.
|
|
29
37
|
*
|
|
30
|
-
* @param deps - `
|
|
38
|
+
* @param deps - `AllCellActionDeps` (`log`, `audit`, the optional `validate_data`,
|
|
39
|
+
* `authorize_create`, and `authorize_grant_create`)
|
|
31
40
|
* @param options - the role schema for grant validation
|
|
32
41
|
* @returns every cell `RpcAction`, in mount order
|
|
33
42
|
*/
|
|
34
|
-
export declare const create_all_cell_actions: (deps:
|
|
43
|
+
export declare const create_all_cell_actions: (deps: AllCellActionDeps, options: AllCellActionsOptions) => Array<RpcAction>;
|
|
35
44
|
//# sourceMappingURL=all_cell_actions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"all_cell_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/all_cell_actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"all_cell_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/all_cell_actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAA6B,KAAK,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAInG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAChD,sBAAsB,CAAC,EAAE,wBAAwB,CAAC;CAClD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,iBAAiB,EACvB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAMjB,CAAC"}
|
|
@@ -19,7 +19,8 @@ import { create_cell_audit_actions } from './cell_audit_actions.js';
|
|
|
19
19
|
* Distinct from `create_cell_actions` (the CRUD-only factory this bundles) —
|
|
20
20
|
* reach for this whenever a backend mounts the complete cell layer.
|
|
21
21
|
*
|
|
22
|
-
* @param deps - `
|
|
22
|
+
* @param deps - `AllCellActionDeps` (`log`, `audit`, the optional `validate_data`,
|
|
23
|
+
* `authorize_create`, and `authorize_grant_create`)
|
|
23
24
|
* @param options - the role schema for grant validation
|
|
24
25
|
* @returns every cell `RpcAction`, in mount order
|
|
25
26
|
*/
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
* throws are isolated.
|
|
32
32
|
* - `drain_inflight()` — test-binary barrier: await every in-flight `emit`
|
|
33
33
|
* write, on an emitter built with `track_inflight: true`.
|
|
34
|
+
* - `is_tracking_inflight()` — whether that barrier can wait on anything, so a
|
|
35
|
+
* test binary built without tracking is caught rather than racing.
|
|
34
36
|
*
|
|
35
37
|
* ## When listeners hear about a row
|
|
36
38
|
*
|
|
@@ -41,7 +43,8 @@
|
|
|
41
43
|
* - A **failure** row is announced as soon as it is written. It records an
|
|
42
44
|
* attempt, which happened whether or not the request's transaction commits.
|
|
43
45
|
* - A **success** row is announced only after the request's transaction
|
|
44
|
-
* commits
|
|
46
|
+
* commits (after the handler returns, on a route without one), and never
|
|
47
|
+
* when the handler throws: the fan-out is queued on
|
|
45
48
|
* `ctx.post_commit_effects` and awaits the write. A success row reports a
|
|
46
49
|
* state change, and the listeners act on it — the revocation listeners close
|
|
47
50
|
* connections, the audit stream broadcasts it. Announced before the commit,
|
|
@@ -51,8 +54,13 @@
|
|
|
51
54
|
* A success row emitted by a handler that later throws is still in the table —
|
|
52
55
|
* the write is outside the transaction — but is never announced.
|
|
53
56
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
57
|
+
* A route that runs without a request transaction and opens its own around
|
|
58
|
+
* just its writes (`/login`, `/password`) emits its success row once that
|
|
59
|
+
* transaction has committed, so the row is written after the change it
|
|
60
|
+
* reports. The Rust spine follows the same order: a success row emitted inside
|
|
61
|
+
* a request transaction is written in that transaction and its fan-out queued
|
|
62
|
+
* behind the commit, and `/login` and `/password` emit on the pool after their
|
|
63
|
+
* own transaction commits.
|
|
56
64
|
*
|
|
57
65
|
* Listeners are a documented registration seam — `create_app_server`
|
|
58
66
|
* registers additional listeners via `add_listener` after the backend is
|
|
@@ -227,6 +235,18 @@ export interface AuditEmitter {
|
|
|
227
235
|
* to wait for. Twin of the Rust `fuz_auth` `AuditEmitter::drain_inflight`.
|
|
228
236
|
*/
|
|
229
237
|
drain_inflight(): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Whether this emitter tracks its in-flight `emit` writes — `true` only
|
|
240
|
+
* when built with `track_inflight: true`. When `false`, `drain_inflight`
|
|
241
|
+
* resolves at once and waits on nothing.
|
|
242
|
+
*
|
|
243
|
+
* The `_testing_drain_effects` action reports it as `tracked`, and the
|
|
244
|
+
* `list_audit_events` reads refuse `false`: a test binary whose emitter was
|
|
245
|
+
* built without tracking would otherwise answer the drain at once and its
|
|
246
|
+
* audit reads would race the writes. Twin of the Rust `fuz_auth`
|
|
247
|
+
* `AuditEmitter::is_tracking_inflight`.
|
|
248
|
+
*/
|
|
249
|
+
is_tracking_inflight(): boolean;
|
|
230
250
|
}
|
|
231
251
|
/**
|
|
232
252
|
* Signature of `AuditEmitter.emit` — captured by the inner closure so
|
|
@@ -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":"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"}
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
* throws are isolated.
|
|
32
32
|
* - `drain_inflight()` — test-binary barrier: await every in-flight `emit`
|
|
33
33
|
* write, on an emitter built with `track_inflight: true`.
|
|
34
|
+
* - `is_tracking_inflight()` — whether that barrier can wait on anything, so a
|
|
35
|
+
* test binary built without tracking is caught rather than racing.
|
|
34
36
|
*
|
|
35
37
|
* ## When listeners hear about a row
|
|
36
38
|
*
|
|
@@ -41,7 +43,8 @@
|
|
|
41
43
|
* - A **failure** row is announced as soon as it is written. It records an
|
|
42
44
|
* attempt, which happened whether or not the request's transaction commits.
|
|
43
45
|
* - A **success** row is announced only after the request's transaction
|
|
44
|
-
* commits
|
|
46
|
+
* commits (after the handler returns, on a route without one), and never
|
|
47
|
+
* when the handler throws: the fan-out is queued on
|
|
45
48
|
* `ctx.post_commit_effects` and awaits the write. A success row reports a
|
|
46
49
|
* state change, and the listeners act on it — the revocation listeners close
|
|
47
50
|
* connections, the audit stream broadcasts it. Announced before the commit,
|
|
@@ -51,8 +54,13 @@
|
|
|
51
54
|
* A success row emitted by a handler that later throws is still in the table —
|
|
52
55
|
* the write is outside the transaction — but is never announced.
|
|
53
56
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
57
|
+
* A route that runs without a request transaction and opens its own around
|
|
58
|
+
* just its writes (`/login`, `/password`) emits its success row once that
|
|
59
|
+
* transaction has committed, so the row is written after the change it
|
|
60
|
+
* reports. The Rust spine follows the same order: a success row emitted inside
|
|
61
|
+
* a request transaction is written in that transaction and its fan-out queued
|
|
62
|
+
* behind the commit, and `/login` and `/password` emit on the pool after their
|
|
63
|
+
* own transaction commits.
|
|
56
64
|
*
|
|
57
65
|
* Listeners are a documented registration seam — `create_app_server`
|
|
58
66
|
* registers additional listeners via `add_listener` after the backend is
|
|
@@ -158,6 +166,7 @@ export const create_audit_emitter = (options) => {
|
|
|
158
166
|
await Promise.allSettled(inflight);
|
|
159
167
|
}
|
|
160
168
|
};
|
|
169
|
+
const is_tracking_inflight = () => inflight !== null;
|
|
161
170
|
const base_emit = (ctx, input) => {
|
|
162
171
|
if (input.outcome === 'failure') {
|
|
163
172
|
// an attempt happened whether or not the transaction commits
|
|
@@ -218,6 +227,7 @@ export const create_audit_emitter = (options) => {
|
|
|
218
227
|
notify,
|
|
219
228
|
add_listener,
|
|
220
229
|
listener_count,
|
|
221
|
-
drain_inflight
|
|
230
|
+
drain_inflight,
|
|
231
|
+
is_tracking_inflight
|
|
222
232
|
});
|
|
223
233
|
};
|
|
@@ -263,6 +263,7 @@ export declare const audit_metadata_schemas: Readonly<{
|
|
|
263
263
|
sibling_accepted: "sibling_accepted";
|
|
264
264
|
role_grant_revoked: "role_grant_revoked";
|
|
265
265
|
scope_destroyed: "scope_destroyed";
|
|
266
|
+
account_deleted: "account_deleted";
|
|
266
267
|
}>;
|
|
267
268
|
cause_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
268
269
|
}, z.core.$loose>;
|
|
@@ -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,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,IAAI,EAAS,MAAM,yBAAyB,CAAC;AAkBtD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,6iBA6BnB,CAAC;AAEZ,wCAAwC;AACxC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAA4B,CAAC;AACxD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEhF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,6BAA6B,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,eAAe,CAAC,CA8BzF,CAAC;AAEJ;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,KAAG,eAG/C,CAAC;AAEX;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,yBAAyB,GACrC,OAAO,aAAa,KAClB;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAQvC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,QAA+B,CAAC;AAExE,0DAA0D;AAC1D,eAAO,MAAM,kBAAkB,aAE7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,2CAA2C;AAC3C,eAAO,MAAM,YAAY;;;EAAiC,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB
|
|
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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"}
|
|
@@ -348,19 +348,23 @@ export const audit_metadata_schemas = Object.freeze({
|
|
|
348
348
|
}),
|
|
349
349
|
// Emitted when an offer is obsoleted by an external event. `reason`
|
|
350
350
|
// distinguishes the trigger; `cause_id` points to the accepted offer
|
|
351
|
-
// (for `sibling_accepted`), the revoked role_grant (for `role_grant_revoked`
|
|
352
|
-
//
|
|
351
|
+
// (for `sibling_accepted`), the revoked role_grant (for `role_grant_revoked`
|
|
352
|
+
// — the recipient's grant of the offered role, or a grant of the grantor's),
|
|
353
|
+
// the destroyed parent scope row (for `scope_destroyed`), or the grantor's
|
|
354
|
+
// removed account (for `account_deleted`).
|
|
353
355
|
role_grant_offer_supersede: z.looseObject({
|
|
354
356
|
offer_id: Uuid.meta({ description: 'Id of the superseded offer.' }),
|
|
355
357
|
role: z.string().meta({ description: 'Role that was offered.' }),
|
|
356
358
|
scope_id: Uuid.nullish().meta({
|
|
357
359
|
description: 'Scope of the offered role; null for global offers.'
|
|
358
360
|
}),
|
|
359
|
-
reason: z
|
|
360
|
-
|
|
361
|
+
reason: z
|
|
362
|
+
.enum(['sibling_accepted', 'role_grant_revoked', 'scope_destroyed', 'account_deleted'])
|
|
363
|
+
.meta({
|
|
364
|
+
description: "Trigger that obsoleted the offer: a sibling offer was accepted, the resulting role_grant or one of the grantor's was revoked, the parent scope row was destroyed, or the grantor's account was deleted or purged."
|
|
361
365
|
}),
|
|
362
366
|
cause_id: Uuid.meta({
|
|
363
|
-
description:
|
|
367
|
+
description: "Row that caused the supersede: accepted offer (`sibling_accepted`), revoked role_grant (`role_grant_revoked`), destroyed parent scope row (`scope_destroyed`), or the grantor's account (`account_deleted`)."
|
|
364
368
|
})
|
|
365
369
|
}),
|
|
366
370
|
invite_create: z.looseObject({
|
|
@@ -66,6 +66,29 @@ export interface BootstrapAccountDeps {
|
|
|
66
66
|
/** Structured logger instance. */
|
|
67
67
|
log: Logger;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* The fewest bytes a bootstrap token holds, counted after its one optional
|
|
71
|
+
* trailing LF. A shorter file is read as unreadable: an empty or `"\n"` file
|
|
72
|
+
* would otherwise leave bootstrap advertised as open while nothing can ever
|
|
73
|
+
* match it, and a short token is guessable. Every generator writes at least
|
|
74
|
+
* this much (32 random bytes, base64url or hex encoded). Twin of the Rust
|
|
75
|
+
* `BOOTSTRAP_TOKEN_MIN_BYTES` (`fuz_auth`).
|
|
76
|
+
*/
|
|
77
|
+
export declare const BOOTSTRAP_TOKEN_MIN_BYTES = 32;
|
|
78
|
+
/**
|
|
79
|
+
* Read the bootstrap token the way both the availability check and the
|
|
80
|
+
* request-time compare need it: the hardened secure read, less one trailing
|
|
81
|
+
* LF, refused when shorter than `BOOTSTRAP_TOKEN_MIN_BYTES`. A short token
|
|
82
|
+
* logs an error naming the file before the throw — it is a misconfiguration
|
|
83
|
+
* the operator must fix, never a quiet "unavailable". Twin of the Rust
|
|
84
|
+
* `bootstrap_token_from_file`.
|
|
85
|
+
*
|
|
86
|
+
* @param deps - the secure read and a logger for the short-token error
|
|
87
|
+
* @param token_path - the bootstrap token file
|
|
88
|
+
* @returns the token bytes the provided token is compared against
|
|
89
|
+
* @throws Error when the secure read refuses the file or the token is too short
|
|
90
|
+
*/
|
|
91
|
+
export declare const read_bootstrap_token: (deps: Pick<BootstrapAccountDeps, "read_secure_file" | "log">, token_path: string) => Promise<Uint8Array>;
|
|
69
92
|
/**
|
|
70
93
|
* Bootstrap the first account with keeper and admin privileges.
|
|
71
94
|
*
|
|
@@ -73,7 +96,8 @@ export interface BootstrapAccountDeps {
|
|
|
73
96
|
* attempts (TOCTOU). The full flow runs in a single transaction:
|
|
74
97
|
*
|
|
75
98
|
* 1. Read and verify the bootstrap token (before transaction) — the file's
|
|
76
|
-
* raw bytes less one optional trailing LF,
|
|
99
|
+
* raw bytes less one optional trailing LF, at least
|
|
100
|
+
* `BOOTSTRAP_TOKEN_MIN_BYTES` long, compared in constant time
|
|
77
101
|
* 2. Hash the password (CPU-intensive, before transaction)
|
|
78
102
|
* 3. Acquire the bootstrap lock atomically (inside transaction)
|
|
79
103
|
* 4. Create account + actor
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap_account.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_account.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EACN,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC,gDAAgD;AAChD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,6DAA6D;AAC7D,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;IACrD,wFAAwF;IACxF,kBAAkB,EAAE,OAAO,CAAC;CAC5B;AAED,gCAAgC;AAChC,MAAM,MAAM,uBAAuB,GAChC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,0BAA0B,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,GACpE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,wBAAwB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,GAClE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,mBAAmB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC;AAEjE,qFAAqF;AACrF,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,GAAG,uBAAuB,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,EAAE,CAAC;IACP,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAClD,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;CACZ;AAcD
|
|
1
|
+
{"version":3,"file":"bootstrap_account.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_account.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EACN,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC,gDAAgD;AAChD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,6DAA6D;AAC7D,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;IACrD,wFAAwF;IACxF,kBAAkB,EAAE,OAAO,CAAC;CAC5B;AAED,gCAAgC;AAChC,MAAM,MAAM,uBAAuB,GAChC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,0BAA0B,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,GACpE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,wBAAwB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,GAClE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,mBAAmB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC;AAEjE,qFAAqF;AACrF,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,GAAG,uBAAuB,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,EAAE,CAAC;IACP,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAClD,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;CACZ;AAcD;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,IAAI,CAAC,oBAAoB,EAAE,kBAAkB,GAAG,KAAK,CAAC,EAC5D,YAAY,MAAM,KAChB,OAAO,CAAC,UAAU,CAQpB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,oBAAoB,EAC1B,gBAAgB,MAAM,EACtB,OAAO,qBAAqB,KAC1B,OAAO,CAAC,sBAAsB,CA8EhC,CAAC"}
|
|
@@ -22,6 +22,37 @@ import { query_create_role_grant } from './role_grant_queries.js';
|
|
|
22
22
|
* `strip_one_trailing_newline` (`fuz_auth`).
|
|
23
23
|
*/
|
|
24
24
|
const strip_one_trailing_newline = (bytes) => bytes.at(-1) === 0x0a ? bytes.subarray(0, -1) : bytes;
|
|
25
|
+
/**
|
|
26
|
+
* The fewest bytes a bootstrap token holds, counted after its one optional
|
|
27
|
+
* trailing LF. A shorter file is read as unreadable: an empty or `"\n"` file
|
|
28
|
+
* would otherwise leave bootstrap advertised as open while nothing can ever
|
|
29
|
+
* match it, and a short token is guessable. Every generator writes at least
|
|
30
|
+
* this much (32 random bytes, base64url or hex encoded). Twin of the Rust
|
|
31
|
+
* `BOOTSTRAP_TOKEN_MIN_BYTES` (`fuz_auth`).
|
|
32
|
+
*/
|
|
33
|
+
export const BOOTSTRAP_TOKEN_MIN_BYTES = 32;
|
|
34
|
+
/**
|
|
35
|
+
* Read the bootstrap token the way both the availability check and the
|
|
36
|
+
* request-time compare need it: the hardened secure read, less one trailing
|
|
37
|
+
* LF, refused when shorter than `BOOTSTRAP_TOKEN_MIN_BYTES`. A short token
|
|
38
|
+
* logs an error naming the file before the throw — it is a misconfiguration
|
|
39
|
+
* the operator must fix, never a quiet "unavailable". Twin of the Rust
|
|
40
|
+
* `bootstrap_token_from_file`.
|
|
41
|
+
*
|
|
42
|
+
* @param deps - the secure read and a logger for the short-token error
|
|
43
|
+
* @param token_path - the bootstrap token file
|
|
44
|
+
* @returns the token bytes the provided token is compared against
|
|
45
|
+
* @throws Error when the secure read refuses the file or the token is too short
|
|
46
|
+
*/
|
|
47
|
+
export const read_bootstrap_token = async (deps, token_path) => {
|
|
48
|
+
const token = strip_one_trailing_newline(await deps.read_secure_file(token_path));
|
|
49
|
+
if (token.length < BOOTSTRAP_TOKEN_MIN_BYTES) {
|
|
50
|
+
const message = `bootstrap token at ${token_path} is ${token.length} bytes, under the ${BOOTSTRAP_TOKEN_MIN_BYTES}-byte minimum — regenerate it`;
|
|
51
|
+
deps.log.error(message);
|
|
52
|
+
throw new Error(message);
|
|
53
|
+
}
|
|
54
|
+
return token;
|
|
55
|
+
};
|
|
25
56
|
/**
|
|
26
57
|
* Bootstrap the first account with keeper and admin privileges.
|
|
27
58
|
*
|
|
@@ -29,7 +60,8 @@ const strip_one_trailing_newline = (bytes) => bytes.at(-1) === 0x0a ? bytes.suba
|
|
|
29
60
|
* attempts (TOCTOU). The full flow runs in a single transaction:
|
|
30
61
|
*
|
|
31
62
|
* 1. Read and verify the bootstrap token (before transaction) — the file's
|
|
32
|
-
* raw bytes less one optional trailing LF,
|
|
63
|
+
* raw bytes less one optional trailing LF, at least
|
|
64
|
+
* `BOOTSTRAP_TOKEN_MIN_BYTES` long, compared in constant time
|
|
33
65
|
* 2. Hash the password (CPU-intensive, before transaction)
|
|
34
66
|
* 3. Acquire the bootstrap lock atomically (inside transaction)
|
|
35
67
|
* 4. Create account + actor
|
|
@@ -48,11 +80,12 @@ export const bootstrap_account = async (deps, provided_token, input) => {
|
|
|
48
80
|
const { db, token_path, read_secure_file, delete_file, password, log } = deps;
|
|
49
81
|
// 1. Read and verify token (non-destructive, before transaction). The
|
|
50
82
|
// secure read fails loud on a symlink / non-regular file / permissive mode /
|
|
51
|
-
// oversized file
|
|
52
|
-
//
|
|
83
|
+
// oversized file, and the read refuses a token under the minimum length —
|
|
84
|
+
// every refusal masks as the same TOKEN_FILE_MISSING the wire always
|
|
85
|
+
// carried, with the specific reason logged for the operator.
|
|
53
86
|
let expected_token;
|
|
54
87
|
try {
|
|
55
|
-
expected_token =
|
|
88
|
+
expected_token = await read_bootstrap_token({ read_secure_file, log }, token_path);
|
|
56
89
|
}
|
|
57
90
|
catch (err) {
|
|
58
91
|
log.warn(`bootstrap token file rejected: ${to_error_message(err)}`);
|
|
@@ -65,10 +65,11 @@ export interface CheckBootstrapStatusDeps {
|
|
|
65
65
|
* Bootstrap is available when:
|
|
66
66
|
* 1. A token path is configured
|
|
67
67
|
* 2. The token file passes the secure read (exists, not a symlink, mode
|
|
68
|
-
* `0600`/`0400`, within the size cap)
|
|
68
|
+
* `0600`/`0400`, within the size cap) and holds at least
|
|
69
|
+
* `BOOTSTRAP_TOKEN_MIN_BYTES` (a shorter one logs an error)
|
|
69
70
|
* 3. The `bootstrap_lock` table shows `bootstrapped = false`
|
|
70
71
|
*
|
|
71
|
-
* The probe **reads through the same `
|
|
72
|
+
* The probe **reads through the same `read_bootstrap_token` the request-time
|
|
72
73
|
* read uses**, so "bootstrap is available" and "the token file can actually
|
|
73
74
|
* be read" can't drift apart — an availability check laxer than the read it
|
|
74
75
|
* gates is the misconfiguration that reports green at boot and fails at
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,
|
|
1
|
+
{"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,EAGN,KAAK,uBAAuB,EAC5B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGxE,OAAO,EAGN,KAAK,WAAW,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,8EAA8E;IAC9E,gBAAgB,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E;;;;;;;;;;OAUG;IACH,yBAAyB,EAAE,WAAW,GAAG,IAAI,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,kFAAkF;IAClF,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACxD,+EAA+E;IAC/E,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,wBAAwB,EAC9B,SAAS;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,KACpC,OAAO,CAAC,eAAe,CAyBzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,gBAAgB,EACtB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CA0HjB,CAAC"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { to_error_message } from '@fuzdev/fuz_util/error.ts';
|
|
10
10
|
import { create_session_and_set_cookie } from './session_middleware.js';
|
|
11
|
-
import { bootstrap_account } from './bootstrap_account.js';
|
|
11
|
+
import { bootstrap_account, read_bootstrap_token } from './bootstrap_account.js';
|
|
12
12
|
import { bootstrap_route_shape } from './bootstrap_route_schema.js';
|
|
13
13
|
import { get_route_input } from '../http/route_spec.js';
|
|
14
14
|
import { get_client_ip } from '../http/client_ip.js';
|
|
@@ -21,10 +21,11 @@ import { ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING } from '../http/er
|
|
|
21
21
|
* Bootstrap is available when:
|
|
22
22
|
* 1. A token path is configured
|
|
23
23
|
* 2. The token file passes the secure read (exists, not a symlink, mode
|
|
24
|
-
* `0600`/`0400`, within the size cap)
|
|
24
|
+
* `0600`/`0400`, within the size cap) and holds at least
|
|
25
|
+
* `BOOTSTRAP_TOKEN_MIN_BYTES` (a shorter one logs an error)
|
|
25
26
|
* 3. The `bootstrap_lock` table shows `bootstrapped = false`
|
|
26
27
|
*
|
|
27
|
-
* The probe **reads through the same `
|
|
28
|
+
* The probe **reads through the same `read_bootstrap_token` the request-time
|
|
28
29
|
* read uses**, so "bootstrap is available" and "the token file can actually
|
|
29
30
|
* be read" can't drift apart — an availability check laxer than the read it
|
|
30
31
|
* gates is the misconfiguration that reports green at boot and fails at
|
|
@@ -41,7 +42,7 @@ export const check_bootstrap_status = async (deps, options) => {
|
|
|
41
42
|
return { available: false, token_path: null };
|
|
42
43
|
}
|
|
43
44
|
try {
|
|
44
|
-
await read_secure_file
|
|
45
|
+
await read_bootstrap_token({ read_secure_file, log }, token_path);
|
|
45
46
|
}
|
|
46
47
|
catch (err) {
|
|
47
48
|
log.info(`Bootstrap unavailable: ${to_error_message(err)}`);
|