Documentation Index
Fetch the complete documentation index at: https://docs.tuturuuu.com/llms.txt
Use this file to discover all available pages before exploring further.
The Tuturuuu platform implements comprehensive Row-Level Security (RLS) policies to enforce workspace-based multi-tenancy and granular permission controls.
Security Model Overview
Core Principles
- Workspace Isolation: All workspace-scoped resources are isolated via RLS policies
- Permission-Based Access: Actions require specific workspace role permissions
- User Ownership: Users can only access resources they own or have permission to view
- Fail-Secure Default: No access unless explicitly granted by policy
Architecture
┌─────────────────────────────────────────┐
│ PostgreSQL Database │
│ ┌───────────────────────────────────┐ │
│ │ RLS Policies (113+ policies) │ │
│ ├───────────────────────────────────┤ │
│ │ 1. Workspace Membership Check │ │
│ │ 2. Permission Verification │ │
│ │ 3. Ownership Validation │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
Policy Patterns
Pattern 1: Workspace Membership Check
Use Case: User must be a member of the workspace to access resources.
Example: workspace_tasks SELECT policy
CREATE POLICY "Users can view tasks in their workspaces"
ON workspace_tasks
FOR SELECT
USING (
ws_id IN (
SELECT ws_id
FROM workspace_members
WHERE user_id = auth.uid()
)
);
Key Components:
auth.uid(): Gets the authenticated user’s ID
- Subquery checks
workspace_members table
- Returns only rows matching user’s workspaces
Pattern 2: Permission-Based Access
Use Case: User needs specific workspace permission to perform action.
Example: Task UPDATE policy
CREATE POLICY "Users can update tasks with manage_tasks permission"
ON workspace_tasks
FOR UPDATE
USING (
ws_id IN (
SELECT wm.ws_id
FROM workspace_members wm
JOIN workspace_role_permissions wrp
ON wrp.ws_id = wm.ws_id
AND wrp.role_id = wm.role
WHERE wm.user_id = auth.uid()
AND wrp.permission = 'manage_tasks'
)
);
Permission Enum Values:
manage_infrastructure_settings
manage_workspace_settings
manage_workspace_security
manage_users
manage_user_groups
manage_user_roles
manage_finance
manage_calendar
manage_documents
manage_inventory
ai_lab_assistant
view_disabled_users
disable_user
Pattern 3: User Ownership
Use Case: User can only access resources they created.
Example: notes policies
-- SELECT
CREATE POLICY "Users can view their own notes"
ON notes
FOR SELECT
USING (created_by = auth.uid());
-- INSERT
CREATE POLICY "Users can create their own notes"
ON notes
FOR INSERT
WITH CHECK (created_by = auth.uid());
-- UPDATE
CREATE POLICY "Users can update their own notes"
ON notes
FOR UPDATE
USING (created_by = auth.uid());
-- DELETE
CREATE POLICY "Users can delete their own notes"
ON notes
FOR DELETE
USING (created_by = auth.uid());
Pattern 4: Combined Workspace + Permission
Use Case: Workspace membership AND specific permission required.
Example: Calendar event management
CREATE POLICY "Users can manage calendar events with permission"
ON workspace_calendar_events
FOR ALL
USING (
ws_id IN (
SELECT wm.ws_id
FROM workspace_members wm
JOIN workspace_role_permissions wrp
ON wrp.ws_id = wm.ws_id
AND wrp.role_id = wm.role
WHERE wm.user_id = auth.uid()
AND wrp.permission IN ('manage_calendar', 'manage_workspace_settings')
)
);
Pattern 5: Public Read, Restricted Write
Use Case: All workspace members can view, only privileged users can modify.
Example: Workspace settings
-- Read: any workspace member
CREATE POLICY "Members can view workspace settings"
ON workspaces
FOR SELECT
USING (
id IN (
SELECT ws_id FROM workspace_members WHERE user_id = auth.uid()
)
);
-- Write: requires permission
CREATE POLICY "Users can update workspace with permission"
ON workspaces
FOR UPDATE
USING (
id IN (
SELECT wm.ws_id
FROM workspace_members wm
JOIN workspace_role_permissions wrp
ON wrp.ws_id = wm.ws_id AND wrp.role_id = wm.role
WHERE wm.user_id = auth.uid()
AND wrp.permission = 'manage_workspace_settings'
)
);
Pattern 6: Cross-Table Permission Inheritance
Use Case: Permission check depends on related table’s policies.
Example: Task project members inherit from project permissions
CREATE POLICY "Project members can view project tasks"
ON task_project_tasks
FOR SELECT
USING (
project_id IN (
SELECT id FROM task_projects
-- task_projects has its own RLS policy checking workspace membership
)
);
Common RLS Helpers
Get User Workspaces
CREATE FUNCTION get_user_workspaces(user_uuid uuid)
RETURNS TABLE(ws_id text) AS $$
SELECT ws_id
FROM workspace_members
WHERE user_id = user_uuid
AND pending = false;
$$ LANGUAGE sql SECURITY DEFINER;
Check Workspace Permission
CREATE FUNCTION has_workspace_permission(
user_uuid uuid,
workspace_id text,
required_permission workspace_role_permission
)
RETURNS boolean AS $$
SELECT EXISTS (
SELECT 1
FROM workspace_members wm
JOIN workspace_role_permissions wrp
ON wrp.ws_id = wm.ws_id
AND wrp.role_id = wm.role
WHERE wm.user_id = user_uuid
AND wm.ws_id = workspace_id
AND wrp.permission = required_permission
);
$$ LANGUAGE sql SECURITY DEFINER;
Policy Testing
Enable RLS (Always Required)
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
Test Policy as User
-- Impersonate user for testing
SET request.jwt.claim.sub = 'user-uuid-here';
-- Run queries to verify policy
SELECT * FROM workspace_tasks WHERE ws_id = 'workspace-123';
-- Reset
RESET request.jwt.claim.sub;
Verify Policy Coverage
-- Check if all tables have RLS enabled
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
AND tablename NOT IN (
SELECT tablename
FROM pg_tables t
JOIN pg_class c ON c.relname = t.tablename
WHERE c.relrowsecurity = true
);
Security Best Practices
1. Always Enable RLS
-- ✅ Good
CREATE TABLE my_table (...);
ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;
-- ❌ Bad
CREATE TABLE my_table (...);
-- Forgot to enable RLS!
2. Use SECURITY DEFINER Carefully
Only use SECURITY DEFINER for helper functions that need elevated privileges:
-- ✅ Good: Helper needs admin access
CREATE FUNCTION admin_helper() ... SECURITY DEFINER;
-- ❌ Bad: Bypasses RLS unnecessarily
CREATE FUNCTION simple_query() ... SECURITY DEFINER;
3. Test Policies Thoroughly
Always test:
- ✅ Users can access their own data
- ✅ Users cannot access other workspaces’ data
- ✅ Permission checks work correctly
- ✅ Edge cases (pending invites, disabled users)
4. Avoid Policy Conflicts
-- ❌ Bad: Multiple policies may conflict
CREATE POLICY "policy_1" ON table FOR SELECT USING (...);
CREATE POLICY "policy_2" ON table FOR SELECT USING (...);
-- Which one applies?
-- ✅ Good: One comprehensive policy per operation
CREATE POLICY "select_policy" ON table FOR SELECT USING (
condition_1 OR condition_2
);
-- ❌ Bad: Subquery runs for every row
CREATE POLICY "slow_policy" ON table FOR SELECT USING (
column IN (SELECT id FROM expensive_query)
);
-- ✅ Good: Use indexed foreign keys
CREATE POLICY "fast_policy" ON table FOR SELECT USING (
ws_id = (SELECT ws_id FROM workspace_members WHERE user_id = auth.uid() LIMIT 1)
);
Common Policy Patterns by Table Type
Workspace-Scoped Resources
ALTER TABLE resource ENABLE ROW LEVEL SECURITY;
CREATE POLICY "workspace_members_can_view"
ON resource FOR SELECT
USING (ws_id IN (SELECT ws_id FROM workspace_members WHERE user_id = auth.uid()));
CREATE POLICY "permission_required_to_modify"
ON resource FOR ALL
USING (
ws_id IN (
SELECT wm.ws_id FROM workspace_members wm
JOIN workspace_role_permissions wrp
ON wrp.ws_id = wm.ws_id AND wrp.role_id = wm.role
WHERE wm.user_id = auth.uid()
AND wrp.permission = 'specific_permission'
)
);
User-Owned Resources
ALTER TABLE user_resource ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users_can_manage_own"
ON user_resource FOR ALL
USING (user_id = auth.uid());
Public Read Resources
ALTER TABLE public_resource ENABLE ROW LEVEL SECURITY;
CREATE POLICY "anyone_can_read"
ON public_resource FOR SELECT
USING (true);
CREATE POLICY "admin_can_write"
ON public_resource FOR ALL
USING (
EXISTS (
SELECT 1 FROM workspace_members wm
JOIN workspace_role_permissions wrp
ON wrp.ws_id = wm.ws_id AND wrp.role_id = wm.role
WHERE wm.user_id = auth.uid()
AND wrp.permission = 'manage_infrastructure_settings'
)
);
Debugging RLS Issues
Check Active Policies
SELECT schemaname, tablename, policyname, permissive, roles, cmd, qual
FROM pg_policies
WHERE tablename = 'your_table_name';
Test as Specific User
-- In Supabase SQL Editor
SELECT set_config('request.jwt.claim.sub', 'user-uuid', false);
SELECT * FROM workspace_tasks; -- Runs with RLS as that user
Bypass RLS for Admin Operations
Use createAdminClient() from @tuturuuu/supabase in server-side code:
import { createAdminClient } from '@tuturuuu/supabase/next/server';
const sbAdmin = await createAdminClient();
// This client bypasses RLS - use carefully!
Always name createAdminClient() locals sbAdmin, not supabase, so service-role access is visually distinct from request-scoped clients during review.
Migration Example
When adding a new table, always include RLS:
-- Create table
CREATE TABLE new_feature (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
ws_id text REFERENCES workspaces(id) ON DELETE CASCADE,
name text NOT NULL,
created_by uuid REFERENCES workspace_users(id),
created_at timestamptz DEFAULT now()
);
-- Enable RLS
ALTER TABLE new_feature ENABLE ROW LEVEL SECURITY;
-- Add policies
CREATE POLICY "workspace_members_can_view"
ON new_feature FOR SELECT
USING (ws_id IN (
SELECT ws_id FROM workspace_members WHERE user_id = auth.uid()
));
CREATE POLICY "permission_required_to_modify"
ON new_feature FOR ALL
USING (ws_id IN (
SELECT wm.ws_id FROM workspace_members wm
JOIN workspace_role_permissions wrp
ON wrp.ws_id = wm.ws_id AND wrp.role_id = wm.role
WHERE wm.user_id = auth.uid()
AND wrp.permission = 'manage_specific_feature'
));
-- Add indexes for policy performance
CREATE INDEX idx_new_feature_ws_id ON new_feature(ws_id);
CREATE INDEX idx_new_feature_created_by ON new_feature(created_by);