PhoneAuthProviderTests.swift 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  1. // Copyright 2023 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License")
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #if os(iOS)
  15. import Foundation
  16. import XCTest
  17. @testable import FirebaseAuth
  18. import FirebaseCore
  19. import SafariServices
  20. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  21. class PhoneAuthProviderTests: RPCBaseTests {
  22. static let kFakeAuthorizedDomain = "test.firebaseapp.com"
  23. static let kFakeAPIKey = "asdfghjkl"
  24. static let kFakeEmulatorHost = "emulatorhost"
  25. static let kFakeEmulatorPort = 12345
  26. static let kFakeClientID = "123456.apps.googleusercontent.com"
  27. static let kFakeFirebaseAppID = "1:123456789:ios:123abc456def"
  28. static let kFakeEncodedFirebaseAppID = "app-1-123456789-ios-123abc456def"
  29. static let kFakeTenantID = "tenantID"
  30. static let kFakeReverseClientID = "com.googleusercontent.apps.123456"
  31. private let kTestVerificationID = "verificationID"
  32. private let kTestVerificationCode = "verificationCode"
  33. private let kTestReceipt = "receipt"
  34. private let kTestTimeout = "1"
  35. private let kTestSecret = "secret"
  36. private let kVerificationIDKey = "sessionInfo"
  37. private let kFakeEncodedFirebaseAppID = "app-1-123456789-ios-123abc456def"
  38. private let kFakeReCAPTCHAToken = "fakeReCAPTCHAToken"
  39. private let kCaptchaResponse: String = "captchaResponse"
  40. private let kRecaptchaVersion: String = "RECAPTCHA_ENTERPRISE"
  41. static var auth: Auth?
  42. // static var authRecaptchaVerifier: AuthRecaptchaVerifier
  43. /** @fn testCredentialWithVerificationID
  44. @brief Tests the @c credentialWithToken method to make sure that it returns a valid AuthCredential instance.
  45. */
  46. func testCredentialWithVerificationID() throws {
  47. initApp(#function)
  48. let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
  49. let provider = PhoneAuthProvider.provider(auth: auth)
  50. let credential = provider.credential(withVerificationID: kTestVerificationID,
  51. verificationCode: kTestVerificationCode)
  52. switch credential.credentialKind {
  53. case .phoneNumber: XCTFail("Should be verification case")
  54. case let .verification(id, code):
  55. XCTAssertEqual(id, kTestVerificationID)
  56. XCTAssertEqual(code, kTestVerificationCode)
  57. }
  58. }
  59. /** @fn testVerifyEmptyPhoneNumber
  60. @brief Tests a failed invocation verifyPhoneNumber because an empty phone
  61. number was provided.
  62. */
  63. func testVerifyEmptyPhoneNumber() async throws {
  64. initApp(#function)
  65. let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
  66. let provider = PhoneAuthProvider.provider(auth: auth)
  67. do {
  68. _ = try await provider.verifyPhoneNumber("")
  69. XCTFail("Expected an error, but verification succeeded.")
  70. } catch {
  71. XCTAssertEqual((error as NSError).code, AuthErrorCode.missingPhoneNumber.rawValue)
  72. }
  73. }
  74. /** @fn testVerifyInvalidPhoneNumber
  75. @brief Tests a failed invocation @c verifyPhoneNumber:completion: because an invalid phone
  76. number was provided.
  77. */
  78. func testVerifyInvalidPhoneNumber() async throws {
  79. try await internalTestVerify(errorString: "INVALID_PHONE_NUMBER",
  80. errorCode: AuthErrorCode.invalidPhoneNumber.rawValue,
  81. function: #function)
  82. }
  83. /** @fn testVerifyPhoneNumber
  84. @brief Tests a successful invocation of @c verifyPhoneNumber:completion:.
  85. */
  86. func testVerifyPhoneNumber() async throws {
  87. try await internalTestVerify(function: #function)
  88. }
  89. /**
  90. @fn testVerifyPhoneNumberWithRceEnforce
  91. @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced
  92. */
  93. func testVerifyPhoneNumberWithRceEnforceSuccess() async throws {
  94. initApp(#function)
  95. let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
  96. // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response
  97. let provider = PhoneAuthProvider.provider(auth: auth)
  98. let mockVerifier = FakeAuthRecaptchaVerifier(captchaResponse: kCaptchaResponse)
  99. AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth)
  100. rpcIssuer.rceMode = "ENFORCE"
  101. let requestExpectation = expectation(description: "verifyRequester")
  102. rpcIssuer?.verifyRequester = { request in
  103. XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
  104. XCTAssertEqual(request.captchaResponse, self.kCaptchaResponse)
  105. XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE")
  106. XCTAssertEqual(request.codeIdentity, CodeIdentity.empty)
  107. requestExpectation.fulfill()
  108. do {
  109. try self.rpcIssuer?
  110. .respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID])
  111. } catch {
  112. XCTFail("Failure sending response: \(error)")
  113. }
  114. }
  115. do {
  116. let result = try await provider.verifyClAndSendVerificationCodeWithRecaptcha(
  117. toPhoneNumber: kTestPhoneNumber,
  118. retryOnInvalidAppCredential: false,
  119. uiDelegate: nil,
  120. recaptchaVerifier: mockVerifier
  121. )
  122. XCTAssertEqual(result, kTestVerificationID)
  123. } catch {
  124. XCTFail("Unexpected error")
  125. }
  126. await fulfillment(of: [requestExpectation], timeout: 5.0)
  127. }
  128. /**
  129. @fn testVerifyPhoneNumberWithRceEnforceInvalidRecaptcha
  130. @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced
  131. */
  132. func testVerifyPhoneNumberWithRceEnforceInvalidRecaptcha() async throws {
  133. initApp(#function)
  134. let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
  135. // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response
  136. let provider = PhoneAuthProvider.provider(auth: auth)
  137. let mockVerifier = FakeAuthRecaptchaVerifier()
  138. AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth)
  139. rpcIssuer.rceMode = "ENFORCE"
  140. let requestExpectation = expectation(description: "verifyRequester")
  141. rpcIssuer?.verifyRequester = { request in
  142. XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
  143. XCTAssertEqual(request.captchaResponse, "NO_RECAPTCHA")
  144. XCTAssertEqual(request.recaptchaVersion, "RECAPTCHA_ENTERPRISE")
  145. XCTAssertEqual(request.codeIdentity, CodeIdentity.empty)
  146. requestExpectation.fulfill()
  147. do {
  148. try self.rpcIssuer?
  149. .respond(
  150. serverErrorMessage: "INVALID_RECAPTCHA_TOKEN",
  151. error: AuthErrorUtils.invalidRecaptchaTokenError() as NSError
  152. )
  153. } catch {
  154. XCTFail("Failure sending response: \(error)")
  155. }
  156. }
  157. do {
  158. _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha(
  159. toPhoneNumber: kTestPhoneNumber,
  160. retryOnInvalidAppCredential: false,
  161. uiDelegate: nil,
  162. recaptchaVerifier: mockVerifier
  163. )
  164. // XCTAssertEqual(result, kTestVerificationID)
  165. } catch {
  166. // Traverse the nested error to find the root cause
  167. let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError
  168. let rootError = underlyingError?.userInfo[NSUnderlyingErrorKey] as? NSError
  169. // Compare the root error code to the expected error code
  170. XCTAssertEqual(rootError?.code, AuthErrorCode.invalidRecaptchaToken.code.rawValue)
  171. }
  172. await fulfillment(of: [requestExpectation], timeout: 5.0)
  173. }
  174. /**
  175. @fn testVerifyPhoneNumberWithRceEnforceSDKNotLinked
  176. @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced
  177. */
  178. func testVerifyPhoneNumberWithRceEnforceRecaptchaSDKNotLinked() async throws {
  179. return try await testRecaptchaFlowError(
  180. function: #function,
  181. rceError: AuthErrorUtils.recaptchaSDKNotLinkedError()
  182. )
  183. }
  184. /**
  185. @fn testVerifyPhoneNumberWithRceEnforceSDKNotLinked
  186. @brief Tests a successful invocation of @c verifyPhoneNumber with recaptcha enterprise enforced
  187. */
  188. func testVerifyPhoneNumberWithRceEnforceRecaptchaActionCreationFailed() async throws {
  189. return try await testRecaptchaFlowError(
  190. function: #function,
  191. rceError: AuthErrorUtils.recaptchaActionCreationFailed()
  192. )
  193. }
  194. /** @fn testVerifyPhoneNumberInTestMode
  195. @brief Tests a successful invocation of @c verifyPhoneNumber:completion: when app verification
  196. is disabled.
  197. */
  198. func testVerifyPhoneNumberInTestMode() async throws {
  199. try await internalTestVerify(function: #function, testMode: true)
  200. }
  201. /** @fn testVerifyPhoneNumberInTestModeFailure
  202. @brief Tests a failed invocation of @c verifyPhoneNumber:completion: when app verification
  203. is disabled.
  204. */
  205. func testVerifyPhoneNumberInTestModeFailure() async throws {
  206. try await internalTestVerify(errorString: "INVALID_PHONE_NUMBER",
  207. errorCode: AuthErrorCode.invalidPhoneNumber.rawValue,
  208. function: #function, testMode: true)
  209. }
  210. /** @fn testVerifyPhoneNumberUIDelegateFirebaseAppIdFlow
  211. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
  212. */
  213. func testVerifyPhoneNumberUIDelegateFirebaseAppIdFlow() async throws {
  214. try await internalTestVerify(function: #function, reCAPTCHAfallback: true)
  215. }
  216. /** @fn testVerifyPhoneNumberUIDelegateFirebaseAppIdWhileClientIdPresentFlow
  217. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion: when the
  218. client ID is present in the plist file, but the encoded app ID is the registered custom URL scheme.
  219. */
  220. func testVerifyPhoneNumberUIDelegateFirebaseAppIdWhileClientIdPresentFlow() async throws {
  221. try await internalTestVerify(function: #function, useClientID: true,
  222. bothClientAndAppID: true, reCAPTCHAfallback: true)
  223. }
  224. /** @fn testVerifyPhoneNumberUIDelegateClientIdFlow
  225. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion:.
  226. */
  227. func testVerifyPhoneNumberUIDelegateClientIdFlow() async throws {
  228. try await internalTestVerify(function: #function, useClientID: true, reCAPTCHAfallback: true)
  229. }
  230. /** @fn testVerifyPhoneNumberUIDelegateInvalidClientID
  231. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  232. invalid client ID error.
  233. */
  234. func testVerifyPhoneNumberUIDelegateInvalidClientID() async throws {
  235. try await internalTestVerify(
  236. errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringInvalidClientID,
  237. errorCode: AuthErrorCode.invalidClientID.rawValue,
  238. function: #function,
  239. useClientID: true,
  240. reCAPTCHAfallback: true
  241. )
  242. }
  243. /** @fn testVerifyPhoneNumberUIDelegateWebNetworkRequestFailed
  244. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
  245. network request failed error.
  246. */
  247. func testVerifyPhoneNumberUIDelegateWebNetworkRequestFailed() async throws {
  248. try await internalTestVerify(
  249. errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringWebNetworkRequestFailed,
  250. errorCode: AuthErrorCode.webNetworkRequestFailed.rawValue,
  251. function: #function,
  252. useClientID: true,
  253. reCAPTCHAfallback: true
  254. )
  255. }
  256. /** @fn testVerifyPhoneNumberUIDelegateWebInternalError
  257. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in a web
  258. internal error.
  259. */
  260. func testVerifyPhoneNumberUIDelegateWebInternalError() async throws {
  261. try await internalTestVerify(
  262. errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringWebInternalError,
  263. errorCode: AuthErrorCode.webInternalError.rawValue,
  264. function: #function,
  265. useClientID: true,
  266. reCAPTCHAfallback: true
  267. )
  268. }
  269. /** @fn testVerifyPhoneNumberUIDelegateUnexpectedError
  270. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  271. invalid client ID.
  272. */
  273. func testVerifyPhoneNumberUIDelegateUnexpectedError() async throws {
  274. try await internalTestVerify(
  275. errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringUnknownError,
  276. errorCode: AuthErrorCode.webSignInUserInteractionFailure.rawValue,
  277. function: #function,
  278. useClientID: true,
  279. reCAPTCHAfallback: true
  280. )
  281. }
  282. /** @fn testVerifyPhoneNumberUIDelegateUnstructuredError
  283. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  284. error being surfaced with a default NSLocalizedFailureReasonErrorKey due to an unexpected
  285. structure of the error response.
  286. */
  287. func testVerifyPhoneNumberUIDelegateUnstructuredError() async throws {
  288. try await internalTestVerify(
  289. errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringUnstructuredError,
  290. errorCode: AuthErrorCode.appVerificationUserInteractionFailure.rawValue,
  291. function: #function,
  292. useClientID: true,
  293. reCAPTCHAfallback: true
  294. )
  295. }
  296. // TODO: This test is skipped. What was formerly an Objective-C exception is now a Swift fatal_error.
  297. // The test runs correctly, but it's not clear how to automate fatal_error testing. Switching to
  298. // Swift exceptions would break the API.
  299. /** @fn testVerifyPhoneNumberUIDelegateRaiseException
  300. @brief Tests a invocation of @c verifyPhoneNumber:UIDelegate:completion: which results in an
  301. exception.
  302. */
  303. func SKIPtestVerifyPhoneNumberUIDelegateRaiseException() async throws {
  304. initApp(#function)
  305. let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
  306. auth.mainBundleUrlTypes = [["CFBundleURLSchemes": ["fail"]]]
  307. let provider = PhoneAuthProvider.provider(auth: auth)
  308. provider.verifyPhoneNumber(kTestPhoneNumber, uiDelegate: nil) { verificationID, error in
  309. XCTFail("Should not call completion")
  310. }
  311. }
  312. /** @fn testNotForwardingNotification
  313. @brief Tests returning an error for the app failing to forward notification.
  314. */
  315. func testNotForwardingNotification() throws {
  316. func testVerifyPhoneNumberUIDelegateUnstructuredError() async throws {
  317. try await internalTestVerify(
  318. errorURLString: PhoneAuthProviderTests.kFakeRedirectURLStringUnstructuredError,
  319. errorCode: AuthErrorCode.appVerificationUserInteractionFailure.rawValue,
  320. function: #function,
  321. useClientID: true,
  322. reCAPTCHAfallback: true,
  323. forwardingNotification: false
  324. )
  325. }
  326. }
  327. /** @fn testMissingAPNSToken
  328. @brief Tests returning an error for the app failing to provide an APNS device token.
  329. */
  330. func testMissingAPNSToken() async throws {
  331. try await internalTestVerify(
  332. errorCode: AuthErrorCode.missingAppToken.rawValue,
  333. function: #function,
  334. useClientID: true,
  335. reCAPTCHAfallback: true,
  336. presenterError: NSError(
  337. domain: AuthErrors.domain,
  338. code: AuthErrorCode.missingAppToken.rawValue
  339. )
  340. )
  341. }
  342. /** @fn testVerifyPhoneNumberUIDelegateiOSSecretMissingFlow
  343. @brief Tests a successful invocation of @c verifyPhoneNumber:UIDelegate:completion: that falls
  344. back to the reCAPTCHA flow when the push notification is not received before the timeout.
  345. */
  346. func testVerifyPhoneNumberUIDelegateiOSSecretMissingFlow() throws {
  347. try internalFlow(function: #function, useClientID: false, reCAPTCHAfallback: true)
  348. }
  349. /** @fn testVerifyClient
  350. @brief Tests verifying client before sending verification code.
  351. */
  352. func testVerifyClient() throws {
  353. try internalFlow(function: #function, useClientID: true, reCAPTCHAfallback: false)
  354. }
  355. /** @fn testSendVerificationCodeFailedRetry
  356. @brief Tests failed retry after failing to send verification code.
  357. */
  358. func testSendVerificationCodeFailedRetry() throws {
  359. try internalFlowRetry(function: #function)
  360. }
  361. /** @fn testSendVerificationCodeSuccessfulRetry
  362. @brief Tests successful retry after failing to send verification code.
  363. */
  364. func testSendVerificationCodeSuccessfulRetry() throws {
  365. try internalFlowRetry(function: #function, goodRetry: true)
  366. }
  367. /** @fn testPhoneAuthCredentialCoding
  368. @brief Tests successful archiving and unarchiving of @c PhoneAuthCredential.
  369. */
  370. func testPhoneAuthCredentialCoding() throws {
  371. let kVerificationID = "My verificationID"
  372. let kVerificationCode = "1234"
  373. let credential = PhoneAuthCredential(withProviderID: PhoneAuthProvider.id,
  374. verificationID: kVerificationID,
  375. verificationCode: kVerificationCode)
  376. XCTAssertTrue(PhoneAuthCredential.supportsSecureCoding)
  377. let data = try NSKeyedArchiver.archivedData(
  378. withRootObject: credential,
  379. requiringSecureCoding: true
  380. )
  381. let unarchivedCredential = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject(
  382. ofClass: PhoneAuthCredential.self, from: data
  383. ))
  384. switch unarchivedCredential.credentialKind {
  385. case .phoneNumber: XCTFail("Should be verification case")
  386. case let .verification(id, code):
  387. XCTAssertEqual(id, kVerificationID)
  388. XCTAssertEqual(code, kVerificationCode)
  389. }
  390. XCTAssertEqual(unarchivedCredential.provider, PhoneAuthProvider.id)
  391. }
  392. /** @fn testPhoneAuthCredentialCodingPhone
  393. @brief Tests successful archiving and unarchiving of @c PhoneAuthCredential after other constructor.
  394. */
  395. func testPhoneAuthCredentialCodingPhone() throws {
  396. let kTemporaryProof = "Proof"
  397. let kPhoneNumber = "123457"
  398. let credential = PhoneAuthCredential(withTemporaryProof: kTemporaryProof,
  399. phoneNumber: kPhoneNumber,
  400. providerID: PhoneAuthProvider.id)
  401. XCTAssertTrue(PhoneAuthCredential.supportsSecureCoding)
  402. let data = try NSKeyedArchiver.archivedData(
  403. withRootObject: credential,
  404. requiringSecureCoding: true
  405. )
  406. let unarchivedCredential = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject(
  407. ofClasses: [PhoneAuthCredential.self, NSString.self], from: data
  408. ) as? PhoneAuthCredential)
  409. switch unarchivedCredential.credentialKind {
  410. case let .phoneNumber(phoneNumber, temporaryProof):
  411. XCTAssertEqual(temporaryProof, kTemporaryProof)
  412. XCTAssertEqual(phoneNumber, kPhoneNumber)
  413. case .verification: XCTFail("Should be phoneNumber case")
  414. }
  415. XCTAssertEqual(unarchivedCredential.provider, PhoneAuthProvider.id)
  416. }
  417. private func testRecaptchaFlowError(function: String, rceError: Error) async throws {
  418. initApp(function)
  419. let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
  420. // TODO: Figure out how to mock objective C's FIRRecaptchaGetToken response
  421. // Mocking the output of verify() method
  422. let provider = PhoneAuthProvider.provider(auth: auth)
  423. let mockVerifier = FakeAuthRecaptchaVerifier(error: rceError)
  424. AuthRecaptchaVerifier.setShared(mockVerifier, auth: auth)
  425. rpcIssuer.rceMode = "ENFORCE"
  426. do {
  427. let _ = try await provider.verifyClAndSendVerificationCodeWithRecaptcha(
  428. toPhoneNumber: kTestPhoneNumber,
  429. retryOnInvalidAppCredential: false,
  430. uiDelegate: nil,
  431. recaptchaVerifier: mockVerifier
  432. )
  433. } catch {
  434. XCTAssertEqual((error as NSError).code, (rceError as NSError).code)
  435. }
  436. }
  437. private func internalFlowRetry(function: String, goodRetry: Bool = false) throws {
  438. let function = function
  439. initApp(function, useClientID: true, fakeToken: true)
  440. let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
  441. let provider = PhoneAuthProvider.provider(auth: auth)
  442. let expectation = self.expectation(description: function)
  443. // Fake push notification.
  444. auth.appCredentialManager?.fakeCredential = AuthAppCredential(
  445. receipt: kTestReceipt,
  446. secret: kTestSecret
  447. )
  448. // 1. Intercept, handle, and test three RPC calls.
  449. let verifyClientRequestExpectation = self.expectation(description: "verifyClientRequest")
  450. verifyClientRequestExpectation.expectedFulfillmentCount = 2
  451. rpcIssuer?.verifyClientRequester = { request in
  452. XCTAssertEqual(request.appToken, "21402324255E")
  453. XCTAssertFalse(request.isSandbox)
  454. verifyClientRequestExpectation.fulfill()
  455. do {
  456. // Response for the underlying VerifyClientRequest RPC call.
  457. try self.rpcIssuer?.respond(withJSON: [
  458. "receipt": self.kTestReceipt,
  459. "suggestedTimeout": self.kTestTimeout,
  460. ])
  461. } catch {
  462. XCTFail("Failure sending response: \(error)")
  463. }
  464. }
  465. let verifyRequesterExpectation = self.expectation(description: "verifyRequester")
  466. verifyRequesterExpectation.expectedFulfillmentCount = 2
  467. var visited = false
  468. rpcIssuer?.verifyRequester = { request in
  469. XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
  470. switch request.codeIdentity {
  471. case let .credential(credential):
  472. XCTAssertEqual(credential.receipt, self.kTestReceipt)
  473. XCTAssertEqual(credential.secret, self.kTestSecret)
  474. default:
  475. XCTFail("Should be credential")
  476. }
  477. verifyRequesterExpectation.fulfill()
  478. do {
  479. if visited == false || goodRetry == false {
  480. // First Response for the underlying SendVerificationCode RPC call.
  481. try self.rpcIssuer?.respond(serverErrorMessage: "INVALID_APP_CREDENTIAL")
  482. visited = true
  483. } else {
  484. // Second Response for the underlying SendVerificationCode RPC call.
  485. try self.rpcIssuer?
  486. .respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID])
  487. }
  488. } catch {
  489. XCTFail("Failure sending response: \(error)")
  490. }
  491. }
  492. // Use fake authURLPresenter so we can test the parameters that get sent to it.
  493. PhoneAuthProviderTests.auth?.authURLPresenter =
  494. FakePresenter(
  495. urlString: PhoneAuthProviderTests.kFakeRedirectURLStringWithReCAPTCHAToken,
  496. clientID: PhoneAuthProviderTests.kFakeClientID,
  497. firebaseAppID: nil,
  498. errorTest: false,
  499. presenterError: nil
  500. )
  501. // 2. After setting up the fakes and parameters, call `verifyPhoneNumber`.
  502. provider
  503. .verifyPhoneNumber(kTestPhoneNumber, uiDelegate: nil) { verificationID, error in
  504. // 8. After the response triggers the callback in the FakePresenter, verify the callback.
  505. XCTAssertTrue(Thread.isMainThread)
  506. if goodRetry {
  507. XCTAssertNil(error)
  508. XCTAssertEqual(verificationID, self.kTestVerificationID)
  509. } else {
  510. XCTAssertNil(verificationID)
  511. XCTAssertEqual((error as? NSError)?.code, AuthErrorCode.internalError.rawValue)
  512. }
  513. expectation.fulfill()
  514. }
  515. waitForExpectations(timeout: 5)
  516. }
  517. private func internalFlow(function: String,
  518. useClientID: Bool = false,
  519. reCAPTCHAfallback: Bool = false) throws {
  520. let function = function
  521. initApp(function, useClientID: useClientID, fakeToken: true)
  522. let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
  523. let provider = PhoneAuthProvider.provider(auth: auth)
  524. let expectation = self.expectation(description: function)
  525. // Fake push notification.
  526. auth.appCredentialManager?.fakeCredential = AuthAppCredential(
  527. receipt: kTestReceipt,
  528. secret: reCAPTCHAfallback ? nil : kTestSecret
  529. )
  530. // 1. Intercept, handle, and test three RPC calls.
  531. let verifyClientRequestExpectation = self.expectation(description: "verifyClientRequest")
  532. rpcIssuer?.verifyClientRequester = { request in
  533. XCTAssertEqual(request.appToken, "21402324255E")
  534. XCTAssertFalse(request.isSandbox)
  535. verifyClientRequestExpectation.fulfill()
  536. do {
  537. // Response for the underlying VerifyClientRequest RPC call.
  538. try self.rpcIssuer?.respond(withJSON: [
  539. "receipt": self.kTestReceipt,
  540. "suggestedTimeout": self.kTestTimeout,
  541. ])
  542. } catch {
  543. XCTFail("Failure sending response: \(error)")
  544. }
  545. }
  546. if reCAPTCHAfallback {
  547. let projectConfigExpectation = self.expectation(description: "projectConfiguration")
  548. rpcIssuer?.projectConfigRequester = { request in
  549. XCTAssertEqual(request.apiKey, PhoneAuthProviderTests.kFakeAPIKey)
  550. projectConfigExpectation.fulfill()
  551. kAuthGlobalWorkQueue.async {
  552. do {
  553. // Response for the underlying VerifyClientRequest RPC call.
  554. try self.rpcIssuer?.respond(
  555. withJSON: ["projectId": "kFakeProjectID",
  556. "authorizedDomains": [PhoneAuthProviderTests.kFakeAuthorizedDomain]]
  557. )
  558. } catch {
  559. XCTFail("Failure sending response: \(error)")
  560. }
  561. }
  562. }
  563. }
  564. let verifyRequesterExpectation = self.expectation(description: "verifyRequester")
  565. rpcIssuer?.verifyRequester = { request in
  566. XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
  567. if reCAPTCHAfallback {
  568. switch request.codeIdentity {
  569. case let .recaptcha(token):
  570. XCTAssertEqual(token, self.kFakeReCAPTCHAToken)
  571. default:
  572. XCTFail("Should be recaptcha")
  573. }
  574. } else {
  575. switch request.codeIdentity {
  576. case let .credential(credential):
  577. XCTAssertEqual(credential.receipt, self.kTestReceipt)
  578. XCTAssertEqual(credential.secret, self.kTestSecret)
  579. default:
  580. XCTFail("Should be credential")
  581. }
  582. }
  583. verifyRequesterExpectation.fulfill()
  584. do {
  585. // Response for the underlying SendVerificationCode RPC call.
  586. try self.rpcIssuer?
  587. .respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID])
  588. } catch {
  589. XCTFail("Failure sending response: \(error)")
  590. }
  591. }
  592. // Use fake authURLPresenter so we can test the parameters that get sent to it.
  593. PhoneAuthProviderTests.auth?.authURLPresenter =
  594. FakePresenter(
  595. urlString: PhoneAuthProviderTests.kFakeRedirectURLStringWithReCAPTCHAToken,
  596. clientID: useClientID ? PhoneAuthProviderTests.kFakeClientID : nil,
  597. firebaseAppID: useClientID ? nil : PhoneAuthProviderTests.kFakeFirebaseAppID,
  598. errorTest: false,
  599. presenterError: nil
  600. )
  601. let uiDelegate = reCAPTCHAfallback ? FakeUIDelegate() : nil
  602. // 2. After setting up the fakes and parameters, call `verifyPhoneNumber`.
  603. provider
  604. .verifyPhoneNumber(kTestPhoneNumber, uiDelegate: uiDelegate) { verificationID, error in
  605. // 8. After the response triggers the callback in the FakePresenter, verify the callback.
  606. XCTAssertTrue(Thread.isMainThread)
  607. XCTAssertNil(error)
  608. XCTAssertEqual(verificationID, self.kTestVerificationID)
  609. expectation.fulfill()
  610. }
  611. waitForExpectations(timeout: 5)
  612. }
  613. /** @fn testVerifyClient
  614. @brief Tests verifying client before sending verification code.
  615. */
  616. private func internalTestVerify(errorString: String? = nil,
  617. errorURLString: String? = nil,
  618. errorCode: Int = 0,
  619. function: String,
  620. testMode: Bool = false,
  621. useClientID: Bool = false,
  622. bothClientAndAppID: Bool = false,
  623. reCAPTCHAfallback: Bool = false,
  624. forwardingNotification: Bool = true,
  625. presenterError: Error? = nil) async throws {
  626. initApp(function, useClientID: useClientID, bothClientAndAppID: bothClientAndAppID,
  627. testMode: testMode,
  628. forwardingNotification: forwardingNotification)
  629. let auth = try XCTUnwrap(PhoneAuthProviderTests.auth)
  630. let provider = PhoneAuthProvider.provider(auth: auth)
  631. var expectations: [XCTestExpectation] = []
  632. if !reCAPTCHAfallback {
  633. // Fake out appCredentialManager flow.
  634. auth.appCredentialManager?.credential = AuthAppCredential(receipt: kTestReceipt,
  635. secret: kTestSecret)
  636. } else {
  637. // 1. Intercept, handle, and test the projectConfiguration RPC calls.
  638. let projectConfigExpectation = expectation(description: "projectConfiguration")
  639. expectations.append(projectConfigExpectation)
  640. rpcIssuer?.projectConfigRequester = { request in
  641. XCTAssertEqual(request.apiKey, PhoneAuthProviderTests.kFakeAPIKey)
  642. projectConfigExpectation.fulfill()
  643. do {
  644. // Response for the underlying VerifyClientRequest RPC call.
  645. try self.rpcIssuer?.respond(
  646. withJSON: ["projectId": "kFakeProjectID",
  647. "authorizedDomains": [PhoneAuthProviderTests.kFakeAuthorizedDomain]]
  648. )
  649. } catch {
  650. XCTFail("Failure sending response: \(error)")
  651. }
  652. }
  653. }
  654. if reCAPTCHAfallback {
  655. // Use fake authURLPresenter so we can test the parameters that get sent to it.
  656. let urlString = errorURLString ??
  657. PhoneAuthProviderTests.kFakeRedirectURLStringWithReCAPTCHAToken
  658. let errorTest = errorURLString != nil
  659. PhoneAuthProviderTests.auth?.authURLPresenter =
  660. FakePresenter(
  661. urlString: urlString,
  662. clientID: useClientID ? PhoneAuthProviderTests.kFakeClientID : nil,
  663. firebaseAppID: useClientID ? nil : PhoneAuthProviderTests.kFakeFirebaseAppID,
  664. errorTest: errorTest,
  665. presenterError: presenterError
  666. )
  667. }
  668. if errorURLString == nil, presenterError == nil {
  669. let requestExpectation = expectation(description: "verifyRequester")
  670. expectations.append(requestExpectation)
  671. rpcIssuer?.verifyRequester = { request in
  672. XCTAssertEqual(request.phoneNumber, self.kTestPhoneNumber)
  673. switch request.codeIdentity {
  674. case let .credential(credential):
  675. XCTAssertFalse(reCAPTCHAfallback)
  676. XCTAssertEqual(credential.receipt, self.kTestReceipt)
  677. XCTAssertEqual(credential.secret, self.kTestSecret)
  678. case let .recaptcha(token):
  679. XCTAssertTrue(reCAPTCHAfallback)
  680. XCTAssertEqual(token, self.kFakeReCAPTCHAToken)
  681. case .empty:
  682. XCTAssertTrue(testMode)
  683. }
  684. requestExpectation.fulfill()
  685. do {
  686. // Response for the underlying SendVerificationCode RPC call.
  687. if let errorString {
  688. try self.rpcIssuer?.respond(serverErrorMessage: errorString)
  689. } else {
  690. try self.rpcIssuer?
  691. .respond(withJSON: [self.kVerificationIDKey: self.kTestVerificationID])
  692. }
  693. } catch {
  694. XCTFail("Failure sending response: \(error)")
  695. }
  696. }
  697. }
  698. let uiDelegate = reCAPTCHAfallback ? FakeUIDelegate() : nil
  699. // 2. After setting up the parameters, call `verifyPhoneNumber`.
  700. do {
  701. // Call the async function to verify the phone number
  702. let verificationID = try await provider.verifyPhoneNumber(
  703. kTestPhoneNumber,
  704. uiDelegate: uiDelegate
  705. )
  706. // Assert that the verificationID matches the expected value
  707. XCTAssertEqual(verificationID, kTestVerificationID)
  708. } catch {
  709. // If an error occurs, assert that verificationID is nil and the error code matches the
  710. // expected value
  711. XCTAssertEqual((error as NSError).code, errorCode)
  712. }
  713. await fulfillment(of: expectations, timeout: 5.0)
  714. }
  715. private func initApp(_ functionName: String,
  716. useClientID: Bool = false,
  717. bothClientAndAppID: Bool = false,
  718. testMode: Bool = false,
  719. forwardingNotification: Bool = true,
  720. fakeToken: Bool = false) {
  721. let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000",
  722. gcmSenderID: "00000000000000000-00000000000-000000000")
  723. options.apiKey = PhoneAuthProviderTests.kFakeAPIKey
  724. options.projectID = "myProjectID"
  725. if useClientID {
  726. options.clientID = PhoneAuthProviderTests.kFakeClientID
  727. }
  728. if !useClientID || bothClientAndAppID {
  729. // Use the appID.
  730. options.googleAppID = PhoneAuthProviderTests.kFakeFirebaseAppID
  731. }
  732. let scheme = useClientID ? PhoneAuthProviderTests.kFakeReverseClientID :
  733. PhoneAuthProviderTests.kFakeEncodedFirebaseAppID
  734. let strippedName = functionName.replacingOccurrences(of: "(", with: "")
  735. .replacingOccurrences(of: ")", with: "")
  736. FirebaseApp.configure(name: strippedName, options: options)
  737. let auth = Auth.auth(app: FirebaseApp.app(name: strippedName)!)
  738. kAuthGlobalWorkQueue.sync {
  739. // Wait for Auth protectedDataInitialization to finish.
  740. PhoneAuthProviderTests.auth = auth
  741. if testMode {
  742. // Disable app verification.
  743. let settings = AuthSettings()
  744. settings.appVerificationDisabledForTesting = true
  745. auth.settings = settings
  746. }
  747. auth.notificationManager?.immediateCallbackForTestFaking = { forwardingNotification }
  748. auth.mainBundleUrlTypes = [["CFBundleURLSchemes": [scheme]]]
  749. if fakeToken {
  750. guard let data = "!@#$%^".data(using: .utf8) else {
  751. XCTFail("Failed to encode data for fake token")
  752. return
  753. }
  754. auth.tokenManager?.tokenStore = AuthAPNSToken(withData: data, type: .prod)
  755. } else {
  756. // Skip APNS token fetching.
  757. auth.tokenManager = FakeTokenManager(withApplication: UIApplication.shared)
  758. }
  759. }
  760. }
  761. class FakeAuthRecaptchaVerifier: AuthRecaptchaVerifier {
  762. var captchaResponse: String
  763. var error: Error?
  764. init(captchaResponse: String? = nil, error: Error? = nil) {
  765. self.captchaResponse = captchaResponse ?? "NO_RECAPTCHA"
  766. self.error = error
  767. super.init()
  768. }
  769. override func verify(forceRefresh: Bool, action: AuthRecaptchaAction) async throws -> String {
  770. if let error = error {
  771. throw error
  772. }
  773. return captchaResponse
  774. }
  775. }
  776. class FakeTokenManager: AuthAPNSTokenManager {
  777. override func getTokenInternal(callback: @escaping (Result<AuthAPNSToken, Error>) -> Void) {
  778. let error = NSError(domain: "dummy domain", code: AuthErrorCode.missingAppToken.rawValue)
  779. callback(.failure(error))
  780. }
  781. }
  782. class FakePresenter: NSObject, AuthWebViewControllerDelegate {
  783. func webViewController(_ webViewController: AuthWebViewController,
  784. canHandle URL: URL) -> Bool {
  785. XCTFail("Do not call")
  786. return false
  787. }
  788. func webViewControllerDidCancel(_ webViewController: AuthWebViewController) {
  789. XCTFail("Do not call")
  790. }
  791. func webViewController(_ webViewController: AuthWebViewController,
  792. didFailWithError error: Error) {
  793. XCTFail("Do not call")
  794. }
  795. func present(_ presentURL: URL,
  796. uiDelegate UIDelegate: AuthUIDelegate?,
  797. callbackMatcher: @escaping (URL?) -> Bool,
  798. completion: @escaping (URL?, Error?) -> Void) {
  799. // 5. Verify flow triggers present in the FakePresenter class with the right parameters.
  800. XCTAssertEqual(presentURL.scheme, "https")
  801. XCTAssertEqual(presentURL.host, kFakeAuthorizedDomain)
  802. XCTAssertEqual(presentURL.path, "/__/auth/handler")
  803. let actualURLComponents = URLComponents(url: presentURL, resolvingAgainstBaseURL: false)
  804. guard let _ = actualURLComponents?.queryItems else {
  805. XCTFail("Failed to get queryItems")
  806. return
  807. }
  808. let params = AuthWebUtils.dictionary(withHttpArgumentsString: presentURL.query)
  809. XCTAssertEqual(params["ibi"], Bundle.main.bundleIdentifier)
  810. XCTAssertEqual(params["apiKey"], PhoneAuthProviderTests.kFakeAPIKey)
  811. XCTAssertEqual(params["authType"], "verifyApp")
  812. XCTAssertNotNil(params["v"])
  813. if OAuthProviderTests.testTenantID {
  814. XCTAssertEqual(params["tid"], OAuthProviderTests.kFakeTenantID)
  815. } else {
  816. XCTAssertNil(params["tid"])
  817. }
  818. let appCheckToken = presentURL.fragment
  819. let verifyAppCheckToken = OAuthProviderTests.testAppCheck ? "fac=fakeAppCheckToken" : nil
  820. XCTAssertEqual(appCheckToken, verifyAppCheckToken)
  821. var redirectURL = ""
  822. if let clientID {
  823. XCTAssertEqual(params["clientId"], clientID)
  824. redirectURL = "\(kFakeReverseClientID)\(urlString)"
  825. }
  826. if let firebaseAppID {
  827. XCTAssertEqual(params["appId"], firebaseAppID)
  828. redirectURL = "\(kFakeEncodedFirebaseAppID)\(urlString)"
  829. }
  830. // 6. Test callbackMatcher
  831. // Verify that the URL is rejected by the callback matcher without the event ID.
  832. XCTAssertFalse(callbackMatcher(URL(string: "\(redirectURL)")))
  833. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  834. guard let eventID = params["eventId"] else {
  835. XCTFail("Failed to get eventID")
  836. return
  837. }
  838. let redirectWithEventID = "\(redirectURL)%26eventId%3D\(eventID)"
  839. let originalComponents = URLComponents(string: redirectWithEventID)!
  840. XCTAssertEqual(callbackMatcher(originalComponents.url), !errorTest)
  841. var components = originalComponents
  842. components.query = "https"
  843. XCTAssertFalse(callbackMatcher(components.url))
  844. components = originalComponents
  845. components.host = "badhost"
  846. XCTAssertFalse(callbackMatcher(components.url))
  847. components = originalComponents
  848. components.path = "badpath"
  849. XCTAssertFalse(callbackMatcher(components.url))
  850. components = originalComponents
  851. components.query = "badquery"
  852. XCTAssertFalse(callbackMatcher(components.url))
  853. // 7. Do the callback to the original call.
  854. kAuthGlobalWorkQueue.async {
  855. if let presenterError = self.presenterError {
  856. completion(nil, presenterError)
  857. } else {
  858. completion(URL(string: "\(kFakeEncodedFirebaseAppID)\(self.urlString)") ?? nil, nil)
  859. }
  860. }
  861. }
  862. let urlString: String
  863. let clientID: String?
  864. let firebaseAppID: String?
  865. let errorTest: Bool
  866. let presenterError: Error?
  867. init(urlString: String, clientID: String?, firebaseAppID: String?, errorTest: Bool,
  868. presenterError: Error?) {
  869. self.urlString = urlString
  870. self.clientID = clientID
  871. self.firebaseAppID = firebaseAppID
  872. self.errorTest = errorTest
  873. self.presenterError = presenterError
  874. }
  875. }
  876. private class FakeUIDelegate: NSObject, AuthUIDelegate {
  877. func present(_ viewControllerToPresent: UIViewController, animated flag: Bool,
  878. completion: (() -> Void)? = nil) {
  879. guard let safariController = viewControllerToPresent as? SFSafariViewController,
  880. let delegate = safariController.delegate as? AuthURLPresenter,
  881. let uiDelegate = delegate.uiDelegate as? FakeUIDelegate else {
  882. XCTFail("Failed to get presentURL from controller")
  883. return
  884. }
  885. XCTAssertEqual(self, uiDelegate)
  886. }
  887. func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
  888. XCTFail("Implement me")
  889. }
  890. }
  891. private static let kFakeRedirectURLStringInvalidClientID =
  892. "//firebaseauth/" +
  893. "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal" +
  894. "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Finvalid-oauth-client-id%2522%252" +
  895. "C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%2520either%252" +
  896. "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%26" +
  897. "authType%3DverifyApp"
  898. private static let kFakeRedirectURLStringWebNetworkRequestFailed =
  899. "//firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fc" +
  900. "allback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Fnetwork-request-failed%2522%" +
  901. "252C%2522message%2522%253A%2522The%2520network%2520request%2520failed%2520.%2522%257D%" +
  902. "26authType%3DverifyApp"
  903. private static let kFakeRedirectURLStringWebInternalError =
  904. "//firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal" +
  905. "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Finternal-error%2522%252C%" +
  906. "2522message%2522%253A%2522Internal%2520error%2520.%2522%257D%26authType%3DverifyApp"
  907. private static let kFakeRedirectURLStringUnknownError =
  908. "//firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal" +
  909. "lback%3FfirebaseError%3D%257B%2522code%2522%253A%2522auth%252Funknown-error-id%2522%252" +
  910. "C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%2520either%252" +
  911. "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%26" +
  912. "authType%3DverifyApp"
  913. private static let kFakeRedirectURLStringUnstructuredError =
  914. "//firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcal" +
  915. "lback%3FfirebaseError%3D%257B%2522unstructuredcode%2522%253A%2522auth%252Funknown-error-id%" +
  916. "2522%252" +
  917. "C%2522unstructuredmessage%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%" +
  918. "2520either%252" +
  919. "0invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%" +
  920. "26authType%3DverifyApp"
  921. private static let kFakeRedirectURLStringWithReCAPTCHAToken =
  922. "://firebaseauth/" +
  923. "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3FauthType%" +
  924. "3DverifyApp%26recaptchaToken%3DfakeReCAPTCHAToken"
  925. }
  926. #endif