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 with admin/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

GIW POC Identity Platform · local demo · not production · generated from the repository