Skip to content

Commit 1f2f32e

Browse files
gpascucciclaude
andauthored
feat(auth): FAM/Cognito SPA sign-in via Amplify (Story 1.2) (#300)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 39fffa9 commit 1f2f32e

39 files changed

Lines changed: 2879 additions & 170 deletions

.github/workflows/reusable-tests.yml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ jobs:
6868
# raw-Playwright frontend/e2e). Only the DATA-INDEPENDENT `@smoke` project runs here: it aborts /api,
6969
# so it needs neither the seeded delivery Oracle nor the pinned fixtures the deployed env lacks. The
7070
# full data-backed suite (setup + chromium — SCH1/SEC) is a LOCAL/manual gate; see frontend/e2e/README.md.
71+
#
72+
# The smoke runs against a LOCAL Vite server on localhost, NOT the deployed PR URL. Since Story 1.2
73+
# turned auth on, mock auth is (by design) disabled off-localhost — `isMockAuth()` double-gates on
74+
# `isLocalHost()` (src/env.ts) — so an anonymous visit to a deployed host correctly bounces to the
75+
# FAM/Cognito Hosted UI and never renders the shell. On localhost with the repo-default
76+
# `{ mockUser: true }` config the shell renders client-side, keeping this smoke auth- AND
77+
# backend-independent (the scenarios still abort every /api call).
7178
defaults:
7279
run:
7380
working-directory: frontend/e2e
@@ -82,17 +89,28 @@ jobs:
8289
node-version: 24
8390
cache: "npm"
8491
cache-dependency-path: frontend/e2e/package-lock.json
85-
- name: Install dependencies
92+
- name: Install e2e dependencies
8693
run: |
8794
npm ci
8895
npx playwright install --with-deps chromium
8996
90-
- name: Run smoke (data-independent)
97+
- name: Install frontend
98+
working-directory: frontend
99+
run: npm ci
100+
101+
- name: Run smoke (data-independent, localhost mock auth)
102+
working-directory: frontend
91103
env:
92-
E2E_BASE_URL: https://${{ env.PREFIX }}.${{ env.DOMAIN }}/
93104
CI: "true"
94105
run: |
95-
npm test -- --project=smoke --reporter=html
106+
# Serve the app on localhost so mock auth engages (isMockAuth() requires localhost); the
107+
# @smoke scenarios abort /api, so no backend is needed. No E2E_BASE_URL → the Playwright
108+
# config defaults baseURL to http://localhost:3000.
109+
npm run dev -- --port 3000 &
110+
DEV_PID=$!
111+
npx --yes wait-on -t 120000 http://localhost:3000
112+
( cd e2e && npm test -- --project=smoke --reporter=html )
113+
kill "$DEV_PID" 2>/dev/null || true
96114
97115
- uses: actions/upload-artifact@v7
98116
if: (! cancelled())

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,49 @@ The recommended, zero-import workaround is to leverage your Windows host's trust
107107
```
108108
Docker Compose will automatically detect this variable and mount your local Windows Maven cache into the container's `/root/.m2` path, bypassing the certificate handshake issues completely!
109109

110+
### Authentication (FAM/Cognito) — local testing
111+
112+
The SPA has two auth modes, selected at runtime by `public/amplify-config.js` (loaded before the
113+
bundle). The repo default is **mock**; deployed environments mount a per-env ConfigMap over it. See
114+
`src/context/auth/` (the `AuthProvider` seam) and `src/config/auth/amplify-initializer.ts`.
115+
116+
**Mock mode (default — no Cognito).** `npm run dev` with the backend running (security off by
117+
default) signs you in automatically. Use the **"Mock user"** dropdown in the header to switch
118+
`ILCR_ADMIN``ILCR_SUBMITTER` — it switches both the nav/route-guards **and** the backend mock
119+
principal (via the `X-Mock-Groups` header), so it exercises role gating end to end. This is the
120+
fastest path for manual testing.
121+
122+
**Real FAM/Cognito login (Hosted UI).**
123+
124+
1. Frontend — copy the example config over the default (do **not** commit it; the repo default must
125+
stay `mockUser: true`):
126+
```bash
127+
cd frontend
128+
cp amplify-config.local.example.js public/amplify-config.js
129+
npm run dev # then hard-refresh the browser (public/ files load at page load)
130+
```
131+
2. Backend — run with security on so `/api/v1/me` validates the real ID token:
132+
```bash
133+
cd backend
134+
ILCR_SECURITY_ENABLED=true COGNITO_REGION=ca-central-1 \
135+
COGNITO_USER_POOL=ca-central-1_UpeAqsYt4 COGNITO_CLIENT_ID=352pis0ark86dam7ht1jlp9uj5 \
136+
SPRING_PROFILES_ACTIVE=oracle,openshift ./mvnw spring-boot:run
137+
```
138+
3. Open `http://localhost:3000` → FAM Hosted UI → sign in (IDIR/BCeID) → back to the app with your
139+
real role. Confirm the exact `cognitoDomain` with the FAM admin if the Hosted UI does not load.
140+
141+
**Dev-only testing aids (real session, local dev only — `import.meta.env.DEV`, tree-shaken from every
142+
deployed build):**
143+
144+
- **"View as (dev)"** header dropdown — overrides the role the SPA uses (nav + route guards) so you
145+
can test both roles without re-logging-in. It is **frontend-only**: the backend still enforces your
146+
real token, so admin APIs still `403` if your account isn't really in that group.
147+
- A **"viewing as" warning banner** appears whenever an override is active, naming your real role.
148+
- A **Sign out** button (header, Logout icon) runs the Cognito/loginproxy logout chain on a real
149+
session; hidden in mock mode.
150+
151+
When done with real login: `git checkout -- frontend/public/amplify-config.js`.
152+
110153
## Frontend Shared Conventions
111154

112155
Reusable building blocks and global styles that new schedule/feature pages should adopt rather than
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Local real-FAM-login example (DEV Cognito client).
2+
//
3+
// Copy this over `public/amplify-config.js` to exercise the real Cognito Hosted UI locally:
4+
// cp amplify-config.local.example.js public/amplify-config.js
5+
// Do NOT commit your edited public/amplify-config.js — the repo default must stay { mockUser: true }.
6+
//
7+
// These are public identifiers (Cognito pool + SPA web-client id), not secrets. Run the backend with
8+
// security on so /api/v1/me validates the real token:
9+
// ILCR_SECURITY_ENABLED=true COGNITO_REGION=ca-central-1 \
10+
// COGNITO_USER_POOL=ca-central-1_UpeAqsYt4 COGNITO_CLIENT_ID=352pis0ark86dam7ht1jlp9uj5 \
11+
// SPRING_PROFILES_ACTIVE=oracle,openshift ./mvnw spring-boot:run
12+
//
13+
// Confirm with the FAM admin (Ian): the exact cognitoDomain, and that your IDIR account carries an
14+
// ILCR group in the DEV pool (otherwise you correctly land on the No-access screen). Values from
15+
// Ian's 2026-07-28 FAM config; the DEV client allow-lists http://localhost:3000/.
16+
window.amplifyConfig = {
17+
mockUser: false,
18+
userPoolId: 'ca-central-1_UpeAqsYt4',
19+
userPoolClientId: '352pis0ark86dam7ht1jlp9uj5', // DEV web client
20+
cognitoDomain: 'lza-prod-fam-user-pool-domain.auth.ca-central-1.amazoncognito.com',
21+
redirectSignIn: 'http://localhost:3000/',
22+
redirectSignOut:
23+
'https://logontest7.gov.bc.ca/clp-cgi/logoff.cgi?retnow=1&returl=https://dev.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/logout?redirect_uri=http://localhost:3000/logout',
24+
}

frontend/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
</head>
1111
<body>
1212
<div id="root"></div>
13+
<!-- Runtime Cognito config, injected before the bundle. Local dev serves the mock template from
14+
public/; each deployed environment mounts a ConfigMap over this file (no secrets in the image). -->
15+
<script src="/amplify-config.js"></script>
1316
<script type="module" src="/src/main.tsx"></script>
1417
</body>
1518
</html>

0 commit comments

Comments
 (0)