@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
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
* principals against the role schema; rejects owner-as-principal.
|
|
16
16
|
* Actor-shaped principals carry a pre-resolved `actor_id` (callers pick
|
|
17
17
|
* via `actor_search`). Idempotent — re-granting the same principal
|
|
18
|
-
* updates `level` via UPSERT.
|
|
18
|
+
* updates `level` via UPSERT. A mounted `authorize_grant_create` is asked
|
|
19
|
+
* after the spine's own gates, before the owner check and the write (403
|
|
20
|
+
* `cell_grant_not_authorized` on refusal).
|
|
19
21
|
* - `cell_grant_revoke` — admin / owner, plus self for actor-shaped grants
|
|
20
22
|
* ("leave shared cell"). Returns `still_admitted` computed by re-running
|
|
21
23
|
* `can_view_cell` against the remaining grants.
|
|
@@ -33,7 +35,7 @@
|
|
|
33
35
|
*/
|
|
34
36
|
import { rpc_action } from '../actions/action_rpc.js';
|
|
35
37
|
import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
|
|
36
|
-
import { cell_grant_create_action_spec, cell_grant_revoke_action_spec, cell_grant_list_action_spec, ERROR_CELL_GRANT_NOT_FOUND, ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER, ERROR_CELL_GRANT_UNAPPROVED, ERROR_CELL_GRANT_UNKNOWN_ROLE } from './cell_grant_action_specs.js';
|
|
38
|
+
import { cell_grant_create_action_spec, cell_grant_revoke_action_spec, cell_grant_list_action_spec, ERROR_CELL_GRANT_NOT_AUTHORIZED, ERROR_CELL_GRANT_NOT_FOUND, ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER, ERROR_CELL_GRANT_UNAPPROVED, ERROR_CELL_GRANT_UNKNOWN_ROLE } from './cell_grant_action_specs.js';
|
|
37
39
|
import { ERROR_CELL_NOT_FOUND } from './cell_action_specs.js';
|
|
38
40
|
import { can_view_cell, can_manage_cell } from './cell_authorize.js';
|
|
39
41
|
import { query_cell_get } from '../db/cell_queries.js';
|
|
@@ -90,7 +92,7 @@ const assert_principal_is_not_owner = (cell, principal) => {
|
|
|
90
92
|
};
|
|
91
93
|
/** Create the three `cell_grant_*` RPC actions. */
|
|
92
94
|
export const create_cell_grant_actions = (deps) => {
|
|
93
|
-
const { roles } = deps;
|
|
95
|
+
const { roles, authorize_grant_create } = deps;
|
|
94
96
|
const create_handler = async (input, ctx) => {
|
|
95
97
|
const auth = ctx.auth;
|
|
96
98
|
const cell = await query_cell_get(ctx, input.cell_id);
|
|
@@ -127,6 +129,21 @@ export const create_cell_grant_actions = (deps) => {
|
|
|
127
129
|
reason: ERROR_CELL_GRANT_UNKNOWN_ROLE
|
|
128
130
|
});
|
|
129
131
|
}
|
|
132
|
+
// The consumer's gate, asked only of a manager past the spine's gates;
|
|
133
|
+
// a refusal writes nothing.
|
|
134
|
+
if (authorize_grant_create) {
|
|
135
|
+
const allowed = await authorize_grant_create(auth, {
|
|
136
|
+
cell_id: cell.id,
|
|
137
|
+
cell_created_by: cell.created_by,
|
|
138
|
+
level: input.level,
|
|
139
|
+
principal
|
|
140
|
+
});
|
|
141
|
+
if (!allowed) {
|
|
142
|
+
throw jsonrpc_errors.forbidden('cell grant is not permitted here', {
|
|
143
|
+
reason: ERROR_CELL_GRANT_NOT_AUTHORIZED
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
130
147
|
assert_principal_is_not_owner(cell, principal);
|
|
131
148
|
const row = await query_cell_grant_create(ctx, {
|
|
132
149
|
cell_id: cell.id,
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* Open-string registry on top so consumers can declare additional
|
|
7
7
|
* credential types (e.g. `'sso_assertion'`, `'agent_token'`) without an
|
|
8
8
|
* upstream release. `RoleSpec.required_credential_types` references
|
|
9
|
-
* entries from this registry; v1 keeps the field informative-only
|
|
10
|
-
*
|
|
9
|
+
* entries from this registry; v1 keeps the field informative-only —
|
|
10
|
+
* validated at construction, enforced nowhere; routes and actions gate
|
|
11
|
+
* with `auth.credential_types`. Mirrors the
|
|
11
12
|
* open-registry pattern used for `RoleName`, `ScopeKindName`,
|
|
12
13
|
* `GrantPathName`, and `AuditEventTypeName`.
|
|
13
14
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential_type_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/credential_type_schema.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"credential_type_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/credential_type_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,QAAgC,CAAC;AAExE,yDAAyD;AACzD,eAAO,MAAM,kBAAkB,aAK7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAIpE,uCAAuC;AACvC,eAAO,MAAM,uBAAuB,YAAY,CAAC;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD,yEAAyE;AACzE,eAAO,MAAM,4BAA4B,iBAAiB,CAAC;AAE3D,0DAA0D;AAC1D,eAAO,MAAM,wBAAwB,mDAI3B,CAAC;AAEX,kDAAkD;AAClD,eAAO,MAAM,qBAAqB;;;;EAAmC,CAAC;AACtE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE1E;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,EAAE,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAa/E,CAAC;AAEH,qFAAqF;AACrF,MAAM,WAAW,0BAA0B;IAC1C;;;;;;OAMG;IACH,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC;;;OAGG;IACH,gBAAgB,EAAE,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,6BAA6B,GACzC,iBAAgB,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAM,KACrD,0BA2BF,CAAC"}
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* Open-string registry on top so consumers can declare additional
|
|
7
7
|
* credential types (e.g. `'sso_assertion'`, `'agent_token'`) without an
|
|
8
8
|
* upstream release. `RoleSpec.required_credential_types` references
|
|
9
|
-
* entries from this registry; v1 keeps the field informative-only
|
|
10
|
-
*
|
|
9
|
+
* entries from this registry; v1 keeps the field informative-only —
|
|
10
|
+
* validated at construction, enforced nowhere; routes and actions gate
|
|
11
|
+
* with `auth.credential_types`. Mirrors the
|
|
11
12
|
* open-registry pattern used for `RoleName`, `ScopeKindName`,
|
|
12
13
|
* `GrantPathName`, and `AuditEventTypeName`.
|
|
13
14
|
*
|
|
@@ -44,6 +44,22 @@ export declare const ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH: "role_grant_
|
|
|
44
44
|
* `role_grant_assign`, `role_grant_offer_create`, and `role_grant_offer_accept`.
|
|
45
45
|
*/
|
|
46
46
|
export declare const ERROR_ROLE_GRANT_BUILTIN_SCOPED: "role_grant_builtin_scoped";
|
|
47
|
+
/**
|
|
48
|
+
* Error reason — `role_grant_revoke` targeted the **keeper** account's global
|
|
49
|
+
* `admin` grant. A **403**: no web path grants `admin` to the keeper again
|
|
50
|
+
* (bootstrap is one-shot and keeper is not web-grantable), so revoking it would
|
|
51
|
+
* lock the keeper out of the admin surface for good. Twin of the Rust
|
|
52
|
+
* `ERROR_CANNOT_REVOKE_KEEPER_ADMIN`.
|
|
53
|
+
*/
|
|
54
|
+
export declare const ERROR_CANNOT_REVOKE_KEEPER_ADMIN: "cannot_revoke_keeper_admin";
|
|
55
|
+
/**
|
|
56
|
+
* Error reason — `role_grant_revoke` targeted a global `admin` grant whose
|
|
57
|
+
* revoke would leave no active admin account. A **403**, counted as
|
|
58
|
+
* `account_delete`'s last-admin guard counts (active accounts, active actors),
|
|
59
|
+
* less the grant being revoked, under the same row locks. Twin of the Rust
|
|
60
|
+
* `ERROR_CANNOT_REVOKE_LAST_ADMIN`.
|
|
61
|
+
*/
|
|
62
|
+
export declare const ERROR_CANNOT_REVOKE_LAST_ADMIN: "cannot_revoke_last_admin";
|
|
47
63
|
/**
|
|
48
64
|
* Whether a grant input's `scope_kind` + `scope_id` are paired — both absent
|
|
49
65
|
* (a global grant) or both present (a scoped one), the
|
|
@@ -510,7 +526,7 @@ export declare const role_grant_revoke_action_spec: {
|
|
|
510
526
|
}, z.core.$strict>;
|
|
511
527
|
async: true;
|
|
512
528
|
description: string;
|
|
513
|
-
error_reasons: ("role_not_web_grantable" | "role_grant_not_found")[];
|
|
529
|
+
error_reasons: ("role_not_web_grantable" | "role_grant_not_found" | "cannot_revoke_keeper_admin" | "cannot_revoke_last_admin")[];
|
|
514
530
|
rate_limit: "account";
|
|
515
531
|
};
|
|
516
532
|
/**
|
|
@@ -542,7 +558,7 @@ export declare const role_grant_assign_action_spec: {
|
|
|
542
558
|
}, z.core.$strict>;
|
|
543
559
|
async: true;
|
|
544
560
|
description: string;
|
|
545
|
-
error_reasons: ("insufficient_permissions" | "role_not_web_grantable" | "role_grant_offer_actor_account_mismatch" | "role_grant_builtin_scoped")[];
|
|
561
|
+
error_reasons: ("insufficient_permissions" | "role_not_web_grantable" | "role_grant_offer_not_authorized" | "role_grant_offer_actor_account_mismatch" | "role_grant_builtin_scoped")[];
|
|
546
562
|
rate_limit: "account";
|
|
547
563
|
};
|
|
548
564
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"role_grant_offer_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAiB3E,oEAAoE;AACpE,eAAO,MAAM,kCAAkC,EAAG,8BAAuC,CAAC;AAC1F,kEAAkE;AAClE,eAAO,MAAM,+BAA+B,EAAG,2BAAoC,CAAC;AACpF,sDAAsD;AACtD,eAAO,MAAM,8BAA8B,EAAG,0BAAmC,CAAC;AAClF,wGAAwG;AACxG,eAAO,MAAM,gCAAgC,EAAG,4BAAqC,CAAC;AACtF,uIAAuI;AACvI,eAAO,MAAM,yCAAyC,EACrD,qCAA8C,CAAC;AAChD,gKAAgK;AAChK,eAAO,MAAM,qCAAqC,EAAG,iCAA0C,CAAC;AAChG,6FAA6F;AAC7F,eAAO,MAAM,qCAAqC,EAAG,iCAA0C,CAAC;AAChG,wHAAwH;AACxH,eAAO,MAAM,6CAA6C,EACzD,yCAAkD,CAAC;AACpD;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,EAAG,2BAAoC,CAAC;AACpF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,EAAG,4BAAqC,CAAC;AACtF;;;;;;GAMG;AACH,eAAO,MAAM,8BAA8B,EAAG,0BAAmC,CAAC;AAElF;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO;IAC5C,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,KAAG,OAAkE,CAAC;AAEvE,+GAA+G;AAC/G,eAAO,MAAM,0BAA0B,2GACkE,CAAC;AAW1G;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;kBAiBkC,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,2CAA2C;AAC3C,eAAO,MAAM,yBAAyB;;;kBAGpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,4CAA4C;AAC5C,eAAO,MAAM,0BAA0B;;;;kBAMrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF,4CAA4C;AAC5C,eAAO,MAAM,0BAA0B;;;kBAGrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF,uGAAuG;AACvG,eAAO,MAAM,uBAAuB;;;mBAOvB,CAAC;AACd,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;;;kBAQ/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,gDAAgD;AAChD,eAAO,MAAM,kCAAkC,MAAM,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;;;mBAe1B,CAAC;AACd,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF,4CAA4C;AAC5C,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;kBAErC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF,4CAA4C;AAC5C,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;kBAIrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB;;kBAA0C,CAAC;AAC9E,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,0CAA0C;AAC1C,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;kBAA0D,CAAC;AAChG,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF,6CAA6C;AAC7C,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;kBAA0D,CAAC;AACnG,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEtF,sCAAsC;AACtC,eAAO,MAAM,qBAAqB;;;kBAGhC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE1E;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB;;;;;;;kBAiBuC,CAAC;AACzE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,sCAAsC;AACtC,eAAO,MAAM,qBAAqB;;;kBAGhC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmBX,CAAC;AAEtC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqBX,CAAC;AAEtC,eAAO,MAAM,oCAAoC;;;;;;;;;;;;;;;;;;;;CAWZ,CAAC;AAEtC,eAAO,MAAM,oCAAoC;;;;;;;;;;;;;;;;;;;CAWZ,CAAC;AAEtC,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWT,CAAC;AAEtC,eAAO,MAAM,oCAAoC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWZ,CAAC;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;CAkBL,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;CAmBL,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,EAAE,KAAK,CAAC,yBAAyB,CAS9E,CAAC"}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { z } from 'zod';
|
|
24
24
|
import { Uuid, bounded_string } from '../primitive_schemas.js';
|
|
25
|
-
import { ERROR_INSUFFICIENT_PERMISSIONS, ERROR_ROLE_GRANT_NOT_FOUND, ERROR_ROLE_NOT_WEB_GRANTABLE } from '../http/error_schemas.js';
|
|
25
|
+
import { ERROR_INSUFFICIENT_PERMISSIONS, ERROR_LIMIT_INVALID, ERROR_OFFSET_INVALID, ERROR_ROLE_GRANT_NOT_FOUND, ERROR_ROLE_NOT_WEB_GRANTABLE } from '../http/error_schemas.js';
|
|
26
26
|
import { RoleName } from './role_schema.js';
|
|
27
27
|
import { SCOPE_KIND_LENGTH_MAX } from './scope_kind_schema.js';
|
|
28
28
|
import { ROLE_GRANT_OFFER_MESSAGE_LENGTH_MAX, RoleGrantOfferJson } from './role_grant_offer_schema.js';
|
|
@@ -50,6 +50,22 @@ export const ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH = 'role_grant_offer_a
|
|
|
50
50
|
* `role_grant_assign`, `role_grant_offer_create`, and `role_grant_offer_accept`.
|
|
51
51
|
*/
|
|
52
52
|
export const ERROR_ROLE_GRANT_BUILTIN_SCOPED = 'role_grant_builtin_scoped';
|
|
53
|
+
/**
|
|
54
|
+
* Error reason — `role_grant_revoke` targeted the **keeper** account's global
|
|
55
|
+
* `admin` grant. A **403**: no web path grants `admin` to the keeper again
|
|
56
|
+
* (bootstrap is one-shot and keeper is not web-grantable), so revoking it would
|
|
57
|
+
* lock the keeper out of the admin surface for good. Twin of the Rust
|
|
58
|
+
* `ERROR_CANNOT_REVOKE_KEEPER_ADMIN`.
|
|
59
|
+
*/
|
|
60
|
+
export const ERROR_CANNOT_REVOKE_KEEPER_ADMIN = 'cannot_revoke_keeper_admin';
|
|
61
|
+
/**
|
|
62
|
+
* Error reason — `role_grant_revoke` targeted a global `admin` grant whose
|
|
63
|
+
* revoke would leave no active admin account. A **403**, counted as
|
|
64
|
+
* `account_delete`'s last-admin guard counts (active accounts, active actors),
|
|
65
|
+
* less the grant being revoked, under the same row locks. Twin of the Rust
|
|
66
|
+
* `ERROR_CANNOT_REVOKE_LAST_ADMIN`.
|
|
67
|
+
*/
|
|
68
|
+
export const ERROR_CANNOT_REVOKE_LAST_ADMIN = 'cannot_revoke_last_admin';
|
|
53
69
|
/**
|
|
54
70
|
* Whether a grant input's `scope_kind` + `scope_id` are paired — both absent
|
|
55
71
|
* (a global grant) or both present (a scoped one), the
|
|
@@ -153,10 +169,12 @@ export const RoleGrantOfferHistoryInput = z
|
|
|
153
169
|
description: 'Admin-only — history for another account. Defaults to the caller.'
|
|
154
170
|
}),
|
|
155
171
|
limit: z.number().int().min(1).max(ROLE_GRANT_OFFER_HISTORY_LIMIT_MAX).nullish().meta({
|
|
156
|
-
description: 'Max rows to return (default 100).'
|
|
172
|
+
description: 'Max rows to return (default 100).',
|
|
173
|
+
invalid_reason: ERROR_LIMIT_INVALID
|
|
157
174
|
}),
|
|
158
175
|
offset: z.number().int().min(0).nullish().meta({
|
|
159
|
-
description: 'Pagination offset (default 0).'
|
|
176
|
+
description: 'Pagination offset (default 0).',
|
|
177
|
+
invalid_reason: ERROR_OFFSET_INVALID
|
|
160
178
|
}),
|
|
161
179
|
acting: ActingActor
|
|
162
180
|
})
|
|
@@ -338,8 +356,13 @@ export const role_grant_revoke_action_spec = {
|
|
|
338
356
|
input: RoleGrantRevokeInput,
|
|
339
357
|
output: RoleGrantRevokeOutput,
|
|
340
358
|
async: true,
|
|
341
|
-
description:
|
|
342
|
-
error_reasons: [
|
|
359
|
+
description: "Revoke an active role_grant on a target actor. Admin-only. Refuses the keeper account's global admin grant and the last active admin's. Supersedes any pending offers for the same (account, role, scope), and every pending offer the target actor made. Fires role_grant_revoke + role_grant_offer_supersede notifications.",
|
|
360
|
+
error_reasons: [
|
|
361
|
+
ERROR_ROLE_GRANT_NOT_FOUND,
|
|
362
|
+
ERROR_ROLE_NOT_WEB_GRANTABLE,
|
|
363
|
+
ERROR_CANNOT_REVOKE_KEEPER_ADMIN,
|
|
364
|
+
ERROR_CANNOT_REVOKE_LAST_ADMIN
|
|
365
|
+
],
|
|
343
366
|
rate_limit: 'account'
|
|
344
367
|
};
|
|
345
368
|
/**
|
|
@@ -356,11 +379,12 @@ export const role_grant_assign_action_spec = {
|
|
|
356
379
|
input: RoleGrantAssignInput,
|
|
357
380
|
output: RoleGrantAssignOutput,
|
|
358
381
|
async: true,
|
|
359
|
-
description: "Immediately assign a role_grant to a target account — global-admin-only, no consent step (the capability-unlock UX). The role's `grant_paths` must include `'admin'
|
|
382
|
+
description: "Immediately assign a role_grant to a target account — global-admin-only, no consent step (the capability-unlock UX). The role's `grant_paths` must include `'admin'`, and the consumer's offer `authorize` callback must admit it; `scope_kind` + `scope_id` are paired, and a builtin role is global-only. Idempotent: re-assigning an active grant returns it. Emits a role_grant_create audit event.",
|
|
360
383
|
error_reasons: [
|
|
361
384
|
ERROR_INSUFFICIENT_PERMISSIONS,
|
|
362
385
|
ERROR_ROLE_NOT_WEB_GRANTABLE,
|
|
363
386
|
ERROR_ROLE_GRANT_BUILTIN_SCOPED,
|
|
387
|
+
ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED,
|
|
364
388
|
ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH
|
|
365
389
|
],
|
|
366
390
|
rate_limit: 'account'
|
|
@@ -57,17 +57,22 @@ import { type RequestContext } from './request_context.js';
|
|
|
57
57
|
import type { ActionFactoryDeps } from './deps.js';
|
|
58
58
|
import { type NotificationSender } from './role_grant_offer_notifications.js';
|
|
59
59
|
/**
|
|
60
|
-
* Authorization callback for `role_grant_offer_create
|
|
61
|
-
* `false` to reject (handler converts to `forbidden`
|
|
60
|
+
* Authorization callback for `role_grant_offer_create` and `role_grant_assign`.
|
|
61
|
+
* Returns `true` to allow, `false` to reject (handler converts to `forbidden`
|
|
62
|
+
* with `role_grant_offer_not_authorized`).
|
|
62
63
|
*
|
|
63
64
|
* Provided with the fully-resolved request context and the parsed input
|
|
64
65
|
* (pre-TTL, pre-normalization). Consumers override the default to implement
|
|
65
66
|
* policies like "teacher may offer classroom_student only in classrooms they
|
|
66
|
-
* teach".
|
|
67
|
+
* teach". `role_grant_assign` — the global-admin path that confers a grant
|
|
68
|
+
* with no offer — runs it too, so a policy over which grants may exist (a
|
|
69
|
+
* role that must be scoped, a scope that must be real) holds on both paths.
|
|
70
|
+
* Twin of the Rust `OfferCreateAuthorize`.
|
|
67
71
|
*/
|
|
68
72
|
export type RoleGrantOfferCreateAuthorize = (auth: RequestContext, input: {
|
|
69
73
|
to_account_id: string;
|
|
70
74
|
role: string;
|
|
75
|
+
scope_kind: string | null;
|
|
71
76
|
scope_id: string | null;
|
|
72
77
|
}, deps: {
|
|
73
78
|
log: Logger;
|
|
@@ -84,7 +89,7 @@ export interface RoleGrantOfferActionOptions {
|
|
|
84
89
|
/** TTL applied to newly-created offers. Defaults to `ROLE_GRANT_OFFER_DEFAULT_TTL_MS`. */
|
|
85
90
|
default_ttl_ms?: number;
|
|
86
91
|
/**
|
|
87
|
-
* Custom authorization for `role_grant_offer_create`. The default requires the
|
|
92
|
+
* Custom authorization for `role_grant_offer_create` and `role_grant_assign`. The default requires the
|
|
88
93
|
* caller to hold a **global** `admin` grant; the role's
|
|
89
94
|
* `RoleSpec.grant_paths` must include `'admin'` either way. Consumers with
|
|
90
95
|
* richer policies (scope-aware, chained roles) override this. A builtin
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,
|
|
1
|
+
{"version":3,"file":"role_grant_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAMN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AAgC1B,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,qCAAqC,CAAC;AA2C7C;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC3C,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IACN,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,EACD,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EACrB,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,6BAA6B,CAAC;CAC1C;AA6CD;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,iBAAiB,GAAG;IACzB,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD,EACD,UAAS,2BAAgC,KACvC,KAAK,CAAC,SAAS,CAgsBjB,CAAC"}
|
|
@@ -53,16 +53,16 @@
|
|
|
53
53
|
import { rpc_action } from '../actions/action_rpc.js';
|
|
54
54
|
import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
|
|
55
55
|
import { emit_after_commit } from '../http/pending_effects.js';
|
|
56
|
-
import { builtin_role_specs_by_name, is_builtin_role, ROLE_ADMIN, role_has_grant_path } from './role_schema.js';
|
|
56
|
+
import { builtin_role_specs_by_name, is_builtin_role, ROLE_ADMIN, ROLE_KEEPER, role_has_grant_path } from './role_schema.js';
|
|
57
57
|
import { GRANT_PATH_ADMIN } from './grant_path_schema.js';
|
|
58
58
|
import { ROLE_GRANT_OFFER_DEFAULT_TTL_MS, to_role_grant_offer_json } from './role_grant_offer_schema.js';
|
|
59
|
-
import { query_role_grant_offer_create, query_role_grant_offer_decline, query_role_grant_offer_retract, query_role_grant_offer_list, query_role_grant_offer_history_for_account, query_accept_offer, RoleGrantOfferActorAccountMismatchError, RoleGrantOfferActorMismatchError, RoleGrantOfferAlreadyTerminalError, RoleGrantOfferBuiltinRefusedError, RoleGrantOfferExpiredError, RoleGrantOfferNotFoundError, RoleGrantOfferSelfTargetError } from './role_grant_offer_queries.js';
|
|
60
|
-
import { query_create_role_grant, query_role_grant_find_active_role_for_actor, query_revoke_role_grant } from './role_grant_queries.js';
|
|
59
|
+
import { query_role_grant_offer_create, query_role_grant_offer_decline, query_role_grant_offer_retract, query_role_grant_offer_list, query_role_grant_offer_history_for_account, query_accept_offer, RoleGrantOfferActorAccountMismatchError, RoleGrantOfferActorMismatchError, RoleGrantOfferAlreadyTerminalError, RoleGrantOfferBuiltinRefusedError, RoleGrantOfferExpiredError, RoleGrantOfferNotFoundError, RoleGrantOfferSelfTargetError, query_role_grant_offer_supersede_outgoing } from './role_grant_offer_queries.js';
|
|
60
|
+
import { query_account_has_active_global_role, query_account_has_global_role, query_count_active_accounts_with_global_role_for_update, query_create_role_grant, query_role_grant_find_active_role_for_actor, query_revoke_role_grant } from './role_grant_queries.js';
|
|
61
61
|
import { query_actor_by_id, query_active_actors_by_account } from './account_queries.js';
|
|
62
62
|
import { has_scoped_role } from './request_context.js';
|
|
63
63
|
import { build_role_grant_offer_accepted_notification, build_role_grant_offer_declined_notification, build_role_grant_offer_received_notification, build_role_grant_offer_retracted_notification, build_role_grant_offer_supersede_notification, build_role_grant_revoke_notification } from './role_grant_offer_notifications.js';
|
|
64
64
|
import { ERROR_INSUFFICIENT_PERMISSIONS, ERROR_ROLE_GRANT_NOT_FOUND, ERROR_ROLE_NOT_WEB_GRANTABLE } from '../http/error_schemas.js';
|
|
65
|
-
import { ERROR_ROLE_GRANT_BUILTIN_SCOPED, ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH, ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH, ERROR_ROLE_GRANT_OFFER_EXPIRED, ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED, ERROR_ROLE_GRANT_OFFER_NOT_FOUND, ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE, ERROR_ROLE_GRANT_OFFER_SELF_TARGET, ERROR_ROLE_GRANT_OFFER_TERMINAL, role_grant_offer_create_action_spec, role_grant_offer_accept_action_spec, role_grant_offer_decline_action_spec, role_grant_offer_retract_action_spec, role_grant_offer_list_action_spec, role_grant_offer_history_action_spec, role_grant_revoke_action_spec, role_grant_assign_action_spec } from './role_grant_offer_action_specs.js';
|
|
65
|
+
import { ERROR_CANNOT_REVOKE_KEEPER_ADMIN, ERROR_CANNOT_REVOKE_LAST_ADMIN, ERROR_ROLE_GRANT_BUILTIN_SCOPED, ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH, ERROR_ROLE_GRANT_OFFER_ACTOR_MISMATCH, ERROR_ROLE_GRANT_OFFER_EXPIRED, ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED, ERROR_ROLE_GRANT_OFFER_NOT_FOUND, ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE, ERROR_ROLE_GRANT_OFFER_SELF_TARGET, ERROR_ROLE_GRANT_OFFER_TERMINAL, role_grant_offer_create_action_spec, role_grant_offer_accept_action_spec, role_grant_offer_decline_action_spec, role_grant_offer_retract_action_spec, role_grant_offer_list_action_spec, role_grant_offer_history_action_spec, role_grant_revoke_action_spec, role_grant_assign_action_spec } from './role_grant_offer_action_specs.js';
|
|
66
66
|
// -- Helpers ----------------------------------------------------------------
|
|
67
67
|
/**
|
|
68
68
|
* Fan out a batch of pre-written audit rows to the bound emitter's
|
|
@@ -203,6 +203,7 @@ export const create_role_grant_offer_actions = (deps, options = {}) => {
|
|
|
203
203
|
const authorized = await authorize(auth, {
|
|
204
204
|
to_account_id: input.to_account_id,
|
|
205
205
|
role: input.role,
|
|
206
|
+
scope_kind: input.scope_kind ?? null,
|
|
206
207
|
scope_id: input.scope_id ?? null
|
|
207
208
|
}, { log }, ctx);
|
|
208
209
|
if (!authorized) {
|
|
@@ -459,7 +460,7 @@ export const create_role_grant_offer_actions = (deps, options = {}) => {
|
|
|
459
460
|
const target_actor_id = input.actor_id;
|
|
460
461
|
// Admin-grant-path gate — keeper / daemon-scoped roles stay CLI-only
|
|
461
462
|
// (their `grant_paths` does not include `'admin'`).
|
|
462
|
-
|
|
463
|
+
const emit_revoke_failure = () => {
|
|
463
464
|
audit.emit_role_grant_target(ctx, auth, {
|
|
464
465
|
event_type: 'role_grant_revoke',
|
|
465
466
|
outcome: 'failure',
|
|
@@ -467,16 +468,46 @@ export const create_role_grant_offer_actions = (deps, options = {}) => {
|
|
|
467
468
|
target_actor_id,
|
|
468
469
|
metadata: { role: role_grant_row.role, role_grant_id: input.role_grant_id }
|
|
469
470
|
});
|
|
471
|
+
};
|
|
472
|
+
if (!role_has_grant_path(role_specs, role_grant_row.role, GRANT_PATH_ADMIN)) {
|
|
473
|
+
emit_revoke_failure();
|
|
470
474
|
throw jsonrpc_errors.forbidden('role not web-grantable', {
|
|
471
475
|
reason: ERROR_ROLE_NOT_WEB_GRANTABLE
|
|
472
476
|
});
|
|
473
477
|
}
|
|
478
|
+
// The global `admin` grant is guarded like the account that holds it:
|
|
479
|
+
// the keeper account's is never revoked (no web path re-grants it, so the
|
|
480
|
+
// keeper would be locked out of the admin surface for good), and the
|
|
481
|
+
// grant goes only when an active admin account remains without it — the
|
|
482
|
+
// count leaves this grant out, so a second admin actor on the same
|
|
483
|
+
// account still counts. The count locks what it counted, so a concurrent
|
|
484
|
+
// revoke or account removal can't pass the same guard.
|
|
485
|
+
if (role_grant_row.role === ROLE_ADMIN && role_grant_row.scope_id === null) {
|
|
486
|
+
if (await query_account_has_global_role(ctx, target_account_id, ROLE_KEEPER)) {
|
|
487
|
+
emit_revoke_failure();
|
|
488
|
+
throw jsonrpc_errors.forbidden("cannot revoke the keeper account's admin grant", {
|
|
489
|
+
reason: ERROR_CANNOT_REVOKE_KEEPER_ADMIN
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
if ((await query_account_has_active_global_role(ctx, target_account_id, ROLE_ADMIN)) &&
|
|
493
|
+
(await query_count_active_accounts_with_global_role_for_update(ctx, ROLE_ADMIN, input.role_grant_id)) === 0) {
|
|
494
|
+
emit_revoke_failure();
|
|
495
|
+
throw jsonrpc_errors.forbidden("cannot revoke the last admin's admin grant", {
|
|
496
|
+
reason: ERROR_CANNOT_REVOKE_LAST_ADMIN
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
474
500
|
const result = await query_revoke_role_grant(ctx, input.role_grant_id, input.actor_id, auth.actor.id, input.reason ?? null);
|
|
475
501
|
if (!result) {
|
|
476
502
|
// Raced with another revoker or the role_grant was revoked between
|
|
477
503
|
// the IDOR check and the UPDATE.
|
|
478
504
|
throw jsonrpc_errors.not_found('role_grant', { reason: ERROR_ROLE_GRANT_NOT_FOUND });
|
|
479
505
|
}
|
|
506
|
+
// The revokee's own pending offers end with the grant: an offer records
|
|
507
|
+
// no authority, so one made under this grant would still confer on
|
|
508
|
+
// accept. Every outgoing offer of the actor goes, superseded like the
|
|
509
|
+
// revokee's incoming siblings (same reason, same cause, same fan-out).
|
|
510
|
+
result.superseded_offers.push(...(await query_role_grant_offer_supersede_outgoing(ctx, [input.actor_id])));
|
|
480
511
|
audit.emit_role_grant_target(ctx, auth, {
|
|
481
512
|
event_type: 'role_grant_revoke',
|
|
482
513
|
target_account_id,
|
|
@@ -535,7 +566,8 @@ export const create_role_grant_offer_actions = (deps, options = {}) => {
|
|
|
535
566
|
// `role_grant_offer_create`. Gated at the dispatcher on a **global** admin
|
|
536
567
|
// (`roles: ['admin']`) and re-checked here, it re-checks admin-grantability
|
|
537
568
|
// (so keeper / CLI-only roles can't be web-assigned), refuses a scoped
|
|
538
|
-
// builtin role,
|
|
569
|
+
// builtin role, runs the consumer's `authorize` callback, resolves the
|
|
570
|
+
// target actor, writes the idempotent grant with
|
|
539
571
|
// the input's `(scope_kind, scope_id)` pair, and emits a `role_grant_create`
|
|
540
572
|
// audit row — the same event the offer-accept and self-service paths emit.
|
|
541
573
|
// No WS notification in v1 (the grantee picks the capability up on its next
|
|
@@ -555,7 +587,7 @@ export const create_role_grant_offer_actions = (deps, options = {}) => {
|
|
|
555
587
|
// omitted — no grant was created).
|
|
556
588
|
const admin_grantable = role_has_grant_path(role_specs, input.role, GRANT_PATH_ADMIN);
|
|
557
589
|
const builtin_scoped = is_scoped_builtin(input.role, input.scope_id);
|
|
558
|
-
|
|
590
|
+
const emit_assign_failure_audit = () => {
|
|
559
591
|
audit.emit_role_grant_target(ctx, auth, {
|
|
560
592
|
event_type: 'role_grant_create',
|
|
561
593
|
outcome: 'failure',
|
|
@@ -567,12 +599,31 @@ export const create_role_grant_offer_actions = (deps, options = {}) => {
|
|
|
567
599
|
scope_id: input.scope_id ?? null
|
|
568
600
|
}
|
|
569
601
|
});
|
|
602
|
+
};
|
|
603
|
+
if (builtin_scoped || !admin_grantable) {
|
|
604
|
+
emit_assign_failure_audit();
|
|
570
605
|
if (builtin_scoped && admin_grantable)
|
|
571
606
|
throw builtin_scoped_error();
|
|
572
607
|
throw jsonrpc_errors.forbidden('role not web-grantable', {
|
|
573
608
|
reason: ERROR_ROLE_NOT_WEB_GRANTABLE
|
|
574
609
|
});
|
|
575
610
|
}
|
|
611
|
+
// The consumer's offer policy binds the immediate path too: assign is an
|
|
612
|
+
// offer the grantee doesn't get to decline, so a grant the `authorize`
|
|
613
|
+
// callback would refuse as an offer (a scope that doesn't exist, a role
|
|
614
|
+
// that must be scoped) is refused here, with the offer path's reason.
|
|
615
|
+
const authorized = await authorize(auth, {
|
|
616
|
+
to_account_id: input.to_account_id,
|
|
617
|
+
role: input.role,
|
|
618
|
+
scope_kind: input.scope_kind ?? null,
|
|
619
|
+
scope_id: input.scope_id ?? null
|
|
620
|
+
}, { log }, ctx);
|
|
621
|
+
if (!authorized) {
|
|
622
|
+
emit_assign_failure_audit();
|
|
623
|
+
throw jsonrpc_errors.forbidden('not authorized to grant this role', {
|
|
624
|
+
reason: ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED
|
|
625
|
+
});
|
|
626
|
+
}
|
|
576
627
|
// Resolve the grant's target actor. A supplied `to_actor_id` must be an
|
|
577
628
|
// active actor of `to_account_id`; otherwise the account's sole active
|
|
578
629
|
// actor is used (a multi-actor account must name one). A missing /
|
|
@@ -151,8 +151,9 @@ export type RoleGrantOfferDeclinedParams = z.infer<typeof RoleGrantOfferDeclined
|
|
|
151
151
|
/**
|
|
152
152
|
* Params for `role_grant_offer_supersede`. Fires to the grantor's sockets when
|
|
153
153
|
* their pending offer is obsoleted — either by a sibling accept
|
|
154
|
-
* (`reason: 'sibling_accepted'`), by revoke of the resulting role_grant
|
|
155
|
-
* (`reason: 'role_grant_revoked'`), or by deletion of
|
|
154
|
+
* (`reason: 'sibling_accepted'`), by revoke of the resulting role_grant or of
|
|
155
|
+
* one of the grantor's own (`reason: 'role_grant_revoked'`), or by deletion of
|
|
156
|
+
* the parent scope row
|
|
156
157
|
* the offer was bound to (`reason: 'scope_destroyed'`). `cause_id` points
|
|
157
158
|
* at the accepted offer id, the revoked role_grant id, or the destroyed scope
|
|
158
159
|
* row id respectively.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_notifications.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAsB,KAAK,IAAI,EAAkB,MAAM,yBAAyB,CAAC;AAIxF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAM9D;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,kBAAkB;IAClC,eAAe,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,mBAAmB,KAAK,MAAM,CAAC;CAC5E;AAID,eAAO,MAAM,6CAA6C,8BAA8B,CAAC;AACzF,eAAO,MAAM,8CAA8C,+BAA+B,CAAC;AAC3F,eAAO,MAAM,6CAA6C,8BAA8B,CAAC;AACzF,eAAO,MAAM,6CAA6C,8BAA8B,CAAC;AACzF,eAAO,MAAM,8CAA8C,+BAA+B,CAAC;AAC3F,eAAO,MAAM,qCAAqC,sBAAsB,CAAC;AAIzE,iFAAiF;AACjF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;kBAEvC,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAExF,yEAAyE;AACzE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;kBAExC,CAAC;AACH,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAE1F,6EAA6E;AAC7E,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;kBAEvC,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAExF;;;;GAIG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;kBAEvC,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAExF
|
|
1
|
+
{"version":3,"file":"role_grant_offer_notifications.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_notifications.ts"],"names":[],"mappings":"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"}
|
|
@@ -67,8 +67,9 @@ export const RoleGrantOfferDeclinedParams = z.strictObject({
|
|
|
67
67
|
/**
|
|
68
68
|
* Params for `role_grant_offer_supersede`. Fires to the grantor's sockets when
|
|
69
69
|
* their pending offer is obsoleted — either by a sibling accept
|
|
70
|
-
* (`reason: 'sibling_accepted'`), by revoke of the resulting role_grant
|
|
71
|
-
* (`reason: 'role_grant_revoked'`), or by deletion of
|
|
70
|
+
* (`reason: 'sibling_accepted'`), by revoke of the resulting role_grant or of
|
|
71
|
+
* one of the grantor's own (`reason: 'role_grant_revoked'`), or by deletion of
|
|
72
|
+
* the parent scope row
|
|
72
73
|
* the offer was bound to (`reason: 'scope_destroyed'`). `cause_id` points
|
|
73
74
|
* at the accepted offer id, the revoked role_grant id, or the destroyed scope
|
|
74
75
|
* row id respectively.
|
|
@@ -286,4 +286,24 @@ export interface AcceptOfferResult {
|
|
|
286
286
|
* @throws Error if the accepting `actor_id` does not belong to `to_account_id`, or invariant assertions fail
|
|
287
287
|
*/
|
|
288
288
|
export declare const query_accept_offer: (deps: QueryDeps, input: AcceptOfferInput) => Promise<AcceptOfferResult>;
|
|
289
|
+
/**
|
|
290
|
+
* Supersede every pending offer **made by** one of `from_actor_ids` — the
|
|
291
|
+
* grantor-side cascade.
|
|
292
|
+
*
|
|
293
|
+
* An offer carries no record of the authority it was made under, so one
|
|
294
|
+
* outliving that authority would still confer its role on accept. Revoking a
|
|
295
|
+
* grant (`role_grant_revoke`, the revokee's actor) and removing an account
|
|
296
|
+
* (`account_delete` / `account_purge`, every actor of the account) end the
|
|
297
|
+
* grantor's outgoing pending offers in the same transaction. Each row carries
|
|
298
|
+
* the grantor's `from_account_id`, as every supersede cascade's does; the
|
|
299
|
+
* caller emits a `role_grant_offer_supersede` audit event per entry, and the
|
|
300
|
+
* revoke caller also sends the supersede notification. Twin of the Rust
|
|
301
|
+
* `query_role_grant_offer_supersede_outgoing`.
|
|
302
|
+
*
|
|
303
|
+
* @param deps - query dependencies
|
|
304
|
+
* @param from_actor_ids - the grantors whose pending offers end
|
|
305
|
+
* @returns the superseded offers, each with its grantor's account
|
|
306
|
+
* @mutates `role_grant_offer` rows - stamps `superseded_at` on every pending offer from `from_actor_ids`
|
|
307
|
+
*/
|
|
308
|
+
export declare const query_role_grant_offer_supersede_outgoing: (deps: QueryDeps, from_actor_ids: ReadonlyArray<Uuid>) => Promise<Array<SupersededOffer>>;
|
|
289
309
|
//# sourceMappingURL=role_grant_offer_queries.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_queries.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"role_grant_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AASrD,OAAO,KAAK,EACX,yBAAyB,EACzB,cAAc,EACd,eAAe,EACf,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D;;;;;GAKG;AACH,qBAAa,kCAAmC,SAAQ,KAAK;gBAChD,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;gBACxC,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;gBACzC,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;;;;GAQG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;;CAKvD;AAED;;;;;;;GAOG;AACH,qBAAa,gCAAiC,SAAQ,KAAK;gBAC9C,QAAQ,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,GAAG,QAAQ,GAAG,wBAAwB,CAAC;AAEzF;;;;;GAKG;AACH,qBAAa,iCAAkC,SAAQ,KAAK;IAC3D,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;gBAC1B,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,mBAAmB;CAM/D;AAED;;;;;GAKG;AACH,qBAAa,uCAAwC,SAAQ,KAAK;;CAKjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,OAAO,yBAAyB,KAC9B,OAAO,CAAC,cAAc,CAwDxB,CAAC;AAEF,uGAAuG;AACvG,MAAM,WAAW,aAAc,SAAQ,cAAc;IACpD;;;;;OAKG;IACH,eAAe,EAAE,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,UAAU,MAAM,EAChB,eAAe,MAAM,EACrB,QAAQ,MAAM,GAAG,IAAI,KACnB,OAAO,CAAC,aAAa,GAAG,IAAI,CAmB9B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,UAAU,MAAM,EAChB,eAAe,MAAM,KACnB,OAAO,CAAC,cAAc,GAAG,IAAI,CAe/B,CAAC;AA8BF;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,eAAe,MAAM,KACnB,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAY/B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,0CAA0C,GACtD,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAW,EACX,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAS/B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,cAAc,GAAG,IAAI,CAY/B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,KACb,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAkB/B,CAAC;AAsCF,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE,IAAI,CAAC;IACf,mGAAmG;IACnG,aAAa,EAAE,IAAI,CAAC;IACpB;;;;;;;;;;OAUG;IACH,QAAQ,EAAE,IAAI,CAAC;IACf,gDAAgD;IAChD,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnB;AAED,6HAA6H;AAC7H,MAAM,WAAW,iBAAiB;IACjC,UAAU,EAAE,SAAS,CAAC;IACtB,KAAK,EAAE,cAAc,CAAC;IACtB,kOAAkO;IAClO,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,iBAAiB,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IAC1C,yMAAyM;IACzM,YAAY,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,kBAAkB,GAC9B,MAAM,SAAS,EACf,OAAO,gBAAgB,KACrB,OAAO,CAAC,iBAAiB,CAmP3B,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,yCAAyC,GACrD,MAAM,SAAS,EACf,gBAAgB,aAAa,CAAC,IAAI,CAAC,KACjC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAiBhC,CAAC"}
|
|
@@ -628,3 +628,38 @@ export const query_accept_offer = async (deps, input) => {
|
|
|
628
628
|
audit_events: [offer_accept_event, role_grant_create_event, ...supersede_events]
|
|
629
629
|
};
|
|
630
630
|
};
|
|
631
|
+
/**
|
|
632
|
+
* Supersede every pending offer **made by** one of `from_actor_ids` — the
|
|
633
|
+
* grantor-side cascade.
|
|
634
|
+
*
|
|
635
|
+
* An offer carries no record of the authority it was made under, so one
|
|
636
|
+
* outliving that authority would still confer its role on accept. Revoking a
|
|
637
|
+
* grant (`role_grant_revoke`, the revokee's actor) and removing an account
|
|
638
|
+
* (`account_delete` / `account_purge`, every actor of the account) end the
|
|
639
|
+
* grantor's outgoing pending offers in the same transaction. Each row carries
|
|
640
|
+
* the grantor's `from_account_id`, as every supersede cascade's does; the
|
|
641
|
+
* caller emits a `role_grant_offer_supersede` audit event per entry, and the
|
|
642
|
+
* revoke caller also sends the supersede notification. Twin of the Rust
|
|
643
|
+
* `query_role_grant_offer_supersede_outgoing`.
|
|
644
|
+
*
|
|
645
|
+
* @param deps - query dependencies
|
|
646
|
+
* @param from_actor_ids - the grantors whose pending offers end
|
|
647
|
+
* @returns the superseded offers, each with its grantor's account
|
|
648
|
+
* @mutates `role_grant_offer` rows - stamps `superseded_at` on every pending offer from `from_actor_ids`
|
|
649
|
+
*/
|
|
650
|
+
export const query_role_grant_offer_supersede_outgoing = async (deps, from_actor_ids) => {
|
|
651
|
+
if (from_actor_ids.length === 0)
|
|
652
|
+
return [];
|
|
653
|
+
return deps.db.query(`WITH updated AS (
|
|
654
|
+
UPDATE role_grant_offer
|
|
655
|
+
SET superseded_at = NOW()
|
|
656
|
+
WHERE from_actor_id = ANY($1)
|
|
657
|
+
AND accepted_at IS NULL
|
|
658
|
+
AND declined_at IS NULL
|
|
659
|
+
AND retracted_at IS NULL
|
|
660
|
+
AND superseded_at IS NULL
|
|
661
|
+
-- raw columns; the outer grantor select formats them
|
|
662
|
+
RETURNING ${columns_sql(ROLE_GRANT_OFFER_COLUMNS)}
|
|
663
|
+
)
|
|
664
|
+
${ROLE_GRANT_OFFER_WITH_GRANTOR_SELECT}`, [from_actor_ids]);
|
|
665
|
+
};
|
|
@@ -50,11 +50,12 @@ export declare const query_create_role_grant: (deps: QueryDeps, input: CreateRol
|
|
|
50
50
|
*/
|
|
51
51
|
export declare const query_role_grant_by_id: (deps: QueryDeps, id: Uuid) => Promise<RoleGrant | undefined>;
|
|
52
52
|
/**
|
|
53
|
-
* Look up the role of an active role_grant (constrained to a
|
|
54
|
-
* actor) plus the actor's `account_id`.
|
|
53
|
+
* Look up the role and scope of an active role_grant (constrained to a
|
|
54
|
+
* specific actor) plus the actor's `account_id`.
|
|
55
55
|
*
|
|
56
56
|
* Used by admin routes to inspect the role_grant's role before acting
|
|
57
|
-
* (e.g., enforcing the admin-grant-path gate
|
|
57
|
+
* (e.g., enforcing the admin-grant-path gate and the global-`admin` guards on
|
|
58
|
+
* revoke). The actor constraint
|
|
58
59
|
* mirrors `query_revoke_role_grant` so IDOR protection is consistent:
|
|
59
60
|
* a caller can only see role_grants belonging to the target actor.
|
|
60
61
|
*
|
|
@@ -71,10 +72,11 @@ export declare const query_role_grant_by_id: (deps: QueryDeps, id: Uuid) => Prom
|
|
|
71
72
|
* @param deps - query dependencies
|
|
72
73
|
* @param role_grant_id - the role_grant id to look up
|
|
73
74
|
* @param actor_id - the actor that must own the role_grant
|
|
74
|
-
* @returns `{role, account_id}` on a match, or `null`
|
|
75
|
+
* @returns `{role, scope_id, account_id}` on a match, or `null`
|
|
75
76
|
*/
|
|
76
77
|
export declare const query_role_grant_find_active_role_for_actor: (deps: QueryDeps, role_grant_id: string, actor_id: string) => Promise<{
|
|
77
78
|
role: string;
|
|
79
|
+
scope_id: Uuid | null;
|
|
78
80
|
account_id: Uuid;
|
|
79
81
|
} | null>;
|
|
80
82
|
/** Result of `query_revoke_role_grant` — the revoked role_grant plus any pending offers superseded by the revoke. */
|
|
@@ -163,7 +165,7 @@ export declare const query_account_has_global_role: (deps: QueryDeps, account_id
|
|
|
163
165
|
* Like `query_account_has_global_role`, but only counts the grant when the
|
|
164
166
|
* **account and the granting actor are both active** (`deleted_at IS NULL`).
|
|
165
167
|
* Used by the last-admin branch of the removability guard: a soft-deleted admin
|
|
166
|
-
* can't log in and is excluded from `
|
|
168
|
+
* can't log in and is excluded from `query_count_active_accounts_with_global_role_for_update`,
|
|
167
169
|
* so the guard must use the same active predicate when testing whether the
|
|
168
170
|
* *target* is an admin — otherwise removing an already-tombstoned admin is
|
|
169
171
|
* falsely blocked as `cannot_delete_last_admin`. Soft-delete does **not** revoke
|
|
@@ -183,18 +185,34 @@ export declare const query_account_has_active_global_role: (deps: QueryDeps, acc
|
|
|
183
185
|
/**
|
|
184
186
|
* Count **active** accounts (`deleted_at IS NULL`) holding an active global
|
|
185
187
|
* role_grant for `role`, counting only grants on **active actors**
|
|
186
|
-
* (`act.deleted_at IS NULL`). Used by the last-admin
|
|
187
|
-
* `
|
|
188
|
+
* (`act.deleted_at IS NULL`) — and lock what it counted. Used by the last-admin
|
|
189
|
+
* guards on `account_delete` / `account_purge` and on `role_grant_revoke` of an
|
|
190
|
+
* `admin` grant: soft-delete revokes neither a tombstoned account's nor a
|
|
188
191
|
* tombstoned actor's admin grant, so an unfiltered count would include unusable
|
|
189
192
|
* admins — this joins `account` *and* filters the actor, so an account whose
|
|
190
193
|
* only admin grant sits on a tombstoned actor no longer counts (otherwise the
|
|
191
|
-
* guard could fail-open and let the last usable admin be
|
|
194
|
+
* guard could fail-open and let the last usable admin be removed).
|
|
195
|
+
*
|
|
196
|
+
* **Locks** every counted `role_grant`, `actor`, and `account` row
|
|
197
|
+
* (`FOR UPDATE`) until the caller's transaction ends, so two guards racing to
|
|
198
|
+
* remove the last two admins serialize: the second waits for the first to
|
|
199
|
+
* commit, re-reads the rows it changed, and counts one. Run it inside the
|
|
200
|
+
* transaction that does the removal. Twin of the Rust
|
|
201
|
+
* `query_count_active_accounts_with_global_role_for_update`.
|
|
202
|
+
*
|
|
203
|
+
* `exclude_role_grant_id` leaves one grant out of the count, so a revoke asks
|
|
204
|
+
* whether any active admin account would remain without that grant — an
|
|
205
|
+
* account holding the grant on two actors keeps counting through the other.
|
|
206
|
+
* The excluded grant is still locked, so two revokes of each other's grant
|
|
207
|
+
* serialize rather than deadlock.
|
|
192
208
|
*
|
|
193
209
|
* @param deps - query dependencies
|
|
194
210
|
* @param role - the role to count (e.g. `ROLE_ADMIN`)
|
|
211
|
+
* @param exclude_role_grant_id - a grant to leave out of the count (the one being revoked)
|
|
195
212
|
* @returns the number of distinct active accounts with an active global `role` grant
|
|
213
|
+
* @mutates `role_grant` / `actor` / `account` rows - row-locks the counted rows for the transaction
|
|
196
214
|
*/
|
|
197
|
-
export declare const
|
|
215
|
+
export declare const query_count_active_accounts_with_global_role_for_update: (deps: QueryDeps, role: string, exclude_role_grant_id?: Uuid | null) => Promise<number>;
|
|
198
216
|
/**
|
|
199
217
|
* List all role_grants for an actor (including revoked/expired).
|
|
200
218
|
*/
|