Skip to content

RLS Policy Reference

Overview

Row Level Security (RLS) is PostgreSQL's built-in mechanism for restricting which rows a given user can read or modify. LenserFight enables RLS on every table across all schemas. Policies are additive: a row is accessible if any matching policy grants access.

The service_role key bypasses RLS entirely and should only be used in trusted server-side contexts (Edge Functions, background jobs).

Auth Tiers

TierDescriptionRLS Behavior
anonUnauthenticated requests (public API key only)Restricted to explicitly public data
authenticatedLogged-in user with a valid JWTScoped to own data plus public data
service_roleServer-side key with elevated privilegesBypasses RLS completely

Policy Inventory

lensers Schema

profiles

OperationTierConditionNotes
SELECTanonAll rowsPublic profile data
SELECTauthenticatedAll rowsPublic profile data
INSERTauthenticateduser_id = auth.uid()User creates own profile
UPDATEauthenticateduser_id = auth.uid()User edits own profile
DELETE--Not allowedSoft-delete via deletion_requested_at

Soft-delete pattern: users set deletion_requested_at via UPDATE. A background job handles actual removal after a grace period.


content Schema

threads

OperationTierConditionNotes
SELECTanonvisibility = 'public'Public threads only
SELECTauthenticatedvisibility = 'public' OR lenser_id = lensers.get_auth_lenser_id()Own threads plus public
INSERTauthenticatedlenser_id = lensers.get_auth_lenser_id()Create own threads
UPDATEauthenticatedlenser_id = lensers.get_auth_lenser_id()Edit own threads
DELETEauthenticatedlenser_id = lensers.get_auth_lenser_id()Delete own threads

xp Schema

rules

OperationTierConditionNotes
SELECTanon / authenticatedAll rowsPublic reference data
INSERT / UPDATE / DELETEservice_role--Admin-managed only

events

OperationTierConditionNotes
SELECTauthenticatedlenser_id = lensers.get_auth_lenser_id()Own events only
INSERT--Via xp.apply() (SECURITY DEFINER)Never inserted directly

totals, levels, streaks, seasons

OperationTierConditionNotes
SELECTauthenticatedlenser_id = lensers.get_auth_lenser_id()Read own data
INSERT / UPDATE / DELETE--Managed by triggers and functionsNo direct writes

ai Schema

models

OperationTierConditionNotes
SELECTanon / authenticatedis_public = truePublic models only
INSERT / UPDATE / DELETEservice_role--Admin-managed

generations

OperationTierConditionNotes
SELECTauthenticatedlenser_id = lensers.get_auth_lenser_id()Own generations
INSERTauthenticatedlenser_id = lensers.get_auth_lenser_id()Create own

tenancy Schema

workspaces

PolicyOperationTierConditionNotes
members_select_own_workspacesSELECTauthenticatedUser is a member of the workspaceMembers can see their own workspaces
admin_update_workspaceUPDATEauthenticatedUser is admin/owner of the workspaceAdmin/owner can update workspace
authenticated_insert_workspaceINSERTauthenticatedowner_id = lensers.get_auth_lenser_id()Authenticated users can create workspaces they own

workspace_members

PolicyOperationTierConditionNotes
members_select_same_workspaceSELECTauthenticatedUser is a member of the same workspaceMembers can see co-members
admin_insert_membersINSERTauthenticatedUser is admin/owner of the workspaceAdmin/owner can add members
admin_delete_membersDELETEauthenticatedUser is admin/owner of the workspaceAdmin/owner can remove members

media Schema

objects

PolicyOperationTierConditionNotes
authenticated_select_own_or_publicSELECTauthenticatedOwner, public visibility, or workspace memberOwner, public, or workspace member
anon_select_publicSELECTanonvisibility = 'public'Public objects visible to anonymous
authenticated_insert_ownINSERTauthenticatedOwner + workspace memberOwner + workspace member can create
authenticated_update_ownUPDATEauthenticatedowner_id = lensers.get_auth_lenser_id()Owner can update
authenticated_delete_ownDELETEauthenticatedowner_id = lensers.get_auth_lenser_id()Owner can delete

attachments

PolicyOperationTierConditionNotes
authenticated_select_attachmentsSELECTauthenticatedParent object is accessible to userCan see if object is accessible
anon_select_public_attachmentsSELECTanonParent object is publicPublic object attachments visible to anon
authenticated_insert_attachmentsINSERTauthenticatedUser owns the parent objectObject owner can attach
authenticated_delete_attachmentsDELETEauthenticatedUser owns the parent objectObject owner can detach

lenses Schema

lenses

OperationTierConditionNotes
SELECTanonvisibility = 'public' AND status = 'published'Public published only
SELECTauthenticated(visibility IN ('public','community') AND status = 'published') OR lenser_id = ownerOwn + public/community
INSERTauthenticatedis_active_lenser(uid()) AND lenser_id = current_active_lenser_id()Active lenser check
UPDATEauthenticatedSame owner check in USING + WITH CHECKOwner only
DELETEauthenticatedSame owner checkOwner only

versions

OperationTierConditionNotes
SELECTanonParent lens is public + publishedVia public lens
SELECTauthenticatedParent lens owner OR public/community + publishedOwn + public
INSERTauthenticatedParent lens owned by callerCreate on own lens
UPDATEauthenticatedParent lens owned by caller AND version status = draftDraft only

version_parameters

OperationTierConditionNotes
SELECTanonParent lens is public + publishedVia public lens
SELECTauthenticatedParent lens owner OR public/community + publishedOwn + public
INSERTauthenticatedParent lens owned AND version is draftDraft only
UPDATEauthenticatedParent lens owned AND version is draftDraft only

parameters (DEPRECATED)

OperationTierConditionNotes
SELECTanonParent lens is public + publishedRead-only for all
SELECTauthenticatedParent lens owner OR public/community + publishedRead-only

Note: lenses.parameters is deprecated. Use lenses.version_parameters instead.

Grant Summary

Tableanonauthenticated
lensesSELECTSELECT, INSERT, UPDATE, DELETE
versionsSELECTSELECT, INSERT, UPDATE
parametersSELECTSELECT (read-only legacy)
version_parametersSELECTSELECT, INSERT, UPDATE, DELETE
version_resourcesSELECTSELECT, INSERT, DELETE

Common Patterns

lensers.get_auth_lenser_id()

A helper function that maps auth.uid() (Supabase auth UUID) to the internal lenser_id in the lensers.profiles table. Used in most authenticated policies to avoid repeated subqueries.

Immutable Votes

Votes cannot be updated. To change a vote, the user must DELETE their existing vote and INSERT a new one. This preserves an audit-friendly pattern and avoids partial-update edge cases.

Soft-Delete

Tables using soft-delete (rubrics, templates) include deleted_at IS NULL in their USING clauses. Rows with a non-null deleted_at are invisible to all non-service-role queries.