From faa8d385925b0791963d26e8f4a38e21dd84919a Mon Sep 17 00:00:00 2001 From: zoubeir23 Date: Sun, 2 Aug 2026 04:54:54 +0000 Subject: [PATCH 1/2] 067-supprime-la-policy-de-lecture-des-signatures-par-clinique --- .../013_signatures_lecture_restreinte.sql | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 supabase/migrations/013_signatures_lecture_restreinte.sql 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; From 69d15e11fafa2be3ac6f72d03919fc3fb7787630 Mon Sep 17 00:00:00 2001 From: zoubeir23 Date: Sun, 2 Aug 2026 04:54:54 +0000 Subject: [PATCH 2/2] 067-lit-la-signature-du-validateur-avec-le-client-d-administration --- actions/doctor-signature.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) 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)