scouthub-home-be
Backend Node.js/TypeScript/Express per la gestione organizzativa di Scouthub
(gruppi scout, inviti), integrato con Keycloak per l'autenticazione/gestione
delle organizzazioni. Affianca scouthub-attivita-be nello stesso ecosistema.
Stato: creazione gruppi, flusso di invito e gestione membri implementati
(vedi sezione Endpoint); l'integrazione reale con le Admin REST API di
Keycloak (src/keycloak-admin/) resta invece solo abbozzata (vedi sotto).
Struttura
src/
config/ configurazione da variabili d'ambiente (dotenv)
routes/ definizione degli endpoint Express
controllers/ gestione richieste/risposte HTTP
services/ logica applicativa
keycloak-admin/ client verso le Admin REST API di Keycloak (token client-credentials + operazioni org/utenti/ruoli)
db/ client Prisma
middleware/ middleware Express (autenticazione JWT, autorizzazione per ruolo, error handling)
prisma/
schema.prisma modelli GruppoScout e Invito
Prerequisiti
- Node.js 20+
- Un'istanza PostgreSQL raggiungibile (locale o remota)
- Un realm Keycloak con un client service-account per la gestione organizzazioni
Variabili d'ambiente
Copiare .env.example in .env e valorizzare:
PORT— porta di ascolto del server (default 8082 — 8081 è già usata dal Keycloak deldocker-compose.ymlalla radice del progetto)DATABASE_URL— connection string PostgreSQL per PrismaFRONTEND_BASE_URL— URL base del frontend, usato per comporre il link di invito ({FRONTEND_BASE_URL}/inviti/{token})KEYCLOAK_BASE_URL— URL base dell'istanza KeycloakKEYCLOAK_REALM— realm Keycloak usato da ScouthubKEYCLOAK_ORG_SERVICE_CLIENT_ID— client id del service account per la gestione organizzazioniKEYCLOAK_ORG_SERVICE_CLIENT_SECRET— client secret corrispondente
Sviluppo locale
npm install
npx prisma migrate dev --name init
npm run dev
Il server si avvia sulla porta definita da PORT e risponde su GET /health
(verifica anche la connessione al database).
Build
npm run build
npm start
Test
npm test
Suite Jest, nessun database richiesto: le chiamate verso Keycloak (endpoint
token e JWKS) sono mockate con nock, e i test di integrazione mockano
interamente src/keycloak-admin e src/db/prisma.
Endpoint
POST /gruppi
Crea un nuovo gruppo scout: organizzazione Keycloak, un gruppo Keycloak per
ciascun ruolo di default, e la riga corrispondente in gruppo_scout.
- Richiede autenticazione + ruolo
admin(temporaneo, vedisrc/routes/gruppi.routes.ts) - Body:
{ nome: string, regione?: string, ruoliDefault?: string[] }(ruoliDefaultdi default["Capi", "Aiuto capi", "Censiti"]) - Risposta
201:{ orgId, gruppiCreati: string[] } 409se Keycloak segnala un nome di organizzazione duplicato (nessuna riga orfana viene creata in locale)- In caso di errore a metà sequenza (es. fallisce la creazione di un gruppo
ruolo dopo che l'organizzazione è già stata creata),
src/services/gruppi.service.tslogga conconsole.erroresattamente a quale step si è fermato e cosa è già stato creato, per permettere un retry manuale mirato invece di ripartire da zero.
Flusso di invito
POST /gruppi/:orgId/inviti— richiede autenticazione + ruolocapo-gruppo, e chereq.auth.organizationIdcoincida con:orgId(un capo gruppo non può invitare in un'altra organizzazione, 403 altrimenti). Body{ email: string, ruolo: string }. Genera un token casuale (crypto.randomBytes(32)), salva l'invito con statopendinge scadenza a 7 giorni, e per ora si limita a loggare in console il link{FRONTEND_BASE_URL}/inviti/{token}(nessun servizio email reale). Risposta201:{ invitoId, scadenza }.GET /inviti/:token— pubblico. Risponde404se il token non esiste, altrimenti{ email, nomeGruppo, ruolo, valido }(valido: falsese l'invito è scaduto o già accettato), usato dal frontend per la schermata di accettazione prima del redirect a Keycloak.POST /inviti/:token/accetta— richiede autenticazione (l'utente deve essersi già autenticato/registrato su Keycloak). Verifica in ordine: invito esistente (404), non già accettato (409), non scaduto (410), email del JWT corrispondente a quella dell'invito (403altrimenti — impedisce che un altro utente autenticato accetti l'invito di qualcun altro). Poi chiama in sequenzaaddMemberToOrganization→assignUserToGroup→assignRealmRoleToUsere marca l'invito comeaccettato. Risposta200:{ organizationId, ruolo }. ⚠️assignUserToGroupriceveinvito.ruolo(un nome, es."Capi") al posto di un verogroupIddi Keycloak:createOrganizationGroupnon persiste ancora una mappa ruolo → groupId, quindi questa risoluzione è solo un placeholder (vediTODOinsrc/services/inviti.service.ts).
Gestione membri
Tutti e tre gli endpoint richiedono autenticazione + ruolo capo-gruppo, e
che req.auth.organizationId coincida con :orgId (403 altrimenti — un capo
gruppo non può gestire membri di un'altra organizzazione).
GET /gruppi/:orgId/membri— Risposta200con un array di{ userId, email, ruolo, gruppoInterno }(entrambinullse non risolvibili). Per ciascun membro restituito dalistOrganizationMembersinterrogagetUserGroupsInOrganizationegetUserRealmRoles, prendendo il primo risultato di ciascuna (il modello attuale assume un solo gruppo/ruolo scout per membro, coerente conPOST /gruppi/:orgId/inviti).PUT /gruppi/:orgId/membri/:userId/ruolo— Body{ ruolo: string }. Rimuove tutti i gruppi/ruoli realm correnti del membro all'interno dell'organizzazione e assegna il nuovo gruppo/ruolo (removeUserFromGroupassignUserToGroup,removeRealmRoleFromUser+assignRealmRoleToUser). Risposta200:{ userId, ruolo }.502se una delle chiamate Keycloak fallisce (loggato conconsole.error, stato potenzialmente parziale su Keycloak da verificare manualmente).
DELETE /gruppi/:orgId/membri/:userId— Rimuove solo la membership dell'Organization (removeMemberFromOrganization), non elimina l'utente da Keycloak. Risposta204.
Autenticazione e autorizzazione
src/middleware/authenticate.ts— estrae il Bearer token, ne valida la firma contro le chiavi pubbliche JWKS di Keycloak (jsonwebtoken+jwks-rsa, con caching delle chiavi) e popolareq.authcon{ userId, email, organizationId, roles }(ruoli realm + ruoli dell'organizzazione attiva). Risponde 401 se il token manca o non è valido.src/middleware/requireRole.ts—requireRole(...ruoli)verifica chereq.auth.rolescontenga almeno uno dei ruoli richiesti, altrimenti 403. Va usato dopoauthenticatenella catena dei middleware.src/keycloak-admin/— client verso le Admin REST API di Keycloak. Il recupero/rinnovo del token via client-credentials (tokenManager.ts) è già funzionante; tutte le operazioni usate dagli endpoint applicativi (createOrganization,createOrganizationGroup,addMemberToOrganization,removeMemberFromOrganization,listOrganizationMembers,assignUserToGroup,removeUserFromGroup,getUserGroupsInOrganization,assignRealmRoleToUser,removeRealmRoleFromUser,getUserRealmRoles) sono già collegate ma il loro corpo è ancora solo unTODO(throw new Error('Not implemented')) — vanno implementate prima che gli endpoint funzionino contro un Keycloak reale (nei test sono sempre mockate).findUserByEmailecreateUserrestano firme tipizzate non ancora usate da nessun endpoint.