Skip to content

fix(auth): let forgot-password reset work without the current password - #648

Merged
appflowy merged 1 commit into
mainfrom
fix/password-reset-without-current
Oct 19, 2026
Merged

appflowy merged 1 commit into
mainfrom
fix/password-reset-without-current

Conversation

@appflowy

@appflowy appflowy commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #647. On web, "Forgot password" could not set a new password. Two independent problems caused this:

  1. The reset form was unreachable. After the user entered the emailed code, afterAuth() redirected the recovery session to /app. fix: prevent first-login failure after logout #503 (forced-login loop fix) blocks every redirect to /login, and that also caught the reset form at /login?action=changePassword. Users landed in the workspace without setting a password. This has been happening since 2026-08-25.
    • Fix: afterAuth() now allows exactly /login?action=changePassword. /auth/callback and other login actions stay blocked. The action name now lives in sign_in.ts (PASSWORD_RESET_LOGIN_ACTION), and LOGIN_ACTION.CHANGE_PASSWORD reuses it.
  2. The reset request required the forgotten password. The form used POST /user/change-password. Once a user has chosen a password (is_non_default_password), GoTrue requires the current one there, and the reset failed with 422 incorrect_current_password.
    • Fix: the reset now sends PUT /gotrue/user {password}, which desktop already uses for its reset (sign_in_bloc.dart → PasswordHttpService.setupPassword). GoTrue's user update doesn't require the current password for sessions opened with an emailed code (amr: otp), even when UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD is on.

Settings → "Change password" (#647) still asks for the current password when GoTrue requires it.

Test plan

  • TZ=UTC npx jest src/application/session src/components/login src/components/app/settings src/pages …/gotrue-password.test.ts …/gotrue.test.ts: 31 suites, 295 tests pass
    • afterAuth reaches the reset form (relative and absolute URLs) and still blocks /auth/callback?action=changePassword and other login actions
    • resetPassword sends PUT /user {password} with no current password, and drops the session only on 401
    • The reset page calls resetPassword, not changePassword
  • pnpm type-check
  • Local GoTrue, throwaway user with the flag set, session opened with a recovery code (amr: otp):
    • Old POST /user/change-password → 422 incorrect_current_password
    • PUT /user → 200, and sign-in with the new password works
    • same_password and weak_password come back as 422s the web already maps
  • Browser (Playwright, local stack): forgot password → enter code → new-password form → "Password reset. Please login again" → sign-in with the new password returns 200. fix(settings): ask for the current password when changing it #647's settings flow passes too (setup, stale-flag recovery, Change password, wrong current, same password, success).

Server note (not changed here)

PUT /user doesn't check the current password for any session unless GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD is set. It isn't set locally. So the current-password check on /user/change-password stops the UI but isn't a security boundary.

🤖 Generated with Claude Code

Summary by Sourcery

Fix web password recovery so users can reach the reset form and set a new password without their forgotten password.

Bug Fixes:

  • Allow password-recovery sessions to reach the new-password form after entering an emailed reset code.
  • Reset forgotten passwords without requiring the current password, while preserving current-password validation for settings-based password changes.

Enhancements:

  • Centralize the password-reset login action and expose a dedicated authentication service operation for recovery password updates.

Tests:

  • Add coverage for recovery redirects, reset-password requests, unauthorized-session handling, and the login reset form.

Two problems stopped the web forgot-password flow from setting a new password:

- After the emailed code, afterAuth() sent the recovery session to /app.
  #503 blocks redirects to /login, which also blocked the reset form at
  /login?action=changePassword, so the new-password form never appeared.
- The reset form used POST /user/change-password. Once a user has chosen a
  password, GoTrue requires the current one there, which a user who forgot
  it cannot give (422 incorrect_current_password).

Let afterAuth() reach exactly the reset form, and reset through
PUT /user like desktop does. GoTrue does not require the current password
for sessions opened with an emailed code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Sorry @appflowy, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 5 hours and 52 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Reviewer's Guide

Fixes web forgot-password recovery by allowing the recovery session to reach the new-password form and updating the password through GoTrue’s OTP-compatible user endpoint, while retaining current-password behavior for authenticated settings changes.

Flow diagram for recovery redirect validation

flowchart LR
    A[Recovery session completes] --> B{Path is /login?action=changePassword?}
    B -->|Yes| C[Show new-password form]
    B -->|No| D{Authentication path?}
    D -->|Yes| E[Block redirect]
    D -->|No| F[Continue normal redirect]
Loading

File-Level Changes

Change Details Files
Route recovery sessions to the password-reset form while preserving authentication-route protections.
  • Define a shared reset action constant and reuse it across sign-in routing and login components.
  • Normalize route paths and allow only the exact /login?action=changePassword destination, including relative and absolute URLs.
  • Continue redirecting callback routes and unrelated login actions to /app.
  • Add coverage for allowed recovery URLs and blocked authentication destinations.
src/application/session/sign_in.ts
src/application/session/__tests__/sign_in.test.ts
src/components/login/const.ts
Reset forgotten passwords through GoTrue’s user update endpoint instead of the current-password flow.
  • Expose a resetPassword auth service method that sends PUT /user with only the new password.
  • Preserve the session for non-401 failures and invalidate the token only when the reset session is unauthorized.
  • Switch the login reset form to the new method while leaving settings password changes on changePassword.
  • Add HTTP and component tests covering the request, token invalidation, success, and error behavior.
src/application/services/domains/auth.ts
src/application/services/js-services/http/gotrue.ts
src/application/services/js-services/http/__tests__/gotrue-password.test.ts
src/components/login/ChangePassword.tsx
src/components/login/__tests__/ChangePassword.reset.test.tsx
src/components/login/__tests__/SignUpPassword.errors.test.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@appflowy
appflowy merged commit 4feb6ce into main Oct 19, 2026
23 checks passed
@appflowy
appflowy deleted the fix/password-reset-without-current branch October 19, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant