Files
CRAPP/docs/decisions/DD-024.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.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-sondaggio e sollecita-presenze → richiediAdmin: token della sessione Supabase verificato con auth.getUser, poi ruolo admin letto da user_roles, la stessa fonte di src/lib/ruoli.ts (DD-011). 401 senza token valido, 403 con 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'inizio richiediSegreto, con un segreto da cron; sostituito subito dopo da richiediAdmin quando 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 requireSupabaseAuth già presente nel repository → è createMiddleware({ type: "function" }), protegge le server function di TanStack Start. In CrAPP createServerFn non compare da nessuna parte: quel file non è mai stato eseguito, e non si applica comunque alle route in src/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.ts non può più verificare la validazione dell'input di sollecita-presenze, che ora sta dietro all'accesso: quel pezzo si è spostato in test/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.