diff --git a/FirebaseAuthUI/FUIAuth.m b/FirebaseAuthUI/FUIAuth.m index 448299fb7b4..09319e2efcf 100644 --- a/FirebaseAuthUI/FUIAuth.m +++ b/FirebaseAuthUI/FUIAuth.m @@ -19,12 +19,14 @@ #import #import +#import #import #import #import "FUIAuthBaseViewController_Internal.h" #import "FUIAuthErrors.h" #import "FUIAuthPickerViewController.h" #import "FUIAuthStrings.h" +#import "FUIGoogleAuth.h" #import "FUIEmailEntryViewController.h" #import "FUIPasswordVerificationViewController.h" @@ -173,7 +175,6 @@ - (void)signInWithProviderUI:(id)providerUI } return; } - // Block to complete sign-in void (^completeSignInBlock)(FIRAuthDataResult *, NSError *) = ^(FIRAuthDataResult *authResult, NSError *error) { @@ -200,7 +201,6 @@ - (void)signInWithProviderUI:(id)providerUI if (error) { // Check for "credential in use" conflict error and handle appropriately. if (error.code == FIRAuthErrorCodeCredentialAlreadyInUse) { - NSError *mergeError; FIRAuthCredential *newCredential = credential; // Check for and handle special case for Phone Auth Provider. if (providerUI.providerID == FIRPhoneAuthProviderID) { @@ -210,11 +210,40 @@ - (void)signInWithProviderUI:(id)providerUI NSDictionary *userInfo = @{ FUIAuthCredentialKey : newCredential, }; - mergeError = [NSError errorWithDomain:FUIAuthErrorDomain - code:FUIAuthErrorCodeMergeConflict - userInfo:userInfo]; - result(nil, mergeError); + NSError *mergeError = [NSError errorWithDomain:FUIAuthErrorDomain + code:FUIAuthErrorCodeMergeConflict + userInfo:userInfo]; completeSignInBlock(authResult, mergeError); + } else if (error.code == FIRAuthErrorCodeEmailAlreadyInUse) { + if ([providerUI respondsToSelector:@selector(email)]) { + // Link federated providers + [self signInWithEmailHint:[providerUI email] + presentingViewController:presentingViewController + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + if (error) { + completeSignInBlock(nil, error); + return; + } + [authResult.user linkAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult + *_Nullable authResult, + NSError *_Nullable error) { + if (error) { + completeSignInBlock(nil, error); + return; + } + FIRAuthCredential *newCredential = credential; + NSDictionary *userInfo = @{ + FUIAuthCredentialKey : newCredential, + }; + NSError *mergeError = [NSError errorWithDomain:FUIAuthErrorDomain + code:FUIAuthErrorCodeMergeConflict + userInfo:userInfo]; + completeSignInBlock(authResult, mergeError); + }]; + }]; + } } else { if (!isAuthPickerShown || error.code != FUIAuthErrorCodeUserCancelledSignIn) { [self invokeResultCallbackWithAuthDataResult:nil error:error]; @@ -250,6 +279,60 @@ - (void)signInWithProviderUI:(id)providerUI }]; } +- (void)signInWithEmailHint:(NSString *)emailHint + presentingViewController:(UIViewController *)presentingViewController + completion:(FIRAuthDataResultCallback)completion { + NSString *kTempApp = @"tempApp"; + FIROptions *options = [FIROptions defaultOptions]; + // Create an new app instance in order to create a new auth instance. + if (![FIRApp appNamed:kTempApp]) { + [FIRApp configureWithName:kTempApp options:options]; + } + FIRApp *tempApp = [FIRApp appNamed:kTempApp]; + // Create a new auth instance in order to perform a successful sign-in without losing the + // currently signed in user on the default auth instance. + FIRAuth *auth = [FIRAuth authWithApp:tempApp]; + + [self.auth fetchProvidersForEmail:emailHint completion:^(NSArray *_Nullable providers, + NSError *_Nullable error) { + NSString *federatedProviderID = [self federatedAuthProviderFromProviders:providers]; + // Google case + if ([federatedProviderID isEqualToString:FIRGoogleAuthProviderID]) { + id googleProviderUI; + for (id provider in self.providers) { + if ([provider.providerID isEqualToString:FIRGoogleAuthProviderID]) { + googleProviderUI = provider; + break; + } + } + [googleProviderUI signOut]; + [googleProviderUI signInWithDefaultValue:emailHint + presentingViewController:presentingViewController + completion:^(FIRAuthCredential *_Nullable credential, + NSError *_Nullable error, + FIRAuthResultCallback _Nullable result) { + if (error) { + completion(nil, error); + return; + } + + [auth signInAndRetrieveDataWithCredential:credential completion:completion]; + }]; + } + }]; +} + +- (nullable NSString *)federatedAuthProviderFromProviders:(NSArray *) providers { + NSSet *providerSet = + [NSSet setWithArray:@[ FIRFacebookAuthProviderID, FIRGoogleAuthProviderID ]]; + for (NSString *provider in providers) { + if ( [providerSet containsObject:provider]) { + return provider; + } + } + return nil; +} + - (void)handleAccountLinkingForEmail:(NSString *)email newCredential:(FIRAuthCredential *)newCredential presentingViewController:(UIViewController *)presentingViewController diff --git a/FirebaseAuthUI/FUIAuthProvider.h b/FirebaseAuthUI/FUIAuthProvider.h index f992730b7b7..493a55f67ee 100644 --- a/FirebaseAuthUI/FUIAuthProvider.h +++ b/FirebaseAuthUI/FUIAuthProvider.h @@ -124,10 +124,15 @@ __attribute__((deprecated("This is deprecated API and will be removed in a futur @optional; /** @property idToken - @brief User Id Token obtained during sign in. Not all providers can return, thus it's optional + @brief User Id Token obtained during sign in. Not all providers can return, thus it's optional. */ @property(nonatomic, copy, readonly) NSString *idToken; +/** @fn email + @brief The email address associated with this provider, if any. + */ +- (NSString *)email; + /** @fn handleOpenURL: @brief May be used to help complete a sign-in flow which requires a callback from Safari. @param URL The URL which may be handled by the auth provider if an URL is expected. diff --git a/FirebaseFacebookAuthUI/FUIFacebookAuth.m b/FirebaseFacebookAuthUI/FUIFacebookAuth.m index 3d75d6b3ad8..86dd5b1deee 100644 --- a/FirebaseFacebookAuthUI/FUIFacebookAuth.m +++ b/FirebaseFacebookAuthUI/FUIFacebookAuth.m @@ -61,6 +61,11 @@ @implementation FUIFacebookAuth { @brief The presenting view controller for interactive sign-in. */ UIViewController *_presentingViewController; + + /** @var _email + @brief The email address associated with this account. + */ + NSString *_email; } - (instancetype)initWithPermissions:(NSArray *)permissions { @@ -141,12 +146,22 @@ - (void)signInWithDefaultValue:(nullable NSString *)defaultValue NSError *newError = [FUIAuthErrorUtils userCancelledSignInError]; [self completeSignInFlowWithAccessToken:nil error:newError]; } else { + // Retrieve email. + [[[FBSDKGraphRequest alloc] initWithGraphPath:@"me" parameters:@{ @"fields" : @"email" }] + startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, + NSError *error) { + self->_email = result[@"email"]; + }]; [self completeSignInFlowWithAccessToken:result.token.tokenString error:nil]; } }]; } +- (NSString *)email { + return _email; +} + - (void)signOut { [_loginManager logOut]; }