aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase
diff options
context:
space:
mode:
authorGravatar Zsika Phillip <protocol86@users.noreply.github.com>2017-09-04 17:37:46 -0700
committerGravatar GitHub <noreply@github.com>2017-09-04 17:37:46 -0700
commit85e81371801f9cd3e94a1505ce3b2f4f66086b08 (patch)
tree220fdf17ff9aed20e865bf019dd7dad805458fab /Firebase
parent540e21c0cc3206a8e911554227cc1f081ef40dda (diff)
Adds app verification alternative (#228)
* Adds app verification alternative
Diffstat (limited to 'Firebase')
-rw-r--r--Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m237
-rw-r--r--Firebase/Auth/Source/FIRAuth.m14
-rw-r--r--Firebase/Auth/Source/FIRAuthURLPresenter.m2
-rw-r--r--Firebase/Auth/Source/FIRAuth_Internal.h8
-rw-r--r--Firebase/Auth/Source/Public/FIRAuth.h12
-rw-r--r--Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h24
-rw-r--r--Firebase/Auth/Source/RPCs/FIRAuthBackend.h6
-rw-r--r--Firebase/Auth/Source/RPCs/FIRAuthBackend.m8
8 files changed, 292 insertions, 19 deletions
diff --git a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m b/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m
index 4a0e7e7..1fe4d15 100644
--- a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m
+++ b/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m
@@ -19,22 +19,37 @@
#import "FIRLogger.h"
#import "FIRPhoneAuthCredential_Internal.h"
#import "NSString+FIRAuth.h"
+#import "FIRApp.h"
#import "FIRAuthAPNSToken.h"
#import "FIRAuthAPNSTokenManager.h"
#import "FIRAuthAppCredential.h"
#import "FIRAuthAppCredentialManager.h"
#import "FIRAuthGlobalWorkQueue.h"
#import "FIRAuth_Internal.h"
+#import "FIRAuthURLPresenter.h"
#import "FIRAuthNotificationManager.h"
#import "FIRAuthErrorUtils.h"
#import "FIRAuthBackend.h"
+#import "FirebaseAuthVersion.h"
+#import "FIROptions.h"
+#import "FIRGetProjectConfigRequest.h"
+#import "FIRGetProjectConfigResponse.h"
#import "FIRSendVerificationCodeRequest.h"
#import "FIRSendVerificationCodeResponse.h"
#import "FIRVerifyClientRequest.h"
#import "FIRVerifyClientResponse.h"
+#import <GoogleToolboxForMac/GTMNSDictionary+URLArguments.h>
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 occured 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
@@ -44,6 +59,28 @@ NS_ASSUME_NONNULL_BEGIN
typedef void (^FIRVerifyClientCallback)(FIRAuthAppCredential *_Nullable appCredential,
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 occured while fetching the auth domain, if any.
+ */
+typedef void (^FIRFetchAuthDomainCallback)(NSString *_Nullable authDomain,
+ NSError *_Nullable error);
+/** @var kAuthDomainSuffix
+ @brief The suffix of the auth domain pertiaining to a given Firebase project.
+ */
+static NSString *const kAuthDomainSuffix = @"firebaseapp.com";
+
+/** @var kauthTypeVerifyApp
+ @brief The auth type to be specified in the app verification request.
+ */
+static NSString *const kAuthTypeVerifyApp = @"verifyApp";
+
+/** @var kReCAPTCHAURLStringFormat
+ @brief The format of the URL used to open the reCAPTCHA page during app verification.
+ */
+NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@";
+
@implementation FIRPhoneAuthProvider {
/** @var _auth
@@ -68,6 +105,21 @@ typedef void (^FIRVerifyClientCallback)(FIRAuthAppCredential *_Nullable appCrede
- (void)verifyPhoneNumber:(NSString *)phoneNumber
completion:(nullable FIRVerificationResultCallback)completion {
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
+ [self internalVerifyPhoneNumber:phoneNumber completion:^(NSString *_Nullable verificationID,
+ NSError *_Nullable error) {
+ if (completion) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion(verificationID, error);
+ });
+ }
+ }];
+ });
+}
+
+- (void)verifyPhoneNumber:(NSString *)phoneNumber
+ UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
+ completion:(nullable FIRVerificationResultCallback)completion {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^{
FIRVerificationResultCallback callBackOnMainThread = ^(NSString *_Nullable verificationID,
NSError *_Nullable error) {
if (completion) {
@@ -76,21 +128,59 @@ typedef void (^FIRVerifyClientCallback)(FIRAuthAppCredential *_Nullable appCrede
});
}
};
-
- if (!phoneNumber.length) {
- callBackOnMainThread(nil,
- [FIRAuthErrorUtils missingPhoneNumberErrorWithMessage:nil]);
- return;
- }
- [_auth.notificationManager checkNotificationForwardingWithCallback:
- ^(BOOL isNotificationBeingForwarded) {
- if (!isNotificationBeingForwarded) {
- callBackOnMainThread(nil, [FIRAuthErrorUtils notificationNotForwardedError]);
+ [self internalVerifyPhoneNumber:phoneNumber completion:^(NSString *_Nullable verificationID,
+ NSError *_Nullable error) {
+ if (!error) {
+ callBackOnMainThread(verificationID, nil);
return;
}
- [self verifyClientAndSendVerificationCodeToPhoneNumber:phoneNumber
- retryOnInvalidAppCredential:YES
- callback:callBackOnMainThread];
+ NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
+ BOOL isInvalidAppCredential = error.code == FIRAuthErrorCodeInternalError &&
+ underlyingError.code == FIRAuthErrorCodeInvalidAppCredential;
+ if (error.code != FIRAuthErrorCodeMissingAppToken && !isInvalidAppCredential) {
+ completion(nil, error);
+ return;
+ }
+ [self reCAPTCHAURLWithCompletion:^(NSURL *_Nullable reCAPTCHAURL,
+ NSError *_Nullable error) {
+ if (error) {
+ callBackOnMainThread(nil, error);
+ return;
+ }
+ [_auth.authURLPresenter presentURL:reCAPTCHAURL
+ UIDelegate:UIDelegate
+ callbackMatcher:^BOOL(NSURL * _Nullable callbackURL) {
+ return [self isVerifyAppURL:callbackURL];
+ }
+ completion:^(NSURL *_Nullable callbackURL,
+ NSError *_Nullable error) {
+ if (error) {
+ completion(nil, error);
+ return;
+ }
+ NSDictionary<NSString *, NSString *> *URLQueryItems =
+ [NSDictionary gtm_dictionaryWithHttpArgumentsString:callbackURL.query];;
+ URLQueryItems =
+ [NSDictionary gtm_dictionaryWithHttpArgumentsString:URLQueryItems[@"deep_link_id"]];
+ NSString *reCAPTCHA = URLQueryItems[@"recaptchaToken"];
+ FIRSendVerificationCodeRequest *request =
+ [[FIRSendVerificationCodeRequest alloc] initWithPhoneNumber:phoneNumber
+ appCredential:nil
+ reCAPTCHAToken:reCAPTCHA
+ requestConfiguration:_auth.requestConfiguration];
+ [FIRAuthBackend sendVerificationCode:request
+ callback:^(FIRSendVerificationCodeResponse
+ *_Nullable response, NSError *_Nullable error) {
+ if (error) {
+ callBackOnMainThread(nil, error);
+ return;
+ }
+ // Associate the phone number with the verification ID.
+ response.verificationID.fir_authPhoneNumber = phoneNumber;
+ callBackOnMainThread(response.verificationID, nil);
+ }];
+ }];
+ }];
}];
});
}
@@ -111,6 +201,71 @@ typedef void (^FIRVerifyClientCallback)(FIRAuthAppCredential *_Nullable appCrede
}
#pragma mark - Internal Methods
+/** @fn isVerifyAppURL:
+ @brief Parses a URL into all available query items.
+ @param URL The url to be checked against the authType string.
+ @return Whether or not the URL matches authType.
+ */
+- (BOOL)isVerifyAppURL:(nullable NSURL *)URL {
+ if (!URL) {
+ return NO;
+ }
+ NSURLComponents *actualURLComponents =
+ [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO];
+ actualURLComponents.query = nil;
+ actualURLComponents.fragment = nil;
+
+ NSURLComponents *expectedURLComponents = [NSURLComponents new];
+ NSArray *strings = [_auth.app.options.clientID componentsSeparatedByString:@"."];
+ expectedURLComponents.scheme =
+ [[strings reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
+ expectedURLComponents.host = @"firebaseauth";
+ expectedURLComponents.path = @"/link";
+
+ if (!([[expectedURLComponents URL] isEqual:[actualURLComponents URL]])) {
+ return NO;
+ }
+ NSDictionary<NSString *, NSString *> *URLQueryItems =
+ [NSDictionary gtm_dictionaryWithHttpArgumentsString:URL.query];
+ NSURL *deeplinkURL = [NSURL URLWithString:URLQueryItems[@"deep_link_id"]];
+ NSDictionary<NSString *, NSString *> *deeplinkQueryItems =
+ [NSDictionary gtm_dictionaryWithHttpArgumentsString:deeplinkURL.query];
+ if ([deeplinkQueryItems[@"authType"] isEqualToString:kAuthTypeVerifyApp]) {
+ return YES;
+ }
+ return NO;
+}
+
+/** @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
+ 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
+ callback:callback];
+ }];
+}
/** @fn verifyClientAndSendVerificationCodeToPhoneNumber:retryOnInvalidAppCredential:callback:
@brief Starts the flow to verify the client via silent push notification.
@@ -201,6 +356,62 @@ typedef void (^FIRVerifyClientCallback)(FIRAuthAppCredential *_Nullable appCrede
}];
}
+- (void)reCAPTCHAURLWithCompletion:(FIRReCAPTCHAURLCallBack)completion {
+ [self fetchAuthDomainWithCompletion:^(NSString *_Nullable authDomain,
+ NSError *_Nullable error) {
+ if (error) {
+ completion(nil, error);
+ return;
+ }
+ NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
+ NSString *clienID = _auth.app.options.clientID;
+ NSString *apiKey = _auth.app.options.APIKey;
+ NSMutableDictionary *urlArguments = [[NSMutableDictionary alloc] initWithDictionary: @{
+ @"apiKey" : apiKey,
+ @"authType" : kAuthTypeVerifyApp,
+ @"ibi" : bundleID,
+ @"clientId" : clienID,
+ @"v" : [FIRAuthBackend authUserAgent]
+ }];
+ if (_auth.requestConfiguration.languageCode) {
+ urlArguments[@"hl"] = _auth.requestConfiguration.languageCode;
+ }
+ NSString *argumentsString = [urlArguments gtm_httpArgumentsString];
+ NSString *URLString =
+ [NSString stringWithFormat:kReCAPTCHAURLStringFormat, authDomain, argumentsString];
+ completion([NSURL URLWithString:URLString], nil);
+ }];
+}
+
+- (void)fetchAuthDomainWithCompletion:(FIRFetchAuthDomainCallback)completion {
+ FIRGetProjectConfigRequest *request =
+ [[FIRGetProjectConfigRequest alloc] initWithRequestConfiguration:_auth.requestConfiguration];
+
+ [FIRAuthBackend getProjectConfig:request
+ callback:^(FIRGetProjectConfigResponse *_Nullable response,
+ NSError *_Nullable error) {
+ if (error) {
+ completion(nil, error);
+ return;
+ }
+ NSString *authDomain;
+ for (NSString *domain in response.authorizedDomains) {
+ NSInteger index = domain.length - kAuthDomainSuffix.length;
+ if (index >= 2) {
+ if ([domain hasSuffix:kAuthDomainSuffix] && domain.length >= kAuthDomainSuffix.length + 2) {
+ authDomain = domain;
+ break;
+ }
+ }
+ }
+ if (!authDomain.length) {
+ completion(nil, [FIRAuthErrorUtils unexpectedErrorResponseWithDeserializedResponse:response]);
+ return;
+ }
+ completion(authDomain, nil);
+ }];
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/FIRAuth.m
index d2915a3..dd0b0bf 100644
--- a/Firebase/Auth/Source/FIRAuth.m
+++ b/Firebase/Auth/Source/FIRAuth.m
@@ -64,6 +64,7 @@
#import "FIRAuthAppDelegateProxy.h"
#import "AuthProviders/Phone/FIRPhoneAuthCredential_Internal.h"
#import "FIRAuthNotificationManager.h"
+#import "FIRAuthURLPresenter.h"
#endif
#pragma mark - Constants
@@ -387,6 +388,9 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
});
return uid;
};
+ #if TARGET_OS_IOS
+ _authURLPresenter = [[FIRAuthURLPresenter alloc] init];
+ #endif
}
return self;
}
@@ -984,11 +988,15 @@ static NSMutableDictionary *gKeychainServiceNameForAppName;
});
return result;
}
-#endif
-- (BOOL)canHandleURL:(NSURL *)url {
- return NO;
+- (BOOL)canHandleURL:(NSURL *)URL {
+ __block BOOL result = NO;
+ dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
+ result = [_authURLPresenter canHandleURL:URL];
+ });
+ return result;
}
+#endif
#pragma mark - Internal Methods
diff --git a/Firebase/Auth/Source/FIRAuthURLPresenter.m b/Firebase/Auth/Source/FIRAuthURLPresenter.m
index 90ceed6..fc84383 100644
--- a/Firebase/Auth/Source/FIRAuthURLPresenter.m
+++ b/Firebase/Auth/Source/FIRAuthURLPresenter.m
@@ -68,7 +68,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (BOOL)canHandleURL:(NSURL *)URL {
- if (_callbackMatcher(URL)) {
+ if (_callbackMatcher && _callbackMatcher(URL)) {
_callbackMatcher = nil;
[self finishPresentationWithURL:URL error:nil];
return YES;
diff --git a/Firebase/Auth/Source/FIRAuth_Internal.h b/Firebase/Auth/Source/FIRAuth_Internal.h
index d568d45..a89ebf9 100644
--- a/Firebase/Auth/Source/FIRAuth_Internal.h
+++ b/Firebase/Auth/Source/FIRAuth_Internal.h
@@ -24,12 +24,20 @@
@class FIRAuthAPNSTokenManager;
@class FIRAuthAppCredentialManager;
@class FIRAuthNotificationManager;
+@class FIRAuthURLPresenter;
#endif
NS_ASSUME_NONNULL_BEGIN
@interface FIRAuth ()
+#if TARGET_OS_IOS
+/** @property authURLPresenter
+ @brief An object that takes care of presenting URLs via the auth instance.
+ */
+@property(nonatomic, strong, readonly) FIRAuthURLPresenter *authURLPresenter;
+#endif
+
/** @property requestConfiguration
@brief The configuration object comprising of paramters needed to make a request to Firebase
Auth's backend.
diff --git a/Firebase/Auth/Source/Public/FIRAuth.h b/Firebase/Auth/Source/Public/FIRAuth.h
index ad6b635..eeb7ff9 100644
--- a/Firebase/Auth/Source/Public/FIRAuth.h
+++ b/Firebase/Auth/Source/Public/FIRAuth.h
@@ -280,6 +280,18 @@ FIR_SWIFT_NAME(Auth)
*/
- (instancetype)init NS_UNAVAILABLE;
+/** @fn canHandleURL:
+ @brief Whether the specific URL is handled by @c FIRAuth .
+ @param URL The URL received by the application delegate from any of the openURL method.
+ @return Whether or the URL is handled. YES means the URL is for Firebase Auth
+ so the caller should ignore the URL from further processing, and NO means the
+ the URL is for the app (or another libaray) so the caller should continue handling
+ this URL as usual.
+ @remarks If swizzling is disabled, URLs received by the application delegate must be forwarded
+ to this method for phone number auth to work.
+ */
+- (BOOL)canHandleURL:(nonnull NSURL *)URL;
+
/** @fn fetchProvidersForEmail:completion:
@brief Fetches the list of IdPs that can be used for signing in with the provided email address.
Useful for an "identifier-first" sign-in flow.
diff --git a/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h b/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h
index 138028a..5acc500 100644
--- a/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h
+++ b/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h
@@ -20,6 +20,7 @@
@class FIRAuth;
@class FIRPhoneAuthCredential;
+@protocol FIRAuthUIDelegate;
NS_ASSUME_NONNULL_BEGIN
@@ -85,6 +86,29 @@ FIR_SWIFT_NAME(PhoneAuthProvider)
- (void)verifyPhoneNumber:(NSString *)phoneNumber
completion:(nullable FIRVerificationResultCallback)completion;
+/** @fn verifyPhoneNumber:UIDelegate: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 UIDelegate A view controller object used to present the SFSafariViewController or
+ WKWebview.
+ @param completion The callback to be invoked when the verification flow is finished.
+ @remarks Possible error codes:
+ <ul>
+ <li>@c FIRAuthErrorCodeCaptchaCheckFailed - Indicates that the reCAPTCHA token obtained by
+ the Firebase Auth is invalid or has expired.</li>
+ <li>@c FIRAuthErrorCodeQuotaExceeded - Indicates that the phone verification quota for this
+ project has been exceeded.</li>
+ <li>@c FIRAuthErrorCodeInvalidPhoneNumber - Indicates that the phone number provided is
+ invalid.</li>
+ <li>@c FIRAuthErrorCodeMissingPhoneNumber - Indicates that a phone number was not provided.
+ </li>
+ </ul>
+ */
+- (void)verifyPhoneNumber:(NSString *)phoneNumber
+ UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
+ completion:(nullable FIRVerificationResultCallback)completion;
+
/** @fn credentialWithVerificationID:verificationCode:
@brief Creates an @c FIRAuthCredential for the phone number provider identified by the
verification ID and verification code.
diff --git a/Firebase/Auth/Source/RPCs/FIRAuthBackend.h b/Firebase/Auth/Source/RPCs/FIRAuthBackend.h
index 65f93ce..a82c3a7 100644
--- a/Firebase/Auth/Source/RPCs/FIRAuthBackend.h
+++ b/Firebase/Auth/Source/RPCs/FIRAuthBackend.h
@@ -210,6 +210,12 @@ typedef void (^FIRVerifyClientResponseCallback)
*/
@interface FIRAuthBackend : NSObject
+/** @fn authUserAgent
+ @brief Retrieves the Firebase Auth user agent.
+ @return The Firebase Auth user agent.
+ */
++ (NSString *)authUserAgent;
+
/** @fn setBackendImplementation:
@brief Changes the default backend implementation to something else.
@param backendImplementation The backend implementation to use.
diff --git a/Firebase/Auth/Source/RPCs/FIRAuthBackend.m b/Firebase/Auth/Source/RPCs/FIRAuthBackend.m
index afe05bd..2a70a80 100644
--- a/Firebase/Auth/Source/RPCs/FIRAuthBackend.m
+++ b/Firebase/Auth/Source/RPCs/FIRAuthBackend.m
@@ -465,6 +465,11 @@ static id<FIRAuthBackendImplementation> gBackendImplementation;
[[self implementation] resetPassword:request callback:callback];
}
++ (NSString *)authUserAgent {
+ return [NSString stringWithFormat:@"FirebaseAuth.iOS/%s %@",
+ FirebaseAuthVersionString, GTMFetcherStandardUserAgentString(nil)];
+}
+
@end
@interface FIRAuthBackendRPCIssuerImplementation : NSObject <FIRAuthBackendRPCIssuer>
@@ -480,8 +485,7 @@ static id<FIRAuthBackendImplementation> gBackendImplementation;
self = [super init];
if (self) {
_fetcherService = [[GTMSessionFetcherService alloc] init];
- _fetcherService.userAgent = [NSString stringWithFormat:@"FirebaseAuth.iOS/%s %@",
- FirebaseAuthVersionString, GTMFetcherStandardUserAgentString(nil)];
+ _fetcherService.userAgent = [FIRAuthBackend authUserAgent];
_fetcherService.callbackQueue = FIRAuthGlobalWorkQueue();
}
return self;