Commit Graph

1 Commits

Author SHA1 Message Date
Tyler eb37df347e Fix admin password reset (Send Reset Email + Reset Password)
Deploy to route.crispygoat.com / deploy (push) Successful in 4m14s
Both buttons in /admin/users were broken:

- "Send Reset Email" called a no-op stub in
  src/actions/admin/users.ts that always returned an error.

- "Reset Password" called resetAdminPassword with a hard-coded
  'Tuxedo2026!' password, and the function itself queried a
  non-existent `users` table and called a non-existent
  `update_user_password` RPC (leftover Supabase-era code).

Rewritten against Neon Auth:

- sendPasswordResetEmail(email) — platform_admin-only action that
  calls auth.requestPasswordReset with the configured
  NEXT_PUBLIC_SITE_URL + '/reset-password' redirect. Always returns
  a clear success/error.

- resetAdminPassword(email) — platform_admin-only action that:
  1. Looks up neon_auth.user by email
  2. Generates a strong server-side random temp password
  3. Tries auth.admin.setUserPassword first (instant credential,
     returned to the UI for the platform admin to share)
  4. On FORBIDDEN / UNAUTHORIZED / INTERNAL_SERVER_ERROR (the common
     case — provision-admin.ts does not promote callers to
     role='admin' in Neon Auth), falls back to
     auth.requestPasswordReset, which sends a reset link the user
     can click to set their own password.
  5. On the privileged path, flips admin_users.must_change_password
     so the user is forced to pick a real password on next sign-in.

UI updated to handle the new response shape (success-with-temp-
password vs success-with-reset-email-sent) with a fourth modal
state.

Tests: 19 new unit tests across both paths cover authz, input
handling, the privileged happy path, the FORBIDDEN/UNAUTHORIZED/
network-throw fallback, USER_NOT_FOUND surfacing, and the
'both paths fail' case. Full suite: 110/113 (3 pre-existing
getAdminUser failures unchanged).
2026-06-17 12:22:34 -06:00