Files
firebase-ios-sdk/FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthProvider.m
T

794 lines
42 KiB
Objective-C

/*
* 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 && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION)
#import <FirebaseAppCheckInterop/FirebaseAppCheckInterop.h>
#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 "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.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<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]];
}
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<FIRAppCheckTokenResultInterop> _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)