From 1c825380d3ef2bc8d4c6039d984b1281cbf3b713 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:08:21 -0500 Subject: [PATCH 1/3] [Auth] Migrate setFields behavior to Response initializer (#14023) --- .../Sources/Swift/Backend/AuthBackend.swift | 88 ++++++++++--------- .../Swift/Backend/AuthRPCResponse.swift | 7 +- .../Backend/RPC/CreateAuthURIResponse.swift | 2 +- .../Backend/RPC/DeleteAccountResponse.swift | 2 +- .../Backend/RPC/EmailLinkSignInResponse.swift | 2 +- .../Backend/RPC/GetAccountInfoResponse.swift | 2 +- .../RPC/GetOOBConfirmationCodeResponse.swift | 2 +- .../RPC/GetProjectConfigResponse.swift | 2 +- .../RPC/GetRecaptchaConfigResponse.swift | 2 +- .../FinalizeMFAEnrollmentResponse.swift | 2 +- .../Enroll/StartMFAEnrollmentResponse.swift | 2 +- .../SignIn/FinalizeMFASignInResponse.swift | 2 +- .../SignIn/StartMFASignInResponse.swift | 2 +- .../Unenroll/WithdrawMFAResponse.swift | 2 +- .../Backend/RPC/ResetPasswordResponse.swift | 2 +- .../Backend/RPC/RevokeTokenResponse.swift | 2 +- .../Backend/RPC/SecureTokenResponse.swift | 2 +- .../RPC/SendVerificationTokenResponse.swift | 2 +- .../Backend/RPC/SetAccountInfoResponse.swift | 2 +- .../RPC/SignInWithGameCenterResponse.swift | 2 +- .../Backend/RPC/SignUpNewUserResponse.swift | 2 +- .../Backend/RPC/VerifyAssertionResponse.swift | 2 +- .../RPC/VerifyCustomTokenResponse.swift | 2 +- .../Backend/RPC/VerifyPasswordResponse.swift | 2 +- .../RPC/VerifyPhoneNumberResponse.swift | 2 +- .../Swift/Backend/VerifyClientResponse.swift | 4 +- .../Tests/Unit/AuthBackendTests.swift | 4 +- 27 files changed, 76 insertions(+), 73 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 58fb6c4be80..ee0b57f8275 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -251,66 +251,74 @@ class AuthBackend: AuthBackendProtocol { } dictionary = decodedDictionary - var response = T.Response() + let responseResult = Result { + try T.Response(dictionary: dictionary) + } // At this point we either have an error with successfully decoded // details in the body, or we have a response which must pass further // validation before we know it's truly successful. We deal with the // case where we have an error with successfully decoded error details // first: - if error != nil { - if let errorDictionary = dictionary["error"] as? [String: AnyHashable] { - if let errorMessage = errorDictionary["message"] as? String { - if let clientError = Self.clientError( - withServerErrorMessage: errorMessage, - errorDictionary: errorDictionary, - response: response, - error: error - ) { - throw clientError + switch responseResult { + case let .success(response): + try propagateError(error, dictionary: dictionary, response: response) + // In case returnIDPCredential of a verifyAssertion request is set to + // @YES, the server may return a 200 with a response that may contain a + // server error. + if let verifyAssertionRequest = request as? VerifyAssertionRequest { + if verifyAssertionRequest.returnIDPCredential { + if let errorMessage = dictionary["errorMessage"] as? String { + if let clientError = Self.clientError( + withServerErrorMessage: errorMessage, + errorDictionary: dictionary, + response: response, + error: error + ) { + throw clientError + } } } - // Not a message we know, return the message directly. - throw AuthErrorUtils.unexpectedErrorResponse( - deserializedResponse: errorDictionary, - underlyingError: error - ) } - // No error message at all, return the decoded response. + return response + case let .failure(failure): + try propagateError(error, dictionary: dictionary, response: nil) throw AuthErrorUtils - .unexpectedErrorResponse(deserializedResponse: dictionary, underlyingError: error) + .RPCResponseDecodingError(deserializedResponse: dictionary, underlyingError: failure) } + } - // Finally, we try to populate the response object with the JSON values. - do { - try response.setFields(dictionary: dictionary) - } catch { - throw AuthErrorUtils - .RPCResponseDecodingError(deserializedResponse: dictionary, underlyingError: error) + private func propagateError(_ error: Error?, dictionary: [String: AnyHashable], + response: AuthRPCResponse?) throws { + guard let error else { + return } - // In case returnIDPCredential of a verifyAssertion request is set to - // @YES, the server may return a 200 with a response that may contain a - // server error. - if let verifyAssertionRequest = request as? VerifyAssertionRequest { - if verifyAssertionRequest.returnIDPCredential { - if let errorMessage = dictionary["errorMessage"] as? String { - if let clientError = Self.clientError( - withServerErrorMessage: errorMessage, - errorDictionary: dictionary, - response: response, - error: error - ) { - throw clientError - } + + if let errorDictionary = dictionary["error"] as? [String: AnyHashable] { + if let errorMessage = errorDictionary["message"] as? String { + if let clientError = Self.clientError( + withServerErrorMessage: errorMessage, + errorDictionary: errorDictionary, + response: response, + error: error + ) { + throw clientError } } + // Not a message we know, return the message directly. + throw AuthErrorUtils.unexpectedErrorResponse( + deserializedResponse: errorDictionary, + underlyingError: error + ) } - return response + // No error message at all, return the decoded response. + throw AuthErrorUtils + .unexpectedErrorResponse(deserializedResponse: dictionary, underlyingError: error) } private static func clientError(withServerErrorMessage serverErrorMessage: String, errorDictionary: [String: Any], - response: AuthRPCResponse, + response: AuthRPCResponse?, error: Error?) -> Error? { let split = serverErrorMessage.split(separator: ":") let shortErrorMessage = split.first?.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift b/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift index 0b7ea8a3a52..00d302e5775 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift @@ -15,14 +15,11 @@ import Foundation protocol AuthRPCResponse: Sendable { - /// Bare initializer for a response. - init() - - /// Sets the response instance from the decoded JSON response. + /// Initializes the response instance from the decoded JSON response. /// - Parameter dictionary: The dictionary decoded from HTTP JSON response. /// - Parameter error: An out field for an error which occurred constructing the request. /// - Returns: Whether the operation was successful or not. - mutating func setFields(dictionary: [String: AnyHashable]) throws + init(dictionary: [String: AnyHashable]) throws /// This optional method allows response classes to create client errors given a short error /// message and a detail error message from the server. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift index 019a92f4b90..66b1ad95023 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift @@ -35,7 +35,7 @@ struct CreateAuthURIResponse: AuthRPCResponse { /// A list of sign-in methods available for the passed identifier. var signinMethods: [String] = [] - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { providerID = dictionary["providerId"] as? String authURI = dictionary["authUri"] as? String registered = dictionary["registered"] as? Bool ?? false diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift index 4b6802dafb3..d3a34371caa 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift @@ -18,5 +18,5 @@ import Foundation /// /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/deleteAccount struct DeleteAccountResponse: AuthRPCResponse { - mutating func setFields(dictionary: [String: AnyHashable]) throws {} + init(dictionary: [String: AnyHashable]) throws {} } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift index 608c38237cc..0fb80a8f9c2 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift @@ -40,7 +40,7 @@ struct EmailLinkSignInResponse: AuthRPCResponse, AuthMFAResponse { /// Info on which multi-factor authentication providers are enabled. private(set) var mfaInfo: [AuthProtoMFAEnrollment]? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { email = dictionary["email"] as? String idToken = dictionary["idToken"] as? String isNewUser = dictionary["isNewUser"] as? Bool ?? false diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift index e0f9c463959..4fb5795bcd5 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift @@ -139,7 +139,7 @@ struct GetAccountInfoResponse: AuthRPCResponse { /// The requested users' profiles. var users: [Self.User]? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { guard let usersData = dictionary["users"] as? [[String: AnyHashable]] else { throw AuthErrorUtils.unexpectedResponse(deserializedResponse: dictionary) } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift index 91721fd659e..dcfba9d5212 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift @@ -19,7 +19,7 @@ private let kOOBCodeKey = "oobCode" struct GetOOBConfirmationCodeResponse: AuthRPCResponse { var OOBCode: String? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { OOBCode = dictionary[kOOBCodeKey] as? String } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift index b9fb18771b7..65091a46c7e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift @@ -19,7 +19,7 @@ struct GetProjectConfigResponse: AuthRPCResponse { var authorizedDomains: [String]? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { projectID = dictionary["projectId"] as? String if let authorizedDomains = dictionary["authorizedDomains"] as? String, let data = authorizedDomains.data(using: .utf8) { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift index b62bd84a695..327ff3751a2 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift @@ -18,7 +18,7 @@ struct GetRecaptchaConfigResponse: AuthRPCResponse { private(set) var recaptchaKey: String? private(set) var enforcementState: [[String: String]]? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { recaptchaKey = dictionary["recaptchaKey"] as? String enforcementState = dictionary["recaptchaEnforcementState"] as? [[String: String]] } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift index bdd3ce89d31..595e3b45952 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift @@ -21,7 +21,7 @@ struct FinalizeMFAEnrollmentResponse: AuthRPCResponse { private(set) var phoneSessionInfo: AuthProtoFinalizeMFAPhoneResponseInfo? private(set) var totpSessionInfo: AuthProtoFinalizeMFATOTPEnrollmentResponseInfo? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift index dadbb9a15be..7c91b54227c 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift @@ -19,7 +19,7 @@ struct StartMFAEnrollmentResponse: AuthRPCResponse { private(set) var phoneSessionInfo: AuthProtoStartMFAPhoneResponseInfo? private(set) var totpSessionInfo: AuthProtoStartMFATOTPEnrollmentResponseInfo? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { if let data = dictionary["phoneSessionInfo"] as? [String: AnyHashable] { phoneSessionInfo = AuthProtoStartMFAPhoneResponseInfo(dictionary: data) } else if let data = dictionary["totpSessionInfo"] as? [String: AnyHashable] { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift index 032dab3ff86..850a969e6cd 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift @@ -19,7 +19,7 @@ struct FinalizeMFASignInResponse: AuthRPCResponse { var IDToken: String? var refreshToken: String? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { IDToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift index cc597172010..291821c58f9 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift @@ -18,7 +18,7 @@ import Foundation struct StartMFASignInResponse: AuthRPCResponse { var responseInfo: AuthProtoStartMFAPhoneResponseInfo? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { if let data = dictionary["phoneResponseInfo"] as? [String: AnyHashable] { responseInfo = AuthProtoStartMFAPhoneResponseInfo(dictionary: data) } else { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift index 4744a6965ee..27bcd0d32e4 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift @@ -18,7 +18,7 @@ struct WithdrawMFAResponse: AuthRPCResponse { var idToken: String? var refreshToken: String? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift index 22cb703e5ad..1744dfc34cf 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift @@ -34,7 +34,7 @@ struct ResetPasswordResponse: AuthRPCResponse { /// The type of request as returned by the backend. var requestType: String? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { email = dictionary["email"] as? String requestType = dictionary["requestType"] as? String verifiedEmail = dictionary["newEmail"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift index 7e6bf0e7378..900959ccbe1 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift @@ -15,7 +15,7 @@ import Foundation struct RevokeTokenResponse: AuthRPCResponse { - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { // Nothing to set or throw. } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift index 86291a60015..44409ba92d4 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift @@ -37,7 +37,7 @@ struct SecureTokenResponse: AuthRPCResponse { var expectedKind: String? { nil } - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { refreshToken = dictionary[kRefreshTokenKey] as? String self.accessToken = dictionary[kAccessTokenKey] as? String idToken = dictionary[kIDTokenKey] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift index f9e6872cfcb..7b1815c5925 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift @@ -17,7 +17,7 @@ import Foundation struct SendVerificationCodeResponse: AuthRPCResponse { var verificationID: String? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { verificationID = dictionary["sessionInfo"] as? String } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift index c1b932b83c2..0a036d95195 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift @@ -60,7 +60,7 @@ struct SetAccountInfoResponse: AuthRPCResponse { /// The refresh token from Secure Token Service. var refreshToken: String? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { email = dictionary["email"] as? String displayName = dictionary["displayName"] as? String idToken = dictionary["idToken"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift index d291384ff49..cc81610b17b 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift @@ -25,7 +25,7 @@ struct SignInWithGameCenterResponse: AuthRPCResponse { var isNewUser: Bool = false var displayName: String? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String localID = dictionary["localId"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift index 7b25c6a6f8c..21f359b0f1a 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift @@ -26,7 +26,7 @@ struct SignUpNewUserResponse: AuthRPCResponse { /// The refresh token from Secure Token Service. var refreshToken: String? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String if let approximateExpirationDate = dictionary["expiresIn"] as? String { self diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift index c031aa22c43..9caa9d38e35 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift @@ -132,7 +132,7 @@ struct VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { private(set) var mfaInfo: [AuthProtoMFAEnrollment]? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { federatedID = dictionary["federatedId"] as? String providerID = dictionary["providerId"] as? String localID = dictionary["localId"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift index 5c66bf6ba74..9cf1ec0924f 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift @@ -30,7 +30,7 @@ struct VerifyCustomTokenResponse: AuthRPCResponse { /// Flag indicating that the user signing in is a new user and not a returning user. var isNewUser: Bool = false - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String if let dateString = dictionary["expiresIn"] as? NSString { approximateExpirationDate = Date(timeIntervalSinceNow: dateString.doubleValue) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift index ba93ac1e0a5..22a3d87e958 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift @@ -51,7 +51,7 @@ struct VerifyPasswordResponse: AuthRPCResponse, AuthMFAResponse { private(set) var mfaInfo: [AuthProtoMFAEnrollment]? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { localID = dictionary["localId"] as? String email = dictionary["email"] as? String displayName = dictionary["displayName"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift index b5efb964c73..a187824b7dd 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift @@ -43,7 +43,7 @@ struct VerifyPhoneNumberResponse: AuthRPCResponse { nil } - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String isNewUser = (dictionary["isNewUser"] as? Bool) ?? false diff --git a/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift b/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift index 683ed33a01f..a468197948f 100644 --- a/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift @@ -15,15 +15,13 @@ import Foundation struct VerifyClientResponse: AuthRPCResponse { - init() {} - /// Receipt that the APNS token was successfully validated with APNS. private(set) var receipt: String? /// The date after which delivery of the silent push notification is considered to have failed. private(set) var suggestedTimeOutDate: Date? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { receipt = dictionary["receipt"] as? String let suggestedTimeout = dictionary["suggestedTimeout"] if let string = suggestedTimeout as? String, diff --git a/FirebaseAuth/Tests/Unit/AuthBackendTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendTests.swift index a7a484a7d5d..b163d34ea68 100644 --- a/FirebaseAuth/Tests/Unit/AuthBackendTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthBackendTests.swift @@ -713,7 +713,7 @@ class AuthBackendTests: RPCBaseTests { private struct FakeResponse: AuthRPCResponse { var receivedValue: String? - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { receivedValue = dictionary["TestKey"] as? String } } @@ -739,7 +739,7 @@ class AuthBackendTests: RPCBaseTests { } private struct FakeDecodingErrorResponse: AuthRPCResponse { - mutating func setFields(dictionary: [String: AnyHashable]) throws { + init(dictionary: [String: AnyHashable]) throws { throw NSError(domain: "dummy", code: -1) } } From 3e39b0280b18f4b7e88279db7732ab8553106789 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:51:37 -0500 Subject: [PATCH 2/3] [Auth] Remove AuthBackend singleton (#14006) --- FirebaseAuth/Sources/Swift/Auth/Auth.swift | 67 +++++++------ .../Swift/AuthProvider/OAuthProvider.swift | 2 +- .../AuthProvider/PhoneAuthProvider.swift | 12 +-- .../Sources/Swift/Backend/AuthBackend.swift | 16 +-- .../Backend/RPC/SecureTokenRequest.swift | 2 +- .../Swift/MultiFactor/MultiFactor.swift | 6 +- .../MultiFactor/MultiFactorResolver.swift | 2 +- .../TOTP/TOTPMultiFactorGenerator.swift | 7 +- .../SystemService/SecureTokenService.swift | 22 +++-- FirebaseAuth/Sources/Swift/User/User.swift | 71 ++++++++------ .../Swift/User/UserProfileUpdate.swift | 22 ++--- .../Utilities/AuthRecaptchaVerifier.swift | 8 +- .../Swift/Utilities/AuthWebUtils.swift | 5 +- .../ViewControllers/AuthViewController.swift | 4 +- .../Tests/Unit/AuthBackendTests.swift | 36 +++---- FirebaseAuth/Tests/Unit/AuthTests.swift | 3 +- .../Tests/Unit/CreateAuthURITests.swift | 4 +- .../Tests/Unit/DeleteAccountTests.swift | 2 +- .../Tests/Unit/EmailLinkSignInTests.swift | 6 +- .../Tests/Unit/GetAccountInfoTests.swift | 2 +- .../Unit/GetOOBConfirmationCodeTests.swift | 4 +- .../Tests/Unit/GetProjectConfigTests.swift | 2 +- .../Tests/Unit/GetRecaptchaConfigTests.swift | 4 +- .../Tests/Unit/OAuthProviderTests.swift | 4 + .../Tests/Unit/PhoneAuthProviderTests.swift | 2 +- FirebaseAuth/Tests/Unit/RPCBaseTests.swift | 9 +- .../Tests/Unit/ResetPasswordTests.swift | 2 +- .../Tests/Unit/RevokeTokenTests.swift | 2 +- .../Unit/SendVerificationCodeTests.swift | 2 +- .../Tests/Unit/SetAccountInfoTests.swift | 2 +- .../Unit/SignInWithGameCenterTests.swift | 2 +- .../Tests/Unit/SignUpNewUserTests.swift | 2 +- FirebaseAuth/Tests/Unit/UserTests.swift | 97 ++++++++++--------- .../Tests/Unit/VerifyAssertionTests.swift | 4 +- .../Tests/Unit/VerifyClientTests.swift | 2 +- .../Tests/Unit/VerifyCustomTokenTests.swift | 2 +- .../Tests/Unit/VerifyPasswordTests.swift | 2 +- .../Tests/Unit/VerifyPhoneNumberTests.swift | 4 +- 38 files changed, 236 insertions(+), 211 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/Auth/Auth.swift b/FirebaseAuth/Sources/Swift/Auth/Auth.swift index 19e8029974b..e5ccdaafac7 100644 --- a/FirebaseAuth/Sources/Swift/Auth/Auth.swift +++ b/FirebaseAuth/Sources/Swift/Auth/Auth.swift @@ -123,11 +123,12 @@ extension Auth: AuthInterop { return } // Call back with current user token. - currentUser.internalGetToken(forceRefresh: forceRefresh) { token, error in - DispatchQueue.main.async { - callback(token, error) + currentUser + .internalGetToken(forceRefresh: forceRefresh, backend: strongSelf.backend) { token, error in + DispatchQueue.main.async { + callback(token, error) + } } - } } } @@ -292,7 +293,7 @@ extension Auth: AuthInterop { requestConfiguration: self.requestConfiguration) Task { do { - let response = try await AuthBackend.call(with: request) + let response = try await self.backend.call(with: request) Auth.wrapMainAsync(callback: completion, withParam: response.signinMethods, error: nil) } catch { Auth.wrapMainAsync(callback: completion, withParam: nil, error: error) @@ -395,7 +396,7 @@ extension Auth: AuthInterop { let response = try await injectRecaptcha(request: request, action: AuthRecaptchaAction.signInWithPassword) #else - let response = try await AuthBackend.call(with: request) + let response = try await backend.call(with: request) #endif return try await completeSignIn( withAccessToken: response.idToken, @@ -709,7 +710,7 @@ extension Auth: AuthInterop { let request = SignUpNewUserRequest(requestConfiguration: self.requestConfiguration) Task { do { - let response = try await AuthBackend.call(with: request) + let response = try await self.backend.call(with: request) let user = try await self.completeSignIn( withAccessToken: response.idToken, accessTokenExpirationDate: response.approximateExpirationDate, @@ -771,7 +772,7 @@ extension Auth: AuthInterop { requestConfiguration: self.requestConfiguration) Task { do { - let response = try await AuthBackend.call(with: request) + let response = try await self.backend.call(with: request) let user = try await self.completeSignIn( withAccessToken: response.idToken, accessTokenExpirationDate: response.approximateExpirationDate, @@ -881,7 +882,7 @@ extension Auth: AuthInterop { if let inResponse { response = inResponse } else { - response = try await AuthBackend.call(with: request) + response = try await self.backend.call(with: request) } let user = try await self.completeSignIn( withAccessToken: response.idToken, @@ -993,7 +994,7 @@ extension Auth: AuthInterop { requestConfiguration: self.requestConfiguration) Task { do { - let response = try await AuthBackend.call(with: request) + let response = try await self.backend.call(with: request) let operation = ActionCodeInfo.actionCodeOperation(forRequestType: response.requestType) guard let email = response.email else { @@ -1433,7 +1434,7 @@ extension Auth: AuthInterop { /// complete, or fails. Invoked asynchronously on the main thread in the future. @objc open func revokeToken(withAuthorizationCode authorizationCode: String, completion: ((Error?) -> Void)? = nil) { - currentUser?.internalGetToken { idToken, error in + currentUser?.internalGetToken(backend: backend) { idToken, error in if let error { Auth.wrapMainAsync(completion, error) return @@ -1613,7 +1614,9 @@ extension Auth: AuthInterop { // MARK: Internal methods - init(app: FirebaseApp, keychainStorageProvider: AuthKeychainStorage = AuthKeychainStorageReal()) { + init(app: FirebaseApp, + keychainStorageProvider: AuthKeychainStorage = AuthKeychainStorageReal(), + backend: AuthBackend = AuthBackend(rpcIssuer: AuthBackendRPCIssuer())) { Auth.setKeychainServiceNameForApp(app) self.app = app mainBundleUrlTypes = Bundle.main @@ -1638,6 +1641,7 @@ extension Auth: AuthInterop { auth: nil, heartbeatLogger: app.heartbeatLogger, appCheck: appCheck) + self.backend = backend super.init() requestConfiguration.auth = self @@ -1911,17 +1915,18 @@ extension Auth: AuthInterop { return } let uid = strongSelf.currentUser?.uid - strongSelf.currentUser?.internalGetToken(forceRefresh: true) { token, error in - if strongSelf.currentUser?.uid != uid { - return - } - if error != nil { - // Kicks off exponential back off logic to retry failed attempt. Starts with one minute - // delay (60 seconds) if this is the first failed attempt. - let rescheduleDelay = retry ? min(delay * 2, 16 * 60) : 60 - strongSelf.scheduleAutoTokenRefresh(withDelay: rescheduleDelay, retry: true) + strongSelf.currentUser? + .internalGetToken(forceRefresh: true, backend: strongSelf.backend) { token, error in + if strongSelf.currentUser?.uid != uid { + return + } + if error != nil { + // Kicks off exponential back off logic to retry failed attempt. Starts with one minute + // delay (60 seconds) if this is the first failed attempt. + let rescheduleDelay = retry ? min(delay * 2, 16 * 60) : 60 + strongSelf.scheduleAutoTokenRefresh(withDelay: rescheduleDelay, retry: true) + } } - } } } @@ -2075,7 +2080,7 @@ extension Auth: AuthInterop { requestConfiguration: requestConfiguration) request.autoCreate = !isReauthentication credential.prepare(request) - let response = try await AuthBackend.call(with: request) + let response = try await backend.call(with: request) if response.needConfirmation { let email = response.email let credential = OAuthCredential(withVerifyAssertionResponse: response) @@ -2114,7 +2119,7 @@ extension Auth: AuthInterop { phoneNumber: phoneNumber, operation: operation, requestConfiguration: requestConfiguration) - return try await AuthBackend.call(with: request) + return try await backend.call(with: request) case let .verification(verificationID, code): guard verificationID.count > 0 else { throw AuthErrorUtils.missingVerificationIDError(message: nil) @@ -2126,7 +2131,7 @@ extension Auth: AuthInterop { verificationCode: code, operation: operation, requestConfiguration: requestConfiguration) - return try await AuthBackend.call(with: request) + return try await backend.call(with: request) } } #endif @@ -2152,7 +2157,7 @@ extension Auth: AuthInterop { timestamp: credential.timestamp, displayName: credential.displayName, requestConfiguration: requestConfiguration) - let response = try await AuthBackend.call(with: request) + let response = try await backend.call(with: request) let user = try await completeSignIn(withAccessToken: response.idToken, accessTokenExpirationDate: response .approximateExpirationDate, @@ -2184,7 +2189,7 @@ extension Auth: AuthInterop { let request = EmailLinkSignInRequest(email: email, oobCode: actionCode, requestConfiguration: requestConfiguration) - let response = try await AuthBackend.call(with: request) + let response = try await backend.call(with: request) let user = try await completeSignIn(withAccessToken: response.idToken, accessTokenExpirationDate: response .approximateExpirationDate, @@ -2242,7 +2247,7 @@ extension Auth: AuthInterop { private func wrapAsyncRPCTask(_ request: any AuthRPCRequest, _ callback: ((Error?) -> Void)?) { Task { do { - let _ = try await AuthBackend.call(with: request) + let _ = try await self.backend.call(with: request) Auth.wrapMainAsync(callback, nil) } catch { Auth.wrapMainAsync(callback, error) @@ -2294,7 +2299,7 @@ extension Auth: AuthInterop { action: action) } else { do { - return try await AuthBackend.call(with: request) + return try await backend.call(with: request) } catch { let nsError = error as NSError if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? NSError, @@ -2313,7 +2318,7 @@ extension Auth: AuthInterop { } } } - return try await AuthBackend.call(with: request) + return try await backend.call(with: request) } #endif @@ -2330,6 +2335,8 @@ extension Auth: AuthInterop { /// Auth's backend. var requestConfiguration: AuthRequestConfiguration + let backend: AuthBackend + #if os(iOS) /// The manager for APNs tokens used by phone number auth. diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift index 01437d49891..b8cca1f5fca 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift @@ -413,7 +413,7 @@ import Foundation private func getHeadfulLiteUrl(eventID: String, sessionID: String) async throws -> URL? { let authDomain = try await AuthWebUtils - .fetchAuthDomain(withRequestConfiguration: auth.requestConfiguration) + .fetchAuthDomain(withRequestConfiguration: auth.requestConfiguration, backend: auth.backend) let bundleID = Bundle.main.bundleIdentifier let clientID = auth.app?.options.clientID let appID = auth.app?.options.googleAppID diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift index 32d15499f74..2a1de385aa4 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift @@ -217,7 +217,7 @@ import Foundation .requestConfiguration) do { - let response = try await AuthBackend.call(with: request) + let response = try await auth.backend.call(with: request) return response.verificationID } catch { return try await handleVerifyErrorWithRetry(error: error, @@ -245,7 +245,7 @@ import Foundation requestConfiguration: auth.requestConfiguration ) - let response = try await AuthBackend.call(with: request) + let response = try await auth.backend.call(with: request) return response.verificationID } guard let session else { @@ -263,7 +263,7 @@ import Foundation let request = StartMFAEnrollmentRequest(idToken: idToken, enrollmentInfo: startMFARequestInfo, requestConfiguration: auth.requestConfiguration) - let response = try await AuthBackend.call(with: request) + let response = try await auth.backend.call(with: request) return response.phoneSessionInfo?.sessionInfo } else { let request = StartMFASignInRequest(MFAPendingCredential: session.mfaPendingCredential, @@ -271,7 +271,7 @@ import Foundation signInInfo: startMFARequestInfo, requestConfiguration: auth.requestConfiguration) - let response = try await AuthBackend.call(with: request) + let response = try await auth.backend.call(with: request) return response.responseInfo?.sessionInfo } } catch { @@ -328,7 +328,7 @@ import Foundation isSandbox: token.type == AuthAPNSTokenType.sandbox, requestConfiguration: auth.requestConfiguration) do { - let verifyResponse = try await AuthBackend.call(with: request) + let verifyResponse = try await auth.backend.call(with: request) guard let receipt = verifyResponse.receipt, let timeout = verifyResponse.suggestedTimeOutDate?.timeIntervalSinceNow else { fatalError("Internal Auth Error: invalid VerifyClientResponse") @@ -436,7 +436,7 @@ import Foundation /// - Parameter eventID: The event ID used for this purpose. private func reCAPTCHAURL(withEventID eventID: String) async throws -> URL? { let authDomain = try await AuthWebUtils - .fetchAuthDomain(withRequestConfiguration: auth.requestConfiguration) + .fetchAuthDomain(withRequestConfiguration: auth.requestConfiguration, backend: auth.backend) let bundleID = Bundle.main.bundleIdentifier let clientID = auth.app?.options.clientID let appID = auth.app?.options.googleAppID diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index ee0b57f8275..85802392052 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -32,21 +32,7 @@ class AuthBackend: AuthBackendProtocol { return "FirebaseAuth.iOS/\(FirebaseVersion()) \(GTMFetcherStandardUserAgentString(nil))" } - static func call(with request: T) async throws -> T.Response { - return try await shared.call(with: request) - } - - static func setTestRPCIssuer(issuer: AuthBackendRPCIssuer) { - shared.rpcIssuer = issuer - } - - static func resetRPCIssuer() { - shared.rpcIssuer = AuthBackendRPCIssuer() - } - - private static let shared: AuthBackend = .init(rpcIssuer: AuthBackendRPCIssuer()) - - private var rpcIssuer: any AuthBackendRPCIssuerProtocol + private let rpcIssuer: any AuthBackendRPCIssuerProtocol init(rpcIssuer: any AuthBackendRPCIssuerProtocol) { self.rpcIssuer = rpcIssuer diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift index c07d60c7a60..e7fa5ee57e5 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift @@ -59,7 +59,7 @@ private var gAPIHost = "securetoken.googleapis.com" /// Represents the parameters for the token endpoint. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -struct SecureTokenRequest: AuthRPCRequest { +class SecureTokenRequest: AuthRPCRequest { typealias Response = SecureTokenResponse /// The type of grant requested. diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift index f3dd5b26981..c60f353e362 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift @@ -88,7 +88,7 @@ import Foundation .requestConfiguration) Task { do { - let response = try await AuthBackend.call(with: request) + let response = try await auth.backend.call(with: request) do { let user = try await auth.completeSignIn(withAccessToken: response.idToken, accessTokenExpirationDate: nil, @@ -139,7 +139,7 @@ import Foundation Task { do { - let response = try await AuthBackend.call(with: request) + let response = try await auth.backend.call(with: request) do { let user = try await auth.completeSignIn(withAccessToken: response.idToken, accessTokenExpirationDate: nil, @@ -217,7 +217,7 @@ import Foundation requestConfiguration: user.requestConfiguration) Task { do { - let response = try await AuthBackend.call(with: request) + let response = try await auth.backend.call(with: request) do { let user = try await auth.completeSignIn(withAccessToken: response.idToken, accessTokenExpirationDate: nil, diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift index 64b8bdda693..366ccc02023 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift @@ -72,7 +72,7 @@ import Foundation ) Task { do { - let response = try await AuthBackend.call(with: request) + let response = try await self.auth.backend.call(with: request) let user = try await self.auth.completeSignIn(withAccessToken: response.idToken, accessTokenExpirationDate: nil, refreshToken: response.refreshToken, diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift index 4fc574caedb..bf3b07634ca 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift @@ -31,8 +31,7 @@ import Foundation @objc(generateSecretWithMultiFactorSession:completion:) open class func generateSecret(with session: MultiFactorSession, completion: @escaping (TOTPSecret?, Error?) -> Void) { - guard let currentUser = session.currentUser, - let requestConfiguration = currentUser.auth?.requestConfiguration else { + guard let currentUser = session.currentUser, let auth = currentUser.auth else { let error = AuthErrorUtils.error(code: AuthErrorCode.internalError, userInfo: [NSLocalizedDescriptionKey: "Invalid ID token."]) @@ -42,10 +41,10 @@ import Foundation let totpEnrollmentInfo = AuthProtoStartMFATOTPEnrollmentRequestInfo() let request = StartMFAEnrollmentRequest(idToken: session.idToken, totpEnrollmentInfo: totpEnrollmentInfo, - requestConfiguration: requestConfiguration) + requestConfiguration: auth.requestConfiguration) Task { do { - let response = try await AuthBackend.call(with: request) + let response = try await auth.backend.call(with: request) if let totpSessionInfo = response.totpSessionInfo { let secret = TOTPSecret(secretKey: totpSessionInfo.sharedSecretKey, hashingAlgorithm: totpSessionInfo.hashingAlgorithm, diff --git a/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift b/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift index dd9e6dbde13..6bdee4c395a 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift @@ -25,12 +25,13 @@ actor SecureTokenServiceInternal { /// - Parameter forceRefresh: Forces the token to be refreshed. /// - Returns : A tuple with the token and flag of whether it was updated. func fetchAccessToken(forcingRefresh forceRefresh: Bool, - service: SecureTokenService) async throws -> (String?, Bool) { + service: SecureTokenService, + backend: AuthBackend) async throws -> (String?, Bool) { if !forceRefresh, hasValidAccessToken(service: service) { return (service.accessToken, false) } else { AuthLog.logDebug(code: "I-AUT000017", message: "Fetching new token from backend.") - return try await requestAccessToken(retryIfExpired: true, service: service) + return try await requestAccessToken(retryIfExpired: true, service: service, backend: backend) } } @@ -41,7 +42,8 @@ actor SecureTokenServiceInternal { /// /// - Returns: Token and Bool indicating if update occurred. private func requestAccessToken(retryIfExpired: Bool, - service: SecureTokenService) async throws -> (String?, Bool) { + service: SecureTokenService, + backend: AuthBackend) async throws -> (String?, Bool) { // TODO: This was a crash in ObjC SDK, should it callback with an error? guard let refreshToken = service.refreshToken, let requestConfiguration = service.requestConfiguration else { @@ -50,7 +52,7 @@ actor SecureTokenServiceInternal { let request = SecureTokenRequest.refreshRequest(refreshToken: refreshToken, requestConfiguration: requestConfiguration) - let response = try await AuthBackend.call(with: request) + let response = try await backend.call(with: request) var tokenUpdated = false if let newAccessToken = response.accessToken, newAccessToken.count > 0, @@ -67,7 +69,11 @@ actor SecureTokenServiceInternal { if expirationDate.timeIntervalSinceNow <= kFiveMinutes { // We only retry once, to avoid an infinite loop in the case that an end-user has // their local time skewed by over an hour. - return try await requestAccessToken(retryIfExpired: false, service: service) + return try await requestAccessToken( + retryIfExpired: false, + service: service, + backend: backend + ) } } } @@ -152,8 +158,10 @@ class SecureTokenService: NSObject, NSSecureCoding { /// Invoked asynchronously on the auth global work queue in the future. /// - Parameter forceRefresh: Forces the token to be refreshed. /// - Returns : A tuple with the token and flag of whether it was updated. - func fetchAccessToken(forcingRefresh forceRefresh: Bool) async throws -> (String?, Bool) { - return try await internalService.fetchAccessToken(forcingRefresh: forceRefresh, service: self) + func fetchAccessToken(forcingRefresh forceRefresh: Bool, + backend: AuthBackend) async throws -> (String?, Bool) { + return try await internalService + .fetchAccessToken(forcingRefresh: forceRefresh, service: self, backend: backend) } // MARK: NSSecureCoding diff --git a/FirebaseAuth/Sources/Swift/User/User.swift b/FirebaseAuth/Sources/Swift/User/User.swift index 94d1c13dd0a..5c2224e1eb7 100644 --- a/FirebaseAuth/Sources/Swift/User/User.swift +++ b/FirebaseAuth/Sources/Swift/User/User.swift @@ -49,6 +49,9 @@ extension User: NSSecureCoding {} var providerDataRaw: [String: UserInfoImpl] + /// The backend service for the given instance. + private(set) var backend: AuthBackend + /// Metadata associated with the Firebase user in question. @objc public private(set) var metadata: UserMetadata @@ -583,7 +586,7 @@ extension User: NSSecureCoding {} open func getIDTokenResult(forcingRefresh: Bool, completion: ((AuthTokenResult?, Error?) -> Void)?) { kAuthGlobalWorkQueue.async { - self.internalGetToken(forceRefresh: forcingRefresh) { token, error in + self.internalGetToken(forceRefresh: forcingRefresh, backend: self.backend) { token, error in var tokenResult: AuthTokenResult? if let token { do { @@ -866,7 +869,7 @@ extension User: NSSecureCoding {} open func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil, completion: ((Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { - self.internalGetToken { accessToken, error in + self.internalGetToken(backend: self.backend) { accessToken, error in if let error { User.callInMainThreadWithError(callback: completion, error: error) return @@ -884,7 +887,7 @@ extension User: NSSecureCoding {} ) Task { do { - let _ = try await AuthBackend.call(with: request) + let _ = try await self.backend.call(with: request) User.callInMainThreadWithError(callback: completion, error: nil) } catch { self.signOutIfTokenIsInvalid(withError: error) @@ -931,7 +934,7 @@ extension User: NSSecureCoding {} /// is complete, or fails. Invoked asynchronously on the main thread in the future. @objc open func delete(completion: ((Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { - self.internalGetToken { accessToken, error in + self.internalGetToken(backend: self.backend) { accessToken, error in if let error { User.callInMainThreadWithError(callback: completion, error: error) return @@ -946,7 +949,7 @@ extension User: NSSecureCoding {} requestConfiguration: requestConfiguration) Task { do { - let _ = try await AuthBackend.call(with: request) + let _ = try await self.backend.call(with: request) try self.auth?.signOutByForce(withUserID: self.uid) User.callInMainThreadWithError(callback: completion, error: nil) } catch { @@ -996,7 +999,7 @@ extension User: NSSecureCoding {} actionCodeSettings: ActionCodeSettings? = nil, completion: ((Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { - self.internalGetToken { accessToken, error in + self.internalGetToken(backend: self.backend) { accessToken, error in if let error { User.callInMainThreadWithError(callback: completion, error: error) return @@ -1015,7 +1018,7 @@ extension User: NSSecureCoding {} ) Task { do { - let _ = try await AuthBackend.call(with: request) + let _ = try await self.backend.call(with: request) User.callInMainThreadWithError(callback: completion, error: nil) } catch { User.callInMainThreadWithError(callback: completion, error: error) @@ -1054,7 +1057,8 @@ extension User: NSSecureCoding {} return tokenService.accessTokenExpirationDate } - init(withTokenService tokenService: SecureTokenService) { + init(withTokenService tokenService: SecureTokenService, backend: AuthBackend) { + self.backend = backend providerDataRaw = [:] userProfileUpdate = UserProfileUpdate() self.tokenService = tokenService @@ -1083,16 +1087,16 @@ extension User: NSSecureCoding {} accessToken: accessToken, accessTokenExpirationDate: accessTokenExpirationDate, refreshToken: refreshToken) - let user = User(withTokenService: tokenService) + let user = User(withTokenService: tokenService, backend: auth.backend) user.auth = auth user.tenantID = auth.tenantID user.requestConfiguration = auth.requestConfiguration - let accessToken2 = try await user.internalGetTokenAsync() + let accessToken2 = try await user.internalGetTokenAsync(backend: user.backend) let getAccountInfoRequest = GetAccountInfoRequest( accessToken: accessToken2, requestConfiguration: user.requestConfiguration ) - let response = try await AuthBackend.call(with: getAccountInfoRequest) + let response = try await auth.backend.call(with: getAccountInfoRequest) user.isAnonymous = anonymous user.update(withGetAccountInfoResponse: response) return user @@ -1137,12 +1141,13 @@ extension User: NSSecureCoding {} /// A weak reference to an `Auth` instance associated with this instance. weak var auth: Auth? { set { - _auth = newValue - guard let requestConfiguration = auth?.requestConfiguration else { - fatalError("Firebase Auth Internal Error: nil requestConfiguration when initializing User") + guard let newValue else { + fatalError("Firebase Auth Internal Error: Set user's auth property with non-nil instance.") } + _auth = newValue + requestConfiguration = newValue.requestConfiguration tokenService.requestConfiguration = requestConfiguration - self.requestConfiguration = requestConfiguration + backend = newValue.backend } get { return _auth } } @@ -1173,12 +1178,12 @@ extension User: NSSecureCoding {} // The list of providers need to be updated for the newly added email-password provider. Task { do { - let accessToken = try await self.internalGetTokenAsync() + let accessToken = try await self.internalGetTokenAsync(backend: self.backend) if let requestConfiguration = self.auth?.requestConfiguration { let getAccountInfoRequest = GetAccountInfoRequest(accessToken: accessToken, requestConfiguration: requestConfiguration) do { - let accountInfoResponse = try await AuthBackend.call(with: getAccountInfoRequest) + let accountInfoResponse = try await self.backend.call(with: getAccountInfoRequest) if let users = accountInfoResponse.users { for userAccountInfo in users { // Set the account to non-anonymous if there are any providers, even if @@ -1313,7 +1318,7 @@ extension User: NSSecureCoding {} private func internalUpdateOrLinkPhoneNumber(credential: PhoneAuthCredential, isLinkOperation: Bool, completion: @escaping (Error?) -> Void) { - internalGetToken { accessToken, error in + internalGetToken(backend: backend) { accessToken, error in if let error { completion(error) return @@ -1335,7 +1340,7 @@ extension User: NSSecureCoding {} request.accessToken = accessToken Task { do { - let verifyResponse = try await AuthBackend.call(with: request) + let verifyResponse = try await self.backend.call(with: request) guard let idToken = verifyResponse.idToken, let refreshToken = verifyResponse.refreshToken else { fatalError("Internal Auth Error: missing token in internalUpdateOrLinkPhoneNumber") @@ -1375,7 +1380,7 @@ extension User: NSSecureCoding {} password: String, authResult: AuthDataResult, _ completion: ((AuthDataResult?, Error?) -> Void)?) { - internalGetToken { accessToken, error in + internalGetToken(backend: backend) { accessToken, error in guard let requestConfiguration = self.auth?.requestConfiguration else { fatalError("Internal auth error: missing auth on User") } @@ -1394,7 +1399,7 @@ extension User: NSSecureCoding {} action: AuthRecaptchaAction .signUpPassword) #else - let response = try await AuthBackend.call(with: request) + let response = try await self.backend.call(with: request) #endif guard let refreshToken = response.refreshToken, let idToken = response.idToken else { @@ -1437,7 +1442,7 @@ extension User: NSSecureCoding {} let result = AuthDataResult(withUser: self, additionalUserInfo: nil) link(withEmail: emailCredential.email, password: password, authResult: result, completion) case let .link(link): - internalGetToken { accessToken, error in + internalGetToken(backend: backend) { accessToken, error in var queryItems = AuthWebUtils.parseURL(link) if link.count == 0 { if let urlComponents = URLComponents(string: link), @@ -1455,7 +1460,7 @@ extension User: NSSecureCoding {} request.idToken = accessToken Task { do { - let response = try await AuthBackend.call(with: request) + let response = try await self.backend.call(with: request) guard let idToken = response.idToken, let refreshToken = response.refreshToken else { fatalError("Internal Auth Error: missing token in EmailLinkSignInResponse") @@ -1484,7 +1489,7 @@ extension User: NSSecureCoding {} #if !os(watchOS) private func link(withGameCenterCredential gameCenterCredential: GameCenterAuthCredential, completion: ((AuthDataResult?, Error?) -> Void)?) { - internalGetToken { accessToken, error in + internalGetToken(backend: backend) { accessToken, error in guard let requestConfiguration = self.auth?.requestConfiguration, let publicKeyURL = gameCenterCredential.publicKeyURL, let signature = gameCenterCredential.signature, @@ -1503,7 +1508,7 @@ extension User: NSSecureCoding {} request.accessToken = accessToken Task { do { - let response = try await AuthBackend.call(with: request) + let response = try await self.backend.call(with: request) guard let idToken = response.idToken, let refreshToken = response.refreshToken else { fatalError("Internal Auth Error: missing token in link(withGameCredential") @@ -1584,10 +1589,11 @@ extension User: NSSecureCoding {} /// - Parameter callback: The block to invoke when the token is available. Invoked asynchronously /// on the global work thread in the future. func internalGetToken(forceRefresh: Bool = false, + backend: AuthBackend, callback: @escaping (String?, Error?) -> Void) { Task { do { - let token = try await internalGetTokenAsync(forceRefresh: forceRefresh) + let token = try await internalGetTokenAsync(forceRefresh: forceRefresh, backend: backend) callback(token, nil) } catch { callback(nil, error) @@ -1597,10 +1603,11 @@ extension User: NSSecureCoding {} /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. /// - Parameter forceRefresh - func internalGetTokenAsync(forceRefresh: Bool = false) async throws -> String { + func internalGetTokenAsync(forceRefresh: Bool = false, + backend: AuthBackend) async throws -> String { do { let (token, tokenUpdated) = try await tokenService.fetchAccessToken( - forcingRefresh: forceRefresh + forcingRefresh: forceRefresh, backend: backend ) if tokenUpdated { if let error = updateKeychain() { @@ -1746,8 +1753,14 @@ extension User: NSSecureCoding {} self.phoneNumber = phoneNumber self.metadata = metadata ?? UserMetadata(withCreationDate: nil, lastSignInDate: nil) self.tenantID = tenantID - // The `heartbeatLogger` and `appCheck` will be set later via a property update. + // This property will be overwritten later via the `user.auth` property update. This is to + // provide the `heartbeatLogger` and `appCheck` will be set later via the `user.auth` property + // update. For now, a placeholder is set as the property update should happen right after this + // intializer. requestConfiguration = AuthRequestConfiguration(apiKey: apiKey, appID: appID) + // This property will be overwritten later via the `user.auth` property update. For now, a + // placeholder is set as the property update should happen right after this intializer. + backend = AuthBackend(rpcIssuer: AuthBackendRPCIssuer()) userProfileUpdate = UserProfileUpdate() #if os(iOS) self.multiFactor = multiFactor ?? MultiFactor() diff --git a/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift b/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift index 74aa5cf5c73..0c51eb9fe83 100644 --- a/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift +++ b/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift @@ -18,13 +18,13 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) actor UserProfileUpdate { func link(user: User, with credential: AuthCredential) async throws -> AuthDataResult { - let accessToken = try await user.internalGetTokenAsync() + let accessToken = try await user.internalGetTokenAsync(backend: user.backend) let request = VerifyAssertionRequest(providerID: credential.provider, requestConfiguration: user.requestConfiguration) credential.prepare(request) request.accessToken = accessToken do { - let response = try await AuthBackend.call(with: request) + let response = try await user.backend.call(with: request) guard let idToken = response.idToken, let refreshToken = response.refreshToken, let providerID = response.providerID else { @@ -48,7 +48,7 @@ actor UserProfileUpdate { } func unlink(user: User, fromProvider provider: String) async throws -> User { - let accessToken = try await user.internalGetTokenAsync() + let accessToken = try await user.internalGetTokenAsync(backend: user.backend) let request = SetAccountInfoRequest( accessToken: accessToken, requestConfiguration: user.requestConfiguration ) @@ -58,7 +58,7 @@ actor UserProfileUpdate { } request.deleteProviders = [provider] do { - let response = try await AuthBackend.call(with: request) + let response = try await user.backend.call(with: request) // We can't just use the provider info objects in SetAccountInfoResponse // because they don't have localID and email fields. Remove the specific @@ -105,7 +105,7 @@ actor UserProfileUpdate { SetAccountInfoRequest) -> Void) async throws { let userAccountInfo = try await getAccountInfoRefreshingCache(user) - let accessToken = try await user.internalGetTokenAsync() + let accessToken = try await user.internalGetTokenAsync(backend: user.backend) // Mutate setAccountInfoRequest in block let setAccountInfoRequest = @@ -115,7 +115,7 @@ actor UserProfileUpdate { ) changeBlock(userAccountInfo, setAccountInfoRequest) do { - let accountInfoResponse = try await AuthBackend.call(with: setAccountInfoRequest) + let accountInfoResponse = try await user.backend.call(with: setAccountInfoRequest) if let idToken = accountInfoResponse.idToken, let refreshToken = accountInfoResponse.refreshToken { let tokenService = SecureTokenService( @@ -143,13 +143,13 @@ actor UserProfileUpdate { accessTokenExpirationDate: expirationDate, refreshToken: refreshToken ) - let accessToken = try await user.internalGetTokenAsync() + let accessToken = try await user.internalGetTokenAsync(backend: user.backend) let getAccountInfoRequest = GetAccountInfoRequest( accessToken: accessToken, requestConfiguration: user.requestConfiguration ) do { - let response = try await AuthBackend.call(with: getAccountInfoRequest) + let response = try await user.backend.call(with: getAccountInfoRequest) user.isAnonymous = false user.update(withGetAccountInfoResponse: response) } catch { @@ -168,7 +168,7 @@ actor UserProfileUpdate { /// - Parameter tokenService: The new token service object. /// - Parameter callback: The block to be called in the global auth working queue once finished. func setTokenService(user: User, tokenService: SecureTokenService) async throws { - _ = try await tokenService.fetchAccessToken(forcingRefresh: false) + _ = try await tokenService.fetchAccessToken(forcingRefresh: false, backend: user.backend) user.tokenService = tokenService if let error = user.updateKeychain() { throw error @@ -180,11 +180,11 @@ actor UserProfileUpdate { /// error has been detected. Invoked asynchronously on the auth global work queue in the future. func getAccountInfoRefreshingCache(_ user: User) async throws -> GetAccountInfoResponse.User { - let token = try await user.internalGetTokenAsync() + let token = try await user.internalGetTokenAsync(backend: user.backend) let request = GetAccountInfoRequest(accessToken: token, requestConfiguration: user.requestConfiguration) do { - let accountInfoResponse = try await AuthBackend.call(with: request) + let accountInfoResponse = try await user.backend.call(with: request) user.update(withGetAccountInfoResponse: accountInfoResponse) if let error = user.updateKeychain() { throw error diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift index 6919ac40807..d4247bd6c31 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift @@ -149,12 +149,12 @@ } } - guard let requestConfiguration = auth?.requestConfiguration else { + guard let auth = auth else { throw AuthErrorUtils.error(code: .recaptchaNotEnabled, message: "No requestConfiguration for Auth instance") } - let request = GetRecaptchaConfigRequest(requestConfiguration: requestConfiguration) - let response = try await AuthBackend.call(with: request) + let request = GetRecaptchaConfigRequest(requestConfiguration: auth.requestConfiguration) + let response = try await auth.backend.call(with: request) AuthLog.logInfo(code: "I-AUT000029", message: "reCAPTCHA config retrieval succeeded.") // Response's site key is of the format projects//keys/' guard let keys = response.recaptchaKey?.components(separatedBy: "/"), @@ -179,7 +179,7 @@ } let config = AuthRecaptchaConfig(siteKey: siteKey, enablementStatus: enablementStatus) - if let tenantID = auth?.tenantID { + if let tenantID = auth.tenantID { tenantConfigs[tenantID] = config } else { agentConfig = config diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift index 2231c040023..69210b9326b 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift @@ -98,7 +98,8 @@ class AuthWebUtils { return urlComponents.first } - static func fetchAuthDomain(withRequestConfiguration requestConfiguration: AuthRequestConfiguration) + static func fetchAuthDomain(withRequestConfiguration requestConfiguration: AuthRequestConfiguration, + backend: AuthBackend) async throws -> String { if let emulatorHostAndPort = requestConfiguration.emulatorHostAndPort { // If we are using the auth emulator, we do not want to call the GetProjectConfig endpoint. @@ -107,7 +108,7 @@ class AuthWebUtils { } let request = GetProjectConfigRequest(requestConfiguration: requestConfiguration) - let response = try await AuthBackend.call(with: request) + let response = try await backend.call(with: request) // Look up an authorized domain ends with one of the supportedAuthDomains. // The sequence of supportedAuthDomains matters. ("firebaseapp.com", "web.app") diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AuthViewController.swift b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AuthViewController.swift index fdde2e0757d..e9567a69e9d 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AuthViewController.swift +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AuthViewController.swift @@ -481,7 +481,7 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate { Task { do { - let verifyResponse = try await AuthBackend.call(with: request) + let verifyResponse = try await AppManager.shared.auth().backend.call(with: request) guard let receipt = verifyResponse.receipt, let timeoutDate = verifyResponse.suggestedTimeOutDate else { @@ -510,7 +510,7 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate { ) do { - _ = try await AuthBackend.call(with: request) + _ = try await AppManager.shared.auth().backend.call(with: request) print("Verify iOS client succeeded") } catch { print("Verify iOS Client failed: \(error.localizedDescription)") diff --git a/FirebaseAuth/Tests/Unit/AuthBackendTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendTests.swift index b163d34ea68..5feaf239e3b 100644 --- a/FirebaseAuth/Tests/Unit/AuthBackendTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthBackendTests.swift @@ -41,7 +41,7 @@ class AuthBackendTests: RPCBaseTests { let request = FakeRequest(withEncodingError: encodingError) do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -71,7 +71,7 @@ class AuthBackendTests: RPCBaseTests { func testBodyDataSerializationError() async throws { let request = FakeRequest(withRequestBody: ["unencodable": self]) do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -99,7 +99,7 @@ class AuthBackendTests: RPCBaseTests { try self.rpcIssuer.respond(withData: nil, error: responseError) } do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -131,7 +131,7 @@ class AuthBackendTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: responseError) } do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -168,7 +168,7 @@ class AuthBackendTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: nil) } do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -210,7 +210,7 @@ class AuthBackendTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: responseError) } do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -252,7 +252,7 @@ class AuthBackendTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: nil) } do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -287,7 +287,7 @@ class AuthBackendTests: RPCBaseTests { error: responseError) } do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -329,7 +329,7 @@ class AuthBackendTests: RPCBaseTests { ) } do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -356,7 +356,7 @@ class AuthBackendTests: RPCBaseTests { error: responseError) } do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -398,7 +398,7 @@ class AuthBackendTests: RPCBaseTests { error: responseError) } do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -436,7 +436,7 @@ class AuthBackendTests: RPCBaseTests { let _ = try self.rpcIssuer.respond(withJSON: [:], error: responseError) } do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -473,7 +473,7 @@ class AuthBackendTests: RPCBaseTests { try self.rpcIssuer.respond(serverErrorMessage: customErrorMessage, error: responseError) } do { - let _ = try await AuthBackend.call(with: FakeRequest(withRequestBody: [:])) + let _ = try await authBackend.call(with: FakeRequest(withRequestBody: [:])) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -497,7 +497,7 @@ class AuthBackendTests: RPCBaseTests { } do { let request = FakeDecodingErrorRequest(withRequestBody: [:]) - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -528,7 +528,7 @@ class AuthBackendTests: RPCBaseTests { // value it was given. try self.rpcIssuer.respond(withJSON: [kTestKey: kTestValue]) } - let rpcResponse = try await AuthBackend.call(with: FakeRequest(withRequestBody: [:])) + let rpcResponse = try await authBackend.call(with: FakeRequest(withRequestBody: [:])) XCTAssertEqual(try XCTUnwrap(rpcResponse.receivedValue), kTestValue) } @@ -593,7 +593,7 @@ class AuthBackendTests: RPCBaseTests { // Force return from async post try self.rpcIssuer.respond(withJSON: [:]) } - _ = try? await AuthBackend.call(with: request) + _ = try? await authBackend.call(with: request) // Then let expectedHeader = HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload.headerValue() @@ -620,7 +620,7 @@ class AuthBackendTests: RPCBaseTests { // Just force return from async call. try self.rpcIssuer.respond(withJSON: [:]) } - _ = try? await AuthBackend.call(with: request) + _ = try? await authBackend.call(with: request) let completeRequest = await rpcIssuer.completeRequest.value let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-AppCheck") @@ -650,7 +650,7 @@ class AuthBackendTests: RPCBaseTests { // Force return from async post try self.rpcIssuer.respond(withJSON: [:]) } - _ = try? await AuthBackend.call(with: request) + _ = try? await authBackend.call(with: request) // Then let completeRequest = await rpcIssuer.completeRequest.value diff --git a/FirebaseAuth/Tests/Unit/AuthTests.swift b/FirebaseAuth/Tests/Unit/AuthTests.swift index 1167bf22ca2..f84eb33d70f 100644 --- a/FirebaseAuth/Tests/Unit/AuthTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthTests.swift @@ -47,7 +47,8 @@ class AuthTests: RPCBaseTests { #endif // (os(macOS) && !FIREBASE_AUTH_TESTING_USE_MACOS_KEYCHAIN) || SWIFT_PACKAGE auth = Auth( app: FirebaseApp.app(name: name)!, - keychainStorageProvider: keychainStorageProvider + keychainStorageProvider: keychainStorageProvider, + backend: authBackend ) // Set authDispatcherCallback implementation in order to save the token refresh task for later diff --git a/FirebaseAuth/Tests/Unit/CreateAuthURITests.swift b/FirebaseAuth/Tests/Unit/CreateAuthURITests.swift index af1f54bce28..334091d21fd 100644 --- a/FirebaseAuth/Tests/Unit/CreateAuthURITests.swift +++ b/FirebaseAuth/Tests/Unit/CreateAuthURITests.swift @@ -75,7 +75,7 @@ class CreateAuthURITests: RPCBaseTests { rpcIssuer?.respondBlock = { try self.rpcIssuer?.respond(withJSON: [kAuthUriKey: kTestAuthUri]) } - let rpcResponse = try await AuthBackend.call(with: makeAuthURIRequest()) + let rpcResponse = try await authBackend.call(with: makeAuthURIRequest()) XCTAssertEqual(rpcResponse.authURI, kTestAuthUri) } @@ -89,7 +89,7 @@ class CreateAuthURITests: RPCBaseTests { .respond(withJSON: ["kind": kTestExpectedKind, "allProviders": [kTestProviderID1, kTestProviderID2]]) } - let rpcResponse = try await AuthBackend.call(with: makeAuthURIRequest()) + let rpcResponse = try await authBackend.call(with: makeAuthURIRequest()) XCTAssertEqual(rpcIssuer?.requestURL?.absoluteString, kExpectedAPIURL) XCTAssertEqual(rpcIssuer?.decodedRequest?["identifier"] as? String, kTestIdentifier) diff --git a/FirebaseAuth/Tests/Unit/DeleteAccountTests.swift b/FirebaseAuth/Tests/Unit/DeleteAccountTests.swift index ae213aa8464..a968a02e3e8 100644 --- a/FirebaseAuth/Tests/Unit/DeleteAccountTests.swift +++ b/FirebaseAuth/Tests/Unit/DeleteAccountTests.swift @@ -64,7 +64,7 @@ class DeleteAccountTests: RPCBaseTests { rpcIssuer?.respondBlock = { try self.rpcIssuer?.respond(withJSON: [:]) } - let rpcResponse = try await AuthBackend.call(with: makeDeleteAccountRequest()) + let rpcResponse = try await authBackend.call(with: makeDeleteAccountRequest()) XCTAssertNotNil(rpcResponse) } diff --git a/FirebaseAuth/Tests/Unit/EmailLinkSignInTests.swift b/FirebaseAuth/Tests/Unit/EmailLinkSignInTests.swift index 2cdd4943653..ce4b2dd57d5 100644 --- a/FirebaseAuth/Tests/Unit/EmailLinkSignInTests.swift +++ b/FirebaseAuth/Tests/Unit/EmailLinkSignInTests.swift @@ -65,7 +65,7 @@ class EmailLinkSignInTests: RPCBaseTests { XCTAssertNil(requestDictionary[self.kIDTokenKey]) try self.rpcIssuer?.respond(withJSON: [:]) // unblock the await } - let _ = try await AuthBackend.call(with: makeEmailLinkSignInRequest()) + let _ = try await authBackend.call(with: makeEmailLinkSignInRequest()) } /** @fn testEmailLinkRequestCreationOptional @@ -87,7 +87,7 @@ class EmailLinkSignInTests: RPCBaseTests { XCTAssertEqual(requestDictionary[self.kIDTokenKey], kTestIDToken) try self.rpcIssuer?.respond(withJSON: [:]) // unblock the await } - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) } func testEmailLinkSignInErrors() async throws { @@ -115,7 +115,7 @@ class EmailLinkSignInTests: RPCBaseTests { "expiresIn": "\(kTestTokenExpirationTimeInterval)", "refreshToken": kTestRefreshToken]) } - let response = try await AuthBackend.call(with: makeEmailLinkSignInRequest()) + let response = try await authBackend.call(with: makeEmailLinkSignInRequest()) XCTAssertEqual(response.idToken, kTestIDTokenResponse) XCTAssertEqual(response.email, kTestEmailResponse) diff --git a/FirebaseAuth/Tests/Unit/GetAccountInfoTests.swift b/FirebaseAuth/Tests/Unit/GetAccountInfoTests.swift index 87a294c0ef2..dff1e74936e 100644 --- a/FirebaseAuth/Tests/Unit/GetAccountInfoTests.swift +++ b/FirebaseAuth/Tests/Unit/GetAccountInfoTests.swift @@ -100,7 +100,7 @@ class GetAccountInfoTests: RPCBaseTests { rpcIssuer?.respondBlock = { try self.rpcIssuer?.respond(withJSON: ["users": usersIn]) } - let rpcResponse = try await AuthBackend.call(with: makeGetAccountInfoRequest()) + let rpcResponse = try await authBackend.call(with: makeGetAccountInfoRequest()) let users = try XCTUnwrap(rpcResponse.users) XCTAssertGreaterThan(users.count, 0) diff --git a/FirebaseAuth/Tests/Unit/GetOOBConfirmationCodeTests.swift b/FirebaseAuth/Tests/Unit/GetOOBConfirmationCodeTests.swift index b9fe798f57d..631bab3a723 100644 --- a/FirebaseAuth/Tests/Unit/GetOOBConfirmationCodeTests.swift +++ b/FirebaseAuth/Tests/Unit/GetOOBConfirmationCodeTests.swift @@ -199,7 +199,7 @@ class GetOOBConfirmationCodeTests: RPCBaseTests { rpcIssuer?.respondBlock = { try self.rpcIssuer?.respond(withJSON: [self.kOOBCodeKey: self.kTestOOBCode]) } - let response = try await AuthBackend.call(with: request()) + let response = try await authBackend.call(with: request()) XCTAssertEqual(response.OOBCode, kTestOOBCode) } } @@ -217,7 +217,7 @@ class GetOOBConfirmationCodeTests: RPCBaseTests { rpcIssuer?.respondBlock = { try self.rpcIssuer?.respond(withJSON: [:]) } - let response = try await AuthBackend.call(with: request()) + let response = try await authBackend.call(with: request()) XCTAssertNil(response.OOBCode) } } diff --git a/FirebaseAuth/Tests/Unit/GetProjectConfigTests.swift b/FirebaseAuth/Tests/Unit/GetProjectConfigTests.swift index 107a5c33b4f..6dbe4c644c6 100644 --- a/FirebaseAuth/Tests/Unit/GetProjectConfigTests.swift +++ b/FirebaseAuth/Tests/Unit/GetProjectConfigTests.swift @@ -60,7 +60,7 @@ class GetProjectConfigTests: RPCBaseTests { try self.rpcIssuer?.respond(withJSON: ["projectId": kTestProjectID, "authorizedDomains": [kTestDomain1, kTestDomain2]]) } - let rpcResponse = try await AuthBackend.call(with: makeGetProjectConfigRequest()) + let rpcResponse = try await authBackend.call(with: makeGetProjectConfigRequest()) XCTAssertEqual(rpcResponse.projectID, kTestProjectID) XCTAssertEqual(rpcResponse.authorizedDomains?.first, kTestDomain1) XCTAssertEqual(rpcResponse.authorizedDomains?[1], kTestDomain2) diff --git a/FirebaseAuth/Tests/Unit/GetRecaptchaConfigTests.swift b/FirebaseAuth/Tests/Unit/GetRecaptchaConfigTests.swift index df64689c7c0..75149c38d45 100644 --- a/FirebaseAuth/Tests/Unit/GetRecaptchaConfigTests.swift +++ b/FirebaseAuth/Tests/Unit/GetRecaptchaConfigTests.swift @@ -24,7 +24,7 @@ class GetRecaptchaConfigTests: RPCBaseTests { */ func testGetRecaptchaConfigRequest() async throws { let request = GetRecaptchaConfigRequest(requestConfiguration: makeRequestConfiguration()) - // let _ = try await AuthBackend.call(with: request) + // let _ = try await authBackend.call(with: request) XCTAssertFalse(request.containsPostBody) // Confirm that the request has no decoded body as it is get request. @@ -47,7 +47,7 @@ class GetRecaptchaConfigTests: RPCBaseTests { let request = GetRecaptchaConfigRequest(requestConfiguration: makeRequestConfiguration()) rpcIssuer.recaptchaSiteKey = kTestRecaptchaKey - let response = try await AuthBackend.call(with: request) + let response = try await authBackend.call(with: request) XCTAssertEqual(response.recaptchaKey, kTestRecaptchaKey) XCTAssertNil(response.enforcementState) } diff --git a/FirebaseAuth/Tests/Unit/OAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/OAuthProviderTests.swift index 6f1ed895b0a..a5a330babf7 100644 --- a/FirebaseAuth/Tests/Unit/OAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/OAuthProviderTests.swift @@ -282,6 +282,10 @@ import FirebaseCore .replacingOccurrences(of: ")", with: "") FirebaseApp.configure(name: strippedName, options: options) OAuthProviderTests.auth = Auth.auth(app: FirebaseApp.app(name: strippedName)!) + OAuthProviderTests.auth = Auth( + app: FirebaseApp.app(name: strippedName)!, + backend: authBackend + ) OAuthProviderTests.auth?.mainBundleUrlTypes = [["CFBundleURLSchemes": [scheme]]] } diff --git a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift index 0030c52776d..9a319ea9755 100644 --- a/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift +++ b/FirebaseAuth/Tests/Unit/PhoneAuthProviderTests.swift @@ -662,7 +662,7 @@ let strippedName = functionName.replacingOccurrences(of: "(", with: "") .replacingOccurrences(of: ")", with: "") FirebaseApp.configure(name: strippedName, options: options) - let auth = Auth.auth(app: FirebaseApp.app(name: strippedName)!) + let auth = Auth(app: FirebaseApp.app(name: strippedName)!, backend: authBackend) kAuthGlobalWorkQueue.sync { // Wait for Auth protectedDataInitialization to finish. diff --git a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift index 009289a1b44..9a8f9026ea9 100644 --- a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift +++ b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift @@ -68,15 +68,16 @@ class RPCBaseTests: XCTestCase { let kTestIdentifier = "Identifier" var rpcIssuer: FakeBackendRPCIssuer! + var authBackend: AuthBackend! override func setUp() { rpcIssuer = FakeBackendRPCIssuer() - AuthBackend.setTestRPCIssuer(issuer: rpcIssuer) + authBackend = AuthBackend(rpcIssuer: rpcIssuer) } override func tearDown() { rpcIssuer = nil - AuthBackend.resetRPCIssuer() + authBackend = nil } /** @fn checkRequest @@ -99,7 +100,7 @@ class RPCBaseTests: XCTestCase { // Dummy response to unblock await. let _ = try self.rpcIssuer?.respond(withJSON: [:]) } - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) } /** @fn checkBackendError @@ -123,7 +124,7 @@ class RPCBaseTests: XCTestCase { } } do { - let _ = try await AuthBackend.call(with: request) + let _ = try await authBackend.call(with: request) XCTFail("Did not throw expected error") return } catch { diff --git a/FirebaseAuth/Tests/Unit/ResetPasswordTests.swift b/FirebaseAuth/Tests/Unit/ResetPasswordTests.swift index 7fd7f346d3d..a3220f3ee41 100644 --- a/FirebaseAuth/Tests/Unit/ResetPasswordTests.swift +++ b/FirebaseAuth/Tests/Unit/ResetPasswordTests.swift @@ -82,7 +82,7 @@ class ResetPasswordTests: RPCBaseTests { try self.rpcIssuer?.respond(withJSON: ["email": kTestEmail, "requestType": kExpectedResetPasswordRequestType]) } - let rpcResponse = try await AuthBackend.call(with: makeResetPasswordRequest()) + let rpcResponse = try await authBackend.call(with: makeResetPasswordRequest()) XCTAssertEqual(rpcResponse.email, kTestEmail) XCTAssertEqual(rpcResponse.requestType, kExpectedResetPasswordRequestType) diff --git a/FirebaseAuth/Tests/Unit/RevokeTokenTests.swift b/FirebaseAuth/Tests/Unit/RevokeTokenTests.swift index 945e1e1ee1b..f55f42514d7 100644 --- a/FirebaseAuth/Tests/Unit/RevokeTokenTests.swift +++ b/FirebaseAuth/Tests/Unit/RevokeTokenTests.swift @@ -53,7 +53,7 @@ class RevokeTokenTests: RPCBaseTests { rpcIssuer.respondBlock = { try self.rpcIssuer?.respond(withJSON: [:]) } - let rpcResponse = try await AuthBackend.call(with: makeRevokeTokenRequest()) + let rpcResponse = try await authBackend.call(with: makeRevokeTokenRequest()) XCTAssertNotNil(rpcResponse) } diff --git a/FirebaseAuth/Tests/Unit/SendVerificationCodeTests.swift b/FirebaseAuth/Tests/Unit/SendVerificationCodeTests.swift index aa5a30686b6..d6b4b42bc14 100644 --- a/FirebaseAuth/Tests/Unit/SendVerificationCodeTests.swift +++ b/FirebaseAuth/Tests/Unit/SendVerificationCodeTests.swift @@ -113,7 +113,7 @@ class SendVerificationCodeTests: RPCBaseTests { rpcIssuer.respondBlock = { try self.rpcIssuer?.respond(withJSON: [kVerificationIDKey: kFakeVerificationID]) } - let rpcResponse = try await AuthBackend.call(with: + let rpcResponse = try await authBackend.call(with: makeSendVerificationCodeRequest(CodeIdentity.recaptcha(kTestReCAPTCHAToken))) XCTAssertNotNil(rpcResponse) XCTAssertEqual(rpcResponse.verificationID, kFakeVerificationID) diff --git a/FirebaseAuth/Tests/Unit/SetAccountInfoTests.swift b/FirebaseAuth/Tests/Unit/SetAccountInfoTests.swift index 4efe951991a..037116982de 100644 --- a/FirebaseAuth/Tests/Unit/SetAccountInfoTests.swift +++ b/FirebaseAuth/Tests/Unit/SetAccountInfoTests.swift @@ -212,7 +212,7 @@ class SetAccountInfoTests: RPCBaseTests { kExpiresInKey: kTestExpiresIn, kRefreshTokenKey: kTestRefreshToken]) } - let response = try await AuthBackend.call(with: setAccountInfoRequest()) + let response = try await authBackend.call(with: setAccountInfoRequest()) XCTAssertEqual(response.providerUserInfo?.first?.photoURL?.absoluteString, kTestPhotoURL) XCTAssertEqual(response.idToken, kTestIDToken) XCTAssertEqual(response.refreshToken, kTestRefreshToken) diff --git a/FirebaseAuth/Tests/Unit/SignInWithGameCenterTests.swift b/FirebaseAuth/Tests/Unit/SignInWithGameCenterTests.swift index 3513e9212c8..515cbd13e87 100644 --- a/FirebaseAuth/Tests/Unit/SignInWithGameCenterTests.swift +++ b/FirebaseAuth/Tests/Unit/SignInWithGameCenterTests.swift @@ -99,7 +99,7 @@ class SignInWithGameCenterTests: RPCBaseTests { "displayName": kDisplayName, ]) } - let rpcResponse = try await AuthBackend.call(with: request) + let rpcResponse = try await authBackend.call(with: request) XCTAssertNotNil(rpcResponse) XCTAssertEqual(rpcResponse.idToken, kIDToken) diff --git a/FirebaseAuth/Tests/Unit/SignUpNewUserTests.swift b/FirebaseAuth/Tests/Unit/SignUpNewUserTests.swift index 74c271bff6e..a75f6263a79 100644 --- a/FirebaseAuth/Tests/Unit/SignUpNewUserTests.swift +++ b/FirebaseAuth/Tests/Unit/SignUpNewUserTests.swift @@ -82,7 +82,7 @@ class SignUpNewUserTests: RPCBaseTests { kRefreshTokenKey: kTestRefreshToken, ]) } - let rpcResponse = try await AuthBackend.call(with: makeSignUpNewUserRequest()) + let rpcResponse = try await authBackend.call(with: makeSignUpNewUserRequest()) XCTAssertEqual(rpcResponse.refreshToken, kTestRefreshToken) let expiresIn = try XCTUnwrap(rpcResponse.approximateExpirationDate?.timeIntervalSinceNow) XCTAssertEqual(expiresIn, 12345, accuracy: 0.1) diff --git a/FirebaseAuth/Tests/Unit/UserTests.swift b/FirebaseAuth/Tests/Unit/UserTests.swift index 77c1449792f..d1f305f7116 100644 --- a/FirebaseAuth/Tests/Unit/UserTests.swift +++ b/FirebaseAuth/Tests/Unit/UserTests.swift @@ -33,12 +33,13 @@ class UserTests: RPCBaseTests { let kVerificationID = "55432" let kPhoneNumber = "555-1234" - static var auth: Auth? + var auth: Auth? - override class func setUp() { + override func setUp() { + super.setUp() let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000", gcmSenderID: "00000000000000000-00000000000-000000000") - options.apiKey = kFakeAPIKey + options.apiKey = Self.kFakeAPIKey options.projectID = "myUserProjectID" FirebaseApp.configure(name: "test-UserTests", options: options) #if (os(macOS) && !FIREBASE_AUTH_TESTING_USE_MACOS_KEYCHAIN) || SWIFT_PACKAGE @@ -48,13 +49,17 @@ class UserTests: RPCBaseTests { #endif // (os(macOS) && !FIREBASE_AUTH_TESTING_USE_MACOS_KEYCHAIN) || SWIFT_PACKAGE auth = Auth( app: FirebaseApp.app(name: "test-UserTests")!, - keychainStorageProvider: keychainStorageProvider + keychainStorageProvider: keychainStorageProvider, + backend: authBackend ) } override func tearDown() { // Verifies that no tasks are left suspended on the AuthSerialTaskQueue. - try? UserTests.auth?.signOut() + try? auth?.signOut() + auth = nil + FirebaseApp.resetApps() + super.tearDown() } /** @fn testUserPropertiesAndNSSecureCoding @@ -415,7 +420,7 @@ class UserTests: RPCBaseTests { // Email should not have changed on the client side. XCTAssertEqual(user.email, self.kEmail) // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -441,7 +446,7 @@ class UserTests: RPCBaseTests { // Email should not have changed on the client side. XCTAssertEqual(user.email, self.kEmail) // User is no longer signed in.. - XCTAssertNil(UserTests.auth?.currentUser) + XCTAssertNil(self.auth?.currentUser) expectation.fulfill() } } @@ -457,7 +462,7 @@ class UserTests: RPCBaseTests { func testUpdatePhoneSuccess() throws { setFakeGetAccountProvider() let expectation = self.expectation(description: #function) - let auth = try XCTUnwrap(UserTests.auth) + let auth = try XCTUnwrap(self.auth) signInWithEmailPasswordReturnFakeUser { user in do { self.rpcIssuer.respondBlock = { @@ -491,7 +496,7 @@ class UserTests: RPCBaseTests { func testUpdatePhoneNumberFailure() throws { setFakeGetAccountProvider() let expectation = self.expectation(description: #function) - let auth = try XCTUnwrap(UserTests.auth) + let auth = try XCTUnwrap(self.auth) signInWithEmailPasswordReturnFakeUser { user in do { self.rpcIssuer.respondBlock = { @@ -523,7 +528,7 @@ class UserTests: RPCBaseTests { func testUpdatePhoneNumberFailureAutoSignOut() throws { setFakeGetAccountProvider() let expectation = self.expectation(description: #function) - let auth = try XCTUnwrap(UserTests.auth) + let auth = try XCTUnwrap(self.auth) signInWithEmailPasswordReturnFakeUser { user in do { self.rpcIssuer.respondBlock = { @@ -540,7 +545,7 @@ class UserTests: RPCBaseTests { let error = try! XCTUnwrap(rawError) XCTAssertEqual((error as NSError).code, AuthErrorCode.userTokenExpired.rawValue) // User is no longer signed in. - XCTAssertNil(UserTests.auth?.currentUser) + XCTAssertNil(self.auth?.currentUser) expectation.fulfill() } } @@ -579,7 +584,7 @@ class UserTests: RPCBaseTests { // Email should not have changed on the client side. XCTAssertEqual(user.email, self.kEmail) // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -605,7 +610,7 @@ class UserTests: RPCBaseTests { // Email should not have changed on the client side. XCTAssertEqual(user.email, self.kEmail) // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -632,7 +637,7 @@ class UserTests: RPCBaseTests { // Email should not have changed on the client side. XCTAssertEqual(user.email, self.kEmail) // User is signed out. - XCTAssertNil(UserTests.auth?.currentUser) + XCTAssertNil(self.auth?.currentUser) expectation.fulfill() } } @@ -688,7 +693,7 @@ class UserTests: RPCBaseTests { XCTAssertEqual(user.email, self.kEmail) XCTAssertEqual(user.displayName, self.kDisplayName) // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -716,7 +721,7 @@ class UserTests: RPCBaseTests { // Email should not have changed on the client side. XCTAssertEqual(user.email, self.kEmail) // User is signed out. - XCTAssertNil(UserTests.auth?.currentUser) + XCTAssertNil(self.auth?.currentUser) expectation.fulfill() } } @@ -821,7 +826,7 @@ class UserTests: RPCBaseTests { let error = try! XCTUnwrap(rawError) XCTAssertEqual((error as NSError).code, AuthErrorCode.quotaExceeded.rawValue) // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -848,7 +853,7 @@ class UserTests: RPCBaseTests { let error = try! XCTUnwrap(rawError) XCTAssertEqual((error as NSError).code, AuthErrorCode.userTokenExpired.rawValue) // User is no longer signed in. - XCTAssertNil(UserTests.auth?.currentUser) + XCTAssertNil(self.auth?.currentUser) expectation.fulfill() } } @@ -878,7 +883,7 @@ class UserTests: RPCBaseTests { XCTAssertEqual(result.user.email, user.email) XCTAssertEqual(result.additionalUserInfo?.isNewUser, false) // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -914,7 +919,7 @@ class UserTests: RPCBaseTests { } XCTAssertNil(error) // Verify that the current user is unchanged. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) // Verify that the current user and reauthenticated user are not same pointers. XCTAssertNotEqual(user, reauthenticatedAuthResult?.user) // Verify that anyway the current user and reauthenticated user have same IDs. @@ -933,7 +938,7 @@ class UserTests: RPCBaseTests { } } waitForExpectations(timeout: 5) - try assertUserGoogle(UserTests.auth?.currentUser) + try assertUserGoogle(auth?.currentUser) } /** @fn testReauthenticateFailure @@ -958,7 +963,7 @@ class UserTests: RPCBaseTests { // Email should not have changed on the client side. XCTAssertEqual(user.email, self.kEmail) // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -987,7 +992,7 @@ class UserTests: RPCBaseTests { // Email should not have changed on the client side. XCTAssertEqual(user.email, self.kEmail) // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -1001,7 +1006,7 @@ class UserTests: RPCBaseTests { func testLinkAndRetrieveDataSuccess() throws { setFakeGetAccountProvider() let expectation = self.expectation(description: #function) - let auth = try XCTUnwrap(UserTests.auth) + let auth = try XCTUnwrap(self.auth) signInWithFacebookCredential { user in XCTAssertNotNil(user) do { @@ -1069,7 +1074,7 @@ class UserTests: RPCBaseTests { // Email should not have changed on the client side. XCTAssertEqual(user.email, self.kFacebookEmail) // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -1100,7 +1105,7 @@ class UserTests: RPCBaseTests { XCTFail("Expected to throw providerAlreadyLinked error.") } // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -1130,7 +1135,7 @@ class UserTests: RPCBaseTests { let error = try! XCTUnwrap(rawError) XCTAssertEqual((error as NSError).code, AuthErrorCode.userDisabled.rawValue) // User is signed out. - XCTAssertNil(UserTests.auth?.currentUser) + XCTAssertNil(self.auth?.currentUser) expectation.fulfill() } } @@ -1145,7 +1150,7 @@ class UserTests: RPCBaseTests { func testLinkEmailAndRetrieveDataSuccess() throws { setFakeGetAccountProvider() let expectation = self.expectation(description: #function) - let auth = try XCTUnwrap(UserTests.auth) + let auth = try XCTUnwrap(self.auth) signInWithFacebookCredential { user in XCTAssertNotNil(user) do { @@ -1213,7 +1218,7 @@ class UserTests: RPCBaseTests { XCTFail("Expected to throw providerAlreadyLinked error.") } // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -1247,7 +1252,7 @@ class UserTests: RPCBaseTests { let error = try! XCTUnwrap(rawError) XCTAssertEqual((error as NSError).code, AuthErrorCode.tooManyRequests.rawValue) // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -1277,7 +1282,7 @@ class UserTests: RPCBaseTests { let error = try! XCTUnwrap(rawError) XCTAssertEqual((error as NSError).code, AuthErrorCode.userTokenExpired.rawValue) // User is signed out. - XCTAssertNil(UserTests.auth?.currentUser) + XCTAssertNil(self.auth?.currentUser) expectation.fulfill() } } @@ -1307,7 +1312,7 @@ class UserTests: RPCBaseTests { func testLinkProviderFailure() throws { setFakeGetAccountProvider() let expectation = self.expectation(description: #function) - let auth = try XCTUnwrap(UserTests.auth) + let auth = try XCTUnwrap(self.auth) signInWithFacebookCredential { user in XCTAssertNotNil(user) do { @@ -1322,7 +1327,7 @@ class UserTests: RPCBaseTests { let error = try! XCTUnwrap(rawError) XCTAssertEqual((error as NSError).code, AuthErrorCode.userTokenExpired.rawValue) // User is signed out. - XCTAssertNil(UserTests.auth?.currentUser) + XCTAssertNil(self.auth?.currentUser) expectation.fulfill() } } @@ -1336,7 +1341,7 @@ class UserTests: RPCBaseTests { func testReauthenticateWithProviderFailure() throws { setFakeGetAccountProvider() let expectation = self.expectation(description: #function) - let auth = try XCTUnwrap(UserTests.auth) + let auth = try XCTUnwrap(self.auth) signInWithFacebookCredential { user in XCTAssertNotNil(user) do { @@ -1351,7 +1356,7 @@ class UserTests: RPCBaseTests { let error = try! XCTUnwrap(rawError) XCTAssertEqual((error as NSError).code, AuthErrorCode.userTokenExpired.rawValue) // User is still signed in. - XCTAssertEqual(UserTests.auth?.currentUser, user) + XCTAssertEqual(self.auth?.currentUser, user) expectation.fulfill() } } @@ -1365,7 +1370,7 @@ class UserTests: RPCBaseTests { func testLinkPhoneAuthCredentialSuccess() throws { setFakeGetAccountProvider() let expectation = self.expectation(description: #function) - let auth = try XCTUnwrap(UserTests.auth) + let auth = try XCTUnwrap(self.auth) signInWithEmailPasswordReturnFakeUser { user in XCTAssertNotNil(user) self.expectVerifyPhoneNumberRequest(isLink: true) @@ -1410,7 +1415,7 @@ class UserTests: RPCBaseTests { func testUnlinkPhoneAuthCredentialSuccess() throws { setFakeGetAccountProvider() let expectation = self.expectation(description: #function) - let auth = try XCTUnwrap(UserTests.auth) + let auth = try XCTUnwrap(self.auth) signInWithEmailPasswordReturnFakeUser { user in XCTAssertNotNil(user) self.expectVerifyPhoneNumberRequest(isLink: true) @@ -1506,7 +1511,7 @@ class UserTests: RPCBaseTests { func testlinkPhoneCredentialAlreadyExistsError() throws { setFakeGetAccountProvider() let expectation = self.expectation(description: #function) - let auth = try XCTUnwrap(UserTests.auth) + let auth = try XCTUnwrap(self.auth) signInWithEmailPasswordReturnFakeUser { user in XCTAssertNotNil(user) self.expectVerifyPhoneNumberRequest(isLink: true) @@ -1668,12 +1673,12 @@ class UserTests: RPCBaseTests { } // 1. After setting up fakes, sign out and sign in. do { - try UserTests.auth?.signOut() + try auth?.signOut() } catch { XCTFail("Sign out failed: \(error)") return } - UserTests.auth?.signIn(withEmail: kEmail, password: kFakePassword) { authResult, error in + auth?.signIn(withEmail: kEmail, password: kFakePassword) { authResult, error in // 4. After the response triggers the callback, verify the returned result. XCTAssertTrue(Thread.isMainThread) guard let user = authResult?.user else { @@ -1716,10 +1721,10 @@ class UserTests: RPCBaseTests { } do { - try UserTests.auth?.signOut() + try auth?.signOut() let googleCredential = GoogleAuthProvider.credential(withIDToken: kGoogleIDToken, accessToken: kGoogleAccessToken) - UserTests.auth?.signIn(with: googleCredential) { authResult, error in + auth?.signIn(with: googleCredential) { authResult, error in // 4. After the response triggers the callback, verify the returned result. XCTAssertTrue(Thread.isMainThread) guard let user = authResult?.user else { @@ -1783,10 +1788,10 @@ class UserTests: RPCBaseTests { } do { - try UserTests.auth?.signOut() + try auth?.signOut() let facebookCredential = FacebookAuthProvider .credential(withAccessToken: kFacebookAccessToken) - UserTests.auth?.signIn(with: facebookCredential) { authResult, error in + auth?.signIn(with: facebookCredential) { authResult, error in // 4. After the response triggers the callback, verify the returned result. XCTAssertTrue(Thread.isMainThread) guard let user = authResult?.user else { @@ -1838,8 +1843,8 @@ class UserTests: RPCBaseTests { } do { - try UserTests.auth?.signOut() - UserTests.auth?.signIn( + try auth?.signOut() + auth?.signIn( withEmail: kEmail, link: "https://www.google.com?oobCode=aCode&mode=signIn" ) { authResult, error in diff --git a/FirebaseAuth/Tests/Unit/VerifyAssertionTests.swift b/FirebaseAuth/Tests/Unit/VerifyAssertionTests.swift index 95e131a8760..2fb630a62ce 100644 --- a/FirebaseAuth/Tests/Unit/VerifyAssertionTests.swift +++ b/FirebaseAuth/Tests/Unit/VerifyAssertionTests.swift @@ -180,7 +180,7 @@ class VerifyAssertionTests: RPCBaseTests { self.kRawUserInfoKey: self.profile, ]) } - let rpcResponse = try await AuthBackend.call(with: makeVerifyAssertionRequest()) + let rpcResponse = try await authBackend.call(with: makeVerifyAssertionRequest()) XCTAssertEqual(rpcResponse.idToken, kTestIDToken) XCTAssertEqual(rpcResponse.refreshToken, kTestRefreshToken) XCTAssertEqual(rpcResponse.verifiedProvider, [kTestProvider]) @@ -211,7 +211,7 @@ class VerifyAssertionTests: RPCBaseTests { self.kRawUserInfoKey: self.convertToJson(self.profile), ]) } - let rpcResponse = try await AuthBackend.call(with: makeVerifyAssertionRequest()) + let rpcResponse = try await authBackend.call(with: makeVerifyAssertionRequest()) XCTAssertEqual(rpcResponse.idToken, kTestIDToken) XCTAssertEqual(rpcResponse.refreshToken, kTestRefreshToken) XCTAssertEqual(rpcResponse.verifiedProvider, [kTestProvider]) diff --git a/FirebaseAuth/Tests/Unit/VerifyClientTests.swift b/FirebaseAuth/Tests/Unit/VerifyClientTests.swift index 0f7b0c792ca..528cb357c88 100644 --- a/FirebaseAuth/Tests/Unit/VerifyClientTests.swift +++ b/FirebaseAuth/Tests/Unit/VerifyClientTests.swift @@ -71,7 +71,7 @@ class VerifyClientTests: RPCBaseTests { kSuggestedTimeOutKey: kFakeSuggestedTimeout, ]) } - let rpcResponse = try await AuthBackend.call(with: makeVerifyClientRequest()) + let rpcResponse = try await authBackend.call(with: makeVerifyClientRequest()) XCTAssertEqual(rpcResponse.receipt, kFakeReceipt) let timeOut = try XCTUnwrap(rpcResponse.suggestedTimeOutDate?.timeIntervalSinceNow) XCTAssertEqual(timeOut, 1234, accuracy: 0.1) diff --git a/FirebaseAuth/Tests/Unit/VerifyCustomTokenTests.swift b/FirebaseAuth/Tests/Unit/VerifyCustomTokenTests.swift index 36fb06d13e6..8e3e052a4b2 100644 --- a/FirebaseAuth/Tests/Unit/VerifyCustomTokenTests.swift +++ b/FirebaseAuth/Tests/Unit/VerifyCustomTokenTests.swift @@ -107,7 +107,7 @@ class VerifyCustomTokenTests: RPCBaseTests { kIsNewUserKey: true, ]) } - let rpcResponse = try await AuthBackend.call(with: makeVerifyCustomTokenRequest()) + let rpcResponse = try await authBackend.call(with: makeVerifyCustomTokenRequest()) XCTAssertEqual(rpcResponse.idToken, kTestIDToken) XCTAssertEqual(rpcResponse.refreshToken, kTestRefreshToken) let expiresIn = try XCTUnwrap(rpcResponse.approximateExpirationDate?.timeIntervalSinceNow) diff --git a/FirebaseAuth/Tests/Unit/VerifyPasswordTests.swift b/FirebaseAuth/Tests/Unit/VerifyPasswordTests.swift index 6b831f0ce80..06821bf74c2 100644 --- a/FirebaseAuth/Tests/Unit/VerifyPasswordTests.swift +++ b/FirebaseAuth/Tests/Unit/VerifyPasswordTests.swift @@ -171,7 +171,7 @@ class VerifyPasswordTests: RPCBaseTests { kPhotoUrlKey: kTestPhotoUrl, ]) } - let rpcResponse = try await AuthBackend.call(with: makeVerifyPasswordRequest()) + let rpcResponse = try await authBackend.call(with: makeVerifyPasswordRequest()) XCTAssertEqual(rpcResponse.email, kTestEmail) XCTAssertEqual(rpcResponse.localID, kTestLocalID) XCTAssertEqual(rpcResponse.displayName, kTestDisplayName) diff --git a/FirebaseAuth/Tests/Unit/VerifyPhoneNumberTests.swift b/FirebaseAuth/Tests/Unit/VerifyPhoneNumberTests.swift index e6ff2a42434..50fb9cf67e5 100644 --- a/FirebaseAuth/Tests/Unit/VerifyPhoneNumberTests.swift +++ b/FirebaseAuth/Tests/Unit/VerifyPhoneNumberTests.swift @@ -116,7 +116,7 @@ import XCTest "isNewUser": true, ]) } - let rpcResponse = try await AuthBackend.call(with: makeVerifyPhoneNumberRequest()) + let rpcResponse = try await authBackend.call(with: makeVerifyPhoneNumberRequest()) XCTAssertEqual(rpcResponse.localID, kTestLocalID) XCTAssertEqual(rpcResponse.idToken, kTestIDToken) let expiresIn = try XCTUnwrap(rpcResponse.approximateExpirationDate?.timeIntervalSinceNow) @@ -135,7 +135,7 @@ import XCTest ]) } do { - let _ = try await AuthBackend.call(with: makeVerifyPhoneNumberRequestWithTemporaryProof()) + let _ = try await authBackend.call(with: makeVerifyPhoneNumberRequestWithTemporaryProof()) XCTFail("Expected to throw") } catch { let rpcError = error as NSError From da4ecf15618a3f8a9320ccf38a78ba7803727e33 Mon Sep 17 00:00:00 2001 From: Nick Cooke Date: Tue, 12 Nov 2024 16:02:29 -0500 Subject: [PATCH 3/3] Fix post merge issue --- FirebaseAuth/Sources/Swift/User/User.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FirebaseAuth/Sources/Swift/User/User.swift b/FirebaseAuth/Sources/Swift/User/User.swift index 29dad562b68..fccd96ab088 100644 --- a/FirebaseAuth/Sources/Swift/User/User.swift +++ b/FirebaseAuth/Sources/Swift/User/User.swift @@ -1763,6 +1763,10 @@ extension User: NSSecureCoding {} ) as? String requestConfiguration = AuthRequestConfiguration(apiKey: apiKey ?? "", appID: appID ?? "") + // This property will be overwritten later via the `user.auth` property update. For now, a + // placeholder is set as the property update should happen right after this intializer. + backend = AuthBackend(rpcIssuer: AuthBackendRPCIssuer()) + userProfileUpdate = UserProfileUpdate() #if os(iOS) self.multiFactor = multiFactor ?? MultiFactor()