Skip to content

Commit f4d5b24

Browse files
committed
Anonymous user upgrade federated (#401)
* Add anonymous sign in upgrade * Returns merge error in the correct place. * Adds comments and minor changes * Adds correct error handling for linking errors * Addresses comments Addresses comments Minor enhances Fixes bug where wrong error is returned
1 parent ece22d6 commit f4d5b24

File tree

6 files changed

+143
-60
lines changed

6 files changed

+143
-60
lines changed

FirebaseAuthUI/FUIAuth.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ __attribute__((deprecated("Instead use authUI:didSignInWithAuthDataResult:error:
201201
*/
202202
@property(nonatomic, copy, nullable) NSURL *TOSURL;
203203

204+
/** @property shouldAutoUpgradeAnonymousUsers
205+
@brief Whether to enable auto upgrading of anonymous accounts, defaults to NO.
206+
*/
207+
@property(nonatomic, assign, getter=shouldAutoUpgradeAnonymousUsers) BOOL
208+
shouldAutoUpgradeAnonymousUsers;
209+
204210
/** @property privacyPolicyURL
205211
@brief The URL of your app's Privacy Policy. If not nil, a privacy policy notice is
206212
displayed on the initial sign-in screen and potentially the phone number auth and

FirebaseAuthUI/FUIAuth.m

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,10 @@ - (void)signInWithProviderUI:(id<FUIAuthProvider>)providerUI
169169
// Sign out first to make sure sign in starts with a clean state.
170170
[providerUI signOut];
171171
[providerUI signInWithDefaultValue:defaultValue
172-
presentingViewController:presentingViewController
173-
completion:^(FIRAuthCredential *_Nullable credential,
174-
NSError *_Nullable error,
175-
_Nullable FIRAuthResultCallback result) {
172+
presentingViewController:presentingViewController
173+
completion:^(FIRAuthCredential *_Nullable credential,
174+
NSError *_Nullable error,
175+
_Nullable FIRAuthResultCallback result) {
176176
BOOL isAuthPickerShown =
177177
[presentingViewController isKindOfClass:[FUIAuthPickerViewController class]];
178178
if (error) {
@@ -185,37 +185,78 @@ - (void)signInWithProviderUI:(id<FUIAuthProvider>)providerUI
185185
return;
186186
}
187187

188-
[self.auth signInAndRetrieveDataWithCredential:credential
189-
completion:^(FIRAuthDataResult *_Nullable authResult,
190-
NSError *_Nullable error) {
191-
if (error.code == FIRAuthErrorCodeAccountExistsWithDifferentCredential) {
192-
NSString *email = error.userInfo[kErrorUserInfoEmailKey];
193-
[self handleAccountLinkingForEmail:email
194-
newCredential:credential
195-
presentingViewController:presentingViewController
196-
signInResult:result];
197-
return;
188+
// Block to complete sign-in
189+
void (^completeSignInBlock)(FIRAuthDataResult *, NSError *) = ^(FIRAuthDataResult *authResult,
190+
NSError *error) {
191+
if (result) {
192+
result(authResult.user, nil);
198193
}
199-
200-
if (error) {
201-
if (result) {
202-
result(nil, error);
203-
}
204-
[self invokeResultCallbackWithAuthDataResult:nil error:error];
194+
// Hide Auth Picker Controller which was presented modally.
195+
if (isAuthPickerShown && presentingViewController.presentingViewController) {
196+
[presentingViewController dismissViewControllerAnimated:YES completion:^{
197+
[self invokeResultCallbackWithAuthDataResult:authResult error:error];
198+
}];
205199
} else {
206-
if (result) {
207-
result(authResult.user, nil);
200+
[self invokeResultCallbackWithAuthDataResult:authResult error:error];
201+
}
202+
};
203+
204+
// Check for the presence of an anonymous user and whether automatic upgrade is enabled.
205+
if (_auth.currentUser.isAnonymous && [FUIAuth defaultAuthUI].shouldAutoUpgradeAnonymousUsers) {
206+
[_auth.currentUser
207+
linkAndRetrieveDataWithCredential:credential
208+
completion:^(FIRAuthDataResult *_Nullable authResult,
209+
NSError * _Nullable error) {
210+
if (error) {
211+
// Check for "credential in use" conflict error and handle appropriately.
212+
if (error.code == FIRAuthErrorCodeCredentialAlreadyInUse) {
213+
NSError *mergeError;
214+
FIRAuthCredential *newCredential = credential;
215+
// Check for and handle special case for Phone Auth Provider.
216+
if (providerUI.providerID == FIRPhoneAuthProviderID) {
217+
// Obtain temporary Phone Auth provider.
218+
newCredential = error.userInfo[FIRAuthUpdatedCredentialKey];
219+
}
220+
NSDictionary *userInfo = @{
221+
FUIAuthCredentialKey : newCredential,
222+
};
223+
mergeError = [NSError errorWithDomain:FUIAuthErrorDomain
224+
code:FUIAuthErrorCodeMergeConflict
225+
userInfo:userInfo];
226+
result(nil, mergeError);
227+
completeSignInBlock(authResult, mergeError);
228+
} else {
229+
if (!isAuthPickerShown || error.code != FUIAuthErrorCodeUserCancelledSignIn) {
230+
[self invokeResultCallbackWithAuthDataResult:nil error:error];
231+
}
232+
if (result) {
233+
result(nil, error);
234+
}
235+
}
208236
}
209-
// Hide Auth Picker Controller which was presented modally.
210-
if (isAuthPickerShown && presentingViewController.presentingViewController) {
211-
[presentingViewController dismissViewControllerAnimated:YES completion:^{
212-
[self invokeResultCallbackWithAuthDataResult:authResult error:nil];
213-
}];
214-
} else {
215-
[self invokeResultCallbackWithAuthDataResult:authResult error:nil];
237+
}];
238+
} else {
239+
[self.auth signInAndRetrieveDataWithCredential:credential
240+
completion:^(FIRAuthDataResult *_Nullable authResult,
241+
NSError *_Nullable error) {
242+
if (error && error.code == FIRAuthErrorCodeAccountExistsWithDifferentCredential) {
243+
NSString *email = error.userInfo[kErrorUserInfoEmailKey];
244+
[self handleAccountLinkingForEmail:email
245+
newCredential:credential
246+
presentingViewController:presentingViewController
247+
singInResult:result];
248+
return;
216249
}
217-
}
218-
}];
250+
if (error) {
251+
if (result) {
252+
result(nil, error);
253+
}
254+
[self invokeResultCallbackWithAuthDataResult:nil error:error];
255+
return;
256+
}
257+
completeSignInBlock(authResult, nil);
258+
}];
259+
}
219260
}];
220261
}
221262

FirebaseAuthUI/FUIAuthErrors.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ extern NSString *const FUIAuthErrorDomain;
2828
*/
2929
extern NSString *const FUIAuthErrorUserInfoProviderIDKey;
3030

31+
/** @bar FUIAuthCredentialKey
32+
@brief The key used to obtain the credential stored within the userInfo dictionary of the
33+
error, if availalble.
34+
*/
35+
extern NSString *const FUIAuthCredentialKey;
36+
3137
/** @var FUIAuthErrorCode
3238
@brief Error codes used by FUIAuth.
3339
*/
@@ -50,6 +56,13 @@ typedef NS_ENUM(NSUInteger, FUIAuthErrorCode) {
5056
key @c FUIAuthErrorUserInfoProviderIDKey).
5157
*/
5258
FUIAuthErrorCodeCantFindProvider = 3,
59+
60+
/** @var FUIAuthErrorCodeMergeConflict
61+
@brief Indicates that a merge conflict occurred while trying to automatically upgrade an
62+
anonymous user. The non-anonymous credential can be obtained from the userInfo dictionary
63+
of the corresponding NSError using the @c FUIAuthCredentialKey.
64+
*/
65+
FUIAuthErrorCodeMergeConflict = 4,
5366
};
5467

5568
NS_ASSUME_NONNULL_END

FirebaseAuthUI/FUIAuthErrors.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@
1919
NSString *const FUIAuthErrorDomain = @"FUIAuthErrorDomain";
2020

2121
NSString *const FUIAuthErrorUserInfoProviderIDKey = @"FUIAuthErrorUserInfoProviderIDKey";
22+
23+
NSString *const FUIAuthCredentialKey = @"FUIAuthCredentialKey";

FirebasePhoneAuthUI/FUIPhoneVerificationViewController.m

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,9 @@ - (void)onNext:(NSString *)verificationCode {
187187
result:^(FIRUser *_Nullable user, NSError *_Nullable error) {
188188
[self decrementActivity];
189189
self.navigationItem.rightBarButtonItem.enabled = YES;
190-
if (!error || error.code == FUIAuthErrorCodeUserCancelledSignIn) {
190+
if (!error ||
191+
error.code == FUIAuthErrorCodeUserCancelledSignIn ||
192+
error.code == FUIAuthErrorCodeMergeConflict) {
191193
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
192194
} else {
193195
UIAlertController *alertController = [FUIPhoneAuth alertControllerForError:error

samples/objc/FirebaseUI-demo-objc/Samples/Auth/FUIAuthViewController.m

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,18 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa
150150

151151
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
152152
if (indexPath.section == kSectionsAnonymousSignIn && indexPath.row == 0) {
153-
if (_authUI.auth.currentUser.isAnonymous) {
154-
[self showAlertWithTitlte:@"" message:@"Already signed in anonymously"];
153+
FIRUser *currentUser = self.authUI.auth.currentUser;
154+
if (currentUser.isAnonymous) {
155+
// If the user is anonymous, delete the user to avoid dangling anonymous users.
156+
if (currentUser.isAnonymous) {
157+
[currentUser deleteWithCompletion:^(NSError * _Nullable error) {
158+
if (error) {
159+
[self showAlertWithTitlte:@"Error" message:error.localizedDescription];
160+
return;
161+
}
162+
[self showAlertWithTitlte:@"" message:@"Anonymous user deleted"];
163+
}];
164+
}
155165
[tableView deselectRowAtIndexPath:indexPath animated:NO];
156166
return;
157167
}
@@ -181,12 +191,15 @@ - (void)updateUI:(FIRAuth * _Nonnull) auth withUser:(FIRUser *_Nullable) user {
181191
self.cellUID.textLabel.text = user.uid;
182192

183193
// If the user is anonymous, delete the user to avoid dangling anonymous users.
184-
if (self.authUI.auth.currentUser.isAnonymous) {
185-
self.buttonAuthorization.title = @"Delete Anonymous User";
186-
} else {
194+
if (auth.currentUser.isAnonymous) {
195+
[_anonymousSignIn.textLabel setText:@"Delete Anonymous User"];
196+
}
197+
else {
198+
[_anonymousSignIn.textLabel setText:@"Sign In Anonymously"];
187199
self.buttonAuthorization.title = @"Sign Out";
188200
}
189201
} else {
202+
[_anonymousSignIn.textLabel setText:@"Sign In Anonymously"];
190203
self.cellSignIn.textLabel.text = @"Not signed-in";
191204
self.cellName.textLabel.text = @"";
192205
self.cellEmail.textLabel.text = @"";
@@ -217,8 +230,8 @@ - (IBAction)onAuthUIDelegateChanged:(UISwitch *)sender {
217230
}
218231

219232
- (IBAction)onAuthorization:(id)sender {
220-
if (!self.auth.currentUser) {
221-
233+
if (!_auth.currentUser || _auth.currentUser.isAnonymous) {
234+
FUIAuth.defaultAuthUI.shouldAutoUpgradeAnonymousUsers = YES;
222235
_authUI.providers = [self getListOfIDPs];
223236
_authUI.signInWithEmailHidden = ![self isEmailEnabled];
224237

@@ -249,13 +262,31 @@ - (void)authUI:(FUIAuth *)authUI
249262
if (error) {
250263
if (error.code == FUIAuthErrorCodeUserCancelledSignIn) {
251264
[self showAlertWithTitlte:@"Error" message:error.localizedDescription];
252-
} else {
253-
NSError *detailedError = error.userInfo[NSUnderlyingErrorKey];
254-
if (!detailedError) {
255-
detailedError = error;
256-
}
257-
NSLog(@"ERROR: %@", detailedError.localizedDescription);
265+
return;
266+
}
267+
if (error.code == FUIAuthErrorCodeMergeConflict) {
268+
FIRAuthCredential *credential = error.userInfo[FUIAuthCredentialKey];
269+
NSString *anonymousUserID = authUI.auth.currentUser.uid;
270+
NSString *messsage = [NSString stringWithFormat:@"A merge conflict occurred. The old user ID "
271+
"was: %@. You are now signed in with the following credential type: %@", anonymousUserID,
272+
[credential.provider uppercaseString]];
273+
[self showAlertWithTitlte:@"Merge Conflict" message:messsage];
274+
NSLog(@"%@", messsage);
275+
[[FUIAuth defaultAuthUI].auth
276+
signInAndRetrieveDataWithCredential:credential
277+
completion:^(FIRAuthDataResult *_Nullable authResult,
278+
NSError *_Nullable error) {
279+
if (error) {
280+
NSLog(@"%@",error.description);
281+
}
282+
}];
283+
return;
284+
}
285+
NSError *detailedError = error.userInfo[NSUnderlyingErrorKey];
286+
if (!detailedError) {
287+
detailedError = error;
258288
}
289+
NSLog(@"ERROR: %@", detailedError.localizedDescription);
259290
}
260291
}
261292

@@ -281,21 +312,9 @@ - (NSString *)getAllIdTokens {
281312

282313
- (void)signOut {
283314
NSError *error;
284-
FIRUser *currentUser = self.authUI.auth.currentUser;
285-
// If the user is anonymous, delete the user to avoid dangling anonymous users.
286-
if (currentUser.isAnonymous) {
287-
[currentUser deleteWithCompletion:^(NSError * _Nullable error) {
288-
if (error) {
289-
[self showAlertWithTitlte:@"Error" message:error.localizedDescription];
290-
return;
291-
}
292-
[self showAlertWithTitlte:@"" message:@"Anonymous user deleted"];
293-
}];
294-
} else {
295-
[self.authUI signOutWithError:&error];
296-
if (error) {
297-
[self showAlertWithTitlte:@"Error" message:error.localizedDescription];
298-
}
315+
[self.authUI signOutWithError:&error];
316+
if (error) {
317+
[self showAlertWithTitlte:@"Error" message:error.localizedDescription];
299318
}
300319
}
301320

0 commit comments

Comments
 (0)