Files
scouthub/scouthub-home-be
2026-07-25 18:40:29 +02:00
..
2026-07-25 12:38:44 +02:00
2026-07-25 18:40:29 +02:00
2026-07-25 12:09:12 +02:00
2026-07-25 12:09:12 +02:00
2026-07-23 19:36:55 +02:00
2026-07-25 12:37:29 +02:00
2026-07-23 19:36:55 +02:00
2026-07-23 19:36:55 +02:00
2026-07-23 19:36:55 +02:00
2026-07-25 12:09:12 +02:00
2026-07-23 19:36:55 +02:00
2026-07-23 19:36:55 +02:00

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 del docker-compose.yml alla radice del progetto)
  • DATABASE_URL — connection string PostgreSQL per Prisma
  • FRONTEND_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 Keycloak
  • KEYCLOAK_REALM — realm Keycloak usato da Scouthub
  • KEYCLOAK_ORG_SERVICE_CLIENT_ID — client id del service account per la gestione organizzazioni
  • KEYCLOAK_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, vedi src/routes/gruppi.routes.ts)
  • Body: { nome: string, regione?: string, ruoliDefault?: string[] } (ruoliDefault di default ["Capi", "Aiuto capi", "Censiti"])
  • Risposta 201: { orgId, gruppiCreati: string[] }
  • 409 se 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.ts logga con console.error esattamente 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 + ruolo capo-gruppo, e che req.auth.organizationId coincida 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 stato pending e scadenza a 7 giorni, e per ora si limita a loggare in console il link {FRONTEND_BASE_URL}/inviti/{token} (nessun servizio email reale). Risposta 201: { invitoId, scadenza }.
  • GET /inviti/:token — pubblico. Risponde 404 se il token non esiste, altrimenti { email, nomeGruppo, ruolo, valido } (valido: false se 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 (403 altrimenti — impedisce che un altro utente autenticato accetti l'invito di qualcun altro). Poi chiama in sequenza addMemberToOrganizationassignUserToGroupassignRealmRoleToUser e marca l'invito come accettato. Risposta 200: { organizationId, ruolo }. ⚠️ assignUserToGroup riceve invito.ruolo (un nome, es. "Capi") al posto di un vero groupId di Keycloak: createOrganizationGroup non persiste ancora una mappa ruolo → groupId, quindi questa risoluzione è solo un placeholder (vedi TODO in src/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 — Risposta 200 con un array di { userId, email, ruolo, gruppoInterno } (entrambi null se non risolvibili). Per ciascun membro restituito da listOrganizationMembers interroga getUserGroupsInOrganization e getUserRealmRoles, prendendo il primo risultato di ciascuna (il modello attuale assume un solo gruppo/ruolo scout per membro, coerente con POST /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 (removeUserFromGroup
    • assignUserToGroup, removeRealmRoleFromUser + assignRealmRoleToUser). Risposta 200: { userId, ruolo }. 502 se una delle chiamate Keycloak fallisce (loggato con console.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. Risposta 204.

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 popola req.auth con { userId, email, organizationId, roles } (ruoli realm + ruoli dell'organizzazione attiva). Risponde 401 se il token manca o non è valido.
  • src/middleware/requireRole.tsrequireRole(...ruoli) verifica che req.auth.roles contenga almeno uno dei ruoli richiesti, altrimenti 403. Va usato dopo authenticate nella 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 un TODO (throw new Error('Not implemented')) — vanno implementate prima che gli endpoint funzionino contro un Keycloak reale (nei test sono sempre mockate). findUserByEmail e createUser restano firme tipizzate non ancora usate da nessun endpoint.