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

361 lines
23 KiB
Markdown

# 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