FIRPhoneAuthProvider.m 28 KB

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