Skip to content

fix(gotrue)!: emit userUpdated instead of signedIn when an email change is confirmed - #1664

Merged
spydon merged 4 commits into
mainfrom
worktree-1398
Aug 7, 2026
Merged

fix(gotrue)!: emit userUpdated instead of signedIn when an email change is confirmed#1664
spydon merged 4 commits into
mainfrom
worktree-1398

Conversation

@spydon

@spydon spydon commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

What

Confirming an email or phone change emitted AuthChangeEvent.signedIn, which made it indistinguishable from an actual sign in. It now emits AuthChangeEvent.userUpdated, the same event that requesting the change through updateUser() emits.

Closes #1398.

Why this is breaking

The change is silent: nothing stops compiling, the session is still saved and currentSession still updates, only the emitted event differs. Any onAuthStateChange listener that navigates or fetches on signedIn and relied on an email-change confirmation reaching it has to handle userUpdated as well. That is why it is landing on v3.

The three confirmation paths

Confirmation Before After
verifyOTP() with OtpType.emailChange or OtpType.phoneChange signedIn userUpdated
getSessionFromUrl() with an implicit type=email_change link signedIn userUpdated
exchangeCodeForSession() for a PKCE code from an email change signedIn userUpdated

The PKCE path needed a little more than a branch. exchangeCodeForSession() had no way to know what the code was issued for, so it always fell through to signedIn. updateUser() now persists userUpdated alongside the code verifier and the exchange reads it back, which is the same mechanism resetPasswordForEmail() already used for passwordRecovery. As a side effect AuthSessionUrlResponse.redirectType is 'userUpdated' for that flow instead of null, so the response can be branched on directly.

phoneChange is included next to emailChange because it is the same class of operation, and leaving it out would mean a confirmed phone change and a confirmed email change emit different events for no reason.

Note that supabase-js still emits SIGNED_IN here, so this is a deliberate divergence rather than a parity fix. The issue calls out the JS behaviour as wrong too.

Tests

  • packages/gotrue/test/otp_mock_test.dart: five new tests covering the emitted event for each relevant OtpType, plus one asserting updateUser() persists the event name with the code verifier.
  • packages/gotrue/test/client_test.dart: the implicit email_change test now expects userUpdated, and the live PKCE email-change test asserts both the emitted event and redirectType.
  • packages/supabase_flutter/test/deep_link_test.dart: the type=email_change deep-link test now waits for userUpdated.

Docs

New MIGRATION.md section under v2 to v3 with the before/after table and a listener snippet, and dartdoc on verifyOTP(), getSessionFromUrl() and exchangeCodeForSession() now states which event each emits.

Summary by CodeRabbit

  • Bug Fixes

    • Email and phone change confirmations now emit the userUpdated authentication event instead of signedIn.
    • Updated behavior applies consistently across OTP verification, email-change links, and PKCE flows.
    • PKCE responses now report redirectType as userUpdated.
    • Session persistence and currentSession updates remain unchanged.
  • Documentation

    • Added migration guidance describing the updated authentication event behavior.

…ge is confirmed

Confirming an email or phone change emitted signedIn, which made it
indistinguishable from an actual sign in. It now emits userUpdated, the
same event that requesting the change through updateUser emits.

This covers all three confirmation paths: verifyOTP with emailChange or
phoneChange, an implicit type=email_change link through
getSessionFromUrl, and a PKCE code through exchangeCodeForSession.
updateUser now persists the userUpdated event name alongside the code
verifier so the exchange can tell an email change apart from a sign in,
using the same mechanism resetPasswordForEmail already used for
passwordRecovery.

Closes #1398
@spydon
spydon requested a review from a team as a code owner August 6, 2026 14:38
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@spydon, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 8 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ba5052c7-872e-4ff2-8bba-dee808a76a07

📥 Commits

Reviewing files that changed from the base of the PR and between c6af1fb and 62c88ec.

📒 Files selected for processing (1)
  • packages/gotrue/lib/src/gotrue_client.dart
📝 Walkthrough

Walkthrough

Email and phone confirmation flows now emit AuthChangeEvent.userUpdated across OTP, implicit callback, and PKCE flows. PKCE responses expose redirectType: 'userUpdated'. Session persistence remains unchanged.

Changes

Authentication event mapping

Layer / File(s) Summary
Confirmation event flow
MIGRATION.md, packages/gotrue/lib/src/gotrue_client.dart
verifyOTP, implicit callbacks, and PKCE callbacks now emit userUpdated for email or phone changes. PKCE email-change challenges persist this event with the verifier. Migration guidance documents the new mapping and unchanged session handling.
Event and persistence validation
packages/gotrue/test/client_test.dart, packages/gotrue/test/otp_mock_test.dart, packages/supabase_flutter/test/deep_link_test.dart
Tests verify userUpdated events for OTP, implicit-token, and PKCE flows. PKCE tests also verify redirectType, and persistence tests verify the stored event and verifier.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant GoTrueClient
  participant AuthStateStream
  App->>GoTrueClient: Confirm email or phone change
  GoTrueClient->>AuthStateStream: Emit userUpdated
  GoTrueClient-->>App: Return session and redirectType userUpdated
Loading

Suggested labels: v3, auth

Suggested reviewers: tr00d

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary breaking change: confirmed email changes now emit userUpdated instead of signedIn.
Linked Issues check ✅ Passed The changes implement issue #1398 by emitting userUpdated for confirmed email changes across OTP, implicit-link, and PKCE flows.
Out of Scope Changes check ✅ Passed The changes remain focused on authentication event handling, related persistence, documentation, and regression tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-1398

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@MIGRATION.md`:
- Around line 151-153: Update the migration text describing the authentication
event so “sign in” is hyphenated as the noun “sign-in,” while preserving the
surrounding wording and meaning.

In `@packages/gotrue/test/client_test.dart`:
- Around line 841-852: Update the auth-state observation around pkceClient so
replayed signedIn and userUpdated events cannot be mistaken for the exchange
event: begin collecting events before signInWithPassword(), then assert the
complete expected event order including the event emitted by
exchangeCodeForSession(). Preserve the existing exchange response assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bed7d9db-8d8f-4659-92c8-839ef2b118ca

📥 Commits

Reviewing files that changed from the base of the PR and between 1ffa0de and 7d16fd6.

📒 Files selected for processing (5)
  • MIGRATION.md
  • packages/gotrue/lib/src/gotrue_client.dart
  • packages/gotrue/test/client_test.dart
  • packages/gotrue/test/otp_mock_test.dart
  • packages/supabase_flutter/test/deep_link_test.dart

Comment thread MIGRATION.md
Comment thread packages/gotrue/test/client_test.dart Outdated
spydon added 3 commits August 6, 2026 16:44
The PKCE email change test subscribed to onAuthStateChange after
updateUser had already emitted userUpdated. The stream replays its latest
event to new subscribers, so firstWhere resolved on that replayed event
and the assertion passed regardless of what exchangeCodeForSession
emitted.

Collect events from before the sign in instead and assert the full order,
which pins the exchange event to the last position. Verified to fail when
exchangeCodeForSession falls back to signedIn.
avoid-wildcard-cases-with-enums fired on both new switch expressions.

verifyOTP now enumerates every OtpType, so adding a value becomes a
compile error rather than silently emitting signedIn.

exchangeCodeForSession drops the switch entirely. The event name stored
with the code verifier is the event to emit, so falling back to signedIn
when none was stored says the same thing in one line.
@spydon
spydon merged commit 7a04241 into main Aug 7, 2026
34 checks passed
@spydon
spydon deleted the worktree-1398 branch August 7, 2026 08: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.

E-mail change should emit userUpdated instead of signedIn

2 participants