Files
CRAPP/src/lib/auth-route.server.ts
T

62 lines
2.8 KiB
TypeScript
Raw Normal View History

/**
* Controllo di accesso per le route in `src/routes/api/public/` che inviano notifiche
* a tutta la squadra (DD-024).
*
* Quelle route usano la service role e saltano la RLS: senza un controllo qui, chiunque
* conosca l'URL può far suonare i telefoni di tutti. L'id di un evento non è un segreto —
* è un timestamp in base 36 e compare negli URL che la squadra si scambia — quindi non
* può fare da credenziale.
*
2026-09-24 14:54:35 +02:00
* Le route che mandano notifiche partono da un pulsante riservato agli amministratori, e il
* sollecito presenze anche agli allenatori (DD-034): `richiediAdmin` e `richiediGestoreEventi`
* verificano il token della sessione Supabase e poi il ruolo in `user_roles`. Torna `null` quando la richiesta può
* proseguire, altrimenti la `Response` di rifiuto già pronta.
*/
/** Il token della sessione Supabase, se la richiesta ne porta uno ben formato. */
function tokenDaRichiesta(request: Request): string | null {
const intestazione = request.headers.get("authorization");
if (!intestazione?.startsWith("Bearer ")) return null;
const token = intestazione.slice("Bearer ".length).trim();
// Un JWT ha tre segmenti non vuoti: scartarlo qui evita una chiamata di rete per ogni rumore.
const segmenti = token.split(".");
return segmenti.length === 3 && segmenti.every(Boolean) ? token : null;
}
/**
2026-09-24 14:54:35 +02:00
* Lascia passare solo un utente autenticato con almeno uno dei `ruoli`. `401` se manca o non
* vale il token, `403` se il token è buono ma il ruolo no.
*/
2026-09-24 14:54:35 +02:00
async function richiediRuolo(
request: Request,
ruoli: Array<"admin" | "allenatore">,
rifiuto: string,
): Promise<Response | null> {
const token = tokenDaRichiesta(request);
if (!token) return new Response("Autenticazione richiesta", { status: 401 });
const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
const { data: utente, error } = await supabaseAdmin.auth.getUser(token);
if (error || !utente?.user) return new Response("Sessione non valida", { status: 401 });
// Stessa fonte di `src/lib/ruoli.ts`: i permessi stanno solo in `user_roles` (DD-011).
2026-09-24 14:54:35 +02:00
const { data: righe } = await supabaseAdmin
.from("user_roles")
.select("role")
.eq("user_id", utente.user.id)
2026-09-24 14:54:35 +02:00
.in("role", ruoli);
2026-09-24 14:54:35 +02:00
if (!righe?.length) return new Response(rifiuto, { status: 403 });
return null;
}
2026-09-24 14:54:35 +02:00
/** Solo un amministratore: promemoria palloni, messaggi, sondaggio, notifiche attive. */
export function richiediAdmin(request: Request): Promise<Response | null> {
return richiediRuolo(request, ["admin"], "Riservato agli amministratori");
}
/** Admin o allenatore: chi gestisce gli eventi può anche sollecitare le presenze (DD-034). */
export function richiediGestoreEventi(request: Request): Promise<Response | null> {
return richiediRuolo(request, ["admin", "allenatore"], "Riservato a admin e allenatori");
}