From ba843d5a88af0fc39c88ce790aadc07a9905a884 Mon Sep 17 00:00:00 2001 From: Liubin Jiang <56564857+Xiaoshouzi-gh@users.noreply.github.com> Date: Wed, 13 Nov 2024 11:24:51 -0800 Subject: [PATCH] Add rCE support for phone MFA enrollment and sign-in (rce-main #2) (#14081) --- .../AuthProvider/PhoneAuthProvider.swift | 6 ++-- .../Sources/Swift/Backend/AuthBackend.swift | 2 ++ .../Enroll/StartMFAEnrollmentRequest.swift | 23 +++++++++++++++ .../SignIn/StartMFASignInRequest.swift | 11 ++++++++ .../AuthProtoStartMFAPhoneRequestInfo.swift | 28 +++++++++++++++++++ .../Utilities/AuthRecaptchaVerifier.swift | 4 +-- 6 files changed, 69 insertions(+), 5 deletions(-) diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift index 9bd0094dcbe..9628876c8c2 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift @@ -199,7 +199,7 @@ import Foundation } let recaptchaVerifier = AuthRecaptchaVerifier.shared(auth: auth) - try await recaptchaVerifier.retrieveRecaptchaConfig(forceRefresh: false) + try await recaptchaVerifier.retrieveRecaptchaConfig(forceRefresh: true) switch recaptchaVerifier.enablementStatus(forProvider: .phone) { case .off: @@ -321,7 +321,7 @@ import Foundation try await recaptchaVerifier.injectRecaptchaFields( request: request, provider: .phone, - action: .startMfaEnrollment + action: .mfaSmsEnrollment ) let response = try await AuthBackend.call(with: request) return response.phoneSessionInfo?.sessionInfo @@ -333,7 +333,7 @@ import Foundation try await recaptchaVerifier.injectRecaptchaFields( request: request, provider: .phone, - action: .startMfaSignin + action: .mfaSmsSignIn ) let response = try await AuthBackend.call(with: request) return response.responseInfo?.sessionInfo diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 85802392052..14d8886cbd1 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -172,6 +172,7 @@ class AuthBackend: AuthBackendProtocol { // perhaps be modeled differently so that the failing unencodedHTTPRequestBody could only // be called when a body exists... postBody = try request.unencodedHTTPRequestBody() + } catch { throw AuthErrorUtils.RPCRequestEncodingError(underlyingError: error) } @@ -187,6 +188,7 @@ class AuthBackend: AuthBackendProtocol { withJSONObject: postBody, options: JSONWritingOptions ) + if bodyData == nil { // This is an untested case. This happens exclusively when there is an error in the // framework implementation of dataWithJSONObject:options:error:. This shouldn't normally diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift index 3896ec14b3d..1a89dad6c3a 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift @@ -16,6 +16,18 @@ import Foundation private let kStartMFAEnrollmentEndPoint = "accounts/mfaEnrollment:start" +/// The key for the "clientType" value in the request. +private let kClientType = "clientType" + +/// The key for the reCAPTCHAToken parameter in the request. +private let kreCAPTCHATokenKey = "recaptchaToken" + +/// The key for the "captchaResponse" value in the request. +private let kCaptchaResponseKey = "captchaResponse" + +/// The key for the "recaptchaVersion" value in the request. +private let kRecaptchaVersion = "recaptchaVersion" + /// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" @@ -79,4 +91,15 @@ class StartMFAEnrollmentRequest: IdentityToolkitRequest, AuthRPCRequest { } return body } + + func injectRecaptchaFields(recaptchaResponse: String?, recaptchaVersion: String) { + // reCAPTCHA check is only available for phone based MFA + if let phoneEnrollmentInfo { + phoneEnrollmentInfo.injectRecaptchaFields( + recaptchaResponse: recaptchaResponse, + recaptchaVersion: recaptchaVersion, + clientType: clientType + ) + } + } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift index 1098c2ef4ea..78deb836896 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift @@ -57,4 +57,15 @@ class StartMFASignInRequest: IdentityToolkitRequest, AuthRPCRequest { } return body } + + func injectRecaptchaFields(recaptchaResponse: String?, recaptchaVersion: String) { + // reCAPTCHA check is only available for phone based MFA + if let signInInfo { + signInInfo.injectRecaptchaFields( + recaptchaResponse: recaptchaResponse, + recaptchaVersion: recaptchaVersion, + clientType: clientType + ) + } + } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneRequestInfo.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneRequestInfo.swift index 274a7b97883..87d0d03f5be 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneRequestInfo.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneRequestInfo.swift @@ -26,6 +26,15 @@ private let kSecretKey = "iosSecret" /// The key for the reCAPTCHAToken parameter in the request. private let kreCAPTCHATokenKey = "recaptchaToken" +/// The key for the "captchaResponse" value in the request. +private let kCaptchaResponseKey = "captchaResponse" + +/// The key for the "recaptchaVersion" value in the request. +private let kRecaptchaVersion = "recaptchaVersion" + +/// The key for the "clientType" value in the request. +private let kClientType = "clientType" + class AuthProtoStartMFAPhoneRequestInfo: NSObject, AuthProto { required init(dictionary: [String: AnyHashable]) { fatalError() @@ -33,6 +42,9 @@ class AuthProtoStartMFAPhoneRequestInfo: NSObject, AuthProto { var phoneNumber: String? var codeIdentity: CodeIdentity + var captchaResponse: String? + var recaptchaVersion: String? + var clientType: String? init(phoneNumber: String?, codeIdentity: CodeIdentity) { self.phoneNumber = phoneNumber self.codeIdentity = codeIdentity @@ -43,6 +55,15 @@ class AuthProtoStartMFAPhoneRequestInfo: NSObject, AuthProto { if let phoneNumber = phoneNumber { dict[kPhoneNumberKey] = phoneNumber } + if let captchaResponse = captchaResponse { + dict[kCaptchaResponseKey] = captchaResponse + } + if let recaptchaVersion = recaptchaVersion { + dict[kRecaptchaVersion] = recaptchaVersion + } + if let clientType = clientType { + dict[kClientType] = clientType + } switch codeIdentity { case let .credential(appCredential): dict[kReceiptKey] = appCredential.receipt @@ -54,4 +75,11 @@ class AuthProtoStartMFAPhoneRequestInfo: NSObject, AuthProto { } return dict } + + func injectRecaptchaFields(recaptchaResponse: String?, recaptchaVersion: String, + clientType: String?) { + captchaResponse = recaptchaResponse + self.recaptchaVersion = recaptchaVersion + self.clientType = clientType + } } diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift index 20ab4a6e397..e817f6b6631 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthRecaptchaVerifier.swift @@ -59,8 +59,8 @@ case getOobCode case signUpPassword case sendVerificationCode - case startMfaSignin - case startMfaEnrollment + case mfaSmsSignIn + case mfaSmsEnrollment // Convenience property for mapping values var stringValue: String { rawValue }