diff --git a/.env.example b/.env.example index 487be40..8bb5d2a 100644 --- a/.env.example +++ b/.env.example @@ -30,13 +30,28 @@ ATTIVITA_BACKEND_PORT=8001 MAGAZZINO_BACKEND_PORT=8002 EVENTI_BACKEND_PORT=8003 -# Deve coincidere con il secret del client "scouthub-home-be" in keycloak/realm-export.json +# URL pubbliche dei servizi, cosi' come raggiungibili dal browser (e da Keycloak per i redirect +# OIDC). Di default puntano a localhost sulle porte sopra, per lo sviluppo locale. Quando lo +# stack va in produzione dietro un reverse proxy (es. Caddy) con dominio pubblico, sovrascrivere +# ognuna con l'URL reale, senza porta (es. https://home.tuodominio.it) - vedi Caddyfile.example +# in root per un setup di riferimento con un sottodominio per servizio. +KEYCLOAK_PUBLIC_URL=http://localhost:6999 +HOME_FE_PUBLIC_URL=http://localhost:7000 +ATTIVITA_FE_PUBLIC_URL=http://localhost:7001 +MAGAZZINO_FE_PUBLIC_URL=http://localhost:7002 +EVENTI_FE_PUBLIC_URL=http://localhost:7003 +HOME_BE_PUBLIC_URL=http://localhost:8000 +ATTIVITA_BE_PUBLIC_URL=http://localhost:8001 +MAGAZZINO_BE_PUBLIC_URL=http://localhost:8002 +EVENTI_BE_PUBLIC_URL=http://localhost:8003 + +# Deve coincidere con il secret del client "scouthub-home-be" in keycloak/realm-export.template.json KEYCLOAK_ORG_SERVICE_CLIENT_SECRET=change_me -# Deve coincidere con il secret del client "scouthub-magazzino-be" in keycloak/realm-export.json +# Deve coincidere con il secret del client "scouthub-magazzino-be" in keycloak/realm-export.template.json KEYCLOAK_MAGAZZINO_CLIENT_SECRET=change_me -# Deve coincidere con il secret del client "scouthub-eventi-be" in keycloak/realm-export.json +# Deve coincidere con il secret del client "scouthub-eventi-be" in keycloak/realm-export.template.json # (secret condiviso anche con scouthub-attivita-be e scouthub-magazzino-be, che lo usano per # chiamare POST /eventi/:id/risorse su scouthub-eventi-be) KEYCLOAK_EVENTI_CLIENT_SECRET=change_me diff --git a/docker-compose.yml b/docker-compose.yml index 52a86b6..f888e27 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,7 +25,7 @@ services: restart: unless-stopped environment: PORT: ${ATTIVITA_BACKEND_PORT} - CORS_ORIGIN: http://localhost:${ATTIVITA_FRONTEND_PORT} + CORS_ORIGIN: ${ATTIVITA_FE_PUBLIC_URL} DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/scouthub?schema=public KEYCLOAK_BASE_URL: http://keycloak:8080 KEYCLOAK_REALM: scouthub @@ -41,8 +41,8 @@ services: build: context: ./scouthub-attivita-fe args: - API_URL: http://localhost:${ATTIVITA_BACKEND_PORT} - KEYCLOAK_BASE_URL: http://localhost:${KEYCLOAK_PORT} + API_URL: ${ATTIVITA_BE_PUBLIC_URL} + KEYCLOAK_BASE_URL: ${KEYCLOAK_PUBLIC_URL} restart: unless-stopped ports: - "${ATTIVITA_FRONTEND_PORT}:80" @@ -56,7 +56,7 @@ services: environment: PORT: ${HOME_BACKEND_PORT} DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public - FRONTEND_BASE_URL: http://localhost:${HOME_FRONTEND_PORT} + FRONTEND_BASE_URL: ${HOME_FE_PUBLIC_URL} # 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 @@ -75,11 +75,11 @@ services: build: context: ./scouthub-home-fe args: - KEYCLOAK_BASE_URL: http://localhost:${KEYCLOAK_PORT} - ORG_SERVICE_API_BASE_URL: http://localhost:${HOME_BACKEND_PORT} - ATTIVITA_FE_BASE_URL: http://localhost:${ATTIVITA_FRONTEND_PORT} - MAGAZZINO_FE_BASE_URL: http://localhost:${MAGAZZINO_FRONTEND_PORT} - EVENTI_FE_BASE_URL: http://localhost:${EVENTI_FRONTEND_PORT} + KEYCLOAK_BASE_URL: ${KEYCLOAK_PUBLIC_URL} + ORG_SERVICE_API_BASE_URL: ${HOME_BE_PUBLIC_URL} + ATTIVITA_FE_BASE_URL: ${ATTIVITA_FE_PUBLIC_URL} + MAGAZZINO_FE_BASE_URL: ${MAGAZZINO_FE_PUBLIC_URL} + EVENTI_FE_BASE_URL: ${EVENTI_FE_PUBLIC_URL} restart: unless-stopped ports: - "${HOME_FRONTEND_PORT}:80" @@ -109,8 +109,8 @@ services: build: context: ./scouthub-magazzino-fe args: - KEYCLOAK_BASE_URL: http://localhost:${KEYCLOAK_PORT} - MAGAZZINO_API_BASE_URL: http://localhost:${MAGAZZINO_BACKEND_PORT} + KEYCLOAK_BASE_URL: ${KEYCLOAK_PUBLIC_URL} + MAGAZZINO_API_BASE_URL: ${MAGAZZINO_BE_PUBLIC_URL} restart: unless-stopped ports: - "${MAGAZZINO_FRONTEND_PORT}:80" @@ -141,8 +141,8 @@ services: build: context: ./scouthub-eventi-fe args: - KEYCLOAK_BASE_URL: http://localhost:${KEYCLOAK_PORT} - EVENTI_API_BASE_URL: http://localhost:${EVENTI_BACKEND_PORT} + KEYCLOAK_BASE_URL: ${KEYCLOAK_PUBLIC_URL} + EVENTI_API_BASE_URL: ${EVENTI_BE_PUBLIC_URL} restart: unless-stopped ports: - "${EVENTI_FRONTEND_PORT}:80" @@ -159,13 +159,27 @@ services: volumes: - scouthub_keycloak_db_data:/var/lib/postgresql/data + keycloak-realm-render: + image: alpine:3.20 + restart: "no" + entrypoint: ["sh", "/render-realm-template.sh"] + environment: + HOME_FE_URL: ${HOME_FE_PUBLIC_URL} + ATTIVITA_FE_URL: ${ATTIVITA_FE_PUBLIC_URL} + MAGAZZINO_FE_URL: ${MAGAZZINO_FE_PUBLIC_URL} + EVENTI_FE_URL: ${EVENTI_FE_PUBLIC_URL} + volumes: + - ./keycloak/realm-export.template.json:/template/realm-export.template.json:ro + - ./keycloak/render-realm-template.sh:/render-realm-template.sh:ro + - scouthub_keycloak_realm:/rendered + 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à. - # Le correzioni post-import (ruoli service account, protocol mapper "organization") non - # coperte dal file JSON sono automatizzate dal servizio one-shot "keycloak-init" sotto, - # non più manuali (vedi CLAUDE.md). + # --import-realm importa il realm-export.json generato da keycloak-realm-render (a partire + # dal template) solo se il realm non esiste già. Le correzioni post-import (ruoli service + # account, protocol mapper "organization") non coperte dal file JSON sono automatizzate dal + # servizio one-shot "keycloak-init" sotto, non più manuali (vedi CLAUDE.md). command: start-dev --import-realm --health-enabled=true environment: KC_DB: postgres @@ -178,16 +192,23 @@ services: # 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_HOSTNAME come URL completo (schema+host, es. https://auth.tuodominio.it dietro Caddy + # in produzione, http://localhost:6999 in locale): Keycloak lo usa per generare tutti i link + # (redirect, well-known, ecc.) visti dal browser. KC_PROXY_HEADERS è necessario perché + # Keycloak sta dietro un reverse proxy (Caddy) che termina il TLS e inoltra in http. + KC_HOSTNAME: ${KEYCLOAK_PUBLIC_URL} KC_HTTP_ENABLED: "true" + KC_PROXY_HEADERS: xforwarded ports: - "${KEYCLOAK_PORT}:8080" volumes: - - ./keycloak/realm-export.json:/opt/keycloak/data/import/realm-export.json:ro + - scouthub_keycloak_realm:/opt/keycloak/data/import:ro - ./keycloak/themes/scouthub:/opt/keycloak/themes/scouthub:ro depends_on: - - keycloak-db + keycloak-db: + condition: service_started + keycloak-realm-render: + condition: service_completed_successfully healthcheck: test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000 && echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && grep -q '\"status\": \"UP\"' <&3"] interval: 5s @@ -211,3 +232,4 @@ services: volumes: scouthub_db_data: scouthub_keycloak_db_data: + scouthub_keycloak_realm: diff --git a/keycloak/README.md b/keycloak/README.md index 1008be6..dafe154 100644 --- a/keycloak/README.md +++ b/keycloak/README.md @@ -1,8 +1,16 @@ -# keycloak/realm-export.json +# keycloak/realm-export.template.json Realm `scouthub` con feature Organizations abilitata, importato automaticamente all'avvio di Keycloak (`--import-realm` nel `docker-compose.yml` alla radice). +Il file è un **template**: i placeholder `__HOME_FE_URL__`/`__ATTIVITA_FE_URL__`/ +`__MAGAZZINO_FE_URL__`/`__EVENTI_FE_URL__` (redirect URI, web origins, post-logout +redirect del client `scouthub-frontend`) vengono sostituiti a runtime dal servizio +one-shot `keycloak-realm-render` con i valori delle variabili `*_FE_PUBLIC_URL` in +`.env` (localhost in locale, dominio dietro Caddy in produzione), e il risultato +(`realm-export.json`) viene scritto in un volume Docker montato da `keycloak` come +cartella di import — non esiste più un `realm-export.json` statico committato. + Basato su `keycloak/realm.json` (bozza di riferimento), con l'aggiunta del ruolo `admin` richiesto da `POST /gruppi` e `GET /gruppi` in `scouthub-home-be`: creazione/elenco diretti restano riservati ad `admin`, @@ -23,7 +31,8 @@ Contiene già: 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, condiviso da tutti i - frontend Angular del progetto, redirect su `http://localhost:7000-7003/*`) + frontend Angular del progetto, redirect sugli URL pubblici configurati in + `.env`/`*_FE_PUBLIC_URL`, `http://localhost:7000-7003/*` di default in locale) Passi manuali ancora da fare dopo l'import (non automatizzabili in un realm export senza conoscere gli id generati a runtime): diff --git a/keycloak/realm-export.json b/keycloak/realm-export.template.json similarity index 89% rename from keycloak/realm-export.json rename to keycloak/realm-export.template.json index 18af4c6..d0c080b 100644 --- a/keycloak/realm-export.json +++ b/keycloak/realm-export.template.json @@ -76,21 +76,21 @@ "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "redirectUris": [ - "http://localhost:7000/*", - "http://localhost:7001/*", - "http://localhost:7002/*", - "http://localhost:7002/silent-check-sso.html", - "http://localhost:7003/*", - "http://localhost:7003/silent-check-sso.html" + "__HOME_FE_URL__/*", + "__ATTIVITA_FE_URL__/*", + "__MAGAZZINO_FE_URL__/*", + "__MAGAZZINO_FE_URL__/silent-check-sso.html", + "__EVENTI_FE_URL__/*", + "__EVENTI_FE_URL__/silent-check-sso.html" ], "webOrigins": [ - "http://localhost:7000", - "http://localhost:7001", - "http://localhost:7002", - "http://localhost:7003" + "__HOME_FE_URL__", + "__ATTIVITA_FE_URL__", + "__MAGAZZINO_FE_URL__", + "__EVENTI_FE_URL__" ], "attributes": { - "post.logout.redirect.uris": "http://localhost:7000/*##http://localhost:7001/*##http://localhost:7002/*##http://localhost:7003/*" + "post.logout.redirect.uris": "__HOME_FE_URL__/*##__ATTIVITA_FE_URL__/*##__MAGAZZINO_FE_URL__/*##__EVENTI_FE_URL__/*" }, "defaultClientScopes": [ "openid", diff --git a/keycloak/render-realm-template.sh b/keycloak/render-realm-template.sh new file mode 100644 index 0000000..c7007c2 --- /dev/null +++ b/keycloak/render-realm-template.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# Sostituisce nel template i placeholder __XXX_FE_URL__ con gli URL pubblici reali dei +# frontend (localhost in locale, dominio dietro Caddy in produzione — vedi CLAUDE.md/.env.example), +# scrivendo il risultato nella cartella di import di Keycloak. Necessario perche' realm-export.json +# è statico e --import-realm non supporta variabili d'ambiente nei suoi campi. +set -eu + +: "${HOME_FE_URL:?variabile HOME_FE_URL mancante}" +: "${ATTIVITA_FE_URL:?variabile ATTIVITA_FE_URL mancante}" +: "${MAGAZZINO_FE_URL:?variabile MAGAZZINO_FE_URL mancante}" +: "${EVENTI_FE_URL:?variabile EVENTI_FE_URL mancante}" + +mkdir -p /rendered + +sed \ + -e "s#__HOME_FE_URL__#$HOME_FE_URL#g" \ + -e "s#__ATTIVITA_FE_URL__#$ATTIVITA_FE_URL#g" \ + -e "s#__MAGAZZINO_FE_URL__#$MAGAZZINO_FE_URL#g" \ + -e "s#__EVENTI_FE_URL__#$EVENTI_FE_URL#g" \ + /template/realm-export.template.json > /rendered/realm-export.json + +echo "[keycloak-realm-render] realm-export.json generato con:" +echo " HOME_FE_URL=$HOME_FE_URL" +echo " ATTIVITA_FE_URL=$ATTIVITA_FE_URL" +echo " MAGAZZINO_FE_URL=$MAGAZZINO_FE_URL" +echo " EVENTI_FE_URL=$EVENTI_FE_URL" diff --git a/scouthub-eventi-fe/README.md b/scouthub-eventi-fe/README.md index e26be18..66a746a 100644 --- a/scouthub-eventi-fe/README.md +++ b/scouthub-eventi-fe/README.md @@ -106,7 +106,7 @@ automaticamente da `ng serve`/`ng build --configuration development`) espongono: - `keycloakBaseUrl` — URL base dell'istanza Keycloak (default `http://localhost:8081`, coerente con `KEYCLOAK_PORT` in `.env` alla root del repo) - `keycloakRealm` — realm Keycloak (`scouthub`) -- `keycloakClientId` — client pubblico condiviso già definito in `keycloak/realm-export.json` +- `keycloakClientId` — client pubblico condiviso già definito in `keycloak/realm-export.template.json` (`scouthub-frontend`) - `eventiApiBaseUrl` — URL base di `scouthub-eventi-be` (default `http://localhost:8084`) @@ -116,12 +116,14 @@ automaticamente da `ng serve`/`ng build --configuration development`) espongono: ```bash docker compose up -d keycloak-db keycloak ``` - Il realm `scouthub` viene importato automaticamente da `keycloak/realm-export.json` al primo - avvio (`--import-realm`). -2. Il client pubblico `scouthub-frontend` ha `redirectUris`/`webOrigins` che includono anche - `http://localhost:4204` (porta di default di questo progetto, vedi sotto) — se si cambia porta, - aggiornare `keycloak/realm-export.json` di conseguenza e reimportare il realm (o aggiornarlo - da console admin Keycloak). + Il realm `scouthub` viene importato automaticamente al primo avvio (`--import-realm`) a + partire dal template `keycloak/realm-export.template.json`, renderizzato con gli URL + pubblici correnti (`*_FE_PUBLIC_URL` in `.env`) dal servizio one-shot + `keycloak-realm-render` (vedi `keycloak/README.md`). +2. Il client pubblico `scouthub-frontend` ha `redirectUris`/`webOrigins` generati dagli URL + pubblici configurati in `.env` — se si cambia porta/dominio, basta aggiornare `.env` e + rifare `docker compose up`; per un realm già importato in precedenza va comunque + aggiornato anche da console admin Keycloak (l'import non sovrascrive un realm esistente). 3. `provideKeycloakAngular()` (`src/app/core/auth/keycloak.provider.ts`) inizializza il client con `onLoad: 'check-sso'`: all'avvio l'app verifica in un iframe nascosto se esiste già una sessione Keycloak, senza forzare subito un redirect di login. diff --git a/scouthub-home-fe/README.md b/scouthub-home-fe/README.md index 9d233ae..082262d 100644 --- a/scouthub-home-fe/README.md +++ b/scouthub-home-fe/README.md @@ -87,7 +87,7 @@ espongono: - `keycloakBaseUrl` — URL base dell'istanza Keycloak (default `http://localhost:8081`, coerente con `KEYCLOAK_PORT` in `.env` alla root del repo) - `keycloakRealm` — realm Keycloak (`scouthub`) -- `keycloakClientId` — client pubblico già definito in `keycloak/realm-export.json` +- `keycloakClientId` — client pubblico già definito in `keycloak/realm-export.template.json` (`scouthub-frontend`) - `orgServiceApiBaseUrl` — URL base di `scouthub-home-be` (default `http://localhost:8082`) @@ -97,14 +97,16 @@ espongono: ```bash docker compose up -d keycloak-db keycloak ``` - Il realm `scouthub` viene importato automaticamente da `keycloak/realm-export.json` - al primo avvio (`--import-realm`). -2. Il client pubblico `scouthub-frontend` ha `redirectUris`/`webOrigins` che includono - `http://localhost:4200` (usato da `scouthub-attivita-fe`), `http://localhost:4201` - (porta di default di questo progetto, vedi sotto) e le porte di - `scouthub-magazzino-fe`/`scouthub-eventi-fe` — il client è ora condiviso da tutti - e quattro i frontend. Se si cambia porta, aggiornare `keycloak/realm-export.json` - di conseguenza e reimportare il realm (o aggiornarlo da console admin Keycloak). + Il realm `scouthub` viene importato automaticamente al primo avvio (`--import-realm`) + a partire dal template `keycloak/realm-export.template.json`, renderizzato con gli URL + pubblici correnti (`*_FE_PUBLIC_URL` in `.env`) dal servizio one-shot + `keycloak-realm-render` (vedi `keycloak/README.md`). +2. Il client pubblico `scouthub-frontend` ha `redirectUris`/`webOrigins` generati dagli + URL pubblici configurati in `.env` per tutti e quattro i frontend — il client è + condiviso. Se si cambia porta/dominio, basta aggiornare `.env` e rifare + `docker compose up`; per un cambiamento su un realm già importato in precedenza va + comunque aggiornato anche da console admin Keycloak (l'import non sovrascrive un + realm esistente). 3. `provideKeycloakAngular()` (`src/app/core/auth/keycloak.provider.ts`) inizializza il client con `onLoad: 'check-sso'`: all'avvio l'app verifica in un iframe nascosto se esiste già una sessione Keycloak, senza forzare un redirect immediato per le diff --git a/scouthub-magazzino-fe/README.md b/scouthub-magazzino-fe/README.md index 6eb5f46..6881798 100644 --- a/scouthub-magazzino-fe/README.md +++ b/scouthub-magazzino-fe/README.md @@ -104,7 +104,7 @@ automaticamente da `ng serve`/`ng build --configuration development`) espongono: - `keycloakBaseUrl` — URL base dell'istanza Keycloak (default `http://localhost:8081`, coerente con `KEYCLOAK_PORT` in `.env` alla root del repo) - `keycloakRealm` — realm Keycloak (`scouthub`) -- `keycloakClientId` — client pubblico condiviso già definito in `keycloak/realm-export.json` +- `keycloakClientId` — client pubblico condiviso già definito in `keycloak/realm-export.template.json` (`scouthub-frontend`) - `magazzinoApiBaseUrl` — URL base di `scouthub-magazzino-be` (default `http://localhost:8083`) @@ -114,12 +114,14 @@ automaticamente da `ng serve`/`ng build --configuration development`) espongono: ```bash docker compose up -d keycloak-db keycloak ``` - Il realm `scouthub` viene importato automaticamente da `keycloak/realm-export.json` al primo - avvio (`--import-realm`). -2. Il client pubblico `scouthub-frontend` ha `redirectUris`/`webOrigins` che includono anche - `http://localhost:4203` (porta di default di questo progetto, vedi sotto) — se si cambia porta, - aggiornare `keycloak/realm-export.json` di conseguenza e reimportare il realm (o aggiornarlo - da console admin Keycloak). + Il realm `scouthub` viene importato automaticamente al primo avvio (`--import-realm`) a + partire dal template `keycloak/realm-export.template.json`, renderizzato con gli URL + pubblici correnti (`*_FE_PUBLIC_URL` in `.env`) dal servizio one-shot + `keycloak-realm-render` (vedi `keycloak/README.md`). +2. Il client pubblico `scouthub-frontend` ha `redirectUris`/`webOrigins` generati dagli URL + pubblici configurati in `.env` — se si cambia porta/dominio, basta aggiornare `.env` e + rifare `docker compose up`; per un realm già importato in precedenza va comunque + aggiornato anche da console admin Keycloak (l'import non sovrascrive un realm esistente). 3. `provideKeycloakAngular()` (`src/app/core/auth/keycloak.provider.ts`) inizializza il client con `onLoad: 'check-sso'`: all'avvio l'app verifica in un iframe nascosto se esiste già una sessione Keycloak, senza forzare un redirect immediato per le pagine pubbliche.