# `PermitEx`
[🔗](https://github.com/devaction-labs/permit_ex/blob/v0.2.0/lib/permit_ex.ex#L1)

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.

# `permission`

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

# `role`

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

# `scope`

```elixir
@type scope() :: %{optional(:permissions) =&gt; Enumerable.t()}
```

# `allowed?`

Checks a permission and an optional resource policy.

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

# `assign_role`

Assigns one role to a user in a context.

# `assign_roles`

Assigns many roles to a user without removing existing roles.

# `authorize`

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

# `can?`

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

# `clone_roles_to_context`

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`

Creates a permission.

# `create_role`

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

# `delete_permission`

Deletes a permission.

# `delete_role`

Deletes a role.

# `get_permission_by_name`

Gets a permission by name.

# `get_role_by_name`

Gets a global or context-specific role by name.

# `has_permission?`

Alias for `can?/2`.

# `has_role?`

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

# `list_permissions`

Lists permissions ordered by name.

# `list_role_permissions`

Lists permissions assigned to a role.

# `list_roles`

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

# `list_user_roles`

Lists role assignments for a user.

# `normalize_permission`

# `normalize_role`

# `permissions_for`

Loads permission names for the user in a context.

# `revoke_role`

Removes one role from a user in a context.

# `role_matrix`

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`

Loads roles assigned to a user in a context.

# `roles_for_context`

Lists roles that belong to one context.

# `scope_data_for`

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

# `seed!`

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`

Alias for `clone_roles_to_context/2`.

# `sync_permissions`

Alias for `sync_role_permissions/3`.

# `sync_role_permissions`

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`

Alias for `sync_user_roles/4`.

# `sync_user_roles`

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`

Creates or updates a context role by name.

# `upsert_permission`

Creates or updates a permission by name.

# `upsert_role`

Creates or updates a global role by name.

# `users_with_role`

Lists user ids assigned to a role.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
