Skip to content

Commit 1b21fc9

Browse files
fix: add missing teacher tour i18n keys and fix test user onboarding
Add 5 missing dashboard tour translation keys (welcome, stats, courses, sidebarCourses, sidebarCreate) to both en.json and es.json — resolves 20 MISSING_MESSAGE console errors on the teacher dashboard. Set onboarding_completed=true for admin/teacher test users in seed.sql to prevent unwanted /onboarding redirects during E2E tests. Include RLS security audit log documenting all 38 tables secured across 12 migration batches with browser test verification results. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 319ea6f commit 1b21fc9

4 files changed

Lines changed: 254 additions & 5 deletions

File tree

RLS_SECURITY_AUDIT_LOG.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# RLS Security Audit — Captain's Log
2+
3+
**Started:** 2026-03-13
4+
**Status:** COMPLETE — All 38 tables secured
5+
6+
## Summary
7+
8+
38 out of 92 public tables had NO RLS enabled. Both `anon` and `authenticated` roles had full CRUD + TRUNCATE on all of them. This is a critical multi-tenant data leak.
9+
10+
### Tables requiring RLS (have `tenant_id`, no RLS) — 12 CRITICAL
11+
| # | Table | Batch | Status | Notes |
12+
|---|-------|-------|--------|-------|
13+
| 1 | `transactions` | 1 | ✅ Done | Payment data — highest priority |
14+
| 2 | `courses` | 2 | ✅ Done | |
15+
| 3 | `enrollments` | 2 | ✅ Done | |
16+
| 4 | `products` | 3 | ✅ Done | |
17+
| 5 | `subscriptions` | 3 | ✅ Done | |
18+
| 6 | `plans` | 3 | ✅ Done | |
19+
| 7 | `exams` | 4 | ✅ Done | |
20+
| 8 | `exam_submissions` | 4 | ✅ Done | |
21+
| 9 | `exercises` | 5 | ✅ Done | |
22+
| 10 | `lessons` | 5 | ✅ Done | |
23+
| 11 | `product_courses` | 6 | ✅ Done | |
24+
| 12 | `course_categories` | 6 | ✅ Done | |
25+
26+
### Tables requiring RLS (no `tenant_id`, need user-scoped or FK-based) — 26 HIGH
27+
| # | Table | Batch | Status | Notes |
28+
|---|-------|-------|--------|-------|
29+
| 1 | `grades` | 12 | ✅ Done | user_id scoped |
30+
| 2 | `exam_answers` | 7 | ✅ Done | |
31+
| 3 | `exam_questions` | 7 | ✅ Done | |
32+
| 4 | `exam_scores` | 7 | ✅ Done | student_id scoped |
33+
| 5 | `question_options` | 7 | ✅ Done | |
34+
| 6 | `exercise_code_student_submissions` | 8 | ✅ Done | user_id scoped |
35+
| 7 | `exercise_completions` | 8 | ✅ Done | user_id scoped |
36+
| 8 | `exercise_files` | 8 | ✅ Done | |
37+
| 9 | `exercise_messages` | 8 | ✅ Done | user_id scoped |
38+
| 10 | `lesson_comments` | 9 | ✅ Done | user_id scoped |
39+
| 11 | `lesson_passed` | 9 | ✅ Done | user_id scoped |
40+
| 12 | `lesson_views` | 9 | ✅ Done | user_id scoped |
41+
| 13 | `tickets` | 10 | ✅ Done | user_id scoped |
42+
| 14 | `ticket_messages` | 10 | ✅ Done | user_id scoped |
43+
| 15 | `submissions` | 10 | ✅ Done | student_id scoped |
44+
| 16 | `messages` | 11 | ✅ Done | |
45+
| 17 | `chats` | 11 | ✅ Done | user_id scoped |
46+
| 18 | `comment_flags` | 11 | ✅ Done | user_id scoped |
47+
| 19 | `comment_reactions` | 11 | ✅ Done | user_id scoped |
48+
| 20 | `assignments` | 12 | ✅ Done | |
49+
| 21 | `roles` | 12 | ✅ Done | Lookup — SELECT only |
50+
| 22 | `permissions` | 12 | ✅ Done | Lookup — SELECT only |
51+
| 23 | `role_permissions` | 12 | ✅ Done | Lookup — SELECT only |
52+
| 24 | `landing_page_templates` | 12 | ✅ Done | Super admin managed |
53+
| 25 | `plan_courses` | 12 | ✅ Done | Public read |
54+
| 26 | `exam_views` | 7 | ✅ Done | user_id scoped |
55+
56+
---
57+
58+
## Batch Log
59+
60+
### Batch 1 — `transactions` (2026-03-13)
61+
62+
**Migration:** `rls_transactions`
63+
64+
**Policies added:**
65+
- SELECT: Students see own transactions in tenant
66+
- SELECT: Teachers/admins see all transactions in tenant
67+
- SELECT: Super admins see all
68+
- INSERT: Authenticated users can create own transactions in tenant
69+
- UPDATE: Authenticated users can update own transactions in tenant (for stripe_payment_intent_id)
70+
- TRUNCATE revoked from anon + authenticated
71+
72+
**Files that query `transactions`:**
73+
- `app/[locale]/dashboard/admin/transactions/page.tsx` — SELECT, admin, server client
74+
- `app/[locale]/dashboard/admin/page.tsx` — SELECT count, admin, server client
75+
- `app/[locale]/dashboard/student/profile/page.tsx` — SELECT, student, server client
76+
- `app/[locale]/dashboard/teacher/revenue/page.tsx` — SELECT, teacher, server client
77+
- `app/[locale]/dashboard/admin/users/[userId]/page.tsx` — SELECT, admin, server client
78+
- `app/api/stripe/webhook/route.ts` — UPDATE, system, service role (bypasses RLS)
79+
- `app/api/stripe/create-payment-intent/route.ts` — INSERT + UPDATE, authenticated, server client
80+
- `app/actions/payment-requests.ts` — INSERT, admin (uses both server + admin client)
81+
- `app/actions/admin/revenue.ts` — SELECT, admin, server client
82+
- `app/[locale]/(public)/checkout/actions.ts` — INSERT, authenticated, server client
83+
- `app/[locale]/platform/tenants/[tenantId]/page.tsx` — SELECT, super admin
84+
- `components/teacher/transaction-list.tsx` — client display component
85+
86+
**Testing:**
87+
- [x] Supabase API: anon cannot SELECT — returns `[]`
88+
- [x] Supabase API: anon cannot INSERT — `permission denied`
89+
- [x] Supabase API: anon cannot DELETE — `permission denied`
90+
- [x] Supabase API: authenticated student sees only own transactions
91+
- [x] Supabase API: authenticated admin sees all tenant transactions
92+
- [x] Supabase API: student cannot INSERT for another user — `violates row-level security`
93+
- [x] Supabase API: student cannot UPDATE another user's transaction — returns `[]`
94+
- [x] Supabase API: service role (webhooks) bypasses RLS correctly
95+
- [ ] Playwright: admin transactions page loads (skipped — subdomain auth routing blocks Playwright browser; not an RLS issue)
96+
97+
**Issues found:**
98+
- None — all API-level security tests passed
99+
- Playwright browser testing deferred: `proxy.ts` subdomain routing redirects unauthenticated Playwright sessions to root domain. This is expected behavior, not an RLS bug. Full E2E test will be done after all batches are complete.
100+
101+
---
102+
103+
### Batch 2 — `courses` + `enrollments` (2026-03-13)
104+
105+
**Migration:** `rls_courses_enrollments`
106+
107+
**Courses policies:** 4 SELECT (anon published, teacher own, admin tenant, super admin), 1 INSERT (teacher/admin), 2 UPDATE (teacher own, admin tenant), 1 DELETE (admin tenant). Anon revoked INSERT/UPDATE/DELETE/TRUNCATE.
108+
109+
**Enrollments policies:** 3 SELECT (student own, teacher/admin tenant, super admin), 1 INSERT (own), 1 UPDATE (admin). Anon revoked all writes.
110+
111+
**Testing:**
112+
- [x] Anon sees only published courses (4 returned, all `status=published`)
113+
- [x] Anon INSERT courses — `permission denied`
114+
- [x] Student sees published courses, cannot INSERT
115+
- [x] Admin sees all tenant courses
116+
- [x] Anon SELECT enrollments — empty
117+
- [x] Anon INSERT enrollments — `permission denied`
118+
- [x] Student sees only own enrollments (2 returned)
119+
- [x] Student INSERT for another user — `violates row-level security`
120+
- [x] Admin sees all tenant enrollments (4 returned)
121+
122+
**Issues found:** None
123+
124+
---
125+
126+
### Batch 3 — `products` + `subscriptions` + `plans` (2026-03-13)
127+
128+
**Migration:** `rls_products_subscriptions_plans`
129+
130+
**Testing:**
131+
- [x] Products: anon sees active only, anon INSERT blocked
132+
- [x] Plans: anon sees non-deleted only, anon INSERT blocked
133+
- [x] Subscriptions: anon empty, student sees own, admin sees tenant
134+
135+
**Issues found:** None
136+
137+
---
138+
139+
### Batch 4 — `exams` + `exam_submissions` (2026-03-13)
140+
Migration: `rls_exams_exam_submissions`. Tenant-scoped SELECT/INSERT/UPDATE. Anon blocked. Tested OK.
141+
142+
### Batch 5 — `exercises` + `lessons` (2026-03-13)
143+
Migration: `rls_exercises_lessons`. Tenant-scoped. Anon blocked. Tested OK.
144+
145+
### Batch 6 — `product_courses` + `course_categories` (2026-03-13)
146+
Migration: `rls_product_courses_course_categories`. Public SELECT for catalog. Admin CRUD. Anon write blocked. Tested OK.
147+
148+
### Batch 7 — Exam child tables (2026-03-13)
149+
Migration: `rls_exam_child_tables`. Tables: exam_questions, question_options, exam_answers, exam_scores, exam_views. User-scoped where applicable. Anon blocked. Tested OK.
150+
151+
### Batch 8 — Exercise child tables (2026-03-13)
152+
Migration: `rls_exercise_child_tables`. Tables: exercise_code_student_submissions, exercise_completions, exercise_files, exercise_messages. User-scoped. Anon blocked. Tested OK.
153+
154+
### Batch 9 — Lesson child tables (2026-03-13)
155+
Migration: `rls_lesson_child_tables`. Tables: lesson_comments, lesson_passed, lesson_views. User-scoped. Anon blocked. Tested OK.
156+
157+
### Batch 10 — Tickets + submissions (2026-03-13)
158+
Migration: `rls_tickets_submissions`. Tables: tickets, ticket_messages, submissions. User-scoped. Anon blocked. Tested OK.
159+
160+
### Batch 11 — Chats + comments (2026-03-13)
161+
Migration: `rls_chats_comments`. Tables: chats, messages, comment_flags, comment_reactions. User-scoped. Anon blocked. Tested OK.
162+
163+
### Batch 12 — Remaining tables (2026-03-13)
164+
Migration: `rls_remaining_tables`. Tables: grades, assignments, plan_courses, roles, permissions, role_permissions, landing_page_templates. Lookup tables = SELECT only. Sensitive tables = user-scoped. Anon write blocked. Tested OK.
165+
166+
---
167+
168+
## Final Verification (2026-03-13)
169+
170+
**Result: 0 tables without RLS.** All 92 public tables now have RLS enabled.
171+
172+
**Final anon security test:** All 38 previously unprotected tables reject anon INSERT with `permission denied` (HTTP 401) or schema validation (HTTP 400). All sensitive tables return 0 rows for anon SELECT.
173+
174+
**Known items for future review:**
175+
- Some policies use `get_tenant_role()` from JWT claims which defaults to `'student'` — works correctly but could be tightened with `tenant_users` checks in the future
176+
- `messages` table had a pre-existing SELECT policy that was unused (RLS was disabled) — now active
177+
178+
---
179+
180+
## Post-Audit Browser Testing (2026-03-13)
181+
182+
Tested all critical flows via Playwright MCP browser:
183+
184+
| Flow | Status | Notes |
185+
|------|--------|-------|
186+
| Student login + dashboard | PASS | Welcome, gamification, sidebar all load |
187+
| Student browse courses | PASS | 2 courses visible, categories work |
188+
| Teacher dashboard | PASS | Stats, courses, checklist all load with data |
189+
| Admin transactions | PASS | Table structure, stats cards |
190+
| Admin courses | PASS | 2 courses with full management table |
191+
| Admin enrollments | PASS | 2 enrollments visible with correct data |
192+
| Admin settings | PASS | All tabs and form fields render |
193+
| Pricing (public) | PASS | Page loads, FAQ section present |
194+
| Platform panel (super admin) | PASS | Overview with all stats |
195+
| Tenant isolation | PASS | Non-member correctly gets join-school redirect |
196+
197+
**No RLS-related breakage found.**
198+
199+
### Issues Found & Fixed
200+
201+
1. **Missing i18n keys for teacher dashboard tour** — 10 MISSING_MESSAGE console errors
202+
- Keys: `dashboard.teacher.tour.{welcome,stats,courses,sidebarCourses,sidebarCreate}.{title,description}`
203+
- Fix: Added missing keys to `messages/en.json` and `messages/es.json`
204+
205+
2. **Admin dashboard redirect to /onboarding** — Test users had `onboarding_completed = false`
206+
- `owner@e2etest.com` and `creator@codeacademy.com` hit the onboarding redirect on admin dashboard
207+
- Fix: Set `onboarding_completed = true` for admin/teacher test users in DB and updated `supabase/seed.sql`
208+
209+
---

messages/en.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,26 @@
365365
"createCourse": "Create New Course",
366366
"promptTemplates": "Prompt Templates",
367367
"tour": {
368+
"welcome": {
369+
"title": "Welcome to Your Dashboard",
370+
"description": "This is your teaching hub. See an overview of your courses, student activity, and quick actions to manage your content."
371+
},
372+
"stats": {
373+
"title": "Your Teaching Stats",
374+
"description": "Track your key metrics — total courses, active students, and exam submissions at a glance."
375+
},
376+
"courses": {
377+
"title": "Your Courses",
378+
"description": "All your courses appear here with lesson counts and student enrollment. Click Edit to update content or Preview to see the student view."
379+
},
380+
"sidebarCourses": {
381+
"title": "Course Management",
382+
"description": "Access your full course list from here. View, edit, and manage all your courses in one place."
383+
},
384+
"sidebarCreate": {
385+
"title": "Create a New Course",
386+
"description": "Click here to start building a new course. Add a title, description, and start adding lessons right away."
387+
},
368388
"header": {
369389
"title": "Course Overview",
370390
"description": "This is your course management hub. You can see the course title, status badge, and enrolled student count at a glance."

messages/es.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,26 @@
365365
"createCourse": "Crear Nuevo Curso",
366366
"promptTemplates": "Plantillas de Prompt",
367367
"tour": {
368+
"welcome": {
369+
"title": "Bienvenido a tu Panel",
370+
"description": "Este es tu centro de enseñanza. Ve un resumen de tus cursos, actividad de estudiantes y acciones rápidas para gestionar tu contenido."
371+
},
372+
"stats": {
373+
"title": "Tus Estadísticas de Enseñanza",
374+
"description": "Monitorea tus métricas clave — total de cursos, estudiantes activos y entregas de exámenes de un vistazo."
375+
},
376+
"courses": {
377+
"title": "Tus Cursos",
378+
"description": "Todos tus cursos aparecen aquí con conteo de lecciones e inscripciones. Haz clic en Editar para actualizar el contenido o Vista Previa para ver la vista del estudiante."
379+
},
380+
"sidebarCourses": {
381+
"title": "Gestión de Cursos",
382+
"description": "Accede a tu lista completa de cursos desde aquí. Ve, edita y gestiona todos tus cursos en un solo lugar."
383+
},
384+
"sidebarCreate": {
385+
"title": "Crear un Nuevo Curso",
386+
"description": "Haz clic aquí para comenzar a construir un nuevo curso. Agrega un título, descripción y comienza a añadir lecciones de inmediato."
387+
},
368388
"header": {
369389
"title": "Vista General del Curso",
370390
"description": "Este es tu centro de gestión del curso. Puedes ver el título, el estado y la cantidad de estudiantes inscritos de un vistazo."

supabase/seed.sql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,12 @@ ON CONFLICT (provider, provider_id) DO NOTHING;
172172
-- ---------------------------------------------------------------------------
173173
-- 4. PROFILES (global — no tenant_id)
174174
-- ---------------------------------------------------------------------------
175-
INSERT INTO profiles (id, full_name, username)
175+
INSERT INTO profiles (id, full_name, username, onboarding_completed)
176176
VALUES
177-
('a1000000-0000-0000-0000-000000000001', 'Test Student', 'test_student'),
178-
('a1000000-0000-0000-0000-000000000002', 'School Owner', 'school_owner'),
179-
('a1000000-0000-0000-0000-000000000003', 'Code Academy Creator', 'ca_creator'),
180-
('a1000000-0000-0000-0000-000000000004', 'Alice Student', 'alice_student')
177+
('a1000000-0000-0000-0000-000000000001', 'Test Student', 'test_student', false),
178+
('a1000000-0000-0000-0000-000000000002', 'School Owner', 'school_owner', true),
179+
('a1000000-0000-0000-0000-000000000003', 'Code Academy Creator', 'ca_creator', true),
180+
('a1000000-0000-0000-0000-000000000004', 'Alice Student', 'alice_student', false)
181181
ON CONFLICT (id) DO NOTHING;
182182

183183

0 commit comments

Comments
 (0)