| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791 |
- /*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import <TargetConditionals.h>
- #if TARGET_OS_IOS
- #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthSettings.h"
- #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRMultiFactorResolver.h"
- #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRPhoneAuthProvider.h"
- #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
- #import "FirebaseAppCheck/Interop/FIRAppCheckTokenResultInterop.h"
- #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h"
- #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h"
- #import "FirebaseAuth/Sources/Backend/FIRAuthBackend+MultiFactor.h"
- #import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h"
- #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigRequest.h"
- #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigResponse.h"
- #import "FirebaseAuth/Sources/Backend/RPC/FIRSendVerificationCodeRequest.h"
- #import "FirebaseAuth/Sources/Backend/RPC/FIRSendVerificationCodeResponse.h"
- #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyClientRequest.h"
- #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyClientResponse.h"
- #import "FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRStartMFAEnrollmentRequest.h"
- #import "FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRStartMFAEnrollmentResponse.h"
- #import "FirebaseAuth/Sources/Backend/RPC/Proto/Phone/FIRAuthProtoStartMFAPhoneRequestInfo.h"
- #import "FirebaseAuth/Sources/MultiFactor/FIRMultiFactorSession+Internal.h"
- #import "FirebaseAuth/Sources/SystemService/FIRAuthAPNSToken.h"
- #import "FirebaseAuth/Sources/SystemService/FIRAuthAPNSTokenManager.h"
- #import "FirebaseAuth/Sources/SystemService/FIRAuthAppCredential.h"
- #import "FirebaseAuth/Sources/SystemService/FIRAuthAppCredentialManager.h"
- #import "FirebaseAuth/Sources/SystemService/FIRAuthNotificationManager.h"
- #import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
- #import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
- #import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
- #if TARGET_OS_IOS
- #import "FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h"
- #import "FirebaseAuth/Sources/MultiFactor/Phone/FIRPhoneMultiFactorInfo+Internal.h"
- #endif
- NS_ASSUME_NONNULL_BEGIN
- /** @typedef FIRReCAPTCHAURLCallBack
- @brief The callback invoked at the end of the flow to fetch a reCAPTCHA URL.
- @param reCAPTCHAURL The reCAPTCHA URL.
- @param error The error that occurred while fetching the reCAPTCHAURL, if any.
- */
- typedef void (^FIRReCAPTCHAURLCallBack)(NSURL *_Nullable reCAPTCHAURL, NSError *_Nullable error);
- /** @typedef FIRVerifyClientCallback
- @brief The callback invoked at the end of a client verification flow.
- @param appCredential credential that proves the identity of the app during a phone
- authentication flow.
- @param error The error that occurred while verifying the app, if any.
- */
- typedef void (^FIRVerifyClientCallback)(FIRAuthAppCredential *_Nullable appCredential,
- NSString *_Nullable reCAPTCHAToken,
- NSError *_Nullable error);
- /** @typedef FIRFetchAuthDomainCallback
- @brief The callback invoked at the end of the flow to fetch the Auth domain.
- @param authDomain The Auth domain.
- @param error The error that occurred while fetching the auth domain, if any.
- */
- typedef void (^FIRFetchAuthDomainCallback)(NSString *_Nullable authDomain,
- NSError *_Nullable error);
- /** @var kauthTypeVerifyApp
- @brief The auth type to be specified in the app verification request.
- */
- static NSString *const kAuthTypeVerifyApp = @"verifyApp";
- /** @var kCustomUrlSchemePrefix
- @brief The prefix to append to the Firebase app ID custom callback scheme..
- */
- static NSString *const kCustomUrlSchemePrefix = @"app-";
- /** @var kReCAPTCHAURLStringFormat
- @brief The format of the URL used to open the reCAPTCHA page during app verification.
- */
- NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?";
- extern NSString *const FIRPhoneMultiFactorID;
- @implementation FIRPhoneAuthProvider {
- /** @var _auth
- @brief The auth instance used for verifying the phone number.
- */
- FIRAuth *_auth;
- /** @var _callbackScheme
- @brief The callback URL scheme used for reCAPTCHA fallback.
- */
- NSString *_callbackScheme;
- /** @var _usingClientIDScheme
- @brief True if the reverse client ID is registered as a custom URL scheme, and false
- otherwise.
- */
- BOOL _usingClientIDScheme;
- }
- /** @fn initWithAuth:
- @brief returns an instance of @c FIRPhoneAuthProvider associated with the provided auth
- instance.
- @return An Instance of @c FIRPhoneAuthProvider.
- */
- - (nullable instancetype)initWithAuth:(FIRAuth *)auth {
- self = [super init];
- if (self) {
- _auth = auth;
- if (_auth.app.options.clientID) {
- NSString *reverseClientIDScheme =
- [[[_auth.app.options.clientID componentsSeparatedByString:@"."]
- reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
- if ([FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:reverseClientIDScheme]) {
- _callbackScheme = reverseClientIDScheme;
- _usingClientIDScheme = YES;
- }
- }
- if (!_usingClientIDScheme) {
- _callbackScheme = [kCustomUrlSchemePrefix
- stringByAppendingString:[_auth.app.options.googleAppID
- stringByReplacingOccurrencesOfString:@":"
- withString:@"-"]];
- }
- }
- return self;
- }
- - (void)verifyPhoneNumber:(NSString *)phoneNumber
- UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
- completion:(nullable FIRVerificationResultCallback)completion {
- if (![FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:_callbackScheme]) {
- [NSException raise:NSInternalInconsistencyException
- format:@"Please register custom URL scheme '%@' in the app's Info.plist file.",
- _callbackScheme];
- }
- dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- FIRVerificationResultCallback callBackOnMainThread =
- ^(NSString *_Nullable verificationID, NSError *_Nullable error) {
- if (completion) {
- dispatch_async(dispatch_get_main_queue(), ^{
- completion(verificationID, error);
- });
- }
- };
- [self
- internalVerifyPhoneNumber:phoneNumber
- UIDelegate:UIDelegate
- completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
- if (!error) {
- callBackOnMainThread(verificationID, nil);
- return;
- } else {
- callBackOnMainThread(nil, error);
- return;
- }
- }];
- });
- }
- - (void)verifyPhoneNumberWithMultiFactorInfo:(FIRPhoneMultiFactorInfo *)phoneMultiFactorInfo
- UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
- multiFactorSession:(nullable FIRMultiFactorSession *)session
- completion:(nullable FIRVerificationResultCallback)completion {
- session.multiFactorInfo = phoneMultiFactorInfo;
- [self verifyPhoneNumber:phoneMultiFactorInfo.phoneNumber
- UIDelegate:UIDelegate
- multiFactorSession:session
- completion:completion];
- }
- - (void)verifyPhoneNumber:(NSString *)phoneNumber
- UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
- multiFactorSession:(nullable FIRMultiFactorSession *)session
- completion:(nullable FIRVerificationResultCallback)completion {
- if (!session) {
- [self verifyPhoneNumber:phoneNumber UIDelegate:UIDelegate completion:completion];
- return;
- }
- if (![FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:_callbackScheme]) {
- [NSException raise:NSInternalInconsistencyException
- format:@"Please register custom URL scheme '%@' in the app's Info.plist file.",
- _callbackScheme];
- }
- dispatch_async(FIRAuthGlobalWorkQueue(), ^{
- FIRVerificationResultCallback callBackOnMainThread =
- ^(NSString *_Nullable verificationID, NSError *_Nullable error) {
- if (completion) {
- dispatch_async(dispatch_get_main_queue(), ^{
- completion(verificationID, error);
- });
- }
- };
- [self
- internalVerifyPhoneNumber:phoneNumber
- UIDelegate:UIDelegate
- multiFactorSession:session
- completion:^(NSString *_Nullable verificationID, NSError *_Nullable error) {
- if (!error) {
- callBackOnMainThread(verificationID, nil);
- return;
- } else {
- callBackOnMainThread(nil, error);
- return;
- }
- }];
- });
- }
- - (FIRPhoneAuthCredential *)credentialWithVerificationID:(NSString *)verificationID
- verificationCode:(NSString *)verificationCode {
- return [[FIRPhoneAuthCredential alloc] initWithProviderID:FIRPhoneAuthProviderID
- verificationID:verificationID
- verificationCode:verificationCode];
- }
- + (instancetype)provider {
- return [[self alloc] initWithAuth:[FIRAuth auth]];
- }
- + (instancetype)providerWithAuth:(FIRAuth *)auth {
- return [[self alloc] initWithAuth:auth];
- }
- #pragma mark - Internal Methods
- /** @fn reCAPTCHATokenForURL:error:
- @brief Parses the reCAPTCHA URL and returns the reCAPTCHA token.
- @param URL The url to be parsed for a reCAPTCHA token.
- @param error The error that occurred if any.
- @return The reCAPTCHA token if successful.
- */
- - (nullable NSString *)reCAPTCHATokenForURL:(NSURL *)URL error:(NSError **_Nonnull)error {
- NSURLComponents *actualURLComponents = [NSURLComponents componentsWithURL:URL
- resolvingAgainstBaseURL:NO];
- NSArray<NSURLQueryItem *> *queryItems = [actualURLComponents queryItems];
- NSString *deepLinkURL = [FIRAuthWebUtils queryItemValue:@"deep_link_id" from:queryItems];
- NSData *errorData;
- if (deepLinkURL) {
- actualURLComponents = [NSURLComponents componentsWithString:deepLinkURL];
- queryItems = [actualURLComponents queryItems];
- NSString *recaptchaToken = [FIRAuthWebUtils queryItemValue:@"recaptchaToken" from:queryItems];
- if (recaptchaToken) {
- return recaptchaToken;
- }
- NSString *firebaseError = [FIRAuthWebUtils queryItemValue:@"firebaseError" from:queryItems];
- errorData = [firebaseError dataUsingEncoding:NSUTF8StringEncoding];
- } else {
- errorData = nil;
- }
- if (error != NULL && errorData != nil) {
- NSError *jsonError;
- NSDictionary *errorDict = [NSJSONSerialization JSONObjectWithData:errorData
- options:0
- error:&jsonError];
- if (jsonError) {
- *error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:jsonError];
- return nil;
- }
- *error = [FIRAuthErrorUtils URLResponseErrorWithCode:errorDict[@"code"]
- message:errorDict[@"message"]];
- if (!*error) {
- NSString *reason;
- if (errorDict[@"code"] && errorDict[@"message"]) {
- reason =
- [NSString stringWithFormat:@"[%@] - %@", errorDict[@"code"], errorDict[@"message"]];
- } else {
- reason = [NSString stringWithFormat:@"An unknown error occurred with the following "
- "response: %@",
- deepLinkURL];
- }
- *error = [FIRAuthErrorUtils appVerificationUserInteractionFailureWithReason:reason];
- }
- }
- return nil;
- }
- /** @fn internalVerifyPhoneNumber:completion:
- @brief Starts the phone number authentication flow by sending a verifcation code to the
- specified phone number.
- @param phoneNumber The phone number to be verified.
- @param completion The callback to be invoked when the verification flow is finished.
- */
- - (void)internalVerifyPhoneNumber:(NSString *)phoneNumber
- UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
- completion:(nullable FIRVerificationResultCallback)completion {
- if (!phoneNumber.length) {
- completion(nil, [FIRAuthErrorUtils missingPhoneNumberErrorWithMessage:nil]);
- return;
- }
- [_auth.notificationManager
- checkNotificationForwardingWithCallback:^(BOOL isNotificationBeingForwarded) {
- if (!isNotificationBeingForwarded) {
- completion(nil, [FIRAuthErrorUtils notificationNotForwardedError]);
- return;
- }
- FIRVerificationResultCallback callback =
- ^(NSString *_Nullable verificationID, NSError *_Nullable error) {
- if (completion) {
- completion(verificationID, error);
- }
- };
- [self verifyClientAndSendVerificationCodeToPhoneNumber:phoneNumber
- retryOnInvalidAppCredential:YES
- UIDelegate:UIDelegate
- callback:callback];
- }];
- }
- - (void)internalVerifyPhoneNumber:(NSString *)phoneNumber
- UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
- multiFactorSession:(nullable FIRMultiFactorSession *)session
- completion:(nullable FIRVerificationResultCallback)completion {
- if (!phoneNumber.length) {
- if (completion) {
- completion(nil, [FIRAuthErrorUtils missingPhoneNumberErrorWithMessage:nil]);
- }
- return;
- }
- [_auth.notificationManager
- checkNotificationForwardingWithCallback:^(BOOL isNotificationBeingForwarded) {
- if (!isNotificationBeingForwarded) {
- if (completion) {
- completion(nil, [FIRAuthErrorUtils notificationNotForwardedError]);
- }
- return;
- }
- FIRVerificationResultCallback callback =
- ^(NSString *_Nullable verificationID, NSError *_Nullable error) {
- if (completion) {
- completion(verificationID, error);
- }
- };
- [self verifyClientAndSendVerificationCodeToPhoneNumber:phoneNumber
- retryOnInvalidAppCredential:YES
- UIDelegate:UIDelegate
- multiFactorSession:session
- callback:callback];
- }];
- }
- /** @fn verifyClientAndSendVerificationCodeToPhoneNumber:retryOnInvalidAppCredential:callback:
- @brief Starts the flow to verify the client via silent push notification.
- @param retryOnInvalidAppCredential Whether of not the flow should be retried if an
- FIRAuthErrorCodeInvalidAppCredential error is returned from the backend.
- @param phoneNumber The phone number to be verified.
- @param callback The callback to be invoked on the global work queue when the flow is
- finished.
- */
- - (void)verifyClientAndSendVerificationCodeToPhoneNumber:(NSString *)phoneNumber
- retryOnInvalidAppCredential:(BOOL)retryOnInvalidAppCredential
- UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
- callback:(FIRVerificationResultCallback)callback {
- if (_auth.settings.isAppVerificationDisabledForTesting) {
- FIRSendVerificationCodeRequest *request =
- [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:phoneNumber
- appCredential:nil
- reCAPTCHAToken:nil
- requestConfiguration:_auth.requestConfiguration];
- [FIRAuthBackend sendVerificationCode:request
- callback:^(FIRSendVerificationCodeResponse *_Nullable response,
- NSError *_Nullable error) {
- callback(response.verificationID, error);
- }];
- return;
- }
- [self
- verifyClientWithUIDelegate:UIDelegate
- completion:^(FIRAuthAppCredential *_Nullable appCredential,
- NSString *_Nullable reCAPTCHAToken, NSError *_Nullable error) {
- if (error) {
- callback(nil, error);
- return;
- }
- FIRSendVerificationCodeRequest *_Nullable request;
- if (appCredential) {
- request = [[FIRSendVerificationCodeRequest alloc]
- initWithPhoneNumber:phoneNumber
- appCredential:appCredential
- reCAPTCHAToken:nil
- requestConfiguration:self->_auth.requestConfiguration];
- } else if (reCAPTCHAToken) {
- request = [[FIRSendVerificationCodeRequest alloc]
- initWithPhoneNumber:phoneNumber
- appCredential:nil
- reCAPTCHAToken:reCAPTCHAToken
- requestConfiguration:self->_auth.requestConfiguration];
- }
- if (request) {
- [FIRAuthBackend
- sendVerificationCode:request
- callback:^(
- FIRSendVerificationCodeResponse *_Nullable response,
- NSError *_Nullable error) {
- if (error) {
- if (error.code ==
- FIRAuthErrorCodeInvalidAppCredential) {
- if (retryOnInvalidAppCredential) {
- [self->_auth
- .appCredentialManager clearCredential];
- [self
- verifyClientAndSendVerificationCodeToPhoneNumber:
- phoneNumber
- retryOnInvalidAppCredential:
- NO
- UIDelegate:
- UIDelegate
- callback:
- callback];
- return;
- }
- callback(
- nil,
- [FIRAuthErrorUtils
- unexpectedResponseWithDeserializedResponse:
- nil
- underlyingError:
- error]);
- return;
- }
- callback(nil, error);
- return;
- }
- callback(response.verificationID, nil);
- }];
- }
- }];
- }
- - (void)verifyClientAndSendVerificationCodeToPhoneNumber:(NSString *)phoneNumber
- retryOnInvalidAppCredential:(BOOL)retryOnInvalidAppCredential
- UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
- multiFactorSession:(nullable FIRMultiFactorSession *)session
- callback:(FIRVerificationResultCallback)callback {
- if (_auth.settings.isAppVerificationDisabledForTesting) {
- FIRSendVerificationCodeRequest *request =
- [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:phoneNumber
- appCredential:nil
- reCAPTCHAToken:nil
- requestConfiguration:_auth.requestConfiguration];
- [FIRAuthBackend sendVerificationCode:request
- callback:^(FIRSendVerificationCodeResponse *_Nullable response,
- NSError *_Nullable error) {
- callback(response.verificationID, error);
- }];
- return;
- }
- [self
- verifyClientWithUIDelegate:UIDelegate
- completion:^(FIRAuthAppCredential *_Nullable appCredential,
- NSString *_Nullable reCAPTCHAToken, NSError *_Nullable error) {
- if (error) {
- if (callback) {
- callback(nil, error);
- }
- return;
- }
- NSString *IDToken = session.IDToken;
- FIRAuthProtoStartMFAPhoneRequestInfo *startMFARequestInfo =
- [[FIRAuthProtoStartMFAPhoneRequestInfo alloc]
- initWithPhoneNumber:phoneNumber
- appCredential:appCredential
- reCAPTCHAToken:reCAPTCHAToken];
- if (session.IDToken) {
- FIRStartMFAEnrollmentRequest *request =
- [[FIRStartMFAEnrollmentRequest alloc]
- initWithIDToken:IDToken
- enrollmentInfo:startMFARequestInfo
- requestConfiguration:self->_auth.requestConfiguration];
- [FIRAuthBackend
- startMultiFactorEnrollment:request
- callback:^(FIRStartMFAEnrollmentResponse
- *_Nullable response,
- NSError *_Nullable error) {
- if (error) {
- if (error.code ==
- FIRAuthErrorCodeInvalidAppCredential) {
- if (retryOnInvalidAppCredential) {
- [self->_auth.appCredentialManager
- clearCredential];
- [self
- verifyClientAndSendVerificationCodeToPhoneNumber:
- phoneNumber
- retryOnInvalidAppCredential:
- NO
- UIDelegate:
- UIDelegate
- multiFactorSession:
- session
- callback:
- callback];
- return;
- }
- if (callback) {
- callback(
- nil,
- [FIRAuthErrorUtils
- unexpectedResponseWithDeserializedResponse:
- nil
- underlyingError:
- error]);
- }
- return;
- } else {
- if (callback) {
- callback(nil, error);
- }
- }
- } else {
- if (callback) {
- callback(
- response.enrollmentResponse.sessionInfo,
- nil);
- }
- }
- }];
- } else {
- FIRStartMFASignInRequest *request = [[FIRStartMFASignInRequest alloc]
- initWithMFAPendingCredential:session.MFAPendingCredential
- MFAEnrollmentID:session.multiFactorInfo.UID
- signInInfo:startMFARequestInfo
- requestConfiguration:self->_auth.requestConfiguration];
- [FIRAuthBackend
- startMultiFactorSignIn:request
- callback:^(
- FIRStartMFASignInResponse *_Nullable response,
- NSError *_Nullable error) {
- if (error) {
- if (error.code ==
- FIRAuthErrorCodeInvalidAppCredential) {
- if (retryOnInvalidAppCredential) {
- [self->_auth
- .appCredentialManager clearCredential];
- [self
- verifyClientAndSendVerificationCodeToPhoneNumber:
- phoneNumber
- retryOnInvalidAppCredential:
- NO
- UIDelegate:
- UIDelegate
- multiFactorSession:
- session
- callback:
- callback];
- return;
- }
- if (callback) {
- callback(
- nil,
- [FIRAuthErrorUtils
- unexpectedResponseWithDeserializedResponse:
- nil
- underlyingError:
- error]);
- }
- return;
- } else {
- if (callback) {
- callback(nil, error);
- }
- }
- } else {
- if (callback) {
- callback(response.responseInfo.sessionInfo, nil);
- }
- }
- }];
- }
- }];
- }
- /** @fn verifyClientWithCompletion:completion:
- @brief Continues the flow to verify the client via silent push notification.
- @param completion The callback to be invoked when the client verification flow is finished.
- */
- - (void)verifyClientWithUIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
- completion:(FIRVerifyClientCallback)completion {
- // Remove the simulator check below after FCM supports APNs in simulators
- #if TARGET_OS_SIMULATOR
- if (@available(iOS 16, *)) {
- NSDictionary *environment = [[NSProcessInfo processInfo] environment];
- if ((environment[@"XCTestConfigurationFilePath"] == nil)) {
- [self reCAPTCHAFlowWithUIDelegate:UIDelegate completion:completion];
- return;
- }
- }
- #endif
- if (_auth.appCredentialManager.credential) {
- completion(_auth.appCredentialManager.credential, nil, nil);
- return;
- }
- [_auth.tokenManager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token,
- NSError *_Nullable error) {
- if (!token) {
- [self reCAPTCHAFlowWithUIDelegate:UIDelegate completion:completion];
- return;
- }
- FIRVerifyClientRequest *request =
- [[FIRVerifyClientRequest alloc] initWithAppToken:token.string
- isSandbox:token.type == FIRAuthAPNSTokenTypeSandbox
- requestConfiguration:self->_auth.requestConfiguration];
- [FIRAuthBackend
- verifyClient:request
- callback:^(FIRVerifyClientResponse *_Nullable response, NSError *_Nullable error) {
- if (error) {
- NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
- BOOL isInvalidAppCredential =
- error.code == FIRAuthErrorCodeInternalError &&
- underlyingError.code == FIRAuthErrorCodeInvalidAppCredential;
- if (error.code != FIRAuthErrorCodeMissingAppToken && !isInvalidAppCredential) {
- completion(nil, nil, error);
- return;
- } else {
- [self reCAPTCHAFlowWithUIDelegate:UIDelegate completion:completion];
- return;
- }
- }
- NSTimeInterval timeout = [response.suggestedTimeOutDate timeIntervalSinceNow];
- [self->_auth.appCredentialManager
- didStartVerificationWithReceipt:response.receipt
- timeout:timeout
- callback:^(FIRAuthAppCredential *credential) {
- if (!credential.secret) {
- FIRLogWarning(kFIRLoggerAuth, @"I-AUT000014",
- @"Failed to receive remote notification "
- @"to verify app identity within "
- @"%.0f second(s), falling back to "
- @"reCAPTCHA verification.",
- timeout);
- [self reCAPTCHAFlowWithUIDelegate:UIDelegate
- completion:completion];
- return;
- }
- completion(credential, nil, nil);
- }];
- }];
- }];
- }
- - (void)reCAPTCHAFlowWithUIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
- completion:(FIRVerifyClientCallback)completion {
- NSString *eventID = [FIRAuthWebUtils randomStringWithLength:10];
- [self
- reCAPTCHAURLWithEventID:eventID
- completion:^(NSURL *_Nullable reCAPTCHAURL, NSError *_Nullable error) {
- if (error) {
- completion(nil, nil, error);
- return;
- }
- FIRAuthURLCallbackMatcher callbackMatcher =
- ^BOOL(NSURL *_Nullable callbackURL) {
- return [FIRAuthWebUtils isExpectedCallbackURL:callbackURL
- eventID:eventID
- authType:kAuthTypeVerifyApp
- callbackScheme:self->_callbackScheme];
- };
- [self->_auth.authURLPresenter
- presentURL:reCAPTCHAURL
- UIDelegate:UIDelegate
- callbackMatcher:callbackMatcher
- completion:^(NSURL *_Nullable callbackURL, NSError *_Nullable error) {
- if (error) {
- completion(nil, nil, error);
- return;
- }
- NSError *reCAPTCHAError;
- NSString *reCAPTCHAToken =
- [self reCAPTCHATokenForURL:callbackURL error:&reCAPTCHAError];
- if (!reCAPTCHAToken) {
- completion(nil, nil, reCAPTCHAError);
- return;
- } else {
- completion(nil, reCAPTCHAToken, nil);
- return;
- }
- }];
- }];
- }
- /** @fn reCAPTCHAURLWithEventID:completion:
- @brief Constructs a URL used for opening a reCAPTCHA app verification flow using a given event
- ID.
- @param eventID The event ID used for this purpose.
- @param completion The callback invoked after the URL has been constructed or an error
- has been encountered.
- */
- - (void)reCAPTCHAURLWithEventID:(NSString *)eventID completion:(FIRReCAPTCHAURLCallBack)completion {
- [FIRAuthWebUtils
- fetchAuthDomainWithRequestConfiguration:_auth.requestConfiguration
- completion:^(NSString *_Nullable authDomain,
- NSError *_Nullable error) {
- if (error) {
- if (completion) {
- completion(nil, error);
- }
- return;
- }
- NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
- NSString *clientID = self->_auth.app.options.clientID;
- NSString *appID = self->_auth.app.options.googleAppID;
- NSString *apiKey = self->_auth.requestConfiguration.APIKey;
- id<FIRAppCheckInterop> appCheck =
- self->_auth.requestConfiguration.appCheck;
- NSMutableArray<NSURLQueryItem *> *queryItems = [@[
- [NSURLQueryItem queryItemWithName:@"apiKey" value:apiKey],
- [NSURLQueryItem queryItemWithName:@"authType"
- value:kAuthTypeVerifyApp],
- [NSURLQueryItem queryItemWithName:@"ibi"
- value:bundleID ?: @""],
- [NSURLQueryItem
- queryItemWithName:@"v"
- value:[FIRAuthBackend authUserAgent]],
- [NSURLQueryItem queryItemWithName:@"eventId" value:eventID]
- ] mutableCopy];
- if (self->_usingClientIDScheme) {
- [queryItems
- addObject:[NSURLQueryItem queryItemWithName:@"clientId"
- value:clientID]];
- } else {
- [queryItems
- addObject:[NSURLQueryItem queryItemWithName:@"appId"
- value:appID]];
- }
- NSURLComponents *components = [[NSURLComponents alloc]
- initWithString:
- [NSString stringWithFormat:kReCAPTCHAURLStringFormat,
- authDomain]];
- [components setQueryItems:queryItems];
- NSURL *url = [components URL];
- if (appCheck) {
- [appCheck
- getTokenForcingRefresh:false
- completion:^(
- id<FIRAppCheckTokenResultInterop> _Nonnull tokenResult) {
- if (tokenResult.error) {
- FIRLogWarning(
- kFIRLoggerAuth, @"I-AUT000018",
- @"Error getting App Check token; "
- @"using placeholder token "
- @"instead. Error: %@",
- tokenResult.error);
- }
- NSString *appCheckTokenFragments = [@"fac="
- stringByAppendingString:tokenResult
- .token];
- NSString *URLString = [url absoluteString];
- NSString *URLStringWithAppCheckToken =
- [URLString stringByAppendingString:
- appCheckTokenFragments];
- NSURL *URLWithAppCheckToken =
- [NSURL URLWithString:
- URLStringWithAppCheckToken];
- if (completion) {
- completion(URLWithAppCheckToken, nil);
- }
- }];
- } else {
- if (completion) {
- completion(url, nil);
- }
- }
- }];
- }
- @end
- NS_ASSUME_NONNULL_END
- #endif
|