Row Level Security (RLS) has been implemented for the SaaS platform to provide fine-grained access control at the database level. This ensures that users can only access data they are authorized to see, with policies enforced directly by PostgreSQL.
RLS is ENABLED but not FORCED
This means:
saas_user) can currently bypass RLS to maintain application functionalityTwo helper functions have been created:
current_user_id()Returns the UUID of the current user from the PostgreSQL session context.
SELECT current_user_id();
is_admin()Returns true if the current user has the 'admin' role.
SELECT is_admin();
Users in the __sys_users table have a role column with possible values:
admin: Full access to all data and administrative functionsuser: Limited access based on ownership and organization membershipTo set a user as admin:
UPDATE __sys_users SET role = 'admin' WHERE email = 'admin@example.com';
To enforce RLS for all roles (including the service role), uncomment these lines in database/init/05-row-level-security.sql:
ALTER TABLE __sys_users FORCE ROW LEVEL SECURITY;
ALTER TABLE __sys_sessions FORCE ROW LEVEL SECURITY;
ALTER TABLE __sys_api_keys FORCE ROW LEVEL SECURITY;
ALTER TABLE __sys_applications FORCE ROW LEVEL SECURITY;
ALTER TABLE __sys_deployments FORCE ROW LEVEL SECURITY;
ALTER TABLE __sys_audit_logs FORCE ROW LEVEL SECURITY;
ALTER TABLE __sys_email_queue FORCE ROW LEVEL SECURITY;
ALTER TABLE __sys_smtp_settings FORCE ROW LEVEL SECURITY;
Then apply the migration:
docker exec saas-postgres psql -U saas_user -d saas_db -f /docker-entrypoint-initdb.d/05-row-level-security.sql
The rlsContext.ts middleware has been created to set user context in PostgreSQL sessions:
import { setRLSContext } from './middleware/rlsContext';
// Apply after authentication middleware
app.use(authenticateToken);
app.use(setRLSContext);
This middleware:
Note: Full integration requires refactoring routes to use req.dbClient instead of the global pool.
-- Set context as a regular user
SET LOCAL app.current_user_id = 'user-uuid-here';
-- Try to see all users (should only see own profile)
SELECT * FROM __sys_users;
-- Try to see all API keys (should only see own keys)
SELECT * FROM __sys_api_keys;
-- Set context as an admin user
SET LOCAL app.current_user_id = 'admin-uuid-here';
-- Should see all users
SELECT * FROM __sys_users;
-- Should see all API keys
SELECT * FROM __sys_api_keys;
-- List all RLS policies
SELECT schemaname, tablename, policyname, permissive, roles, cmd, qual
FROM pg_policies
WHERE schemaname = 'public'
ORDER BY tablename, policyname;
Indexes have been created to optimize RLS policy checks:
idx_users_role on __sys_users(role)idx_sessions_user_id on __sys_sessions(user_id)idx_api_keys_created_by on __sys_api_keys(created_by)idx_applications_created_by on __sys_applications(created_by)idx_deployments_application_id on __sys_deployments(application_id)idx_audit_logs_user_id on __sys_audit_logs(user_id)Check if RLS is enabled:
SELECT tablename, rowsecurity FROM pg_tables
WHERE schemaname = 'public' AND tablename LIKE '__sys_%';
Check user role:
SELECT id, email, role FROM __sys_users WHERE email = 'user@example.com';
Verify current context:
SELECT current_user_id(), is_admin();
If background jobs or system operations fail with permission errors:
Use the bypassRLS() helper function:
import { bypassRLS } from './middleware/rlsContext';
const result = await bypassRLS(async () => {
// System operation that needs full access
return await pool.query('SELECT * FROM __sys_email_queue');
});
Or use executeAsUser() for operations on behalf of a specific user:
import { executeAsUser } from './middleware/rlsContext';
const result = await executeAsUser(userId, async (client) => {
return await client.query('SELECT * FROM __sys_users');
});
Always use HTTPS - RLS protects data at the database level, but authentication tokens should be protected in transit
Validate JWT tokens - Ensure the authenticateToken middleware properly validates tokens before setting user context
Regular audits - Monitor __sys_audit_logs for suspicious activities
Least privilege - Grant users the minimum role needed for their tasks
Test policies - Thoroughly test RLS policies before enforcing them in production
✅ RLS policies created ✅ Helper functions implemented ✅ Indexes created for performance ✅ User roles added ✅ Service role can bypass RLS
setRLSContext middleware to API servicereq.dbClientdatabase/init/05-row-level-security.sqlservices/api/src/middleware/rlsContext.ts