FemeSalud enforces a role-based access model backed by Supabase Row Level Security (RLS). Every authenticated user is assigned at least one role —Documentation Index
Fetch the complete documentation index at: https://mintlify.com/KevxxAlva/femesalud-zen-flow/llms.txt
Use this file to discover all available pages before exploring further.
admin or doctor — stored in the user_roles table. RLS policies then restrict what each role can read or write at the database level, so access boundaries are enforced on the server, not just in the UI.
The Two Roles
Theapp_role PostgreSQL enum defines exactly two values: admin and doctor. A single user can hold both roles simultaneously — rows are simply added to or removed from user_roles.
admin
Full read and write access to all patients, all appointments, all clinical notes, and all reports across the entire practice. Admins can also manage users, create new accounts, toggle roles, and edit clinic settings. Only the Datos de la Clínica tab in Configuración is visible to admins.
doctor
Scoped access enforced by RLS policies. Doctors see and modify only the patients and appointments assigned to them. They can update their own profile and view their own schedule, but they cannot access clinic-wide settings or other doctors’ patient records.
RLS policies must be enabled in Supabase for role separation to take effect. Without active policies, both roles would have unrestricted access to all rows.
Role Assignment
Roles live in theuser_roles table with columns user_id (references auth.users) and role (app_role enum). A user can hold multiple roles by having multiple rows.
The useToggleRole() mutation handles both granting and revoking roles:
- Enable (
enable: true): callssupabase.from("user_roles").insert({ user_id, role }). Duplicate inserts are silently ignored. - Disable (
enable: false): callssupabase.from("user_roles").delete()matchinguser_idandrole.
profiles_with_roles, doctors, and user_roles are all invalidated so every subscriber refreshes immediately.
How Authentication Works
Client Side
Three hooks insrc/hooks/useAuth.ts cover the full auth lifecycle:
useAuthSession() subscribes to supabase.auth.onAuthStateChange, so session changes (login, logout, token refresh) propagate reactively across the entire component tree. useRoles() is a TanStack Query query that runs only when a user is present (enabled: !!user), querying the user_roles table scoped to the current user.id. useIsAdmin() derives a simple boolean from the roles array.
Server Side
TherequireSupabaseAuth middleware in src/integrations/supabase/auth-middleware.ts secures TanStack Start server functions. For each incoming request it:
Reads the Authorization header
Expects a
Bearer <token> header. Throws Unauthorized if the header is missing or malformed.Creates a scoped Supabase client
Instantiates a server-side
createClient<Database> with persistSession: false and autoRefreshToken: false, forwarding the Bearer token in every downstream request.Validates the token and extracts claims
Calls
supabase.auth.getClaims(token). Throws Unauthorized: Invalid token if the JWT is expired or tampered with.Route Guard
The_authenticated/route.tsx layout route calls supabase.auth.getSession() in its beforeLoad hook. If no session is found the router throws a redirect to /auth, preventing unauthenticated users from mounting any protected page. The Doctores page adds a second layer: it also checks for an admin role row in user_roles, redirecting non-admins to / if they somehow reach the URL directly.
Database Helper Functions
Two PostgreSQL functions are available for use inside RLS policies or SQL queries:_user_id uuid argument, making them safe to call from within RLS USING expressions without risk of row-level privilege escalation.
Adding the First Admin
New Supabase projects have no admin users by default. Bootstrap the first admin by inserting a row directly in the Supabase SQL Editor:<your-auth-user-id> with the UUID from Supabase → Authentication → Users. After the insert, the user will see the Doctores page and the Datos de la Clínica tab on their next page load (or after a hard refresh).
Doctors Management Page
The Doctores page (/doctores) is restricted to admins and is the primary interface for user management. From this page an admin can:
- View all registered users with their current roles displayed as badges
- Create new users via a dialog form that collects full name, email, password, specialty, and initial role
- Toggle the admin role on any user (except themselves) — promotes or demotes with a single click
- Toggle the doctor role on any user — assigns or removes clinical access
- Delete users permanently via a confirmation dialog
useAllProfilesWithRoles(), which joins profiles with user_roles client-side and returns a ProfileWithRoles[] array. User creation and deletion are handled by adminCreateUser and adminDeleteUser server functions protected by requireSupabaseAuth.