2026-09-06 12:05:39 +02:00
|
|
|
/**
|
|
|
|
|
* 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ò
|
2026-09-06 12:15:39 +02:00
|
|
|
* proseguire, altrimenti la `Response` di rifiuto già pronta.
|
2026-09-06 12:05:39 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/** 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();
|
2026-09-06 19:03:18 +02:00
|
|
|
// 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-06 12:05:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
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-06 12:05:39 +02:00
|
|
|
*/
|
2026-09-24 14:54:35 +02:00
|
|
|
async function richiediRuolo(
|
|
|
|
|
request: Request,
|
|
|
|
|
ruoli: Array<"admin" | "allenatore">,
|
|
|
|
|
rifiuto: string,
|
|
|
|
|
): Promise<Response | null> {
|
2026-09-06 12:05:39 +02:00
|
|
|
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
|
2026-09-06 12:05:39 +02:00
|
|
|
.from("user_roles")
|
|
|
|
|
.select("role")
|
|
|
|
|
.eq("user_id", utente.user.id)
|
2026-09-24 14:54:35 +02:00
|
|
|
.in("role", ruoli);
|
2026-09-06 12:05:39 +02:00
|
|
|
|
2026-09-24 14:54:35 +02:00
|
|
|
if (!righe?.length) return new Response(rifiuto, { status: 403 });
|
2026-09-06 12:05:39 +02:00
|
|
|
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");
|
|
|
|
|
}
|