FIRPhoneAuthProvider.m 38 KB

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