t-convex-nextjs-saas/.sisyphus/plans/dashboard-password-reset-flow.md
nxtkofi d41d4687ee feat(legal): add GDPR-compliant cookie consent banner
Add CookieBanner component with useCookieConsent hook, translations in EN/PL, and integration into root layout

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-02 16:02:13 +02:00

23 KiB

Dashboard Password Reset Flow

TL;DR

Summary: Add an authenticated password-change flow reachable from the dashboard, with the actual form hosted on a protected /settings page that follows the repo's existing server-page plus client-component auth pattern. Deliverables:

  • Dashboard entry affordance to account security settings
  • Protected /settings page with localized password-change UI
  • Better Auth changePassword integration with explicit success/error handling
  • Agent-executable manual QA evidence for auth protection, validation, and credential rotation Effort: Medium Parallel: YES - 2 waves Critical Path: 1 -> 3 -> 4 -> 5 -> 6

Context

Original Request

Implement a user-facing password reset flow from src/app/dashboard/page.tsx.

Interview Summary

  • User selected the authenticated in-session flow, not forgot-password by email.
  • Dashboard is the entry point, but the actual form should live on /settings.
  • Manual QA only for now; no test infrastructure setup in this slice.

Metis Review (gaps addressed)

  • Protect /settings explicitly instead of assuming auth.
  • Keep scope to password/security only; do not expand into general settings architecture.
  • Use Better Auth's existing changePassword flow instead of inventing custom backend plumbing.
  • Define concrete browser QA with fixed credentials and explicit redirect/session assertions.

Work Objectives

Core Objective

Provide a secure, authenticated password-change experience that users can reach from the dashboard and complete on a dedicated settings page.

Deliverables

  • Auth-protected src/app/settings/page.tsx
  • Dashboard entry UI from src/app/dashboard/page.tsx to /settings
  • Localized client password-change component under src/components/
  • Better Auth client submission flow with revokeOtherSessions: true
  • Updated translation files for all new strings
  • QA evidence captured for happy path and failure cases

Definition of Done (verifiable conditions with commands)

  • pnpm lint completes successfully
  • pnpm build completes successfully
  • Visiting /settings while unauthenticated redirects to sign-in with a callback back to /settings
  • A signed-in user can open /settings, submit a valid current/new password pair, and subsequently sign in with only the new password
  • Wrong current password and mismatched confirmation both fail with clear user feedback

Must Have

  • Protected /settings route with explicit redirect behavior
  • Dashboard affordance linking to /settings
  • Fields for currentPassword, newPassword, and confirmPassword
  • Client validation for required fields, confirmation match, and rejecting newPassword === currentPassword
  • Backend-driven password validation through Better Auth's configured policies, including HIBP plugin
  • Success/error feedback and loading state consistent with existing auth UI
  • New strings added to both messages/en.json and messages/pl.json

Must NOT Have (guardrails, AI slop patterns, scope boundaries)

  • No forgot-password email flow, token flow, reset page, or mail templates
  • No full settings IA, profile editor, preferences, or navigation redesign
  • No new test framework, Playwright project, or CI setup in this slice
  • No custom Convex mutation/server action for password change unless Better Auth client API proves unusable during execution
  • No route-protection behavior left implicit or undocumented

Verification Strategy

ZERO HUMAN INTERVENTION - all verification is agent-executed.

  • Test decision: none + existing repo has no test framework; use browser-driven QA and build/lint verification
  • QA policy: Every task includes agent-executed scenarios with exact credentials, selectors, and expected outcomes
  • Evidence: .sisyphus/evidence/task-{N}-{slug}.{ext}

Execution Strategy

Parallel Execution Waves

Target: 5-8 tasks per wave. <3 per wave (except final) = under-splitting. Extract shared dependencies as Wave-1 tasks for max parallelism.

Wave 1: 1) route protection and settings page scaffold, 2) dashboard entry affordance, 3) settings UI shell + translations Wave 2: 4) client validation and field UX, 5) Better Auth submission + session behavior, 6) polish and end-to-end manual QA capture

Dependency Matrix (full, all tasks)

  • 1 blocks 4, 5, 6
  • 2 is independent after route constants are confirmed; feeds 6 QA coverage
  • 3 blocks 4 and 5
  • 4 blocks 5 and 6
  • 5 blocks 6
  • 6 blocks final verification wave

Agent Dispatch Summary (wave -> task count -> categories)

  • Wave 1 -> 3 tasks -> unspecified-high, visual-engineering, visual-engineering
  • Wave 2 -> 3 tasks -> quick, unspecified-high, unspecified-high

TODOs

Implementation + Test = ONE task. Never separate. EVERY task MUST have: Agent Profile + Parallelization + QA Scenarios.

  • 1. Add protected /settings page scaffold

    What to do: Read the relevant Next.js App Router docs under node_modules/next/dist/docs/ before coding, then create src/app/settings/page.tsx as a server page that checks authentication with the existing server auth helpers from src/lib/auth-server.ts. If unauthenticated, redirect to /sign-in?callbackURL=/settings. If authenticated, render a dedicated client settings/password component and keep the page focused on security only. Must NOT do: Do not implement forgot-password here. Do not place the full form directly in src/app/settings/page.tsx. Do not create middleware or a broader settings layout in this slice.

    Recommended Agent Profile:

    • Category: unspecified-high - Reason: auth-aware App Router work with redirect behavior and server/client boundary decisions
    • Skills: [] - No special skill required beyond repo pattern matching
    • Omitted: playwright - UI automation belongs in QA, not scaffolding

    Parallelization: Can Parallel: YES | Wave 1 | Blocks: 4, 5, 6 | Blocked By: none

    References (executor has NO interview context - be exhaustive):

    • Pattern: src/app/sign-in/page.tsx - thin server page wrapper pattern already used for auth routes
    • Pattern: src/app/sign-up/page.tsx - same page composition pattern for route-level wrappers
    • Auth helper: src/lib/auth-server.ts - server-side auth utilities available for checking session/auth state
    • Route contract: src/lib/routes.ts - /settings already exists as a private route constant
    • Existing target: src/app/dashboard/page.tsx - current dashboard stub that will link into this route
    • External: node_modules/next/dist/docs/ - project instruction requires reading relevant Next.js docs before implementation

    Acceptance Criteria (agent-executable only):

    • src/app/settings/page.tsx exists and remains a server page wrapper rather than a client-heavy file
    • Unauthenticated access to /settings redirects to /sign-in?callbackURL=/settings
    • Authenticated access to /settings renders the dedicated password settings component
    • pnpm lint passes after the page scaffold is added

    QA Scenarios (MANDATORY - task incomplete without these):

    Scenario: Unauthenticated redirect from settings
      Tool: Playwright
      Steps: Open a fresh browser context; visit http://localhost:3000/settings directly.
      Expected: Browser lands on `/sign-in` and preserves a callback URL containing `/settings`.
      Evidence: .sisyphus/evidence/task-1-settings-redirect.png
    
    Scenario: Authenticated settings page render
      Tool: Playwright
      Steps: Sign up `changeflow@example.com` with password `OldPass123!`; sign in; visit http://localhost:3000/settings.
      Expected: The page renders a password/security card instead of redirecting away.
      Evidence: .sisyphus/evidence/task-1-settings-render.png
    

    Commit: YES | Message: add protected settings page scaffold and dashboard entry | Files: src/app/settings/page.tsx, src/lib/routes.ts (only if needed), related imports

  • 2. Add dashboard entry to account security

    What to do: Replace the dashboard stub with a minimal, intentional dashboard card or section that exposes a single clear affordance to account security settings. Link to /settings via existing route constants. Keep the page intentionally narrow: one CTA, one short description, no full settings UI embedded here. Must NOT do: Do not turn dashboard into a general settings page. Do not add unrelated profile, billing, or preferences UI.

    Recommended Agent Profile:

    • Category: visual-engineering - Reason: small UI composition task that must feel deliberate, not boilerplate
    • Skills: [] - Existing component library provides all primitives needed
    • Omitted: playwright - verification happens via QA scenario, not implementation

    Parallelization: Can Parallel: YES | Wave 1 | Blocks: 6 QA coverage only | Blocked By: none

    References (executor has NO interview context - be exhaustive):

    • Target page: src/app/dashboard/page.tsx - currently only a stub and should become the entry point
    • Route contract: src/lib/routes.ts - use the existing private route constant for settings
    • UI pattern: src/components/ui/card.tsx - use existing card primitives for a compact dashboard section
    • UI pattern: src/components/ui/button.tsx - use existing button variants for the CTA

    Acceptance Criteria (agent-executable only):

    • /dashboard contains a visible CTA linking to /settings
    • The CTA uses route constants rather than a duplicated hard-coded path
    • The dashboard change stays focused on password/security entry only
    • pnpm lint passes after the dashboard update

    QA Scenarios (MANDATORY - task incomplete without these):

    Scenario: Dashboard exposes security entry
      Tool: Playwright
      Steps: Sign in as `changeflow@example.com` with `OldPass123!`; open http://localhost:3000/dashboard; inspect the visible security/settings CTA.
      Expected: Clicking the CTA navigates to `/settings`.
      Evidence: .sisyphus/evidence/task-2-dashboard-entry.png
    
    Scenario: Dashboard does not embed the password form
      Tool: Playwright
      Steps: Load http://localhost:3000/dashboard while signed in.
      Expected: No `currentPassword`, `newPassword`, or `confirmPassword` input fields are present on the dashboard page itself.
      Evidence: .sisyphus/evidence/task-2-dashboard-no-form.png
    

    Commit: YES | Message: add protected settings page scaffold and dashboard entry | Files: src/app/dashboard/page.tsx

  • 3. Create the localized password settings component shell

    What to do: Create a dedicated client component for password change under src/components/settings/ and wire it into src/app/settings/page.tsx. Use the same card, field, button, spinner, and password input-group patterns already used in src/components/auth/AuthForm.tsx. Add all new translation keys to both locale files up front so no hard-coded strings remain. Must NOT do: Do not reuse AuthForm directly for password change. Do not leave untranslated labels, helper text, button copy, or toast messages.

    Recommended Agent Profile:

    • Category: visual-engineering - Reason: new client UI should blend with existing auth UI while staying scoped
    • Skills: [] - Existing component system is sufficient
    • Omitted: writing - copy additions are straightforward translation entries, not long-form docs

    Parallelization: Can Parallel: YES | Wave 1 | Blocks: 4, 5, 6 | Blocked By: 1

    References (executor has NO interview context - be exhaustive):

    • Pattern: src/components/auth/AuthForm.tsx - existing client auth form conventions for card layout, field composition, spinner, toast, and password visibility controls
    • UI primitives: src/components/ui/field.tsx, src/components/ui/input-group.tsx, src/components/ui/button.tsx, src/components/ui/spinner.tsx
    • Translation files: messages/en.json, messages/pl.json - extend with a dedicated settings/security namespace or equivalent flat keys
    • Existing i18n usage: src/components/auth/AuthForm.tsx - useTranslations(...) pattern

    Acceptance Criteria (agent-executable only):

    • A dedicated client component exists under src/components/settings/
    • /settings renders a single password/security card with localized heading, description, and button text
    • Both messages/en.json and messages/pl.json contain all strings needed for the new UI
    • No new hard-coded user-facing strings remain in the component

    QA Scenarios (MANDATORY - task incomplete without these):

    Scenario: Password settings shell renders expected fields
      Tool: Playwright
      Steps: Sign in; visit http://localhost:3000/settings; inspect the form.
      Expected: Inputs named `currentPassword`, `newPassword`, and `confirmPassword` are visible, plus a submit button.
      Evidence: .sisyphus/evidence/task-3-settings-shell.png
    
    Scenario: Localized shell does not regress rendering
      Tool: Playwright
      Steps: Load the page in the default locale; inspect heading, field labels, and submit button.
      Expected: All visible strings are rendered from translation data with no raw key names or empty labels.
      Evidence: .sisyphus/evidence/task-3-settings-i18n.png
    

    Commit: YES | Message: add password change form UI and localization | Files: src/components/settings/PasswordChangeCard.tsx, src/app/settings/page.tsx, messages/en.json, messages/pl.json

  • 4. Implement client-side validation and field UX

    What to do: Define a dedicated Zod schema for the password-change form that requires currentPassword, reuses defaultPasswordValidator() for newPassword, enforces confirmPassword === newPassword, and rejects newPassword === currentPassword. Provide inline field errors and disable duplicate submissions with a loading state. Keep password visibility UX consistent with the existing auth form pattern. Must NOT do: Do not add uppercase/number/special-character composition rules back into the client. Do not rely only on toasts for validation errors that belong inline.

    Recommended Agent Profile:

    • Category: quick - Reason: bounded form-schema and field-state work inside one component
    • Skills: [] - Existing validation patterns are enough
    • Omitted: ultrabrain - no deep algorithmic work needed

    Parallelization: Can Parallel: NO | Wave 2 | Blocks: 5, 6 | Blocked By: 1, 3

    References (executor has NO interview context - be exhaustive):

    • Validation helper: src/constants.ts - current shared minimum-length validator to reuse for newPassword
    • Pattern: src/components/auth/AuthForm.tsx - React Hook Form + Zod resolver usage and inline FieldError handling
    • UI primitives: src/components/ui/field.tsx, src/components/ui/input-group.tsx, src/components/ui/spinner.tsx

    Acceptance Criteria (agent-executable only):

    • Submitting mismatched newPassword and confirmPassword surfaces an inline error on the confirmation field
    • Submitting the same value for current and new password surfaces an inline error before any network request
    • Submitting an empty required field surfaces inline validation without a success toast
    • Submit button disables while the request is pending

    QA Scenarios (MANDATORY - task incomplete without these):

    Scenario: Mismatched confirmation is blocked locally
      Tool: Playwright
      Steps: Sign in; open `/settings`; fill `currentPassword=OldPass123!`, `newPassword=NewPass123!`, `confirmPassword=Mismatch123!`; submit.
      Expected: Inline error appears for `confirmPassword`; no network-driven success state is shown.
      Evidence: .sisyphus/evidence/task-4-confirm-mismatch.png
    
    Scenario: Reusing the same password is blocked locally
      Tool: Playwright
      Steps: Fill `currentPassword=OldPass123!`, `newPassword=OldPass123!`, `confirmPassword=OldPass123!`; submit.
      Expected: Inline error indicates the new password must differ from the current password.
      Evidence: .sisyphus/evidence/task-4-same-password.png
    

    Commit: YES | Message: add password change form UI and localization | Files: src/components/settings/PasswordChangeCard.tsx, related validation helpers only if needed

  • 5. Wire Better Auth password change submission

    What to do: Connect the settings form to Better Auth's client password-change API using the authenticated session. Submit currentPassword, newPassword, and revokeOtherSessions: true. On success, stay on /settings, clear sensitive fields, and show a localized success toast. On failure, surface the returned error cleanly without clearing the current user session. Must NOT do: Do not create a custom Convex mutation for password change unless the Better Auth client call is proven unusable. Do not redirect away from settings after success. Do not silently swallow backend errors.

    Recommended Agent Profile:

    • Category: unspecified-high - Reason: auth-sensitive submission flow with session behavior and backend error handling
    • Skills: [] - Existing Better Auth client is already configured in repo
    • Omitted: writing - this is behavior wiring, not docs work

    Parallelization: Can Parallel: NO | Wave 2 | Blocks: 6 | Blocked By: 1, 3, 4

    References (executor has NO interview context - be exhaustive):

    • Client auth entry: src/lib/auth-client.ts - Better Auth client already configured with Convex plugin
    • Existing auth pattern: src/components/auth/AuthForm.tsx - request pending state, toast handling, Better Auth client invocation patterns
    • Backend policy: convex/auth.ts - Better Auth email/password configuration with HIBP plugin and min/max length
    • Settings component: src/components/settings/PasswordChangeCard.tsx - task 3/4 output

    Acceptance Criteria (agent-executable only):

    • Valid submission calls Better Auth password-change API and succeeds for the signed-in user
    • Wrong current password yields a visible error state without logging the user out of the current session
    • Successful submission clears all password inputs and leaves the user on /settings
    • pnpm build passes after the integration is complete

    QA Scenarios (MANDATORY - task incomplete without these):

    Scenario: Wrong current password is rejected
      Tool: Playwright
      Steps: Sign in; open `/settings`; fill `currentPassword=WrongPass123!`, `newPassword=NewPass123!`, `confirmPassword=NewPass123!`; submit.
      Expected: Error feedback appears; the current session remains usable; revisiting `/dashboard` still works in the same tab.
      Evidence: .sisyphus/evidence/task-5-wrong-current-password.png
    
    Scenario: Valid password change succeeds
      Tool: Playwright
      Steps: Fill `currentPassword=OldPass123!`, `newPassword=NewPass123!`, `confirmPassword=NewPass123!`; submit.
      Expected: Success toast/message appears; fields clear; page remains on `/settings`.
      Evidence: .sisyphus/evidence/task-5-successful-change.png
    

    Commit: YES | Message: wire password change flow and verify session behavior | Files: src/components/settings/PasswordChangeCard.tsx, any minimal auth client touch-ups only if required

  • 6. Finalize credential-rotation behavior and capture manual QA evidence

    What to do: Validate the full user journey end to end: dashboard -> settings -> change password -> sign out -> sign back in with the new credential. Because revokeOtherSessions: true is the default for this slice, also verify in a second browser context that another active session becomes unauthorized after the password change. Capture screenshots/logs into the evidence paths referenced below. Must NOT do: Do not add automated test infrastructure or CI in order to satisfy this task. Do not leave QA as a vague manual checklist.

    Recommended Agent Profile:

    • Category: unspecified-high - Reason: auth/stateful browser verification across two sessions
    • Skills: [playwright] - Use browser automation to validate auth and session transitions precisely
    • Omitted: frontend-ui-ux - this task is verification-focused, not design-focused

    Parallelization: Can Parallel: NO | Wave 2 | Blocks: Final verification wave | Blocked By: 1, 2, 3, 4, 5

    References (executor has NO interview context - be exhaustive):

    • Entry page: src/app/dashboard/page.tsx
    • Protected page: src/app/settings/page.tsx
    • Form component: src/components/settings/PasswordChangeCard.tsx
    • Auth UI precedent: src/components/auth/AuthForm.tsx
    • Command surface: package.json - use existing pnpm dev, pnpm lint, and pnpm build

    Acceptance Criteria (agent-executable only):

    • After password change, signing in with OldPass123! fails and signing in with NewPass123! succeeds
    • A second active browser context is no longer authorized after the password change
    • Evidence files exist for redirect, validation failure, success state, old-password rejection, and second-session invalidation
    • pnpm lint and pnpm build both pass on the final implementation

    QA Scenarios (MANDATORY - task incomplete without these):

    Scenario: Old password fails and new password succeeds
      Tool: Playwright
      Steps: Complete the password change; sign out; attempt sign-in with `OldPass123!`; then attempt sign-in with `NewPass123!`.
      Expected: Old password sign-in fails; new password sign-in succeeds and returns to an authenticated page.
      Evidence: .sisyphus/evidence/task-6-old-vs-new-credential.png
    
    Scenario: Other sessions are revoked
      Tool: Playwright
      Steps: Open session A and session B signed in as the same user; change the password in session A; in session B navigate to `/dashboard` or refresh.
      Expected: Session B is redirected to sign-in or otherwise loses authenticated access.
      Evidence: .sisyphus/evidence/task-6-revoke-other-sessions.png
    

    Commit: YES | Message: wire password change flow and verify session behavior | Files: no net-new feature files expected beyond minimal polish; evidence output only

Final Verification Wave (MANDATORY - after ALL implementation tasks)

4 review agents run in PARALLEL. ALL must APPROVE. Present consolidated results to user and get explicit "okay" before completing. Do NOT auto-proceed after verification. Wait for user's explicit approval before marking work complete. Never mark F1-F4 as checked before getting user's okay. Rejection or user feedback -> fix -> re-run -> present again -> wait for okay.

  • F1. Plan Compliance Audit - oracle
  • F2. Code Quality Review - unspecified-high
  • F3. Real Manual QA - unspecified-high (+ playwright if UI)
  • F4. Scope Fidelity Check - deep

Commit Strategy

  • Commit 1: add protected settings page scaffold and dashboard entry
  • Commit 2: add password change form UI and localization
  • Commit 3: wire password change flow and verify session behavior

Success Criteria

  • The feature remains scoped to authenticated password change only
  • /dashboard clearly leads users into account security settings
  • /settings is inaccessible without authentication
  • Password change succeeds only with the correct current password and valid new password
  • QA evidence proves redirect, validation failure, success path, and new-credential sign-in behavior