fix(e2e): run the security-service in the compose stack + route /api/v1/security - #279
Conversation
…v1/security The SPA was migrated onto the provider-agnostic Security API (/api/v1/security/*), served ONLY by backend/security. But no e2e stack ran that service: this compose builds backend/Dockerfile (the MONOLITH), which mounts /api/auth, /api/apps, /api/organizations and /api/v1/app-registry — not /api/v1/security. So in CI the SPA's login POST 404s and the sign-in e2e times out waiting for a response that can never arrive. That is why "E2E (sign-in)" has been red on master since the cutover, and red on EVERY pr since — including PRs that touch only a shell script and markdown. The tests were not flaky and the failure was not pre-existing noise: the security service, which now serves all SPA auth, has had ZERO e2e coverage. Adds the `security` service (Authentik + DB env — password sign-in is brokered server-side through the flow-executor, so it needs Authentik even for the plain email/password path) and routes /api/v1/security/ → security:3002 in the e2e nginx, mirroring the prod Ingress. The frontend now depends on it: nginx resolves upstreams at startup and refuses to boot on an unresolvable one. Prod was never affected — the k8s Ingress routes /api/v1/security → fuzefront-security ahead of nginx. Verified: docker compose -f docker-compose.e2e.yml config -> PARSE OK, `security` resolved nginx -t (upstreams host-mapped) -> syntax is ok, test successful Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com> Claude-Session-Id: cf830721-b1ef-4fe0-a024-035ad280dcf7
Automated code review (gate-code-review)Credit balance is too low Report-only — this check never blocks merge. |
Self-review catch: I had pointed the security container at http://localhost:4173/api/v1/security/social/callback, which is not in the OIDC provider's redirect_uris. Authentik matches those strict, so it would have rejected the authorize call and password login would still have failed — the container would be present but sign-in still broken. Password sign-in is a full authorize→code→exchange (services/authentikPassword.ts drives the flow-executor, then GETs the authorize URL with the session cookies and lifts `code` out of the 302 Location). The redirect_uri therefore has to be REGISTERED, but never has to be browser-reachable — the server follows the redirect itself. So reuse the shared, already-registered /api/auth/oidc/callback, which is exactly what prod's security service does (authentik.oidc.redirectUri → https://app.fuzefront.com/api/auth/oidc/callback). Registered set: authentik/blueprints/provider-oidc.yaml. Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com> Claude-Session-Id: cf830721-b1ef-4fe0-a024-035ad280dcf7
Automated code review (gate-code-review)Credit balance is too low Report-only — this check never blocks merge. |
|
Self-review catch — pushed I had set the security service's The reason it matters: password sign-in is a full authorize→code→exchange, not just a flow-executor call. Fixed by reusing the shared, already-registered Re-verified: (The failing 🤖 Addressed by Claude Code |
…the backend
Regression I introduced in this PR: adding the security container made the
OIDC-plumbing stack fail with
dependency failed to start: container fuzefront-e2e-backend-1 exited (1)
which the control run (same branch, before this container existed) did NOT do —
there the stack came up and the test failed on its own merits (access_denied).
Cause: backend/security/src/index.ts runs the SAME 001-009 migration chain
against the SAME `knex_migrations` table as the monolith. Compose started both
at once, they raced for the migration lock, and the loser exited 1 — taking the
whole stack down, since frontend depends on it.
Serialise: security now waits for backend to be healthy, so the chain is already
applied and security's own run is a harmless no-op. Chain is
postgres → backend → security → frontend (no cycle; compose config parses).
Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>
Claude-Session-Id: cf830721-b1ef-4fe0-a024-035ad280dcf7
Automated code review (gate-code-review)Credit balance is too low Report-only — this check never blocks merge. |
|
Regression I introduced here — fixed in I checked it against a control (the same plumbing workflow on Cause: Fix: serialise — Worth flagging separately, because it's the next real problem rather than this PR's: that control run shows plumbing's genuine failure is i.e. the OIDC flow itself returns access_denied. So this PR unblocks the stack, but plumbing will likely still fail on that — a real auth bug, tracked next, not something to hide behind "pre-existing". 🤖 Addressed by Claude Code |
…rity flows (#276) The MFA stages (TOTP/WebAuthn/SMS) were defined but never BOUND to any flow — the stages-mfa.yaml comment claimed a user-settings binding that did not exist, so none of them were reachable. Binds TOTP/WebAuthn/SMS setup into default-user-settings-flow (device enrollment + the Google link/unlink surface) and fuzefront-mfa-validate into default-authentication-flow with not_configured_action=skip (enforce-if-enrolled, optional). Idempotent, state: present. Verified: helm template → EXIT 0, blueprint present in the authentik-blueprints ConfigMap; YAML parses with Authentik's !Find tags. Note: the CI-autofix branch proposed for this PR was rejected — it added a bcrypt login shim to the monolith, bypassing Authentik and testing a fake Security API. The real cause (the e2e stack never ran the security service) is fixed in #279/#280. Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com> Claude-Session-Id: cf830721-b1ef-4fe0-a024-035ad280dcf7
Phone-based 2FA behind the provider-agnostic surface: enroll a phone factor, verify enrollment, challenge at login, remove factor. OTP generation/expiry is delegated to Twilio Verify rather than rolled locally, fronted by services/sms-service so the security-service never talks to the vendor directly. Provider-neutral in the API: no vendor name appears in any response. Config via env only (TWILIO_ACCOUNT_SID / TWILIO_AUTH_TOKEN / TWILIO_VERIFY_SERVICE_SID / SMS_SERVICE_URL). NOTE: the CI-autofix branch proposed here was rejected — it added a bcrypt auth shim to the monolith, which would have greened the sign-in check against a fake Security API that does not exist in prod. The real cause (the e2e stack never ran the security service) is fixed in #279/#280. Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com> Claude-Session-Id: cf830721-b1ef-4fe0-a024-035ad280dcf7
The gap
The SPA was migrated onto the provider-agnostic Security API (
/api/v1/security/*), served only bybackend/security. But no e2e stack ran that service. This compose buildsbackend/Dockerfile— the monolith, which mounts/api/auth,/api/apps,/api/organizations,/api/v1/app-registryand not/api/v1/security. So in CI the SPA's login POST 404s and the sign-in e2e times out waiting for a response that can never arrive.The security service — which now serves all SPA auth — has had zero e2e coverage since the cutover. That is why
E2E (sign-in)has been red on master since then, and red on every PR since, including #278 which touches only a shell script and markdown. A docs-and-script PR cannot break sign-in — that's the tell. It was never flake, and "pre-existing" was never a reason to wave it off.Prod was never affected: the k8s Ingress routes
/api/v1/security→fuzefront-securityahead of nginx, which is why real sign-in works.The fix
securityservice todocker-compose.e2e.yml— with Authentik + DB env, because password sign-in is brokered server-side through the flow-executor, so Authentik is required even for the plain email/password path (not just social)./api/v1/security/→security:3002indeploy/e2e/nginx.e2e.conf, above the/api/catch-all — mirroring the prod Ingress.frontendnowdepends_onit: nginx resolves upstreams at startup and refuses to boot on an unresolvable one (confirmed while testing:host not found in upstream "security").Verified (actually run, not asserted)
Scope
Foundation only. Not included:
.github/workflows/e2e.yml(the sign-in workflow hand-starts the monolith with no Authentik — separate piece), the oidc-plumbing spec base URLs, andfrontend/nginx.confparity.hold+ noauto-merge— master is deploy-on-push.Co-Authored-By: Claude