From 60db30a9600d81b5fd30147d52e557ac66956599 Mon Sep 17 00:00:00 2001 From: Lorenzo Sanesi Date: Thu, 23 Jul 2026 19:18:34 +0200 Subject: [PATCH] Initial commit --- .env.example | 27 ++++++++ db-init/init-home-db.sql | 5 ++ docker-compose.yml | 122 +++++++++++++++++++++++++++++++++++++ keycloak/README.md | 67 ++++++++++++++++++++ keycloak/realm-export.json | 64 +++++++++++++++++++ 5 files changed, 285 insertions(+) create mode 100644 .env.example create mode 100644 db-init/init-home-db.sql create mode 100644 docker-compose.yml create mode 100644 keycloak/README.md create mode 100644 keycloak/realm-export.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..dad3452 --- /dev/null +++ b/.env.example @@ -0,0 +1,27 @@ +# Configurazione per docker-compose (postgres + backend + frontend + keycloak) + +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=scouthub +POSTGRES_PORT=5432 + +ATTIVITA_BACKEND_PORT=8080 + +# Deve restare 8080: il frontend Angular ha l'URL dell'API hardcoded +# in src/environments/environment.ts (apiUrl: 'http://localhost:8080') +ATTIVITA_FRONTEND_PORT=4200 + +KEYCLOAK_DB_PASSWORD=keycloak_change_me +KEYCLOAK_ADMIN_PASSWORD=admin_change_me + +# Se in locale esegui anche scouthub-home-be fuori da Docker (npm run dev), +# il suo PORT di default (8082) è già scelto per non collidere con questo. +KEYCLOAK_PORT=8081 + +# Deve restare 8082: scouthub-home-fe ha l'URL dell'org-service hardcoded +# in src/environments/environment.ts (orgServiceApiBaseUrl: 'http://localhost:8082') +HOME_BACKEND_PORT=8082 +HOME_FRONTEND_PORT=4201 + +# Deve coincidere con il secret del client "scouthub-home-be" in keycloak/realm-export.json +KEYCLOAK_ORG_SERVICE_CLIENT_SECRET=change_me diff --git a/db-init/init-home-db.sql b/db-init/init-home-db.sql new file mode 100644 index 0000000..1b2b764 --- /dev/null +++ b/db-init/init-home-db.sql @@ -0,0 +1,5 @@ +-- scouthub-home-be usa un database separato sulla stessa istanza Postgres di "db". +-- Eseguito solo al primo avvio del container (volume dati vuoto). +SELECT 'CREATE DATABASE scouthub_home' +WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'scouthub_home') +\gexec diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ea8acae --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,122 @@ +services: + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - "${POSTGRES_PORT}:5432" + volumes: + - scouthub_db_data:/var/lib/postgresql/data + # Crea anche il DB scouthub_home usato da scouthub-home-be (solo al primo init). + - ./db-init:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 5s + timeout: 5s + retries: 10 + + attivita-backend: + build: + context: ./scouthub-attivita-be + restart: unless-stopped + environment: + PORT: ${ATTIVITA_BACKEND_PORT} + CORS_ORIGIN: http://localhost:${ATTIVITA_FRONTEND_PORT} + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public + KEYCLOAK_BASE_URL: http://keycloak:8080 + KEYCLOAK_REALM: scouthub + ports: + - "${ATTIVITA_BACKEND_PORT}:${ATTIVITA_BACKEND_PORT}" + depends_on: + db: + condition: service_healthy + keycloak: + condition: service_started + + attivita-frontend: + build: + context: ./scouthub-attivita-fe + restart: unless-stopped + ports: + - "${ATTIVITA_FRONTEND_PORT}:80" + depends_on: + - attivita-backend + + home-backend: + build: + context: ./scouthub-home-be + restart: unless-stopped + environment: + PORT: ${HOME_BACKEND_PORT} + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/scouthub_home?schema=public + FRONTEND_BASE_URL: http://localhost:${HOME_FRONTEND_PORT} + # Rete interna Docker: la validazione del JWT (JWKS) non controlla l'issuer, + # quindi non serve che coincida con l'hostname visto dal browser (localhost:8081). + KEYCLOAK_BASE_URL: http://keycloak:8080 + KEYCLOAK_REALM: scouthub + KEYCLOAK_ORG_SERVICE_CLIENT_ID: scouthub-home-be + KEYCLOAK_ORG_SERVICE_CLIENT_SECRET: ${KEYCLOAK_ORG_SERVICE_CLIENT_SECRET} + ports: + - "${HOME_BACKEND_PORT}:${HOME_BACKEND_PORT}" + depends_on: + db: + condition: service_healthy + keycloak: + condition: service_started + + home-frontend: + build: + context: ./scouthub-home-fe + restart: unless-stopped + ports: + - "${HOME_FRONTEND_PORT}:80" + depends_on: + - home-backend + + keycloak-db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_DB: keycloak + POSTGRES_USER: keycloak + POSTGRES_PASSWORD: ${KEYCLOAK_DB_PASSWORD} + volumes: + - scouthub_keycloak_db_data:/var/lib/postgresql/data + + keycloak: + image: quay.io/keycloak/keycloak:26.7 + restart: unless-stopped + # --import-realm importa keycloak/realm-export.json solo se il realm non esiste già + # NOTA: dopo l'import, assegnare al service account di scouthub-home-be i client role + # del client 'realm-management': manage-organizations, manage-users, manage-groups, + # view-users (permessi minimi, non manage-realm). Con Fine-Grained Admin Permissions + # (26.7+, feature admin-fine-grained-authz:v1) si puo' poi restringere ulteriormente + # per singola Organization. + command: start-dev --import-realm + environment: + KC_DB: postgres + KC_DB_URL: jdbc:postgresql://keycloak-db:5432/keycloak + KC_DB_USERNAME: keycloak + KC_DB_PASSWORD: ${KEYCLOAK_DB_PASSWORD} + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} + # admin-fine-grained-authz:v1 serve per i permessi granulari sulle Organization (26.7+). + # Verificare con `kc.sh show-config` che i flag richiesti siano invariati nella versione + # effettivamente usata: i nomi dei feature flag cambiano tra minor version di Keycloak. + KC_FEATURES: organization,admin-fine-grained-authz:v1 + KC_HOSTNAME: localhost + KC_HOSTNAME_PORT: "${KEYCLOAK_PORT}" + KC_HTTP_ENABLED: "true" + ports: + - "${KEYCLOAK_PORT}:8080" + volumes: + - ./keycloak/realm-export.json:/opt/keycloak/data/import/realm-export.json:ro + depends_on: + - keycloak-db + +volumes: + scouthub_db_data: + scouthub_keycloak_db_data: diff --git a/keycloak/README.md b/keycloak/README.md new file mode 100644 index 0000000..01b4f69 --- /dev/null +++ b/keycloak/README.md @@ -0,0 +1,67 @@ +# keycloak/realm-export.json + +Realm `scouthub` con feature Organizations abilitata, importato automaticamente +all'avvio di Keycloak (`--import-realm` nel `docker-compose.yml` alla radice). + +Basato su `keycloak/realm.json` (bozza di riferimento), con l'aggiunta del +ruolo `admin-centrale` richiesto da `POST /gruppi` in `scouthub-home-be` +(placeholder temporaneo, vedi il TODO in `src/routes/gruppi.routes.ts`). + +Contiene già: + +- i realm role usati dal backend: `admin-centrale`, `capo-gruppo`, + `capo-unita`, `censito`, più il placeholder `manage-organizations` +- il client scope `organization` (aggiunge id/attributi della Organization + nei token OIDC) +- il client confidential `scouthub-home-be` (service account abilitato, + usato dal backend per il flow client-credentials verso le Admin REST API — + il suo `clientId`/`secret` devono corrispondere a + `KEYCLOAK_ORG_SERVICE_CLIENT_ID`/`_SECRET` in `scouthub-home-be/.env.example`) +- il client pubblico `scouthub-frontend` (login utente, redirect su + `http://localhost:4200/*`) + +Passi manuali ancora da fare dopo l'import (non automatizzabili in un realm +export senza conoscere gli id generati a runtime): + +- assegnare al service account di `scouthub-home-be` i client role del + client `realm-management`: `manage-organizations`, `manage-users`, + `manage-groups`, `view-users` (permessi minimi, non `manage-realm`). Con le + Fine-Grained Admin Permissions (26.7+, `admin-fine-grained-authz:v1`, già + abilitata via `KC_FEATURES`) si può poi restringere ulteriormente per + singola Organization +- eventuali mapper aggiuntivi sui client per garantire che `email`, + `realm_access.roles` e `organization` compaiano nel token come atteso da + `src/middleware/authenticate.ts` in `scouthub-home-be` (i default di + Keycloak per organization scope e realm roles di solito bastano, ma vanno + verificati contro la versione effettivamente in uso) + +I passaggi da fare sono: +1. Apri http://localhost:8081/admin e fai login (admin / admin, realm master). +2. In alto a sinistra, cambia realm da master a scouthub. +3. Menu laterale → Clients → clicca su scouthub-home-be. +4. Vai sul tab "Service account roles" (visibile perché il client ha "Service accounts enabled"). +5. Clicca "Assign role". +6. Nella finestra che si apre, in alto c'è un filtro impostato su "Filter by realm roles" — cambialo in "Filter by clients". +7. Cerca/seleziona il client realm-management. +8. Spunta questi ruoli — attenzione: ho già controllato via API l'elenco reale dei client role disponibili su realm-management in questa istanza Keycloak 26.7, e manage-groups non esiste come ruolo separato in questa versione (probabilmente + la gestione gruppi è coperta da manage-users). Quindi vedrai solo questi 3, spuntali tutti: + - manage-organizations + - manage-users + - view-users +9. Clicca Assign. +10. Dopo fatto questo bisogna fare un user admin-centrale + +I passaggi da fare per admin-centrale: +1. Apri http://localhost:8081/admin, login admin/admin, cambia realm in scouthub. +2. Menu laterale → Users → clicca su movioletto@yahoo.it. +3. Vai sul tab "Role mapping". +4. Clicca "Assign role". +5. Assicurati che il filtro sia su "Filter by realm roles" (dovrebbe esserlo di default per gli utenti normali). +6. Cerca e seleziona admin-centrale. +7. Clicca Assign. + +⚠️ Il secret del client `scouthub-home-be` (`CAMBIA-QUESTO-SECRET-IN-UN-VAULT`) +è un placeholder di sviluppo committato in chiaro, coerente con le altre +credenziali dev del progetto (vedi `.env` alla radice): da sostituire con un +segreto reale gestito a parte prima di qualunque uso condiviso o di +produzione. diff --git a/keycloak/realm-export.json b/keycloak/realm-export.json new file mode 100644 index 0000000..aa49e1c --- /dev/null +++ b/keycloak/realm-export.json @@ -0,0 +1,64 @@ +{ + "realm": "scouthub", + "enabled": true, + "organizationsEnabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "registrationEmailAsUsername": true, + "loginWithEmailAllowed": true, + "resetPasswordAllowed": true, + "verifyEmail": false, + + "roles": { + "realm": [ + { "name": "admin-centrale", "description": "Placeholder temporaneo: crea nuovi gruppi scout (Organization). Vedi TODO in src/routes/gruppi.routes.ts" }, + { "name": "capo-gruppo", "description": "Gestisce il proprio gruppo scout (Organization): membri, inviti, ruoli" }, + { "name": "capo-unita", "description": "Gestisce attivita' e materiali della propria branca" }, + { "name": "censito", "description": "Accesso in sola consultazione" }, + { "name": "manage-organizations", "description": "Placeholder locale: il ruolo reale e' il client role manage-organizations su realm-management" } + ] + }, + + "clients": [ + { + "clientId": "scouthub-home-be", + "name": "Servizio di gestione gruppi scout (org-service)", + "enabled": true, + "protocol": "openid-connect", + "publicClient": false, + "standardFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "CAMBIA-QUESTO-SECRET-IN-UN-VAULT", + "attributes": { + "note": "Usato solo server-to-server dal backend org-service verso le Admin REST API. Non esporre mai il secret al frontend." + } + }, + { + "clientId": "scouthub-frontend", + "name": "Frontend Angular Scouthub", + "enabled": true, + "protocol": "openid-connect", + "publicClient": true, + "standardFlowEnabled": true, + "directAccessGrantsEnabled": false, + "redirectUris": [ + "http://localhost:4200/*", + "http://localhost:4201/*" + ], + "webOrigins": [ + "http://localhost:4200", + "http://localhost:4201" + ], + "defaultClientScopes": [ + "openid", + "basic", + "roles", + "profile", + "email", + "organization" + ] + } + ] +}