Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions Sources/Auth/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -589,26 +589,29 @@
case email
}

/// The response from sign-up and OTP-verification calls that may return either a session or a
/// user depending on whether email confirmation is required.
/// The response from sign-up and OTP-verification calls that may return a session, a user, or
/// neither, depending on whether email confirmation is required.
public enum AuthResponse: Codable, Hashable, Sendable {
/// A full session was created, meaning the user is immediately signed in.
case session(Session)

/// Only a user record was returned, meaning email confirmation is still pending.
case user(User)

/// Neither a session nor a user was returned. This is also the fallback for any response body
/// that doesn't match a ``Session`` or ``User`` shape. GoTrue returns this shape for
/// intermediate confirmation steps that don't carry user data, e.g. the first of the two
/// confirmations required for a secure email change.
case none

public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(Session.self) {
self = .session(value)
} else if let value = try? container.decode(User.self) {
self = .user(value)
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Data could not be decoded as any of the expected types (Session, User)."
)
self = .none
}
}

Expand All @@ -617,18 +620,22 @@
switch self {
case .session(let value): try container.encode(value)
case .user(let value): try container.encode(value)
case .none: try container.encodeNil()
}
}

/// The user in either case of the response.
public var user: User {
/// The user in either case of the response, or `nil` if neither a session nor a user was
/// returned.
public var user: User? {
switch self {
case .session(let session): session.user
case .user(let user): user
case .none: nil
}
}

/// The session, or `nil` if only a user was returned (confirmation pending).
/// The session, or `nil` if only a user was returned (confirmation pending) or neither was
/// returned.
public var session: Session? {
if case .session(let session) = self { return session }
return nil
Expand Down Expand Up @@ -684,7 +691,7 @@
self.phone = phone
self.password = password
self.nonce = nonce
self.emailChangeToken = emailChangeToken

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (legacy) (MACOS, 16.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (legacy) (MACOS, 16.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (legacy) (MAC_CATALYST, 16.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (legacy) (MAC_CATALYST, 16.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (macOS latest) (MACOS, 26.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (macOS latest) (MACOS, 26.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (legacy) (test, MACOS, 16.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (legacy) (test, MAC_CATALYST, 16.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (legacy) (IOS, 16.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (legacy) (IOS, 16.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (macOS latest) (IOS, 26.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (macOS latest) (IOS, 26.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 694 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (legacy) (test, IOS, 16.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.
self.data = data
}
}
Expand Down
48 changes: 48 additions & 0 deletions Tests/AuthTests/AuthClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,25 @@ final class AuthClientTests: XCTestCase {
)
}

func testSignUpWhenConfirmationRequired() async throws {
Mock(
url: clientURL.appendingPathComponent("signup"),
ignoreQuery: true,
statusCode: 200,
data: [.post: MockData.signUpConfirmationRequired]
).register()

let sut = makeSUT()

let response = try await sut.signUp(
email: "guilherme@grds.dev",
password: "the.pass"
)

XCTAssertNil(response.session)
XCTAssertNotNil(response.user)
}

func testSignInWithEmailAndPassword() async throws {
Mock(
url: clientURL.appendingPathComponent("token"),
Expand Down Expand Up @@ -1281,6 +1300,25 @@ final class AuthClientTests: XCTestCase {
)
}

func testVerifyOTPForEmailChangeSingleConfirmation() async throws {
Mock(
url: clientURL.appendingPathComponent("verify"),
ignoreQuery: true,
statusCode: 200,
data: [.post: MockData.emailChangeSingleConfirmation]
).register()

let sut = makeSUT()

let response = try await sut.verifyOTP(
tokenHash: "abc-def",
type: .emailChange
)

XCTAssertNil(response.session)
XCTAssertNil(response.user)
}

func testUpdateUser() async throws {
Mock(
url: clientURL.appendingPathComponent("user"),
Expand Down Expand Up @@ -3296,4 +3334,14 @@ enum MockData {
static let anonymousSignInResponse = try! Data(
contentsOf: Bundle.module.url(forResource: "anonymous-sign-in-response", withExtension: "json")!
)

static let signUpConfirmationRequired = try! Data(
contentsOf: Bundle.module.url(forResource: "signup-response", withExtension: "json")!
)

static let emailChangeSingleConfirmation = try! Data(
contentsOf: Bundle.module.url(
forResource: "email-change-single-confirmation", withExtension: "json"
)!
)
}
18 changes: 18 additions & 0 deletions Tests/AuthTests/AuthResponseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,22 @@ final class AuthResponseTests: XCTestCase {
)
XCTAssertNil(response.session)
}

func testSignUpConfirmationRequired() throws {
let response = try AuthClient.Configuration.jsonDecoder.decode(
AuthResponse.self,
from: json(named: "signup-response")
)
XCTAssertNil(response.session)
XCTAssertNotNil(response.user)
}

func testEmailChangeSingleConfirmation() throws {
let response = try AuthClient.Configuration.jsonDecoder.decode(
AuthResponse.self,
from: json(named: "email-change-single-confirmation")
)
XCTAssertNil(response.session)
XCTAssertNil(response.user)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"code": "200",
"msg": "Confirmation link accepted. Please proceed to confirm link sent to the other email"
}
10 changes: 5 additions & 5 deletions Tests/IntegrationTests/AuthClientIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ final class AuthClientIntegrationTests: XCTestCase {
)

XCTAssertNotNil(response.session)
XCTAssertEqual(response.user.email, email)
XCTAssertEqual(response.user.userMetadata["test"], 42)
XCTAssertEqual(response.user?.email, email)
XCTAssertEqual(response.user?.userMetadata["test"], 42)

try await authClient.signOut()

Expand Down Expand Up @@ -166,7 +166,7 @@ final class AuthClientIntegrationTests: XCTestCase {
let user = try await authClient.user(jwt: firstUserSession?.accessToken)

XCTAssertEqual(user.id, firstUserSession?.user.id)
XCTAssertNotEqual(user.id, secondUserSession.user.id)
XCTAssertNotEqual(user.id, secondUserSession.user?.id)
}

func testUpdateUser() async throws {
Expand All @@ -182,14 +182,14 @@ final class AuthClientIntegrationTests: XCTestCase {
let session = try await signUpIfNeededOrSignIn(email: mockEmail(), password: mockPassword())
let identities = try await authClient.userIdentities()
expectNoDifference(
session.user.identities?.map(\.identityId) ?? [],
session.user?.identities?.map(\.identityId) ?? [],
identities.map(\.identityId)
)
}

func testUnlinkIdentity_withOnlyOneIdentity() async throws {
let identities = try await signUpIfNeededOrSignIn(email: mockEmail(), password: mockPassword())
.user.identities
.user?.identities
let identity = try XCTUnwrap(identities?.first)

do {
Expand Down
Loading