From b427576d5e8b56c9a1a39026319af7a9bdcac1a7 Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Tue, 2 Jun 2026 01:32:35 +0200 Subject: [PATCH] feat(infra): single-file toggle between apps-profile dev modes (#265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Make the toggle between the two `apps`-profile access modes — `localhost` (VSCode Remote-SSH port forwarding) and HTTPS hostname (`apf-portal.dev-XX.local` via the mkcert team CA) — a **single-file edit** in `infra/local/.env`. Today the switch needs touching two files (the BFF's own `.env` for the four `ENTRA_*_REDIRECT_URI`, plus `infra/local/.env` for `NX_SERVE_CONFIGURATION`), with the extra cost that the BFF `.env` then carries mode-specific values and can drift from the `localhost`-friendly defaults that native `nx serve` (no Docker) expects. After this PR: - `apps/portal-bff/.env` stays at `localhost` defaults always — native `nx serve` works untouched, no mode-aware editing of secrets-bearing files. - `infra/local/.env` is the **only** file a developer touches to flip between modes. Mode A is the default. Mode B is a five-line block to uncomment. ## How The four `ENTRA_*_REDIRECT_URI` values are added to the `portal-bff` service's `environment:` block in `dev.compose.yml` with Compose interpolation that defaults to `localhost` and accepts overrides from `infra/local/.env`: ```yaml ENTRA_REDIRECT_URI: ${ENTRA_REDIRECT_URI:-http://localhost:3000/api/auth/callback} ENTRA_POST_LOGOUT_REDIRECT_URI: ${ENTRA_POST_LOGOUT_REDIRECT_URI:-http://localhost:4200/} ENTRA_ADMIN_REDIRECT_URI: ${ENTRA_ADMIN_REDIRECT_URI:-http://localhost:3000/api/admin/auth/callback} ENTRA_ADMIN_POST_LOGOUT_REDIRECT_URI: ${ENTRA_ADMIN_POST_LOGOUT_REDIRECT_URI:-http://localhost:4300/} ``` Compose's `environment:` block wins over `env_file:`, so the BFF inside the container always sees these — `localhost` when nothing is set in `infra/local/.env`, the HTTPS hostname values when Mode B is enabled. The BFF's own `.env` is irrelevant to the container's redirect URIs in either mode; it remains the canonical source for `localhost`-friendly defaults that native `nx serve` (running outside Docker) reads as before. ## What lands | File | Change | | --- | --- | | `infra/local/dev.compose.yml` | Four `ENTRA_*_REDIRECT_URI` lines added to the `portal-bff` service's `environment:` block with `${VAR:-localhost-default}` interpolation. Inline comment explains the mode-toggle intent and points at `infra/README.md`. | | `infra/local/.env.example` | The end-of-file "Apps" block is restructured into two clearly labelled profiles: **Mode A — Localhost (DEFAULT)** (an empty block — nothing to set) and **Mode B — HTTPS hostname** (a commented five-line template the developer uncomments). | | `apps/portal-bff/.env.example` | Comment block above the redirect-URI defaults updated: instead of telling the developer to override these values here, it now explains they stay at `localhost` regardless and points at the compose-level override in `infra/local/.env`. | | `infra/README.md` | New "**Switching between dev modes — `localhost` vs hostname**" subsection between "Dockerised app dev mode" and "HTTPS dev-server setup". Comparison table + step-by-step for Mode A + pointer to the HTTPS / mkcert subsections that follow for Mode B. | ## Test plan - [x] Compose validates with no env overrides — the four `ENTRA_*_REDIRECT_URI` resolve to their `localhost` defaults (Mode A). - [x] Compose validates with the four `ENTRA_*` plus `NX_SERVE_CONFIGURATION=https` set in the environment — the URIs resolve to the HTTPS hostname values (Mode B). - [ ] **On vm-jg**: starting from a fresh checkout, leave `infra/local/.env` at its `.env.example` defaults. `./infra/local/dev.sh up apps`. Open `http://localhost:4200/` via VSCode Remote-SSH port forwarding. Login succeeds — the BFF receives `ENTRA_REDIRECT_URI=http://localhost:3000/api/auth/callback`. - [ ] **On vm-jg**: uncomment the five Mode B lines in `infra/local/.env` (replacing `dev-jg` with the actual hostname). `./infra/local/dev.sh down && up apps`. Open `https://apf-portal.dev-jg.local:4200/`. Login succeeds against the HTTPS URIs. - [ ] **Native WSL `nx serve`** (no Docker): unchanged — keeps reading `apps/portal-bff/.env`'s `localhost` defaults; the compose override never runs in this path. ## Related - [ADR-0030](docs/decisions/0030-dockerised-dev-mode.md) — dockerised dev mode this finishes for the toggle-between-modes case. - PR #263 (`feat(spa): opt-in 'https' nx serve config`) — provides the SPA-side TLS plumbing the Mode B switch enables. - PR #264 (`docs(infra): document team mkcert CA on vm-gitlab`) — documents the trust root that Mode B relies on. --------- Co-authored-by: Julien Gautier Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/265 --- apps/portal-bff/.env.example | 32 +++++++++++++++---------------- infra/README.md | 30 +++++++++++++++++++++++++++++ infra/local/.env.example | 37 ++++++++++++++++++++++-------------- infra/local/dev.compose.yml | 15 +++++++++++++++ 4 files changed, 83 insertions(+), 31 deletions(-) diff --git a/apps/portal-bff/.env.example b/apps/portal-bff/.env.example index c5291e8..468f244 100644 --- a/apps/portal-bff/.env.example +++ b/apps/portal-bff/.env.example @@ -61,24 +61,22 @@ ENTRA_CLIENT_SECRET=replace_with_real_value # post-logout URL is where Entra sends the browser after RP-initiated # logout (typically the SPA landing page). # -# The default values below match WSL-native dev (browser on the same -# host as the BFF, accessing http://localhost:4200/). For the -# ADR-0030 dockerised dev mode accessed via a hostname (e.g. -# https://apf-portal.dev-jg.local:4200/), override these to point at -# the SPA dev-server origin instead — `/api/auth/callback` is then -# proxied to the BFF (see apps/portal-shell/proxy.conf.js): +# The four `localhost` values below are the **WSL-native default** — +# browser and BFF on the same host, `http://localhost:*` redirect +# URIs (which Entra accepts as the only exception to its HTTPS rule). +# Leave them here in this file regardless of how the docker `apps` +# profile is being run. # -# ENTRA_REDIRECT_URI=https://apf-portal.dev-jg.local:4200/api/auth/callback -# ENTRA_POST_LOGOUT_REDIRECT_URI=https://apf-portal.dev-jg.local:4200/ -# ENTRA_ADMIN_REDIRECT_URI=https://apf-portal.dev-jg.local:4300/api/admin/auth/callback -# ENTRA_ADMIN_POST_LOGOUT_REDIRECT_URI=https://apf-portal.dev-jg.local:4300/ -# -# The `https:` scheme is mandatory for non-`localhost` hosts — Entra -# rejects `http:` for anything else. See the "HTTPS dev-server setup" -# section of infra/README.md for the dev-server TLS / mkcert setup. -# Each override URI must also be registered in the Entra app -# registration's Redirect URIs list (the BFF sends *one* of them per -# auth request; Entra validates it is allowed). +# For the ADR-0030 dockerised `apps` profile accessed via a +# hostname (e.g. `https://apf-portal.dev-jg.local:4200/`), do NOT +# edit these values — instead override them at the compose level +# from `infra/local/.env`. Compose's `environment:` block on the +# `portal-bff` service interpolates each of these four vars at +# parse time and wins over `env_file:`, so the BFF in the container +# sees the hostname URIs while native `nx serve` keeps reading +# this file's localhost defaults. See +# `infra/README.md` → "Switching between dev modes" for the +# two-mode toggle. ENTRA_REDIRECT_URI=http://localhost:3000/api/auth/callback ENTRA_POST_LOGOUT_REDIRECT_URI=http://localhost:4200/ # Admin portal — distinct callback per ADR-0020 §"Sessions — distinct diff --git a/infra/README.md b/infra/README.md index 2f31031..435a9c7 100644 --- a/infra/README.md +++ b/infra/README.md @@ -226,6 +226,36 @@ How it works (see [ADR-0030](../docs/decisions/0030-dockerised-dev-mode.md)): The three dev modes (native `nx serve`, devcontainer, this `apps` profile) and when to use each are summarised in [docs/setup/01-dev-debian-vm-setup.md](../docs/setup/01-dev-debian-vm-setup.md). +### Switching between dev modes — `localhost` vs hostname + +Within the `apps` profile, there are **two access modes** for the SPA. They differ only in how the browser reaches the dev-servers — the BFF and the rest of the stack are unchanged. The toggle is a single file: [`infra/local/.env`](local/.env.example). + +| Mode | When to use | What flips | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| **A — `localhost` (default)** | Solo dev, browser on the same workstation as the VSCode Remote-SSH client. No cert plumbing, no `hosts` file change. The fastest path to a working stack. | Nothing — `infra/local/.env` is the default. | +| **B — HTTPS hostname** | A teammate (or PM / QA) needs to browse YOUR VM from THEIR machine, or you have a shared VM. Requires the team mkcert CA already installed (see "Team mkcert CA on `vm-gitlab`" below) and `apf-portal.dev-XX.local` in their `hosts` file. | `NX_SERVE_CONFIGURATION=https` plus the four `ENTRA_*_REDIRECT_URI` lines in `infra/local/.env` (commented template in `.env.example`). | + +The toggle works because Compose's `environment:` block on the `portal-bff` service interpolates each `ENTRA_*_REDIRECT_URI` from `infra/local/.env` with a `localhost` fallback, and wins over `env_file:`. As a result `apps/portal-bff/.env` keeps its native-friendly `localhost` defaults regardless of mode — native `nx serve` (no Docker) and Mode A both read the same `.env` cleanly, while Mode B sees the override only inside the container. + +#### Mode A — `localhost` via VSCode port forwarding + +1. Make sure `infra/local/.env` does NOT set `NX_SERVE_CONFIGURATION` (or sets it to `development`) and leaves the four `ENTRA_*_REDIRECT_URI` lines commented (this is the `.env.example` default). +2. `./infra/local/dev.sh up apps`. +3. VSCode Remote-SSH auto-discovers the published ports (panel **PORTS** at the bottom of VSCode) and forwards them to your workstation. If a port doesn't show up, "Forward a Port" manually (4200, 4300, 3000). +4. Open `http://localhost:4200/` on the workstation. The SPA loads, fetches `/api/...` (proxied to the BFF inside Compose), and the OIDC callback at `http://localhost:3000/api/auth/callback` (already registered in Entra) is reachable through the same VSCode tunnel. + +Nothing else to configure. No mkcert. No `hosts` file. No cert warning. + +#### Mode B — HTTPS hostname (`apf-portal.dev-XX.local`) + +Detailed setup in the next two subsections ("HTTPS dev-server setup" + "Team mkcert CA on `vm-gitlab`"). Once the workstation has the team CA installed and the host file knows the hostname, switching is: + +1. In `infra/local/.env`, uncomment the five Mode B lines (and replace `dev-jg` with the right hostname). +2. `./infra/local/dev.sh down && ./infra/local/dev.sh up apps`. +3. Browse `https://apf-portal.dev-XX.local:4200/`. + +To switch back to Mode A: comment those five lines, `down && up apps`. No other file touched. + ### HTTPS dev-server setup — remote-browser access via a hostname By default the dev-servers serve plain HTTP — fine when the browser is on the same host as the BFF (`http://localhost:4200/`), which is also the only HTTP origin Entra accepts as a redirect URI. The moment you access the SPA over a **hostname** (e.g. `apf-portal.dev.local`, useful when the browser sits on a workstation and the stack runs on a shared / per-dev VM), Entra refuses the `http:` redirect URI and the dev-servers must terminate TLS. diff --git a/infra/local/.env.example b/infra/local/.env.example index 605f668..89bc769 100644 --- a/infra/local/.env.example +++ b/infra/local/.env.example @@ -37,18 +37,27 @@ JAEGER_UI_PORT=16686 SERVE_STATIC_PORT=4200 # ---------------------------------------------------------------- Apps (`--profile apps`) -# Nx serve configuration for the SPA dev-servers. Two values matter: -# - `development` (default) — plain HTTP. Use with `localhost` -# browser access; Entra accepts -# `http://localhost:*` redirect URIs. -# - `https` — TLS-terminating dev-server using the -# cert at `.secrets/dev-tls.{key,pem}`. -# Required when accessing the SPA via a -# hostname (e.g. `apf-portal.dev.local`) -# since Entra rejects `http:` for any -# non-`localhost` redirect URI. See the -# "HTTPS dev-server setup" section of -# infra/README.md for the mkcert -# procedure and how to wire the -# matching `ENTRA_REDIRECT_URI` value. +# Two access modes for the SPA dev-servers — pick one. The switch +# happens entirely in this file (compose interpolates the SPA's nx +# serve --configuration AND the BFF's ENTRA_*_REDIRECT_URI from here); +# `apps/portal-bff/.env` stays at its localhost defaults regardless. +# See infra/README.md → "Switching between dev modes". + +# === Mode A — Localhost / VSCode port-forwarding (DEFAULT) === +# Leave the entire Mode B block below commented. The SPA dev-servers +# stay on plain HTTP, the BFF receives the `http://localhost:*` +# redirect URIs already registered in Entra. Browse `http:// +# localhost:4200/` from the workstation — VSCode Remote-SSH auto- +# forwards 4200 / 4300 / 3000 from the VM. + +# === Mode B — HTTPS via hostname (apf-portal.dev-XX.local) === +# Required when the SPA is accessed via a hostname (Entra refuses +# `http:` for non-`localhost` redirect URIs — see the team mkcert CA +# setup in infra/README.md). Replace `dev-jg` with YOUR hostname and +# uncomment the five lines. +# # NX_SERVE_CONFIGURATION=https +# ENTRA_REDIRECT_URI=https://apf-portal.dev-jg.local:4200/api/auth/callback +# ENTRA_POST_LOGOUT_REDIRECT_URI=https://apf-portal.dev-jg.local:4200/ +# ENTRA_ADMIN_REDIRECT_URI=https://apf-portal.dev-jg.local:4300/api/admin/auth/callback +# ENTRA_ADMIN_POST_LOGOUT_REDIRECT_URI=https://apf-portal.dev-jg.local:4300/ diff --git a/infra/local/dev.compose.yml b/infra/local/dev.compose.yml index aa25dc0..063e582 100644 --- a/infra/local/dev.compose.yml +++ b/infra/local/dev.compose.yml @@ -240,6 +240,21 @@ services: DATABASE_URL: 'postgresql://${POSTGRES_USER:-portal}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-portal_dev}?schema=public' REDIS_URL: 'redis://default:${REDIS_PASSWORD}@redis:6379/0' OTEL_EXPORTER_OTLP_ENDPOINT: 'http://otel-collector:4318/v1/traces' + # OIDC redirect URIs. Driven by infra/local/.env so switching + # between the two access modes — `localhost` (default; VSCode + # Remote-SSH port-forwarding) and `https` hostname (the + # ADR-0030 mkcert path) — is a single-file change. Unset → + # localhost defaults; set in infra/local/.env to override with + # an `https://apf-portal.dev-XX.local:…` hostname. See the + # "Switching between dev modes" subsection of infra/README.md. + # Compose `environment` overrides `env_file`, so the BFF's + # own .env stays at localhost (so native `nx serve` works + # untouched) and only `infra/local/.env` carries the + # docker-apps-mode override. + ENTRA_REDIRECT_URI: ${ENTRA_REDIRECT_URI:-http://localhost:3000/api/auth/callback} + ENTRA_POST_LOGOUT_REDIRECT_URI: ${ENTRA_POST_LOGOUT_REDIRECT_URI:-http://localhost:4200/} + ENTRA_ADMIN_REDIRECT_URI: ${ENTRA_ADMIN_REDIRECT_URI:-http://localhost:3000/api/admin/auth/callback} + ENTRA_ADMIN_POST_LOGOUT_REDIRECT_URI: ${ENTRA_ADMIN_POST_LOGOUT_REDIRECT_URI:-http://localhost:4300/} # Secrets (Entra / session / jwks) come from the BFF's own dev env. # `required: false` so SPA-only devs can `up` without it — the BFF # then fails its own boot-time config validators with a clear message