Skip to content

fix(auth)!: return AuthResponse.none when GoTrue returns no session or user - #1088

Open
grdsdev wants to merge 2 commits into
mainfrom
fix/sdk-1022-verifyotp-email-change-parity
Open

fix(auth)!: return AuthResponse.none when GoTrue returns no session or user#1088
grdsdev wants to merge 2 commits into
mainfrom
fix/sdk-1022-verifyotp-email-change-parity

Conversation

@grdsdev

@grdsdev grdsdev commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

verifyOTP(type: .emailChange) threw an uncaught DecodingError when GoTrue's /verify endpoint returned { msg, code } — the body sent for the first of the two confirmations required by a secure email change. AuthResponse could only decode a Session or a User, so any other shape crashed instead of returning a usable result. This brings the Swift SDK's auth response handling in line with the equivalent fix in supabase-js (supabase/supabase-js#2378, #2391).

Changes

  • Sources/Auth/Types.swift: AuthResponse gains a .none case for when neither Session nor User decodes. public var user is now User? instead of User so (session: nil, user: nil) is representable.
  • Tests/AuthTests/AuthResponseTests.swift / AuthClientTests.swift: added coverage for the {msg, code} email-change shape (new fixture email-change-single-confirmation.json) and for signUp() when confirmation is required (already worked correctly — this was investigated and found to be a pre-existing behavior, now with regression coverage using the previously-orphaned signup-response.json fixture).
  • Tests/IntegrationTests/AuthClientIntegrationTests.swift: updated the few .user access sites to handle the new optional.

Root cause

Sources/Auth/Types.swift:601-613 (pre-fix) — AuthResponse.init(from:) tried decoding Session, then User, and threw DecodingError.dataCorruptedError if neither succeeded. GoTrue's intermediate email-change confirmation response matches neither shape, so every call crashed instead of returning a null result.

Breaking change

AuthResponse.user changes from User (non-optional) to User?. Any code accessing response.user.someProperty directly needs to switch to optional chaining (response.user?.someProperty). This is necessary because the fix requires representing "neither session nor user was returned," which the previous non-optional user couldn't express.

Test plan

  • Reproduction test added: Tests/AuthTests/AuthResponseTests.swift (testEmailChangeSingleConfirmation) — confirmed it failed with the exact DecodingError before the fix
  • Client-level test: Tests/AuthTests/AuthClientTests.swift (testVerifyOTPForEmailChangeSingleConfirmation)
  • Regression test for signUp() confirmation-required flow: testSignUpConfirmationRequired / testSignUpWhenConfirmationRequired
  • Full suite green: make PLATFORM=IOS XCODEBUILD_ARGUMENT=test xcodebuild (all targets, 0 failures)
  • Tests/IntegrationTests package builds cleanly (swift build --build-tests)

Linear

Closes SDK-1022

…r user

verifyOTP(type: .emailChange) previously threw a DecodingError when
GoTrue's /verify endpoint returned the `{ msg, code }` body sent for the
first of the two confirmations required by a secure email change,
since AuthResponse could only decode a Session or a User.

AuthResponse gains a `.none` case for this shape, and `user` is now
`User?` instead of `User` so `(session: nil, user: nil)` is
representable.

BREAKING CHANGE: `AuthResponse.user` is now `User?` instead of `User`.
Update call sites that access `response.user` directly to handle the
optional (e.g. `response.user?.email`).

Linear: SDK-1022
@grdsdev
grdsdev requested a review from a team as a code owner July 7, 2026 08:50
…ne doc

Addresses code review feedback on #1088 (SDK-1022): the import ordering
in AuthClientTests.swift had regressed to a non-swift-format-canonical
layout, and the `.none` doc didn't make clear it's a catch-all for any
unrecognized response shape, not just the email-change confirmation case.
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 28853929393

Coverage increased (+0.2%) to 83.199%

Details

  • Coverage increased (+0.2%) from the base build.
  • Patch coverage: 1 uncovered change across 1 file (3 of 4 lines covered, 75.0%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
Sources/Auth/Types.swift 4 3 75.0%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 9398
Covered Lines: 7819
Line Coverage: 83.2%
Coverage Strength: 37.2 hits per line

💛 - Coveralls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants