FIRPhoneAuthProvider.m 40 KB

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