FIRPhoneAuthProvider.m 39 KB

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