FIRPhoneAuthProvider.m 38 KB

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