diff --git a/docs/superpowers/specs/2026-07-21-forgot-password-design.md b/docs/superpowers/specs/2026-07-21-forgot-password-design.md new file mode 100644 index 0000000..a06968d --- /dev/null +++ b/docs/superpowers/specs/2026-07-21-forgot-password-design.md @@ -0,0 +1,71 @@ +# Forgot Password Flow — Design Spec + +**Date:** 2026-07-21 +**Status:** Approved + +## Overview + +Add a self-service password reset flow so users can recover access without admin intervention. Uses Supabase Auth's built-in `resetPasswordForEmail` + PKCE code exchange via the existing `/api/auth/callback` route. + +## User Flow + +1. User visits `/ims/login`, clicks "Forgot password?" link +2. Redirected to `/ims/forgot-password` +3. Enters email → form calls `supabase.auth.resetPasswordForEmail(email, { redirectTo })` +4. Supabase sends reset email with a link containing a one-time code +5. User clicks link → browser hits `/ims/api/auth/callback?code=XXX&next=/auth/reset-password` +6. Existing callback exchanges code, creates recovery session, redirects to `/ims/auth/reset-password` +7. User enters new password + confirmation → `supabase.auth.updateUser({ password })` +8. On success, redirect to `/ims/login` with success message + +## Files Changed + +### Modified +- `components/auth/login-form.tsx` — add "Forgot password?" link below the Sign in button + +### New +- `app/(auth)/forgot-password/page.tsx` — public page, email input form +- `app/(auth)/reset-password/page.tsx` — public page (but requires recovery session), new password form + +## Architecture + +Both new pages live in the `(auth)` route group (public, no middleware auth redirect). The reset-password page is technically public but only functional with a valid Supabase recovery session in the browser cookie — without it, `updateUser` returns an error and the form shows a graceful message. + +No new API routes needed. No changes to `/api/auth/callback`. + +## Redirect URL + +`resetPasswordForEmail` redirectTo value: +``` +${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback?next=/auth/reset-password +``` + +This must be added to Supabase Auth → URL Configuration → Redirect URLs: +``` +http://64.176.82.100/ims/api/auth/callback +``` +(likely already present from existing auth setup — verify before deploying) + +## Validation + +**Forgot password form:** +- Email must be non-empty and valid format (client-side only) +- On submit: always show success message regardless of whether email exists (prevents user enumeration) + +**Reset password form:** +- Min 8 characters +- New password and confirm must match +- On Supabase error (expired/invalid session): show "Link expired — request a new one" with link back to `/forgot-password` + +## UI + +Matches existing login form styling: +- `max-w-sm` centered card +- Same border/rounded/focus Tailwind classes as `components/auth/login-form.tsx` +- Same error/success message pattern (`bg-red-50 border border-red-200 text-red-700` / green equivalent) + +## Out of Scope + +- Email template customisation (handled in Supabase dashboard) +- Rate limiting (Supabase enforces this on `resetPasswordForEmail`) +- Admin-triggered reset (already possible via Supabase dashboard)