Permissions & Security
MovaLab implements enterprise-grade security with ~40 granular permissions, role-based access control, and database-level Row Level Security.
Security Grade: S (Enterprise-Grade)
Zero SQL injection vulnerabilities, complete RLS coverage on 45+ tables, SECURITY INVOKER views, and function search_path protection.
Permission Categories
Override Permissions (Admin)
Override permissions grant access across all resources, regardless of assignment. Use for admin-level roles.
VIEW_ALL_PROJECTSView any project in the systemEDIT_ALL_PROJECTSEdit any project in the systemDELETE_ALL_PROJECTSDelete any project in the systemVIEW_ALL_ACCOUNTSView/edit/delete any accountVIEW_ALL_DEPARTMENTSView/edit/delete any departmentVIEW_ALL_ANALYTICSView analytics for entire orgProject Permissions
VIEW_PROJECTSView assigned projectsCREATE_PROJECTCreate new projectsEDIT_PROJECTEdit specific project (context required)DELETE_PROJECTDelete specific project (context required)MANAGE_PROJECT_MEMBERSAdd/remove project membersVIEW_PROJECT_ANALYTICSView project analyticsAccount Permissions
VIEW_ACCOUNTSView assigned accountsCREATE_ACCOUNTCreate new client accountsEDIT_ACCOUNTEdit specific account (context required)DELETE_ACCOUNTDelete specific account (context required)MANAGE_ACCOUNT_MEMBERSAdd/remove account membersTask Permissions
VIEW_TASKSView tasks in assigned projectsCREATE_TASKCreate new tasksEDIT_TASKEdit tasksDELETE_TASKDelete tasksASSIGN_TASKSAssign tasks to usersMANAGE_TASK_STATUSChange task statusAdmin Permissions
MANAGE_ROLESCreate/edit/delete rolesMANAGE_USERSManage user assignmentsMANAGE_DEPARTMENTSCreate/edit departmentsVIEW_AUDIT_LOGSView system audit logsMANAGE_WORKFLOWSCreate/edit workflow templatesMANAGE_FORMSCreate/edit form templatesPermission Hierarchy
Permissions are checked in order of precedence. Override permissions grant access everywhere.
Permission Check Flow:
User Login
↓
Load User Profile with Roles
↓
Permission Check Request
├─ Superadmin? → YES → Grant Access
├─ Override Permission? → YES → Grant Access
├─ Base Permission? → YES → Check Context
│ └─ Context Valid? → Grant/Deny
└─ No Permission → Deny Access
↓
Render UI / Execute ActionRow Level Security (RLS)
MovaLab uses PostgreSQL Row Level Security for database-level access control. This provides defense in depth - even if application code has bugs, the database enforces access rules.
45+ Tables Protected
Every table has RLS policies
SECURITY INVOKER Views
Views run with caller's permissions
Function Protection
29 functions with search_path = ''
Zero Linter Errors
Passes Supabase security audit
RLS Policy Example
-- Users can view task assignments for accessible tasks
CREATE POLICY "Users can view task assignments"
ON public.task_assignments
FOR SELECT
TO authenticated
USING (
is_superadmin(auth.uid())
OR EXISTS (
SELECT 1 FROM tasks t
WHERE t.id = task_assignments.task_id
AND (t.assigned_to = auth.uid()
OR t.created_by = auth.uid()
OR t.owner_id = auth.uid())
)
OR task_assignments.user_id = auth.uid()
);Security Features
Rate Limiting
Redis-backed rate limiting prevents brute force attacks. 100 requests/15min for API, 5 requests/15min for auth endpoints.
Input Validation (Zod)
All API inputs validated with Zod schemas. Type-safe validation prevents SQL injection and malformed data.
Security Headers
CSP, HSTS, X-Frame-Options, X-Content-Type-Options, and more. Prevents XSS, clickjacking, and MIME sniffing.
Audit Logging
All critical actions logged with sanitization. Sensitive data (passwords, tokens) automatically redacted.
Best Practices
- 1Always check permissions on the server, even if checked on client
- 2Use Permission enum constants, never hardcode permission strings
- 3Provide context (accountId, projectId) for context-aware permissions
- 4Handle async properly - always await hasPermission() calls
- 5Show meaningful error messages - tell users why access is denied
- 6Test with multiple user types: no-perms, view-only, full-access, superadmin
- 7Use override permissions sparingly for admin-level access only
Usage Example
import { hasPermission } from '@/lib/rbac';
import { Permission } from '@/lib/permissions';
// Server Component / API Route
const userProfile = await getCurrentUserProfileServer();
// Simple check
const canView = await hasPermission(
userProfile,
Permission.VIEW_PROJECTS
);
// With context (for context-aware permissions)
const canEdit = await hasPermission(
userProfile,
Permission.EDIT_ACCOUNT,
{ accountId: 'account-123' }
);
if (!canEdit) {
return { error: 'Permission denied' };
}Learn More
Explore the full permission system in the source code or read the security implementation guide.