| -- Fix for Insecure Public Verification Policy | |
| -- This migration replaces the broad 'is_completed = true' policy with a secure RPC-based lookup. | |
| -- 1. Remove the insecure anon SELECT policy | |
| DROP POLICY "Public verification of certificates" ON public.user_achievements; | |
| -- 2. Implement secure RPC function for certificate verification | |
| -- This function is SECURITY DEFINER to bypass RLS and perform the specific lookup. | |
| CREATE OR REPLACE FUNCTION public.verify_certificate_token(token UUID, slug TEXT) | |
| RETURNS TABLE ( | |
| user_id UUID, | |
| user_display_name TEXT, | |
| achievement_display_name TEXT, | |
| completed_at TIMESTAMPTZ | |
| ) | |
| LANGUAGE plpgsql | |
| SECURITY DEFINER | |
| AS $$ | |
| BEGIN | |
| RETURN QUERY | |
| SELECT | |
| ua.user_id, | |
| p.display_name as user_display_name, | |
| ad.display_name as achievement_display_name, | |
| ua.completed_at | |
| FROM public.user_achievements ua | |
| JOIN public.achievement_definitions ad ON ua.achievement_id = ad.id | |
| LEFT JOIN public.profiles p ON ua.user_id = p.id | |
| WHERE ua.verification_token = token | |
| AND ad.slug = slug | |
| AND ua.is_completed = true; | |
| END; | |
| $$; | |
| -- 3. Add restrictive RLS policy to prevent direct anon access | |
| -- This ensures that anon users cannot list achievements via SELECT * FROM user_achievements. | |
| CREATE POLICY "Anon has no direct access to user_achievements" | |
| ON public.user_achievements FOR SELECT | |
| TO anon | |
| USING (false); | |