Initial commit

This commit is contained in:
Lorenzo Sanesi
2026-07-23 19:18:34 +02:00
commit 60db30a960
5 changed files with 285 additions and 0 deletions
+27
View File
@@ -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
+5
View File
@@ -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
+122
View File
@@ -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:
+67
View File
@@ -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.
+64
View File
@@ -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"
]
}
]
}