-- Migration : durcissement de public.insert_public_photo() -- Réf. : relecture sécurité finale du 2026-09-16 (reviewer-securite). -- -- Problème trouvé : insert_public_photo() ne vérifiait pas que le fichier -- référencé par p_url_storage existe réellement dans Storage. N'importe qui -- connaissant un event_id actif (trivialement récupérable via -- get_public_event(slug), le slug étant public par conception, diffusé via -- QR code) pouvait appeler cette RPC directement, en boucle, sans jamais -- uploader de fichier réel — polluant la galerie admin de lignes fantômes -- (images "cassées") ou de contenu arbitraire dans nom_invite/message. -- -- Correctif : vérifie l'existence de l'objet dans storage.objects avant -- d'insérer la ligne. Ne supprime pas entièrement le risque d'abus (pas de -- rate limiting/CAPTCHA), mais un attaquant doit désormais réellement -- uploader un fichier (soumis à file_size_limit/allowed_mime_types) pour -- chaque ligne créée, ce qui relève nettement le coût de l'abus. Risque -- résiduel accepté pour la V1 (usage perso) ; à revisiter avant la V2/V3 -- commerciale si le volume d'événements/invités augmente. -- -- Appliqué et testé en conditions réelles sur kevin-lecou-hub le 2026-09-16 -- (insert sans upload préalable -> rejeté ; insert après upload réel -> -- fonctionne toujours). create or replace function public.insert_public_photo( p_event_id uuid, p_url_storage text, p_nom_invite text default null, p_message text default null ) returns uuid language plpgsql security definer set search_path = public, photobooth, storage, pg_temp as $$ declare v_photo_id uuid; begin if not public.event_accepts_uploads(p_event_id) then raise exception 'event_not_accepting_uploads' using errcode = 'P0001', detail = 'L''événement est inconnu, inactif ou expiré.'; end if; if not starts_with(p_url_storage, p_event_id::text || '/') then raise exception 'url_storage_event_mismatch' using errcode = 'P0001', detail = 'url_storage doit commencer par "/".'; end if; if not exists ( select 1 from storage.objects where bucket_id = 'event-photos' and name = p_url_storage ) then raise exception 'storage_object_not_found' using errcode = 'P0001', detail = 'Aucun fichier trouvé dans Storage pour ce chemin. Uploadez le fichier avant d''enregistrer la ligne.'; end if; insert into photobooth.photos (event_id, url_storage, nom_invite, message) values (p_event_id, p_url_storage, p_nom_invite, p_message) returning id into v_photo_id; return v_photo_id; end; $$; comment on function public.insert_public_photo(uuid, text, text, text) is 'Chemin d''écriture unique pour les invités. Revalide event_accepts_uploads(), la cohérence url_storage/event_id, ET l''existence réelle du fichier dans storage.objects avant insertion. Retourne l''id de la ligne créée.';