PermitEx (permit_ex v0.2.0)

Copy Markdown View Source

Role and permission management for Ecto and Phoenix applications.

PermitEx keeps the core authorization model intentionally small: users receive roles globally or inside an optional context, roles receive permissions, and permissions are checked against the current scope.

Atom safety

Permission and role identifiers can be passed as atoms for convenience (can?(scope, :orders_manage)), but atoms must always be compile-time literals. Never derive them from user input via String.to_atom/1 — the atom table is not garbage-collected and exhausting it crashes the VM. Use strings for any value that originates from user input or external data.

Policy contract

allowed?/4 accepts an optional :policy module implementing PermitEx.Policy. The policy callback must return a boolean or {:error, reason}. Unexpected return values or raised exceptions propagate to the caller — wrap your policy in a try/rescue if you need guaranteed fail-closed semantics on errors.

Summary

Functions

Checks a permission and an optional resource policy.

Assigns one role to a user in a context.

Assigns many roles to a user without removing existing roles.

Returns :ok or {:error, :unauthorized} for command-style flows.

Returns true when the given scope or permission collection includes permission.

Clones global role templates into a context.

Creates a permission.

Creates a global role or a context role when context_id is present.

Gets a permission by name.

Gets a global or context-specific role by name.

Returns true when the given scope or role collection includes role.

Lists permissions ordered by name.

Lists permissions assigned to a role.

Lists global roles and roles for the given context, with permissions preloaded.

Lists role assignments for a user.

Loads permission names for the user in a context.

Removes one role from a user in a context.

Returns a map of role name to permission names for the given context.

Loads roles assigned to a user in a context.

Lists roles that belong to one context.

Loads roles and permissions for a user in a single query.

Seeds permissions and roles in one transaction.

Replaces all permissions assigned to a role.

Replaces all roles assigned to a user in a context.

Creates or updates a context role by name.

Creates or updates a permission by name.

Creates or updates a global role by name.

Types

permission()

@type permission() :: String.t() | atom()

role()

@type role() :: PermitEx.Role.t() | Ecto.UUID.t() | String.t()

scope()

@type scope() :: %{optional(:permissions) => Enumerable.t()}

Functions

allowed?(scope, permission, resource \\ nil, opts \\ [])

Checks a permission and an optional resource policy.

Pass a policy module with :policy. The policy module must implement PermitEx.Policy.authorize/3.

assign_role(user_id, role_or_id, context_id \\ nil, opts \\ [])

Assigns one role to a user in a context.

assign_roles(user_id, roles, context_id \\ nil, opts \\ [])

Assigns many roles to a user without removing existing roles.

authorize(scope_or_permissions, permission)

Returns :ok or {:error, :unauthorized} for command-style flows.

can?(scope_or_permissions, permission)

Returns true when the given scope or permission collection includes permission.

clone_roles_to_context(context_id, opts \\ [])

Clones global role templates into a context.

A global role is any role with context_id == nil. The cloned context role receives the same name, description, locked flag, and permissions. Existing context roles are updated idempotently.

PermitEx.clone_roles_to_context(workspace.id)
PermitEx.clone_roles_to_context(workspace.id, roles: ["admin", "viewer"])

create_permission(attrs, opts \\ [])

Creates a permission.

create_role(attrs, opts \\ [])

Creates a global role or a context role when context_id is present.

delete_permission(permission, opts \\ [])

Deletes a permission.

delete_role(role, opts \\ [])

Deletes a role.

get_permission_by_name(name, opts \\ [])

Gets a permission by name.

get_role_by_name(name, context_id \\ nil, opts \\ [])

Gets a global or context-specific role by name.

has_permission?(scope_or_permissions, permission)

Alias for can?/2.

has_role?(scope_or_roles, role)

Returns true when the given scope or role collection includes role.

list_permissions(opts \\ [])

Lists permissions ordered by name.

list_role_permissions(role_ref, opts \\ [])

Lists permissions assigned to a role.

list_roles(context_id \\ nil, opts \\ [])

Lists global roles and roles for the given context, with permissions preloaded.

list_user_roles(user_id, context_id \\ nil, opts \\ [])

Lists role assignments for a user.

normalize_permission(permission)

normalize_role(role)

permissions_for(user_id, context_id \\ nil, opts \\ [])

Loads permission names for the user in a context.

revoke_role(user_id, role_or_id, context_id \\ nil, opts \\ [])

Removes one role from a user in a context.

role_matrix(context_id \\ nil, opts \\ [])

Returns a map of role name to permission names for the given context.

Useful for rendering admin permission matrices and exposing role definitions via API. Roles with no permissions appear with an empty list.

PermitEx.role_matrix()
#=> %{"admin" => ["orders:manage", "orders:view"], "viewer" => ["orders:view"]}

PermitEx.role_matrix(workspace.id)

roles_for(user_id, context_id \\ nil, opts \\ [])

Loads roles assigned to a user in a context.

roles_for_context(context_id, opts \\ [])

Lists roles that belong to one context.

scope_data_for(user_id, context_id \\ nil, opts \\ [])

Loads roles and permissions for a user in a single query.

seed!(definitions, opts \\ [])

Seeds permissions and roles in one transaction.

Expected shape:

PermitEx.seed!(
  permissions: [
    {"orders:view", "View orders"},
    {"orders:manage", "Manage orders"}
  ],
  roles: [
    {"admin", "Context admin", ["orders:view", "orders:manage"]},
    {"viewer", "Read-only user", ["orders:view"]}
  ]
)

sync_context_roles_from_templates(context_id, opts \\ [])

Alias for clone_roles_to_context/2.

sync_permissions(role_ref, permissions, opts \\ [])

Alias for sync_role_permissions/3.

sync_role_permissions(role_ref, permissions, opts \\ [])

Replaces all permissions assigned to a role.

Accepts a role struct, role id, or role name. Permissions can be names, ids, atoms, or %PermitEx.Permission{} structs. Missing permissions return {:error, {:permissions_not_found, missing}} unless allow_missing?: true is passed.

sync_roles(user_id, roles, context_id \\ nil, opts \\ [])

Alias for sync_user_roles/4.

sync_user_roles(user_id, roles, context_id \\ nil, opts \\ [])

Replaces all roles assigned to a user in a context.

This is the Spatie-style syncRoles equivalent. It accepts role structs, ids or names and leaves the user with exactly the resolved roles.

upsert_context_role(name, context_id, attrs \\ %{}, opts \\ [])

Creates or updates a context role by name.

upsert_permission(name, attrs \\ %{}, opts \\ [])

Creates or updates a permission by name.

upsert_role(name, attrs \\ %{}, opts \\ [])

Creates or updates a global role by name.

users_with_role(role_ref, context_id \\ nil, opts \\ [])

Lists user ids assigned to a role.