Skip to content

Commit a72ad9e

Browse files
authored
Automatic anonymous user upgrade (#481)
Adds anonymous user upgrade through several commits.
1 parent ece22d6 commit a72ad9e

16 files changed

+612
-115
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: 271 additions & 40 deletions
Large diffs are not rendered by default.

FirebaseAuthUI/FUIAuthBaseViewController.m

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,20 +242,61 @@ + (void)showAlertWithTitle:(nullable NSString *)title
242242
[presentingViewController presentViewController:alertController animated:YES completion:nil];
243243
}
244244

245+
+ (void)showAlertWithTitle:(nullable NSString *)title
246+
message:(NSString *)message
247+
actionTitle:(NSString *)actionTitle
248+
presentingViewController:(UIViewController *)presentingViewController
249+
actionHandler:(FUIAuthAlertActionHandler)actionHandler
250+
cancelHandler:(FUIAuthAlertActionHandler)cancelHandler {
251+
UIAlertController *alertController =
252+
[UIAlertController alertControllerWithTitle:title
253+
message:message
254+
preferredStyle:UIAlertControllerStyleAlert];
255+
UIAlertAction *okAction =
256+
[UIAlertAction actionWithTitle:actionTitle
257+
style:UIAlertActionStyleDefault
258+
handler:^(UIAlertAction *_Nonnull action) {
259+
actionHandler();
260+
}];
261+
[alertController addAction:okAction];
262+
UIAlertAction *cancelAction =
263+
[UIAlertAction actionWithTitle:FUILocalizedString(kStr_Cancel)
264+
style:UIAlertActionStyleCancel
265+
handler:^(UIAlertAction * _Nonnull action) {
266+
cancelHandler();
267+
}];
268+
[alertController addAction:cancelAction];
269+
[presentingViewController presentViewController:alertController animated:YES completion:nil];
270+
}
271+
245272
+ (void)showSignInAlertWithEmail:(NSString *)email
246273
provider:(id<FUIAuthProvider>)provider
247274
presentingViewController:(UIViewController *)presentingViewController
248275
signinHandler:(FUIAuthAlertActionHandler)signinHandler
249276
cancelHandler:(FUIAuthAlertActionHandler)cancelHandler {
277+
[self showSignInAlertWithEmail:email
278+
providerShortName:provider.shortName
279+
providerSignInLabel:provider.signInLabel
280+
presentingViewController:presentingViewController
281+
signinHandler:signinHandler
282+
cancelHandler:cancelHandler];
283+
}
284+
285+
+ (void)showSignInAlertWithEmail:(NSString *)email
286+
providerShortName:(NSString *)providerShortName
287+
providerSignInLabel:(NSString *)providerSignInLabel
288+
presentingViewController:(UIViewController *)presentingViewController
289+
signinHandler:(FUIAuthAlertActionHandler)signinHandler
290+
cancelHandler:(FUIAuthAlertActionHandler)cancelHandler {
250291
NSString *message =
251292
[NSString stringWithFormat:FUILocalizedString(kStr_ProviderUsedPreviouslyMessage),
252-
email, provider.shortName];
293+
email, providerShortName];
253294
UIAlertController *alertController =
254295
[UIAlertController alertControllerWithTitle:FUILocalizedString(kStr_ExistingAccountTitle)
255296
message:message
256297
preferredStyle:UIAlertControllerStyleAlert];
257298
UIAlertAction *signInAction =
258-
[UIAlertAction actionWithTitle:provider.signInLabel
299+
[UIAlertAction actionWithTitle:providerSignInLabel
259300
style:UIAlertActionStyleDefault
260301
handler:^(UIAlertAction *_Nonnull action) {
261302
signinHandler();

FirebaseAuthUI/FUIAuthBaseViewController_Internal.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,51 @@ NS_ASSUME_NONNULL_BEGIN
5656
actionTitle:(NSString *)actionTitle
5757
presentingViewController:(UIViewController *)presentingViewController;
5858

59+
/** @fn showAlertWithTitle:message:actionTitle:presentingViewController:
60+
@brief Displays an alert view with given title, message and action title on top of the
61+
specified view controller.
62+
@param title The title of the alert.
63+
@param message The message of the alert.
64+
@param actionTitle The title of the action button.
65+
@param actionHandler The block to execute if the action button is tapped.
66+
@param cancelHandler The block to execute if the cancel button is tapped.
67+
@param presentingViewController The controller which shows alert.
68+
*/
69+
+ (void)showAlertWithTitle:(nullable NSString *)title
70+
message:(NSString *)message
71+
actionTitle:(NSString *)actionTitle
72+
presentingViewController:(UIViewController *)presentingViewController
73+
actionHandler:(FUIAuthAlertActionHandler)actionHandler
74+
cancelHandler:(FUIAuthAlertActionHandler)cancelHandler;
75+
76+
/** @fn showSignInAlertWithEmail:provider:handler:
77+
@brief Displays an alert to conform with user whether she wants to proceed with the provider.
78+
@param email The email address to sign in with.
79+
@param provider The identity provider to sign in with.
80+
@param signinHandler Handler for the sign in action of the alert.
81+
@param cancelHandler Handler for the cancel action of the alert.
82+
*/
83+
+ (void)showSignInAlertWithEmail:(NSString *)email
84+
provider:(id<FUIAuthProvider>)provider
85+
presentingViewController:(UIViewController *)presentingViewController
86+
signinHandler:(FUIAuthAlertActionHandler)signinHandler
87+
cancelHandler:(FUIAuthAlertActionHandler)cancelHandler;
88+
89+
/** @fn showSignInAlertWithEmail:providerShortName:providerSignInLabel:handler:
90+
@brief Displays an alert to conform with user whether she wants to proceed with the provider.
91+
@param email The email address to sign in with.
92+
@param providerShortName The name of the provider as displayed in the sign-in alert message.
93+
@param providerSignInLabel The name of the provider as displayed in the sign-in alert button.
94+
@param signinHandler Handler for the sign in action of the alert.
95+
@param cancelHandler Handler for the cancel action of the alert.
96+
*/
97+
+ (void)showSignInAlertWithEmail:(NSString *)email
98+
providerShortName:(NSString *)providerShortName
99+
providerSignInLabel:(NSString *)providerSignInLabel
100+
presentingViewController:(UIViewController *)presentingViewController
101+
signinHandler:(FUIAuthAlertActionHandler)signinHandler
102+
cancelHandler:(FUIAuthAlertActionHandler)cancelHandler;
103+
59104
/** @fn pushViewController:
60105
@brief Push the view controller to the navigation controller of the current view controller
61106
with animation. The pushed view controller will have a fixed "Back" title for back button.

FirebaseAuthUI/FUIAuthErrorUtils.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ NS_ASSUME_NONNULL_BEGIN
3636
*/
3737
+ (NSError *)userCancelledSignInError;
3838

39+
/** @fn userCancelledSignIn
40+
@brief Constructs an @c NSError with the @c FUIAuthErrorCodeMergeConflict code.
41+
@param userInfo The userInfo dictionary to add to the NSError object.
42+
@return The merge conflict error.
43+
*/
44+
+ (NSError *)mergeConflictErrorWithUserInfo:(NSDictionary *)userInfo;
45+
3946
/** @fn providerErrorWithUnderlyingError:providerID:
4047
@brief Constructs an @c NSError with the @c FUIAuthErrorCodeProviderError code and a populated
4148
@c NSUnderlyingErrorKey and @c FUIAuthErrorUserInfoProviderIDKey in the

FirebaseAuthUI/FUIAuthErrorUtils.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ + (NSError *)userCancelledSignInError {
2626
return [self errorWithCode:FUIAuthErrorCodeUserCancelledSignIn userInfo:nil];
2727
}
2828

29+
+ (NSError *)mergeConflictErrorWithUserInfo:(NSDictionary *)userInfo {
30+
return [self errorWithCode:FUIAuthErrorCodeMergeConflict userInfo:userInfo];
31+
}
32+
2933
+ (NSError *)providerErrorWithUnderlyingError:(NSError *)underlyingError
3034
providerID:(NSString *)providerID {
3135
return [self errorWithCode:FUIAuthErrorCodeProviderError

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";

FirebaseAuthUI/FUIAuthProvider.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,15 @@ __attribute__((deprecated("This is deprecated API and will be removed in a futur
124124
@optional;
125125

126126
/** @property idToken
127-
@brief User Id Token obtained during sign in. Not all providers can return, thus it's optional
127+
@brief User Id Token obtained during sign in. Not all providers can return, thus it's optional.
128128
*/
129129
@property(nonatomic, copy, readonly) NSString *idToken;
130130

131+
/** @fn email
132+
@brief The email address associated with this provider, if any.
133+
*/
134+
- (NSString *)email;
135+
131136
/** @fn handleOpenURL:
132137
@brief May be used to help complete a sign-in flow which requires a callback from Safari.
133138
@param URL The URL which may be handled by the auth provider if an URL is expected.

FirebaseAuthUI/FUIPasswordSignInViewController.m

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@
1414
// limitations under the License.
1515
//
1616

17-
#import "FUIPasswordSignInViewController.h"
17+
#import "FUIPasswordSignInViewController_Internal.h"
1818

1919
#import <FirebaseAuth/FirebaseAuth.h>
2020
#import "FUIAuthBaseViewController_Internal.h"
21+
#import "FUIAuthErrorUtils.h"
2122
#import "FUIAuthStrings.h"
2223
#import "FUIAuthTableViewCell.h"
2324
#import "FUIAuthUtils.h"
2425
#import "FUIAuth_Internal.h"
26+
#import "FUIAuthErrors.h"
2527
#import "FUIPasswordRecoveryViewController.h"
2628

2729
/** @var kCellReuseIdentifier
@@ -78,6 +80,10 @@ - (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil
7880
_email = [email copy];
7981

8082
self.title = FUILocalizedString(kStr_SignInTitle);
83+
__weak FUIPasswordSignInViewController *weakself = self;
84+
_onDismissCallback = ^(FIRAuthDataResult *authResult, NSError *error){
85+
[weakself.authUI invokeResultCallbackWithAuthDataResult:authResult error:error];
86+
};
8187
}
8288
return self;
8389
}
@@ -127,35 +133,61 @@ - (void)signInWithDefaultValue:(NSString *)email andPassword:(NSString *)passwor
127133
}
128134

129135
[self incrementActivity];
130-
131136
FIRAuthCredential *credential =
132137
[FIREmailAuthProvider credentialWithEmail:email password:password];
133-
[self.auth signInAndRetrieveDataWithCredential:credential
134-
completion:^(FIRAuthDataResult *_Nullable authResult,
135-
NSError *_Nullable error) {
136-
[self decrementActivity];
137-
138-
if (error) {
139-
switch (error.code) {
140-
case FIRAuthErrorCodeWrongPassword:
141-
[self showAlertWithMessage:FUILocalizedString(kStr_WrongPasswordError)];
142-
return;
143-
case FIRAuthErrorCodeUserNotFound:
144-
[self showAlertWithMessage:FUILocalizedString(kStr_UserNotFoundError)];
145-
return;
146-
case FIRAuthErrorCodeUserDisabled:
147-
[self showAlertWithMessage:FUILocalizedString(kStr_AccountDisabledError)];
148-
return;
149-
case FIRAuthErrorCodeTooManyRequests:
150-
[self showAlertWithMessage:FUILocalizedString(kStr_SignInTooManyTimesError)];
138+
139+
void (^completeSignInBlock)(FIRAuthDataResult *, NSError *) = ^(FIRAuthDataResult *authResult,
140+
NSError *error) {
141+
[self decrementActivity];
142+
143+
if (error) {
144+
switch (error.code) {
145+
case FIRAuthErrorCodeWrongPassword:
146+
[self showAlertWithMessage:FUILocalizedString(kStr_WrongPasswordError)];
147+
return;
148+
case FIRAuthErrorCodeUserNotFound:
149+
[self showAlertWithMessage:FUILocalizedString(kStr_UserNotFoundError)];
150+
return;
151+
case FIRAuthErrorCodeUserDisabled:
152+
[self showAlertWithMessage:FUILocalizedString(kStr_AccountDisabledError)];
153+
return;
154+
case FIRAuthErrorCodeTooManyRequests:
155+
[self showAlertWithMessage:FUILocalizedString(kStr_SignInTooManyTimesError)];
156+
return;
157+
}
158+
}
159+
[self.navigationController dismissViewControllerAnimated:YES completion:^{
160+
if (self->_onDismissCallback) {
161+
self->_onDismissCallback(authResult, error);
162+
}
163+
}];
164+
};
165+
166+
// Check for the presence of an anonymous user and whether automatic upgrade is enabled.
167+
if (self.auth.currentUser.isAnonymous &&
168+
[FUIAuth defaultAuthUI].shouldAutoUpgradeAnonymousUsers) {
169+
170+
[self.auth.currentUser
171+
linkAndRetrieveDataWithCredential:credential
172+
completion:^(FIRAuthDataResult *_Nullable authResult,
173+
NSError *_Nullable error) {
174+
if (error) {
175+
if (error.code == FIRAuthErrorCodeEmailAlreadyInUse) {
176+
NSDictionary *userInfo = @{ FUIAuthCredentialKey : credential };
177+
NSError *mergeError = [FUIAuthErrorUtils mergeConflictErrorWithUserInfo:userInfo];
178+
[self.navigationController dismissViewControllerAnimated:YES completion:^{
179+
[self.authUI invokeResultCallbackWithAuthDataResult:authResult error:mergeError];
180+
}];
151181
return;
182+
}
183+
completeSignInBlock(nil, error);
184+
return;
152185
}
153-
}
154-
155-
[self dismissNavigationControllerAnimated:YES completion:^{
156-
[self.authUI invokeResultCallbackWithAuthDataResult:authResult error:error];
186+
completeSignInBlock(authResult, nil);
157187
}];
158-
}];
188+
} else {
189+
[self.auth signInAndRetrieveDataWithCredential:credential completion:completeSignInBlock];
190+
}
159191
}
160192

161193
- (void)signIn {

0 commit comments

Comments
 (0)