diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift index 4fc3b642024..2b5bab1209e 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift @@ -274,14 +274,13 @@ import Foundation let startMFARequestInfo = AuthProtoStartMFAPhoneRequestInfo(phoneNumber: phoneNumber, codeIdentity: codeIdentity) do { - switch codeIdentity { - case .credential: - let request = StartMFAEnrollmentRequest(idToken: session.idToken, + if let idToken = session.idToken { + let request = StartMFAEnrollmentRequest(idToken: idToken, enrollmentInfo: startMFARequestInfo, requestConfiguration: auth.requestConfiguration) let response = try await AuthBackend.call(with: request) return response.phoneSessionInfo?.sessionInfo - case .recaptcha: + } else { let request = StartMFASignInRequest(MFAPendingCredential: session.mfaPendingCredential, MFAEnrollmentID: session.multiFactorInfo?.uid, signInInfo: startMFARequestInfo, @@ -289,8 +288,6 @@ import Foundation let response = try await AuthBackend.call(with: request) return response.responseInfo?.sessionInfo - case .empty: - return nil } } catch { return try await handleVerifyErrorWithRetry( diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 127ef3bfc96..481d80de518 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -185,18 +185,16 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation #if os(iOS) private class func generateMFAError(response: AuthRPCResponse, auth: Auth) -> Error? { - if let mfaResponse = response as? EmailLinkSignInResponse, + if let mfaResponse = response as? AuthMFAResponse, mfaResponse.idToken == nil, let enrollments = mfaResponse.mfaInfo { var info: [MultiFactorInfo] = [] for enrollment in enrollments { // check which MFA factors are enabled. if let _ = enrollment.phoneInfo { - info.append(MultiFactorInfo(proto: enrollment, - factorID: PhoneMultiFactorInfo.PhoneMultiFactorID)) + info.append(PhoneMultiFactorInfo(proto: enrollment)) } else if let _ = enrollment.totpInfo { - info.append(MultiFactorInfo(proto: enrollment, - factorID: PhoneMultiFactorInfo.TOTPMultiFactorID)) + info.append(TOTPMultiFactorInfo(proto: enrollment)) } else { AuthLog.logError(code: "I-AUT000021", message: "Multifactor type is not supported") } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/AuthMFAResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/AuthMFAResponse.swift new file mode 100644 index 00000000000..6ebfbae80ac --- /dev/null +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/AuthMFAResponse.swift @@ -0,0 +1,28 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +/// Protocol for responses that support Multi-Factor Authentication. +protocol AuthMFAResponse { + /// An opaque string that functions as proof that the user has successfully passed the first + /// factor check. + var mfaPendingCredential: String? { get } + + /// Info on which multi-factor authentication providers are enabled. + var mfaInfo: [AuthProtoMFAEnrollment]? { get } + + /// MFA is only done when the idToken is nil. + var idToken: String? { get } +} diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift index c6b51d349b2..f41d0bde732 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift @@ -17,13 +17,13 @@ import Foundation /** @class FIRVerifyAssertionResponse @brief Represents the response from the emailLinkSignin endpoint. */ -class EmailLinkSignInResponse: NSObject, AuthRPCResponse { +class EmailLinkSignInResponse: NSObject, AuthRPCResponse, AuthMFAResponse { override required init() {} /** @property IDToken @brief The ID token in the email link sign-in response. */ - var idToken: String? + private(set) var idToken: String? /** @property email @brief The email returned by the IdP. @@ -45,16 +45,18 @@ class EmailLinkSignInResponse: NSObject, AuthRPCResponse { */ var isNewUser: Bool = false + // MARK: - AuthMFAResponse + /** @property MFAPendingCredential @brief An opaque string that functions as proof that the user has successfully passed the first factor check. */ - var mfaPendingCredential: String? + private(set) var mfaPendingCredential: String? /** @property MFAInfo @brief Info on which multi-factor authentication providers are enabled. */ - var mfaInfo: [AuthProtoMFAEnrollment]? + private(set) var mfaInfo: [AuthProtoMFAEnrollment]? func setFields(dictionary: [String: AnyHashable]) throws { email = dictionary["email"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift index 90fb04c5213..b8610028df5 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift @@ -18,7 +18,7 @@ import Foundation @brief Represents the response from the verifyAssertion endpoint. @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyAssertion */ -class VerifyAssertionResponse: AuthRPCResponse { +class VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { required init() {} /** @property federatedID @@ -97,7 +97,7 @@ class VerifyAssertionResponse: AuthRPCResponse { access token from Secure Token Service, depending on whether @c returnSecureToken is set on the request. */ - var idToken: String? + private(set) var idToken: String? /** @property approximateExpirationDate @brief The approximate expiration date of the access token. @@ -201,9 +201,11 @@ class VerifyAssertionResponse: AuthRPCResponse { */ var pendingToken: String? - var MFAPendingCredential: String? + // MARK: - AuthMFAResponse - var MFAInfo: [AuthProtoMFAEnrollment]? + private(set) var mfaPendingCredential: String? + + private(set) var mfaInfo: [AuthProtoMFAEnrollment]? func setFields(dictionary: [String: AnyHashable]) throws { federatedID = dictionary["federatedId"] as? String @@ -267,10 +269,10 @@ class VerifyAssertionResponse: AuthRPCResponse { pendingToken = dictionary["pendingToken"] as? String if let mfaInfoDicts = dictionary["mfaInfo"] as? [[String: AnyHashable]] { - MFAInfo = mfaInfoDicts.map { + mfaInfo = mfaInfoDicts.map { AuthProtoMFAEnrollment(dictionary: $0) } } - MFAPendingCredential = dictionary["mfaPendingCredential"] as? String + mfaPendingCredential = dictionary["mfaPendingCredential"] as? String } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift index 1ff37380380..8a5e03262c6 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift @@ -21,7 +21,7 @@ import Foundation - FIRAuthInternalErrorCodeEmailNotFound @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword */ -class VerifyPasswordResponse: AuthRPCResponse { +class VerifyPasswordResponse: AuthRPCResponse, AuthMFAResponse { required init() {} /** @property localID @@ -45,7 +45,7 @@ class VerifyPasswordResponse: AuthRPCResponse { access token from Secure Token Service, depending on whether @c returnSecureToken is set on the request. */ - var idToken: String? + private(set) var idToken: String? /** @property approximateExpirationDate @brief The approximate expiration date of the access token. @@ -62,9 +62,11 @@ class VerifyPasswordResponse: AuthRPCResponse { */ var photoURL: URL? - var mfaPendingCredential: String? + // MARK: - AuthMFAResponse - var mfaInfo: [AuthProtoMFAEnrollment]? + private(set) var mfaPendingCredential: String? + + private(set) var mfaInfo: [AuthProtoMFAEnrollment]? func setFields(dictionary: [String: AnyHashable]) throws { localID = dictionary["localId"] as? String diff --git a/FirebaseAuth/Sources/Swift/User/User.swift b/FirebaseAuth/Sources/Swift/User/User.swift index 768a0f6cede..86a05d0f27b 100644 --- a/FirebaseAuth/Sources/Swift/User/User.swift +++ b/FirebaseAuth/Sources/Swift/User/User.swift @@ -334,7 +334,7 @@ extension User: NSSecureCoding {} @remarks See `AuthErrors` for a list of error codes that are common to all API methods. */ - @objc public func reload(withCompletion completion: ((Error?) -> Void)? = nil) { + @objc public func reload(completion: ((Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { self.getAccountInfoRefreshingCache { user, error in User.callInMainThreadWithError(callback: completion, error: error) @@ -1023,7 +1023,7 @@ extension User: NSSecureCoding {} */ @objc(sendEmailVerificationWithCompletion:) public func __sendEmailVerification(withCompletion completion: ((Error?) -> Void)?) { - sendEmailVerification(withCompletion: completion) + sendEmailVerification(completion: completion) } /** @fn sendEmailVerificationWithActionCodeSettings:completion: @@ -1052,7 +1052,7 @@ extension User: NSSecureCoding {} */ @objc(sendEmailVerificationWithActionCodeSettings:completion:) public func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil, - withCompletion completion: ((Error?) -> Void)? = nil) { + completion: ((Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { self.internalGetToken { accessToken, error in if let error { @@ -1136,7 +1136,7 @@ extension User: NSSecureCoding {} @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. */ - @objc public func delete(withCompletion completion: ((Error?) -> Void)? = nil) { + @objc public func delete(completion: ((Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { self.internalGetToken { accessToken, error in if let error { diff --git a/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift b/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift index a7b5613bb44..e06df5fb5df 100644 --- a/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift +++ b/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift @@ -65,7 +65,7 @@ import Foundation @param completion Optionally; the block invoked when the user profile change has been applied. Invoked asynchronously on the main thread in the future. */ - @objc public func commitChanges(withCompletion completion: ((Error?) -> Void)? = nil) { + @objc public func commitChanges(completion: ((Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { if self.consumed { fatalError("Internal Auth Error: commitChanges should only be called once.")