basalt / permissions/src / Gate
Class: Gate
Defined in: permissions/src/index.ts:460
Constructors
Constructor
> new Gate(options): Gate
Defined in: permissions/src/index.ts:472
Parameters
options
Returns
Gate
Accessors
store
Get Signature
> get store(): AccessStore
Defined in: permissions/src/index.ts:462
The grants this gate reads. Exposed for accessRoutes(); treat as read-only.
Returns
Methods
actor()
> actor(): Promise<PolicyUser | null>
Defined in: permissions/src/index.ts:515
The user of the current request, with its roles attached.
What @basaltkit/auth puts in the context is PublicUser — { id, email, emailVerified }. No roles, and rightly so: auth does not know this package exists.
But policies receive that object, and PolicyUser is open, so user.roles?.includes('partner') reads undefined and the policy denies. The right failure mode, and an invisible one — a partner treated as a stranger in their own firm, with no error anywhere to say why.
Nothing filled the gap, so every service wrote this by hand and memoised it under a private context key it had to invent. Here it is once, memoised per request and per scope, because the same person can hold different roles in two tenants.
null when there is no user — a background job, a public route. An object with an empty id would be an actor that fails every check for a reason nobody can read.
Returns
Promise<PolicyUser | null>
assignRole()
> assignRole(userId, role, scope?): Promise<void>
Defined in: permissions/src/index.ts:666
Gives userId a role in scope (default: the current scope) and emits permission:role_assigned.
Parameters
userId
string
role
string
scope?
string
Returns
Promise<void>
audienceRoles()
> audienceRoles(userId): Promise<string[]>
Defined in: permissions/src/index.ts:640
The roles the audience guard confines on. Inside a tenant, the roles held IN that tenant decide; the global (and legacy) roles decide only when the tenant grants none. Not the effectiveRoles union: one unnamed global role (a baseline user every signup gets) would otherwise count as "something else" and un-confine a tenant's portal client in every tenant. A confined role assigned globally still confines wherever the user holds no tenant role, and a tenant role that no rule names still un-confines.
Parameters
userId
string
Returns
Promise<string[]>
authorize()
> authorize(user, permission, resource?): Promise<void>
Defined in: permissions/src/index.ts:837
Like can(), but throws PERMISSION_DENIED (403).
Parameters
user
permission
string
resource?
unknown
Returns
Promise<void>
can()
> can(user, permission, resource?): Promise<boolean>
Defined in: permissions/src/index.ts:543
Permission check. With a resource, a matching policy ('resource:action') decides; otherwise the granted permission strings (with wildcards) do. Grants are looked up in the current scope AND the global scope.
Side-effect free: store and grant reads plus the superAdmin callback (keep it pure), never a hook, a denial record or a write — authorize() is what emits permission:denied. The plugin's http:route-visibility check relies on this to answer listings without auditing them.
Parameters
user
permission
string
resource?
unknown
Returns
Promise<boolean>
delegate()
> delegate(input): Promise<Delegation>
Defined in: permissions/src/index.ts:803
Delegate a subset of from's authority to to (bounded at check time). Needs a delegations store.
Parameters
input
expiresAt?
number
from
string
permissions
string[]
scope?
string
to
string
Returns
Promise<Delegation>
denied()
> denied(userId, permission): Promise<PermissionDeniedError>
Defined in: permissions/src/index.ts:660
Emits permission:denied and returns the error to throw. Every refusal the package makes goes through here, so the audit trail sees them all.
Parameters
userId
string
permission
string
Returns
Promise<PermissionDeniedError>
describeAccess()
> describeAccess(user): Promise<AccessReport>
Defined in: permissions/src/index.ts:877
Everything user may do right now, with where each permission comes from — what GET /me/access answers. Covers every source a check consults: direct and role grants in the current scope AND the global one (plus the legacy global scope when read), live temporary grants, live delegations (bounded, like the check, by the delegator's own direct permissions) and the superAdmin bypass (reported as '*').
Side-effect free, like can(). Not a security surface: every request is still decided by the Gate — this only lets an interface show the doors that open and hide the ones that don't.
Parameters
user
Returns
Promise<AccessReport>
effectiveRoles()
> effectiveRoles(userId): Promise<string[]>
Defined in: permissions/src/index.ts:622
The union of the user's roles over every scope a check consults.
Parameters
userId
string
Returns
Promise<string[]>
grantTemporarily()
> grantTemporarily(userId, permissions, options?): Promise<TemporaryGrant>
Defined in: permissions/src/index.ts:760
Grant a user extra permissions until expiresAt (or ttlMs from now). Needs a temporaryGrants store.
Parameters
userId
string
permissions
string[]
options?
expiresAt?
number
grantedBy?
string
reason?
string
scope?
string
ttlMs?
number
Returns
Promise<TemporaryGrant>
grantToRole()
> grantToRole(role, permissions, scope?): Promise<void>
Defined in: permissions/src/index.ts:684
Grants permissions to a role and emits permission:granted.
Parameters
role
string
permissions
string[]
scope?
string
Returns
Promise<void>
grantToUser()
> grantToUser(userId, permissions, scope?): Promise<void>
Defined in: permissions/src/index.ts:693
Grants permissions directly to a user and emits permission:granted.
Parameters
userId
string
permissions
string[]
scope?
string
Returns
Promise<void>
hasPolicy()
> hasPolicy(permission): boolean
Defined in: permissions/src/index.ts:578
True when a registered policy check decides exactly permission (resource:action) — i.e. when can(user, permission, resource) would consult a policy instead of throwing MissingPolicyError (or falling back to RBAC under onMissingPolicy: 'rbac'). A pure lookup.
Parameters
permission
string
Returns
boolean
hasRole()
> hasRole(user, role): Promise<boolean>
Defined in: permissions/src/index.ts:851
Whether the user actually holds role — in the current scope or globally. Role membership, not authority: the superAdmin bypass does NOT make a super admin a member of every role (it short-circuits can()/authorize() instead). Ask isSuperAdmin for that, or — better — check the permission the role stands for with can().
Parameters
user
role
string
Returns
Promise<boolean>
isSuperAdmin()
> isSuperAdmin(user): Promise<boolean>
Defined in: permissions/src/index.ts:860
Whether the configured superAdmin callback lets user bypass every check. false without one.
Parameters
user
Returns
Promise<boolean>
register()
> register(policy): this
Defined in: permissions/src/index.ts:487
Parameters
policy
Policy<never>
Returns
this
removeRole()
> removeRole(userId, role, scope?): Promise<void>
Defined in: permissions/src/index.ts:675
Takes a role away and emits permission:role_removed.
Parameters
userId
string
role
string
scope?
string
Returns
Promise<void>
rolePermissions()
> rolePermissions(role, scope): Promise<string[]>
Defined in: permissions/src/index.ts:708
The permissions role carries when held in scope: the store's definition in that scope, the roleCatalog entry, and — with inheritGlobalRolePermissions — the store's global definition. Always evaluated FOR scope: the caller only uses the result for a role held there, so nothing here widens a grant to another tenant.
Parameters
role
string
scope
string
Returns
Promise<string[]>