# 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.