Auth Flow
Three-layer auth architecture: edge middleware, server-side helper, client-side context. Supabase Auth with SSR cookies, role-based access, blocked-user enforcement, and debug mode bypass.
Auth is implemented in three layers that each serve a different purpose: the edge middleware guards routes, the server helper provides user data to pages and API routes, and the client context drives the UI.
Debug mode
When NEXT_PUBLIC_DEBUG_MODE=true, all three layers are bypassed. The middleware allows every route, auth() returns a mock admin user, and the client context shows a fully authenticated session. This is the development escape hatch.
Layer 1: Edge Middleware
middleware.ts runs on every request (except static assets). It is the first line of defense:
- If
DEBUG_MODEis set, every request is allowed immediately. - If
NEXT_PUBLIC_SUPABASE_URLorNEXT_PUBLIC_SUPABASE_ANON_KEYis missing, only public routes are allowed. - Otherwise, it creates a Supabase SSR client and calls
getUser(). - If the user is authenticated but their profile status is
blocked, they are redirected to/auth/login?error=blocked. - If the route is not in the public list and the user is not authenticated, they are redirected to
/auth/login?error=unauthenticated. - If the route is an auth page (
/auth/...) and the user is already authenticated, they are redirected to/library.
The public route list covers: home, category, book detail, all auth pages, settings, library, chat, wishlist, search, help, notifications, upload-book, and checkout.
Layer 2: Server Helper
auth() in auth.ts is the canonical way for pages and API routes to get the current user:
- Creates an SSR Supabase client and calls
getUser(). - Uses the admin client (service-role key) to query
profiles(display name, status, email) anduser_roles(role) - bypassing RLS so that cookie token restrictions don't interfere. - If the profile status is
blocked, returns null. - Returns a
{ user }object withid,email,displayName,role, andstatus.
The same pattern is available through getAuthUser() and requireAuth() in lib/auth-middleware.ts, which wraps the Supabase call with the same debug-mode escape hatch.
The admin client uses SUPABASE_SERVICE_ROLE_KEY with a fallback to NEXT_PUBLIC_SUPABASE_ANON_KEY, so privileged queries work even when the service key is unset in development.
Layer 3: Client Context
use-auth.tsx provides the <AuthProvider> component and the useAuth() hook:
- On mount, calls
supabase.auth.getUser()and fetches/api/auth/mefor role/status enrichment. - Subscribes to Supabase
onAuthStateChangeto re-check on login or logout. - Exposes through context:
user,isLoading,isAuthenticated,isAdmin,isStaff,role,status,logout(), andrefresh().
Pages check isAuthenticated and isAdmin to show or hide UI elements. The admin panel uses useAdminAuth() which redirects to /library if the user is not an admin.
Registration Flow
Sign-up is a server action (actions.ts:register):
- Validates fields (min length six for password, name min length two).
- Calls
supabase.auth.signUp()withdisplay_namein user metadata. - On success, inserts a row into
profileswith the user's ID, email, and display name. - Returns
'success'or an error message string.
Authorization Model
Authorization is role-based using the user_roles table:
| Role | Access |
|---|---|
user | Authenticated pages (library, chat, wishlist, upload, settings) |
moderator | User access + comment moderation, report management |
admin | Everything, including admin panel, user management, settings |
Role checks happen at the server level using the admin client (to avoid RLS issues in server actions). The client context also exposes isAdmin and isStaff booleans for UI conditional rendering.
Blocked Users
Blocked users are enforced at two levels:
- Middleware: immediately redirects blocked users to login with a
?error=blockedparam. - Server helper:
auth()returns null for blocked users, preventing any API call from succeeding.
There is no client-side block enforcement - the middleware and server layers ensure blocked users cannot reach any authenticated route or execute any privileged operation.