- 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>
3.2 KiB
DD-024 — Le route che avvisano la squadra chiedono le credenziali
Data: 6 settembre 2026
Stato: Accettata
Contesto
Le route in src/routes/api/public/ girano con la service role e saltano la RLS: DD-023 non
le tocca. Nessuna di loro faceva un controllo di accesso — cercando authorization in quella
cartella l'unico header era lo User-Agent con cui csi.ts chiama il portale CSI. Chiunque
conoscesse l'URL poteva quindi far suonare i telefoni di tutta la squadra:
promemoria-palloni accetta perfino una POST con il corpo vuoto.
La difesa apparente delle altre due — «serve un id evento valido» — non è una difesa: l'id è
e + il timestamp in base 36 (nuovoIdEvento()), compare negli URL che la squadra si
scambia, ed è elencabile da qualsiasi utente loggato.
Il danno non è furto di dati: i testi delle notifiche li costruisce il server. È molestia e consumo della quota push. Non è però una ragione per lasciare la porta aperta.
Decisione
Le tre route che inviano notifiche chiedono le credenziali, con due controlli diversi perché
i chiamanti sono diversi (src/lib/auth-route.server.ts):
apri-sondaggioesollecita-presenze→richiediAdmin: token della sessione Supabase verificato conauth.getUser, poi ruoloadminletto dauser_roles, la stessa fonte disrc/lib/ruoli.ts(DD-011).401senza token valido,403con token ma senza ruolo. Il controllo viene prima della validazione dell'input, così la risposta non rivela nemmeno se un evento esiste.promemoria-palloni→ all'iniziorichiediSegreto, con un segreto da cron; sostituito subito dopo darichiediAdminquando la route è diventata manuale (DD-025).
csi, push-config e push-subscribe restano aperte: le chiama il browser prima del login,
dove qualsiasi segreto sarebbe pubblico. (push-messaggio esisteva per lo stesso motivo ed è
sparita con DD-026.)
Alternative scartate
- Un segreto condiviso anche per le due route dell'app → finirebbe nel bundle JavaScript, cioè pubblico.
- Il middleware
requireSupabaseAuthgià presente nel repository → ècreateMiddleware({ type: "function" }), protegge le server function di TanStack Start. In CrAPPcreateServerFnnon compare da nessuna parte: quel file non è mai stato eseguito, e non si applica comunque alle route insrc/routes/api/. - Fidarsi dell'id evento come credenziale → è un timestamp in un URL condiviso.
Conseguenze
- Nessuna variabile d'ambiente da configurare: dopo DD-025 tutte e tre le route usano lo stesso controllo sul ruolo.
- I due pulsanti dell'app mandano ora il token con
intestazioniAutenticate()(src/lib/auth.ts), letto al momento della chiamata e non da uno stato React, così non si spedisce un token scaduto. api.test.tsnon può più verificare la validazione dell'input disollecita-presenze, che ora sta dietro all'accesso: quel pezzo si è spostato intest/integration/permessi-route.test.ts, che gira sullo stack locale perché ha bisogno di utenti veri.
Riesame
Se un giorno l'app userà createServerFn, il middleware già presente diventa la strada
naturale e questi controlli vanno riletti alla sua luce.