feat(auth): post-login landing route resolution per context

login.vue is rewritten to consume useAuthStore.login()'s discriminated
union — no more direct apiClient calls or branching on raw API response
shapes. The page maps result.kind to UI/routing decisions only:

- mfa-required → swap to MfaChallengeCard with the typed payload
- authenticated → resolvePostLoginTarget() (?to= relative, else
  auth.resolveLandingRoute())
- must-set-password → forward-compatible placeholder route
- failed → field-level errors + rate_limit message branch

resolveLandingRoute() now returns a string path instead of
RouteLocationRaw — the typed router accepts string-paths cleanly,
removes the cast at every call site, and lets useAuthStore.spec.ts +
guards.spec.ts assert the resolved path directly.

A13-3 minimum precaution lives in a new utility:
src/utils/postLoginRedirect.ts. The relative-only check
(`startsWith('/') && !startsWith('//')`) rejects absolute, protocol-
relative, javascript:, and data: schemes. Full domain validation lands
in WS-3 PR-B2b.

6 vitest specs in utils/__tests__/postLoginRedirect.spec.ts cover the
six rejection / passthrough scenarios.

Test count 192 → 198. Lint + typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 21:40:32 +02:00
parent 209e0ef682
commit 38a94c78e9
7 changed files with 127 additions and 67 deletions

View File

@@ -246,7 +246,7 @@ describe('router guards (WS-3 PR-B2a)', () => {
() => {},
)
expect(result).toEqual({ path: '/portal/evenementen' })
expect(result).toBe('/portal/evenementen')
})
it('MFA setup gate redirects to /account-settings when mfaSetupRequired', async () => {