From 75b8a5591c3a7a9c483bdb1365a05bf337f6f72c Mon Sep 17 00:00:00 2001 From: Zsika Phillip Date: Mon, 29 Jan 2018 16:44:38 -0800 Subject: [PATCH 1/5] Add anonymous sign in upgrade --- FirebaseAuthUI/FUIAuth.h | 5 ++ FirebaseAuthUI/FUIAuth.m | 85 +++++++++++++------ FirebaseAuthUI/FUIAuthErrors.h | 13 +++ FirebaseAuthUI/FUIAuthErrors.m | 2 + .../FUIPhoneVerificationViewController.m | 4 +- .../Samples/Auth/FUIAuthViewController.m | 75 ++++++++++------ 6 files changed, 129 insertions(+), 55 deletions(-) diff --git a/FirebaseAuthUI/FUIAuth.h b/FirebaseAuthUI/FUIAuth.h index 8fa95b3ff2a..f7097015814 100644 --- a/FirebaseAuthUI/FUIAuth.h +++ b/FirebaseAuthUI/FUIAuth.h @@ -190,6 +190,11 @@ __attribute__((deprecated("Instead use authUI:didSignInWithAuthDataResult:error: */ @property(nonatomic, copy, nullable) NSURL *TOSURL; +/** @property autoUpgradeAnonymousUsers + @brief Whether to enable auto upgrading of anonymous accounts, defaults to NO. + */ +@property(nonatomic, assign, getter=isAutoUpgradeAnonymousUsers) BOOL autoUpgradeAnonymousUsers; + /** @property delegate @brief A delegate that receives callbacks or provides custom UI for @c FUIAuth. */ diff --git a/FirebaseAuthUI/FUIAuth.m b/FirebaseAuthUI/FUIAuth.m index af789c66ec5..e68f8e0f5c3 100644 --- a/FirebaseAuthUI/FUIAuth.m +++ b/FirebaseAuthUI/FUIAuth.m @@ -174,37 +174,70 @@ - (void)signInWithProviderUI:(id)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 *) = ^(FIRAuthDataResult *authResult){ + 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:nil]; + }]; } else { - if (result) { - result(authResult.user, nil); + [self invokeResultCallbackWithAuthDataResult:authResult error:nil]; + } + }; + + // Check for the presence of an anonymous user and whether automatic upgrade is enabled. + if (_auth.currentUser.isAnonymous && [FUIAuth defaultAuthUI].autoUpgradeAnonymousUsers) { + [_auth.currentUser + linkAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError * _Nullable error) { + if (error) { + // Handle error cases + NSError *mergeError; + FIRAuthCredential *newCredential = credential; + if (error.code == FIRAuthErrorCodeCredentialAlreadyInUse) { + if (providerUI.providerID == FIRPhoneAuthProviderID) { + newCredential = error.userInfo[FIRAuthUpdatedCredentialKey]; + } + NSDictionary *userInfo = @{ + FUIAuthCredentialKey : newCredential, + }; + mergeError = [NSError errorWithDomain:FUIAuthErrorDomain + code:FUIAuthErrorCodeMergeConflict + userInfo:userInfo]; + result(nil, mergeError); + [self invokeResultCallbackWithAuthDataResult:nil error:mergeError]; + } } - // Hide Auth Picker Controller which was presented modally. - if (isAuthPickerShown && presentingViewController.presentingViewController) { - [presentingViewController dismissViewControllerAnimated:YES completion:^{ - [self invokeResultCallbackWithAuthDataResult:authResult error:nil]; - }]; + completeSignInBlock(authResult); + }]; + } 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]; } else { - [self invokeResultCallbackWithAuthDataResult:authResult error:nil]; + completeSignInBlock(authResult); } - } - }]; + }]; + } }]; } diff --git a/FirebaseAuthUI/FUIAuthErrors.h b/FirebaseAuthUI/FUIAuthErrors.h index bee0bb225a5..87cfccf6e0d 100644 --- a/FirebaseAuthUI/FUIAuthErrors.h +++ b/FirebaseAuthUI/FUIAuthErrors.h @@ -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. */ @@ -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 diff --git a/FirebaseAuthUI/FUIAuthErrors.m b/FirebaseAuthUI/FUIAuthErrors.m index 90cc468826a..dc7df99a40d 100644 --- a/FirebaseAuthUI/FUIAuthErrors.m +++ b/FirebaseAuthUI/FUIAuthErrors.m @@ -19,3 +19,5 @@ NSString *const FUIAuthErrorDomain = @"FUIAuthErrorDomain"; NSString *const FUIAuthErrorUserInfoProviderIDKey = @"FUIAuthErrorUserInfoProviderIDKey"; + +NSString *const FUIAuthCredentialKey = @"FUIAuthCredentialKey"; diff --git a/FirebasePhoneAuthUI/FUIPhoneVerificationViewController.m b/FirebasePhoneAuthUI/FUIPhoneVerificationViewController.m index 2da2deb485d..8d370e6cee5 100644 --- a/FirebasePhoneAuthUI/FUIPhoneVerificationViewController.m +++ b/FirebasePhoneAuthUI/FUIPhoneVerificationViewController.m @@ -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 diff --git a/samples/objc/FirebaseUI-demo-objc/Samples/Auth/FUIAuthViewController.m b/samples/objc/FirebaseUI-demo-objc/Samples/Auth/FUIAuthViewController.m index db90e18dc8f..0e8a83fc25a 100644 --- a/samples/objc/FirebaseUI-demo-objc/Samples/Auth/FUIAuthViewController.m +++ b/samples/objc/FirebaseUI-demo-objc/Samples/Auth/FUIAuthViewController.m @@ -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; } @@ -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 = @""; @@ -215,8 +228,8 @@ - (IBAction)onAuthUIDelegateChanged:(UISwitch *)sender { } - (IBAction)onAuthorization:(id)sender { - if (!self.auth.currentUser) { - + if (!_auth.currentUser || _auth.currentUser.isAnonymous) { + FUIAuth.defaultAuthUI.autoUpgradeAnonymousUsers = YES; _authUI.providers = [self getListOfIDPs]; _authUI.signInWithEmailHidden = ![self isEmailEnabled]; @@ -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 sig 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); } } @@ -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) { - 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]; } } From d203dda67ec903dcf985dca64c2a72a7428ee6c4 Mon Sep 17 00:00:00 2001 From: Zsika Phillip Date: Tue, 30 Jan 2018 14:32:55 -0800 Subject: [PATCH 2/5] Returns merge error in the correct place. --- FirebaseAuthUI/FUIAuth.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/FirebaseAuthUI/FUIAuth.m b/FirebaseAuthUI/FUIAuth.m index e68f8e0f5c3..5a1743273cf 100644 --- a/FirebaseAuthUI/FUIAuth.m +++ b/FirebaseAuthUI/FUIAuth.m @@ -175,17 +175,18 @@ - (void)signInWithProviderUI:(id)providerUI } // Block to complete sign-in - void (^completeSignInBlock)(FIRAuthDataResult *) = ^(FIRAuthDataResult *authResult){ + void (^completeSignInBlock)(FIRAuthDataResult *, NSError *) = ^(FIRAuthDataResult *authResult, + NSError *error) { if (result) { result(authResult.user, nil); } // Hide Auth Picker Controller which was presented modally. if (isAuthPickerShown && presentingViewController.presentingViewController) { [presentingViewController dismissViewControllerAnimated:YES completion:^{ - [self invokeResultCallbackWithAuthDataResult:authResult error:nil]; + [self invokeResultCallbackWithAuthDataResult:authResult error:error]; }]; } else { - [self invokeResultCallbackWithAuthDataResult:authResult error:nil]; + [self invokeResultCallbackWithAuthDataResult:authResult error:error]; } }; @@ -210,10 +211,9 @@ - (void)signInWithProviderUI:(id)providerUI code:FUIAuthErrorCodeMergeConflict userInfo:userInfo]; result(nil, mergeError); - [self invokeResultCallbackWithAuthDataResult:nil error:mergeError]; + completeSignInBlock(authResult, mergeError); } } - completeSignInBlock(authResult); }]; } else { [self.auth signInAndRetrieveDataWithCredential:credential @@ -234,7 +234,7 @@ - (void)signInWithProviderUI:(id)providerUI } [self invokeResultCallbackWithAuthDataResult:nil error:error]; } else { - completeSignInBlock(authResult); + completeSignInBlock(authResult, nil); } }]; } From fcd48542a0636d483bd596728cf87f5730a5de4a Mon Sep 17 00:00:00 2001 From: Zsika Phillip Date: Wed, 31 Jan 2018 14:38:28 -0800 Subject: [PATCH 3/5] Adds comments and minor changes --- FirebaseAuthUI/FUIAuth.m | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/FirebaseAuthUI/FUIAuth.m b/FirebaseAuthUI/FUIAuth.m index 5a1743273cf..03d97d3ca13 100644 --- a/FirebaseAuthUI/FUIAuth.m +++ b/FirebaseAuthUI/FUIAuth.m @@ -197,11 +197,13 @@ - (void)signInWithProviderUI:(id)providerUI completion:^(FIRAuthDataResult *_Nullable authResult, NSError * _Nullable error) { if (error) { - // Handle error cases - NSError *mergeError; - FIRAuthCredential *newCredential = credential; + // 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 = @{ @@ -233,9 +235,9 @@ - (void)signInWithProviderUI:(id)providerUI result(nil, error); } [self invokeResultCallbackWithAuthDataResult:nil error:error]; - } else { - completeSignInBlock(authResult, nil); + return; } + completeSignInBlock(authResult, nil); }]; } }]; From 9140cb0fc7eb9c84db7faff8639cb4d273e7621d Mon Sep 17 00:00:00 2001 From: Zsika Phillip Date: Wed, 31 Jan 2018 17:26:27 -0800 Subject: [PATCH 4/5] Adds correct error handling for linking errors --- FirebaseAuthUI/FUIAuth.m | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/FirebaseAuthUI/FUIAuth.m b/FirebaseAuthUI/FUIAuth.m index 03d97d3ca13..2f6aa1d1d3f 100644 --- a/FirebaseAuthUI/FUIAuth.m +++ b/FirebaseAuthUI/FUIAuth.m @@ -212,8 +212,15 @@ - (void)signInWithProviderUI:(id)providerUI mergeError = [NSError errorWithDomain:FUIAuthErrorDomain code:FUIAuthErrorCodeMergeConflict userInfo:userInfo]; - result(nil, mergeError); - completeSignInBlock(authResult, mergeError); + result(nil, error); + completeSignInBlock(authResult, error); + } else { + if (!isAuthPickerShown || error.code != FUIAuthErrorCodeUserCancelledSignIn) { + [self invokeResultCallbackWithAuthDataResult:nil error:error]; + } + if (result) { + result(nil, error); + } } } }]; @@ -229,7 +236,6 @@ - (void)signInWithProviderUI:(id)providerUI singInResult:result]; return; } - if (error) { if (result) { result(nil, error); From ba5e70845047af40d07cd73472251db8806c2cd3 Mon Sep 17 00:00:00 2001 From: Zsika Phillip Date: Thu, 1 Feb 2018 19:58:17 -0800 Subject: [PATCH 5/5] Addresses comments Addresses comments Minor enhances Fixes bug where wrong error is returned --- FirebaseAuthUI/FUIAuth.h | 5 +++-- FirebaseAuthUI/FUIAuth.m | 6 +++--- .../Samples/Auth/FUIAuthViewController.m | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/FirebaseAuthUI/FUIAuth.h b/FirebaseAuthUI/FUIAuth.h index f7097015814..80e6f01f298 100644 --- a/FirebaseAuthUI/FUIAuth.h +++ b/FirebaseAuthUI/FUIAuth.h @@ -190,10 +190,11 @@ __attribute__((deprecated("Instead use authUI:didSignInWithAuthDataResult:error: */ @property(nonatomic, copy, nullable) NSURL *TOSURL; -/** @property autoUpgradeAnonymousUsers +/** @property shouldAutoUpgradeAnonymousUsers @brief Whether to enable auto upgrading of anonymous accounts, defaults to NO. */ -@property(nonatomic, assign, getter=isAutoUpgradeAnonymousUsers) BOOL autoUpgradeAnonymousUsers; +@property(nonatomic, assign, getter=shouldAutoUpgradeAnonymousUsers) BOOL + shouldAutoUpgradeAnonymousUsers; /** @property delegate @brief A delegate that receives callbacks or provides custom UI for @c FUIAuth. diff --git a/FirebaseAuthUI/FUIAuth.m b/FirebaseAuthUI/FUIAuth.m index 2f6aa1d1d3f..63245d1e77a 100644 --- a/FirebaseAuthUI/FUIAuth.m +++ b/FirebaseAuthUI/FUIAuth.m @@ -191,7 +191,7 @@ - (void)signInWithProviderUI:(id)providerUI }; // Check for the presence of an anonymous user and whether automatic upgrade is enabled. - if (_auth.currentUser.isAnonymous && [FUIAuth defaultAuthUI].autoUpgradeAnonymousUsers) { + if (_auth.currentUser.isAnonymous && [FUIAuth defaultAuthUI].shouldAutoUpgradeAnonymousUsers) { [_auth.currentUser linkAndRetrieveDataWithCredential:credential completion:^(FIRAuthDataResult *_Nullable authResult, @@ -212,8 +212,8 @@ - (void)signInWithProviderUI:(id)providerUI mergeError = [NSError errorWithDomain:FUIAuthErrorDomain code:FUIAuthErrorCodeMergeConflict userInfo:userInfo]; - result(nil, error); - completeSignInBlock(authResult, error); + result(nil, mergeError); + completeSignInBlock(authResult, mergeError); } else { if (!isAuthPickerShown || error.code != FUIAuthErrorCodeUserCancelledSignIn) { [self invokeResultCallbackWithAuthDataResult:nil error:error]; diff --git a/samples/objc/FirebaseUI-demo-objc/Samples/Auth/FUIAuthViewController.m b/samples/objc/FirebaseUI-demo-objc/Samples/Auth/FUIAuthViewController.m index 0e8a83fc25a..13a05264ee6 100644 --- a/samples/objc/FirebaseUI-demo-objc/Samples/Auth/FUIAuthViewController.m +++ b/samples/objc/FirebaseUI-demo-objc/Samples/Auth/FUIAuthViewController.m @@ -229,7 +229,7 @@ - (IBAction)onAuthUIDelegateChanged:(UISwitch *)sender { - (IBAction)onAuthorization:(id)sender { if (!_auth.currentUser || _auth.currentUser.isAnonymous) { - FUIAuth.defaultAuthUI.autoUpgradeAnonymousUsers = YES; + FUIAuth.defaultAuthUI.shouldAutoUpgradeAnonymousUsers = YES; _authUI.providers = [self getListOfIDPs]; _authUI.signInWithEmailHidden = ![self isEmailEnabled]; @@ -266,7 +266,7 @@ - (void)authUI:(FUIAuth *)authUI 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 sig in with the following credential type: %@", anonymousUserID, + "was: %@. You are now signed in with the following credential type: %@", anonymousUserID, [credential.provider uppercaseString]]; [self showAlertWithTitlte:@"Merge Conflict" message:messsage]; NSLog(@"%@", messsage);