Skip to content

permissions ​

The permissions module decouples role-based and per-user permission grants from the framework's in-memory PermissionRegistry. It owns:

  • Two assignment tables (permissions_role_permission, permissions_user_permission).
  • An admin UI to edit them.
  • A RequiresPermission dependency that consults both roles and direct user grants.

The framework ships its own simpler RequiresPermission in simple_module_hosting.permissions that only checks roles; if you install the permissions module, prefer the one re-exported from permissions.deps because it also honours direct grants.

ModuleMeta ​

FieldValue
namePermissions
route_prefix/api/permissions
view_prefix/permissions
depends_on["Auth", "Users"]

Routes ​

API ​

All require authentication. Read endpoints need permissions.view; mutate endpoints need permissions.manage.

Method + pathBody / responsePermission
GET /api/permissions/→ list[PermissionGroupOut]permissions.view
GET /api/permissions/roles/{role_id}→ RolePermissionsOutpermissions.view
PUT /api/permissions/roles/{role_id}RolePermissionsUpdate → RolePermissionsOutpermissions.manage
GET /api/permissions/users/{user_id}→ UserPermissionsOutpermissions.view
PUT /api/permissions/users/{user_id}UserPermissionsUpdate → UserPermissionsOutpermissions.manage

View ​

Method + pathInertia component / behaviourPermission
GET /permissions/redirect to /users/adminlogin
GET /permissions/roles/{role_id}/editPermissions/RoleEditpermissions.manage
PUT /permissions/roles/{role_id}form action; redirectspermissions.manage
GET /permissions/users/{user_id}/editPermissions/UserEditpermissions.manage
PUT /permissions/users/{user_id}form action; redirectspermissions.manage

Using RequiresPermission ​

python
from fastapi import APIRouter, Depends
from permissions.deps import RequiresPermission

router = APIRouter()

@router.delete(
    "/orders/{order_id}",
    dependencies=[Depends(RequiresPermission("orders.delete"))],
)
async def delete_order(order_id: int) -> None:
    ...

RequiresPermission(permission) takes a single permission key and 403s unless the request's user holds it, considering:

  1. The keys assigned to any of the user's roles (read from the request-cached resolved_permissions).
  2. The keys assigned directly to the user (permissions_user_permission).
  3. The implicit WILDCARD grant — the admin role is synced to hold every permission key at startup, so admins pass any check.

For something tied to only role membership (no direct grants), use auth.deps.require_permission instead — it's a hair cheaper.

Public contracts ​

python
from permissions.contracts.schemas import (
    PermissionGroupOut, RoleOut, RolePermissionsOut, RolePermissionsUpdate,
    UserOut, UserPermissionsOut, UserPermissionsUpdate,
)
ClassPurpose
PermissionGroupOutA named group (e.g. Orders) with the list of permission keys it owns. Built from the framework's PermissionRegistry.
RoleOutid, name, description.
RolePermissionsOutA role plus its assigned permission keys.
RolePermissionsUpdate{ "permissions": ["orders.view", ...] } — replaces the full set.
UserOutid, email, full_name.
UserPermissionsOutA user, the keys granted directly, and the keys inherited from roles.
UserPermissionsUpdate{ "permissions": [...] } — replaces the user's direct grants only.

Models ​

RolePermission (table permissions_role_permission)

ColumnTypeNotes
role_namestrcomposite PK
permission_keystrcomposite PK; indexed for reverse lookups
assigned_atdatetime
assigned_bystr | Noneactor email

UserPermission (table permissions_user_permission)

ColumnTypeNotes
user_idUUIDcomposite PK; references users_user.id
permission_keystrcomposite PK
assigned_atdatetime
assigned_bystr | Noneactor email

The schema deliberately keys by string permission key, not a normalised permissions table. Permission keys are the source of truth (registered at boot from register_permissions); the rows here are just assignments. If a key disappears from the registry, its assignments become inert (no FK to break) and the admin UI flags them as orphans.

Permissions ​

CodeGranted toPurpose
permissions.viewadminread groups, roles, user grants
permissions.manageadminedit role + user grants

(none) — the editor is reachable from the users admin pages (each user/role row links to its permissions edit page).

Inertia pages ​

  • Permissions/RoleEdit.tsx — checkbox grid grouped by PermissionGroup; submits the full set on save.
  • Permissions/UserEdit.tsx — same grid, with badges showing which permissions are inherited from roles vs granted directly.

Locales ​

Top-level keys in permissions/locales/en.json: browse, table, edit (role editor), user_edit (user editor), toasts, errors.

Released under the MIT License.