diff --git a/actions/doctor-signature.ts b/actions/doctor-signature.ts index 6dacbb6..1f49ecc 100644 --- a/actions/doctor-signature.ts +++ b/actions/doctor-signature.ts @@ -1,6 +1,6 @@ "use server"; -import { createClient } from "@/lib/supabase/server"; +import { createClient, createAdminClient } from "@/lib/supabase/server"; import { z } from "zod"; export interface DoctorSignature { @@ -89,6 +89,12 @@ export async function deleteDoctorSignature(): Promise<{ success: boolean; error * de la clinique puisse récupérer l'image de signature d'un confrère sans * passer par un document réel. Tant que le diagnostic n'est pas validé, aucune * signature n'est renvoyée. + * + * Depuis la migration 013, plus aucune policy n'autorise un compte à lire la + * signature d'un confrère : la lecture finale passe donc par le client + * d'administration, une fois la clinique de l'appelant et le statut du + * diagnostic vérifiés ici — c'est cette fonction, et non une policy trop large, + * qui porte la décision d'autorisation. */ export async function getSignatureForValidatedDiagnostic( diagnosticId: string @@ -125,7 +131,13 @@ export async function getSignatureForValidatedDiagnostic( return null; } - const { data } = await db + // Le filtre sur clinic_id reste indispensable : le client d'administration + // ignore la RLS, c'est donc cette contrainte qui garantit qu'on ne lit pas la + // signature d'un praticien extérieur à la clinique de l'appelant. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const adminDb = (await createAdminClient()) as any; + + const { data } = await adminDb .from("doctor_signatures") .select("*") .eq("user_id", diagnostic.validated_by_user_id) diff --git a/supabase/migrations/013_signatures_lecture_restreinte.sql b/supabase/migrations/013_signatures_lecture_restreinte.sql new file mode 100644 index 0000000..e19dd3e --- /dev/null +++ b/supabase/migrations/013_signatures_lecture_restreinte.sql @@ -0,0 +1,27 @@ +-- ═══════════════════════════════════════════════════════════════════════════════ +-- 013 — Restriction de la lecture des signatures de médecins +-- (audit signature / carnet / ordonnance 2026-08-02) +-- +-- CRITICAL : la policy `doctor_signatures_select_clinic` autorisait tout compte +-- partageant la clinique à lire `signature_data_url`. Une secrétaire ou un +-- assistant pouvait donc exécuter, depuis son navigateur : +-- +-- supabase.from('doctor_signatures').select('*') +-- +-- et récupérer l'image de signature manuscrite de chaque médecin du cabinet, +-- sans ouvrir le moindre document. Or cette image est le seul élément qui +-- authentifie une ordonnance imprimée : sa diffusion permet de fabriquer un +-- document signé hors du produit, sans trace. +-- +-- La policy est supprimée. Il ne reste que `doctor_signatures_select_own` : +-- chacun lit sa propre signature, personne ne lit celle d'un confrère. +-- +-- L'affichage légitime de la signature du validateur sur un document reste +-- assuré par la Server Action `getSignatureForValidatedDiagnostic`, qui vérifie +-- la clinique de l'appelant et le statut du diagnostic avant de lire la +-- signature avec le client d'administration — même schéma que +-- `importPatientCarnet`. L'autorisation est ainsi décidée par du code relu et +-- testable, au lieu d'être déléguée à une policy trop large. +-- ═══════════════════════════════════════════════════════════════════════════════ + +DROP POLICY IF EXISTS "doctor_signatures_select_clinic" ON doctor_signatures;