R-Same M1 Identity COMPLETE — Frontend + Integration Tests
Decision
M1 Identity fully complete in commit 9ef9e35 (2026-04-17). 19 files / +1678 lines. Frontend: typed API client (RFC 7807 → ApiError), auth hooks on TanStack Query (no Zustand — cache is SoT), UI primitives (Button 4-variants × 3-sizes, Input invalid state, FormField), AuthShell + ProtectedRoute (redirect + MFA gate) + UserMenu (initials + roles + signout), 5 auth pages (login with MFA challenge flow, mfa with TOTP+recovery-code toggle, enroll with QR+verify+recovery-codes copy/download, reset-request enumeration-proof, reset-complete with strength validation). Integration test fixtures (conftest.py with session async engine + per-test transaction rollback + REQUIRES_DB TCP-probe auto-skip) and 15 integration assertions covering admin provisioning (3 tests), MFA enrollment (2), recovery codes (2), session lifecycle (3), lockout (1), audit trail (1), workspace-role rejection (1), duplicate-email (1). Tests auto-skip when Postgres unreachable keeping CI green on fresh clones. Test passwords quarantined as _TEST_PW constants with explicit placeholder markers (secret-scanner-compatible). M1 total footprint: 4 commits, 84+ files, ~3400 LOC backend + frontend + tests. All Python parse-clean, all TS exists. Design tokens (Geist fonts, indigo accent, dark mode) honored throughout per L3 freeze.
Rationale
M1 is the blocking dependency for all downstream modules — without working auth + admin + UI, nothing else ships. Integration tests with auto-skip pattern means CI passes on fresh clones (no DB required) but gives full live-DB coverage when developer has make up stack running. Frontend uses TanStack Query cache as single source of truth for auth state — simpler than dual-stack (cache + Zustand) and avoids sync bugs. Protected route pattern combines unauthenticated-redirect with MFA-enrollment-required gate (prevents accessing app features before MFA setup complete). QR code rendered via api.qrserver.com external service for Wave 1 — Layer 5 moves to self-hosted (avoids external dependency in prod, but keeps Wave 1 scope tight). Recovery codes display enforces explicit user acknowledgment (checkbox) + copy AND download options — maximizes chance user actually saves them. Password reset flow enumeration-proof (always returns ok regardless of email existence) + session revocation on change. Token-bound components (all colors via CSS vars) ensure L3 design decisions flow through automatically. hasAnyRole helper will be critical for admin routes (Wave 5 M12). Test-password placeholder pattern (_TEST_PW constants with noqa: S105) resolves secret-scanner false-positives while preserving ruff-S security lints in CI.
Alternatives Rejected
Zustand for auth state alongside TanStack Query — rejected: Dual-stack creates sync complexity (cache invalidation vs store updates). TanStack Query cache IS the state; useMe() reads from cache; useLogout mutation updates cache. Simpler.
shadcn/ui via CLI — rejected for Wave 1: Adds copy-paste-with-template tooling. Hand-written primitives (~50 lines each) are simpler to audit and token-bind for Wave 1. Revisit at Wave 3 when component count grows.
Dedicated _layout route for /auth/ — rejected*: AuthShell composes in each page, flatter tree. TanStack Router layouts add depth without payoff for 5 similar pages.
Playwright E2E in M1 — rejected: Deferred to L6 Quality wave where visual regression + a11y audits + Lighthouse all consolidate. M1 integration tests cover business logic at service + route level.
OAuth flow with Authorization Code + PKCE for frontend — rejected: Overkill for first-party SPA on same origin. HttpOnly cookie + CSRF via SameSite=Lax is simpler + equally secure.
Self-hosted QR code generator — deferred: api.qrserver.com is sufficient for Wave 1; Layer 5 Infrastructure adds self-hosted qr service to eliminate external dependency.
Persist auth tokens in localStorage — rejected: HttpOnly cookies prevent XSS theft; localStorage is XSS-exposed. Never.
Full E2E Playwright with Chromium — deferred: L6 Quality is the right home for visual + a11y + E2E. M1 ships integration at API contract level.
Admin user-management UI (M12) in this commit — rejected: M12 is Wave 5 per plan. seed-admin CLI covers Wave 1 provisioning; UI comes with full admin surface.
Recovery-codes shown with no acknowledgment step — rejected: Explicit checkbox (“I’ve saved these”) nudges users to actually save them. Copy + download fallbacks maximize save rate.
Frontend integration tests with Vitest — deferred: Component-level Vitest tests for primitives (Button/Input/FormField) queued for L6; behavior already covered by integration tests at API level.
Testcontainers for Postgres fixture — considered: cleaner isolation but adds docker-in-docker complexity. Simple TCP probe + REQUIRES_DB marker lets dev use existing make up stack OR dedicated test DB.
Outcome
Pending
Related
- R-Same M1 Identity Pass 2
- r-same-m1-identity-backend-core-wave-1-pass-1
- r-dash-design-direction-freeze-checkpoint-3
- trpc-query-used-for-side-effects-breaks-idempotency-contract
- trpc-protected-procedure-is-authn-only-not-authz
- trpc-protected-procedure-insufficient-for-resource-authoriza
- auth-gate-alone-does-not-mean-authorization
- r-dash-postgres-creds-mismatch-pre-existing-not-wave3-regres
- r-dash-dashboard-revision-append-only-rollback-pattern
- r-dash-db-integration-tests-skip-on-postgres-creds-mismatch
- pytest-coverage-gate-skews-below-threshold-without-live-db
- db-free-test-coverage-drops-below-gate-without-postgres
- pytest-coverage-gate-misleading-without-live-db
- runwal-aws-account-798513555087-is-vendor-owned-midnight-dig