Skip to main content

Overview

Torn implements a role-based access control (RBAC) system where users are assigned roles with granular permissions. Each tenant has its own set of roles stored in their isolated schema.

Role Architecture

Role Model Structure

class Role:
    id: int
    name: str                        # "ADMINISTRADOR", "VENDEDOR", "BODEGUERO"
    description: str                 # Human-readable explanation
    
    # Granular Permissions (Boolean Flags)
    can_manage_users: bool = False
    can_view_reports: bool = False
    can_edit_products: bool = True
    can_perform_sales: bool = True
    can_perform_returns: bool = False
    
    # Dynamic Permissions (JSON)
    permissions: dict = {}           # UI/menu access, custom features
Source: /app/models/user.py:10-35

User-Role Relationship

class User:
    id: int
    email: str
    full_name: str
    role_id: int                     # Foreign key to roles table
    role: str                        # Legacy string field ("ADMIN", "SELLER")
    role_obj: Role                   # Relationship to full role object
Source: /app/models/user.py:38-89
The role string field exists for backward compatibility. New implementations should use role_id and role_obj for granular permissions.

Default Roles

When a tenant is provisioned, these roles are seeded:
RoleDescriptionDefault Permissions
ADMINISTRADORFull system accessAll permissions enabled
VENDEDORCashier/sales staffSales, products (view only), basic reports
BODEGUEROWarehouse managerProducts, inventory, purchases
CLIENTECustomer accountsMinimal (view own orders, account balance)
Source: Referenced in tenant provisioning at /app/services/tenant_service.py

Listing Roles

Retrieve all roles for the current tenant:
curl -X GET https://api.torn.cl/roles/ \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -H "X-Tenant-ID: 5"
Response:
[
  {
    "id": 1,
    "name": "ADMINISTRADOR",
    "description": "Acceso completo al sistema",
    "can_manage_users": true,
    "can_view_reports": true,
    "can_edit_products": true,
    "can_perform_sales": true,
    "can_perform_returns": true,
    "permissions": {
      "dashboard": true,
      "pos": true,
      "inventory": true,
      "reports": true,
      "settings": true
    }
  },
  {
    "id": 2,
    "name": "VENDEDOR",
    "description": "Personal de caja y atención al cliente",
    "can_manage_users": false,
    "can_view_reports": false,
    "can_edit_products": false,
    "can_perform_sales": true,
    "can_perform_returns": false,
    "permissions": {
      "dashboard": true,
      "pos": true,
      "inventory": false,
      "reports": false,
      "settings": false
    }
  },
  {
    "id": 3,
    "name": "BODEGUERO",
    "description": "Gestión de inventario y compras",
    "can_manage_users": false,
    "can_view_reports": true,
    "can_edit_products": true,
    "can_perform_sales": false,
    "can_perform_returns": true,
    "permissions": {
      "dashboard": true,
      "pos": false,
      "inventory": true,
      "reports": true,
      "settings": false
    }
  }
]
Source: /app/routers/roles.py:11-14
The /roles/ endpoint requires admin privileges. Non-admin users will receive a 403 Forbidden response.

Getting Role Details

Retrieve a specific role by ID:
curl -X GET https://api.torn.cl/roles/2 \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -H "X-Tenant-ID: 5"
Response:
{
  "id": 2,
  "name": "VENDEDOR",
  "description": "Personal de caja y atención al cliente",
  "can_manage_users": false,
  "can_view_reports": false,
  "can_edit_products": false,
  "can_perform_sales": true,
  "can_perform_returns": false,
  "permissions": {
    "dashboard": true,
    "pos": true,
    "inventory": false,
    "reports": false,
    "settings": false
  }
}
Source: /app/routers/roles.py:16-22

Updating Role Permissions

1

Identify Permission Needs

Determine which permissions need adjustment. For example, allowing cashiers to process returns:
  • can_perform_returns: false → true
2

Update Role Configuration

curl -X PUT https://api.torn.cl/roles/2 \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -H "X-Tenant-ID: 5" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Personal de caja con capacidad de devoluciones",
    "can_perform_returns": true,
    "permissions": {
      "dashboard": true,
      "pos": true,
      "returns": true,
      "inventory": false,
      "reports": false,
      "settings": false
    }
  }'
3

Verify Changes

Response:
{
  "id": 2,
  "name": "VENDEDOR",
  "description": "Personal de caja con capacidad de devoluciones",
  "can_manage_users": false,
  "can_view_reports": false,
  "can_edit_products": false,
  "can_perform_sales": true,
  "can_perform_returns": true,
  "permissions": {
    "dashboard": true,
    "pos": true,
    "returns": true,
    "inventory": false,
    "reports": false,
    "settings": false
  }
}
4

Changes Apply Immediately

All users with role_id = 2 immediately inherit the updated permissions. No logout/login required.
Source: /app/routers/roles.py:24-37
Use the permissions JSON field for UI-level controls (show/hide menus, buttons) and boolean fields for backend enforcement (API authorization).

Permission Enforcement

Dependency Injection Pattern

Torn uses FastAPI dependencies to enforce permissions:
from app.dependencies.tenant import require_admin

@router.get("/roles/", dependencies=[Depends(require_admin)])
def list_roles(db: Session = Depends(get_tenant_db)):
    return db.query(Role).all()
Source: /app/routers/roles.py:11

Custom Permission Checks

Implement granular checks in route handlers:
from fastapi import Depends, HTTPException
from app.dependencies.tenant import get_current_local_user
from app.models.user import User

@router.post("/sales/return")
def create_return(
    return_in: ReturnCreate,
    db: Session = Depends(get_tenant_db),
    current_user: User = Depends(get_current_local_user)
):
    # Check permission
    if not current_user.role_obj.can_perform_returns:
        raise HTTPException(
            status_code=403,
            detail="No tiene permisos para realizar devoluciones"
        )
    
    # Process return...
Always enforce permissions at the API layer, not just in the frontend. Never trust client-side permission checks alone.

Assigning Roles to Users

During User Creation

When creating a user, specify the role_id:
curl -X POST https://api.torn.cl/users/ \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -H "X-Tenant-ID: 5" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "full_name": "Carlos Ramírez",
    "password": "secure-password-123",
    "role_id": 2
  }'
The user automatically inherits all permissions from the VENDEDOR role.

Updating User Role

Promote or demote users by changing their role_id:
curl -X PATCH https://api.torn.cl/users/45 \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -H "X-Tenant-ID: 5" \
  -H "Content-Type: application/json" \
  -d '{
    "role_id": 1
  }'
This immediately promotes user #45 to ADMINISTRADOR with full permissions.
Changing a user’s role doesn’t affect their existing sessions. Users may need to log out and back in for UI changes to reflect properly.

Boolean Permission Fields

Available Permission Flags

FieldControlsUsed In
can_manage_usersCreate/edit/delete usersUser management endpoints
can_view_reportsAccess analytics and reportsReports module
can_edit_productsModify product catalogProduct CRUD operations
can_perform_salesProcess sales transactionsSales endpoints
can_perform_returnsIssue credit notes/refundsReturn endpoints
Source: /app/models/user.py:22-26

ADMINISTRADOR (Admin)

{
  "can_manage_users": true,
  "can_view_reports": true,
  "can_edit_products": true,
  "can_perform_sales": true,
  "can_perform_returns": true
}

VENDEDOR (Cashier)

{
  "can_manage_users": false,
  "can_view_reports": false,
  "can_edit_products": false,
  "can_perform_sales": true,
  "can_perform_returns": false
}

BODEGUERO (Warehouse Manager)

{
  "can_manage_users": false,
  "can_view_reports": true,
  "can_edit_products": true,
  "can_perform_sales": false,
  "can_perform_returns": true
}

Dynamic Permissions (JSON Field)

The permissions JSON field allows flexible, application-specific controls:

UI/Menu Visibility

{
  "permissions": {
    "dashboard": true,
    "pos": true,
    "inventory": false,
    "reports": false,
    "settings": false,
    "customers": true,
    "providers": false
  }
}

Feature Flags

{
  "permissions": {
    "can_discount": true,
    "max_discount_percent": 15,
    "can_void_sale": false,
    "can_override_price": false,
    "can_view_cost_prices": false
  }
}

Module Access

{
  "permissions": {
    "modules": {
      "sales": {"read": true, "write": true, "delete": false},
      "products": {"read": true, "write": false, "delete": false},
      "reports": {"read": false, "write": false, "delete": false}
    }
  }
}
Use the JSON field for business-specific permissions that don’t warrant dedicated database columns. Keep the schema flexible.

Frontend Integration

Retrieve current user with role information:
curl -X GET https://api.torn.cl/auth/me \
  -H "Authorization: Bearer USER_TOKEN" \
  -H "X-Tenant-ID: 5"
Response:
{
  "id": 45,
  "email": "[email protected]",
  "name": "Carlos Ramírez",
  "role": "VENDEDOR",
  "role_id": 2,
  "is_active": true,
  "role_obj": {
    "id": 2,
    "name": "VENDEDOR",
    "can_manage_users": false,
    "can_view_reports": false,
    "can_edit_products": false,
    "can_perform_sales": true,
    "can_perform_returns": false,
    "permissions": {
      "dashboard": true,
      "pos": true,
      "inventory": false
    }
  }
}
Use this to:
  • Show/hide UI elements
  • Enable/disable buttons
  • Filter menu items
  • Display role-appropriate dashboards

Special User Types

Owner Users

class User:
    is_owner: bool = False           # Tenant owner (highest privilege)
Owner users bypass most permission checks. Typically assigned to the business owner during tenant creation.

System Users

class User:
    is_system_user: bool = False     # Torn support/admin user
System users are injected for technical support and leave audit trails:
audit_metadata = {"saas_admin_email": global_user.email}
Source: /app/routers/sales.py:248
System user actions should always be logged in audit_metadata for compliance and accountability.

Role Naming Conventions

Torn uses Spanish role names by default (Chilean market focus):
  • ADMINISTRADOR (Administrator)
  • VENDEDOR (Seller/Cashier)
  • BODEGUERO (Warehouse Manager)
  • CLIENTE (Customer)
You can customize role names per tenant, but maintain consistent permission structures.

Best Practices

Principle of Least Privilege

Grant users only the minimum permissions needed to perform their job. Start restrictive, add permissions as needed.

Regular Permission Audits

Review role assignments quarterly:
  • Remove unused roles
  • Verify permission matrices align with business needs
  • Audit users with admin privileges

Separation of Duties

Avoid assigning all permissions to a single role. For example:
  • Cashiers shouldn’t manage inventory
  • Warehouse staff shouldn’t process sales
  • Only admins should manage users

Document Custom Permissions

If using the JSON permissions field extensively, document your schema:
// permissions.schema.ts
interface RolePermissions {
  dashboard: boolean;
  pos: boolean;
  inventory: boolean;
  reports: boolean;
  settings: boolean;
  can_discount: boolean;
  max_discount_percent?: number;
}

Test Permission Changes

Before updating production roles, test in a staging tenant:
  1. Clone the role configuration
  2. Test with a test user
  3. Verify API access and UI behavior
  4. Deploy to production

Build docs developers (and LLMs) love