Row-Level Security (RLS) is PostgreSQL’s mechanism for attaching authorization logic directly to tables. When a query runs, the database engine automatically appends the active policy’sDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ency07/B2B-import/llms.txt
Use this file to discover all available pages before exploring further.
USING clause as a WHERE predicate, making it impossible — at the database layer — for a user to read or write rows outside their permitted scope. In B2B Import ERP, RLS is the last line of tenant isolation defence after application-level checks.
How RLS Works with Supabase
When a user authenticates, Supabase issues a signed JWT containing the user’sauth.uid() (the UUID from auth.users). Every database query made by the supabase anon/user client sends this JWT in the Authorization header. PostgreSQL extracts it via the auth.uid() built-in function, making it available to policy expressions.
The users table joins auth.uid() to the operational tenant_id and is_platform_user flag:
Standard Tenant Isolation Policy
Every operational table uses a variant of this template, as seen in20260617000000_init_core.sql:
is_platform_super_admin() helper function (defined in the same migration) encapsulates the is_platform_user check:
FOR ALL policy covers SELECT, INSERT, UPDATE, and DELETE with a single clause. Tables that need more granular control define separate per-operation policies.
SUPER_ADMIN Bypass
Users whoseusers.is_platform_user = true pass the first condition of every isolation policy and therefore see all tenants’ data. This flag is set only for platform-level administrators who manage the multi-tenant infrastructure — it is never granted to individual tenant users.
is_platform_super_admin() is also used to gate write access on tables like permissions:
user_access_logs:
Read-Only Policies: AUDITOR Role
TheAUDITOR role is granted SELECT on operational and audit tables but cannot INSERT, UPDATE, or DELETE. This is enforced by defining separate operation-scoped policies:
FOR SELECT scope means this policy never allows writes — the absence of a write policy for the AUDITOR role implicitly denies mutations.
Client Portal Policies
TheCLIENTE role is used for the self-service client portal. Portal users are linked to a specific client_id through their users record. Their policies restrict access to rows where client_id or tenant_id matches their own scope.
A typical read-only portal policy follows the same tenant-isolation pattern but scoped to the client_id found on the authenticated user’s record:
INSERT or DELETE policies on invoices or quotes — they are read-only consumers of the ERP data.
User Self-Service Policies
Regular authenticated users can read their own access log entries regardless of role:audit_log Policy
audit_log is driven entirely by server-side triggers — the application never calls INSERT on it directly. The core policy (from 20260617000000_init_core.sql) uses the standard FOR ALL tenant-isolation pattern:
- SELECT: Any authenticated user within the same tenant can read their own audit trail records.
- INSERT: Permitted for authenticated users within the same tenant (triggers fire under their session context).
- UPDATE / DELETE: No additional policies exist — effectively denied for all non-SUPER_ADMIN roles because application Server Actions never issue these statements on
audit_log.
Service Role Bypass
supabaseAdmin is constructed with the SUPABASE_SERVICE_ROLE_KEY environment variable. The Supabase gateway recognises service-role tokens and instructs PostgreSQL to execute queries as a superuser, bypassing all RLS policies entirely.
Intentional uses of the service role in this project:
| Use Case | Why Service Role is Needed |
|---|---|
| Wizard form submission | Visitor is unauthenticated — no JWT, no tenant session. |
| Server Actions (all) | Trusted server code; avoids per-user policy overhead. |
| Admin operations (tenant creation, seeding) | Require cross-tenant writes. |
Security Testing
Run the built-in security audit script to verify that RLS policies are active on all expected tables and that no policy gaps exist:rowsecurity = true for every table listed in the security model and that no table has zero policies defined.