Ajoute la V1 fonctionnelle : upload invité, galerie admin, QR code, backend Supabase
Schéma dédié `photobooth` (jamais exposé via PostgREST, accès exclusivement via fonctions SECURITY DEFINER dans public) avec RLS, policies Storage sur bucket privé event-photos, Edge Functions admin-gallery et expire-events, et job pg_cron d'expiration J+7. Le tout déployé et testé en conditions réelles sur le projet Supabase kevin-lecou-hub avant relecture sécurité (faille d'abus sur insert_public_photo corrigée en cours de route). Côté front : page upload invité (compression + HEIC + RGPD), galerie admin avec export ZIP, générateur de QR code autonome. Workflow n8n de purge Storage J+7 fourni (à activer manuellement côté n8n).
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
// Comparaison de secrets en temps constant, pour éviter les attaques par
|
||||
// mesure de timing sur des comparaisons naïves (`a === b`) qui court-
|
||||
// circuitent dès le premier caractère différent.
|
||||
//
|
||||
// On hache d'abord les deux valeurs en SHA-256 (Web Crypto native, pas de
|
||||
// dépendance tierce) : ça a deux effets utiles ici -
|
||||
// 1. la comparaison finale porte sur deux buffers de longueur FIXE
|
||||
// (32 octets), donc la longueur du secret fourni par l'appelant ne
|
||||
// fuite pas non plus (pas de court-circuit "longueur différente" sur
|
||||
// l'entrée brute) ;
|
||||
// 2. on peut comparer octet par octet sans early-return (accumulation par
|
||||
// OR bit à bit) pour un temps d'exécution indépendant du contenu.
|
||||
|
||||
async function sha256(input: string): Promise<Uint8Array> {
|
||||
const bytes = new TextEncoder().encode(input);
|
||||
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
||||
return new Uint8Array(digest);
|
||||
}
|
||||
|
||||
function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {
|
||||
// a et b sont toujours des digests SHA-256 ici (32 octets), donc cette
|
||||
// comparaison de longueur ne dépend jamais des entrées utilisateur.
|
||||
if (a.length !== b.length) return false;
|
||||
let diff = 0;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
diff |= a[i] ^ b[i];
|
||||
}
|
||||
return diff === 0;
|
||||
}
|
||||
|
||||
/** Vrai si `provided` correspond à `expected`, en temps constant. */
|
||||
export async function timingSafeEqual(
|
||||
provided: string | null | undefined,
|
||||
expected: string | null | undefined
|
||||
): Promise<boolean> {
|
||||
if (!provided || !expected) return false;
|
||||
const [a, b] = await Promise.all([sha256(provided), sha256(expected)]);
|
||||
return constantTimeEqual(a, b);
|
||||
}
|
||||
Reference in New Issue
Block a user