Skip to main content

Overview

EpiNeko uses PostgreSQL Row Level Security (RLS) to ensure users can only access and modify their own data. All tables have RLS enabled with specific policies for different operations.
RLS policies are defined in supabase/migrations/20260218_initial_schema.sql:13 and enforce security at the database level.
Never disable RLS on production tables. RLS is your primary defense against unauthorized data access.

Why RLS?

Row Level Security provides database-level security that:
  • Cannot be bypassed - Even if application code has bugs, RLS protects data
  • Works automatically - No need to add WHERE clauses to every query
  • Leverages Supabase Auth - Uses auth.uid() to identify current user
  • Prevents data leaks - Users can only see their own data

profiles Table Policies

SELECT Policy: Public profiles are viewable by everyone

string
Public profiles are viewable by everyone.
SELECT
Applies to SELECT queries
using (true)
Always returns true - all profiles are publicly visible
Use Case: Anyone can view user profiles, enabling features like public user pages and profile discovery.

INSERT Policy: Users can insert their own profile

string
Users can insert their own profile.
INSERT
Applies to INSERT queries
with check (auth.uid() = id)
User can only insert if the profile id matches their authenticated auth.uid()
Use Case: Prevents users from creating profiles for other users. The handle_new_user() trigger creates profiles automatically, so this policy rarely applies in practice.

UPDATE Policy: Users can update own profile

string
Users can update own profile.
UPDATE
Applies to UPDATE queries
using (auth.uid() = id)
User can only update rows where id matches their auth.uid()
Use Case: Users can only edit their own profile information.

user_library Table Policies

SELECT Policy: Users can view their own library items

string
Users can view their own library items.
SELECT
Applies to SELECT queries
using (auth.uid() = user_id)
User can only select rows where user_id matches their auth.uid()
Use Case: Users can only see anime in their own library, not other users’ libraries.

INSERT Policy: Users can insert their own library items

string
Users can insert their own library items.
INSERT
Applies to INSERT queries
with check (auth.uid() = user_id)
User can only insert if user_id matches their auth.uid()
Use Case: Users can only add anime to their own library.

UPDATE Policy: Users can update their own library items

string
Users can update their own library items.
UPDATE
Applies to UPDATE queries
using (auth.uid() = user_id)
User can only update rows where user_id matches their auth.uid()
Use Case: Users can only modify anime in their own library (change status, update score, etc.).

DELETE Policy: Users can delete their own library items

string
Users can delete their own library items.
DELETE
Applies to DELETE queries
using (auth.uid() = user_id)
User can only delete rows where user_id matches their auth.uid()
Use Case: Users can remove anime from their own library but cannot delete other users’ library items.

How RLS Works

Authentication Context

RLS policies use auth.uid() to get the current user’s ID from the JWT token:
  • Authenticated users: auth.uid() returns their UUID
  • Unauthenticated users: auth.uid() returns NULL

Policy Evaluation

PostgreSQL evaluates RLS policies for every query:
  1. SELECT: Check USING clause - if false, row is filtered out
  2. INSERT: Check WITH CHECK clause - if false, insert fails
  3. UPDATE: Check USING clause - if false, row cannot be updated
  4. DELETE: Check USING clause - if false, row cannot be deleted

Example Flow

Testing RLS Policies

Test as Different Users

Using the Supabase Dashboard

  1. Go to SQL Editor in Supabase Dashboard
  2. Use auth.uid() to test as specific users:

Bypassing RLS (Admin Operations)

Only bypass RLS for trusted admin operations. Never expose these functions to client-side code.

Service Role Key

Use the service role key for operations that need to bypass RLS:

Security Definer Functions

Create database functions with SECURITY DEFINER to run with elevated privileges:

Common Issues

Error: new row violates row-level security policyThis means you’re trying to insert/update data that doesn’t pass the RLS policy check. Common causes:
  • Inserting with wrong user_id
  • Not authenticated when required
  • Trying to modify another user’s data
No rows returned when you expect dataRLS is filtering out the rows. Common causes:
  • Wrong user authenticated
  • Not authenticated when required
  • Querying with wrong user_id

Best Practices

Policy Summary

Database Schema

View complete database schema

Supabase Integration

How to use Supabase clients

Library Service

High-level library operations with RLS

Supabase RLS Docs

Official RLS documentation