- Supabase Auth — Email/Password
- JWT tokens — Short-lived access tokens + refresh tokens
- Cookie-based sessions — HTTPOnly cookies via
@supabase/ssr
// Server-side session validation (every request via middleware)
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) redirect("/login");// Admin check
if (user.user_metadata?.role !== "admin") {
redirect("/login"); // or '/admin/login'
}Roles:
student(default) — no metadata role setadmin—user_metadata.role = 'admin'
Admin account creation: Only via Supabase dashboard/SQL (no self-registration for admin).
RLS is NOT yet implemented. All tables are currently accessible to any authenticated user.
-- ─── student_profiles ──────────────────────────────────────────
ALTER TABLE student_profiles ENABLE ROW LEVEL SECURITY;
-- Students can only read/update their own profile
CREATE POLICY "student_own_profile" ON student_profiles
FOR ALL USING (user_id = auth.uid());
-- Admins can read all (use service_role key from server)
-- ─── attendance ────────────────────────────────────────────────
ALTER TABLE attendance ENABLE ROW LEVEL SECURITY;
-- Students can only see/insert their own attendance
CREATE POLICY "student_own_attendance" ON attendance
FOR ALL USING (
student_id IN (
SELECT id FROM student_profiles WHERE user_id = auth.uid()
)
);
-- ─── subjects / timetable / class_sessions ─────────────────────
-- These are read-only for students (admin writes via service_role)
CREATE POLICY "students_read_subjects" ON subjects
FOR SELECT USING (true); -- any authenticated user can read
CREATE POLICY "students_read_timetable" ON timetable
FOR SELECT USING (true);
CREATE POLICY "students_read_sessions" ON class_sessions
FOR SELECT USING (true);
-- ─── notes ─────────────────────────────────────────────────────
CREATE POLICY "student_own_notes" ON notes
FOR ALL USING (
student_id IN (
SELECT id FROM student_profiles WHERE user_id = auth.uid()
)
);Client sends: { sessionId, status, location? }
Server validates:
- Session exists and belongs to student's semester
- Time window hasn't expired
- Student is within geo-fence
- Not already marked as present
Never trust client-side validation. The validation functions in lib/engines/validation/ are called from markAttendance.ts (server action), never from client.
Time Spoofing: Server uses new Date() — client-provided time is ignored.
// validateAttendanceTiming.ts
const now = new Date(); // Server time, not client timeLocation Spoofing: Cannot fully prevent. Mitigations:
- Server logs lat/lon with each present mark
- Accuracy > 100m → soft pass but logged
- Admins can audit suspicious patterns
Session ID Manipulation: Session IDs are UUIDs. Even if guessed:
validateDuplicateMarkprevents marking twice- Sessions only valid for student's semester subjects
- Student email (via Supabase Auth)
- Attendance records with GPS coordinates
- No biometric data, no photos
- Coordinates stored only when marking
present - Only accessible to student and admin (via RLS)
- Used for audit trail, not shared externally
// Never log sensitive data
// console.error("Login error:", error) // ← Good: no user dataAll inputs validated before DB operations:
// Admin actions
if (!form.subject_id || !form.start_time || !form.end_time) {
toast.error("All fields required");
return;
}TypeScript strict mode catches most type mismatches at compile time.
Supabase client uses parameterized queries — no raw SQL in application code.
NEXT_PUBLIC_SUPABASE_URL # Safe to expose (public)
NEXT_PUBLIC_SUPABASE_ANON_KEY # Safe to expose (limited permissions)
# SUPABASE_SERVICE_ROLE_KEY # NEVER expose (bypasses RLS)Currently use anon_key (same as students).
TODO: Admin mutations should use service_role_key on server-only routes:
// For admin-only mutations
const adminSupabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!, // server-only
);| Item | Status |
|---|---|
| Auth required for all student routes | ✅ Done (middleware) |
| Auth required for all admin routes | ✅ Done (middleware) |
| Role check for admin operations | ✅ Done |
| Time validation for attendance | ✅ Done |
| Geo-fence validation | ✅ Done |
| Duplicate attendance prevention | ✅ Done |
| RLS on all tables | ❌ Not implemented |
| Service role for admin DB ops | ❌ Not implemented |
| CORS configuration | ✅ Handled by Supabase |
| Rate limiting (attendance marking) | ❌ Not implemented |
| HTTPS enforcement | ✅ Vercel/hosting layer |
| XSS prevention | ✅ React escapes by default |
| CSRF protection | ✅ Server Actions use same-origin check |
- No RLS — Any authenticated user can read all attendance data
- Admin uses anon key — Admin mutations should use service role
- No rate limiting — Could spam attendance marks
- Middleware file named wrong —
proxy.tsinstead ofmiddleware.ts