Environments — GIW POC
Status: LOCAL DEMO ONLY · Date: 2026-09-21 Rule in force: local on the Founder's laptop. No cloud, no test environment, no shared server, no production, no external hosting.
This document keeps what runs today and what a real environment would need apart, so neither leaks into the other.
1. LOCAL — the only environment that exists
Docker Compose, localhost, mock services, synthetic data.
Endpoints
Every port binds to 127.0.0.1. Nothing is reachable from the LAN or the Internet.
| Service | URL | Auth | Notes |
|---|---|---|---|
| Wi-Fi Portal | http://localhost:3000 | none | The only host the browser ever contacts |
| › the app (all screens) | http://localhost:3000/ | none | Portal-rendered. No Keycloak page exists. |
| › login | POST http://localhost:3000/api/v1/auth/login |
none | Sets the sid cookie |
| › register | POST http://localhost:3000/api/v1/auth/register |
none | |
| › employee verification | POST http://localhost:3000/api/v1/auth/employee |
none | |
| › session state | GET http://localhost:3000/api/v1/auth/session |
sid cookie |
Raw token only when DEBUG_EXPOSE_TOKEN=1 |
| › refresh | POST http://localhost:3000/api/v1/auth/refresh |
sid cookie |
|
| › logout | POST http://localhost:3000/api/v1/auth/logout |
sid cookie |
Revokes the network session too |
| › meta | GET http://localhost:3000/api/v1/meta |
none | |
| › health | http://localhost:3000/health | none | |
| Keycloak (Galaxy ID) | http://localhost:8080 | admin / admin |
Admin console. Not part of any user journey. |
| › realm discovery | http://localhost:8080/realms/galaxy-id-poc/.well-known/openid-configuration | none | |
| › JWKS | http://localhost:8080/realms/galaxy-id-poc/protocol/openid-connect/certs | none | |
| › health (readiness) | http://localhost:9000/health/ready | none | Separate port — not 8080 |
| Mock HR API | http://localhost:3001 | X-API-Key |
|
| › verify | POST http://localhost:3001/api/v1/employees/verify |
X-API-Key |
|
| › fault injection | POST http://localhost:3001/admin/fault |
none | Test harness. Must not exist in a real environment. |
| › health | http://localhost:3001/health | none | |
| Entitlement Service | http://localhost:3002 | Bearer JWT | |
| › evaluate | POST http://localhost:3002/api/v1/entitlements/evaluate |
Bearer JWT | |
| › read | GET http://localhost:3002/api/v1/entitlements/{id} |
Bearer JWT | |
| › health | http://localhost:3002/health | none | |
| Session Service | http://localhost:3003 | X-API-Key |
|
| DMN Decision Service | http://localhost:3005 | none | Rules from .dmn on disk |
| › evaluate | POST http://localhost:3005/api/v1/decisions/{model}/evaluate |
none | |
| › list models | GET http://localhost:3005/api/v1/decisions |
none | Shows the source file each model came from |
| › reload | POST http://localhost:3005/api/v1/decisions/reload |
none | ⚠️ unauthenticated |
| › create / grant / revoke / read | /api/v1/sessions* |
X-API-Key |
|
| › health | http://localhost:3003/health | none | |
| giw-admin module | http://localhost:8080/realms/galaxy-id-poc/giw-admin/ | Bearer (realm, manage-users) |
GUI shell is public; every /api call authenticates |
| › webhook | POST .../giw-admin/webhook/{company} |
HMAC over the raw body | Not a bearer endpoint — the caller is a member company |
| Mail sink | http://localhost:3008 | none | Demo mailbox. No outbound socket exists in this service |
| › SMTP | localhost:1025 | none | Refuses AUTH and STARTTLS outright rather than faking them |
| › messages | GET/DELETE http://localhost:3008/api/v1/messages |
none | In memory, bounded, lost on restart |
| Postgres | not published | — | Container network only |
Ports are env-driven (PORT_PORTAL, PORT_HR, PORT_ENTITLEMENT, PORT_SESSION, PORT_KEYCLOAK, PORT_KEYCLOAK_HEALTH, PORT_DMN, PORT_LOYALTY, PORT_CONSENT, PORT_MAIL_SINK, PORT_SMTP) and the bind address is BIND_ADDR, default 127.0.0.1.
The realm's themes and its SMTP target are applied by bootstrap from GIW_THEME, SMTP_HOST, SMTP_PORT and SMTP_FROM — not written into realm-export.json, so the same realm file works with or without the mail sink running, and another environment can point elsewhere without editing a committed file.
Do not set
BIND_ADDR=0.0.0.0. On a café, hotel or office network that publishes Keycloak withadmin/admin, an unauthenticated fault-injection endpoint and an open SMTP port to everyone on the subnet.
Run
cd poc/identity
cp .env.example .env
docker compose up -d --build
node tests/e2e.mjs
What local deliberately is
| Aspect | Local | Why it is acceptable here |
|---|---|---|
| Transport | plain HTTP | loopback only; nothing crosses a wire |
| Secrets | weak defaults in .env |
boots unattended; .env is git-ignored |
| Data | synthetic | no real person, employee record or citizen ID |
| Entitlement store | in memory | lost on restart, and that is fine for a demo |
| Session store | in memory | same |
| HR | mock service | the real system of record is undecided (T-HR-SOR) |
| Service auth | shared X-API-Key (services) + client secret (Galaxy ID) |
no untrusted party on the network |
| Grant | Direct Grant — POC ONLY | see docs/AUTH-STRATEGY.md. Production path is browser OIDC / federation, TBC |
| Observability | stdout JSON logs | docker logs is the whole toolchain |
2. Configuration boundaries — why this can move later
The point of the rules below is that a future environment needs new values, not new architecture.
| Rule | How it is honoured |
|---|---|
| Config through environment variables | Every service reads host, port, URL, timeout, TTL and credential from env. No config file is read at runtime except realm-export.json, which is seed data. |
No hard-coded localhost in business logic |
localhost appears only as an env default. Audit: grep -rn localhost */src returns four lines, all process.env.X || "http://localhost:…". |
| One place per URL | PORTAL_BASE_URL is the only definition of the portal address. bootstrap reconciles the Keycloak clients against it on every start, so realm-export.json never needs editing. |
| Browser-facing vs container-facing addresses are separate | The portal reaches Galaxy ID at OIDC_INTERNAL_BASE over the container network, while tokens carry KC_HOSTNAME as iss and the Entitlement Service checks against that. Two variables, deliberately. Conflating them is the classic captive-portal bug. |
| No secret in the repository | The API client secret reaches Keycloak through OIDC_API_CLIENT_SECRET and is applied by bootstrap; realm-export.json ships a placeholder. |
.env.example |
Present, commented, every value a placeholder. .env is git-ignored. |
| Docker Compose is the run contract | One command up, one command down. |
| OpenAPI is not localhost-bound | servers use {scheme}://{host}:{port} variables; localhost is the default, not the spec. |
| Production assumptions live apart from code | This document plus api/API-OVERVIEW.md §7 (POC assumptions), api/API-SECURITY.md §11 (gaps), RUNBOOK.md §9 (pre-deploy checklist). No production claim sits inside a service. |
3. LATER — what a real environment adds
Nothing below is built, and nothing below should be built before a real environment is approved.
| Area | What is needed | Blocked by |
|---|---|---|
| TLS | sslRequired: external, https KC_HOSTNAME, Secure on the sid cookie, HSTS |
a domain |
| DNS / domain | real hostnames for portal and Keycloak | a domain |
| Deployment profile | docker-compose.prod.yml overlay or Helm chart |
an environment |
| Database | managed Postgres for Keycloak; a real store for entitlement and session | T-STORE |
| Secret management | vault or cloud secret store; rotate every value in .env |
an environment |
| Service auth | replace both X-API-Key legs with mTLS or short-lived service tokens |
— |
| Audience | dedicated audience per resource server; stop accepting account |
T-AUD |
| Rate limiting | Entitlement and Session have none | — |
| Observability | metrics, tracing, alerting, log shipping | an environment |
| HA | more than one replica; the in-memory stores make this impossible today | T-STORE |
| Real HR integration | contract, auth model, SLA, versioning, contract tests | T-HR-SOR, D7 |
| Consent capture | required for the CCCD path under Law 91/2025 | T-CCCD |
| Data subject access / erasure | required in production | D7 |
| Remove test harness | delete POST /admin/fault; set DEBUG_EXPOSE_TOKEN=0 |
— |
| Authentication | Move from Direct Grant (Mode A) to browser OIDC / federation (Mode B) | T-GRANT — Direct Grant is POC only |
The full pre-deployment checklist is RUNBOOK.md §9.
Authentication modes and the migration path: AUTH-STRATEGY.md.
4. Hard stops
These are not "later" items. They are conditions that must be met before the corresponding step is taken at all.
| Stop | Condition |
|---|---|
| Do not point this stack at a real HR system | D7 (Data Controller / Processor) is open |
| Do not load real personal data | no consent artefact, no retention policy, no erasure path |
Do not expose any port beyond 127.0.0.1 |
Keycloak admin is admin/admin; /admin/fault is unauthenticated |
| Do not treat this as an approved architecture | all 11 ADRs are PROPOSED |
| Do not ship the CCCD flow to production | ADR-003 and D3 recommend removing it; see AI_CONTEXT.md §11b |
| Do not ship Direct Grant to production | RFC 9700 says the password grant MUST NOT be used; POC ONLY, see AUTH-STRATEGY.md |
| Do not delete the browser / PKCE path | It is the PRODUCTION CANDIDATE and the only route to IdP federation |