Files
firebase-ios-sdk/AuthSamples/Sample/MainViewController.m
T
2017-05-15 12:27:07 -07:00

2497 lines
97 KiB
Objective-C

/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "MainViewController.h"
#import <objc/runtime.h>
#import "FIRAdditionalUserInfo.h"
#import "FIRApp.h"
#import "FIROAuthProvider.h"
#import "FIRPhoneAuthCredential.h"
#import "FIRPhoneAuthProvider.h"
#import "FirebaseAuth.h"
#import "CustomTokenDataEntryViewController.h"
#import "FacebookAuthProvider.h"
#import "GoogleAuthProvider.h"
#import "SettingsViewController.h"
#import "StaticContentTableViewManager.h"
#import "UIViewController+Alerts.h"
#import "UserInfoViewController.h"
#import "UserTableViewCell.h"
#if INTERNAL_GOOGLE3_BUILD
#import "third_party/objective_c/FirebaseDatabase/FirebaseDatabase.framework/Headers/FirebaseDatabase.h"
#endif
/** @var kTokenGetButtonText
@brief The text of the "Get Token" button.
*/
static NSString *const kTokenGetButtonText = @"Get Token";
/** @var kTokenRefreshButtonText
@brief The text of the "Refresh Token" button.
*/
static NSString *const kTokenRefreshButtonText = @"Force Refresh Token";
/** @var kTokenRefreshedAlertTitle
@brief The title of the "Token Refreshed" alert.
*/
static NSString *const kTokenRefreshedAlertTitle = @"Token";
/** @var kTokenRefreshErrorAlertTitle
@brief The title of the "Token Refresh error" alert.
*/
static NSString *const kTokenRefreshErrorAlertTitle = @"Get Token Error";
/** @var kSettingsButtonText
@brief The text of the "Settings" button.
*/
static NSString *const kSettingsButtonText = @"[Sample App Settings]";
/** @var kUserInfoButtonText
@brief The text of the "Show User Info" button.
*/
static NSString *const kUserInfoButtonText = @"[Show User Info]";
/** @var kSetImageURLText
@brief The text of the "Set Photo url" button.
*/
static NSString *const kSetPhotoURLText = @"Set Photo url";
/** @var kSignInButtonText
@brief The text of the "Sign In" button.
*/
static NSString *const kSignInButtonText = @"Sign In (HEADFUL)";
/** @var kSignInGoogleButtonText
@brief The text of the "Google SignIn" button.
*/
static NSString *const kSignInGoogleButtonText = @"Sign in with Google";
/** @var kSignInAndRetrieveGoogleButtonText
@brief The text of the "Sign in with Google and retrieve data" button.
*/
static NSString *const kSignInGoogleAndRetrieveDataButtonText =
@"Sign in with Google and retrieve data";
/** @var kSignInFacebookButtonText
@brief The text of the "Facebook SignIn" button.
*/
static NSString *const kSignInFacebookButtonText = @"Sign in with Facebook";
/** @var kSignInFacebookAndRetrieveDataButtonText
@brief The text of the "Facebook SignIn and retrieve data" button.
*/
static NSString *const kSignInFacebookAndRetrieveDataButtonText =
@"Sign in with Facebook and retrieve data";
/** @var kSignInEmailPasswordButtonText
@brief The text of the "Email/Password SignIn" button.
*/
static NSString *const kSignInEmailPasswordButtonText = @"Sign in with Email/Password";
/** @var kSignInWithCustomTokenButtonText
@brief The text of the "Sign In (BYOAuth)" button.
*/
static NSString *const kSignInWithCustomTokenButtonText = @"Sign In (BYOAuth)";
/** @var kSignInAnonymouslyButtonText
@brief The text of the "Sign In Anonymously" button.
*/
static NSString *const kSignInAnonymouslyButtonText = @"Sign In Anonymously";
/** @var kSignedInAlertTitle
@brief The text of the "Sign In Succeeded" alert.
*/
static NSString *const kSignedInAlertTitle = @"Signed In";
/** @var kSignInErrorAlertTitle
@brief The text of the "Sign In Encountered an Error" alert.
*/
static NSString *const kSignInErrorAlertTitle = @"Sign-In Error";
/** @var kSignOutButtonText
@brief The text of the "Sign Out" button.
*/
static NSString *const kSignOutButtonText = @"Sign Out";
/** @var kDeleteAccountText
@brief The text of the "Delete Account" button.
*/
static NSString *const kDeleteUserText = @"Delete Account";
/** @var kReauthenticateGoogleText
@brief The text of the "Reathenticate Google" button.
*/
static NSString *const kReauthenticateGoogleText = @"Reauthenticate Google";
/** @var kReauthenticateGoogleAndRetrieveDataText
@brief The text of the "Reathenticate Google and retrieve data" button.
*/
static NSString *const kReauthenticateGoogleAndRetrieveDataText =
@"Reauthenticate Google and retrieve data";
/** @var kReauthenticateFBText
@brief The text of the "Reathenticate Facebook" button.
*/
static NSString *const kReauthenticateFBText = @"Reauthenticate FB";
/** @var kReauthenticateFBAndRetrieveDataText
@brief The text of the "Reathenticate Facebook and retrieve data" button.
*/
static NSString *const kReauthenticateFBAndRetrieveDataText =
@"Reauthenticate FB and retrieve data";
/** @var kReAuthenticateEmail
@brief The text of the "Reathenticate Email" button.
*/
static NSString *const kReauthenticateEmailText = @"Reauthenticate Email/Password";
/** @var kOKButtonText
@brief The text of the "OK" button for the Sign In result dialogs.
*/
static NSString *const kOKButtonText = @"OK";
/** @var kSetDisplayNameTitle
@brief The title of the "Set Display Name" error dialog.
*/
static NSString *const kSetDisplayNameTitle = @"Set Display Name";
/** @var kUpdateEmailText
@brief The title of the "Update Email" button.
*/
static NSString *const kUpdateEmailText = @"Update Email";
/** @var kUpdatePasswordText
@brief The title of the "Update Password" button.
*/
static NSString *const kUpdatePasswordText = @"Update Password";
/** @var kUpdatePhoneNumber
@brief The title of the "Update Photo" button.
*/
static NSString *const kUpdatePhoneNumber = @"Update Phone Number";
/** @var kLinkPhoneNumber
@brief The title of the "Link phone" button.
*/
static NSString *const kLinkPhoneNumber = @"Link Phone Number";
/** @var kUnlinkPhone
@brief The title of the "Unlink Phone" button for unlinking phone auth provider.
*/
static NSString *const kUnlinkPhoneNumber = @"Unlink Phone Number";
/** @var kReloadText
@brief The title of the "Reload User" button.
*/
static NSString *const kReloadText = @"Reload User";
/** @var kLinkWithGoogleText
@brief The title of the "Link with Google" button.
*/
static NSString *const kLinkWithGoogleText = @"Link with Google";
/** @var kLinkWithGoogleAndRetrieveDataText
@brief The title of the "Link with Google and retrieve data" button.
*/
static NSString *const kLinkWithGoogleAndRetrieveDataText = @"Link with Google and retrieve data";
/** @var kLinkWithFacebookText
@brief The title of the "Link with Facebook Account" button.
*/
static NSString *const kLinkWithFacebookText = @"Link with Facebook";
/** @var kLinkWithFacebookAndRetrieveDataText
@brief The title of the "Link with Facebook and retrieve data" button.
*/
static NSString *const kLinkWithFacebookAndRetrieveDataText =
@"Link with Facebook and retrieve data";
/** @var kLinkWithEmailPasswordText
@brief The title of the "Link with Email/Password Account" button.
*/
static NSString *const kLinkWithEmailPasswordText = @"Link with Email/Password";
/** @var kUnlinkTitle
@brief The text of the "Unlink from Provider" error Dialog.
*/
static NSString *const kUnlinkTitle = @"Unlink from Provider";
/** @var kUnlinkFromGoogle
@brief The text of the "Unlink from Google" button.
*/
static NSString *const kUnlinkFromGoogle = @"Unlink from Google";
/** @var kUnlinkFromFacebook
@brief The text of the "Unlink from Facebook" button.
*/
static NSString *const kUnlinkFromFacebook = @"Unlink from Facebook";
/** @var kUnlinkFromEmailPassword
@brief The text of the "Unlink from Google" button.
*/
static NSString *const kUnlinkFromEmailPassword = @"Unlink from Email/Password";
/** @var kGetProvidersForEmail
@brief The text of the "Get Provider IDs for Email" button.
*/
static NSString *const kGetProvidersForEmail = @"Get Provider IDs for Email";
/** @var kRequestVerifyEmail
@brief The text of the "Request Verify Email Link" button.
*/
static NSString *const kRequestVerifyEmail = @"Request Verify Email Link";
/** @var kRequestPasswordReset
@brief The text of the "Email Password Reset" button.
*/
static NSString *const kRequestPasswordReset = @"Send Password Reset Email";
/** @var kResetPassword
@brief The text of the "Password Reset" button.
*/
static NSString *const kResetPassword = @"Reset Password";
/** @var kCheckActionCode
@brief The text of the "Check action code" button.
*/
static NSString *const kCheckActionCode = @"Check action code";
/** @var kApplyActionCode
@brief The text of the "Apply action code" button.
*/
static NSString *const kApplyActionCode = @"Apply action code";
/** @var kVerifyPasswordResetCode
@brief The text of the "Verify password reset code" button.
*/
static NSString *const kVerifyPasswordResetCode = @"Verify password reset code";
/** @var kSectionTitleSettings
@brief The text for the title of the "Settings" section.
*/
static NSString *const kSectionTitleSettings = @"SETTINGS";
/** @var kSectionTitleSignIn
@brief The text for the title of the "Sign-In" section.
*/
static NSString *const kSectionTitleSignIn = @"SIGN-IN";
/** @var kSectionTitleReauthenticate
@brief The text for the title of the "Reauthenticate" section.
*/
static NSString *const kSectionTitleReauthenticate = @"REAUTHENTICATE";
/** @var kSectionTitleTokenActions
@brief The text for the title of the "Token Actions" section.
*/
static NSString *const kSectionTitleTokenActions = @"TOKEN ACTIONS";
/** @var kSectionTitleEditUser
@brief The text for the title of the "Edit User" section.
*/
static NSString *const kSectionTitleEditUser = @"EDIT USER";
/** @var kSectionTitleLinkUnlinkAccount
@brief The text for the title of the "Link/Unlink account" section.
*/
static NSString *const kSectionTitleLinkUnlinkAccounts = @"LINK/UNLINK ACCOUNT";
/** @var kSectionTitleUserActions
@brief The text for the title of the "User Actions" section.
*/
static NSString *const kSectionTitleUserActions = @"USER ACTIONS";
/** @var kSectionTitleUserDetails
@brief The text for the title of the "User Details" section.
*/
static NSString *const kSectionTitleUserDetails = @"SIGNED-IN USER DETAILS";
/** @var kSectionTitleListeners
@brief The title for the table view section dedicated to auth state did change listeners.
*/
static NSString *const kSectionTitleListeners = @"Listeners";
/** @var kAddAuthStateListenerTitle
@brief The title for the table view row which adds a block to the auth state did change
listeners.
*/
static NSString *const kAddAuthStateListenerTitle = @"Add Auth State Change Listener";
/** @var kRemoveAuthStateListenerTitle
@brief The title for the table view row which removes a block to the auth state did change
listeners.
*/
static NSString *const kRemoveAuthStateListenerTitle = @"Remove Last Auth State Change Listener";
/** @var kAddIDTokenListenerTitle
@brief The title for the table view row which adds a block to the ID token did change
listeners.
*/
static NSString *const kAddIDTokenListenerTitle = @"Add ID Token Change Listener";
/** @var kRemoveIDTokenListenerTitle
@brief The title for the table view row which removes a block to the ID token did change
listeners.
*/
static NSString *const kRemoveIDTokenListenerTitle = @"Remove Last ID Token Change Listener";
/** @var kSectionTitleApp
@brief The text for the title of the "App" section.
*/
static NSString *const kSectionTitleApp = @"APP";
/** @var kCreateUserTitle
@brief The text of the "Create User" button.
*/
static NSString *const kCreateUserTitle = @"Create User";
/** @var kDeleteAppTitle
@brief The text of the "Delete App" button.
*/
static NSString *const kDeleteAppTitle = @"Delete App";
/** @var kSectionTitleManualTests
@brief The section title for automated manual tests.
*/
static NSString *const kSectionTitleManualTests = @"Manual Tests";
/** @var kAutoBYOAuthTitle
@brief The button title for automated BYOAuth operation.
*/
static NSString *const kAutoBYOAuthTitle = @"BYOAuth";
/** @var kAutoSignInGoogle
@brief The button title for automated Google sign-in operation.
*/
static NSString *const kAutoSignInGoogle = @"Sign In With Google";
/** @var kAutoSignInFacebook
@brief The button title for automated Facebook sign-in operation.
*/
static NSString *const kAutoSignInFacebook = @"Sign In With Facebook";
/** @var kAutoSignUpEmailPassword
@brief The button title for automated sign-up with email/password.
*/
static NSString *const kAutoSignUpEmailPassword = @"Sign Up With Email/Password";
/** @var kAutoSignInAnonymously
@brief The button title for automated sign-in anonymously.
*/
static NSString *const kAutoSignInAnonymously = @"Sign In Anonymously";
/** @var kAutoAccountLinking
@brief The button title for automated account linking.
*/
static NSString *const kAutoAccountLinking = @"Link with Google";
/** @var kGitHubSignInButtonText
@brief The button title for signing in with github.
*/
static NSString *const kGitHubSignInButtonText = @"Sign In with GitHub";
/** @var kExpiredCustomTokenUrl
@brief The url for obtaining a valid custom token string used to test BYOAuth.
*/
static NSString *const kCustomTokenUrl = @"https://fb-sa-1211.appspot.com/token";
/** @var kCustomTokenUrl
@brief The url for obtaining an expired custom token string used to test BYOAuth.
*/
static NSString *const kExpiredCustomTokenUrl = @"https://fb-sa-1211.appspot.com/expired_token";
/** @var kFakeDisplayPhotoUrl
@brief The url for obtaining a display displayPhoto used for testing.
*/
static NSString *const kFakeDisplayPhotoUrl =
@"https://www.gstatic.com/images/branding/product/1x/play_apps_48dp.png";
/** @var kFakeDisplayName
@brief Fake display name for testing.
*/
static NSString *const kFakeDisplayName = @"John GoogleSpeed";
/** @var kFakeEmail
@brief Fake email for testing.
*/
static NSString *const kFakeEmail =@"firemail@example.com";
/** @var kFakePassword
@brief Fake password for testing.
*/
static NSString *const kFakePassword =@"fakePassword";
/** @var kInvalidCustomToken
@brief The custom token string for testing BYOAuth.
*/
static NSString *const kInvalidCustomToken = @"invalid custom token.";
/** @var kSafariGoogleSignOutMessagePrompt
@brief The message text informing user to sign-out from Google on safari before continuing.
*/
static NSString *const kSafariGoogleSignOutMessagePrompt = @"This automated test assumes that no "
"Google account is signed in on Safari, if your are not prompted for a password, sign out on "
"Safari and rerun the test.";
/** @var kSafariFacebookSignOutMessagePrompt
@brief The message text informing user to sign-out from Facebook on safari before continuing.
*/
static NSString *const kSafariFacebookSignOutMessagePrompt = @"This automated test assumes that no "
"Facebook account is signed in on Safari, if your are not prompted for a password, sign out on "
"Safari and rerun the test.";
/** @var kUnlinkAccountMessagePrompt
@brief The message text informing user to use an unlinked account for account linking.
*/
static NSString *const kUnlinkAccountMessagePrompt = @"Sign into gmail with an email address "
"that has not been linked to this sample application before. Delete account if necessary.";
// Declared extern in .h file.
NSString *const kCreateUserAccessibilityID = @"CreateUserAccessibilityID";
/** @var kPhoneAuthSectionTitle
@brief The title for the phone auth section of the test app.
*/
static NSString *const kPhoneAuthSectionTitle = @"Phone Auth";
/** @var kPhoneNumberSignInTitle
@brief The title for button to sign in with phone number.
*/
static NSString *const kPhoneNumberSignInTitle = @"Sign in With Phone Number";
/** @typedef showEmailPasswordDialogCompletion
@brief The type of block which gets called to complete the Email/Password dialog flow.
*/
typedef void (^ShowEmailPasswordDialogCompletion)(FIRAuthCredential *credential);
/** @typedef FIRTokenCallback
@brief The type of block which gets called when a token is ready.
*/
typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable error);
/** @brief Method declarations copied from FIRAppInternal.h to avoid importing non-public headers.
*/
@interface FIRApp (Internal)
/** @fn getTokenForcingRefresh:withCallback:
@brief Retrieves the Firebase authentication token, possibly refreshing it.
@param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason
other than an expiration.
@param callback The block to invoke when the token is available.
*/
- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(nonnull FIRTokenCallback)callback;
@end
@implementation MainViewController {
NSMutableString *_consoleString;
/** @var _authStateDidChangeListeners
@brief An array of handles created during calls to @c FIRAuth.addAuthStateDidChangeListener:
*/
NSMutableArray<FIRAuthStateDidChangeListenerHandle> *_authStateDidChangeListeners;
/** @var _IDTokenDidChangeListeners
@brief An array of handles created during calls to @c FIRAuth.addIDTokenDidChangeListener:
*/
NSMutableArray<FIRAuthStateDidChangeListenerHandle> *_IDTokenDidChangeListeners;
/** @var _userInMemory
@brief Acts like the "memory" function of a calculator. An operation allows sample app users
to assign this value based on @c FIRAuth.currentUser or clear this value.
*/
FIRUser *_userInMemory;
/** @var _useUserInMemory
@brief Instructs the application to use _userInMemory instead of @c FIRAuth.currentUser for
testing operations. This allows us to test if things still work with a user who is not
the @c FIRAuth.currentUser, and also allows us to test those things while
@c FIRAuth.currentUser remains nil (after a sign-out) and also when @c FIRAuth.currentUser
is non-nil (do to a subsequent sign-in.)
*/
BOOL _useUserInMemory;
}
/** @fn initWithNibName:bundle:
@brief Overridden default initializer.
*/
- (id)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_authStateDidChangeListeners = [NSMutableArray array];
_IDTokenDidChangeListeners = [NSMutableArray array];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(authStateChangedForAuth:)
name:FIRAuthStateDidChangeNotification
object:nil];
self.useStatusBarSpinner = YES;
#if INTERNAL_GOOGLE3_BUILD
// Trigger automatic token refresh.
[[FIRDatabase database] reference];
#endif
}
return self;
}
- (void)viewDidLoad {
// Give us a circle for the image view:
_userInfoTableViewCell.userInfoProfileURLImageView.layer.cornerRadius =
_userInfoTableViewCell.userInfoProfileURLImageView.frame.size.width / 2.0f;
_userInfoTableViewCell.userInfoProfileURLImageView.layer.masksToBounds = YES;
_userInMemoryInfoTableViewCell.userInfoProfileURLImageView.layer.cornerRadius =
_userInMemoryInfoTableViewCell.userInfoProfileURLImageView.frame.size.width / 2.0f;
_userInMemoryInfoTableViewCell.userInfoProfileURLImageView.layer.masksToBounds = YES;
[self updateTable];
[self updateUserInfo];
}
- (void)updateTable {
__weak typeof(self) weakSelf = self;
_tableViewManager.contents =
[StaticContentTableViewContent contentWithSections:@[
[StaticContentTableViewSection sectionWithTitle:kSectionTitleUserDetails cells:@[
[StaticContentTableViewCell cellWithCustomCell:_userInfoTableViewCell action:^{
[weakSelf presentUserInfo];
}],
[StaticContentTableViewCell cellWithCustomCell:_userToUseCell],
[StaticContentTableViewCell cellWithCustomCell:_userInMemoryInfoTableViewCell action:^{
[weakSelf presentUserInMemoryInfo];
}],
]],
[StaticContentTableViewSection sectionWithTitle:kSectionTitleSettings cells:@[
[StaticContentTableViewCell cellWithTitle:kSettingsButtonText
action:^{ [weakSelf presentSettings]; }]
]],
[StaticContentTableViewSection sectionWithTitle:kPhoneAuthSectionTitle cells:@[
[StaticContentTableViewCell cellWithTitle:kPhoneNumberSignInTitle
action:^{ [weakSelf signInWithPhoneNumber]; }],
[StaticContentTableViewCell cellWithTitle:kUpdatePhoneNumber
action:^{ [weakSelf updatePhoneNumber]; }],
[StaticContentTableViewCell cellWithTitle:kLinkPhoneNumber
action:^{ [weakSelf linkPhoneNumber]; }],
[StaticContentTableViewCell cellWithTitle:kUnlinkPhoneNumber
action:^{
[weakSelf unlinkFromProvider:FIRPhoneAuthProviderID];
}],
]],
[StaticContentTableViewSection sectionWithTitle:kSectionTitleSignIn cells:@[
[StaticContentTableViewCell cellWithTitle:kCreateUserTitle
value:nil
action:^{ [weakSelf createUser]; }
accessibilityID:kCreateUserAccessibilityID],
[StaticContentTableViewCell cellWithTitle:kSignInGoogleButtonText
action:^{ [weakSelf signInGoogle]; }],
[StaticContentTableViewCell cellWithTitle:kSignInGoogleAndRetrieveDataButtonText
action:^{ [weakSelf signInGoogleAndRetrieveData]; }],
[StaticContentTableViewCell cellWithTitle:kSignInFacebookButtonText
action:^{ [weakSelf signInFacebook]; }],
[StaticContentTableViewCell cellWithTitle:kSignInFacebookAndRetrieveDataButtonText
action:^{ [weakSelf signInFacebookAndRetrieveData]; }],
[StaticContentTableViewCell cellWithTitle:kSignInEmailPasswordButtonText
action:^{ [weakSelf signInEmailPassword]; }],
[StaticContentTableViewCell cellWithTitle:kSignInWithCustomTokenButtonText
action:^{ [weakSelf signInWithCustomToken]; }],
[StaticContentTableViewCell cellWithTitle:kSignInAnonymouslyButtonText
action:^{ [weakSelf signInAnonymously]; }],
[StaticContentTableViewCell cellWithTitle:kGitHubSignInButtonText
action:^{ [weakSelf signInWithGitHub]; }],
[StaticContentTableViewCell cellWithTitle:kSignOutButtonText
action:^{ [weakSelf signOut]; }]
]],
[StaticContentTableViewSection sectionWithTitle:kSectionTitleUserActions cells:@[
[StaticContentTableViewCell cellWithTitle:kSetDisplayNameTitle
action:^{ [weakSelf setDisplayName]; }],
[StaticContentTableViewCell cellWithTitle:kSetPhotoURLText
action:^{ [weakSelf setPhotoURL]; }],
[StaticContentTableViewCell cellWithTitle:kReloadText
action:^{ [weakSelf reloadUser]; }],
[StaticContentTableViewCell cellWithTitle:kGetProvidersForEmail
action:^{ [weakSelf getProvidersForEmail]; }],
[StaticContentTableViewCell cellWithTitle:kRequestVerifyEmail
action:^{ [weakSelf requestVerifyEmail]; }],
[StaticContentTableViewCell cellWithTitle:kRequestPasswordReset
action:^{ [weakSelf requestPasswordReset]; }],
[StaticContentTableViewCell cellWithTitle:kResetPassword
action:^{ [weakSelf resetPassword]; }],
[StaticContentTableViewCell cellWithTitle:kCheckActionCode
action:^{ [weakSelf checkActionCode]; }],
[StaticContentTableViewCell cellWithTitle:kApplyActionCode
action:^{ [weakSelf applyActionCode]; }],
[StaticContentTableViewCell cellWithTitle:kVerifyPasswordResetCode
action:^{ [weakSelf verifyPasswordResetCode]; }],
[StaticContentTableViewCell cellWithTitle:kUpdateEmailText
action:^{ [weakSelf updateEmail]; }],
[StaticContentTableViewCell cellWithTitle:kUpdatePasswordText
action:^{ [weakSelf updatePassword]; }],
[StaticContentTableViewCell cellWithTitle:kDeleteUserText
action:^{ [weakSelf deleteAccount]; }],
]],
[StaticContentTableViewSection sectionWithTitle:kSectionTitleReauthenticate cells:@[
[StaticContentTableViewCell cellWithTitle:kReauthenticateGoogleText
action:^{ [weakSelf reauthenticateGoogle]; }],
[StaticContentTableViewCell
cellWithTitle:kReauthenticateGoogleAndRetrieveDataText
action:^{ [weakSelf reauthenticateGoogleAndRetrieveData]; }],
[StaticContentTableViewCell cellWithTitle:kReauthenticateFBText
action:^{ [weakSelf reauthenticateFB]; }],
[StaticContentTableViewCell cellWithTitle:kReauthenticateFBAndRetrieveDataText
action:^{ [weakSelf reauthenticateFBAndRetrieveData]; }],
[StaticContentTableViewCell cellWithTitle:kReauthenticateEmailText
action:^{ [weakSelf reauthenticateEmailPassword]; }]
]],
[StaticContentTableViewSection sectionWithTitle:kSectionTitleTokenActions cells:@[
[StaticContentTableViewCell cellWithTitle:kTokenGetButtonText
action:^{ [weakSelf getUserTokenWithForce:NO]; }],
[StaticContentTableViewCell cellWithTitle:kTokenRefreshButtonText
action:^{ [weakSelf getUserTokenWithForce:YES]; }]
]],
[StaticContentTableViewSection sectionWithTitle:kSectionTitleLinkUnlinkAccounts cells:@[
[StaticContentTableViewCell cellWithTitle:kLinkWithGoogleText
action:^{ [weakSelf linkWithGoogle]; }],
[StaticContentTableViewCell cellWithTitle:kLinkWithGoogleAndRetrieveDataText
action:^{ [weakSelf linkWithGoogleAndRetrieveData]; }],
[StaticContentTableViewCell cellWithTitle:kLinkWithFacebookText
action:^{ [weakSelf linkWithFacebook]; }],
[StaticContentTableViewCell cellWithTitle:kLinkWithFacebookAndRetrieveDataText
action:^{ [weakSelf linkWithFacebookAndRetrieveData]; }],
[StaticContentTableViewCell cellWithTitle:kLinkWithEmailPasswordText
action:^{ [weakSelf linkWithEmailPassword]; }],
[StaticContentTableViewCell cellWithTitle:kUnlinkFromGoogle
action:^{
[weakSelf unlinkFromProvider:FIRGoogleAuthProviderID];
}],
[StaticContentTableViewCell cellWithTitle:kUnlinkFromFacebook
action:^{
[weakSelf unlinkFromProvider:FIRFacebookAuthProviderID];
}],
[StaticContentTableViewCell cellWithTitle:kUnlinkFromEmailPassword
action:^{
[weakSelf unlinkFromProvider:FIREmailAuthProviderID];
}]
]],
[StaticContentTableViewSection sectionWithTitle:kSectionTitleApp cells:@[
[StaticContentTableViewCell cellWithTitle:kDeleteAppTitle
action:^{ [weakSelf deleteApp]; }],
[StaticContentTableViewCell cellWithTitle:kTokenGetButtonText
action:^{ [weakSelf getAppTokenWithForce:NO]; }],
[StaticContentTableViewCell cellWithTitle:kTokenRefreshButtonText
action:^{ [weakSelf getAppTokenWithForce:YES]; }]
]],
[StaticContentTableViewSection sectionWithTitle:kSectionTitleListeners cells:@[
[StaticContentTableViewCell cellWithTitle:kAddAuthStateListenerTitle
action:^{ [weakSelf addAuthStateListener]; }],
[StaticContentTableViewCell cellWithTitle:kRemoveAuthStateListenerTitle
action:^{ [weakSelf removeAuthStateListener]; }],
[StaticContentTableViewCell cellWithTitle:kAddIDTokenListenerTitle
action:^{ [weakSelf addIDTokenListener]; }],
[StaticContentTableViewCell cellWithTitle:kRemoveIDTokenListenerTitle
action:^{ [weakSelf removeIDTokenListener]; }],
]],
[StaticContentTableViewSection sectionWithTitle:kSectionTitleManualTests cells:@[
[StaticContentTableViewCell cellWithTitle:kAutoBYOAuthTitle
action:^{ [weakSelf automatedBYOAuth]; }],
[StaticContentTableViewCell cellWithTitle:kAutoSignInGoogle
action:^{ [weakSelf automatedSignInGoogle]; }],
[StaticContentTableViewCell cellWithTitle:kAutoSignInFacebook
action:^{ [weakSelf automatedSignInFacebook]; }],
[StaticContentTableViewCell cellWithTitle:kAutoSignUpEmailPassword
action:^{ [weakSelf automatedEmailSignUp]; }],
[StaticContentTableViewCell cellWithTitle:kAutoSignInAnonymously
action:^{ [weakSelf automatedAnonymousSignIn]; }],
[StaticContentTableViewCell cellWithTitle:kAutoAccountLinking
action:^{ [weakSelf automatedAccountLinking]; }]
]]
]];
}
#pragma mark - Interface Builder Actions
- (IBAction)userToUseDidChange:(UISegmentedControl *)sender {
_useUserInMemory = (sender.selectedSegmentIndex == 1);
}
- (IBAction)memoryPlus {
_userInMemory = [FIRAuth auth].currentUser;
[self updateUserInfo];
}
- (IBAction)memoryClear {
_userInMemory = nil;
[self updateUserInfo];
}
#pragma mark - Actions
/** @fn signInWithProvider:provider:
@brief Perform sign in with credential operataion, for given auth provider.
@provider The auth provider.
@callback The callback to continue the flow which executed this sign-in.
*/
- (void)signInWithProvider:(nonnull id<AuthProvider>)provider callback:(void(^)(void))callback {
if (!provider) {
[self logFailedTest:@"A valid auth provider was not provided to the signInWithProvider."];
return;
}
[provider getAuthCredentialWithPresentingViewController:self
callback:^(FIRAuthCredential *credential,
NSError *error) {
if (!credential) {
[self logFailedTest:@"The test needs a valid credential to continue."];
return;
}
[[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in with provider failed" error:error];
[self logFailedTest:@"Sign-in should succeed"];
return;
} else {
[self logSuccess:@"sign-in with provider succeeded."];
callback();
}
}];
}];
}
/** @fn automatedSignInGoogle
@brief Automatically executes the manual test for sign-in with Google.
*/
- (void)automatedSignInGoogle {
[self showMessagePromptWithTitle:kAutoSignInGoogle
message:kSafariGoogleSignOutMessagePrompt
showCancelButton:NO
completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
FIRAuth *auth = [FIRAuth auth];
if (!auth) {
[self logFailedTest:@"Could not obtain auth object."];
return;
}
[auth signOut:NULL];
[self log:@"INITIATING AUTOMATED MANUAL TEST FOR GOOGLE SIGN IN:"];
[self signInWithProvider:[AuthProviders google] callback:^{
[self logSuccess:@"sign-in with Google provider succeeded."];
[auth signOut:NULL];
[self signInWithProvider:[AuthProviders google] callback:^{
[self logSuccess:@"sign-in with Google provider succeeded."];
[self updateEmailPasswordWithCompletion:^{
[self automatedSignInGoogleDisplayNamePhotoURL];
}];
}];
}];
}];
}
/** @fn automatedSignInGoogleDisplayNamePhotoURL
@brief Automatically executes the manual test for setting email and password for sign in with
Google.
*/
- (void)automatedSignInGoogleDisplayNamePhotoURL {
[self signInWithProvider:[AuthProviders google] callback:^{
[self updateDisplayNameAndPhotoURlWithCompletion:^{
[self log:@"FINISHED AUTOMATED MANUAL TEST FOR SIGN-IN WITH GOOGlE."];
[self reloadUser];
}];
}];
}
/** @fn automatedSignInFacebook
@brief Automatically executes the manual test for sign-in with Facebook.
*/
- (void)automatedSignInFacebook {
[self showMessagePromptWithTitle:kAutoSignInFacebook
message:kSafariFacebookSignOutMessagePrompt
showCancelButton:NO
completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
FIRAuth *auth = [FIRAuth auth];
if (!auth) {
[self logFailedTest:@"Could not obtain auth object."];
return;
}
[auth signOut:NULL];
[self log:@"INITIATING AUTOMATED MANUAL TEST FOR FACEBOOK SIGN IN:"];
[self signInWithProvider:[AuthProviders facebook] callback:^{
[self logSuccess:@"sign-in with Facebook provider succeeded."];
[auth signOut:NULL];
[self signInWithProvider:[AuthProviders facebook] callback:^{
[self logSuccess:@"sign-in with Facebook provider succeeded."];
[self updateEmailPasswordWithCompletion:^{
[self automatedSignInFacebookDisplayNamePhotoURL];
}];
}];
}];
}];
}
/** @fn automatedEmailSignUp
@brief Automatically executes the manual test for sign-up with email/password.
*/
- (void)automatedEmailSignUp {
[self log:@"INITIATING AUTOMATED MANUAL TEST FOR FACEBOOK SIGN IN:"];
FIRAuth *auth = [FIRAuth auth];
if (!auth) {
[self logFailedTest:@"Could not obtain auth object."];
return;
}
[self signUpNewEmail:kFakeEmail password:kFakePassword callback:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
[self logFailedTest: @" Email/Password Account account creation failed"];
return;
}
[auth signOut:NULL];
FIRAuthCredential *credential =
[FIREmailPasswordAuthProvider credentialWithEmail:kFakeEmail
password:kFakePassword];
[auth signInWithCredential:credential
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in with Email/Password failed" error:error];
[self logFailedTest:@"sign-in with Email/Password should succeed."];
return;
}
[self logSuccess:@"sign-in with Email/Password succeeded."];
[self log:@"FINISHED AUTOMATED MANUAL TEST FOR SIGN-IN WITH EMAIL/PASSWORD."];
// Delete the user so that we can reuse the fake email address for subsequent tests.
[auth.currentUser deleteWithCompletion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"Failed to delete user" error:error];
[self logFailedTest:@"Deleting a user that was recently signed-in should succeed."];
return;
}
[self logSuccess:@"User deleted."];
}];
}];
}];
}
/** @fn automatedAnonymousSignIn
@brief Automatically executes the manual test for sign-in anonymously.
*/
- (void)automatedAnonymousSignIn {
[self log:@"INITIATING AUTOMATED MANUAL TEST FOR ANONYMOUS SIGN IN:"];
FIRAuth *auth = [FIRAuth auth];
if (!auth) {
[self logFailedTest:@"Could not obtain auth object."];
return;
}
[auth signOut:NULL];
[self signInAnonymouslyWithCallback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (user) {
NSString *anonymousUID = user.uid;
[self signInAnonymouslyWithCallback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (![user.uid isEqual:anonymousUID]) {
[self logFailedTest:@"Consecutive anonymous sign-ins should yeild the same User ID"];
return;
}
[self log:@"FINISHED AUTOMATED MANUAL TEST FOR ANONYMOUS SIGN IN."];
}];
}
}];
}
/** @fn signInAnonymouslyWithCallback:
@brief Performs anonymous sign in and then executes callback.
@param callback The callback to be executed.
*/
- (void)signInAnonymouslyWithCallback:(nullable FIRAuthResultCallback)callback {
FIRAuth *auth = [FIRAuth auth];
if (!auth) {
[self logFailedTest:@"Could not obtain auth object."];
return;
}
[auth signInAnonymouslyWithCompletion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in anonymously failed" error:error];
[self logFailedTest:@"Recently signed out user should be able to sign in anonymously."];
return;
}
[self logSuccess:@"sign-in anonymously succeeded."];
if (callback) {
callback(user, nil);
}
}];
}
/** @fn automatedAccountLinking
@brief Automatically executes the manual test for account linking.
*/
- (void)automatedAccountLinking {
[self log:@"INITIATING AUTOMATED MANUAL TEST FOR ACCOUNT LINKING:"];
FIRAuth *auth = [FIRAuth auth];
if (!auth) {
[self logFailedTest:@"Could not obtain auth object."];
return;
}
[auth signOut:NULL];
[self signInAnonymouslyWithCallback:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (user) {
NSString *anonymousUID = user.uid;
[self showMessagePromptWithTitle:@"Sign In Instructions"
message:kUnlinkAccountMessagePrompt
showCancelButton:NO
completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
[[AuthProviders google]
getAuthCredentialWithPresentingViewController:self
callback:^(FIRAuthCredential *credential,
NSError *error) {
if (credential) {
[user linkWithCredential:credential completion:^(FIRUser *user, NSError *error) {
if (error) {
[self logFailure:@"link auth provider failed" error:error];
[self logFailedTest:@"Account needs to be linked to complete the test."];
return;
}
[self logSuccess:@"link auth provider succeeded."];
if (user.isAnonymous) {
[self logFailure:@"link auth provider failed, user still anonymous" error:error];
[self logFailedTest:@"Account needs to be linked to complete the test."];
}
if (![user.uid isEqual:anonymousUID]){
[self logFailedTest:@"link auth provider failed, UID's are different. Make sure "
"you link an account that has NOT been Linked nor Signed-In before."];
return;
}
[self log:@"FINISHED AUTOMATED MANUAL TEST FOR ACCOUNT LINKING."];
}];
}
}];
}];
}
}];
}
/** @fn automatedSignInFacebookDisplayNamePhotoURL
@brief Automatically executes the manual test for setting email and password for sign-in with
Facebook.
*/
- (void)automatedSignInFacebookDisplayNamePhotoURL {
[self signInWithProvider:[AuthProviders facebook] callback:^{
[self updateDisplayNameAndPhotoURlWithCompletion:^{
[self log:@"FINISHED AUTOMATED MANUAL TEST FOR SIGN-IN WITH FACEBOOK."];
[self reloadUser];
}];
}];
}
/** @fn automatedBYOauth
@brief Automatically executes the manual test for BYOAuth.
*/
- (void)automatedBYOAuth {
[self log:@"INITIATING AUTOMATED MANUAL TEST FOR BYOAUTH:"];
[self showSpinner:^{
NSError *error;
NSString *customToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:kCustomTokenUrl]
encoding:NSUTF8StringEncoding
error:&error];
NSString *expiredCustomToken =
[NSString stringWithContentsOfURL:[NSURL URLWithString:kExpiredCustomTokenUrl]
encoding:NSUTF8StringEncoding
error:&error];
[self hideSpinner:^{
if (error) {
[self log:@"There was an error retrieving the custom token."];
return;
}
FIRAuth *auth = [FIRAuth auth];
[auth signInWithCustomToken:customToken
completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in with custom token failed" error:error];
[self logFailedTest:@"A fresh custom token should succeed in signing-in."];
return;
}
[self logSuccess:@"sign-in with custom token succeeded."];
[auth.currentUser getIDTokenForcingRefresh:NO
completion:^(NSString *_Nullable token,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"refresh token failed" error:error];
[self logFailedTest:@"Refresh token should be available."];
return;
}
[self logSuccess:@"refresh token succeeded."];
[auth signOut:NULL];
[auth signInWithCustomToken:expiredCustomToken
completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (!error) {
[self logSuccess:@"sign-in with custom token succeeded."];
[self logFailedTest:@"sign-in with an expired custom token should NOT succeed."];
return;
}
[self logFailure:@"sign-in with custom token failed" error:error];
[auth signInWithCustomToken:kInvalidCustomToken
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (!error) {
[self logSuccess:@"sign-in with custom token succeeded."];
[self logFailedTest:@"sign-in with an invalid custom token should NOT succeed."];
return;
}
[self logFailure:@"sign-in with custom token failed" error:error];
//next step of automated test.
[self automatedBYOAuthEmailPassword];
}];
}];
}];
}];
}];
}];
}
/** @fn automatedBYOAuthEmailPassword
@brief Automatically executes the manual test for setting email and password in BYOAuth.
*/
- (void)automatedBYOAuthEmailPassword {
[self showSpinner:^{
NSError *error;
NSString *customToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:kCustomTokenUrl]
encoding:NSUTF8StringEncoding
error:&error];
[self hideSpinner:^{
if (error) {
[self log:@"There was an error retrieving the custom token."];
return;
}
[[FIRAuth auth] signInWithCustomToken:customToken
completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in with custom token failed" error:error];
[self logFailedTest:@"A fresh custom token should succeed in signing-in."];
return;
}
[self logSuccess:@"sign-in with custom token succeeded."];
[self updateEmailPasswordWithCompletion:^{
[self automatedBYOAuthDisplayNameAndPhotoURl];
}];
}];
}];
}];
}
/** @fn automatedBYOAuthDisplayNameAndPhotoURl
@brief Automatically executes the manual test for setting display name and photo url in BYOAuth.
*/
- (void)automatedBYOAuthDisplayNameAndPhotoURl {
[self showSpinner:^{
NSError *error;
NSString *customToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:kCustomTokenUrl]
encoding:NSUTF8StringEncoding
error:&error];
[self hideSpinner:^{
if (error) {
[self log:@"There was an error retrieving the custom token."];
return;
}
[[FIRAuth auth] signInWithCustomToken:customToken
completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in with custom token failed" error:error];
[self logFailedTest:@"A fresh custom token should succeed in signing-in."];
return;
}
[self logSuccess:@"sign-in with custom token succeeded."];
[self updateDisplayNameAndPhotoURlWithCompletion:^{
[self log:@"FINISHED AUTOMATED MANUAL TEST FOR BYOAUTH."];
[self reloadUser];
}];
}];
}];
}];
}
/** @fn updateEmailPasswordWithCompletion:
@brief Updates email and password for automatic manual tests, and signs user in with new email
and password.
@param completion The completion block to continue the automatic test flow.
*/
- (void)updateEmailPasswordWithCompletion:(void(^)(void))completion {
FIRAuth *auth = [FIRAuth auth];
[auth.currentUser updateEmail:kFakeEmail completion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"update email failed" error:error];
[self logFailedTest:@"Update email should succeed when properly signed-in."];
return;
}
[self logSuccess:@"update email succeeded."];
[auth.currentUser updatePassword:kFakePassword completion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"update password failed" error:error];
[self logFailedTest:@"Update password should succeed when properly signed-in."];
return;
}
[self logSuccess:@"update password succeeded."];
[auth signOut:NULL];
FIRAuthCredential *credential =
[FIREmailAuthProvider credentialWithEmail:kFakeEmail password:kFakePassword];
[auth signInWithCredential:credential
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in with Email/Password failed" error:error];
[self logFailedTest:@"sign-in with Email/Password should succeed."];
return;
}
[self logSuccess:@"sign-in with Email/Password succeeded."];
// Delete the user so that we can reuse the fake email address for subsequent tests.
[auth.currentUser deleteWithCompletion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"Failed to delete user." error:error];
[self logFailedTest:@"Deleting a user that was recently signed-in should succeed"];
return;
}
[self logSuccess:@"User deleted."];
completion();
}];
}];
}];
}];
}
/** @fn updateDisplayNameAndPhotoURlWithCompletion:
@brief Automatically executes the manual test for setting displayName and photoUrl.
@param completion The completion block to continue the automatic test flow.
*/
- (void)updateDisplayNameAndPhotoURlWithCompletion:(void(^)(void))completion {
FIRAuth *auth = [FIRAuth auth];
FIRUserProfileChangeRequest *changeRequest = [auth.currentUser profileChangeRequest];
changeRequest.photoURL = [NSURL URLWithString:kFakeDisplayPhotoUrl];
[changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"set photo URL failed" error:error];
[self logFailedTest:@"Change photo Url should succeed when signed-in."];
return;
}
[self logSuccess:@"set PhotoURL succeeded."];
FIRUserProfileChangeRequest *changeRequest = [auth.currentUser profileChangeRequest];
changeRequest.displayName = kFakeDisplayName;
[changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"set display name failed" error:error];
[self logFailedTest:@"Change display name should succeed when signed-in."];
return;
}
[self logSuccess:@"set display name succeeded."];
completion();
}];
}];
}
/** @fn addAuthStateListener
@brief Adds an auth state did change listener (block).
*/
- (void)addAuthStateListener {
__weak typeof(self) weakSelf = self;
NSUInteger index = _authStateDidChangeListeners.count;
[self log:[NSString stringWithFormat:@"Auth State Did Change Listener #%lu was added.",
(unsigned long)index]];
FIRAuthStateDidChangeListenerHandle handle =
[[FIRAuth auth] addAuthStateDidChangeListener:^(FIRAuth *_Nonnull auth,
FIRUser *_Nullable user) {
[weakSelf log:[NSString stringWithFormat:
@"Auth State Did Change Listener #%lu was invoked on user '%@'.",
(unsigned long)index, user.uid]];
}];
[_authStateDidChangeListeners addObject:handle];
}
/** @fn removeAuthStateListener
@brief Removes an auth state did change listener (block).
*/
- (void)removeAuthStateListener {
if (!_authStateDidChangeListeners.count) {
[self log:@"No remaining Auth State Did Change Listeners."];
return;
}
NSUInteger index = _authStateDidChangeListeners.count - 1;
FIRAuthStateDidChangeListenerHandle handle = _authStateDidChangeListeners.lastObject;
[[FIRAuth auth] removeAuthStateDidChangeListener:handle];
[_authStateDidChangeListeners removeObject:handle];
NSString *logString =
[NSString stringWithFormat:@"Auth State Did Change Listener #%lu was removed.",
(unsigned long)index];
[self log:logString];
}
/** @fn addIDTokenListener
@brief Adds an ID token did change listener (block).
*/
- (void)addIDTokenListener {
__weak typeof(self) weakSelf = self;
NSUInteger index = _IDTokenDidChangeListeners.count;
[self log:[NSString stringWithFormat:@"ID Token Did Change Listener #%lu was added.",
(unsigned long)index]];
FIRIDTokenDidChangeListenerHandle handle =
[[FIRAuth auth] addIDTokenDidChangeListener:^(FIRAuth *_Nonnull auth,
FIRUser *_Nullable user) {
[weakSelf log:[NSString stringWithFormat:
@"ID Token Did Change Listener #%lu was invoked on user '%@'.",
(unsigned long)index, user.uid]];
}];
[_IDTokenDidChangeListeners addObject:handle];
}
/** @fn removeIDTokenListener
@brief Removes an ID token did change listener (block).
*/
- (void)removeIDTokenListener {
if (!_IDTokenDidChangeListeners.count) {
[self log:@"No remaining ID Token Did Change Listeners."];
return;
}
NSUInteger index = _IDTokenDidChangeListeners.count - 1;
FIRIDTokenDidChangeListenerHandle handle = _IDTokenDidChangeListeners.lastObject;
[[FIRAuth auth] removeIDTokenDidChangeListener:handle];
[_IDTokenDidChangeListeners removeObject:handle];
NSString *logString =
[NSString stringWithFormat:@"ID Token Did Change Listener #%lu was removed.",
(unsigned long)index];
[self log:logString];
}
/** @fn log:
@brief Prints a log message to the sample app console.
@param string The string to add to the console.
*/
- (void)log:(NSString *)string {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSString *date = [dateFormatter stringFromDate:[NSDate date]];
if (!_consoleString) {
_consoleString = [NSMutableString string];
}
[_consoleString appendString:[NSString stringWithFormat:@"%@ %@\n", date, string]];
_consoleTextView.text = _consoleString;
CGRect targetRect = CGRectMake(0, _consoleTextView.contentSize.height - 1, 1, 1);
[_consoleTextView scrollRectToVisible:targetRect animated:YES];
}
/** @fn logSuccess:
@brief Wraps a string into a succeful log message format.
@param string Part of the log message.
@remarks The @string parameter should be a string ending with a period, as it is the end of the
log message.
*/
- (void)logSuccess:(NSString *)string {
[self log:[NSString stringWithFormat:@"SUCCESS: %@", string]];
}
/** @fn logFailure:
@brief Wraps a string into a failed log message format.
@param string Part of the message to wrap.
@remarks The @string parameter should be a string that NEVER ends with a period, as it is
guaranteed not to be the last part fo the log message.
*/
- (void)logFailure:(NSString *)string error:(NSError *) error {
NSString *message =
[NSString stringWithFormat:@"FAILURE: %@ Error Description: %@.", string, error.description];
[self log:message];
}
/** @fn logTestFailed
@brief Logs test failure to the console.
@param reason The reason why the test is considered a failure.
@remarks The calling method should immediately terminate after invoking this method i.e by
return statement or end of fucntions. The @reason parameter should be a string ending with a
period, as it is the end of the log message.
*/
- (void)logFailedTest:( NSString *_Nonnull )reason {
[self log:[NSString stringWithFormat:@"FAILIURE: TEST FAILED - %@", reason]];
}
/** @fn presentSettings
@brief Invoked when the settings row is pressed.
*/
- (void)presentSettings {
SettingsViewController *settingsViewController = [[SettingsViewController alloc]
initWithNibName:NSStringFromClass([SettingsViewController class])
bundle:nil];
[self presentViewController:settingsViewController animated:YES completion:nil];
}
/** @fn presentUserInfo
@brief Invoked when the user info row is pressed.
*/
- (void)presentUserInfo {
UserInfoViewController *userInfoViewController =
[[UserInfoViewController alloc] initWithUser:[FIRAuth auth].currentUser];
[self presentViewController:userInfoViewController animated:YES completion:nil];
}
/** @fn presentUserInMemoryInfo
@brief Invoked when the user in memory info row is pressed.
*/
- (void)presentUserInMemoryInfo {
UserInfoViewController *userInfoViewController =
[[UserInfoViewController alloc] initWithUser:_userInMemory];
[self presentViewController:userInfoViewController animated:YES completion:nil];
}
/** @fn signInGoogle
@brief Invoked when "Sign in with Google" row is pressed.
*/
- (void)signInGoogle {
[self signinWithProvider:[AuthProviders google] retrieveData:NO];
}
/** @fn signInGoogleAndRetrieveData
@brief Invoked when "Sign in with Google and retrieve data" row is pressed.
*/
- (void)signInGoogleAndRetrieveData {
[self signinWithProvider:[AuthProviders google] retrieveData:YES];
}
/** @fn signInFacebook
@brief Invoked when "Sign in with Facebook" row is pressed.
*/
- (void)signInFacebook {
[self signinWithProvider:[AuthProviders facebook] retrieveData:NO];
}
/** @fn signInFacebookAndRetrieveData
@brief Invoked when "Sign in with Facebook and retrieve data" row is pressed.
*/
- (void)signInFacebookAndRetrieveData {
[self signinWithProvider:[AuthProviders facebook] retrieveData:YES];
}
/** @fn signInEmailPassword
@brief Invoked when "sign in with Email/Password" row is pressed.
*/
- (void)signInEmailPassword {
[self showTextInputPromptWithMessage:@"Email Address:"
keyboardType:UIKeyboardTypeEmailAddress
completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) {
if (!userPressedOK || !email.length) {
return;
}
[self showTextInputPromptWithMessage:@"Password:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable password) {
if (!userPressedOK) {
return;
}
FIRAuthCredential *credential =
[FIREmailAuthProvider credentialWithEmail:email
password:password];
[self showSpinner:^{
[[FIRAuth auth] signInWithCredential:credential
completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"sign-in with Email/Password failed" error:error];
} else {
[self logSuccess:@"sign-in with Email/Password succeeded."];
}
[self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
}];
}];
}];
}];
}];
}
/** @fn signUpNewEmail
@brief Invoked if sign-in is attempted with new email/password.
@remarks Should only be called if @c FIRAuthErrorCodeInvalidEmail is encountered on attepmt to
sign in with email/password.
*/
- (void)signUpNewEmail:(NSString *)email
password:(NSString *)password
callback:(nullable FIRAuthResultCallback)callback {
[[FIRAuth auth] createUserWithEmail:email
password:password
completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-up with Email/Password failed" error:error];
if (callback) {
callback(nil, error);
}
} else {
[self logSuccess:@"sign-up with Email/Password succeeded."];
if (callback) {
callback(user, nil);
}
}
[self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In" error:error];
}];
}
/** @fn signInWithCustomToken
@brief Signs the user in using a manually-entered custom token.
*/
- (void)signInWithCustomToken {
CustomTokenDataEntryViewControllerCompletion action =
^(BOOL cancelled, NSString *_Nullable userEnteredTokenText) {
if (cancelled) {
[self log:@"CANCELLED:sign-in with custom token cancelled."];
return;
}
[self doSignInWithCustomToken:userEnteredTokenText];
};
CustomTokenDataEntryViewController *dataEntryViewController =
[[CustomTokenDataEntryViewController alloc] initWithCompletion:action];
[self presentViewController:dataEntryViewController animated:YES completion:nil];
}
/** @fn signOut
@brief Signs the user out.
*/
- (void)signOut {
[[AuthProviders google] signOut];
[[AuthProviders facebook] signOut];
[[FIRAuth auth] signOut:NULL];
}
/** @fn deleteAccount
@brief Deletes the current user account and signs the user out.
*/
- (void)deleteAccount {
FIRUser *user = [self user];
[user deleteWithCompletion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"delete account failed" error:error];
}
[self showTypicalUIForUserUpdateResultsWithTitle:kDeleteUserText error:error];
}];
}
/** @fn reauthenticateGoogle
@brief Asks the user to reauthenticate with Google.
*/
- (void)reauthenticateGoogle {
[self reauthenticate:[AuthProviders google] retrieveData:NO];
}
/** @fn reauthenticateGoogleAndRetrieveData
@brief Asks the user to reauthenticate with Google and retrieve additional data.
*/
- (void)reauthenticateGoogleAndRetrieveData {
[self reauthenticate:[AuthProviders google] retrieveData:YES];
}
/** @fn reauthenticateFB
@brief Asks the user to reauthenticate with Facebook.
*/
- (void)reauthenticateFB {
[self reauthenticate:[AuthProviders facebook] retrieveData:NO];
}
/** @fn reauthenticateFBAndRetrieveData
@brief Asks the user to reauthenticate with Facebook and retrieve additional data.
*/
- (void)reauthenticateFBAndRetrieveData {
[self reauthenticate:[AuthProviders facebook] retrieveData:YES];
}
/** @fn reauthenticateEmailPassword
@brief Asks the user to reauthenticate with email/password.
*/
- (void)reauthenticateEmailPassword {
FIRUser *user = [self user];
if (!user) {
NSString *title = @"Missing User";
NSString *message = @"There is no signed-in email/password user.";
[self showMessagePromptWithTitle:title message:message showCancelButton:NO completion:nil];
return;
}
[self showEmailPasswordDialogWithCompletion:^(FIRAuthCredential *credential) {
[self showSpinner:^{
[[self user] reauthenticateWithCredential:credential
completion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"reauthicate with email/password failed" error:error];
} else {
[self logSuccess:@"reauthicate with email/password succeeded."];
}
[self hideSpinner:^{
[self showTypicalUIForUserUpdateResultsWithTitle:kReauthenticateEmailText error:error];
}];
}];
}];
}];
}
/** @fn reauthenticate:
@brief Reauthenticates user.
@param authProvider The auth provider to use for reauthentication.
@param retrieveData Defines if additional provider data should be read.
*/
- (void)reauthenticate:(id<AuthProvider>)authProvider retrieveData:(BOOL)retrieveData {
FIRUser *user = [self user];
if (!user) {
NSString *provider = @"Firebase";
if ([authProvider isKindOfClass:[GoogleAuthProvider class]]) {
provider = @"Google";
} else if ([authProvider isKindOfClass:[FacebookAuthProvider class]]) {
provider = @"Facebook";
}
NSString *title = @"Missing User";
NSString *message =
[NSString stringWithFormat:@"There is no signed-in %@ user.", provider];
[self showMessagePromptWithTitle:title message:message showCancelButton:NO completion:nil];
return;
}
[authProvider getAuthCredentialWithPresentingViewController:self
callback:^(FIRAuthCredential *credential,
NSError *error) {
if (credential) {
FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"reauthenticate operation failed" error:error];
} else {
[self logSuccess:@"reauthenticate operation succeeded."];
}
if (authResult.additionalUserInfo) {
[self logSuccess:
[NSString stringWithFormat:@"%@", authResult.additionalUserInfo.profile]];
}
[self showTypicalUIForUserUpdateResultsWithTitle:@"Reauthenticate" error:error];
};
FIRUserProfileChangeCallback callback = ^(NSError *_Nullable error) {
completion(nil, error);
};
if (retrieveData) {
[user reauthenticateAndRetrieveDataWithCredential:credential completion:completion];
} else {
[user reauthenticateWithCredential:credential completion:callback];
}
}
}];
}
/** @fn signinWithProvider:
@brief Signs in the user with provided auth provider.
@param authProvider The auth provider to use for sign-in.
@param retrieveData Defines if additional provider data should be read.
*/
- (void)signinWithProvider:(id<AuthProvider>)authProvider retrieveData:(BOOL)retrieveData {
FIRAuth *auth = [FIRAuth auth];
if (!auth) {
return;
}
[authProvider getAuthCredentialWithPresentingViewController:self
callback:^(FIRAuthCredential *credential,
NSError *error) {
if (credential) {
FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in with provider failed" error:error];
} else {
[self logSuccess:@"sign-in with provider succeeded."];
}
if (authResult.additionalUserInfo) {
[self logSuccess:
[NSString stringWithFormat:@"%@", authResult.additionalUserInfo.profile]];
}
[self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In" error:error];
};
FIRAuthResultCallback callback = ^(FIRUser *_Nullable user,
NSError *_Nullable error) {
completion(nil, error);
};
if (retrieveData) {
[auth signInAndRetrieveDataWithCredential:credential completion:completion];
} else {
[auth signInWithCredential:credential completion:callback];
}
}
}];
}
/** @fn tokenCallback
@return A callback block to show the token.
*/
- (FIRAuthTokenCallback)tokenCallback {
return ^(NSString *_Nullable token, NSError *_Nullable error) {
if (error) {
[self showMessagePromptWithTitle:kTokenRefreshErrorAlertTitle
message:error.localizedDescription
showCancelButton:NO
completion:nil];
[self logFailure:@"refresh token failed" error:error];
return;
}
[self logSuccess:@"refresh token succeeded."];
[self showMessagePromptWithTitle:kTokenRefreshedAlertTitle
message:token
showCancelButton:NO
completion:nil];
};
}
/** @fn getUserTokenWithForce:
@brief Gets the token from @c FIRUser , optionally a refreshed one.
@param force Whether the refresh is forced or not.
*/
- (void)getUserTokenWithForce:(BOOL)force {
[[self user] getIDTokenForcingRefresh:force completion:[self tokenCallback]];
}
/** @fn getAppTokenWithForce:
@brief Gets the token from @c FIRApp , optionally a refreshed one.
@param force Whether the refresh is forced or not.
*/
- (void)getAppTokenWithForce:(BOOL)force {
[[FIRApp defaultApp] getTokenForcingRefresh:force withCallback:[self tokenCallback]];
}
/** @fn setDisplayName
@brief Changes the display name of the current user.
*/
- (void)setDisplayName {
[self showTextInputPromptWithMessage:@"Display Name:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
if (!userPressedOK || !userInput.length) {
return;
}
[self showSpinner:^{
FIRUserProfileChangeRequest *changeRequest = [[self user] profileChangeRequest];
changeRequest.displayName = userInput;
[changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"set display name failed" error:error];
} else {
[self logSuccess:@"set display name succeeded."];
}
[self showTypicalUIForUserUpdateResultsWithTitle:kSetDisplayNameTitle error:error];
}];
}];
}];
}];
}
/** @fn setPhotoURL
@brief Changes the photo url of the current user.
*/
- (void)setPhotoURL {
[self showTextInputPromptWithMessage:@"Photo URL:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
if (!userPressedOK || !userInput.length) {
return;
}
[self showSpinner:^{
FIRUserProfileChangeRequest *changeRequest = [[self user] profileChangeRequest];
changeRequest.photoURL = [NSURL URLWithString:userInput];
[changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"set photo URL failed" error:error];
} else {
[self logSuccess:@"set Photo URL succeeded."];
}
[self hideSpinner:^{
[self showTypicalUIForUserUpdateResultsWithTitle:kSetPhotoURLText error:error];
}];
}];
}];
}];
}
/** @fn reloadUser
@brief Reloads the user from server.
*/
- (void)reloadUser {
[self showSpinner:^() {
[[self user] reloadWithCompletion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"reload user failed" error:error];
} else {
[self logSuccess:@"reload user succeeded."];
}
[self hideSpinner:^() {
[self showTypicalUIForUserUpdateResultsWithTitle:kReloadText error:error];
}];
}];
}];
}
/** @fn linkWithAuthProvider:retrieveData:
@brief Asks the user to sign in with an auth provider and link the current user with it.
@param authProvider The auth provider to sign in and link with.
@param retrieveData Defines if additional provider data should be read.
*/
- (void)linkWithAuthProvider:(id<AuthProvider>)authProvider retrieveData:(BOOL)retrieveData {
FIRUser *user = [self user];
if (!user) {
return;
}
[authProvider getAuthCredentialWithPresentingViewController:self
callback:^(FIRAuthCredential *credential,
NSError *error) {
if (credential) {
FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"link auth provider failed" error:error];
} else {
[self logSuccess:@"link auth provider succeeded."];
}
if (authResult.additionalUserInfo) {
[self logSuccess:
[NSString stringWithFormat:@"%@", authResult.additionalUserInfo.profile]];
}
if (retrieveData) {
[self showUIForAuthDataResultWithResult:authResult error:error];
} else {
[self showTypicalUIForUserUpdateResultsWithTitle:@"Link Account" error:error];
}
};
FIRAuthResultCallback callback = ^(FIRUser *_Nullable user,
NSError *_Nullable error) {
completion(nil, error);
};
if (retrieveData) {
[user linkAndRetrieveDataWithCredential:credential completion:completion];
} else {
[user linkWithCredential:credential completion:callback];
}
}
}];
}
/** @fn linkWithGoogle
@brief Asks the user to sign in with Google and link the current user with this Google account.
*/
- (void)linkWithGoogle {
[self linkWithAuthProvider:[AuthProviders google] retrieveData:NO];
}
/** @fn linkWithGoogleAndRetrieveData
@brief Asks the user to sign in with Google and link the current user with this Google account
and retrieve additional data.
*/
- (void)linkWithGoogleAndRetrieveData {
[self linkWithAuthProvider:[AuthProviders google] retrieveData:YES];
}
/** @fn linkWithFacebook
@brief Asks the user to sign in with Facebook and link the current user with this Facebook
account.
*/
- (void)linkWithFacebook {
[self linkWithAuthProvider:[AuthProviders facebook] retrieveData:NO];
}
/** @fn linkWithFacebookAndRetrieveData
@brief Asks the user to sign in with Facebook and link the current user with this Facebook
account and retrieve additional data.
*/
- (void)linkWithFacebookAndRetrieveData {
[self linkWithAuthProvider:[AuthProviders facebook] retrieveData:YES];
}
/** @fn linkWithEmailPassword
@brief Asks the user to sign in with Facebook and link the current user with this Facebook
account.
*/
- (void)linkWithEmailPassword {
[self showEmailPasswordDialogWithCompletion:^(FIRAuthCredential *credential) {
[self showSpinner:^{
[[self user] linkWithCredential:credential
completion:^(FIRUser *user, NSError *_Nullable error) {
if (error) {
[self logFailure:@"link Email/Password failed" error:error];
} else {
[self logSuccess:@"link Email/Password succeeded."];
}
[self hideSpinner:^{
[self showTypicalUIForUserUpdateResultsWithTitle:kLinkWithEmailPasswordText error:error];
}];
}];
}];
}];
}
/** @fn showEmailPasswordDialogWithCompletion:
@brief shows email/password input dialog.
@param completion The completion block that will do some operation on the credential email
/passwowrd credential obtained.
*/
- (void)showEmailPasswordDialogWithCompletion:(ShowEmailPasswordDialogCompletion)completion {
[self showTextInputPromptWithMessage:@"Email Address:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) {
if (!userPressedOK || !email.length) {
return;
}
[self showTextInputPromptWithMessage:@"Password:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable password) {
if (!userPressedOK || !password.length) {
return;
}
FIRAuthCredential *credential = [FIREmailAuthProvider credentialWithEmail:email
password:password];
completion(credential);
}];
}];
}
/** @fn unlinkFromProvider:
@brief Unlinks the current user from the provider with the specified provider ID.
@param provider The provider ID of the provider to unlink the current user's account from.
*/
- (void)unlinkFromProvider:(NSString *)provider {
[[self user] unlinkFromProvider:provider
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"unlink auth provider failed" error:error];
} else {
[self logSuccess:@"unlink auth provider succeeded."];
}
[self showTypicalUIForUserUpdateResultsWithTitle:kUnlinkTitle error:error];
}];
}
/** @fn getProvidersForEmail
@brief Prompts the user for an email address, calls @c FIRAuth.getProvidersForEmail:callback:
and displays the result.
*/
- (void)getProvidersForEmail {
[self showTextInputPromptWithMessage:@"Email:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
if (!userPressedOK || !userInput.length) {
return;
}
[self showSpinner:^{
[[FIRAuth auth] fetchProvidersForEmail:userInput
completion:^(NSArray<NSString *> *_Nullable providers,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"get providers for email failed" error:error];
} else {
[self logSuccess:@"get providers for email succeeded."];
}
[self hideSpinner:^{
if (error) {
[self showMessagePrompt:error.localizedDescription];
return;
}
[self showMessagePrompt:[providers componentsJoinedByString:@", "]];
}];
}];
}];
}];
}
/** @fn requestVerifyEmail
@brief Requests a "verify email" email be sent.
*/
- (void)requestVerifyEmail {
[self showSpinner:^{
[[self user] sendEmailVerificationWithCompletion:^(NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"request verify email failed" error:error];
[self showMessagePrompt:error.localizedDescription];
return;
}
[self logSuccess:@"request verify email succeeded."];
[self showMessagePrompt:@"Sent"];
}];
}];
}];
}
/** @fn requestPasswordReset
@brief Requests a "password reset" email be sent.
*/
- (void)requestPasswordReset {
[self showTextInputPromptWithMessage:@"Email:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
if (!userPressedOK || !userInput.length) {
return;
}
[self showSpinner:^{
[[FIRAuth auth] sendPasswordResetWithEmail:userInput completion:^(NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"request password reset failed" error:error];
[self showMessagePrompt:error.localizedDescription];
return;
}
[self logSuccess:@"request password reset succeeded."];
[self showMessagePrompt:@"Sent"];
}];
}];
}];
}];
}
/** @fn resetPassword
@brief Performs a password reset operation.
*/
- (void)resetPassword {
[self showTextInputPromptWithMessage:@"OOB Code:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
if (!userPressedOK || !userInput.length) {
return;
}
NSString *code = userInput;
[self showTextInputPromptWithMessage:@"New Password:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
if (!userPressedOK || !userInput.length) {
return;
}
[self showSpinner:^{
[[FIRAuth auth] confirmPasswordResetWithCode:code
newPassword:userInput
completion:^(NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"Password reset failed" error:error];
[self showMessagePrompt:error.localizedDescription];
return;
}
[self logSuccess:@"Password reset succeeded."];
[self showMessagePrompt:@"Password reset succeeded."];
}];
}];
}];
}];
}];
}
/** @fn checkActionCode
@brief Performs a "check action code" operation.
*/
- (void)checkActionCode {
[self showTextInputPromptWithMessage:@"OOB Code:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
if (!userPressedOK || !userInput.length) {
return;
}
[self showSpinner:^{
[[FIRAuth auth] checkActionCode:userInput completion:^(FIRActionCodeInfo *_Nullable info,
NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"Check action code failed" error:error];
[self showMessagePrompt:error.localizedDescription];
return;
}
[self logSuccess:@"Check action code succeeded."];
NSString *email = [info dataForKey:FIRActionCodeEmailKey];
NSString *operation = [self nameForActionCodeOperation:info.operation];
NSString *infoMessage =
[[NSString alloc] initWithFormat:@"Email: %@\n Operation: %@", email, operation];
[self showMessagePrompt:infoMessage];
}];
}];
}];
}];
}
/** @fn applyActionCode
@brief Performs a "apply action code" operation.
*/
- (void)applyActionCode {
[self showTextInputPromptWithMessage:@"OOB Code:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
if (!userPressedOK || !userInput.length) {
return;
}
[self showSpinner:^{
[[FIRAuth auth] applyActionCode:userInput completion:^(NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"Apply action code failed" error:error];
[self showMessagePrompt:error.localizedDescription];
return;
}
[self logSuccess:@"Apply action code succeeded."];
[self showMessagePrompt:@"Action code was properly applied."];
}];
}];
}];
}];
}
/** @fn verifyPasswordResetCode
@brief Performs a "verify password reset code" operation.
*/
- (void)verifyPasswordResetCode {
[self showTextInputPromptWithMessage:@"OOB Code:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
if (!userPressedOK || !userInput.length) {
return;
}
[self showSpinner:^{
[[FIRAuth auth] verifyPasswordResetCode:userInput completion:^(NSString *_Nullable email,
NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"Verify password reset code failed" error:error];
[self showMessagePrompt:error.localizedDescription];
return;
}
[self logSuccess:@"Verify password resest code succeeded."];
NSString *alertMessage =
[[NSString alloc] initWithFormat:@"Code verified for email: %@", email];
[self showMessagePrompt:alertMessage];
}];
}];
}];
}];
}
/** @fn nameForActionCodeOperation
@brief Returns the string value of the provided FIRActionCodeOperation value.
@param operation the FIRActionCodeOperation value to convert to string.
@return String conversion of FIRActionCodeOperation value.
*/
- (NSString *)nameForActionCodeOperation:(FIRActionCodeOperation)operation {
switch (operation) {
case FIRActionCodeOperationVerifyEmail:
return @"Verify Email";
case FIRActionCodeOperationPasswordReset:
return @"Password Reset";
case FIRActionCodeOperationUnknown:
return @"Unknown action";
}
}
/** @fn updateEmail
@brief Changes the email address of the current user.
*/
- (void)updateEmail {
[self showTextInputPromptWithMessage:@"Email Address:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
if (!userPressedOK || !userInput.length) {
return;
}
[self showSpinner:^{
[[self user] updateEmail:userInput completion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"update email failed" error:error];
} else {
[self logSuccess:@"update email succeeded."];
}
[self hideSpinner:^{
[self showTypicalUIForUserUpdateResultsWithTitle:kUpdateEmailText error:error];
}];
}];
}];
}];
}
/** @fn updatePassword
@brief Updates the password of the current user.
*/
- (void)updatePassword {
[self showTextInputPromptWithMessage:@"New Password:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) {
if (!userPressedOK) {
return;
}
[self showSpinner:^{
[[self user] updatePassword:userInput completion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"update password failed" error:error];
} else {
[self logSuccess:@"update password succeeded."];
}
[self hideSpinner:^{
[self showTypicalUIForUserUpdateResultsWithTitle:kUpdatePasswordText error:error];
}];
}];
}];
}];
}
/** @fn createUser
@brief Creates a new user.
*/
- (void)createUser {
[self showTextInputPromptWithMessage:@"Email:"
keyboardType:UIKeyboardTypeEmailAddress
completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) {
if (!userPressedOK || !email.length) {
return;
}
[self showTextInputPromptWithMessage:@"Password:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable password) {
if (!userPressedOK) {
return;
}
[self showSpinner:^{
[[FIRAuth auth] createUserWithEmail:email
password:password
completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (error) {
[self logFailure:@"create user failed" error:error];
} else {
[self logSuccess:@"create user succeeded."];
}
[self hideSpinner:^{
[self showTypicalUIForUserUpdateResultsWithTitle:kCreateUserTitle error:error];
}];
}];
}];
}];
}];
}
/** @fn signInWithPhoneNumber
@brief Allows sign in with phone number.
*/
- (void)signInWithPhoneNumber {
[self showTextInputPromptWithMessage:@"Phone #:"
keyboardType:UIKeyboardTypePhonePad
completionBlock:^(BOOL userPressedOK, NSString *_Nullable phoneNumber) {
if (!userPressedOK || !phoneNumber.length) {
return;
}
[self showSpinner:^{
[[FIRPhoneAuthProvider provider] verifyPhoneNumber:phoneNumber
completion:^(NSString *_Nullable verificationID,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"failed to send verification code" error:error];
return;
}
[self logSuccess:@"Code sent"];
[self showTextInputPromptWithMessage:@"Verification code:"
keyboardType:UIKeyboardTypeNumberPad
completionBlock:^(BOOL userPressedOK,
NSString *_Nullable verificationCode) {
if (!userPressedOK || !verificationCode.length) {
return;
}
[self showSpinner:^{
FIRAuthCredential *credential =
[[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID
verificationCode:verificationCode];
[[FIRAuth auth] signInWithCredential:credential
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"failed to verify phone number" error:error];
return;
}
}];
}];
}];
[self hideSpinner:^{
[self showTypicalUIForUserUpdateResultsWithTitle:kCreateUserTitle error:error];
}];
}];
}];
}];
}
/** @fn updatePhoneNumber
@brief Allows adding a verified phone number to the currently signed user.
*/
- (void)updatePhoneNumber {
[self showTextInputPromptWithMessage:@"Phone #:"
keyboardType:UIKeyboardTypePhonePad
completionBlock:^(BOOL userPressedOK, NSString *_Nullable phoneNumber) {
if (!userPressedOK || !phoneNumber.length) {
return;
}
[self showSpinner:^{
[[FIRPhoneAuthProvider provider] verifyPhoneNumber:phoneNumber
completion:^(NSString *_Nullable verificationID,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"failed to send verification code" error:error];
return;
}
[self logSuccess:@"Code sent"];
[self showTextInputPromptWithMessage:@"Verification code:"
keyboardType:UIKeyboardTypeNumberPad
completionBlock:^(BOOL userPressedOK,
NSString *_Nullable verificationCode) {
if (!userPressedOK || !verificationCode.length) {
return;
}
[self showSpinner:^{
FIRPhoneAuthCredential *credential =
[[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID
verificationCode:verificationCode];
[[self user] updatePhoneNumberCredential:credential
completion:^(NSError *_Nullable error) {
if (error) {
[self logFailure:@"update phone number failed" error:error];
} else {
[self logSuccess:@"update phone number succeeded."];
}
}];
}];
}];
[self hideSpinner:^{
[self showTypicalUIForUserUpdateResultsWithTitle:kCreateUserTitle error:error];
}];
}];
}];
}];
}
/** @fn linkPhoneNumber
@brief Allows linking a verified phone number to the currently signed user.
*/
- (void)linkPhoneNumber {
[self showTextInputPromptWithMessage:@"Phone #:"
keyboardType:UIKeyboardTypePhonePad
completionBlock:^(BOOL userPressedOK, NSString *_Nullable phoneNumber) {
if (!userPressedOK || !phoneNumber.length) {
return;
}
[self showSpinner:^{
[[FIRPhoneAuthProvider provider] verifyPhoneNumber:phoneNumber
completion:^(NSString *_Nullable verificationID,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"failed to send verification code" error:error];
return;
}
[self logSuccess:@"Code sent"];
[self showTextInputPromptWithMessage:@"Verification code:"
keyboardType:UIKeyboardTypeNumberPad
completionBlock:^(BOOL userPressedOK,
NSString *_Nullable verificationCode) {
if (!userPressedOK || !verificationCode.length) {
return;
}
[self showSpinner:^{
FIRPhoneAuthCredential *credential =
[[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID
verificationCode:verificationCode];
[[self user] linkWithCredential:credential
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
if (error.code == FIRAuthErrorCodeCredentialAlreadyInUse) {
[self showMessagePromptWithTitle:@"Phone number is already linked to another user"
message:@"Tap Ok to sign in with that user now."
showCancelButton:YES
completion:^(BOOL userPressedOK,
NSString *_Nullable userInput) {
if (userPressedOK) {
// If FIRAuthErrorCodeCredentialAlreadyInUse error, sign in with the provided
// credential.
FIRPhoneAuthCredential *credential =
error.userInfo[FIRAuthUpdatedCredentialKey];
[[FIRAuth auth] signInWithCredential:credential
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"failed to verify phone number" error:error];
return;
}
}];
}
}];
} else {
[self logFailure:@"link phone number failed" error:error];
}
return;
}
[self logSuccess:@"link phone number succeeded."];
}];
}];
}];
[self hideSpinner:^{
[self showTypicalUIForUserUpdateResultsWithTitle:kCreateUserTitle error:error];
}];
}];
}];
}];
}
/** @fn signInAnonymously
@brief Signs in as an anonymous user.
*/
- (void)signInAnonymously {
[[FIRAuth auth] signInAnonymouslyWithCompletion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in anonymously failed" error:error];
} else {
[self logSuccess:@"sign-in anonymously succeeded."];
}
[self showTypicalUIForUserUpdateResultsWithTitle:kSignInAnonymouslyButtonText error:error];
}];
}
/** @fn signInWithGitHub
@brief Signs in as a GitHub user. Prompts the user for an access token and uses this access
token to create a GitHub (generic) credential for signing in.
*/
- (void)signInWithGitHub {
[self showTextInputPromptWithMessage:@"GitHub Access Token:"
completionBlock:^(BOOL userPressedOK, NSString *_Nullable accessToken) {
if (!userPressedOK || !accessToken.length) {
return;
}
FIRAuthCredential *credential =
[FIROAuthProvider credentialWithProviderID:FIRGitHubAuthProviderID accessToken:accessToken];
if (credential) {
[[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in with provider failed" error:error];
} else {
[self logSuccess:@"sign-in with provider succeeded."];
}
[self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In" error:error];
}];
}
}];
}
/** @fn deleteApp
@brief Deletes the @c FIRApp associated with our @c FIRAuth instance.
*/
- (void)deleteApp {
[[FIRApp defaultApp] deleteApp:^(BOOL success) {
[self log:success ? @"App deleted successfully." : @"Failed to delete app."];
}];
}
#pragma mark - Helpers
/** @fn user
@brief The user to use for user operations. Takes into account the "use signed-in user vs. use
user in memory" setting.
*/
- (FIRUser *)user {
return _useUserInMemory ? _userInMemory : [FIRAuth auth].currentUser;
}
/** @fn showTypicalUIForUserUpdateResultsWithTitle:error:
@brief Shows a prompt if error is non-nil with the localized description of the error.
@param resultsTitle The title of the prompt
@param error The error details to display if non-nil.
*/
- (void)showTypicalUIForUserUpdateResultsWithTitle:(NSString *)resultsTitle
error:(NSError *)error {
if (error) {
NSString *message = [NSString stringWithFormat:@"%@ (%ld)\n%@",
error.domain,
(long)error.code,
error.localizedDescription];
if (error.code == FIRAuthErrorCodeAccountExistsWithDifferentCredential) {
NSString *errorEmail = error.userInfo[FIRAuthErrorUserInfoEmailKey];
resultsTitle = [NSString stringWithFormat:@"Existing email : %@", errorEmail];
}
[self showMessagePromptWithTitle:resultsTitle
message:message
showCancelButton:NO
completion:nil];
return;
}
[self updateUserInfo];
}
/** @fn showUIForAuthDataResultWithResult:error:
@brief Shows a prompt if error is non-nil with the localized description of the error.
@param result The auth data result if non-nil.
@param error The error details to display if non-nil.
*/
- (void)showUIForAuthDataResultWithResult:(FIRAuthDataResult *)result
error:(NSError *)error {
NSString *errorMessage = [NSString stringWithFormat:@"%@ (%ld)\n%@",
error.domain ?: @"",
(long)error.code,
error.localizedDescription ?: @""];
[self showMessagePromptWithTitle:@"Error"
message:errorMessage
showCancelButton:NO
completion:^(BOOL userPressedOK,
NSString *_Nullable userInput) {
NSString *profileMessaage =
[NSString stringWithFormat:@"%@", result.additionalUserInfo.profile ?: @""];
[self showMessagePromptWithTitle:@"Profile Info"
message:profileMessaage
showCancelButton:NO
completion:nil];
[self updateUserInfo];
}];
}
- (void)doSignInWithCustomToken:(NSString *_Nullable)userEnteredTokenText {
[[FIRAuth auth] signInWithCustomToken:userEnteredTokenText
completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in with custom token failed" error:error];
[self showMessagePromptWithTitle:kSignInErrorAlertTitle
message:error.localizedDescription
showCancelButton:NO
completion:nil];
return;
}
[self logSuccess:@"sign-in with custom token succeeded."];
[self showMessagePromptWithTitle:kSignedInAlertTitle
message:user.displayName
showCancelButton:NO
completion:nil];
}];
}
- (void)updateUserInfo {
[_userInfoTableViewCell updateContentsWithUser:[FIRAuth auth].currentUser];
[_userInMemoryInfoTableViewCell updateContentsWithUser:_userInMemory];
}
- (void)authStateChangedForAuth:(NSNotification *)notification {
[self updateUserInfo];
if (notification) {
[self log:[NSString stringWithFormat:
@"received FIRAuthStateDidChange notification on user '%@'.",
((FIRAuth *)notification.object).currentUser.uid]];
}
}
/** @fn clearConsole
@brief Clears the console text.
*/
- (IBAction)clearConsole:(id)sender {
[_consoleString appendString:@"\n\n"];
_consoleTextView.text = @"";
}
/** @fn copyConsole
@brief Copies the current console string to the clipboard.
*/
- (IBAction)copyConsole:(id)sender {
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.string = _consoleString ?: @"";
}
@end