auth/role_grant_offer_actions.ts

Role grant offer RPC action handlers — the consentful-role-grants action surface.

Seven actions: six offer-lifecycle methods (create / accept / decline / retract / list / history) plus role_grant_revoke (admin-only). All mount on a consumer's JSON-RPC endpoint via create_rpc_endpoint. The action specs themselves live in auth/role_grant_offer_action_specs.ts. Mutations declare side_effects: true so the RPC dispatcher wraps the handler in a DB transaction; role_grant_offer_list and role_grant_offer_history declare side_effects: false so they are addressable via GET.

Authorization: - role_grant_offer_create — the grantor must hold an active role_grant for the role being offered, and that role's grant_paths must include 'admin'. Consumers needing a richer policy (e.g., "teacher may offer student in *their* classroom") pass an authorize callback that overrides the default. - role_grant_offer_accept / role_grant_offer_decline — keyed to the caller's account; query_* helpers enforce the IDOR guard. - role_grant_offer_retract — keyed to the caller's actor. - role_grant_offer_list / role_grant_offer_history — self by default; {account_id} is admin-only. - role_grant_revoke — spec-level auth: {role: 'admin'}; the RPC dispatcher rejects non-admin callers before the handler runs. The admin-grant-path gate prevents revoking keeper / daemon-scoped roles via this surface. Keys on actor_id to survive multi-actor accounts.

Audit events are emitted in-transaction by the query layer (atomic with the role_grant write on accept/revoke) or by the handler via the bound deps.audit.emit_role_grant_target helper for single-event lifecycle transitions. audit.notify (SSE/WS broadcast) fires post-commit in both paths.

WS notifications fan out post-commit via emit_after_commit when a notification_sender is wired: offer lifecycle transitions notify the counterparty, role_grant_revoke notifies the revokee plus each superseded pending offer's grantor.

Declarations
#

4 declarations

view source

authorize_admin_or_holder
#

auth/role_grant_offer_actions.ts view source

(auth: RequestContext, input: { to_account_id: string; role: string; scope_id: string | null; }, deps: Pick<RouteFactoryDeps, "log">, ctx: ActionContext): boolean | Promise<...>

Authorization callback that admits any admin and otherwise falls back to the symmetric default (caller must hold the offered role globally).

The admin-grant-path filter in create_handler runs before the authorize callback, so this never sees roles whose grant_paths omits 'admin'. Drop into create_role_grant_offer_actions({authorize: authorize_admin_or_holder}) (or any factory that forwards authorize, e.g. create_standard_rpc_actions) for the common "admins offer anything; users offer what they hold" pattern. Scope-aware policies (e.g. classroom_teacher offering classroom_student in their own scope) wrap this and short-circuit true before delegating.

auth

input

type { to_account_id: string; role: string; scope_id: string | null; }

deps

type Pick<RouteFactoryDeps, "log">

ctx

returns

boolean | Promise<boolean>

create_role_grant_offer_actions
#

auth/role_grant_offer_actions.ts view source

(deps: Pick<RouteFactoryDeps, "log" | "audit"> & { notification_sender?: NotificationSender | null | undefined; }, options?: RoleGrantOfferActionOptions): RpcAction[]

Create the seven role-grant-offer RPC actions (six offer-lifecycle methods plus role_grant_revoke).

deps

RouteFactoryDeps (log, audit, …) plus optional notification_sender for WS fan-out — when absent, WS fan-out is silently skipped (DB-only side effects still happen). Consumers wiring BackendWebsocketTransport assign its instance directly (the transport's send_to_account signature accepts the broader JsonrpcMessageFromServerToClient, which is contravariantly compatible)

type Pick<RouteFactoryDeps, "log" | "audit"> & { notification_sender?: NotificationSender | null | undefined; }

options

role schema, default TTL, authorization override

default {}

returns

RpcAction[]

the RpcAction array to spread into a create_rpc_endpoint call

RoleGrantOfferActionOptions
#

auth/role_grant_offer_actions.ts view source

RoleGrantOfferActionOptions

roles

Role schema result from create_role_schema(). Defaults to builtin roles only. Drives the grantability gate: a role is offerable / revocable through this surface only when its RoleSpec.grant_paths includes 'admin' (the GRANT_PATH_ADMIN constant).

default_ttl_ms

TTL applied to newly-created offers. Defaults to ROLE_GRANT_OFFER_DEFAULT_TTL_MS.

type number

authorize

Custom authorization for role_grant_offer_create. The default requires the caller to hold an active role_grant for the offered role *and* the role's RoleSpec.grant_paths to include 'admin'. Consumers with richer policies (scope-aware, chained roles) override this.

RoleGrantOfferCreateAuthorize
#

auth/role_grant_offer_actions.ts view source

RoleGrantOfferCreateAuthorize

Authorization callback for role_grant_offer_create. Returns true to allow, false to reject (handler converts to forbidden).

Provided with the fully-resolved request context and the parsed input (pre-TTL, pre-normalization). Consumers override the default to implement policies like "teacher may offer classroom_student only in classrooms they teach".

Depends on
#

Imported by
#