From 54aef651d82ebcc529498e4eba945a5512af4c95 Mon Sep 17 00:00:00 2001 From: Zsika Phillip Date: Mon, 20 Nov 2017 15:56:03 -0800 Subject: Add Auth Result to createUserWithEmail (#477) --- Example/Auth/Sample/MainViewController.m | 44 +++++++++++++++++ Example/Auth/Tests/FIRAuthTests.m | 62 ++++++++++++++++++++++++ Firebase/Auth/Source/FIRAuth.m | 82 +++++++++++++++++++++++++------- Firebase/Auth/Source/Public/FIRAuth.h | 34 +++++++++++++ 4 files changed, 205 insertions(+), 17 deletions(-) diff --git a/Example/Auth/Sample/MainViewController.m b/Example/Auth/Sample/MainViewController.m index 4708b4c..eb45226 100644 --- a/Example/Auth/Sample/MainViewController.m +++ b/Example/Auth/Sample/MainViewController.m @@ -384,6 +384,11 @@ static NSString *const kSectionTitleApp = @"APP"; */ static NSString *const kCreateUserTitle = @"Create User"; +/** @var kCreateUserAuthDataResultTitle + @brief The text of the "Create User (AuthDataResult)" button. + */ +static NSString *const kCreateUserAuthDataResultTitle = @"Create User (AuthDataResult)"; + /** @var kDeleteAppTitle @brief The text of the "Delete App" button. */ @@ -694,6 +699,8 @@ typedef enum { value:nil action:^{ [weakSelf createUser]; } accessibilityID:kCreateUserAccessibilityID], + [StaticContentTableViewCell cellWithTitle:kCreateUserAuthDataResultTitle + action:^{ [weakSelf createUserAuthDataResult]; }], [StaticContentTableViewCell cellWithTitle:kSignInGoogleButtonText action:^{ [weakSelf signInGoogle]; }], [StaticContentTableViewCell cellWithTitle:kSignInGoogleAndRetrieveDataButtonText @@ -2493,6 +2500,43 @@ static NSDictionary *parseURL(NSString *urlString) { }]; } +/** @fn createUserAuthDataResult + @brief Creates a new user. + */ +- (void)createUserAuthDataResult { + [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:^{ + [[AppManager auth] createUserAndRetrieveDataWithEmail:email + password:password + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { + if (error) { + [self logFailure:@"create user failed" error:error]; + } else { + [self logSuccess:@"create user succeeded."]; + [self log:result.user.uid]; + } + [self hideSpinner:^{ + [self showTypicalUIForUserUpdateResultsWithTitle:kCreateUserTitle error:error]; + }]; + }]; + }]; + }]; + }]; +} + /** @fn signInWithPhoneNumber @brief Allows sign in with phone number. */ diff --git a/Example/Auth/Tests/FIRAuthTests.m b/Example/Auth/Tests/FIRAuthTests.m index 557f0c7..2842ef3 100644 --- a/Example/Auth/Tests/FIRAuthTests.m +++ b/Example/Auth/Tests/FIRAuthTests.m @@ -1243,6 +1243,68 @@ static const NSTimeInterval kWaitInterval = .5; OCMVerifyAll(_mockBackend); } +/** @fn testCreateUserAndRetrieveDataWithEmailPasswordSuccess + @brief Tests the flow of a successful @c createUserAndRetrieveDataWithEmail:password:completion: + call. + */ +- (void)testCreateUserAndRetrieveDataWithEmailPasswordSuccess { + OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]]) + .andCallBlock2(^(FIRSignUpNewUserRequest *_Nullable request, + FIRSignupNewUserCallback callback) { + XCTAssertEqualObjects(request.APIKey, kAPIKey); + XCTAssertEqualObjects(request.email, kEmail); + XCTAssertEqualObjects(request.password, kFakePassword); + XCTAssertTrue(request.returnSecureToken); + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + id mockSignUpNewUserResponse = OCMClassMock([FIRSignUpNewUserResponse class]); + [self stubTokensWithMockResponse:mockSignUpNewUserResponse]; + callback(mockSignUpNewUserResponse, nil); + }); + }); + [self expectGetAccountInfo]; + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] signOut:NULL]; + [[FIRAuth auth] createUserAndRetrieveDataWithEmail:kEmail + password:kFakePassword + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + [self assertUser:result.user]; + XCTAssertTrue(result.additionalUserInfo.isNewUser); + XCTAssertNil(error); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + [self assertUser:[FIRAuth auth].currentUser]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testCreateUserAndRetrieveDataWithEmailPasswordFailure + @brief Tests the flow of a failed @c createUserAndRetrieveDataWithEmail:password:completion: + call. + */ +- (void)testCreateUserAndRetrieveDataWithEmailPasswordFailure { + NSString *reason = @"Password shouldn't be a common word."; + OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]]) + .andDispatchError2([FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:reason]); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] signOut:NULL]; + [[FIRAuth auth] createUserAndRetrieveDataWithEmail:kEmail + password:kFakePassword + completion:^(FIRAuthDataResult *_Nullable result, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertNil(result); + XCTAssertEqual(error.code, FIRAuthErrorCodeWeakPassword); + XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]); + XCTAssertEqualObjects(error.userInfo[NSLocalizedFailureReasonErrorKey], reason); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + XCTAssertNil([FIRAuth auth].currentUser); + OCMVerifyAll(_mockBackend); +} + /** @fn testCreateUserEmptyPasswordFailure @brief Tests the flow of a failed @c createUserWithEmail:password:completion: call due to an empty password. This error occurs on the client side, so there is no need to fake an RPC diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/FIRAuth.m index fda84fa..9020d3f 100644 --- a/Firebase/Auth/Source/FIRAuth.m +++ b/Firebase/Auth/Source/FIRAuth.m @@ -770,23 +770,10 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; dispatch_async(FIRAuthGlobalWorkQueue(), ^{ FIRAuthResultCallback decoratedCallback = [self signInFlowAuthResultCallbackByDecoratingCallback:completion]; - FIRSignUpNewUserRequest *request = - [[FIRSignUpNewUserRequest alloc] initWithEmail:email - password:password - displayName:nil - requestConfiguration:_requestConfiguration]; - if (![request.password length]) { - decoratedCallback(nil, [FIRAuthErrorUtils - weakPasswordErrorWithServerResponseReason:kMissingPasswordReason]); - return; - } - if (![request.email length]) { - decoratedCallback(nil, [FIRAuthErrorUtils missingEmailErrorWithMessage:nil]); - return; - } - [FIRAuthBackend signUpNewUser:request - callback:^(FIRSignUpNewUserResponse *_Nullable response, - NSError *_Nullable error) { + [self internalCreateUserWithEmail:email + password:password + completion:^(FIRSignUpNewUserResponse *_Nullable response, + NSError *_Nullable error) { if (error) { decoratedCallback(nil, error); return; @@ -800,6 +787,40 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; }); } +- (void)createUserAndRetrieveDataWithEmail:(NSString *)email + password:(NSString *)password + completion:(FIRAuthDataResultCallback)completion { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRAuthDataResultCallback decoratedCallback = + [self signInFlowAuthDataResultCallbackByDecoratingCallback:completion]; + [self internalCreateUserWithEmail:email + password:password + completion:^(FIRSignUpNewUserResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + decoratedCallback(nil, error); + return; + } + + [self completeSignInWithAccessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken + anonymous:NO + callback:^(FIRUser *_Nullable user, NSError *_Nullable error) { + FIRAdditionalUserInfo *additionalUserInfo = + [[FIRAdditionalUserInfo alloc] initWithProviderID:FIREmailAuthProviderID + profile:nil + username:nil + isNewUser:YES]; + FIRAuthDataResult *authDataResult = + [[FIRAuthDataResult alloc] initWithUser:user + additionalUserInfo:additionalUserInfo]; + decoratedCallback(authDataResult, nil); + }]; + }]; + }); +} + - (void)confirmPasswordResetWithCode:(NSString *)code newPassword:(NSString *)newPassword completion:(FIRConfirmPasswordResetCallback)completion { @@ -1130,6 +1151,33 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; } #endif +/** @fn internalCreateUserWithEmail:password:completion: + @brief Makes a backend request attempting to create a new Firebase user given an email address + and password. + @param email The email address used to create the new Firebase user. + @param password The password used to create the new Firebase user. + @param completion Optionally; a block which is invoked when the request finishes. + */ +- (void)internalCreateUserWithEmail:(NSString *)email + password:(NSString *)password + completion:(nullable FIRSignupNewUserCallback)completion { + FIRSignUpNewUserRequest *request = + [[FIRSignUpNewUserRequest alloc] initWithEmail:email + password:password + displayName:nil + requestConfiguration:_requestConfiguration]; + if (![request.password length]) { + completion(nil, [FIRAuthErrorUtils + weakPasswordErrorWithServerResponseReason:kMissingPasswordReason]); + return; + } + if (![request.email length]) { + completion(nil, [FIRAuthErrorUtils missingEmailErrorWithMessage:nil]); + return; + } + [FIRAuthBackend signUpNewUser:request callback:completion]; +} + /** @fn internalSignInAnonymouslyWithCompletion: @param completion A block which is invoked when the anonymous sign in request completes. */ diff --git a/Firebase/Auth/Source/Public/FIRAuth.h b/Firebase/Auth/Source/Public/FIRAuth.h index 2963d7b..1aa4bcf 100644 --- a/Firebase/Auth/Source/Public/FIRAuth.h +++ b/Firebase/Auth/Source/Public/FIRAuth.h @@ -484,6 +484,40 @@ FIR_SWIFT_NAME(Auth) password:(NSString *)password completion:(nullable FIRAuthResultCallback)completion; +/** @fn createUserAndRetrieveDataWithEmail:password:completion: + @brief Creates and, on success, signs in a user with the given email address and password. + + @param email The user's email address. + @param password The user's desired password. + @param completion Optionally; a block which is invoked when the sign up flow finishes, or is + canceled. Invoked asynchronously on the main thread in the future. + + @remarks Possible error codes: + + + @remarks See @c FIRAuthErrors for a list of error codes that are common to all API methods. + @remarks This method will only exist until the next major Firebase release following 4.x.x. + After the next major release the method @c createUserWithEmail:password:completion: will + support the @c FIRAuthDataResultCallback. + */ +- (void)createUserAndRetrieveDataWithEmail:(NSString *)email + password:(NSString *)password + completion:(nullable FIRAuthDataResultCallback)completion; + /** @fn confirmPasswordResetWithCode:newPassword:completion: @brief Resets the password given a code sent to the user outside of the app and a new password for the user. -- cgit v1.2.3