You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1873 lines
74 KiB
1873 lines
74 KiB
/*
|
|
* 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 <Foundation/Foundation.h>
|
|
|
|
#import "FIRAuth_Internal.h"
|
|
|
|
#import <FirebaseCore/FIRAppAssociationRegistration.h>
|
|
#import <FirebaseCore/FIRAppInternal.h>
|
|
#import <FirebaseCore/FIRLogger.h>
|
|
#import <FirebaseCore/FIROptions.h>
|
|
|
|
#import "AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h"
|
|
#import "FIRAdditionalUserInfo_Internal.h"
|
|
#import "FIRAuthCredential_Internal.h"
|
|
#import "FIRAuthDataResult_Internal.h"
|
|
#import "FIRAuthDispatcher.h"
|
|
#import "FIRAuthErrorUtils.h"
|
|
#import "FIRAuthExceptionUtils.h"
|
|
#import "FIRAuthGlobalWorkQueue.h"
|
|
#import "FIRAuthKeychain.h"
|
|
#import "FIRAuthOperationType.h"
|
|
#import "FIRAuthSettings.h"
|
|
#import "FIRUser_Internal.h"
|
|
#import "FirebaseAuth.h"
|
|
#import "FIRAuthBackend.h"
|
|
#import "FIRAuthRequestConfiguration.h"
|
|
#import "FIRCreateAuthURIRequest.h"
|
|
#import "FIRCreateAuthURIResponse.h"
|
|
#import "FIREmailLinkSignInRequest.h"
|
|
#import "FIREmailLinkSignInResponse.h"
|
|
#import "FIRGetOOBConfirmationCodeRequest.h"
|
|
#import "FIRGetOOBConfirmationCodeResponse.h"
|
|
#import "FIRResetPasswordRequest.h"
|
|
#import "FIRResetPasswordResponse.h"
|
|
#import "FIRSendVerificationCodeRequest.h"
|
|
#import "FIRSendVerificationCodeResponse.h"
|
|
#import "FIRSetAccountInfoRequest.h"
|
|
#import "FIRSetAccountInfoResponse.h"
|
|
#import "FIRSignUpNewUserRequest.h"
|
|
#import "FIRSignUpNewUserResponse.h"
|
|
#import "FIRVerifyAssertionRequest.h"
|
|
#import "FIRVerifyAssertionResponse.h"
|
|
#import "FIRVerifyCustomTokenRequest.h"
|
|
#import "FIRVerifyCustomTokenResponse.h"
|
|
#import "FIRVerifyPasswordRequest.h"
|
|
#import "FIRVerifyPasswordResponse.h"
|
|
#import "FIRVerifyPhoneNumberRequest.h"
|
|
#import "FIRVerifyPhoneNumberResponse.h"
|
|
|
|
#if TARGET_OS_IOS
|
|
#import <UIKit/UIKit.h>
|
|
#import "FIRAuthAPNSToken.h"
|
|
#import "FIRAuthAPNSTokenManager.h"
|
|
#import "FIRAuthAppCredentialManager.h"
|
|
#import "FIRAuthAppDelegateProxy.h"
|
|
#import "AuthProviders/Phone/FIRPhoneAuthCredential_Internal.h"
|
|
#import "FIRAuthNotificationManager.h"
|
|
#import "FIRAuthURLPresenter.h"
|
|
#endif
|
|
|
|
#pragma mark - Constants
|
|
|
|
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
|
const NSNotificationName FIRAuthStateDidChangeNotification = @"FIRAuthStateDidChangeNotification";
|
|
#else
|
|
NSString *const FIRAuthStateDidChangeNotification = @"FIRAuthStateDidChangeNotification";
|
|
#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
|
|
|
/** @var kMaxWaitTimeForBackoff
|
|
@brief The maximum wait time before attempting to retry auto refreshing tokens after a failed
|
|
attempt.
|
|
@remarks This is the upper limit (in seconds) of the exponential backoff used for retrying
|
|
token refresh.
|
|
*/
|
|
static NSTimeInterval kMaxWaitTimeForBackoff = 16 * 60;
|
|
|
|
/** @var kTokenRefreshHeadStart
|
|
@brief The amount of time before the token expires that proactive refresh should be attempted.
|
|
*/
|
|
NSTimeInterval kTokenRefreshHeadStart = 5 * 60;
|
|
|
|
/** @var kUserKey
|
|
@brief Key of user stored in the keychain. Prefixed with a Firebase app name.
|
|
*/
|
|
static NSString *const kUserKey = @"%@_firebase_user";
|
|
|
|
/** @var kMissingEmailInvalidParameterExceptionReason
|
|
@brief The reason for @c invalidParameterException when the email used to initiate password
|
|
reset is nil.
|
|
*/
|
|
static NSString *const kMissingEmailInvalidParameterExceptionReason =
|
|
@"The email used to initiate password reset cannot be nil.";
|
|
|
|
/** @var kHandleCodeInAppFalseExceptionReason
|
|
@brief The reason for @c invalidParameterException when the handleCodeInApp parameter is false
|
|
on the ActionCodeSettings object used to send the link for Email-link Authentication.
|
|
*/
|
|
static NSString *const kHandleCodeInAppFalseExceptionReason =
|
|
@"You must set handleCodeInApp in your ActionCodeSettings to true for Email-link "
|
|
"Authentication.";
|
|
|
|
static NSString *const kInvalidEmailSignInLinkExceptionMessage =
|
|
@"The link provided is not valid for email/link sign-in. Please check the link by calling "
|
|
"isSignInWithEmailLink:link: on Auth before attempting to use it for email/link sign-in.";
|
|
|
|
/** @var kPasswordResetRequestType
|
|
@brief The action code type value for resetting password in the check action code response.
|
|
*/
|
|
static NSString *const kPasswordResetRequestType = @"PASSWORD_RESET";
|
|
|
|
/** @var kVerifyEmailRequestType
|
|
@brief The action code type value for verifying email in the check action code response.
|
|
*/
|
|
static NSString *const kVerifyEmailRequestType = @"VERIFY_EMAIL";
|
|
|
|
/** @var kRecoverEmailRequestType
|
|
@brief The action code type value for recovering email in the check action code response.
|
|
*/
|
|
static NSString *const kRecoverEmailRequestType = @"RECOVER_EMAIL";
|
|
|
|
/** @var kEmailLinkSignInRequestType
|
|
@brief The action code type value for an email sign-in link in the check action code response.
|
|
*/
|
|
static NSString *const kEmailLinkSignInRequestType = @"EMAIL_SIGNIN";
|
|
|
|
/** @var kMissingPasswordReason
|
|
@brief The reason why the @c FIRAuthErrorCodeWeakPassword error is thrown.
|
|
@remarks This error message will be localized in the future.
|
|
*/
|
|
static NSString *const kMissingPasswordReason = @"Missing Password";
|
|
|
|
/** @var gKeychainServiceNameForAppName
|
|
@brief A map from Firebase app name to keychain service names.
|
|
@remarks This map is needed for looking up the keychain service name after the FIRApp instance
|
|
is deleted, to remove the associated keychain item. Accessing should occur within a
|
|
@syncronized([FIRAuth class]) context.
|
|
*/
|
|
static NSMutableDictionary *gKeychainServiceNameForAppName;
|
|
|
|
#pragma mark - FIRActionCodeInfo
|
|
|
|
@implementation FIRActionCodeInfo {
|
|
/** @var _email
|
|
@brief The email address to which the code was sent. The new email address in the case of
|
|
FIRActionCodeOperationRecoverEmail.
|
|
*/
|
|
NSString *_email;
|
|
|
|
/** @var _fromEmail
|
|
@brief The current email address in the case of FIRActionCodeOperationRecoverEmail.
|
|
*/
|
|
NSString *_fromEmail;
|
|
}
|
|
|
|
- (NSString *)dataForKey:(FIRActionDataKey)key{
|
|
switch (key) {
|
|
case FIRActionCodeEmailKey:
|
|
return _email;
|
|
case FIRActionCodeFromEmailKey:
|
|
return _fromEmail;
|
|
}
|
|
}
|
|
|
|
- (instancetype)initWithOperation:(FIRActionCodeOperation)operation
|
|
email:(NSString *)email
|
|
newEmail:(nullable NSString *)newEmail {
|
|
self = [super init];
|
|
if (self) {
|
|
_operation = operation;
|
|
if (newEmail) {
|
|
_email = [newEmail copy];
|
|
_fromEmail = [email copy];
|
|
} else {
|
|
_email = [email copy];
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/** @fn actionCodeOperationForRequestType:
|
|
@brief Returns the corresponding operation type per provided request type string.
|
|
@param requestType Request type returned in in the server response.
|
|
@return The corresponding FIRActionCodeOperation for the supplied request type.
|
|
*/
|
|
+ (FIRActionCodeOperation)actionCodeOperationForRequestType:(NSString *)requestType {
|
|
if ([requestType isEqualToString:kPasswordResetRequestType]) {
|
|
return FIRActionCodeOperationPasswordReset;
|
|
}
|
|
if ([requestType isEqualToString:kVerifyEmailRequestType]) {
|
|
return FIRActionCodeOperationVerifyEmail;
|
|
}
|
|
if ([requestType isEqualToString:kRecoverEmailRequestType]) {
|
|
return FIRActionCodeOperationRecoverEmail;
|
|
}
|
|
if ([requestType isEqualToString:kEmailLinkSignInRequestType]) {
|
|
return FIRActionCodeOperationEmailLink;
|
|
}
|
|
return FIRActionCodeOperationUnknown;
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - FIRAuth
|
|
|
|
#if TARGET_OS_IOS
|
|
@interface FIRAuth () <FIRAuthAppDelegateHandler>
|
|
#else
|
|
@interface FIRAuth ()
|
|
#endif
|
|
|
|
/** @property firebaseAppId
|
|
@brief The Firebase app ID.
|
|
*/
|
|
@property(nonatomic, copy, readonly) NSString *firebaseAppId;
|
|
|
|
/** @property additionalFrameworkMarker
|
|
@brief Additional framework marker that will be added as part of the header of every request.
|
|
*/
|
|
@property(nonatomic, copy, nullable) NSString *additionalFrameworkMarker;
|
|
|
|
/** @fn initWithApp:
|
|
@brief Creates a @c FIRAuth instance associated with the provided @c FIRApp instance.
|
|
@param app The application to associate the auth instance with.
|
|
*/
|
|
- (instancetype)initWithApp:(FIRApp *)app;
|
|
|
|
@end
|
|
|
|
@implementation FIRAuth {
|
|
/** @var _currentUser
|
|
@brief The current user.
|
|
*/
|
|
FIRUser *_currentUser;
|
|
|
|
/** @var _firebaseAppName
|
|
@brief The Firebase app name.
|
|
*/
|
|
NSString *_firebaseAppName;
|
|
|
|
/** @var _listenerHandles
|
|
@brief Handles returned from @c NSNotificationCenter for blocks which are "auth state did
|
|
change" notification listeners.
|
|
@remarks Mutations should occur within a @syncronized(self) context.
|
|
*/
|
|
NSMutableArray<FIRAuthStateDidChangeListenerHandle> *_listenerHandles;
|
|
|
|
/** @var _keychain
|
|
@brief The keychain service.
|
|
*/
|
|
FIRAuthKeychain *_keychain;
|
|
|
|
/** @var _lastNotifiedUserToken
|
|
@brief The user access (ID) token used last time for posting auth state changed notification.
|
|
*/
|
|
NSString *_lastNotifiedUserToken;
|
|
|
|
/** @var _autoRefreshTokens
|
|
@brief This flag denotes whether or not tokens should be automatically refreshed.
|
|
@remarks Will only be set to @YES if the another Firebase service is included (additionally to
|
|
Firebase Auth).
|
|
*/
|
|
BOOL _autoRefreshTokens;
|
|
|
|
/** @var _autoRefreshScheduled
|
|
@brief Whether or not token auto-refresh is currently scheduled.
|
|
*/
|
|
BOOL _autoRefreshScheduled;
|
|
|
|
/** @var _isAppInBackground
|
|
@brief A flag that is set to YES if the app is put in the background and no when the app is
|
|
returned to the foreground.
|
|
*/
|
|
BOOL _isAppInBackground;
|
|
|
|
/** @var _applicationDidBecomeActiveObserver
|
|
@brief An opaque object to act as the observer for UIApplicationDidBecomeActiveNotification.
|
|
*/
|
|
id<NSObject> _applicationDidBecomeActiveObserver;
|
|
|
|
/** @var _applicationDidBecomeActiveObserver
|
|
@brief An opaque object to act as the observer for
|
|
UIApplicationDidEnterBackgroundNotification.
|
|
*/
|
|
id<NSObject> _applicationDidEnterBackgroundObserver;
|
|
}
|
|
|
|
+ (void)load {
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
gKeychainServiceNameForAppName = [[NSMutableDictionary alloc] init];
|
|
|
|
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
|
|
|
|
// Ensures the @c FIRAuth instance for a given app gets loaded as soon as the app is ready.
|
|
[defaultCenter addObserverForName:kFIRAppReadyToConfigureSDKNotification
|
|
object:[FIRApp class]
|
|
queue:nil
|
|
usingBlock:^(NSNotification *notification) {
|
|
[FIRAuth authWithApp:[FIRApp appNamed:notification.userInfo[kFIRAppNameKey]]];
|
|
}];
|
|
// Ensures the saved user is cleared when the app is deleted.
|
|
[defaultCenter addObserverForName:kFIRAppDeleteNotification
|
|
object:[FIRApp class]
|
|
queue:nil
|
|
usingBlock:^(NSNotification *notification) {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
// This doesn't stop any request already issued, see b/27704535 .
|
|
NSString *appName = notification.userInfo[kFIRAppNameKey];
|
|
NSString *keychainServiceName = [FIRAuth keychainServiceNameForAppName:appName];
|
|
if (keychainServiceName) {
|
|
[self deleteKeychainServiceNameForAppName:appName];
|
|
FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:keychainServiceName];
|
|
NSString *userKey = [NSString stringWithFormat:kUserKey, appName];
|
|
[keychain removeDataForKey:userKey error:NULL];
|
|
}
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName:FIRAuthStateDidChangeNotification
|
|
object:nil];
|
|
});
|
|
});
|
|
}];
|
|
});
|
|
}
|
|
|
|
+ (FIRAuth *)auth {
|
|
FIRApp *defaultApp = [FIRApp defaultApp];
|
|
if (!defaultApp) {
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"The default FIRApp instance must be configured before the default FIRAuth"
|
|
@"instance can be initialized. One way to ensure that is to call "
|
|
@"`[FIRApp configure];` (`FirebaseApp.configure()` in Swift) in the App "
|
|
@"Delegate's `application:didFinishLaunchingWithOptions:` "
|
|
@"(`application(_:didFinishLaunchingWithOptions:)` in Swift)."];
|
|
}
|
|
return [self authWithApp:defaultApp];
|
|
}
|
|
|
|
+ (FIRAuth *)authWithApp:(FIRApp *)app {
|
|
return [FIRAppAssociationRegistration registeredObjectWithHost:app
|
|
key:NSStringFromClass(self)
|
|
creationBlock:^FIRAuth *_Nullable() {
|
|
return [[FIRAuth alloc] initWithApp:app];
|
|
}];
|
|
}
|
|
|
|
- (instancetype)initWithApp:(FIRApp *)app {
|
|
[FIRAuth setKeychainServiceNameForApp:app];
|
|
self = [self initWithAPIKey:app.options.APIKey appName:app.name];
|
|
if (self) {
|
|
_app = app;
|
|
__weak FIRAuth *weakSelf = self;
|
|
app.getTokenImplementation = ^(BOOL forceRefresh, FIRTokenCallback callback) {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuth *strongSelf = weakSelf;
|
|
// Enable token auto-refresh if not aleady enabled.
|
|
if (strongSelf && !strongSelf->_autoRefreshTokens) {
|
|
FIRLogInfo(kFIRLoggerAuth, @"I-AUT000002", @"Token auto-refresh enabled.");
|
|
strongSelf->_autoRefreshTokens = YES;
|
|
[strongSelf scheduleAutoTokenRefresh];
|
|
|
|
#if TARGET_OS_IOS // TODO: Is a similar mechanism needed on macOS?
|
|
strongSelf->_applicationDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter]
|
|
addObserverForName:UIApplicationDidBecomeActiveNotification
|
|
object:nil
|
|
queue:nil
|
|
usingBlock:^(NSNotification *notification) {
|
|
FIRAuth *strongSelf = weakSelf;
|
|
if (strongSelf) {
|
|
strongSelf->_isAppInBackground = NO;
|
|
if (!strongSelf->_autoRefreshScheduled) {
|
|
[weakSelf scheduleAutoTokenRefresh];
|
|
}
|
|
}
|
|
}];
|
|
strongSelf->_applicationDidEnterBackgroundObserver = [[NSNotificationCenter defaultCenter]
|
|
addObserverForName:UIApplicationDidEnterBackgroundNotification
|
|
object:nil
|
|
queue:nil
|
|
usingBlock:^(NSNotification *notification) {
|
|
FIRAuth *strongSelf = weakSelf;
|
|
if (strongSelf) {
|
|
strongSelf->_isAppInBackground = YES;
|
|
}
|
|
}];
|
|
#endif
|
|
}
|
|
// Call back with 'nil' if there is no current user.
|
|
if (!strongSelf || !strongSelf->_currentUser) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
callback(nil, nil);
|
|
});
|
|
return;
|
|
}
|
|
// Call back with current user token.
|
|
[strongSelf->_currentUser internalGetTokenForcingRefresh:forceRefresh
|
|
callback:^(NSString *_Nullable token,
|
|
NSError *_Nullable error) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
callback(token, error);
|
|
});
|
|
}];
|
|
});
|
|
};
|
|
app.getUIDImplementation = ^NSString *_Nullable() {
|
|
__block NSString *uid;
|
|
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
|
|
uid = [weakSelf getUID];
|
|
});
|
|
return uid;
|
|
};
|
|
#if TARGET_OS_IOS
|
|
_authURLPresenter = [[FIRAuthURLPresenter alloc] init];
|
|
#endif
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (instancetype)initWithAPIKey:(NSString *)APIKey appName:(NSString *)appName {
|
|
self = [super init];
|
|
if (self) {
|
|
_listenerHandles = [NSMutableArray array];
|
|
_requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:APIKey];
|
|
_settings = [[FIRAuthSettings alloc] init];
|
|
_firebaseAppName = [appName copy];
|
|
#if TARGET_OS_IOS
|
|
UIApplication *application = [UIApplication sharedApplication];
|
|
// Initialize the shared FIRAuthAppDelegateProxy instance in the main thread if not already.
|
|
[FIRAuthAppDelegateProxy sharedInstance];
|
|
#endif
|
|
|
|
// Continue with the rest of initialization in the work thread.
|
|
__weak FIRAuth *weakSelf = self;
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
// Load current user from Keychain.
|
|
FIRAuth *strongSelf = weakSelf;
|
|
if (!strongSelf) {
|
|
return;
|
|
}
|
|
NSString *keychainServiceName =
|
|
[FIRAuth keychainServiceNameForAppName:strongSelf->_firebaseAppName];
|
|
if (keychainServiceName) {
|
|
strongSelf->_keychain = [[FIRAuthKeychain alloc] initWithService:keychainServiceName];
|
|
}
|
|
FIRUser *user;
|
|
NSError *error;
|
|
if ([strongSelf getUser:&user error:&error]) {
|
|
[strongSelf updateCurrentUser:user byForce:NO savingToDisk:NO error:&error];
|
|
self->_lastNotifiedUserToken = user.rawAccessToken;
|
|
} else {
|
|
FIRLogError(kFIRLoggerAuth, @"I-AUT000001",
|
|
@"Error loading saved user when starting up: %@", error);
|
|
}
|
|
|
|
#if TARGET_OS_IOS
|
|
// Initialize for phone number auth.
|
|
strongSelf->_tokenManager =
|
|
[[FIRAuthAPNSTokenManager alloc] initWithApplication:application];
|
|
|
|
strongSelf->_appCredentialManager =
|
|
[[FIRAuthAppCredentialManager alloc] initWithKeychain:strongSelf->_keychain];
|
|
|
|
strongSelf->_notificationManager = [[FIRAuthNotificationManager alloc]
|
|
initWithApplication:application
|
|
appCredentialManager:strongSelf->_appCredentialManager];
|
|
|
|
[[FIRAuthAppDelegateProxy sharedInstance] addHandler:strongSelf];
|
|
#endif
|
|
});
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
@synchronized (self) {
|
|
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
|
|
while (_listenerHandles.count != 0) {
|
|
FIRAuthStateDidChangeListenerHandle handleToRemove = _listenerHandles.lastObject;
|
|
[defaultCenter removeObserver:handleToRemove];
|
|
[_listenerHandles removeLastObject];
|
|
}
|
|
|
|
#if TARGET_OS_IOS
|
|
[defaultCenter removeObserver:_applicationDidBecomeActiveObserver
|
|
name:UIApplicationDidBecomeActiveNotification
|
|
object:nil];
|
|
[defaultCenter removeObserver:_applicationDidEnterBackgroundObserver
|
|
name:UIApplicationDidEnterBackgroundNotification
|
|
object:nil];
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#pragma mark - Public API
|
|
|
|
- (FIRUser *)currentUser {
|
|
__block FIRUser *result;
|
|
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
|
|
result = self->_currentUser;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
- (void)fetchProvidersForEmail:(NSString *)email
|
|
completion:(FIRProviderQueryCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRCreateAuthURIRequest *request =
|
|
[[FIRCreateAuthURIRequest alloc] initWithIdentifier:email
|
|
continueURI:@"http://www.google.com/"
|
|
requestConfiguration:self->_requestConfiguration];
|
|
[FIRAuthBackend createAuthURI:request callback:^(FIRCreateAuthURIResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (completion) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(response.allProviders, error);
|
|
});
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)fetchSignInMethodsForEmail:(nonnull NSString *)email
|
|
completion:(nullable FIRSignInMethodQueryCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRCreateAuthURIRequest *request =
|
|
[[FIRCreateAuthURIRequest alloc] initWithIdentifier:email
|
|
continueURI:@"http://www.google.com/"
|
|
requestConfiguration:self->_requestConfiguration];
|
|
[FIRAuthBackend createAuthURI:request callback:^(FIRCreateAuthURIResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (completion) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(response.signinMethods, error);
|
|
});
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)signInWithEmail:(NSString *)email
|
|
password:(NSString *)password
|
|
completion:(FIRAuthDataResultCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuthDataResultCallback decoratedCallback =
|
|
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
|
|
[self internalSignInAndRetrieveDataWithEmail:email
|
|
password:password
|
|
completion:^(FIRAuthDataResult *_Nullable authResult,
|
|
NSError *_Nullable error) {
|
|
decoratedCallback(authResult, error);
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)signInWithEmail:(NSString *)email
|
|
link:(NSString *)link
|
|
completion:(FIRAuthDataResultCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuthDataResultCallback decoratedCallback =
|
|
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
|
|
FIREmailPasswordAuthCredential *credential =
|
|
[[FIREmailPasswordAuthCredential alloc] initWithEmail:email link:link];
|
|
[self internalSignInAndRetrieveDataWithCredential:credential
|
|
isReauthentication:NO
|
|
callback:^(FIRAuthDataResult *_Nullable authResult,
|
|
NSError *_Nullable error) {
|
|
decoratedCallback(authResult, error);
|
|
}];
|
|
});
|
|
}
|
|
|
|
/** @fn signInWithEmail:password:callback:
|
|
@brief Signs in using an email address and password.
|
|
@param email The user's email address.
|
|
@param password The user's password.
|
|
@param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked
|
|
asynchronously on the global auth work queue in the future.
|
|
@remarks This is the internal counterpart of this method, which uses a callback that does not
|
|
update the current user.
|
|
*/
|
|
- (void)signInWithEmail:(NSString *)email
|
|
password:(NSString *)password
|
|
callback:(FIRAuthResultCallback)callback {
|
|
|
|
FIRVerifyPasswordRequest *request =
|
|
[[FIRVerifyPasswordRequest alloc] initWithEmail:email
|
|
password:password
|
|
requestConfiguration:_requestConfiguration];
|
|
|
|
if (![request.password length]) {
|
|
callback(nil, [FIRAuthErrorUtils wrongPasswordErrorWithMessage:nil]);
|
|
return;
|
|
}
|
|
[FIRAuthBackend verifyPassword:request
|
|
callback:^(FIRVerifyPasswordResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (error) {
|
|
callback(nil, error);
|
|
return;
|
|
}
|
|
[self completeSignInWithAccessToken:response.IDToken
|
|
accessTokenExpirationDate:response.approximateExpirationDate
|
|
refreshToken:response.refreshToken
|
|
anonymous:NO
|
|
callback:callback];
|
|
}];
|
|
}
|
|
|
|
- (void)signInAndRetrieveDataWithEmail:(NSString *)email
|
|
password:(NSString *)password
|
|
completion:(FIRAuthDataResultCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuthDataResultCallback decoratedCallback =
|
|
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
|
|
[self internalSignInAndRetrieveDataWithEmail:email
|
|
password:password
|
|
completion:decoratedCallback];
|
|
});
|
|
}
|
|
|
|
/** @fn internalSignInAndRetrieveDataWithEmail:password:callback:
|
|
@brief Signs in using an email address and password.
|
|
@param email The user's email address.
|
|
@param password The user's password.
|
|
@param completion A block which is invoked when the sign in finishes (or is cancelled.) Invoked
|
|
asynchronously on the global auth work queue in the future.
|
|
@remarks This is the internal counterpart of this method, which uses a callback that does not
|
|
update the current user.
|
|
*/
|
|
- (void)internalSignInAndRetrieveDataWithEmail:(NSString *)email
|
|
password:(NSString *)password
|
|
completion:(FIRAuthDataResultCallback)completion {
|
|
FIREmailPasswordAuthCredential *credentail =
|
|
[[FIREmailPasswordAuthCredential alloc] initWithEmail:email password:password];
|
|
[self internalSignInAndRetrieveDataWithCredential:credentail
|
|
isReauthentication:NO
|
|
callback:completion];
|
|
}
|
|
|
|
/** @fn internalSignInWithEmail:link:completion:
|
|
@brief Signs in using an email and email sign-in link.
|
|
@param email The user's email address.
|
|
@param link The email sign-in link.
|
|
@param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked
|
|
asynchronously on the global auth work queue in the future.
|
|
*/
|
|
- (void)internalSignInWithEmail:(nonnull NSString *)email
|
|
link:(nonnull NSString *)link
|
|
callback:(nullable FIRAuthResultCallback)callback {
|
|
if (![self isSignInWithEmailLink:link]) {
|
|
[FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason:
|
|
kInvalidEmailSignInLinkExceptionMessage];
|
|
return;
|
|
}
|
|
NSDictionary<NSString *, NSString *> *queryItems = FIRAuthParseURL(link);
|
|
if (![queryItems count]) {
|
|
NSURLComponents *urlComponents = [NSURLComponents componentsWithString:link];
|
|
queryItems = FIRAuthParseURL(urlComponents.query);
|
|
}
|
|
NSString *actionCode = queryItems[@"oobCode"];
|
|
|
|
FIREmailLinkSignInRequest *request =
|
|
[[FIREmailLinkSignInRequest alloc] initWithEmail:email
|
|
oobCode:actionCode
|
|
requestConfiguration:_requestConfiguration];
|
|
|
|
[FIRAuthBackend emailLinkSignin:request
|
|
callback:^(FIREmailLinkSignInResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (error) {
|
|
callback(nil, error);
|
|
return;
|
|
}
|
|
[self completeSignInWithAccessToken:response.IDToken
|
|
accessTokenExpirationDate:response.approximateExpirationDate
|
|
refreshToken:response.refreshToken
|
|
anonymous:NO
|
|
callback:callback];
|
|
}];
|
|
}
|
|
|
|
- (void)signInWithCredential:(FIRAuthCredential *)credential
|
|
completion:(FIRAuthResultCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuthResultCallback callback =
|
|
[self signInFlowAuthResultCallbackByDecoratingCallback:completion];
|
|
[self internalSignInWithCredential:credential callback:callback];
|
|
});
|
|
}
|
|
|
|
- (void)signInAndRetrieveDataWithCredential:(FIRAuthCredential *)credential
|
|
completion:(nullable FIRAuthDataResultCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuthDataResultCallback callback =
|
|
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
|
|
[self internalSignInAndRetrieveDataWithCredential:credential
|
|
isReauthentication:NO
|
|
callback:callback];
|
|
});
|
|
}
|
|
|
|
- (void)internalSignInWithCredential:(FIRAuthCredential *)credential
|
|
callback:(FIRAuthResultCallback)callback {
|
|
[self internalSignInAndRetrieveDataWithCredential:credential
|
|
isReauthentication:NO
|
|
callback:^(FIRAuthDataResult *_Nullable authResult,
|
|
NSError *_Nullable error) {
|
|
callback(authResult.user, error);
|
|
}];
|
|
}
|
|
|
|
- (void)internalSignInAndRetrieveDataWithCredential:(FIRAuthCredential *)credential
|
|
isReauthentication:(BOOL)isReauthentication
|
|
callback:(nullable FIRAuthDataResultCallback)callback {
|
|
if ([credential isKindOfClass:[FIREmailPasswordAuthCredential class]]) {
|
|
// Special case for email/password credentials
|
|
FIREmailPasswordAuthCredential *emailPasswordCredential =
|
|
(FIREmailPasswordAuthCredential *)credential;
|
|
FIRAuthResultCallback completeEmailSignIn = ^(FIRUser *user, NSError *error) {
|
|
if (callback) {
|
|
if (error) {
|
|
callback(nil, error);
|
|
return;
|
|
}
|
|
FIRAdditionalUserInfo *additionalUserInfo =
|
|
[[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID
|
|
profile:nil
|
|
username:nil
|
|
isNewUser:NO];
|
|
FIRAuthDataResult *result = [[FIRAuthDataResult alloc] initWithUser:user
|
|
additionalUserInfo:additionalUserInfo];
|
|
callback(result, error);
|
|
}
|
|
};
|
|
if (emailPasswordCredential.link) {
|
|
[self internalSignInWithEmail:emailPasswordCredential.email
|
|
link:emailPasswordCredential.link
|
|
callback:completeEmailSignIn];
|
|
} else {
|
|
[self signInWithEmail:emailPasswordCredential.email
|
|
password:emailPasswordCredential.password
|
|
callback:completeEmailSignIn];
|
|
}
|
|
return;
|
|
}
|
|
|
|
#if TARGET_OS_IOS
|
|
if ([credential isKindOfClass:[FIRPhoneAuthCredential class]]) {
|
|
// Special case for phone auth credentials
|
|
FIRPhoneAuthCredential *phoneCredential = (FIRPhoneAuthCredential *)credential;
|
|
FIRAuthOperationType operation =
|
|
isReauthentication ? FIRAuthOperationTypeReauth : FIRAuthOperationTypeSignUpOrSignIn;
|
|
[self signInWithPhoneCredential:phoneCredential
|
|
operation:operation
|
|
callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (callback) {
|
|
if (error) {
|
|
callback(nil, error);
|
|
return;
|
|
}
|
|
|
|
[self completeSignInWithAccessToken:response.IDToken
|
|
accessTokenExpirationDate:response.approximateExpirationDate
|
|
refreshToken:response.refreshToken
|
|
anonymous:NO
|
|
callback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
|
|
FIRAdditionalUserInfo *additionalUserInfo =
|
|
[[FIRAdditionalUserInfo alloc] initWithProviderID:FIRPhoneAuthProviderID
|
|
profile:nil
|
|
username:nil
|
|
isNewUser:response.isNewUser];
|
|
FIRAuthDataResult *result = user ?
|
|
[[FIRAuthDataResult alloc] initWithUser:user
|
|
additionalUserInfo:additionalUserInfo] : nil;
|
|
callback(result, error);
|
|
}];
|
|
}
|
|
}];
|
|
return;
|
|
}
|
|
#endif
|
|
FIRVerifyAssertionRequest *request =
|
|
[[FIRVerifyAssertionRequest alloc] initWithProviderID:credential.provider
|
|
requestConfiguration:_requestConfiguration];
|
|
request.autoCreate = !isReauthentication;
|
|
[credential prepareVerifyAssertionRequest:request];
|
|
[FIRAuthBackend verifyAssertion:request
|
|
callback:^(FIRVerifyAssertionResponse *response, NSError *error) {
|
|
if (error) {
|
|
if (callback) {
|
|
callback(nil, error);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (response.needConfirmation) {
|
|
if (callback) {
|
|
NSString *email = response.email;
|
|
callback(nil, [FIRAuthErrorUtils accountExistsWithDifferentCredentialErrorWithEmail:email]);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!response.providerID.length) {
|
|
if (callback) {
|
|
callback(nil, [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:response]);
|
|
}
|
|
return;
|
|
}
|
|
[self completeSignInWithAccessToken:response.IDToken
|
|
accessTokenExpirationDate:response.approximateExpirationDate
|
|
refreshToken:response.refreshToken
|
|
anonymous:NO
|
|
callback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
|
|
if (callback) {
|
|
FIRAdditionalUserInfo *additionalUserInfo =
|
|
[FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:response];
|
|
FIRAuthDataResult *result = user ?
|
|
[[FIRAuthDataResult alloc] initWithUser:user
|
|
additionalUserInfo:additionalUserInfo] : nil;
|
|
callback(result, error);
|
|
}
|
|
}];
|
|
}];
|
|
}
|
|
|
|
- (void)signInWithCredential:(FIRAuthCredential *)credential
|
|
callback:(FIRAuthResultCallback)callback {
|
|
[self signInAndRetrieveDataWithCredential:credential
|
|
completion:^(FIRAuthDataResult *_Nullable authResult,
|
|
NSError *_Nullable error) {
|
|
callback(authResult.user, error);
|
|
}];
|
|
}
|
|
|
|
- (void)signInAnonymouslyAndRetrieveDataWithCompletion:(FIRAuthDataResultCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuthDataResultCallback decoratedCallback =
|
|
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
|
|
if (self->_currentUser.anonymous) {
|
|
FIRAdditionalUserInfo *additionalUserInfo =
|
|
[[FIRAdditionalUserInfo alloc] initWithProviderID:nil
|
|
profile:nil
|
|
username:nil
|
|
isNewUser:NO];
|
|
FIRAuthDataResult *authDataResult =
|
|
[[FIRAuthDataResult alloc] initWithUser:self->_currentUser
|
|
additionalUserInfo:additionalUserInfo];
|
|
decoratedCallback(authDataResult, nil);
|
|
return;
|
|
}
|
|
[self internalSignInAnonymouslyWithCompletion:^(FIRSignUpNewUserResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (error) {
|
|
decoratedCallback(nil, error);
|
|
return;
|
|
}
|
|
[self completeSignInWithAccessToken:response.IDToken
|
|
accessTokenExpirationDate:response.approximateExpirationDate
|
|
refreshToken:response.refreshToken
|
|
anonymous:YES
|
|
callback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
|
|
FIRAdditionalUserInfo *additionalUserInfo =
|
|
[[FIRAdditionalUserInfo alloc] initWithProviderID:nil
|
|
profile:nil
|
|
username:nil
|
|
isNewUser:YES];
|
|
FIRAuthDataResult *authDataResult =
|
|
[[FIRAuthDataResult alloc] initWithUser:user
|
|
additionalUserInfo:additionalUserInfo];
|
|
decoratedCallback(authDataResult, nil);
|
|
}];
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)signInAnonymouslyWithCompletion:(FIRAuthDataResultCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuthDataResultCallback decoratedCallback =
|
|
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
|
|
if (self->_currentUser.anonymous) {
|
|
FIRAuthDataResult *result =
|
|
[[FIRAuthDataResult alloc] initWithUser:self->_currentUser additionalUserInfo:nil];
|
|
decoratedCallback(result, nil);
|
|
return;
|
|
}
|
|
[self internalSignInAnonymouslyWithCompletion:^(FIRSignUpNewUserResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (error) {
|
|
decoratedCallback(nil, error);
|
|
return;
|
|
}
|
|
[self completeSignInWithAccessToken:response.IDToken
|
|
accessTokenExpirationDate:response.approximateExpirationDate
|
|
refreshToken:response.refreshToken
|
|
anonymous:YES
|
|
callback:^(FIRUser * _Nullable user, NSError * _Nullable error) {
|
|
FIRAdditionalUserInfo *additionalUserInfo =
|
|
[[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID
|
|
profile:nil
|
|
username:nil
|
|
isNewUser:YES];
|
|
FIRAuthDataResult *authDataResult =
|
|
[[FIRAuthDataResult alloc] initWithUser:user
|
|
additionalUserInfo:additionalUserInfo];
|
|
decoratedCallback(authDataResult, nil);
|
|
}];
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)signInWithCustomToken:(NSString *)token
|
|
completion:(nullable FIRAuthDataResultCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuthDataResultCallback decoratedCallback =
|
|
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
|
|
[self internalSignInAndRetrieveDataWithCustomToken:token
|
|
completion:^(FIRAuthDataResult *_Nullable authResult,
|
|
NSError *_Nullable error) {
|
|
decoratedCallback(authResult, error);
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)signInAndRetrieveDataWithCustomToken:(NSString *)token
|
|
completion:(nullable FIRAuthDataResultCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuthDataResultCallback decoratedCallback =
|
|
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
|
|
[self internalSignInAndRetrieveDataWithCustomToken:token completion:decoratedCallback];
|
|
});
|
|
}
|
|
|
|
- (void)createUserWithEmail:(NSString *)email
|
|
password:(NSString *)password
|
|
completion:(nullable FIRAuthDataResultCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuthDataResultCallback decoratedCallback =
|
|
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
|
|
[self internalCreateUserWithEmail:email
|
|
password:password
|
|
completion:^(FIRSignUpNewUserResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (error) {
|
|
decoratedCallback(nil, error);
|
|
return;
|
|
}
|
|
[self completeSignInWithAccessToken:response.IDToken
|
|
accessTokenExpirationDate:response.approximateExpirationDate
|
|
refreshToken:response.refreshToken
|
|
anonymous:NO
|
|
callback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
|
|
FIRAdditionalUserInfo *additionalUserInfo =
|
|
[[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID
|
|
profile:nil
|
|
username:nil
|
|
isNewUser:YES];
|
|
FIRAuthDataResult *authDataResult =
|
|
[[FIRAuthDataResult alloc] initWithUser:user
|
|
additionalUserInfo:additionalUserInfo];
|
|
decoratedCallback(authDataResult, nil);
|
|
}];
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)createUserAndRetrieveDataWithEmail:(NSString *)email
|
|
password:(NSString *)password
|
|
completion:(FIRAuthDataResultCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRAuthDataResultCallback decoratedCallback =
|
|
[self signInFlowAuthDataResultCallbackByDecoratingCallback:completion];
|
|
[self internalCreateUserWithEmail:email
|
|
password:password
|
|
completion:^(FIRSignUpNewUserResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (error) {
|
|
decoratedCallback(nil, error);
|
|
return;
|
|
}
|
|
|
|
[self completeSignInWithAccessToken:response.IDToken
|
|
accessTokenExpirationDate:response.approximateExpirationDate
|
|
refreshToken:response.refreshToken
|
|
anonymous:NO
|
|
callback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
|
|
FIRAdditionalUserInfo *additionalUserInfo =
|
|
[[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID
|
|
profile:nil
|
|
username:nil
|
|
isNewUser:YES];
|
|
FIRAuthDataResult *authDataResult =
|
|
[[FIRAuthDataResult alloc] initWithUser:user
|
|
additionalUserInfo:additionalUserInfo];
|
|
decoratedCallback(authDataResult, nil);
|
|
}];
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)confirmPasswordResetWithCode:(NSString *)code
|
|
newPassword:(NSString *)newPassword
|
|
completion:(FIRConfirmPasswordResetCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
FIRResetPasswordRequest *request =
|
|
[[FIRResetPasswordRequest alloc] initWithOobCode:code
|
|
newPassword:newPassword
|
|
requestConfiguration:self->_requestConfiguration];
|
|
[FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (completion) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (error) {
|
|
completion(error);
|
|
return;
|
|
}
|
|
completion(nil);
|
|
});
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)checkActionCode:(NSString *)code completion:(FIRCheckActionCodeCallBack)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^ {
|
|
FIRResetPasswordRequest *request =
|
|
[[FIRResetPasswordRequest alloc] initWithOobCode:code
|
|
newPassword:nil
|
|
requestConfiguration:self->_requestConfiguration];
|
|
[FIRAuthBackend resetPassword:request callback:^(FIRResetPasswordResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (completion) {
|
|
if (error) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(nil, error);
|
|
});
|
|
return;
|
|
}
|
|
FIRActionCodeOperation operation =
|
|
[FIRActionCodeInfo actionCodeOperationForRequestType:response.requestType];
|
|
FIRActionCodeInfo *actionCodeInfo =
|
|
[[FIRActionCodeInfo alloc] initWithOperation:operation
|
|
email:response.email
|
|
newEmail:response.verifiedEmail];
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(actionCodeInfo, nil);
|
|
});
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)verifyPasswordResetCode:(NSString *)code
|
|
completion:(FIRVerifyPasswordResetCodeCallback)completion {
|
|
[self checkActionCode:code completion:^(FIRActionCodeInfo *_Nullable info,
|
|
NSError *_Nullable error) {
|
|
if (completion) {
|
|
if (error) {
|
|
completion(nil, error);
|
|
return;
|
|
}
|
|
completion([info dataForKey:FIRActionCodeEmailKey], nil);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)applyActionCode:(NSString *)code completion:(FIRApplyActionCodeCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^ {
|
|
FIRSetAccountInfoRequest *request =
|
|
[[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:self->_requestConfiguration];
|
|
request.OOBCode = code;
|
|
[FIRAuthBackend setAccountInfo:request callback:^(FIRSetAccountInfoResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (completion) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(error);
|
|
});
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)sendPasswordResetWithEmail:(NSString *)email
|
|
completion:(nullable FIRSendPasswordResetCallback)completion {
|
|
[self sendPasswordResetWithNullableActionCodeSettings:nil email:email completion:completion];
|
|
}
|
|
|
|
- (void)sendPasswordResetWithEmail:(NSString *)email
|
|
actionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings
|
|
completion:(nullable FIRSendPasswordResetCallback)completion {
|
|
[self sendPasswordResetWithNullableActionCodeSettings:actionCodeSettings
|
|
email:email
|
|
completion:completion];
|
|
}
|
|
|
|
/** @fn sendPasswordResetWithNullableActionCodeSettings:actionCodeSetting:email:completion:
|
|
@brief Initiates a password reset for the given email address and @FIRActionCodeSettings object.
|
|
|
|
@param actionCodeSettings Optionally, An @c FIRActionCodeSettings object containing settings
|
|
related to the handling action codes.
|
|
@param email The email address of the user.
|
|
@param completion Optionally; a block which is invoked when the request finishes. Invoked
|
|
asynchronously on the main thread in the future.
|
|
*/
|
|
- (void)sendPasswordResetWithNullableActionCodeSettings:(nullable FIRActionCodeSettings *)
|
|
actionCodeSettings
|
|
email:(NSString *)email
|
|
completion:(nullable FIRSendPasswordResetCallback)
|
|
completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
if (!email) {
|
|
[FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason:
|
|
kMissingEmailInvalidParameterExceptionReason];
|
|
}
|
|
FIRGetOOBConfirmationCodeRequest *request =
|
|
[FIRGetOOBConfirmationCodeRequest passwordResetRequestWithEmail:email
|
|
actionCodeSettings:actionCodeSettings
|
|
requestConfiguration:self->_requestConfiguration
|
|
];
|
|
[FIRAuthBackend getOOBConfirmationCode:request
|
|
callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (completion) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(error);
|
|
});
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)sendSignInLinkToEmail:(nonnull NSString *)email
|
|
actionCodeSettings:(nonnull FIRActionCodeSettings *)actionCodeSettings
|
|
completion:(nullable FIRSendSignInLinkToEmailCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
if (!email) {
|
|
[FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason:
|
|
kMissingEmailInvalidParameterExceptionReason];
|
|
}
|
|
|
|
if (!actionCodeSettings.handleCodeInApp) {
|
|
[FIRAuthExceptionUtils raiseInvalidParameterExceptionWithReason:
|
|
kHandleCodeInAppFalseExceptionReason];
|
|
}
|
|
FIRGetOOBConfirmationCodeRequest *request =
|
|
[FIRGetOOBConfirmationCodeRequest signInWithEmailLinkRequest:email
|
|
actionCodeSettings:actionCodeSettings
|
|
requestConfiguration:self->_requestConfiguration];
|
|
[FIRAuthBackend getOOBConfirmationCode:request
|
|
callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (completion) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(error);
|
|
});
|
|
}
|
|
}];
|
|
});
|
|
}
|
|
|
|
- (void)updateCurrentUser:(FIRUser *)user completion:(nullable FIRUserUpdateCallback)completion {
|
|
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
|
|
if (!user) {
|
|
if (completion) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion([FIRAuthErrorUtils nullUserErrorWithMessage:nil]);
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
void (^updateUserBlock)(FIRUser *user) = ^(FIRUser *user) {
|
|
NSError *error;
|
|
[self updateCurrentUser:user byForce:YES savingToDisk:YES error:(&error)];
|
|
if (error) {
|
|
if (completion) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(error);
|
|
});
|
|
}
|
|
return;
|
|
} if (completion) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(nil);
|
|
});
|
|
}
|
|
};
|
|
if (![user.requestConfiguration.APIKey isEqualToString:self->_requestConfiguration.APIKey]) {
|
|
// If the API keys are different, then we need to confirm that the user belongs to the same
|
|
// project before proceeding.
|
|
user.requestConfiguration = self->_requestConfiguration;
|
|
[user reloadWithCompletion:^(NSError *_Nullable error) {
|
|
if (error) {
|
|
if (completion) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(error);
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
updateUserBlock(user);
|
|
}];
|
|
} else {
|
|
updateUserBlock(user);
|
|
}
|
|
});
|
|
}
|
|
|
|
- (BOOL)signOut:(NSError *_Nullable __autoreleasing *_Nullable)error {
|
|
__block BOOL result = YES;
|
|
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
|
|
if (!self->_currentUser) {
|
|
return;
|
|
}
|
|
result = [self updateCurrentUser:nil byForce:NO savingToDisk:YES error:error];
|
|
});
|
|
return result;
|
|
}
|
|
|
|
- (BOOL)signOutByForceWithUserID:(NSString *)userID error:(NSError *_Nullable *_Nullable)error {
|
|
if (_currentUser.uid != userID) {
|
|
return YES;
|
|
}
|
|
return [self updateCurrentUser:nil byForce:YES savingToDisk:YES error:error];
|
|
}
|
|
|
|
- (BOOL)isSignInWithEmailLink:(NSString *)link {
|
|
if (link.length == 0) {
|
|
return NO;
|
|
}
|
|
NSDictionary<NSString *, NSString *> *queryItems = FIRAuthParseURL(link);
|
|
if (![queryItems count]) {
|
|
NSURLComponents *urlComponents = [NSURLComponents componentsWithString:link];
|
|
if (!urlComponents.query) {
|
|
return NO;
|
|
}
|
|
queryItems = FIRAuthParseURL(urlComponents.query);
|
|
}
|
|
|
|
if (![queryItems count]) {
|
|
return NO;
|
|
}
|
|
|
|
NSString *actionCode = queryItems[@"oobCode"];
|
|
NSString *mode = queryItems[@"mode"];
|
|
|
|
if (actionCode && [mode isEqualToString:@"signIn"]) {
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
/** @fn FIRAuthParseURL:NSString
|
|
@brief Parses an incoming URL into all available query items.
|
|
@param urlString The url to be parsed.
|
|
@return A dictionary of available query items in the target URL.
|
|
*/
|
|
static NSDictionary<NSString *, NSString *> *FIRAuthParseURL(NSString *urlString) {
|
|
NSString *linkURL = [NSURLComponents componentsWithString:urlString].query;
|
|
if (!linkURL) {
|
|
return @{};
|
|
}
|
|
NSArray<NSString *> *URLComponents = [linkURL componentsSeparatedByString:@"&"];
|
|
NSMutableDictionary<NSString *, NSString *> *queryItems =
|
|
[[NSMutableDictionary alloc] initWithCapacity:URLComponents.count];
|
|
for (NSString *component in URLComponents) {
|
|
NSRange equalRange = [component rangeOfString:@"="];
|
|
if (equalRange.location != NSNotFound) {
|
|
NSString *queryItemKey =
|
|
[[component substringToIndex:equalRange.location] stringByRemovingPercentEncoding];
|
|
NSString *queryItemValue =
|
|
[[component substringFromIndex:equalRange.location + 1] stringByRemovingPercentEncoding];
|
|
if (queryItemKey && queryItemValue) {
|
|
queryItems[queryItemKey] = queryItemValue;
|
|
}
|
|
}
|
|
}
|
|
return queryItems;
|
|
}
|
|
|
|
- (FIRAuthStateDidChangeListenerHandle)addAuthStateDidChangeListener:
|
|
(FIRAuthStateDidChangeListenerBlock)listener {
|
|
__block BOOL firstInvocation = YES;
|
|
__block NSString *previousUserID;
|
|
return [self addIDTokenDidChangeListener:^(FIRAuth *_Nonnull auth, FIRUser *_Nullable user) {
|
|
BOOL shouldCallListener = firstInvocation ||
|
|
!(previousUserID == user.uid || [previousUserID isEqualToString:user.uid]);
|
|
firstInvocation = NO;
|
|
previousUserID = [user.uid copy];
|
|
if (shouldCallListener) {
|
|
listener(auth, user);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)removeAuthStateDidChangeListener:(FIRAuthStateDidChangeListenerHandle)listenerHandle {
|
|
[self removeIDTokenDidChangeListener:listenerHandle];
|
|
}
|
|
|
|
- (FIRIDTokenDidChangeListenerHandle)addIDTokenDidChangeListener:
|
|
(FIRIDTokenDidChangeListenerBlock)listener {
|
|
if (!listener) {
|
|
[NSException raise:NSInvalidArgumentException format:@"listener must not be nil."];
|
|
return nil;
|
|
}
|
|
FIRAuthStateDidChangeListenerHandle handle;
|
|
NSNotificationCenter *notifications = [NSNotificationCenter defaultCenter];
|
|
handle = [notifications addObserverForName:FIRAuthStateDidChangeNotification
|
|
object:self
|
|
queue:[NSOperationQueue mainQueue]
|
|
usingBlock:^(NSNotification *_Nonnull notification) {
|
|
FIRAuth *auth = notification.object;
|
|
listener(auth, auth.currentUser);
|
|
}];
|
|
@synchronized (self) {
|
|
[_listenerHandles addObject:handle];
|
|
}
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
listener(self, self->_currentUser);
|
|
});
|
|
return handle;
|
|
}
|
|
|
|
- (void)removeIDTokenDidChangeListener:(FIRIDTokenDidChangeListenerHandle)listenerHandle {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:listenerHandle];
|
|
@synchronized (self) {
|
|
[_listenerHandles removeObject:listenerHandle];
|
|
}
|
|
}
|
|
|
|
- (void)useAppLanguage {
|
|
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
|
|
self->_requestConfiguration.languageCode =
|
|
[NSBundle mainBundle].preferredLocalizations.firstObject;
|
|
});
|
|
}
|
|
|
|
- (nullable NSString *)languageCode {
|
|
return _requestConfiguration.languageCode;
|
|
}
|
|
|
|
- (void)setLanguageCode:(nullable NSString *)languageCode {
|
|
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
|
|
self->_requestConfiguration.languageCode = [languageCode copy];
|
|
});
|
|
}
|
|
|
|
- (NSString *)additionalFrameworkMarker {
|
|
return self->_requestConfiguration.additionalFrameworkMarker;
|
|
}
|
|
|
|
- (void)setAdditionalFrameworkMarker:(NSString *)additionalFrameworkMarker {
|
|
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
|
|
self->_requestConfiguration.additionalFrameworkMarker = [additionalFrameworkMarker copy];
|
|
});
|
|
}
|
|
|
|
#if TARGET_OS_IOS
|
|
- (NSData *)APNSToken {
|
|
__block NSData *result = nil;
|
|
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
|
|
result = self->_tokenManager.token.data;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
- (void)setAPNSToken:(NSData *)APNSToken {
|
|
[self setAPNSToken:APNSToken type:FIRAuthAPNSTokenTypeUnknown];
|
|
}
|
|
|
|
- (void)setAPNSToken:(NSData *)token type:(FIRAuthAPNSTokenType)type {
|
|
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
|
|
self->_tokenManager.token = [[FIRAuthAPNSToken alloc] initWithData:token type:type];
|
|
});
|
|
}
|
|
|
|
- (void)handleAPNSTokenError:(NSError *)error {
|
|
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
|
|
[self->_tokenManager cancelWithError:error];
|
|
});
|
|
}
|
|
|
|
- (BOOL)canHandleNotification:(NSDictionary *)userInfo {
|
|
__block BOOL result = NO;
|
|
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
|
|
result = [self->_notificationManager canHandleNotification:userInfo];
|
|
});
|
|
return result;
|
|
}
|
|
|
|
- (BOOL)canHandleURL:(NSURL *)URL {
|
|
__block BOOL result = NO;
|
|
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
|
|
result = [self->_authURLPresenter canHandleURL:URL];
|
|
});
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
#pragma mark - Internal Methods
|
|
|
|
#if TARGET_OS_IOS
|
|
/** @fn signInWithPhoneCredential:callback:
|
|
@brief Signs in using a phone credential.
|
|
@param credential The Phone Auth credential used to sign in.
|
|
@param operation The type of operation for which this sign-in attempt is initiated.
|
|
@param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked
|
|
asynchronously on the global auth work queue in the future.
|
|
*/
|
|
- (void)signInWithPhoneCredential:(FIRPhoneAuthCredential *)credential
|
|
operation:(FIRAuthOperationType)operation
|
|
callback:(FIRVerifyPhoneNumberResponseCallback)callback {
|
|
if (credential.temporaryProof.length && credential.phoneNumber.length) {
|
|
FIRVerifyPhoneNumberRequest *request =
|
|
[[FIRVerifyPhoneNumberRequest alloc] initWithTemporaryProof:credential.temporaryProof
|
|
phoneNumber:credential.phoneNumber
|
|
operation:operation
|
|
requestConfiguration:_requestConfiguration];
|
|
[FIRAuthBackend verifyPhoneNumber:request callback:callback];
|
|
return;
|
|
}
|
|
|
|
if (!credential.verificationID.length) {
|
|
callback(nil, [FIRAuthErrorUtils missingVerificationIDErrorWithMessage:nil]);
|
|
return;
|
|
}
|
|
if (!credential.verificationCode.length) {
|
|
callback(nil, [FIRAuthErrorUtils missingVerificationCodeErrorWithMessage:nil]);
|
|
return;
|
|
}
|
|
FIRVerifyPhoneNumberRequest *request =
|
|
[[FIRVerifyPhoneNumberRequest alloc]initWithVerificationID:credential.verificationID
|
|
verificationCode:credential.verificationCode
|
|
operation:operation
|
|
requestConfiguration:_requestConfiguration];
|
|
[FIRAuthBackend verifyPhoneNumber:request callback:callback];
|
|
}
|
|
|
|
#endif
|
|
|
|
/** @fn internalSignInAndRetrieveDataWithCustomToken:completion:
|
|
@brief Signs in a Firebase user given a custom token.
|
|
@param token A self-signed custom auth token.
|
|
@param completion A block which is invoked when the custom token sign in request completes.
|
|
*/
|
|
- (void)internalSignInAndRetrieveDataWithCustomToken:(NSString *)token
|
|
completion:(nullable FIRAuthDataResultCallback)
|
|
completion {
|
|
FIRVerifyCustomTokenRequest *request =
|
|
[[FIRVerifyCustomTokenRequest alloc] initWithToken:token
|
|
requestConfiguration:_requestConfiguration];
|
|
[FIRAuthBackend verifyCustomToken:request
|
|
callback:^(FIRVerifyCustomTokenResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
if (error) {
|
|
if (completion) {
|
|
completion(nil, error);
|
|
return;
|
|
}
|
|
}
|
|
[self completeSignInWithAccessToken:response.IDToken
|
|
accessTokenExpirationDate:response.approximateExpirationDate
|
|
refreshToken:response.refreshToken
|
|
anonymous:NO
|
|
callback:^(FIRUser *_Nullable user,
|
|
NSError *_Nullable error) {
|
|
if (error) {
|
|
if (completion) {
|
|
completion(nil, error);
|
|
}
|
|
return;
|
|
}
|
|
FIRAdditionalUserInfo *additonalUserInfo =
|
|
[[FIRAdditionalUserInfo alloc] initWithProviderID:nil
|
|
profile:nil
|
|
username:nil
|
|
isNewUser:response.isNewUser];
|
|
FIRAuthDataResult *result =
|
|
[[FIRAuthDataResult alloc] initWithUser:user additionalUserInfo:additonalUserInfo];
|
|
if (completion) {
|
|
completion(result, nil);
|
|
}
|
|
}];
|
|
}];
|
|
}
|
|
|
|
/** @fn internalCreateUserWithEmail:password:completion:
|
|
@brief Makes a backend request attempting to create a new Firebase user given an email address
|
|
and password.
|
|
@param email The email address used to create the new Firebase user.
|
|
@param password The password used to create the new Firebase user.
|
|
@param completion Optionally; a block which is invoked when the request finishes.
|
|
*/
|
|
- (void)internalCreateUserWithEmail:(NSString *)email
|
|
password:(NSString *)password
|
|
completion:(nullable FIRSignupNewUserCallback)completion {
|
|
FIRSignUpNewUserRequest *request =
|
|
[[FIRSignUpNewUserRequest alloc] initWithEmail:email
|
|
password:password
|
|
displayName:nil
|
|
requestConfiguration:_requestConfiguration];
|
|
if (![request.password length]) {
|
|
completion(nil, [FIRAuthErrorUtils
|
|
weakPasswordErrorWithServerResponseReason:kMissingPasswordReason]);
|
|
return;
|
|
}
|
|
if (![request.email length]) {
|
|
completion(nil, [FIRAuthErrorUtils missingEmailErrorWithMessage:nil]);
|
|
return;
|
|
}
|
|
[FIRAuthBackend signUpNewUser:request callback:completion];
|
|
}
|
|
|
|
/** @fn internalSignInAnonymouslyWithCompletion:
|
|
@param completion A block which is invoked when the anonymous sign in request completes.
|
|
*/
|
|
- (void)internalSignInAnonymouslyWithCompletion:(FIRSignupNewUserCallback)completion {
|
|
FIRSignUpNewUserRequest *request =
|
|
[[FIRSignUpNewUserRequest alloc]initWithRequestConfiguration:_requestConfiguration];
|
|
[FIRAuthBackend signUpNewUser:request
|
|
callback:completion];
|
|
}
|
|
|
|
/** @fn possiblyPostAuthStateChangeNotification
|
|
@brief Posts the auth state change notificaton if current user's token has been changed.
|
|
*/
|
|
- (void)possiblyPostAuthStateChangeNotification {
|
|
NSString *token = _currentUser.rawAccessToken;
|
|
if (_lastNotifiedUserToken == token || [_lastNotifiedUserToken isEqualToString:token]) {
|
|
return;
|
|
}
|
|
_lastNotifiedUserToken = token;
|
|
if (_autoRefreshTokens) {
|
|
// Shedule new refresh task after successful attempt.
|
|
[self scheduleAutoTokenRefresh];
|
|
}
|
|
NSMutableDictionary *internalNotificationParameters = [NSMutableDictionary dictionary];
|
|
if (self.app) {
|
|
internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationAppKey] = self.app;
|
|
}
|
|
if (token.length) {
|
|
internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationTokenKey] = token;
|
|
}
|
|
internalNotificationParameters[FIRAuthStateDidChangeInternalNotificationUIDKey] = _currentUser.uid;
|
|
NSNotificationCenter *notifications = [NSNotificationCenter defaultCenter];
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[notifications postNotificationName:FIRAuthStateDidChangeInternalNotification
|
|
object:self
|
|
userInfo:internalNotificationParameters];
|
|
[notifications postNotificationName:FIRAuthStateDidChangeNotification
|
|
object:self];
|
|
});
|
|
}
|
|
|
|
- (BOOL)updateKeychainWithUser:(FIRUser *)user error:(NSError *_Nullable *_Nullable)error {
|
|
if (user != _currentUser) {
|
|
// No-op if the user is no longer signed in. This is not considered an error as we don't check
|
|
// whether the user is still current on other callbacks of user operations either.
|
|
return YES;
|
|
}
|
|
if ([self saveUser:user error:error]) {
|
|
[self possiblyPostAuthStateChangeNotification];
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
/** @fn setKeychainServiceNameForApp
|
|
@brief Sets the keychain service name global data for the particular app.
|
|
@param app The Firebase app to set keychain service name for.
|
|
*/
|
|
+ (void)setKeychainServiceNameForApp:(FIRApp *)app {
|
|
@synchronized (self) {
|
|
gKeychainServiceNameForAppName[app.name] =
|
|
[@"firebase_auth_" stringByAppendingString:app.options.googleAppID];
|
|
}
|
|
}
|
|
|
|
/** @fn keychainServiceNameForAppName:
|
|
@brief Gets the keychain service name global data for the particular app by name.
|
|
@param appName The name of the Firebase app to get keychain service name for.
|
|
*/
|
|
+ (NSString *)keychainServiceNameForAppName:(NSString *)appName {
|
|
@synchronized (self) {
|
|
return gKeychainServiceNameForAppName[appName];
|
|
}
|
|
}
|
|
|
|
/** @fn deleteKeychainServiceNameForAppName:
|
|
@brief Deletes the keychain service name global data for the particular app by name.
|
|
@param appName The name of the Firebase app to delete keychain service name for.
|
|
*/
|
|
+ (void)deleteKeychainServiceNameForAppName:(NSString *)appName {
|
|
@synchronized (self) {
|
|
[gKeychainServiceNameForAppName removeObjectForKey:appName];
|
|
}
|
|
}
|
|
|
|
/** @fn scheduleAutoTokenRefreshWithDelay:
|
|
@brief Schedules a task to automatically refresh tokens on the current user. The token refresh
|
|
is scheduled 5 minutes before the scheduled expiration time.
|
|
@remarks If the token expires in less than 5 minutes, schedule the token refresh immediately.
|
|
*/
|
|
- (void)scheduleAutoTokenRefresh {
|
|
NSTimeInterval tokenExpirationInterval =
|
|
[_currentUser.accessTokenExpirationDate timeIntervalSinceNow] - kTokenRefreshHeadStart;
|
|
[self scheduleAutoTokenRefreshWithDelay:MAX(tokenExpirationInterval, 0) retry:NO];
|
|
}
|
|
|
|
/** @fn scheduleAutoTokenRefreshWithDelay:
|
|
@brief Schedules a task to automatically refresh tokens on the current user.
|
|
@param delay The delay in seconds after which the token refresh task should be scheduled to be
|
|
executed.
|
|
@param retry Flag to determine whether the invocation is a retry attempt or not.
|
|
*/
|
|
- (void)scheduleAutoTokenRefreshWithDelay:(NSTimeInterval)delay retry:(BOOL)retry {
|
|
NSString *accessToken = _currentUser.rawAccessToken;
|
|
if (!accessToken) {
|
|
return;
|
|
}
|
|
if (retry) {
|
|
FIRLogInfo(kFIRLoggerAuth, @"I-AUT000003",
|
|
@"Token auto-refresh re-scheduled in %02d:%02d "
|
|
@"because of error on previous refresh attempt.",
|
|
(int)ceil(delay) / 60, (int)ceil(delay) % 60);
|
|
} else {
|
|
FIRLogInfo(kFIRLoggerAuth, @"I-AUT000004",
|
|
@"Token auto-refresh scheduled in %02d:%02d for the new token.",
|
|
(int)ceil(delay) / 60, (int)ceil(delay) % 60);
|
|
}
|
|
_autoRefreshScheduled = YES;
|
|
__weak FIRAuth *weakSelf = self;
|
|
[[FIRAuthDispatcher sharedInstance] dispatchAfterDelay:delay
|
|
queue:FIRAuthGlobalWorkQueue()
|
|
task:^(void) {
|
|
FIRAuth *strongSelf = weakSelf;
|
|
if (!strongSelf) {
|
|
return;
|
|
}
|
|
if (![strongSelf->_currentUser.rawAccessToken isEqualToString:accessToken]) {
|
|
// Another auto refresh must have been scheduled, so keep _autoRefreshScheduled unchanged.
|
|
return;
|
|
}
|
|
strongSelf->_autoRefreshScheduled = NO;
|
|
if (strongSelf->_isAppInBackground) {
|
|
return;
|
|
}
|
|
NSString *uid = strongSelf->_currentUser.uid;
|
|
[strongSelf->_currentUser internalGetTokenForcingRefresh:YES
|
|
callback:^(NSString *_Nullable token,
|
|
NSError *_Nullable error) {
|
|
if (![strongSelf->_currentUser.uid isEqualToString:uid]) {
|
|
return;
|
|
}
|
|
if (error) {
|
|
// Kicks off exponential back off logic to retry failed attempt. Starts with one minute
|
|
// delay (60 seconds) if this is the first failed attempt.
|
|
NSTimeInterval rescheduleDelay;
|
|
if (retry) {
|
|
rescheduleDelay = MIN(delay * 2, kMaxWaitTimeForBackoff);
|
|
} else {
|
|
rescheduleDelay = 60;
|
|
}
|
|
[strongSelf scheduleAutoTokenRefreshWithDelay:rescheduleDelay retry:YES];
|
|
}
|
|
}];
|
|
}];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
/** @fn completeSignInWithTokenService:callback:
|
|
@brief Completes a sign-in flow once we have access and refresh tokens for the user.
|
|
@param accessToken The STS access token.
|
|
@param accessTokenExpirationDate The approximate expiration date of the access token.
|
|
@param refreshToken The STS refresh token.
|
|
@param anonymous Whether or not the user is anonymous.
|
|
@param callback Called when the user has been signed in or when an error occurred. Invoked
|
|
asynchronously on the global auth work queue in the future.
|
|
*/
|
|
- (void)completeSignInWithAccessToken:(NSString *)accessToken
|
|
accessTokenExpirationDate:(NSDate *)accessTokenExpirationDate
|
|
refreshToken:(NSString *)refreshToken
|
|
anonymous:(BOOL)anonymous
|
|
callback:(FIRAuthResultCallback)callback {
|
|
[FIRUser retrieveUserWithAuth:self
|
|
accessToken:accessToken
|
|
accessTokenExpirationDate:accessTokenExpirationDate
|
|
refreshToken:refreshToken
|
|
anonymous:anonymous
|
|
callback:callback];
|
|
}
|
|
|
|
/** @fn signInFlowAuthResultCallbackByDecoratingCallback:
|
|
@brief Creates a FIRAuthResultCallback block which wraps another FIRAuthResultCallback; trying
|
|
to update the current user before forwarding it's invocations along to a subject block
|
|
@param callback Called when the user has been updated or when an error has occurred. Invoked
|
|
asynchronously on the main thread in the future.
|
|
@return Returns a block that updates the current user.
|
|
@remarks Typically invoked as part of the complete sign-in flow. For any other uses please
|
|
consider alternative ways of updating the current user.
|
|
*/
|
|
- (FIRAuthResultCallback)signInFlowAuthResultCallbackByDecoratingCallback:
|
|
(nullable FIRAuthResultCallback)callback {
|
|
return ^(FIRUser *_Nullable user, NSError *_Nullable error) {
|
|
if (error) {
|
|
if (callback) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
callback(nil, error);
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
if (![self updateCurrentUser:user byForce:NO savingToDisk:YES error:&error]) {
|
|
if (callback) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
callback(nil, error);
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
if (callback) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
callback(user, nil);
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
/** @fn signInFlowAuthDataResultCallbackByDecoratingCallback:
|
|
@brief Creates a FIRAuthDataResultCallback block which wraps another FIRAuthDataResultCallback;
|
|
trying to update the current user before forwarding it's invocations along to a subject
|
|
block.
|
|
@param callback Called when the user has been updated or when an error has occurred. Invoked
|
|
asynchronously on the main thread in the future.
|
|
@return Returns a block that updates the current user.
|
|
@remarks Typically invoked as part of the complete sign-in flow. For any other uses please
|
|
consider alternative ways of updating the current user.
|
|
*/
|
|
- (FIRAuthDataResultCallback)signInFlowAuthDataResultCallbackByDecoratingCallback:
|
|
(nullable FIRAuthDataResultCallback)callback {
|
|
return ^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) {
|
|
if (error) {
|
|
if (callback) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
callback(nil, error);
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
if (![self updateCurrentUser:authResult.user byForce:NO savingToDisk:YES error:&error]) {
|
|
if (callback) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
callback(nil, error);
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
if (callback) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
callback(authResult, nil);
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
#pragma mark - User-Related Methods
|
|
|
|
/** @fn updateCurrentUser:savingToDisk:
|
|
@brief Update the current user; initializing the user's internal properties correctly, and
|
|
optionally saving the user to disk.
|
|
@remarks This method is called during: sign in and sign out events, as well as during class
|
|
initialization time. The only time the saveToDisk parameter should be set to NO is during
|
|
class initialization time because the user was just read from disk.
|
|
@param user The user to use as the current user (including nil, which is passed at sign out
|
|
time.)
|
|
@param saveToDisk Indicates the method should persist the user data to disk.
|
|
*/
|
|
- (BOOL)updateCurrentUser:(FIRUser *)user
|
|
byForce:(BOOL)force
|
|
savingToDisk:(BOOL)saveToDisk
|
|
error:(NSError *_Nullable *_Nullable)error {
|
|
if (user == _currentUser) {
|
|
[self possiblyPostAuthStateChangeNotification];
|
|
return YES;
|
|
}
|
|
BOOL success = YES;
|
|
if (saveToDisk) {
|
|
success = [self saveUser:user error:error];
|
|
}
|
|
if (success || force) {
|
|
_currentUser = user;
|
|
[self possiblyPostAuthStateChangeNotification];
|
|
}
|
|
return success;
|
|
}
|
|
|
|
/** @fn saveUser:error:
|
|
@brief Persists user.
|
|
@param user The user to save.
|
|
@param error Return value for any error which occurs.
|
|
@return @YES on success, @NO otherwise.
|
|
*/
|
|
- (BOOL)saveUser:(FIRUser *)user
|
|
error:(NSError *_Nullable *_Nullable)error {
|
|
BOOL success;
|
|
NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName];
|
|
|
|
if (!user) {
|
|
success = [_keychain removeDataForKey:userKey error:error];
|
|
} else {
|
|
// Encode the user object.
|
|
NSMutableData *archiveData = [NSMutableData data];
|
|
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archiveData];
|
|
[archiver encodeObject:user forKey:userKey];
|
|
[archiver finishEncoding];
|
|
|
|
// Save the user object's encoded value.
|
|
success = [_keychain setData:archiveData forKey:userKey error:error];
|
|
}
|
|
return success;
|
|
}
|
|
|
|
/** @fn getUser:error:
|
|
@brief Retrieves the saved user associated, if one exists, from the keychain.
|
|
@param outUser An out parameter which is populated with the saved user, if one exists.
|
|
@param error Return value for any error which occurs.
|
|
@return YES if the operation was a success (irrespective of whether or not a saved user existed
|
|
for the given @c firebaseAppId,) NO if an error occurred.
|
|
*/
|
|
- (BOOL)getUser:(FIRUser *_Nullable *)outUser
|
|
error:(NSError *_Nullable *_Nullable)error {
|
|
NSString *userKey = [NSString stringWithFormat:kUserKey, _firebaseAppName];
|
|
|
|
NSError *keychainError;
|
|
NSData *encodedUserData = [_keychain dataForKey:userKey error:&keychainError];
|
|
if (keychainError) {
|
|
if (error) {
|
|
*error = keychainError;
|
|
}
|
|
return NO;
|
|
}
|
|
if (!encodedUserData) {
|
|
*outUser = nil;
|
|
return YES;
|
|
}
|
|
NSKeyedUnarchiver *unarchiver =
|
|
[[NSKeyedUnarchiver alloc] initForReadingWithData:encodedUserData];
|
|
FIRUser *user = [unarchiver decodeObjectOfClass:[FIRUser class] forKey:userKey];
|
|
user.auth = self;
|
|
*outUser = user;
|
|
return YES;
|
|
}
|
|
|
|
- (nullable NSString *)getUID {
|
|
return _currentUser.uid;
|
|
}
|
|
|
|
@end
|