/* * 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 #if TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION) #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)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)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)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 *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)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)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)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)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.phoneSessionInfo.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)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)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 appCheck = self->_auth.requestConfiguration.appCheck; NSMutableArray *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]]; } if (self->_auth.requestConfiguration.languageCode) { [queryItems addObject:[NSURLQueryItem queryItemWithName:@"hl" value:self->_auth .requestConfiguration .languageCode]]; } NSURLComponents *components = [[NSURLComponents alloc] initWithString: [NSString stringWithFormat:kReCAPTCHAURLStringFormat, authDomain]]; [components setQueryItems:queryItems]; if (appCheck) { [appCheck getTokenForcingRefresh:false completion:^( id _Nonnull tokenResult) { if (tokenResult.error) { FIRLogWarning( kFIRLoggerAuth, @"I-AUT000018", @"Error getting App Check token; " @"using placeholder token " @"instead. Error: %@", tokenResult.error); } NSString *appCheckTokenFragment = [@"fac=" stringByAppendingString:tokenResult .token]; [components setFragment:appCheckTokenFragment]; if (completion) { completion([components URL], nil); } }]; } else { if (completion) { completion([components URL], nil); } } }]; } @end NS_ASSUME_NONNULL_END #endif // TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION)