FIRPhoneAuthProvider.m 42 KB

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