Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 89 additions & 6 deletions FirebaseAuthUI/FUIAuth.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
#import <objc/runtime.h>

#import <FirebaseCore/FIRApp.h>
#import <FirebaseCore/FIROptions.h>
#import <FirebaseAuth/FIRAuth.h>
#import <FirebaseAuth/FirebaseAuth.h>
#import "FUIAuthBaseViewController_Internal.h"
#import "FUIAuthErrors.h"
#import "FUIAuthPickerViewController.h"
#import "FUIAuthStrings.h"
#import "FUIGoogleAuth.h"
#import "FUIEmailEntryViewController.h"
#import "FUIPasswordVerificationViewController.h"

Expand Down Expand Up @@ -173,7 +175,6 @@ - (void)signInWithProviderUI:(id<FUIAuthProvider>)providerUI
}
return;
}

// Block to complete sign-in
void (^completeSignInBlock)(FIRAuthDataResult *, NSError *) = ^(FIRAuthDataResult *authResult,
NSError *error) {
Expand All @@ -200,7 +201,6 @@ - (void)signInWithProviderUI:(id<FUIAuthProvider>)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) {
Expand All @@ -210,11 +210,40 @@ - (void)signInWithProviderUI:(id<FUIAuthProvider>)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];
Expand Down Expand Up @@ -250,6 +279,60 @@ - (void)signInWithProviderUI:(id<FUIAuthProvider>)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<NSString *> *_Nullable providers,
NSError *_Nullable error) {
NSString *federatedProviderID = [self federatedAuthProviderFromProviders:providers];
// Google case
if ([federatedProviderID isEqualToString:FIRGoogleAuthProviderID]) {
id<FUIAuthProvider> googleProviderUI;
for (id<FUIAuthProvider> 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];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this use a different auth instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we need to complete a sign-in of the existing credential before linking; however we do not want to lose the currently signed in user. A sign-in operation usually replaces the currently signed-in user if successful.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment describing that :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}];
}
}];
}

- (nullable NSString *)federatedAuthProviderFromProviders:(NSArray <NSString *> *) 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
Expand Down
7 changes: 6 additions & 1 deletion FirebaseAuthUI/FUIAuthProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions FirebaseFacebookAuthUI/FUIFacebookAuth.m
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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];
}
Expand Down