From 6b9171885a58d47bf11416f5813bd105832404c9 Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 28 Jul 2026 13:48:57 +0530 Subject: [PATCH] security(supabase): add RLS enablement and policies for users, goals, and streak_freezes tables --- DEVELOPMENT.md | 15 +++++++++++++++ supabase/schema.sql | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 32cbd2aeb..37ca3c17e 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -53,6 +53,21 @@ The `service_role` key is a **database superkey** — it completely bypasses all DevTrack uses this key only in server-side API routes. See `.env.example` for detailed security requirements. +### 🔒 Database Security: Row Level Security (RLS) & Verification + +All user-owned database tables in DevTrack (`users`, `goals`, `goal_history`, `streak_freezes`, `user_github_achievements`, `user_sponsor_metrics`, `wakatime_stats`, `collaboration_rooms`, etc.) have Row Level Security (RLS) enabled in `supabase/schema.sql`. + +#### Why RLS Matters +Although DevTrack primarily performs server-side database operations using `SUPABASE_SERVICE_ROLE_KEY`, explicit RLS policies provide critical defense-in-depth: +- If client-side queries or the public `NEXT_PUBLIC_SUPABASE_ANON_KEY` are used directly, users are strictly restricted to reading and writing their own data (`user_id = auth.uid()`). +- Prevents cross-tenant data leaks and unauthorized modifications. + +#### Verifying RLS in the Supabase Dashboard +1. Log in to your project on [supabase.com](https://supabase.com). +2. Go to **Authentication** → **Policies** (or **Table Editor**). +3. Confirm that **RLS Enabled** (green lock badge) is present on all user tables (`users`, `goals`, `streak_freezes`, etc.). +4. Verify that each table has explicit policies configured (e.g., `"Users can manage own goals"` with expression `user_id = auth.uid()::text`). + --- ## 3. Create a GitHub OAuth App diff --git a/supabase/schema.sql b/supabase/schema.sql index 4b87d7528..214709c65 100644 --- a/supabase/schema.sql +++ b/supabase/schema.sql @@ -38,6 +38,11 @@ add column if not exists dashboard_layout jsonb not null default CREATE INDEX IF NOT EXISTS users_leaderboard_opt_in_idx ON users(leaderboard_opt_in) WHERE leaderboard_opt_in = true; +alter table users enable row level security; +drop policy if exists "Users can manage own record" on users; +create policy "Users can manage own record" + on users for all + using (id = auth.uid()::text); create table if not exists goals ( id text primary key default gen_random_uuid()::text, @@ -57,6 +62,11 @@ create table if not exists goals ( ); create index if not exists goals_user_period on goals(user_id, period_start); create index if not exists goals_user_category on goals(user_id, category); +alter table goals enable row level security; +drop policy if exists "Users can manage own goals" on goals; +create policy "Users can manage own goals" + on goals for all + using (user_id = auth.uid()::text); create table if not exists goal_history ( id text primary key default gen_random_uuid()::text, @@ -121,6 +131,11 @@ create table if not exists streak_freezes ( create index if not exists streak_freezes_user on streak_freezes(user_id); create unique index if not exists streak_freezes_user_date_uniq on streak_freezes(user_id, freeze_date); +alter table streak_freezes enable row level security; +drop policy if exists "Users can manage own streak freezes" on streak_freezes; +create policy "Users can manage own streak freezes" + on streak_freezes for all + using (user_id = auth.uid()::text); create table if not exists notifications ( id text primary key default gen_random_uuid()::text,