| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099 |
- // 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.
- #if os(iOS)
- import Foundation
- import XCTest
- @testable import FirebaseAuth
- import FirebaseCore
- import SafariServices
- @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
- class PhoneAuthProviderTests: RPCBaseTests {
- static let kFakeAuthorizedDomain = "test.firebaseapp.com"
- static let kFakeAPIKey = "asdfghjkl"
- static let kFakeEmulatorHost = "emulatorhost"
- static let kFakeEmulatorPort = 12345
- static let kFakeClientID = "123456.apps.googleusercontent.com"
- static let kFakeFirebaseAppID = "1:123456789:ios:123abc456def"
- static let kFakeEncodedFirebaseAppID = "app-1-123456789-ios-123abc456def"
- static let kFakeTenantID = "tenantID"
- static let kFakeReverseClientID = "com.googleusercontent.apps.123456"
- private let kTestVerificationID = "verificationID"
- private let kTestVerificationCode = "verificationCode"
- private let kTestReceipt = "receipt"
- private let kTestTimeout = "1"
- private let kTestSecret = "secret"
- private let kVerificationIDKey = "sessionInfo"
- private let kFakeEncodedFirebaseAppID = "app-1-123456789-ios-123abc456def"
- private let kFakeReCAPTCHAToken = "fakeReCAPTCHAToken"
- private let kCaptchaResponse: String = "captchaResponse"
- private let kRecaptchaVersion: String = "RECAPTCHA_ENTERPRISE"
- static var auth: Auth?
- /** @fn testCredentialWithVerificationID
- @brief Tests the @c credentialWithToken method to make sure that it returns a valid AuthCredential instance.
- */
- func testCredentialWithVerificationID() throws {
- initApp(#function)
- let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
- let provider = PhoneAuthProvider.provider(auth: auth)
- let credential = provider.credential(withVerificationID: kTestVerificationID,
- verificationCode: kTestVerificationCode)
- switch credential.credentialKind {
- case .phoneNumber: XCTFail("Should be verification case")
- case let .verification(id, code):
- XCTAssertEqual(id, kTestVerificationID)
- XCTAssertEqual(code, kTestVerificationCode)
- }
- }
- /** @fn testVerifyEmptyPhoneNumber
- @brief Tests a failed invocation verifyPhoneNumber because an empty phone
- number was provided.
- */
- func testVerifyEmptyPhoneNumber() async throws {
- initApp(#function)
- let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
- let provider = PhoneAuthProvider.provider(auth: auth)
- do {
- _ = try await provider.verifyPhoneNumber("")
- XCTFail("Expected an error, but verification succeeded.")
- } catch {
- XCTAssertEqual((error as NSError).code, AuthErrorCode.missingPhoneNumber.rawValue)
- }
- }
- /** @fn testVerifyInvalidPhoneNumber
- @brief Tests a failed invocation @c verifyPhoneNumber:completion: because an invalid phone
- number was provided.
- */
- func testVerifyInvalidPhoneNumber() async throws {
- try await internalTestVerify(errorString: "INVALID_PHONE_NUMBER",
- errorCode: AuthErrorCode.invalidPhoneNumber.rawValue,
- function: #function)
- }
- /** @fn testVerifyPhoneNumber
- @brief Tests a successful invocation of @c verifyPhoneNumber:completion:.
- */
- func testVerifyPhoneNumber() async throws {
- try await internalTestVerify(function: #function)
- }
- /**
- @fn testVerifyPhoneNumberWithRceEnforce
- @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced
- */
- func testVerifyPhoneNumberWithRceEnforceSuccess() async throws {
- initApp(#function)
- let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
- // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response
- let provider = PhoneAuthProvider.provider(auth: auth)
- let mockVerifier = FakeAuthRecaptchaVerifier(captchaResponse: kCaptchaResponse)
- AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth)
- rpcIssuer.rceMode = "ENFORCE"
- let requestExpectation = expectation(description: "verifyRequester")
- rpcIssuer.verifyRequester = { request in
- XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
- XCTAssertEqual(request.captchaResponse, self.kCaptchaResponse)
- XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE")
- XCTAssertEqual(request.codeIdentity, CodeIdentity.empty)
- requestExpectation.fulfill()
- do {
- return try self.rpcIssuer
- .respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID])
- } catch {
- XCTFail("Failure sending response: \(error)")
- return (nil, nil)
- }
- }
- do {
- let result = try await provider.verifyClAndSendVerificationCodeWithRecaptcha(
- toPhoneNumber: kTestPhoneNumber,
- retryOnInvalidAppCredential: false,
- uiDelegate: nil,
- recaptchaVerifier: mockVerifier
- )
- XCTAssertEqual(result, kTestVerificationID)
- } catch {
- XCTFail("Unexpected error")
- }
- await fulfillment(of: [requestExpectation], timeout: 5.0)
- }
- /**
- @fn testVerifyPhoneNumberWithRceEnforceInvalidRecaptcha
- @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced
- */
- func testVerifyPhoneNumberWithRceEnforceInvalidRecaptcha() async throws {
- initApp(#function)
- let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
- // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response
- let provider = PhoneAuthProvider.provider(auth: auth)
- let mockVerifier = FakeAuthRecaptchaVerifier()
- AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth)
- rpcIssuer.rceMode = "ENFORCE"
- let requestExpectation = expectation(description: "verifyRequester")
- rpcIssuer?.verifyRequester = { request in
- XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
- XCTAssertEqual(request.captchaResponse, "NO_RECAPTCHA")
- XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE")
- XCTAssertEqual(request.codeIdentity, CodeIdentity.empty)
- requestExpectation.fulfill()
- do {
- return try self.rpcIssuer
- .respond(serverErrorMessage: "INVALID_RECAPTCHA_TOKEN",
- error: AuthErrorUtils.invalidRecaptchaTokenError() as NSError)
- } catch {
- XCTFail("Failure sending response: \(error)")
- return (nil, nil)
- }
- }
- do {
- _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha(
- toPhoneNumber: kTestPhoneNumber,
- retryOnInvalidAppCredential: false,
- uiDelegate: nil,
- recaptchaVerifier: mockVerifier
- )
- // XCTAssertEqual(result, kTestVerificationID)
- } catch {
- // Traverse the nested error to find the root cause
- let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError
- let rootError = underlyingError?.userInfo[NSUnderlyingErrorKey] as? NSError
- // Compare the root error code to the expected error code
- XCTAssertEqual(rootError?.code, AuthErrorCode.invalidRecaptchaToken.code.rawValue)
- }
- await fulfillment(of: [requestExpectation], timeout: 5.0)
- }
- /**
- @fn testVerifyPhoneNumberWithRceEnforceSDKNotLinked
- @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced
- */
- func testVerifyPhoneNumberWithRceEnforceRecaptchaSDKNotLinked() async throws {
- return try await testRecaptchaFlowError(
- function: #function,
- rceError: AuthErrorUtils.recaptchaSDKNotLinkedError()
- )
- }
- /**
- @fn testVerifyPhoneNumberWithRceEnforceSDKNotLinked
- @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced
- */
- func testVerifyPhoneNumberWithRceEnforceRecaptchaActionCreationFailed() async throws {
- return try await testRecaptchaFlowError(
- function: #function,
- rceError: AuthErrorUtils.recaptchaActionCreationFailed()
- )
- }
- /// @fn testVerifyPhoneNumberWithRceAudit
- /// @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise in
- /// audit mode
- func testVerifyPhoneNumberWithRceAuditSuccess() async throws {
- initApp(#function)
- let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
- let provider = PhoneAuthProvider.provider(auth: auth)
- let mockVerifier = FakeAuthRecaptchaVerifier(captchaResponse: kCaptchaResponse)
- AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth)
- rpcIssuer.rceMode = "AUDIT"
- let requestExpectation = expectation(description: "verifyRequester")
- rpcIssuer.verifyRequester = { request in
- XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
- XCTAssertEqual(request.captchaResponse, self.kCaptchaResponse)
- XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE")
- XCTAssertEqual(request.codeIdentity, CodeIdentity.empty)
- requestExpectation.fulfill()
- do {
- return try self.rpcIssuer
- .respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID])
- } catch {
- XCTFail("Failure sending response: \(error)")
- return (nil, nil)
- }
- }
- do {
- let result = try await provider.verifyClAndSendVerificationCodeWithRecaptcha(
- toPhoneNumber: kTestPhoneNumber,
- retryOnInvalidAppCredential: false,
- uiDelegate: nil,
- recaptchaVerifier: mockVerifier
- )
- XCTAssertEqual(result, kTestVerificationID)
- } catch {
- XCTFail("Unexpected error")
- }
- await fulfillment(of: [requestExpectation], timeout: 5.0)
- }
- /// @fn testVerifyPhoneNumberWithRceAuditInvalidRecaptcha
- /// @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise in
- /// audit mode
- func testVerifyPhoneNumberWithRceAuditInvalidRecaptcha() async throws {
- initApp(#function)
- let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
- let provider = PhoneAuthProvider.provider(auth: auth)
- let mockVerifier = FakeAuthRecaptchaVerifier()
- AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth)
- rpcIssuer.rceMode = "AUDIT"
- let requestExpectation = expectation(description: "verifyRequester")
- rpcIssuer?.verifyRequester = { request in
- XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
- XCTAssertEqual(request.captchaResponse, "NO_RECAPTCHA")
- XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE")
- XCTAssertEqual(request.codeIdentity, CodeIdentity.empty)
- requestExpectation.fulfill()
- do {
- return try self.rpcIssuer
- .respond(
- serverErrorMessage: "INVALID_RECAPTCHA_TOKEN",
- error: AuthErrorUtils.invalidRecaptchaTokenError() as NSError
- )
- } catch {
- XCTFail("Failure sending response: \(error)")
- return (nil, nil)
- }
- }
- do {
- _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha(
- toPhoneNumber: kTestPhoneNumber,
- retryOnInvalidAppCredential: false,
- uiDelegate: nil,
- recaptchaVerifier: mockVerifier
- )
- } catch {
- let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError
- let rootError = underlyingError?.userInfo[NSUnderlyingErrorKey] as? NSError
- XCTAssertEqual(rootError?.code, AuthErrorCode.invalidRecaptchaToken.code.rawValue)
- }
- await fulfillment(of: [requestExpectation], timeout: 5.0)
- }
- /** @fn testVerifyPhoneNumberInTestMode
- @brief Tests a successful invocation of @c verifyPhoneNumber:completion: when app verification
- is disabled.
- */
- func testVerifyPhoneNumberInTestMode() async throws {
- try await internalTestVerify(function: #function, testMode: true)
- }
- /** @fn testVerifyPhoneNumberInTestModeFailure
- @brief Tests a failed invocation of @c verifyPhoneNumber:completion: when app verification
- is disabled.
- */
- func testVerifyPhoneNumberInTestModeFailure() async throws {
- try await internalTestVerify(errorString: "INVALID_PHONE_NUMBER",
- errorCode: AuthErrorCode.invalidPhoneNumber.rawValue,
- function: #function, testMode: true)
- }
- /** @fn testVerifyPhoneNumberUIDelegateFirebaseAppIdFlow
- @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
- */
- func testVerifyPhoneNumberUIDelegateFirebaseAppIdFlow() async throws {
- try await internalTestVerify(function: #function, reCAPTCHAfallback: true)
- }
- /** @fn testVerifyPhoneNumberUIDelegateFirebaseAppIdWhileClientIdPresentFlow
- @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion: when the
- client ID is present in the plist file, but the encoded app ID is the registered custom URL scheme.
- */
- func testVerifyPhoneNumberUIDelegateFirebaseAppIdWhileClientIdPresentFlow() async throws {
- try await internalTestVerify(function: #function, useClientID: true,
- bothClientAndAppID: true, reCAPTCHAfallback: true)
- }
- /** @fn testVerifyPhoneNumberUIDelegateClientIdFlow
- @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
- */
- func testVerifyPhoneNumberUIDelegateClientIdFlow() async throws {
- try await internalTestVerify(function: #function, useClientID: true, reCAPTCHAfallback: true)
- }
- /** @fn testVerifyPhoneNumberUIDelegateInvalidClientID
- @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
- invalid client ID error.
- */
- func testVerifyPhoneNumberUIDelegateInvalidClientID() async throws {
- try await internalTestVerify(
- errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringInvalidClientID,
- errorCode: AuthErrorCode.invalidClientID.rawValue,
- function: #function,
- useClientID: true,
- reCAPTCHAfallback: true
- )
- }
- /** @fn testVerifyPhoneNumberUIDelegateWebNetworkRequestFailed
- @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
- network request failed error.
- */
- func testVerifyPhoneNumberUIDelegateWebNetworkRequestFailed() async throws {
- try await internalTestVerify(
- errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringWebNetworkRequestFailed,
- errorCode: AuthErrorCode.webNetworkRequestFailed.rawValue,
- function: #function,
- useClientID: true,
- reCAPTCHAfallback: true
- )
- }
- /** @fn testVerifyPhoneNumberUIDelegateWebInternalError
- @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
- internal error.
- */
- func testVerifyPhoneNumberUIDelegateWebInternalError() async throws {
- try await internalTestVerify(
- errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringWebInternalError,
- errorCode: AuthErrorCode.webInternalError.rawValue,
- function: #function,
- useClientID: true,
- reCAPTCHAfallback: true
- )
- }
- /** @fn testVerifyPhoneNumberUIDelegateUnexpectedError
- @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
- invalid client ID.
- */
- func testVerifyPhoneNumberUIDelegateUnexpectedError() async throws {
- try await internalTestVerify(
- errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringUnknownError,
- errorCode: AuthErrorCode.webSignInUserInteractionFailure.rawValue,
- function: #function,
- useClientID: true,
- reCAPTCHAfallback: true
- )
- }
- /** @fn testVerifyPhoneNumberUIDelegateUnstructuredError
- @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
- error being surfaced with a default NSLocalizedFailureReasonErrorKey due to an unexpected
- structure of the error response.
- */
- func testVerifyPhoneNumberUIDelegateUnstructuredError() async throws {
- try await internalTestVerify(
- errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringUnstructuredError,
- errorCode: AuthErrorCode.appVerificationUserInteractionFailure.rawValue,
- function: #function,
- useClientID: true,
- reCAPTCHAfallback: true
- )
- }
- // TODO: This test is skipped. What was formerly an Objective-C exception is now a Swift fatal_error.
- // The test runs correctly, but it's not clear how to automate fatal_error testing. Switching to
- // Swift exceptions would break the API.
- /** @fn testVerifyPhoneNumberUIDelegateRaiseException
- @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
- exception.
- */
- func SKIPtestVerifyPhoneNumberUIDelegateRaiseException() async throws {
- initApp(#function)
- let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
- auth.mainBundleUrlTypes = [["CFBundleURLSchemes": ["fail"]]]
- let provider = PhoneAuthProvider.provider(auth: auth)
- provider.verifyPhoneNumber(kTestPhoneNumber, uiDelegate: nil) { verificationID, error in
- XCTFail("Should not call completion")
- }
- }
- /** @fn testNotForwardingNotification
- @brief Tests returning an error for the app failing to forward notification.
- */
- func testNotForwardingNotification() throws {
- func testVerifyPhoneNumberUIDelegateUnstructuredError() async throws {
- try await internalTestVerify(
- errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringUnstructuredError,
- errorCode: AuthErrorCode.appVerificationUserInteractionFailure.rawValue,
- function: #function,
- useClientID: true,
- reCAPTCHAfallback: true,
- forwardingNotification: false
- )
- }
- }
- /** @fn testMissingAPNSToken
- @brief Tests returning an error for the app failing to provide an APNS device token.
- */
- func testMissingAPNSToken() async throws {
- try await internalTestVerify(
- errorCode: AuthErrorCode.missingAppToken.rawValue,
- function: #function,
- useClientID: true,
- reCAPTCHAfallback: true,
- presenterError: NSError(
- domain: AuthErrors.domain,
- code: AuthErrorCode.missingAppToken.rawValue
- )
- )
- }
- /** @fn testVerifyPhoneNumberUIDelegateiOSSecretMissingFlow
- @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion: that falls
- back to the reCAPTCHA flow when the push notification is not received before the timeout.
- */
- func testVerifyPhoneNumberUIDelegateiOSSecretMissingFlow() throws {
- try internalFlow(function: #function, useClientID: false, reCAPTCHAfallback: true)
- }
- /** @fn testVerifyClient
- @brief Tests verifying client before sending verification code.
- */
- func testVerifyClient() throws {
- try internalFlow(function: #function, useClientID: true, reCAPTCHAfallback: false)
- }
- /** @fn testSendVerificationCodeFailedRetry
- @brief Tests failed retry after failing to send verification code.
- */
- func testSendVerificationCodeFailedRetry() throws {
- try internalFlowRetry(function: #function)
- }
- /** @fn testSendVerificationCodeSuccessfulRetry
- @brief Tests successful retry after failing to send verification code.
- */
- func testSendVerificationCodeSuccessfulRetry() throws {
- try internalFlowRetry(function: #function, goodRetry: true)
- }
- /** @fn testPhoneAuthCredentialCoding
- @brief Tests successful archiving and unarchiving of @c PhoneAuthCredential.
- */
- func testPhoneAuthCredentialCoding() throws {
- let kVerificationID = "My verificationID"
- let kVerificationCode = "1234"
- let credential = PhoneAuthCredential(withProviderID: PhoneAuthProvider.id,
- verificationID: kVerificationID,
- verificationCode: kVerificationCode)
- XCTAssertTrue(PhoneAuthCredential.supportsSecureCoding)
- let data = try NSKeyedArchiver.archivedData(
- withRootObject: credential,
- requiringSecureCoding: true
- )
- let unarchivedCredential = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject(
- ofClass: PhoneAuthCredential.self, from: data
- ))
- switch unarchivedCredential.credentialKind {
- case .phoneNumber: XCTFail("Should be verification case")
- case let .verification(id, code):
- XCTAssertEqual(id, kVerificationID)
- XCTAssertEqual(code, kVerificationCode)
- }
- XCTAssertEqual(unarchivedCredential.provider, PhoneAuthProvider.id)
- }
- /** @fn testPhoneAuthCredentialCodingPhone
- @brief Tests successful archiving and unarchiving of @c PhoneAuthCredential after other constructor.
- */
- func testPhoneAuthCredentialCodingPhone() throws {
- let kTemporaryProof = "Proof"
- let kPhoneNumber = "123457"
- let credential = PhoneAuthCredential(withTemporaryProof: kTemporaryProof,
- phoneNumber: kPhoneNumber,
- providerID: PhoneAuthProvider.id)
- XCTAssertTrue(PhoneAuthCredential.supportsSecureCoding)
- let data = try NSKeyedArchiver.archivedData(
- withRootObject: credential,
- requiringSecureCoding: true
- )
- let unarchivedCredential = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject(
- ofClasses: [PhoneAuthCredential.self, NSString.self], from: data
- ) as? PhoneAuthCredential)
- switch unarchivedCredential.credentialKind {
- case let .phoneNumber(phoneNumber, temporaryProof):
- XCTAssertEqual(temporaryProof, kTemporaryProof)
- XCTAssertEqual(phoneNumber, kPhoneNumber)
- case .verification: XCTFail("Should be phoneNumber case")
- }
- XCTAssertEqual(unarchivedCredential.provider, PhoneAuthProvider.id)
- }
- private func testRecaptchaFlowError(function: String, rceError: Error) async throws {
- initApp(function)
- let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
- // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response
- // Mocking the output of verify() method
- let provider = PhoneAuthProvider.provider(auth: auth)
- let mockVerifier = FakeAuthRecaptchaVerifier(error: rceError)
- AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth)
- rpcIssuer.rceMode = "ENFORCE"
- do {
- let _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha(
- toPhoneNumber: kTestPhoneNumber,
- retryOnInvalidAppCredential: false,
- uiDelegate: nil,
- recaptchaVerifier: mockVerifier
- )
- } catch {
- XCTAssertEqual((error as NSError).code, (rceError as NSError).code)
- }
- }
- private func internalFlowRetry(function: String, goodRetry: Bool = false) throws {
- let function = function
- initApp(function, useClientID: true, fakeToken: true)
- let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
- let provider = PhoneAuthProvider.provider(auth: auth)
- let expectation = self.expectation(description: function)
- // Fake push notification.
- auth.appCredentialManager?.fakeCredential = AuthAppCredential(
- receipt: kTestReceipt,
- secret: kTestSecret
- )
- // 1. Intercept, handle, and test three RPC calls.
- let verifyClientRequestExpectation = self.expectation(description: "verifyClientRequest")
- verifyClientRequestExpectation.expectedFulfillmentCount = 2
- rpcIssuer?.verifyClientRequester = { request in
- XCTAssertEqual(request.appToken, "21402324255E")
- XCTAssertFalse(request.isSandbox)
- verifyClientRequestExpectation.fulfill()
- do {
- // Response for the underlying VerifyClientRequest RPC call.
- return try self.rpcIssuer.respond(withJSON: [
- "receipt": self.kTestReceipt,
- "suggestedTimeout": self.kTestTimeout,
- ])
- } catch {
- XCTFail("Failure sending response: \(error)")
- return (nil, nil)
- }
- }
- let verifyRequesterExpectation = self.expectation(description: "verifyRequester")
- verifyRequesterExpectation.expectedFulfillmentCount = 2
- var visited = false
- rpcIssuer?.verifyRequester = { request in
- XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
- switch request.codeIdentity {
- case let .credential(credential):
- XCTAssertEqual(credential.receipt, self.kTestReceipt)
- XCTAssertEqual(credential.secret, self.kTestSecret)
- default:
- XCTFail("Should be credential")
- }
- verifyRequesterExpectation.fulfill()
- do {
- if visited == false || goodRetry == false {
- // First Response for the underlying SendVerificationCode RPC call.
- let (data, error) = try self.rpcIssuer
- .respond(serverErrorMessage: "INVALID_APP_CREDENTIAL")
- visited = true
- return (data, error)
- } else {
- // Second Response for the underlying SendVerificationCode RPC call.
- return try self.rpcIssuer
- .respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID])
- }
- } catch {
- XCTFail("Failure sending response: \(error)")
- return (nil, nil)
- }
- }
- // Use fake authURLPresenter so we can test the parameters that get sent to it.
- PhoneAuthProviderTests.auth?.authURLPresenter =
- FakePresenter(
- urlString: PhoneAuthProviderTests.kFakeRedirectURLStringWithReCAPTCHAToken,
- clientID: PhoneAuthProviderTests.kFakeClientID,
- firebaseAppID: nil,
- errorTest: false,
- presenterError: nil
- )
- // 2. After setting up the fakes and parameters, call `verifyPhoneNumber`.
- provider
- .verifyPhoneNumber(kTestPhoneNumber, uiDelegate: nil) { verificationID, error in
- // 8. After the response triggers the callback in the FakePresenter, verify the callback.
- XCTAssertTrue(Thread.isMainThread)
- if goodRetry {
- XCTAssertNil(error)
- XCTAssertEqual(verificationID, self.kTestVerificationID)
- } else {
- XCTAssertNil(verificationID)
- XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.internalError.rawValue)
- }
- expectation.fulfill()
- }
- waitForExpectations(timeout: 5)
- }
- private func internalFlow(function: String,
- useClientID: Bool = false,
- reCAPTCHAfallback: Bool = false) throws {
- let function = function
- initApp(function, useClientID: useClientID, fakeToken: true)
- let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
- let provider = PhoneAuthProvider.provider(auth: auth)
- let expectation = self.expectation(description: function)
- // Fake push notification.
- auth.appCredentialManager?.fakeCredential = AuthAppCredential(
- receipt: kTestReceipt,
- secret: reCAPTCHAfallback ? nil : kTestSecret
- )
- // 1. Intercept, handle, and test three RPC calls.
- let verifyClientRequestExpectation = self.expectation(description: "verifyClientRequest")
- rpcIssuer?.verifyClientRequester = { request in
- XCTAssertEqual(request.appToken, "21402324255E")
- XCTAssertFalse(request.isSandbox)
- verifyClientRequestExpectation.fulfill()
- do {
- // Response for the underlying VerifyClientRequest RPC call.
- return try self.rpcIssuer.respond(withJSON: [
- "receipt": self.kTestReceipt,
- "suggestedTimeout": self.kTestTimeout,
- ])
- } catch {
- XCTFail("Failure sending response: \(error)")
- return (nil, nil)
- }
- }
- if reCAPTCHAfallback {
- let projectConfigExpectation = self.expectation(description: "projectConfiguration")
- rpcIssuer.projectConfigRequester = { request in
- XCTAssertEqual(request.apiKey, PhoneAuthProviderTests.kFakeAPIKey)
- projectConfigExpectation.fulfill()
- do {
- // Response for the underlying VerifyClientRequest RPC call.
- return try self.rpcIssuer.respond(
- withJSON: ["projectId": "kFakeProjectID",
- "authorizedDomains": [PhoneAuthProviderTests.kFakeAuthorizedDomain]]
- )
- } catch {
- XCTFail("Failure sending response: \(error)")
- return (nil, nil)
- }
- }
- }
- let verifyRequesterExpectation = self.expectation(description: "verifyRequester")
- rpcIssuer?.verifyRequester = { request in
- XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
- if reCAPTCHAfallback {
- switch request.codeIdentity {
- case let .recaptcha(token):
- XCTAssertEqual(token, self.kFakeReCAPTCHAToken)
- default:
- XCTFail("Should be recaptcha")
- }
- } else {
- switch request.codeIdentity {
- case let .credential(credential):
- XCTAssertEqual(credential.receipt, self.kTestReceipt)
- XCTAssertEqual(credential.secret, self.kTestSecret)
- default:
- XCTFail("Should be credential")
- }
- }
- verifyRequesterExpectation.fulfill()
- do {
- // Response for the underlying SendVerificationCode RPC call.
- return try self.rpcIssuer
- .respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID])
- } catch {
- XCTFail("Failure sending response: \(error)")
- return (nil, nil)
- }
- }
- // Use fake authURLPresenter so we can test the parameters that get sent to it.
- PhoneAuthProviderTests.auth?.authURLPresenter =
- FakePresenter(
- urlString: PhoneAuthProviderTests.kFakeRedirectURLStringWithReCAPTCHAToken,
- clientID: useClientID ? PhoneAuthProviderTests.kFakeClientID : nil,
- firebaseAppID: useClientID ? nil : PhoneAuthProviderTests.kFakeFirebaseAppID,
- errorTest: false,
- presenterError: nil
- )
- let uiDelegate = reCAPTCHAfallback ? FakeUIDelegate() : nil
- // 2. After setting up the fakes and parameters, call `verifyPhoneNumber`.
- provider
- .verifyPhoneNumber(kTestPhoneNumber, uiDelegate: uiDelegate) { verificationID, error in
- // 8. After the response triggers the callback in the FakePresenter, verify the callback.
- XCTAssertTrue(Thread.isMainThread)
- XCTAssertNil(error)
- XCTAssertEqual(verificationID, self.kTestVerificationID)
- expectation.fulfill()
- }
- waitForExpectations(timeout: 5)
- }
- /** @fn testVerifyClient
- @brief Tests verifying client before sending verification code.
- */
- private func internalTestVerify(errorString: String? = nil,
- errorURLString: String? = nil,
- errorCode: Int = 0,
- function: String,
- testMode: Bool = false,
- useClientID: Bool = false,
- bothClientAndAppID: Bool = false,
- reCAPTCHAfallback: Bool = false,
- forwardingNotification: Bool = true,
- presenterError: Error? = nil) async throws {
- initApp(function, useClientID: useClientID, bothClientAndAppID: bothClientAndAppID,
- testMode: testMode,
- forwardingNotification: forwardingNotification)
- let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
- let provider = PhoneAuthProvider.provider(auth: auth)
- var expectations: [XCTestExpectation] = []
- if !reCAPTCHAfallback {
- // Fake out appCredentialManager flow.
- auth.appCredentialManager?.credential = AuthAppCredential(receipt: kTestReceipt,
- secret: kTestSecret)
- } else {
- // 1. Intercept, handle, and test the projectConfiguration RPC calls.
- let projectConfigExpectation = expectation(description: "projectConfiguration")
- expectations.append(projectConfigExpectation)
- rpcIssuer.projectConfigRequester = { request in
- XCTAssertEqual(request.apiKey, PhoneAuthProviderTests.kFakeAPIKey)
- projectConfigExpectation.fulfill()
- do {
- // Response for the underlying VerifyClientRequest RPC call.
- return try self.rpcIssuer.respond(
- withJSON: ["projectId": "kFakeProjectID",
- "authorizedDomains": [PhoneAuthProviderTests.kFakeAuthorizedDomain]]
- )
- } catch {
- XCTFail("Failure sending response: \(error)")
- return (nil, nil)
- }
- }
- }
- if reCAPTCHAfallback {
- // Use fake authURLPresenter so we can test the parameters that get sent to it.
- let urlString = errorURLString ??
- PhoneAuthProviderTests.kFakeRedirectURLStringWithReCAPTCHAToken
- let errorTest = errorURLString != nil
- PhoneAuthProviderTests.auth?.authURLPresenter =
- FakePresenter(
- urlString: urlString,
- clientID: useClientID ? PhoneAuthProviderTests.kFakeClientID : nil,
- firebaseAppID: useClientID ? nil : PhoneAuthProviderTests.kFakeFirebaseAppID,
- errorTest: errorTest,
- presenterError: presenterError
- )
- }
- if errorURLString == nil, presenterError == nil {
- let requestExpectation = expectation(description: "verifyRequester")
- expectations.append(requestExpectation)
- rpcIssuer.verifyRequester = { request in
- XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
- switch request.codeIdentity {
- case let .credential(credential):
- XCTAssertFalse(reCAPTCHAfallback)
- XCTAssertEqual(credential.receipt, self.kTestReceipt)
- XCTAssertEqual(credential.secret, self.kTestSecret)
- case let .recaptcha(token):
- XCTAssertTrue(reCAPTCHAfallback)
- XCTAssertEqual(token, self.kFakeReCAPTCHAToken)
- case .empty:
- XCTAssertTrue(testMode)
- }
- requestExpectation.fulfill()
- do {
- // Response for the underlying SendVerificationCode RPC call.
- if let errorString {
- return try self.rpcIssuer.respond(serverErrorMessage: errorString)
- } else {
- return try self.rpcIssuer
- .respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID])
- }
- } catch {
- XCTFail("Failure sending response: \(error)")
- return (nil, nil)
- }
- }
- }
- let uiDelegate = reCAPTCHAfallback ? FakeUIDelegate() : nil
- // 2. After setting up the parameters, call `verifyPhoneNumber`.
- do {
- // Call the async function to verify the phone number
- let verificationID = try await provider.verifyPhoneNumber(
- kTestPhoneNumber,
- uiDelegate: uiDelegate
- )
- // Assert that the verificationID matches the expected value
- XCTAssertEqual(verificationID, kTestVerificationID)
- } catch {
- // If an error occurs, assert that verificationID is nil and the error code matches the
- // expected value
- XCTAssertEqual((error as NSError).code, errorCode)
- }
- await fulfillment(of: expectations, timeout: 5.0)
- }
- private func initApp(_ functionName: String,
- useClientID: Bool = false,
- bothClientAndAppID: Bool = false,
- testMode: Bool = false,
- forwardingNotification: Bool = true,
- fakeToken: Bool = false) {
- let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000",
- gcmSenderID: "00000000000000000-00000000000-000000000")
- options.apiKey = PhoneAuthProviderTests.kFakeAPIKey
- options.projectID = "myProjectID"
- if useClientID {
- options.clientID = PhoneAuthProviderTests.kFakeClientID
- }
- if !useClientID || bothClientAndAppID {
- // Use the appID.
- options.googleAppID = PhoneAuthProviderTests.kFakeFirebaseAppID
- }
- let scheme = useClientID ? PhoneAuthProviderTests.kFakeReverseClientID :
- PhoneAuthProviderTests.kFakeEncodedFirebaseAppID
- let strippedName = functionName.replacingOccurrences(of: "(", with: "")
- .replacingOccurrences(of: ")", with: "")
- FirebaseApp.configure(name: strippedName, options: options)
- let auth = Auth(app: FirebaseApp.app(name: strippedName)!, backend: authBackend)
- kAuthGlobalWorkQueue.sync {
- // Wait for Auth protectedDataInitialization to finish.
- PhoneAuthProviderTests.auth = auth
- if testMode {
- // Disable app verification.
- let settings = AuthSettings()
- settings.appVerificationDisabledForTesting = true
- auth.settings = settings
- }
- auth.notificationManager?.immediateCallbackForTestFaking = { forwardingNotification }
- auth.mainBundleUrlTypes = [["CFBundleURLSchemes": [scheme]]]
- if fakeToken {
- guard let data = "!@#$%^".data(using: .utf8) else {
- XCTFail("Failed to encode data for fake token")
- return
- }
- auth.tokenManager?.tokenStore = AuthAPNSToken(withData: data, type: .prod)
- } else {
- // Skip APNS token fetching.
- auth.tokenManager = FakeTokenManager(withApplication: UIApplication.shared)
- }
- }
- }
- class FakeAuthRecaptchaVerifier: AuthRecaptchaVerifier, @unchecked Sendable {
- var captchaResponse: String
- var error: Error?
- init(captchaResponse: String? = nil, error: Error? = nil) {
- self.captchaResponse = captchaResponse ?? "NO_RECAPTCHA"
- self.error = error
- super.init()
- }
- override func verify(forceRefresh: Bool, action: AuthRecaptchaAction) async throws -> String {
- if let error = error {
- throw error
- }
- return captchaResponse
- }
- }
- class FakeTokenManager: AuthAPNSTokenManager, @unchecked Sendable {
- override func getTokenInternal(callback: @escaping (Result<AuthAPNSToken, Error>) -> Void) {
- let error = NSError(domain: "dummy domain", code: AuthErrorCode.missingAppToken.rawValue)
- callback(.failure(error))
- }
- }
- class FakePresenter: NSObject, AuthWebViewControllerDelegate {
- func webViewController(_ webViewController: AuthWebViewController,
- canHandle URL: URL) -> Bool {
- XCTFail("Do not call")
- return false
- }
- func webViewControllerDidCancel(_ webViewController: AuthWebViewController) {
- XCTFail("Do not call")
- }
- func webViewController(_ webViewController: AuthWebViewController,
- didFailWithError error: Error) {
- XCTFail("Do not call")
- }
- func present(_ presentURL: URL,
- uiDelegate UIDelegate: AuthUIDelegate?,
- callbackMatcher: @escaping (URL?) -> Bool,
- completion: @escaping (URL?, Error?) -> Void) {
- // 5. Verify flow triggers present in the FakePresenter class with the right parameters.
- XCTAssertEqual(presentURL.scheme, "https")
- XCTAssertEqual(presentURL.host, kFakeAuthorizedDomain)
- XCTAssertEqual(presentURL.path, "/__/auth/handler")
- let actualURLComponents = URLComponents(url: presentURL, resolvingAgainstBaseURL: false)
- guard let _ = actualURLComponents?.queryItems else {
- XCTFail("Failed to get queryItems")
- return
- }
- let params = AuthWebUtils.dictionary(withHttpArgumentsString: presentURL.query)
- XCTAssertEqual(params["ibi"], Bundle.main.bundleIdentifier)
- XCTAssertEqual(params["apiKey"], PhoneAuthProviderTests.kFakeAPIKey)
- XCTAssertEqual(params["authType"], "verifyApp")
- XCTAssertNotNil(params["v"])
- if OAuthProviderTests.testTenantID {
- XCTAssertEqual(params["tid"], OAuthProviderTests.kFakeTenantID)
- } else {
- XCTAssertNil(params["tid"])
- }
- let appCheckToken = presentURL.fragment
- let verifyAppCheckToken = OAuthProviderTests.testAppCheck ? "fac=fakeAppCheckToken" : nil
- XCTAssertEqual(appCheckToken, verifyAppCheckToken)
- var redirectURL = ""
- if let clientID {
- XCTAssertEqual(params["clientId"], clientID)
- redirectURL = "\(kFakeReverseClientID)\(urlString)"
- }
- if let firebaseAppID {
- XCTAssertEqual(params["appId"], firebaseAppID)
- redirectURL = "\(kFakeEncodedFirebaseAppID)\(urlString)"
- }
- // 6. Test callbackMatcher
- // Verify that the URL is rejected by the callback matcher without the event ID.
- XCTAssertFalse(callbackMatcher(URL(string: "\(redirectURL)")))
- // Verify that the URL is accepted by the callback matcher with the matching event ID.
- guard let eventID = params["eventId"] else {
- XCTFail("Failed to get eventID")
- return
- }
- let redirectWithEventID = "\(redirectURL)%26eventId%3D\(eventID)"
- let originalComponents = URLComponents(string: redirectWithEventID)!
- XCTAssertEqual(callbackMatcher(originalComponents.url), !errorTest)
- var components = originalComponents
- components.query = "https"
- XCTAssertFalse(callbackMatcher(components.url))
- components = originalComponents
- components.host = "badhost"
- XCTAssertFalse(callbackMatcher(components.url))
- components = originalComponents
- components.path = "badpath"
- XCTAssertFalse(callbackMatcher(components.url))
- components = originalComponents
- components.query = "badquery"
- XCTAssertFalse(callbackMatcher(components.url))
- // 7. Do the callback to the original call.
- kAuthGlobalWorkQueue.async {
- if let presenterError = self.presenterError {
- completion(nil, presenterError)
- } else {
- completion(URL(string: "\(kFakeEncodedFirebaseAppID)\(self.urlString)") ?? nil, nil)
- }
- }
- }
- let urlString: String
- let clientID: String?
- let firebaseAppID: String?
- let errorTest: Bool
- let presenterError: Error?
- init(urlString: String, clientID: String?, firebaseAppID: String?, errorTest: Bool,
- presenterError: Error?) {
- self.urlString = urlString
- self.clientID = clientID
- self.firebaseAppID = firebaseAppID
- self.errorTest = errorTest
- self.presenterError = presenterError
- }
- }
- private class FakeUIDelegate: NSObject, AuthUIDelegate {
- func present(_ viewControllerToPresent: UIViewController, animated flag: Bool,
- completion: (() -> Void)? = nil) {
- guard let safariController = viewControllerToPresent as? SFSafariViewController,
- let delegate = safariController.delegate as? AuthURLPresenter,
- let uiDelegate = delegate.uiDelegate as? FakeUIDelegate else {
- XCTFail("Failed to get presentURL from controller")
- return
- }
- XCTAssertEqual(self, uiDelegate)
- }
- func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
- XCTFail("Implement me")
- }
- }
- private static let kFakeRedirectURLStringInvalidClientID =
- "//firebaseauth/" +
- "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal" +
- "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Finvalid-oauth-client-id%2522%252" +
- "C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%2520either%252" +
- "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%26" +
- "authType%3DverifyApp"
- private static let kFakeRedirectURLStringWebNetworkRequestFailed =
- "//firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fc" +
- "allback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Fnetwork-request-failed%2522%" +
- "252C%2522message%2522%253A%2522The%2520network%2520request%2520failed%2520.%2522%257D%" +
- "26authType%3DverifyApp"
- private static let kFakeRedirectURLStringWebInternalError =
- "//firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal" +
- "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Finternal-error%2522%252C%" +
- "2522message%2522%253A%2522Internal%2520error%2520.%2522%257D%26authType%3DverifyApp"
- private static let kFakeRedirectURLStringUnknownError =
- "//firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal" +
- "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Funknown-error-id%2522%252" +
- "C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%2520either%252" +
- "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%26" +
- "authType%3DverifyApp"
- private static let kFakeRedirectURLStringUnstructuredError =
- "//firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal" +
- "lback%3FfirebaseError%3D%257B%2522unstructuredcode%2522%253A%2522auth%252Funknown-error-id%" +
- "2522%252" +
- "C%2522unstructuredmessage%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%" +
- "2520either%252" +
- "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%" +
- "26authType%3DverifyApp"
- private static let kFakeRedirectURLStringWithReCAPTCHAToken =
- "://firebaseauth/" +
- "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3FauthType%" +
- "3DverifyApp%26recaptchaToken%3DfakeReCAPTCHAToken"
- }
- #endif
|