feat(portal-bff): redis client foundation per ADR-0010
First step toward Redis-backed sessions. Adds the shared `ioredis` connection that every downstream consumer (session storage, OBO token cache, …) will inject via the new `REDIS_CLIENT` DI token. What lands: - `ioredis@^5.10.1` as a direct dependency. Chosen by ADR-0010 for its mature Sentinel support — single-instance URL today, Sentinel-HA configuration plumbing lands with the production infrastructure ADR. - `.env.example` promotes `REDIS_URL` from its previous future-vars comment block into an active variable, with a default that matches `infra/local/.env` (REDIS_PASSWORD + REDIS_PORT). The Sentinel-style keys (`REDIS_SENTINEL_HOSTS`, `REDIS_SENTINEL_NAME`, `REDIS_TLS`) stay in the future-vars comment until the prod deploy lands. - `apps/portal-bff/src/config/check-redis-config.ts` — boot-time guard mirroring the existing four: `assertDatabaseUrl` / `assertEntraConfig` / `assertSessionSecret`. Refuses to start if `REDIS_URL` is unset, not a valid `redis://` / `rediss://` URL, missing the password (the local stack requires one), or still set to the `redis_dev_change_me` .env.example placeholder. Returns a typed `RedisConfig` with parsed `host` + `port` for downstream observability. - `apps/portal-bff/src/redis/redis.token.ts` — `REDIS_CLIENT` string token + `Redis` type alias. Same shape as `ENTRA_CONFIG` / `MSAL_CLIENT`. - `apps/portal-bff/src/redis/redis.module.ts` — `RedisModule` exposes a factory provider for `REDIS_CLIENT`. The factory builds the `ioredis` client from the parsed config, caps `maxRetriesPerRequest` at 3 (so an unreachable Redis surfaces a command-time error instead of an infinite reconnect storm), and wires `connect` / `ready` / `error` / `close` / `reconnecting` events into the Pino stream with the `redis` Pino context. Non-global on purpose — modules import it to state "I depend on Redis". - `main.ts` calls `assertRedisConfig()` alongside the other three validators. `AppModule` imports `RedisModule`. Verification: - `nx run-many -t lint test build --projects=portal-bff` — green. - 62 / 62 specs (was 52; +10 across the config validator spec and the module spec — the latter exercises both the happy path against an unreachable URL — `ioredis` constructs lazily so no real socket opens — and the missing-env failure mode). - Boot smoke (with the local Compose stack running): the `redis` Pino context shows `redis.connect` → `redis.ready` lines on startup; killing the Redis container later produces `redis.close` / `redis.reconnecting`. What this PR explicitly does NOT do: - Mount `express-session` + `connect-redis` middleware. The next PR wires the session cookie (`__Host-portal_session`) + the encrypted payload + the lookup middleware that attaches `user` to every request. - Plug the callback into session creation. Auth still ends with a Pino log + redirect; the SPA still sees the user anonymous on the next request.
This commit is contained in:
@@ -73,6 +73,17 @@ ENTRA_POST_LOGOUT_REDIRECT_URI=http://localhost:4200/
|
||||
# node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
|
||||
SESSION_SECRET=replace_with_32_random_bytes_base64url
|
||||
|
||||
# Redis connection (per ADR-0010). The BFF uses `ioredis` for session
|
||||
# storage (today: just the connection; the express-session +
|
||||
# connect-redis middleware lands in the next PR).
|
||||
#
|
||||
# REDIS_URL — full URL form including auth. Must match `infra/local/.env`
|
||||
# (REDIS_PASSWORD + REDIS_PORT) when running against the local Compose
|
||||
# stack. Production wiring uses Sentinel (REDIS_SENTINEL_HOSTS +
|
||||
# REDIS_SENTINEL_NAME — future-vars block below) and TLS; the current
|
||||
# variable supports the dev single-instance shape only.
|
||||
REDIS_URL=redis://default:redis_dev_change_me@localhost:6379/0
|
||||
|
||||
# Future env vars introduced by upcoming phases / ADRs:
|
||||
#
|
||||
# Auth flow (ADR-0009) — additional keys wired as the routes land:
|
||||
@@ -81,9 +92,9 @@ SESSION_SECRET=replace_with_32_random_bytes_base64url
|
||||
# in the multi-tenant phase — empty means
|
||||
# "only ENTRA_TENANT_ID is accepted")
|
||||
#
|
||||
# Sessions (ADR-0010):
|
||||
# REDIS_URL (or REDIS_SENTINEL_HOSTS + REDIS_SENTINEL_NAME)
|
||||
# REDIS_PASSWORD
|
||||
# Sessions (ADR-0010) — additional keys wired as the layers land:
|
||||
# REDIS_SENTINEL_HOSTS (CSV `host:port,host:port,…`; prod HA)
|
||||
# REDIS_SENTINEL_NAME (master name in Sentinel; prod HA)
|
||||
# REDIS_TLS ('true' in prod)
|
||||
# SESSION_ENCRYPTION_KEY (32-byte base64)
|
||||
# SESSION_IDLE_TIMEOUT_SECONDS (default 1800)
|
||||
|
||||
Reference in New Issue
Block a user