Files
CRAPP/docs/decisions/DD-023.md
T
davideandClaude Sonnet 5.5 8f25143aa5 Riorganizza la documentazione per l'uso con assistenti AI
- Spezza il registro delle decisioni in docs/decisions/DD-XXX.md, con indice
- Ripulisce DATABASE.md dalla storia delle migration e aggiunge la sezione Lettura
- Riduce PROJECT_STATE.md a una pagina; la procedura di allestimento passa in OPERATIONS.md
- Aggiunge OPERATIONS.md, GLOSSARIO.md, trappole note e checklist in ARCHITECTURE.md
- Sintetizza la copertura test di badge.md e documenta docs/archive
- Allinea AGENTS.md, README.md e la regola Cursor (tolto l'obbligo di develop, in contrasto con DD-019)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 08:53:38 +02:00

3.3 KiB

DD-023 — Ogni scrittura è limitata a chi la fa

Data: 6 settembre 2026
Stato: Accettata

Contesto
Le tabelle preesistenti sono nate con policy USING (true) per anon, authenticated. M4 (DD-011) ha tolto il GRANT ad anon, e la cosa è stata letta come «ora è chiuso». Non lo era: per gli autenticati non c'era rimasto nessun limite. Verificato sul database locale con un utente appena creato, senza ruolo e senza slot nella rosa: POST /rest/v1/eventi_app → 201, DELETE → 200. Qualsiasi giocatore loggato poteva svuotare il calendario della squadra o riscrivere il voto pagella di un altro, parlando direttamente con PostgREST — senza passare dall'interfaccia, che quei pulsanti glieli nasconde.

Il permesso viveva quindi solo nei componenti (useIsAdmin(), io.id), cioè nel posto che un attaccante non usa.

Decisione
Le policy rispecchiano quello che l'interfaccia già fa. Tre gruppi:

  • solo admin: eventi_app. Nell'app li scrive unicamente la rotta /eventi, che è già riservata agli amministratori.
  • solo la propria riga: risposte_presenze, cacche_partita (per giocatore_id), pagelle_voti, mvp_voti, badge_social_voti (per votante_id). L'admin resta incluso, perché DD-017 gli riconosce già il diritto di agire al posto del giocatore.
  • invariate: turni_palloni e le tre tabelle scout. Nell'interfaccia non hanno nessun gate — il turno palloni se lo passa chiunque, e chiunque può aprire lo Scout Live — quindi stringerle sarebbe una funzionalità nuova, non una messa in sicurezza.

L'identità è lo slot di giocatori_squadra collegato all'account, espresso con lo stesso EXISTS che usano le policy dei profili — la funzione mio_giocatore_id() di M2 è stata rimossa dalla migration di correzione e non si reintroduce. Regge perché io.id non è una scelta libera: dopo DD-011 e DD-018 il localStorage viene forzato sullo slot collegato all'account (benvenuto.tsx), e senza slot non si entra.

Alternative scartate

  • Lasciare tutto aperto e documentarlo → si può difendere per una squadra di venti persone che si conoscono, ma non regge il primo account che passa di mano o il primo telefono perso, e rende ogni bug indistinguibile da un dispetto.
  • Controllare i permessi nelle route server → CrAPP scrive dal client con supabase-js. Ci vorrebbe un livello API che oggi non esiste, per ottenere quello che la RLS fa da sola.
  • Stringere anche palloni e scout → cambierebbe come funziona la squadra, e nessuno l'ha chiesto.

Conseguenze

  • Un giocatore che non ha ancora collegato lo slot non scrive più niente: l'EXISTS non trova nessuna riga. È lo stesso muro di /benvenuto, ora applicato anche al database.
  • La lettura resta aperta a tutti gli autenticati, pagelle comprese: chi interroga PostgREST può ancora vedere chi ha votato cosa. L'anonimato delle pagelle è una scelta di interfaccia, non una garanzia del database, e questa migration non lo cambia.
  • I test in test/integration/permessi.test.ts diventano la definizione eseguibile di questa tabella dei permessi.

Riesame
Se l'anonimato delle pagelle deve diventare reale (servirebbe una vista aggregata e la chiusura della lettura riga per riga), o se turni e scout acquistano un gate nell'interfaccia: allora le loro policy devono seguirlo.