FIRPhoneAuthProvider.m 40 KB

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