User Roles & Permissions
Complete guide to user roles, permissions, and access control in Lager Guru.
Overview
Lager Guru uses a role-based access control (RBAC) system with a canonical set of user roles defined in the app_role PostgreSQL enum. All roles are stored in the user_roles table and enforced through Row-Level Security (RLS) policies.
Canonical Role List
The following roles are defined in the app_role enum:
Core Roles
admin- System Administrator- Full system access
- Can manage all tenants, users, and settings
- Bypasses RLS via
is_admin()function - Use case: System administrators
driver- Delivery Driver- Transport operations
- View and update assigned shipments
- Limited safety reporting
- Use case: Delivery drivers
worker- Warehouse Worker- Warehouse operations
- Pick & pack operations
- Inventory movements
- Safety incident reporting
- Use case: Warehouse workers
Safety Pro Roles
safety_officer- Safety Officer (Sicherheitsbeauftragter)- Full control over Safety module
- Create and manage checklists
- Manage incidents and actions
- Create and manage trainings
- View compliance dashboard
- Access AI features
- Generate reports
- Create and manage workflows (visual editor)
- Use case: Primary safety manager
hse_manager- HSE Manager (Health, Safety, Environment)- All Safety Officer permissions
- Access to all tenant safety data
- Compliance reporting
- Strategic analytics
- Cross-tenant insights (if applicable)
- Use case: Senior safety management
auditor- Auditor- Read-only access to all safety data
- View compliance metrics
- Export reports
- Access audit logs
- Cannot modify data
- Use case: External/internal auditors
training_supervisor- Training Supervisor- Create and manage trainings
- View training completions
- Generate certificates
- Access training analytics
- Cannot manage incidents
- Use case: Training department
Inventory Roles
inventory- Inventory Manager- Read access to inventory intelligence
- View inventory metrics and analytics
- Access inventory snapshots
- Use case: Inventory management staff
Role Assignment
Roles are assigned via:
User Management UI (
/admin→ Users)- Admin can assign any role to any user
- All roles are visible in the dropdown
Database (
user_rolestable)sqlINSERT INTO public.user_roles (user_id, role) VALUES ('user-uuid', 'safety_officer');Tenant-specific assignments (
tenant_userstable)- Tenant admins can assign tenant-level roles
- Works in conjunction with
user_roles
Role Validation
Database Validation
The system includes built-in validation to prevent undefined roles:
- Enum Type Constraint: The
app_roleenum type enforces that only valid enum values can be stored - Validation Function:
public.is_valid_app_role(TEXT)checks if a role string is valid - Trigger:
validate_user_role_triggervalidates role assignments on insert/update
-- Check if a role is valid
SELECT public.is_valid_app_role('safety_officer'); -- Returns true
SELECT public.is_valid_app_role('invalid_role'); -- Returns false
-- Get all valid roles
SELECT public.get_all_app_roles(); -- Returns array of all rolesFrontend Validation
TypeScript types ensure type safety:
import { AppRole } from '@/lib/queries';
const role: AppRole = 'safety_officer'; // Type-safe
// const invalid: AppRole = 'invalid'; // TypeScript errorPermission Matrix
| Feature | Admin | Driver | Worker | Safety Officer | HSE Manager | Auditor | Training Supervisor | Inventory |
|---|---|---|---|---|---|---|---|---|
| System Management | ||||||||
| Manage Tenants | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Manage Users | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| System Settings | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Operations | ||||||||
| View Shipments | ✅ | ✅ (own) | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Create Shipments | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Update Shipments | ✅ | ✅ (own) | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Pick & Pack | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Inventory | ||||||||
| View Inventory | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Manage Inventory | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Inventory Intelligence | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Safety | ||||||||
| Report Incident | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Manage Incidents | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Create Checklists | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Manage Trainings | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ |
| View Compliance | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Manage Workflows | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Safety Analytics | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
Role Helper Functions
Database Functions
public.is_admin()- Returns true if current user hasadminrolepublic.is_safety_officer()- Returns true if current user hassafety_officerrolepublic.is_worker()- Returns true if current user hasworkerrolepublic.is_driver()- Returns true if current user hasdriverrolepublic.is_auditor()- Returns true if current user hasauditorrolepublic.has_role(_user_id UUID, _role app_role)- Check if a user has a specific rolepublic.is_valid_app_role(TEXT)- Validate if a role string is validpublic.get_all_app_roles()- Get array of all valid roles
Frontend Usage
import { useAuth } from '@/contexts/AuthContext';
import { AppRole } from '@/lib/queries';
const { userRole } = useAuth();
// Check role
if (userRole === 'safety_officer') {
// Show safety management UI
}
// Type-safe role assignment
const assignRole = async (userId: string, role: AppRole) => {
// ...
};RLS Policy Patterns
Roles are enforced through Row-Level Security (RLS) policies:
-- Example: Safety Officer can access all safety incidents
CREATE POLICY safety_incidents_safety_officer_all ON public.safety_incidents
FOR ALL
USING (
EXISTS (
SELECT 1 FROM public.user_roles ur
WHERE ur.user_id = auth.uid()
AND ur.role::text = 'safety_officer'
)
);Adding New Roles
To add a new role:
Add to enum (separate migration):
sqlALTER TYPE public.app_role ADD VALUE 'new_role';Update TypeScript types:
typescriptexport type AppRole = ... | 'new_role';Add RLS policies for the new role
Update UI (UserManagement component)
Add translations (i18n files)
Update documentation
Safety Officer Specifics
The safety_officer role is critical for Safety Pro module access:
- Defined: ✅ In
app_roleenum (migration20250118000001_add_safety_officer_role.sql) - Function: ✅
public.is_safety_officer()exists - RLS Policies: ✅ Used in all Safety Pro tables
- UI Visibility: ✅ Now visible in User Management
- TypeScript Types: ✅ Included in
AppRoletype
Safety Officer Permissions
- Full CRUD on
safety_incidents - Full CRUD on
safety_actions - Full CRUD on
safety_checklists - Full CRUD on
safety_trainings - Access to
safety_insights - Access to workflow editor
- Can moderate safety feed
- Can manage recognitions
Best Practices
- Always use enum values: Never hardcode role strings
- Validate roles: Use
is_valid_app_role()before assignment - Type safety: Use
AppRoletype in TypeScript - RLS first: Always enforce permissions via RLS, not just UI
- Document changes: Update this doc when adding roles
- Test permissions: Verify RLS policies work correctly