-- Phase 1: Auth0 Migration - Add auth0_sub column and update RLS policies -- This migration prepares the database to support Auth0 authentication alongside Supabase auth -- Add auth0_sub column to profiles table ALTER TABLE public.profiles ADD COLUMN IF NOT EXISTS auth0_sub TEXT UNIQUE; -- Create index for faster auth0_sub lookups CREATE INDEX IF NOT EXISTS idx_profiles_auth0_sub ON public.profiles(auth0_sub); -- Add comment explaining the column COMMENT ON COLUMN public.profiles.auth0_sub IS 'Auth0 user identifier (sub claim from JWT). Used for Auth0 authentication integration.'; -- Update RLS policies to support both Supabase and Auth0 authentication -- We'll keep existing policies and add Auth0 support -- Create helper function to get current user ID (works with both Supabase and Auth0) CREATE OR REPLACE FUNCTION public.get_current_user_id() RETURNS uuid LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE v_user_id uuid; v_auth0_sub text; BEGIN -- Try Supabase auth first v_user_id := auth.uid(); IF v_user_id IS NOT NULL THEN RETURN v_user_id; END IF; -- Try Auth0 sub from JWT v_auth0_sub := current_setting('request.jwt.claims', true)::json->>'sub'; IF v_auth0_sub IS NOT NULL THEN -- Look up user_id by auth0_sub SELECT user_id INTO v_user_id FROM public.profiles WHERE auth0_sub = v_auth0_sub; RETURN v_user_id; END IF; -- No authenticated user found RETURN NULL; END; $$; -- Add audit log column for Auth0 events ALTER TABLE public.admin_audit_log ADD COLUMN IF NOT EXISTS auth0_event_type TEXT; COMMENT ON COLUMN public.admin_audit_log.auth0_event_type IS 'Type of Auth0 event that triggered this audit log entry (e.g., mfa_enrollment, login, role_change)'; -- Create table for tracking Auth0 sync status CREATE TABLE IF NOT EXISTS public.auth0_sync_log ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), auth0_sub text NOT NULL, user_id uuid REFERENCES public.profiles(user_id), sync_type text NOT NULL, -- 'user_created', 'profile_updated', 'mfa_enrolled', etc. sync_status text NOT NULL DEFAULT 'pending', -- 'pending', 'completed', 'failed' error_message text, metadata jsonb DEFAULT '{}'::jsonb, created_at timestamp with time zone NOT NULL DEFAULT now(), completed_at timestamp with time zone ); -- Create index on auth0_sync_log CREATE INDEX IF NOT EXISTS idx_auth0_sync_log_auth0_sub ON public.auth0_sync_log(auth0_sub); CREATE INDEX IF NOT EXISTS idx_auth0_sync_log_user_id ON public.auth0_sync_log(user_id); CREATE INDEX IF NOT EXISTS idx_auth0_sync_log_status ON public.auth0_sync_log(sync_status); -- Enable RLS on auth0_sync_log ALTER TABLE public.auth0_sync_log ENABLE ROW LEVEL SECURITY; -- RLS policies for auth0_sync_log CREATE POLICY "Moderators can view all sync logs" ON public.auth0_sync_log FOR SELECT TO authenticated USING (is_moderator(auth.uid())); CREATE POLICY "System can insert sync logs" ON public.auth0_sync_log FOR INSERT TO authenticated WITH CHECK (true); CREATE POLICY "Users can view their own sync logs" ON public.auth0_sync_log FOR SELECT TO authenticated USING (user_id = auth.uid()); -- Create function to extract Auth0 sub from JWT CREATE OR REPLACE FUNCTION public.get_auth0_sub_from_jwt() RETURNS text LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ BEGIN RETURN current_setting('request.jwt.claims', true)::json->>'sub'; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$; -- Create function to check if user is authenticated via Auth0 CREATE OR REPLACE FUNCTION public.is_auth0_user() RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE v_iss text; BEGIN v_iss := current_setting('request.jwt.claims', true)::json->>'iss'; -- Auth0 issuer format: https://.auth0.com/ or https://..auth0.com/ RETURN v_iss LIKE '%auth0.com%'; EXCEPTION WHEN OTHERS THEN RETURN false; END; $$; -- Create function to check Auth0 MFA status from JWT claims CREATE OR REPLACE FUNCTION public.has_auth0_mfa() RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE v_amr jsonb; BEGIN -- Check if 'mfa' is in the amr (Authentication Methods Reference) array v_amr := current_setting('request.jwt.claims', true)::json->'amr'; RETURN v_amr ? 'mfa'; EXCEPTION WHEN OTHERS THEN RETURN false; END; $$; COMMENT ON FUNCTION public.get_current_user_id IS 'Returns the current user ID, supporting both Supabase auth.uid() and Auth0 sub claim lookup'; COMMENT ON FUNCTION public.get_auth0_sub_from_jwt IS 'Extracts the Auth0 sub claim from the JWT token'; COMMENT ON FUNCTION public.is_auth0_user IS 'Checks if the current user is authenticated via Auth0'; COMMENT ON FUNCTION public.has_auth0_mfa IS 'Checks if the current Auth0 user has completed MFA (checks amr claim for mfa)';