fix: email-code sign-in never succeeds when the identifier differs in case - #444
Open
dbeattie71 wants to merge 2 commits into
Open
fix: email-code sign-in never succeeds when the identifier differs in case#444dbeattie71 wants to merge 2 commits into
dbeattie71 wants to merge 2 commits into
Conversation
The back end normalises an identifier — an email address is stored lower-cased — while the sign-in UI sends back exactly what the user typed. `attemptSignIn` compared the two with `!=`, so `Person@example.com` and the stored `person@example.com` read as two different users. When that happens the in-flight `SignIn` is discarded and a new one created on *every* submission. Creating it re-prepares the first factor, which sends a fresh code and invalidates the one already in the user's inbox, and the code they typed is then checked against a verification it does not belong to. The attempt fails with `form_code_incorrect`, and because each retry repeats the cycle it can never succeed. This is easy to hit on iOS, where the identifier field sets no `keyboardType`, `textCapitalization`, `autocorrect` or `autofillHints`, so the platform treats it as prose and autocorrect capitalises a correctly typed address as the user types it. Adds a regression test asserting no second `SignIn` is created when the submitted identifier differs from the stored one only by case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The identifier field set no `keyboardType`, `textCapitalization`, `autocorrect` or `autofillHints`, so the platform treated an email address as prose. On iOS that means autocorrect rewrites a correctly typed address as the user enters it — capitalising a local part that matches a proper noun in its dictionary, so `derek@…` becomes `Derek@…` without the user doing anything wrong. It also means a prose keyboard with no `@` key, and no saved credentials offered, because the platform has no hints to match on. `ClerkTextFormField` did not expose these at all, so no caller could opt in. They are now parameters, defaulting to the previous behaviour so no other field changes, and set explicitly on the identifier input. This is the other half of the sign-in failure fixed in the previous commit: the field corrupted the identifier, and `attemptSignIn` then rejected the value the field had corrupted. Note the email branch already knows it is the email branch — it sits under `Closeable(closed: identifierType.value.isPhoneNumber)`, with phone numbers handled by a separate widget — so an email keyboard here costs nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #443.
Email-code sign-in fails permanently when the submitted identifier differs from the stored one only by case.
attemptSignIncompares them with!=, and because the back end normalises identifiers while the panel sends back what the user typed, the guard discards the in-flightSignInand creates a new one on every submission — re-preparing the first factor, invalidating the code already emailed, and checking the user's code against a verification it does not belong to. Each retry repeats the cycle.The issue has the full HTTP traces from a device: the failing run creates, prepares and attempts inside 544 ms, while the succeeding run (same build, same device, one character's case different) carries a single
SignInacross the pause and completes.Changes
clerk_auth— compare identifiers case-insensitively. Given the back end normalises them, a case-sensitive comparison cannot be correct.clerk_flutter— stop the platform rewriting the identifier. The field set nokeyboardType,textCapitalization,autocorrectorautofillHints, so iOS treated an email address as prose and autocorrect capitalised correctly typed input as it was entered. In the report this came from, the user typed lower-case every time and iOS capitalised it every time.ClerkTextFormFielddid not expose these at all, so no caller could opt in. They are now parameters defaulting to the previous behaviour — no other field changes — and set explicitly on the identifier input. The email branch already sits underCloseable(closed: identifierType.value.isPhoneNumber)with phone numbers handled separately, so an email keyboard here does not conflict with phone support.Either change alone stops the reported failure. Together they fix the cause and the trigger.
Tests
sign_in_identifier_case_test.dart— asserts no secondSignInis created when the identifier differs only by case. Confirmed failing before the fix (Expected: empty, Actual: [MockHttpCall]) and passing after.clerk_identifier_input_test.dart— asserts the identifier field's input attributes.Suites green: 647 in
clerk_auth, 784 inclerk_flutter.Notes
The comparison is
toLowerCase()rather than a locale-aware fold, matching how the identifier is normalised server-side. Phone identifiers are unaffected — they contain no cased characters, so the comparison is unchanged for them.