FIRPhoneAuthProvider.m 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <TargetConditionals.h>
  17. #if TARGET_OS_IOS
  18. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthSettings.h"
  19. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRMultiFactorResolver.h"
  20. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRPhoneAuthProvider.h"
  21. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  22. #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h"
  23. #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h"
  24. #import "FirebaseAuth/Sources/Backend/FIRAuthBackend+MultiFactor.h"
  25. #import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h"
  26. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigRequest.h"
  27. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigResponse.h"
  28. #import "FirebaseAuth/Sources/Backend/RPC/FIRSendVerificationCodeRequest.h"
  29. #import "FirebaseAuth/Sources/Backend/RPC/FIRSendVerificationCodeResponse.h"
  30. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyClientRequest.h"
  31. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyClientResponse.h"
  32. #import "FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRStartMFAEnrollmentRequest.h"
  33. #import "FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRStartMFAEnrollmentResponse.h"
  34. #import "FirebaseAuth/Sources/Backend/RPC/Proto/Phone/FIRAuthProtoStartMFAPhoneRequestInfo.h"
  35. #import "FirebaseAuth/Sources/MultiFactor/FIRMultiFactorSession+Internal.h"
  36. #import "FirebaseAuth/Sources/SystemService/FIRAuthAPNSToken.h"
  37. #import "FirebaseAuth/Sources/SystemService/FIRAuthAPNSTokenManager.h"
  38. #import "FirebaseAuth/Sources/SystemService/FIRAuthAppCredential.h"
  39. #import "FirebaseAuth/Sources/SystemService/FIRAuthAppCredentialManager.h"
  40. #import "FirebaseAuth/Sources/SystemService/FIRAuthNotificationManager.h"
  41. #import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
  42. #import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
  43. #import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
  44. #if TARGET_OS_IOS
  45. #import "FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h"
  46. #import "FirebaseAuth/Sources/MultiFactor/Phone/FIRPhoneMultiFactorInfo+Internal.h"
  47. #endif
  48. NS_ASSUME_NONNULL_BEGIN
  49. /** @typedef FIRReCAPTCHAURLCallBack
  50. @brief The callback invoked at the end of the flow to fetch a reCAPTCHA URL.
  51. @param reCAPTCHAURL The reCAPTCHA URL.
  52. @param error The error that occurred while fetching the reCAPTCHAURL, if any.
  53. */
  54. typedef void (^FIRReCAPTCHAURLCallBack)(NSURL *_Nullable reCAPTCHAURL, NSError *_Nullable error);
  55. /** @typedef FIRVerifyClientCallback
  56. @brief The callback invoked at the end of a client verification flow.
  57. @param appCredential credential that proves the identity of the app during a phone
  58. authentication flow.
  59. @param error The error that occurred while verifying the app, if any.
  60. */
  61. typedef void (^FIRVerifyClientCallback)(FIRAuthAppCredential *_Nullable appCredential,
  62. NSString *_Nullable reCAPTCHAToken,
  63. NSError *_Nullable error);
  64. /** @typedef FIRFetchAuthDomainCallback
  65. @brief The callback invoked at the end of the flow to fetch the Auth domain.
  66. @param authDomain The Auth domain.
  67. @param error The error that occurred while fetching the auth domain, if any.
  68. */
  69. typedef void (^FIRFetchAuthDomainCallback)(NSString *_Nullable authDomain,
  70. NSError *_Nullable error);
  71. /** @var kauthTypeVerifyApp
  72. @brief The auth type to be specified in the app verification request.
  73. */
  74. static NSString *const kAuthTypeVerifyApp = @"verifyApp";
  75. /** @var kCustomUrlSchemePrefix
  76. @brief The prefix to append to the Firebase app ID custom callback scheme..
  77. */
  78. static NSString *const kCustomUrlSchemePrefix = @"app-";
  79. /** @var kReCAPTCHAURLStringFormat
  80. @brief The format of the URL used to open the reCAPTCHA page during app verification.
  81. */
  82. NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?";
  83. extern NSString *const FIRPhoneMultiFactorID;
  84. @implementation FIRPhoneAuthProvider {
  85. /** @var _auth
  86. @brief The auth instance used for verifying the phone number.
  87. */
  88. FIRAuth *_auth;
  89. /** @var _callbackScheme
  90. @brief The callback URL scheme used for reCAPTCHA fallback.
  91. */
  92. NSString *_callbackScheme;
  93. }
  94. /** @fn initWithAuth:
  95. @brief returns an instance of @c FIRPhoneAuthProvider associated with the provided auth
  96. instance.
  97. @return An Instance of @c FIRPhoneAuthProvider.
  98. */
  99. - (nullable instancetype)initWithAuth:(FIRAuth *)auth {
  100. self = [super init];
  101. if (self) {
  102. _auth = auth;
  103. if (_auth.app.options.clientID) {
  104. _callbackScheme = [[[_auth.app.options.clientID componentsSeparatedByString:@"."]
  105. reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
  106. } else {
  107. _callbackScheme = [kCustomUrlSchemePrefix
  108. stringByAppendingString:[_auth.app.options.googleAppID
  109. stringByReplacingOccurrencesOfString:@":"
  110. withString:@"-"]];
  111. }
  112. }
  113. return self;
  114. }
  115. - (void)verifyPhoneNumber:(NSString *)phoneNumber
  116. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  117. completion:(nullable FIRVerificationResultCallback)completion {
  118. if (![FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:_callbackScheme]) {
  119. [NSException raise:NSInternalInconsistencyException
  120. format:@"Please register custom URL scheme '%@' in the app's Info.plist file.",
  121. _callbackScheme];
  122. }
  123. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  124. FIRVerificationResultCallback callBackOnMainThread =
  125. ^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  126. if (completion) {
  127. dispatch_async(dispatch_get_main_queue(), ^{
  128. completion(verificationID, error);
  129. });
  130. }
  131. };
  132. [self
  133. internalVerifyPhoneNumber:phoneNumber
  134. UIDelegate:UIDelegate
  135. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  136. if (!error) {
  137. callBackOnMainThread(verificationID, nil);
  138. return;
  139. } else {
  140. callBackOnMainThread(nil, error);
  141. return;
  142. }
  143. }];
  144. });
  145. }
  146. - (void)verifyPhoneNumberWithMultiFactorInfo:(FIRPhoneMultiFactorInfo *)phoneMultiFactorInfo
  147. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  148. multiFactorSession:(nullable FIRMultiFactorSession *)session
  149. completion:(nullable FIRVerificationResultCallback)completion {
  150. session.multiFactorInfo = phoneMultiFactorInfo;
  151. [self verifyPhoneNumber:phoneMultiFactorInfo.phoneNumber
  152. UIDelegate:UIDelegate
  153. multiFactorSession:session
  154. completion:completion];
  155. }
  156. - (void)verifyPhoneNumber:(NSString *)phoneNumber
  157. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  158. multiFactorSession:(nullable FIRMultiFactorSession *)session
  159. completion:(nullable FIRVerificationResultCallback)completion {
  160. if (!session) {
  161. [self verifyPhoneNumber:phoneNumber UIDelegate:UIDelegate completion:completion];
  162. return;
  163. }
  164. if (![FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:_callbackScheme]) {
  165. [NSException raise:NSInternalInconsistencyException
  166. format:@"Please register custom URL scheme '%@' in the app's Info.plist file.",
  167. _callbackScheme];
  168. }
  169. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  170. FIRVerificationResultCallback callBackOnMainThread =
  171. ^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  172. if (completion) {
  173. dispatch_async(dispatch_get_main_queue(), ^{
  174. completion(verificationID, error);
  175. });
  176. }
  177. };
  178. [self
  179. internalVerifyPhoneNumber:phoneNumber
  180. UIDelegate:UIDelegate
  181. multiFactorSession:session
  182. completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  183. if (!error) {
  184. callBackOnMainThread(verificationID, nil);
  185. return;
  186. } else {
  187. callBackOnMainThread(nil, error);
  188. return;
  189. }
  190. }];
  191. });
  192. }
  193. - (FIRPhoneAuthCredential *)credentialWithVerificationID:(NSString *)verificationID
  194. verificationCode:(NSString *)verificationCode {
  195. return [[FIRPhoneAuthCredential alloc] initWithProviderID:FIRPhoneAuthProviderID
  196. verificationID:verificationID
  197. verificationCode:verificationCode];
  198. }
  199. + (instancetype)provider {
  200. return [[self alloc] initWithAuth:[FIRAuth auth]];
  201. }
  202. + (instancetype)providerWithAuth:(FIRAuth *)auth {
  203. return [[self alloc] initWithAuth:auth];
  204. }
  205. #pragma mark - Internal Methods
  206. /** @fn reCAPTCHATokenForURL:error:
  207. @brief Parses the reCAPTCHA URL and returns the reCAPTCHA token.
  208. @param URL The url to be parsed for a reCAPTCHA token.
  209. @param error The error that occurred if any.
  210. @return The reCAPTCHA token if successful.
  211. */
  212. - (NSString *)reCAPTCHATokenForURL:(NSURL *)URL error:(NSError **)error {
  213. NSURLComponents *actualURLComponents = [NSURLComponents componentsWithURL:URL
  214. resolvingAgainstBaseURL:NO];
  215. NSArray<NSURLQueryItem *> *queryItems = [actualURLComponents queryItems];
  216. NSString *deepLinkURL = [FIRAuthWebUtils queryItemValue:@"deep_link_id" from:queryItems];
  217. NSData *errorData;
  218. if (deepLinkURL) {
  219. actualURLComponents = [NSURLComponents componentsWithString:deepLinkURL];
  220. queryItems = [actualURLComponents queryItems];
  221. NSString *recaptchaToken = [FIRAuthWebUtils queryItemValue:@"recaptchaToken" from:queryItems];
  222. if (recaptchaToken) {
  223. return recaptchaToken;
  224. }
  225. NSString *firebaseError = [FIRAuthWebUtils queryItemValue:@"firebaseError" from:queryItems];
  226. errorData = [firebaseError dataUsingEncoding:NSUTF8StringEncoding];
  227. } else {
  228. errorData = nil;
  229. }
  230. NSError *jsonError;
  231. NSDictionary *errorDict = [NSJSONSerialization JSONObjectWithData:errorData
  232. options:0
  233. error:&jsonError];
  234. if (jsonError) {
  235. *error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:jsonError];
  236. return nil;
  237. }
  238. *error = [FIRAuthErrorUtils URLResponseErrorWithCode:errorDict[@"code"]
  239. message:errorDict[@"message"]];
  240. if (!*error) {
  241. NSString *reason;
  242. if (errorDict[@"code"] && errorDict[@"message"]) {
  243. reason = [NSString stringWithFormat:@"[%@] - %@", errorDict[@"code"], errorDict[@"message"]];
  244. } else {
  245. reason = [NSString stringWithFormat:@"An unknown error occurred with the following "
  246. "response: %@",
  247. deepLinkURL];
  248. }
  249. *error = [FIRAuthErrorUtils appVerificationUserInteractionFailureWithReason:reason];
  250. }
  251. return nil;
  252. }
  253. /** @fn internalVerifyPhoneNumber:completion:
  254. @brief Starts the phone number authentication flow by sending a verifcation code to the
  255. specified phone number.
  256. @param phoneNumber The phone number to be verified.
  257. @param completion The callback to be invoked when the verification flow is finished.
  258. */
  259. - (void)internalVerifyPhoneNumber:(NSString *)phoneNumber
  260. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  261. completion:(nullable FIRVerificationResultCallback)completion {
  262. if (!phoneNumber.length) {
  263. completion(nil, [FIRAuthErrorUtils missingPhoneNumberErrorWithMessage:nil]);
  264. return;
  265. }
  266. [_auth.notificationManager
  267. checkNotificationForwardingWithCallback:^(BOOL isNotificationBeingForwarded) {
  268. if (!isNotificationBeingForwarded) {
  269. completion(nil, [FIRAuthErrorUtils notificationNotForwardedError]);
  270. return;
  271. }
  272. FIRVerificationResultCallback callback =
  273. ^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  274. if (completion) {
  275. completion(verificationID, error);
  276. }
  277. };
  278. [self verifyClientAndSendVerificationCodeToPhoneNumber:phoneNumber
  279. retryOnInvalidAppCredential:YES
  280. UIDelegate:UIDelegate
  281. callback:callback];
  282. }];
  283. }
  284. - (void)internalVerifyPhoneNumber:(NSString *)phoneNumber
  285. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  286. multiFactorSession:(nullable FIRMultiFactorSession *)session
  287. completion:(nullable FIRVerificationResultCallback)completion {
  288. if (!phoneNumber.length) {
  289. if (completion) {
  290. completion(nil, [FIRAuthErrorUtils missingPhoneNumberErrorWithMessage:nil]);
  291. }
  292. return;
  293. }
  294. [_auth.notificationManager
  295. checkNotificationForwardingWithCallback:^(BOOL isNotificationBeingForwarded) {
  296. if (!isNotificationBeingForwarded) {
  297. if (completion) {
  298. completion(nil, [FIRAuthErrorUtils notificationNotForwardedError]);
  299. }
  300. return;
  301. }
  302. FIRVerificationResultCallback callback =
  303. ^(NSString *_Nullable verificationID, NSError *_Nullable error) {
  304. if (completion) {
  305. completion(verificationID, error);
  306. }
  307. };
  308. [self verifyClientAndSendVerificationCodeToPhoneNumber:phoneNumber
  309. retryOnInvalidAppCredential:YES
  310. UIDelegate:UIDelegate
  311. multiFactorSession:session
  312. callback:callback];
  313. }];
  314. }
  315. /** @fn verifyClientAndSendVerificationCodeToPhoneNumber:retryOnInvalidAppCredential:callback:
  316. @brief Starts the flow to verify the client via silent push notification.
  317. @param retryOnInvalidAppCredential Whether of not the flow should be retried if an
  318. FIRAuthErrorCodeInvalidAppCredential error is returned from the backend.
  319. @param phoneNumber The phone number to be verified.
  320. @param callback The callback to be invoked on the global work queue when the flow is
  321. finished.
  322. */
  323. - (void)verifyClientAndSendVerificationCodeToPhoneNumber:(NSString *)phoneNumber
  324. retryOnInvalidAppCredential:(BOOL)retryOnInvalidAppCredential
  325. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  326. callback:(FIRVerificationResultCallback)callback {
  327. if (_auth.settings.isAppVerificationDisabledForTesting) {
  328. FIRSendVerificationCodeRequest *request =
  329. [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:phoneNumber
  330. appCredential:nil
  331. reCAPTCHAToken:nil
  332. requestConfiguration:_auth.requestConfiguration];
  333. [FIRAuthBackend sendVerificationCode:request
  334. callback:^(FIRSendVerificationCodeResponse *_Nullable response,
  335. NSError *_Nullable error) {
  336. callback(response.verificationID, error);
  337. }];
  338. return;
  339. }
  340. [self
  341. verifyClientWithUIDelegate:UIDelegate
  342. completion:^(FIRAuthAppCredential *_Nullable appCredential,
  343. NSString *_Nullable reCAPTCHAToken, NSError *_Nullable error) {
  344. if (error) {
  345. callback(nil, error);
  346. return;
  347. }
  348. FIRSendVerificationCodeRequest *_Nullable request;
  349. if (appCredential) {
  350. request = [[FIRSendVerificationCodeRequest alloc]
  351. initWithPhoneNumber:phoneNumber
  352. appCredential:appCredential
  353. reCAPTCHAToken:nil
  354. requestConfiguration:self->_auth.requestConfiguration];
  355. } else if (reCAPTCHAToken) {
  356. request = [[FIRSendVerificationCodeRequest alloc]
  357. initWithPhoneNumber:phoneNumber
  358. appCredential:nil
  359. reCAPTCHAToken:reCAPTCHAToken
  360. requestConfiguration:self->_auth.requestConfiguration];
  361. }
  362. if (request) {
  363. [FIRAuthBackend
  364. sendVerificationCode:request
  365. callback:^(
  366. FIRSendVerificationCodeResponse *_Nullable response,
  367. NSError *_Nullable error) {
  368. if (error) {
  369. if (error.code ==
  370. FIRAuthErrorCodeInvalidAppCredential) {
  371. if (retryOnInvalidAppCredential) {
  372. [self->_auth
  373. .appCredentialManager clearCredential];
  374. [self
  375. verifyClientAndSendVerificationCodeToPhoneNumber:
  376. phoneNumber
  377. retryOnInvalidAppCredential:
  378. NO
  379. UIDelegate:
  380. UIDelegate
  381. callback:
  382. callback];
  383. return;
  384. }
  385. callback(
  386. nil,
  387. [FIRAuthErrorUtils
  388. unexpectedResponseWithDeserializedResponse:
  389. nil
  390. underlyingError:
  391. error]);
  392. return;
  393. }
  394. callback(nil, error);
  395. return;
  396. }
  397. callback(response.verificationID, nil);
  398. }];
  399. }
  400. }];
  401. }
  402. - (void)verifyClientAndSendVerificationCodeToPhoneNumber:(NSString *)phoneNumber
  403. retryOnInvalidAppCredential:(BOOL)retryOnInvalidAppCredential
  404. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  405. multiFactorSession:(nullable FIRMultiFactorSession *)session
  406. callback:(FIRVerificationResultCallback)callback {
  407. if (_auth.settings.isAppVerificationDisabledForTesting) {
  408. FIRSendVerificationCodeRequest *request =
  409. [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:phoneNumber
  410. appCredential:nil
  411. reCAPTCHAToken:nil
  412. requestConfiguration:_auth.requestConfiguration];
  413. [FIRAuthBackend sendVerificationCode:request
  414. callback:^(FIRSendVerificationCodeResponse *_Nullable response,
  415. NSError *_Nullable error) {
  416. callback(response.verificationID, error);
  417. }];
  418. return;
  419. }
  420. [self
  421. verifyClientWithUIDelegate:UIDelegate
  422. completion:^(FIRAuthAppCredential *_Nullable appCredential,
  423. NSString *_Nullable reCAPTCHAToken, NSError *_Nullable error) {
  424. if (error) {
  425. if (callback) {
  426. callback(nil, error);
  427. }
  428. return;
  429. }
  430. NSString *IDToken = session.IDToken;
  431. FIRAuthProtoStartMFAPhoneRequestInfo *startMFARequestInfo =
  432. [[FIRAuthProtoStartMFAPhoneRequestInfo alloc]
  433. initWithPhoneNumber:phoneNumber
  434. appCredential:appCredential
  435. reCAPTCHAToken:reCAPTCHAToken];
  436. if (session.IDToken) {
  437. FIRStartMFAEnrollmentRequest *request =
  438. [[FIRStartMFAEnrollmentRequest alloc]
  439. initWithIDToken:IDToken
  440. enrollmentInfo:startMFARequestInfo
  441. requestConfiguration:self->_auth.requestConfiguration];
  442. [FIRAuthBackend
  443. startMultiFactorEnrollment:request
  444. callback:^(FIRStartMFAEnrollmentResponse
  445. *_Nullable response,
  446. NSError *_Nullable error) {
  447. if (error) {
  448. if (error.code ==
  449. FIRAuthErrorCodeInvalidAppCredential) {
  450. if (retryOnInvalidAppCredential) {
  451. [self->_auth.appCredentialManager
  452. clearCredential];
  453. [self
  454. verifyClientAndSendVerificationCodeToPhoneNumber:
  455. phoneNumber
  456. retryOnInvalidAppCredential:
  457. NO
  458. UIDelegate:
  459. UIDelegate
  460. multiFactorSession:
  461. session
  462. callback:
  463. callback];
  464. return;
  465. }
  466. if (callback) {
  467. callback(
  468. nil,
  469. [FIRAuthErrorUtils
  470. unexpectedResponseWithDeserializedResponse:
  471. nil
  472. underlyingError:
  473. error]);
  474. }
  475. return;
  476. } else {
  477. if (callback) {
  478. callback(nil, error);
  479. }
  480. }
  481. } else {
  482. if (callback) {
  483. callback(
  484. response.enrollmentResponse.sessionInfo,
  485. nil);
  486. }
  487. }
  488. }];
  489. } else {
  490. FIRStartMFASignInRequest *request = [[FIRStartMFASignInRequest alloc]
  491. initWithMFAPendingCredential:session.MFAPendingCredential
  492. MFAEnrollmentID:session.multiFactorInfo.UID
  493. signInInfo:startMFARequestInfo
  494. requestConfiguration:self->_auth.requestConfiguration];
  495. [FIRAuthBackend
  496. startMultiFactorSignIn:request
  497. callback:^(
  498. FIRStartMFASignInResponse *_Nullable response,
  499. NSError *_Nullable error) {
  500. if (error) {
  501. if (error.code ==
  502. FIRAuthErrorCodeInvalidAppCredential) {
  503. if (retryOnInvalidAppCredential) {
  504. [self->_auth
  505. .appCredentialManager clearCredential];
  506. [self
  507. verifyClientAndSendVerificationCodeToPhoneNumber:
  508. phoneNumber
  509. retryOnInvalidAppCredential:
  510. NO
  511. UIDelegate:
  512. UIDelegate
  513. multiFactorSession:
  514. session
  515. callback:
  516. callback];
  517. return;
  518. }
  519. if (callback) {
  520. callback(
  521. nil,
  522. [FIRAuthErrorUtils
  523. unexpectedResponseWithDeserializedResponse:
  524. nil
  525. underlyingError:
  526. error]);
  527. }
  528. return;
  529. } else {
  530. if (callback) {
  531. callback(nil, error);
  532. }
  533. }
  534. } else {
  535. if (callback) {
  536. callback(response.responseInfo.sessionInfo, nil);
  537. }
  538. }
  539. }];
  540. }
  541. }];
  542. }
  543. /** @fn verifyClientWithCompletion:completion:
  544. @brief Continues the flow to verify the client via silent push notification.
  545. @param completion The callback to be invoked when the client verification flow is finished.
  546. */
  547. - (void)verifyClientWithUIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  548. completion:(FIRVerifyClientCallback)completion {
  549. if (_auth.appCredentialManager.credential) {
  550. completion(_auth.appCredentialManager.credential, nil, nil);
  551. return;
  552. }
  553. [_auth.tokenManager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token,
  554. NSError *_Nullable error) {
  555. if (!token) {
  556. [self reCAPTCHAFlowWithUIDelegate:UIDelegate completion:completion];
  557. return;
  558. }
  559. FIRVerifyClientRequest *request =
  560. [[FIRVerifyClientRequest alloc] initWithAppToken:token.string
  561. isSandbox:token.type == FIRAuthAPNSTokenTypeSandbox
  562. requestConfiguration:self->_auth.requestConfiguration];
  563. [FIRAuthBackend
  564. verifyClient:request
  565. callback:^(FIRVerifyClientResponse *_Nullable response, NSError *_Nullable error) {
  566. if (error) {
  567. NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
  568. BOOL isInvalidAppCredential =
  569. error.code == FIRAuthErrorCodeInternalError &&
  570. underlyingError.code == FIRAuthErrorCodeInvalidAppCredential;
  571. if (error.code != FIRAuthErrorCodeMissingAppToken && !isInvalidAppCredential) {
  572. completion(nil, nil, error);
  573. return;
  574. } else {
  575. [self reCAPTCHAFlowWithUIDelegate:UIDelegate completion:completion];
  576. return;
  577. }
  578. }
  579. NSTimeInterval timeout = [response.suggestedTimeOutDate timeIntervalSinceNow];
  580. [self->_auth.appCredentialManager
  581. didStartVerificationWithReceipt:response.receipt
  582. timeout:timeout
  583. callback:^(FIRAuthAppCredential *credential) {
  584. if (!credential.secret) {
  585. FIRLogWarning(kFIRLoggerAuth, @"I-AUT000014",
  586. @"Failed to receive remote notification "
  587. @"to verify app identity within "
  588. @"%.0f second(s)",
  589. timeout);
  590. }
  591. completion(credential, nil, nil);
  592. }];
  593. }];
  594. }];
  595. }
  596. - (void)reCAPTCHAFlowWithUIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  597. completion:(FIRVerifyClientCallback)completion {
  598. NSString *eventID = [FIRAuthWebUtils randomStringWithLength:10];
  599. [self
  600. reCAPTCHAURLWithEventID:eventID
  601. completion:^(NSURL *_Nullable reCAPTCHAURL, NSError *_Nullable error) {
  602. if (error) {
  603. completion(nil, nil, error);
  604. return;
  605. }
  606. FIRAuthURLCallbackMatcher callbackMatcher =
  607. ^BOOL(NSURL *_Nullable callbackURL) {
  608. return [FIRAuthWebUtils isExpectedCallbackURL:callbackURL
  609. eventID:eventID
  610. authType:kAuthTypeVerifyApp
  611. callbackScheme:self->_callbackScheme];
  612. };
  613. [self->_auth.authURLPresenter
  614. presentURL:reCAPTCHAURL
  615. UIDelegate:UIDelegate
  616. callbackMatcher:callbackMatcher
  617. completion:^(NSURL *_Nullable callbackURL, NSError *_Nullable error) {
  618. if (error) {
  619. completion(nil, nil, error);
  620. return;
  621. }
  622. NSError *reCAPTCHAError;
  623. NSString *reCAPTCHAToken =
  624. [self reCAPTCHATokenForURL:callbackURL error:&reCAPTCHAError];
  625. if (!reCAPTCHAToken) {
  626. completion(nil, nil, reCAPTCHAError);
  627. return;
  628. } else {
  629. completion(nil, reCAPTCHAToken, nil);
  630. return;
  631. }
  632. }];
  633. }];
  634. }
  635. /** @fn reCAPTCHAURLWithEventID:completion:
  636. @brief Constructs a URL used for opening a reCAPTCHA app verification flow using a given event
  637. ID.
  638. @param eventID The event ID used for this purpose.
  639. @param completion The callback invoked after the URL has been constructed or an error
  640. has been encountered.
  641. */
  642. - (void)reCAPTCHAURLWithEventID:(NSString *)eventID completion:(FIRReCAPTCHAURLCallBack)completion {
  643. [FIRAuthWebUtils
  644. fetchAuthDomainWithRequestConfiguration:_auth.requestConfiguration
  645. completion:^(NSString *_Nullable authDomain,
  646. NSError *_Nullable error) {
  647. if (error) {
  648. if (completion) {
  649. completion(nil, error);
  650. return;
  651. }
  652. }
  653. NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
  654. NSString *clientID = self->_auth.app.options.clientID;
  655. NSString *appID = self->_auth.app.options.googleAppID;
  656. NSString *apiKey = self->_auth.requestConfiguration.APIKey;
  657. NSMutableArray<NSURLQueryItem *> *queryItems = [@[
  658. [NSURLQueryItem queryItemWithName:@"apiKey" value:apiKey],
  659. [NSURLQueryItem queryItemWithName:@"authType"
  660. value:kAuthTypeVerifyApp],
  661. [NSURLQueryItem queryItemWithName:@"ibi"
  662. value:bundleID ?: @""],
  663. [NSURLQueryItem
  664. queryItemWithName:@"v"
  665. value:[FIRAuthBackend authUserAgent]],
  666. [NSURLQueryItem queryItemWithName:@"eventId" value:eventID]
  667. ] mutableCopy];
  668. if (clientID) {
  669. [queryItems
  670. addObject:[NSURLQueryItem queryItemWithName:@"clientId"
  671. value:clientID]];
  672. } else {
  673. [queryItems
  674. addObject:[NSURLQueryItem queryItemWithName:@"appId"
  675. value:appID]];
  676. }
  677. if (self->_auth.requestConfiguration.languageCode) {
  678. [queryItems
  679. addObject:[NSURLQueryItem
  680. queryItemWithName:@"hl"
  681. value:self->_auth
  682. .requestConfiguration
  683. .languageCode]];
  684. }
  685. NSURLComponents *components = [[NSURLComponents alloc]
  686. initWithString:
  687. [NSString stringWithFormat:kReCAPTCHAURLStringFormat,
  688. authDomain]];
  689. [components setQueryItems:queryItems];
  690. if (completion) {
  691. completion([components URL], nil);
  692. }
  693. }];
  694. }
  695. @end
  696. NS_ASSUME_NONNULL_END
  697. #endif