Skip to content

Anonymous user upgrade federated #401

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
6 changes: 6 additions & 0 deletions FirebaseAuthUI/FUIAuth.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ __attribute__((deprecated("Instead use authUI:didSignInWithAuthDataResult:error:
*/
@property(nonatomic, copy, nullable) NSURL *TOSURL;

/** @property shouldAutoUpgradeAnonymousUsers
@brief Whether to enable auto upgrading of anonymous accounts, defaults to NO.
*/
@property(nonatomic, assign, getter=shouldAutoUpgradeAnonymousUsers) BOOL
shouldAutoUpgradeAnonymousUsers;

/** @property delegate
@brief A delegate that receives callbacks or provides custom UI for @c FUIAuth.
*/
Expand Down
95 changes: 68 additions & 27 deletions FirebaseAuthUI/FUIAuth.m
Original file line number Diff line number Diff line change
Expand Up @@ -174,37 +174,78 @@ - (void)signInWithProviderUI:(id<FUIAuthProvider>)providerUI
return;
}

[self.auth signInAndRetrieveDataWithCredential:credential
completion:^(FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error) {
if (error && error.code == FIRAuthErrorCodeAccountExistsWithDifferentCredential) {
NSString *email = error.userInfo[kErrorUserInfoEmailKey];
[self handleAccountLinkingForEmail:email
newCredential:credential
presentingViewController:presentingViewController
singInResult:result];
return;
// Block to complete sign-in
void (^completeSignInBlock)(FIRAuthDataResult *, NSError *) = ^(FIRAuthDataResult *authResult,
NSError *error) {
if (result) {
result(authResult.user, nil);
}

if (error) {
if (result) {
result(nil, error);
}
[self invokeResultCallbackWithAuthDataResult:nil error:error];
// Hide Auth Picker Controller which was presented modally.
if (isAuthPickerShown && presentingViewController.presentingViewController) {
[presentingViewController dismissViewControllerAnimated:YES completion:^{
[self invokeResultCallbackWithAuthDataResult:authResult error:error];
}];
} else {
if (result) {
result(authResult.user, nil);
[self invokeResultCallbackWithAuthDataResult:authResult error:error];
}
};

// Check for the presence of an anonymous user and whether automatic upgrade is enabled.
if (_auth.currentUser.isAnonymous && [FUIAuth defaultAuthUI].shouldAutoUpgradeAnonymousUsers) {
[_auth.currentUser
linkAndRetrieveDataWithCredential:credential
completion:^(FIRAuthDataResult *_Nullable authResult,
NSError * _Nullable error) {
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) {
// Obtain temporary Phone Auth provider.
newCredential = error.userInfo[FIRAuthUpdatedCredentialKey];
}
NSDictionary *userInfo = @{
FUIAuthCredentialKey : newCredential,
};
mergeError = [NSError errorWithDomain:FUIAuthErrorDomain
code:FUIAuthErrorCodeMergeConflict
userInfo:userInfo];
result(nil, mergeError);
completeSignInBlock(authResult, mergeError);
} else {
if (!isAuthPickerShown || error.code != FUIAuthErrorCodeUserCancelledSignIn) {
[self invokeResultCallbackWithAuthDataResult:nil error:error];
}
if (result) {
result(nil, error);
}
}
}
// Hide Auth Picker Controller which was presented modally.
if (isAuthPickerShown && presentingViewController.presentingViewController) {
[presentingViewController dismissViewControllerAnimated:YES completion:^{
[self invokeResultCallbackWithAuthDataResult:authResult error:nil];
}];
} else {
[self invokeResultCallbackWithAuthDataResult:authResult error:nil];
}];
} else {
[self.auth signInAndRetrieveDataWithCredential:credential
completion:^(FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error) {
if (error && error.code == FIRAuthErrorCodeAccountExistsWithDifferentCredential) {
NSString *email = error.userInfo[kErrorUserInfoEmailKey];
[self handleAccountLinkingForEmail:email
newCredential:credential
presentingViewController:presentingViewController
singInResult:result];
return;
}
}
}];
if (error) {
if (result) {
result(nil, error);
}
[self invokeResultCallbackWithAuthDataResult:nil error:error];
return;
}
completeSignInBlock(authResult, nil);
}];
}
}];
}

Expand Down
13 changes: 13 additions & 0 deletions FirebaseAuthUI/FUIAuthErrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ extern NSString *const FUIAuthErrorDomain;
*/
extern NSString *const FUIAuthErrorUserInfoProviderIDKey;

/** @bar FUIAuthCredentialKey
@brief The key used to obtain the credential stored within the userInfo dictionary of the
error, if availalble.
*/
extern NSString *const FUIAuthCredentialKey;

/** @var FUIAuthErrorCode
@brief Error codes used by FUIAuth.
*/
Expand All @@ -50,6 +56,13 @@ typedef NS_ENUM(NSUInteger, FUIAuthErrorCode) {
key @c FUIAuthErrorUserInfoProviderIDKey).
*/
FUIAuthErrorCodeCantFindProvider = 3,

/** @var FUIAuthErrorCodeMergeConflict
@brief Indicates that a merge conflict occurred while trying to automatically upgrade an
anonymous user. The non-anonymous credential can be obtained from the userInfo dictionary
of the corresponding NSError using the @c FUIAuthCredentialKey.
*/
FUIAuthErrorCodeMergeConflict = 4,
};

NS_ASSUME_NONNULL_END
2 changes: 2 additions & 0 deletions FirebaseAuthUI/FUIAuthErrors.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@
NSString *const FUIAuthErrorDomain = @"FUIAuthErrorDomain";

NSString *const FUIAuthErrorUserInfoProviderIDKey = @"FUIAuthErrorUserInfoProviderIDKey";

NSString *const FUIAuthCredentialKey = @"FUIAuthCredentialKey";
4 changes: 3 additions & 1 deletion FirebasePhoneAuthUI/FUIPhoneVerificationViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@ - (void)onNext:(NSString *)verificationCode {
result:^(FIRUser *_Nullable user, NSError *_Nullable error) {
[self decrementActivity];
self.navigationItem.rightBarButtonItem.enabled = YES;
if (!error || error.code == FUIAuthErrorCodeUserCancelledSignIn) {
if (!error ||
error.code == FUIAuthErrorCodeUserCancelledSignIn ||
error.code == FUIAuthErrorCodeMergeConflict) {
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
} else {
UIAlertController *alertController = [FUIPhoneAuth alertControllerForError:error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,18 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == kSectionsAnonymousSignIn && indexPath.row == 0) {
if (_authUI.auth.currentUser.isAnonymous) {
[self showAlertWithTitlte:@"" message:@"Already signed in anonymously"];
FIRUser *currentUser = self.authUI.auth.currentUser;
if (currentUser.isAnonymous) {
// If the user is anonymous, delete the user to avoid dangling anonymous users.
if (currentUser.isAnonymous) {
[currentUser deleteWithCompletion:^(NSError * _Nullable error) {
if (error) {
[self showAlertWithTitlte:@"Error" message:error.localizedDescription];
return;
}
[self showAlertWithTitlte:@"" message:@"Anonymous user deleted"];
}];
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
return;
}
Expand Down Expand Up @@ -179,12 +189,15 @@ - (void)updateUI:(FIRAuth * _Nonnull) auth withUser:(FIRUser *_Nullable) user {
self.cellUID.textLabel.text = user.uid;

// If the user is anonymous, delete the user to avoid dangling anonymous users.
if (self.authUI.auth.currentUser.isAnonymous) {
self.buttonAuthorization.title = @"Delete Anonymous User";
} else {
if (auth.currentUser.isAnonymous) {
[_anonymousSignIn.textLabel setText:@"Delete Anonymous User"];
}
else {
[_anonymousSignIn.textLabel setText:@"Sign In Anonymously"];
self.buttonAuthorization.title = @"Sign Out";
}
} else {
[_anonymousSignIn.textLabel setText:@"Sign In Anonymously"];
self.cellSignIn.textLabel.text = @"Not signed-in";
self.cellName.textLabel.text = @"";
self.cellEmail.textLabel.text = @"";
Expand Down Expand Up @@ -215,8 +228,8 @@ - (IBAction)onAuthUIDelegateChanged:(UISwitch *)sender {
}

- (IBAction)onAuthorization:(id)sender {
if (!self.auth.currentUser) {

if (!_auth.currentUser || _auth.currentUser.isAnonymous) {
FUIAuth.defaultAuthUI.shouldAutoUpgradeAnonymousUsers = YES;
_authUI.providers = [self getListOfIDPs];
_authUI.signInWithEmailHidden = ![self isEmailEnabled];

Expand Down Expand Up @@ -247,13 +260,31 @@ - (void)authUI:(FUIAuth *)authUI
if (error) {
if (error.code == FUIAuthErrorCodeUserCancelledSignIn) {
[self showAlertWithTitlte:@"Error" message:error.localizedDescription];
} else {
NSError *detailedError = error.userInfo[NSUnderlyingErrorKey];
if (!detailedError) {
detailedError = error;
}
NSLog(@"ERROR: %@", detailedError.localizedDescription);
return;
}
if (error.code == FUIAuthErrorCodeMergeConflict) {
FIRAuthCredential *credential = error.userInfo[FUIAuthCredentialKey];
NSString *anonymousUserID = authUI.auth.currentUser.uid;
NSString *messsage = [NSString stringWithFormat:@"A merge conflict occurred. The old user ID "
"was: %@. You are now signed in with the following credential type: %@", anonymousUserID,
[credential.provider uppercaseString]];
[self showAlertWithTitlte:@"Merge Conflict" message:messsage];
NSLog(@"%@", messsage);
[[FUIAuth defaultAuthUI].auth
signInAndRetrieveDataWithCredential:credential
completion:^(FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error) {
if (error) {
NSLog(@"%@",error.description);
}
}];
return;
}
NSError *detailedError = error.userInfo[NSUnderlyingErrorKey];
if (!detailedError) {
detailedError = error;
}
NSLog(@"ERROR: %@", detailedError.localizedDescription);
}
}

Expand All @@ -279,21 +310,9 @@ - (NSString *)getAllIdTokens {

- (void)signOut {
NSError *error;
FIRUser *currentUser = self.authUI.auth.currentUser;
// If the user is anonymous, delete the user to avoid dangling anonymous users.
if (currentUser.isAnonymous) {
[currentUser deleteWithCompletion:^(NSError * _Nullable error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we no longer need to delete anonymous users?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do, I just moved the code to the tableview cell. The reason is that I wanted to use the sign in button to trigger a sign-in when the current user was anonymous. Essentially I am separating anonymous sign in/out with "normal" sign in/out.

if (error) {
[self showAlertWithTitlte:@"Error" message:error.localizedDescription];
return;
}
[self showAlertWithTitlte:@"" message:@"Anonymous user deleted"];
}];
} else {
[self.authUI signOutWithError:&error];
if (error) {
[self showAlertWithTitlte:@"Error" message:error.localizedDescription];
}
[self.authUI signOutWithError:&error];
if (error) {
[self showAlertWithTitlte:@"Error" message:error.localizedDescription];
}
}

Expand Down