howdy/authorization
Role-based authorization, independent of login methods and HTTP transport. Grants apply only to their exact scope. Global grants do not imply access to organizations. No role name has implicit privileges or wildcard powers.
Types
pub opaque type Authorization
Values
pub fn allowed(
access: Authorization,
principal: user.Principal,
permission: String,
scope: Scope,
) -> Result(Bool, service.Error)
RBAC permission check. Any currently assigned role may grant a permission. Unknown permissions and scopes deny access; database errors remain errors.
pub fn assign(
access: Authorization,
user_id: String,
role: String,
scope: Scope,
by actor: user.Actor,
) -> Result(Nil, service.Error)
Assign an existing role to an existing user in exactly this scope. Privileged: the application must authorize the caller first.
pub fn assignments(
access: Authorization,
user_id: String,
) -> Result(List(#(Scope, String)), service.Error)
The roles a user holds, as scope and role name, for management consoles. Suspension does not remove assignments; checks ignore them while it lasts.
pub fn define_role(
access: Authorization,
scope: Scope,
name: String,
permissions: List(String),
by actor: user.Actor,
) -> Result(Nil, service.Error)
Define or replace a role’s permissions atomically. This is a privileged management operation, not a public endpoint. Existing assignments remain.
pub fn delete_role(
access: Authorization,
scope: Scope,
name: String,
by actor: user.Actor,
) -> Result(Nil, service.Error)
Remove a role and every assignment of it. Privileged: authorize the
caller first. NotFound when there is no such role.
pub fn has_role(
access: Authorization,
principal: user.Principal,
role: String,
scope: Scope,
) -> Result(Bool, service.Error)
Simple role check. The principal must come from authentication.
pub fn holders(
access: Authorization,
scope: Scope,
role: String,
) -> Result(List(String), service.Error)
The ids of the users assigned a role, for management consoles.
pub fn new(
repo: repo.Repo,
) -> Result(Authorization, service.Error)
pub fn require_permission(
access: Authorization,
permission: String,
scope: Scope,
) -> fn(context.Context(user.Principal)) -> Result(
Nil,
service.Error,
)
Use with guard.require after auth.required. Use a resource’s validated organization ID for scope, not an unverified client-provided tenant header.
pub fn require_role(
access: Authorization,
role: String,
scope: Scope,
) -> fn(context.Context(user.Principal)) -> Result(
Nil,
service.Error,
)
pub fn revoke(
access: Authorization,
user_id: String,
role: String,
scope: Scope,
by actor: user.Actor,
) -> Result(Nil, service.Error)
Revoke a role immediately. Privileged: authorize the caller first.
pub fn roles(
access: Authorization,
) -> Result(List(Role), service.Error)
Every defined role in every scope, with its permissions, for management consoles. Roles come sorted by scope then name.
pub fn schema() -> migration.Package
pub fn scope_from_string(text: String) -> Result(Scope, Nil)
Parse what scope_to_string produced.
pub fn scope_to_string(scope: Scope) -> String
The text a scope is stored and displayed as: global, or org:<id>.
pub fn with_cache(
access: Authorization,
seconds seconds: Int,
) -> Result(Authorization, service.Error)
Opt into at most seconds (1–60) of stale authorization on changes made
from other BEAM nodes or direct SQL. Local grant/suspension changes invalidate all
caches. Construct once at application startup; authenticate every request
before consulting authorization. The default always reads the database.
pub fn with_changes(run: fn() -> a) -> a
Wrap application-owned grant data changes, or a transaction opened directly
through gloo/repo or pog, so invalidation happens after its actual
commit/rollback, not a savepoint. Transactions opened with howdy/database
or howdy/database/postgres need no wrapper. Cached decisions are bypassed
inside the callback. Without this wrapper, direct SQL/external transactions
have the same bounded TTL as remote nodes.