From 8ef0f1490a72fd700f609dc9971ec16868d6747b Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Wed, 7 Mar 2018 07:47:09 -0800 Subject: Adds Email link sign-in (#882) --- Example/Auth/Sample/MainViewController.m | 127 ++++++++- Example/Auth/Tests/FIRAuthTests.m | 286 ++++++++++++++++++++- Example/Auth/Tests/FIREmailLinkRequestTests.m | 137 ++++++++++ .../Auth/Tests/FIREmailLinkSignInResponseTests.m | 195 ++++++++++++++ .../Tests/FIRGetOOBConfirmationCodeRequestTests.m | 43 ++++ 5 files changed, 783 insertions(+), 5 deletions(-) create mode 100644 Example/Auth/Tests/FIREmailLinkRequestTests.m create mode 100644 Example/Auth/Tests/FIREmailLinkSignInResponseTests.m (limited to 'Example/Auth') diff --git a/Example/Auth/Sample/MainViewController.m b/Example/Auth/Sample/MainViewController.m index 31c103a..36ef92d 100644 --- a/Example/Auth/Sample/MainViewController.m +++ b/Example/Auth/Sample/MainViewController.m @@ -91,6 +91,16 @@ static NSString *const kSetPhotoURLText = @"Set Photo url"; */ static NSString *const kSignInGoogleButtonText = @"Sign in with Google"; +/** @var kSignInWithEmailLink + @brief The text of the "Sign in with Email Link" button. + */ +static NSString *const kSignInWithEmailLink = @"Sign in with Email Link"; + +/** @var kSendEmailSignInLink + @brief The text of the "Send Email SignIn link" button +*/ +static NSString *const kSendEmailSignInLink = @"Send Email Sign in Link"; + /** @var kSignInAndRetrieveGoogleButtonText @brief The text of the "Sign in with Google and retrieve data" button. */ @@ -279,6 +289,11 @@ static NSString *const kUnlinkFromEmailPassword = @"Unlink from Email/Password"; */ static NSString *const kGetProvidersForEmail = @"Get Provider IDs for Email"; +/** @var kGetAllSignInMethodsForEmail + @brief The text of the "Get sign-in methods for Email" button. + */ +static NSString *const kGetAllSignInMethodsForEmail = @"Get Sign-in methods for Email"; + /** @var kActionCodeTypeDescription @brief The description of the "Action Type" entry. */ @@ -722,6 +737,10 @@ typedef enum { action:^{ [weakSelf createUserAuthDataResult]; }], [StaticContentTableViewCell cellWithTitle:kSignInGoogleButtonText action:^{ [weakSelf signInGoogle]; }], + [StaticContentTableViewCell cellWithTitle:kSignInWithEmailLink + action:^{ [weakSelf signInWithEmailLink]; }], + [StaticContentTableViewCell cellWithTitle:kSendEmailSignInLink + action:^{ [weakSelf sendEmailSignInLink]; }], [StaticContentTableViewCell cellWithTitle:kSignInGoogleAndRetrieveDataButtonText action:^{ [weakSelf signInGoogleAndRetrieveData]; }], [StaticContentTableViewCell cellWithTitle:kSignInFacebookButtonText @@ -754,6 +773,8 @@ typedef enum { action:^{ [weakSelf reloadUser]; }], [StaticContentTableViewCell cellWithTitle:kGetProvidersForEmail action:^{ [weakSelf getProvidersForEmail]; }], + [StaticContentTableViewCell cellWithTitle:kGetAllSignInMethodsForEmail + action:^{ [weakSelf getAllSignInMethodsForEmail]; }], [StaticContentTableViewCell cellWithTitle:kUpdateEmailText action:^{ [weakSelf updateEmail]; }], [StaticContentTableViewCell cellWithTitle:kUpdatePasswordText @@ -1657,7 +1678,7 @@ static NSDictionary *parseURL(NSString *urlString) { } /** @fn signInEmailPassword - @brief Invoked when "sign in with Email/Password" row is pressed. + @brief Invoked when "Sign in with Email/Password" row is pressed. */ - (void)signInEmailPassword { [self showTextInputPromptWithMessage:@"Email Address:" @@ -1724,6 +1745,75 @@ static NSDictionary *parseURL(NSString *urlString) { }]; } +/** @fn signInWithEmailLink + @brief Invoked when "Sign in with email link" row is pressed. + */ +- (void)signInWithEmailLink { + [self showTextInputPromptWithMessage:@"Email Address:" + keyboardType:UIKeyboardTypeEmailAddress + completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) { + if (!userPressedOK || !email.length) { + return; + } + [self showTextInputPromptWithMessage:@"Email Sign-In Link:" + completionBlock:^(BOOL userPressedOK, NSString *_Nullable link) { + if (!userPressedOK) { + return; + } + if ([[FIRAuth auth] isSignInWithEmailLink:link]) { + [self showSpinner:^{ + [[AppManager auth] signInWithEmail:email + link:link + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + [self hideSpinner:^{ + if (error) { + [self logFailure:@"sign-in with Email/Sign-In failed" error:error]; + } else { + [self logSuccess:@"sign-in with Email/Sign-In link succeeded."]; + [self log:[NSString stringWithFormat:@"UID: %@",authResult.user.uid]]; + } + [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error]; + }]; + }]; + }]; + } else { + [self log:@"The sign-in link is invalid"]; + } + }]; + }]; +} + +/** @fn sendEmailSignInLink + @brief Invoked when "Send email sign-in link" row is pressed. + */ +- (void)sendEmailSignInLink { + [self showTextInputPromptWithMessage:@"Email:" + completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) { + if (!userPressedOK) { + return; + } + [self showSpinner:^{ + void (^requestEmailSignInLink)(void (^)(NSError *)) = ^(void (^completion)(NSError *)) { + [[AppManager auth] sendSignInLinkToEmail:userInput + actionCodeSettings:[self actionCodeSettings] + completion:completion]; + }; + requestEmailSignInLink(^(NSError *_Nullable error) { + [self hideSpinner:^{ + if (error) { + [self logFailure:@"Email Link request failed" error:error]; + [self showMessagePrompt:error.localizedDescription]; + return; + } + [self logSuccess:@"Email Link request succeeded."]; + [self showMessagePrompt:@"Sent"]; + }]; + }); + }]; + }]; +} + /** @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 @@ -2245,6 +2335,39 @@ static NSDictionary *parseURL(NSString *urlString) { }]; } +/** @fn getAllSignInMethodsForEmail + @brief Prompts user for an email address, calls @c FIRAuth.getAllSignInMethodsForEmail:callback: + and displays the result. + */ +- (void)getAllSignInMethodsForEmail { + [self showTextInputPromptWithMessage:@"Email:" + completionBlock:^(BOOL userPressedOK, NSString *_Nullable userInput) { + if (!userPressedOK || !userInput.length) { + return; + } + + [self showSpinner:^{ + [[AppManager auth] fetchSignInMethodsForEmail:userInput + completion:^(NSArray *_Nullable signInMethods, + NSError *_Nullable error) { + if (error) { + [self logFailure:@"get sign-in methods for email failed" error:error]; + } else { + [self logSuccess:@"get sign-in methods for email succeeded."]; + } + [self hideSpinner:^{ + if (error) { + [self showMessagePrompt:error.localizedDescription]; + return; + } + [self showMessagePrompt:[signInMethods componentsJoinedByString:@", "]]; + }]; + }]; + }]; + }]; +} + + /** @fn actionCodeRequestTypeString @brief Returns a string description for the type of the next action code request. */ @@ -2486,6 +2609,8 @@ static NSDictionary *parseURL(NSString *urlString) { return @"Recover Email"; case FIRActionCodeOperationPasswordReset: return @"Password Reset"; + case FIRActionCodeOperationEmailLink: + return @"Email Sign-In Link"; case FIRActionCodeOperationUnknown: return @"Unknown action"; } diff --git a/Example/Auth/Tests/FIRAuthTests.m b/Example/Auth/Tests/FIRAuthTests.m index b22c600..914c58b 100644 --- a/Example/Auth/Tests/FIRAuthTests.m +++ b/Example/Auth/Tests/FIRAuthTests.m @@ -33,6 +33,8 @@ #import "FIRAuthBackend.h" #import "FIRCreateAuthURIRequest.h" #import "FIRCreateAuthURIResponse.h" +#import "FIREmailLinkSignInRequest.h" +#import "FIREmailLinkSignInResponse.h" #import "FIRGetAccountInfoRequest.h" #import "FIRGetAccountInfoResponse.h" #import "FIRGetOOBConfirmationCodeRequest.h" @@ -56,6 +58,7 @@ #import "FIRApp+FIRAuthUnitTests.h" #import "OCMStubRecorder+FIRAuthUnitTests.h" #import +#import "FIRActionCodeSettings.h" #if TARGET_OS_IOS #import "FIRPhoneAuthCredential.h" @@ -167,6 +170,38 @@ static NSString *const kVerificationCode = @"12345678"; */ static NSString *const kVerificationID = @"55432"; +/** @var kContinueURL + @brief Fake string value of continue url. + */ +static NSString *const kContinueURL = @"continueURL"; + +/** @var kCanHandleCodeInAppKey + @brief The key for the request parameter indicating whether the action code can be handled in + the app or not. + */ +static NSString *const kCanHandleCodeInAppKey = @"canHandleCodeInApp"; + +/** @var kFIREmailLinkAuthSignInMethod + @brief Fake email link sign-in method for testing. + */ +static NSString *const kFIREmailLinkAuthSignInMethod = @"emailLink"; + +/** @var kFIRFacebookAuthSignInMethod + @brief Fake Facebook sign-in method for testing. + */ +static NSString *const kFIRFacebookAuthSignInMethod = @"facebook.com"; + +/** @var kBadSignInEmailLink + @brief Bad sign-in link to test email link sign-in + */ +static NSString *const kBadSignInEmailLink = @"http://www.facebook.com"; + +/** @var kFakeEmailSignInlink + @brief Fake email sign-in link + */ +static NSString *const kFakeEmailSignInlink = @"https://fex9s.app.goo.gl?link=" + "https://fb-sa-upgraded.firebaseapp.com/_?mode%3DsignIn%26oobCode%3Dtestoobcode"; + /** @var kExpectationTimeout @brief The maximum time waiting for expectations to fulfill. */ @@ -360,6 +395,39 @@ static const NSTimeInterval kWaitInterval = .5; OCMVerifyAll(_mockBackend); } +/** @fn testFetchSignInMethodsForEmailSuccess + @brief Tests the flow of a successful @c fetchSignInMethodsForEmail:completion: call. + */ +- (void)testFetchSignInMethodsForEmailSuccess { + NSArray *allSignInMethods = + @[ kFIREmailLinkAuthSignInMethod, kFIRFacebookAuthSignInMethod ]; + OCMExpect([_mockBackend createAuthURI:[OCMArg any] + callback:[OCMArg any]]) + .andCallBlock2(^(FIRCreateAuthURIRequest *_Nullable request, + FIRCreateAuthURIResponseCallback callback) { + XCTAssertEqualObjects(request.identifier, kEmail); + XCTAssertNotNil(request.endpoint); + XCTAssertEqualObjects(request.APIKey, kAPIKey); + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + id mockCreateAuthURIResponse = OCMClassMock([FIRCreateAuthURIResponse class]); + OCMStub([mockCreateAuthURIResponse signinMethods]).andReturn(allSignInMethods); + callback(mockCreateAuthURIResponse, nil); + }); + }); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] fetchSignInMethodsForEmail:kEmail + completion:^(NSArray *_Nullable signInMethods, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertEqualObjects(signInMethods, allSignInMethods); + XCTAssertTrue([allSignInMethods isKindOfClass:[NSArray class]]); + XCTAssertNil(error); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + /** @fn testFetchProvidersForEmailSuccessDeprecatedProviderID @brief Tests the flow of a successful @c fetchProvidersForEmail:completion: call using the deprecated FIREmailPasswordAuthProviderID. @@ -416,6 +484,25 @@ static const NSTimeInterval kWaitInterval = .5; OCMVerifyAll(_mockBackend); } +/** @fn testFetchSignInMethodsForEmailFailure + @brief Tests the flow of a failed @c fetchSignInMethodsForEmail:completion: call. + */ +- (void)testFetchSignInMethodsForEmailFailure { + OCMExpect([_mockBackend createAuthURI:[OCMArg any] callback:[OCMArg any]]) + .andDispatchError2([FIRAuthErrorUtils tooManyRequestsErrorWithMessage:nil]); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] fetchSignInMethodsForEmail:kEmail + completion:^(NSArray *_Nullable signInMethods, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertNil(signInMethods); + XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests); + XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} #if TARGET_OS_IOS /** @fn testPhoneAuthSuccess @brief Tests the flow of a successful @c signInWithCredential:completion for phone auth. @@ -501,6 +588,63 @@ static const NSTimeInterval kWaitInterval = .5; } #endif +/** @fn testSignInWithEmailLinkSuccess + @brief Tests the flow of a successful @c signInWithEmail:link:completion: call. + */ +- (void)testSignInWithEmailLinkSuccess { + NSString *fakeCode = @"testoobcode"; + OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]]) + .andCallBlock2(^(FIREmailLinkSignInRequest *_Nullable request, + FIREmailLinkSigninResponseCallback callback) { + XCTAssertEqualObjects(request.email, kEmail); + XCTAssertEqualObjects(request.oobCode, fakeCode); + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + id mockEmailLinkSignInResponse = OCMClassMock([FIREmailLinkSignInResponse class]); + [self stubTokensWithMockResponse:mockEmailLinkSignInResponse]; + callback(mockEmailLinkSignInResponse, nil); + OCMStub([mockEmailLinkSignInResponse refreshToken]).andReturn(kRefreshToken); + }); + }); + [self expectGetAccountInfo]; + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] signOut:NULL]; + [[FIRAuth auth] signInWithEmail:kEmail + link:kFakeEmailSignInlink + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertNotNil(authResult.user); + XCTAssertEqualObjects(authResult.user.refreshToken, kRefreshToken); + XCTAssertFalse(authResult.user.anonymous); + XCTAssertEqualObjects(authResult.user.email, kEmail); + XCTAssertNil(error); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + [self assertUser:[FIRAuth auth].currentUser]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testSignInWithEmailLinkFailure + @brief Tests the flow of a failed @c signInWithEmail:link:completion: call. + */ +- (void)testSignInWithEmailLinkFailure { + OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]]) + ._andDispatchError2([FIRAuthErrorUtils invalidActionCodeErrorWithMessage:nil]); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] signOut:NULL]; + [[FIRAuth auth] signInWithEmail:kEmail + link:kFakeEmailSignInlink + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidActionCode); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + /** @fn testSignInWithEmailPasswordSuccess @brief Tests the flow of a successful @c signInWithEmail:password:completion: call. */ @@ -521,8 +665,10 @@ static const NSTimeInterval kWaitInterval = .5; [self expectGetAccountInfo]; XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; - [[FIRAuth auth] signInWithEmail:kEmail password:kFakePassword completion:^(FIRUser *_Nullable user, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithEmail:kEmail + password:kFakePassword + completion:^(FIRUser *_Nullable user, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); [self assertUser:user]; XCTAssertNil(error); @@ -541,8 +687,10 @@ static const NSTimeInterval kWaitInterval = .5; .andDispatchError2([FIRAuthErrorUtils wrongPasswordErrorWithMessage:nil]); XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; [[FIRAuth auth] signOut:NULL]; - [[FIRAuth auth] signInWithEmail:kEmail password:kFakePassword completion:^(FIRUser *_Nullable user, - NSError *_Nullable error) { + [[FIRAuth auth] signInWithEmail:kEmail + password:kFakePassword + completion:^(FIRUser *_Nullable user, + NSError *_Nullable error) { XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(user); XCTAssertEqual(error.code, FIRAuthErrorCodeWrongPassword); @@ -829,6 +977,68 @@ static const NSTimeInterval kWaitInterval = .5; OCMVerifyAll(_mockBackend); } +/** @fn testSignInWithEmailLinkCredentialSuccess + @brief Tests the flow of a successfully @c signInWithCredential:completion: call with an + email sign-in link credential using FIREmailAuthProvider. + */ +- (void)testSignInWithEmailLinkCredentialSuccess { + NSString *fakeCode = @"testoobcode"; + OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]]) + .andCallBlock2(^(FIREmailLinkSignInRequest *_Nullable request, + FIREmailLinkSigninResponseCallback callback) { + XCTAssertEqualObjects(request.email, kEmail); + XCTAssertEqualObjects(request.oobCode, fakeCode); + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + id mockEmailLinkSigninResponse = OCMClassMock([FIREmailLinkSignInResponse class]); + [self stubTokensWithMockResponse:mockEmailLinkSigninResponse]; + callback(mockEmailLinkSigninResponse, nil); + }); + }); + [self expectGetAccountInfo]; + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] signOut:NULL]; + FIRAuthCredential *emailCredential = + [FIREmailAuthProvider credentialWithEmail:kEmail link:kFakeEmailSignInlink]; + [[FIRAuth auth] signInAndRetrieveDataWithCredential:emailCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertNotNil(authResult.user); + XCTAssertEqualObjects(authResult.user.refreshToken, kRefreshToken); + XCTAssertFalse(authResult.user.anonymous); + XCTAssertEqualObjects(authResult.user.email, kEmail); + XCTAssertNil(error); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + [self assertUser:[FIRAuth auth].currentUser]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testSignInWithEmailLinkCredentialFailure + @brief Tests the flow of a failed @c signInWithCredential:completion: call with an + email-email sign-in link credential using FIREmailAuthProvider. + */ +- (void)testSignInWithEmailLinkCredentialFailure { + OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]]) + .andDispatchError2([FIRAuthErrorUtils userDisabledErrorWithMessage:nil]); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] signOut:NULL]; + FIRAuthCredential *emailCredential = + [FIREmailAuthProvider credentialWithEmail:kEmail link:kFakeEmailSignInlink]; + [[FIRAuth auth] signInWithCredential:emailCredential completion:^(FIRUser *_Nullable user, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertNil(user); + XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); + XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + XCTAssertNil([FIRAuth auth].currentUser); + OCMVerifyAll(_mockBackend); +} + /** @fn testSignInWithEmailCredentialSuccess @brief Tests the flow of a successfully @c signInWithCredential:completion: call with an email-password credential. @@ -1504,6 +1714,65 @@ static const NSTimeInterval kWaitInterval = .5; OCMVerifyAll(_mockBackend); } +/** @fn testSendSignInLinkToEmailSuccess + @brief Tests the flow of a successful @c sendSignInLinkToEmail:actionCodeSettings:completion: + call. + */ +- (void)testSendSignInLinkToEmailSuccess { + OCMExpect([_mockBackend getOOBConfirmationCode:[OCMArg any] callback:[OCMArg any]]) + .andCallBlock2(^(FIRGetOOBConfirmationCodeRequest *_Nullable request, + FIRGetOOBConfirmationCodeResponseCallback callback) { + XCTAssertEqualObjects(request.APIKey, kAPIKey); + XCTAssertEqualObjects(request.email, kEmail); + XCTAssertEqualObjects(request.continueURL, kContinueURL); + XCTAssertTrue(request.handleCodeInApp); + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + callback([[FIRGetOOBConfirmationCodeResponse alloc] init], nil); + }); + }); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] sendSignInLinkToEmail:kEmail + actionCodeSettings:[self fakeActionCodeSettings] + completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertNil(error); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testSendSignInLinkToEmailFailure + @brief Tests the flow of a failed @c sendSignInLinkToEmail:actionCodeSettings:completion: + call. + */ +- (void)testSendSignInLinkToEmailFailure { + OCMExpect([_mockBackend getOOBConfirmationCode:[OCMArg any] callback:[OCMArg any]]) + .andDispatchError2([FIRAuthErrorUtils appNotAuthorizedError]); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] sendSignInLinkToEmail:kEmail + actionCodeSettings:[self fakeActionCodeSettings] + completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertEqual(error.code, FIRAuthErrorCodeAppNotAuthorized); + XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey]); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + +/** @fn fakeActionCodeSettings + @brief Constructs and returns a fake instance of @c FIRActionCodeSettings for testing. + @return An instance of @c FIRActionCodeSettings for testing. + */ +- (FIRActionCodeSettings *)fakeActionCodeSettings { + FIRActionCodeSettings *actionCodeSettings = [[FIRActionCodeSettings alloc]init]; + actionCodeSettings.URL = [NSURL URLWithString:kContinueURL]; + actionCodeSettings.handleCodeInApp = YES; + return actionCodeSettings; +} + /** @fn testSignOut @brief Tests the @c signOut: method. */ @@ -1515,6 +1784,15 @@ static const NSTimeInterval kWaitInterval = .5; XCTAssertNil([FIRAuth auth].currentUser); } +/** @fn testIsSignInWithEmailLink + @brief Tests the @c isSignInWithEmailLink: method. +*/ +- (void)testIsSignInWithEmailLink { + XCTAssertTrue([[FIRAuth auth] isSignInWithEmailLink:kFakeEmailSignInlink]); + XCTAssertFalse([[FIRAuth auth] isSignInWithEmailLink:kBadSignInEmailLink]); + XCTAssertFalse([[FIRAuth auth] isSignInWithEmailLink:@""]); +} + /** @fn testAuthStateChanges @brief Tests @c addAuthStateDidChangeListener: and @c removeAuthStateDidChangeListener: methods. */ diff --git a/Example/Auth/Tests/FIREmailLinkRequestTests.m b/Example/Auth/Tests/FIREmailLinkRequestTests.m new file mode 100644 index 0000000..90d7c18 --- /dev/null +++ b/Example/Auth/Tests/FIREmailLinkRequestTests.m @@ -0,0 +1,137 @@ +/* + * 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 + +#import "FIRAuthErrors.h" +#import "FIRAuthBackend.h" +#import "FIREmailLinkSignInRequest.h" +#import "FIREmailLinkSignInResponse.h" +#import "FIRFakeBackendRPCIssuer.h" + +/** @var kTestAPIKey + @brief Fake API key used for testing. + */ +static NSString *const kTestAPIKey = @"APIKey"; + +/** @var kTestEmail + @brief The key for the "email" value in the request. + */ +static NSString *const kTestEmail = @"TestEmail@email.com"; + +/** @var kTestOOBCode + @brief The test value for the "oobCode" in the request. + */ +static NSString *const kTestOOBCode = @"TestOOBCode"; + +/** @var kTestIDToken + @brief The test value for "idToken" in the request. + */ +static NSString *const kTestIDToken = @"testIDToken"; + +/** @var kEmailKey + @brief The key for the "identifier" value in the request. + */ +static NSString *const kEmailKey = @"email"; + +/** @var kEmailLinkKey + @brief The key for the "oobCode" value in the request. + */ +static NSString *const kOOBCodeKey = @"oobCode"; + +/** @var kIDTokenKey + @brief The key for the "IDToken" value in the request. + */ +static NSString *const kIDTokenKey = @"idToken"; + +/** @var kExpectedAPIURL + @brief The value of the expected URL (including the backend endpoint) in the request. + */ +static NSString *const kExpectedAPIURL = + @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/emailLinkSignin?key=APIKey"; + +/** @class FIREmailLinkRequestTests + @brief Tests for @c FIREmailLinkRequests. + */ +@interface FIREmailLinkRequestTests : XCTestCase +@end + +@implementation FIREmailLinkRequestTests { + /** @var _RPCIssuer + @brief This backend RPC issuer is used to fake network responses for each test in the suite. + In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it. + */ + FIRFakeBackendRPCIssuer *_RPCIssuer; + + /** @var _requestConfiguration + @brief This is the request configuration used for testing. + */ + FIRAuthRequestConfiguration *_requestConfiguration; +} + +- (void)setUp { + [super setUp]; + FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init]; + [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer]; + _RPCIssuer = RPCIssuer; + _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:kTestAPIKey]; +} + +- (void)tearDown { + _RPCIssuer = nil; + _requestConfiguration = nil; + [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil]; + [super tearDown]; +} + +/** @fn testEmailLinkRequestCreation + @brief Tests the email link sign-in request with mandatory parameters. + */ +- (void)testEmailLinkRequest { + FIREmailLinkSignInRequest *request = + [[FIREmailLinkSignInRequest alloc] initWithEmail:kTestEmail + oobCode:kTestOOBCode + requestConfiguration:_requestConfiguration]; + [FIRAuthBackend emailLinkSignin:request callback:^(FIREmailLinkSignInResponse *_Nullable response, + NSError *_Nullable error) { + }]; + XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL); + XCTAssertNotNil(_RPCIssuer.decodedRequest); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kEmailKey], kTestEmail); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kOOBCodeKey], kTestOOBCode); + XCTAssertNil(_RPCIssuer.decodedRequest[kIDTokenKey]); +} + +/** @fn testEmailLinkRequestCreationOptional + @brief Tests the email link sign-in request with mandatory parameters and optional ID token. + */ +- (void)testEmailLinkRequestCreationOptional { + FIREmailLinkSignInRequest *request = + [[FIREmailLinkSignInRequest alloc] initWithEmail:kTestEmail + oobCode:kTestOOBCode + requestConfiguration:_requestConfiguration]; + request.IDToken = kTestIDToken; + [FIRAuthBackend emailLinkSignin:request callback:^(FIREmailLinkSignInResponse *_Nullable response, + NSError *_Nullable error) { + }]; + XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL); + XCTAssertNotNil(_RPCIssuer.decodedRequest); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kEmailKey], kTestEmail); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kOOBCodeKey], kTestOOBCode); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kIDTokenKey], kTestIDToken); +} + +@end diff --git a/Example/Auth/Tests/FIREmailLinkSignInResponseTests.m b/Example/Auth/Tests/FIREmailLinkSignInResponseTests.m new file mode 100644 index 0000000..cc2c544 --- /dev/null +++ b/Example/Auth/Tests/FIREmailLinkSignInResponseTests.m @@ -0,0 +1,195 @@ +/* + * 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 + +#import "FIRAuthErrors.h" +#import "FIRAuthErrorUtils.h" +#import "FIRAuthBackend.h" +#import "FIREmailLinkSignInRequest.h" +#import "FIREmailLinkSignInResponse.h" +#import "FIRFakeBackendRPCIssuer.h" + +/** @var kTestAPIKey + @brief Fake API key used for testing. + */ +static NSString *const kTestAPIKey = @"APIKey"; + +/** @var kTestEmail + @brief The key for the "email" value in the request. + */ +static NSString *const kTestEmail = @"TestEmail@email.com"; + +/** @var kTestOOBCode + @brief The test value for the "oobCode" in the request. + */ +static NSString *const kTestOOBCode = @"TestOOBCode"; + +/** @var kTestIDToken + @brief The test value for "idToken" in the request. + */ +static NSString *const kTestIDToken = @"testIDToken"; + +/** @var kEmailKey + @brief The key for the "identifier" value in the request. + */ +static NSString *const kEmailKey = @"email"; + +/** @var kEmailLinkKey + @brief The key for the "emailLink" value in the request. + */ +static NSString *const kOOBCodeKey = @"oobCode"; + +/** @var kIDTokenKey + @brief The key for the "IDToken" value in the request. + */ +static NSString *const kIDTokenKey = @"idToken"; + +/** @var kTestIDTokenResponse + @brief A fake ID Token in the server response. + */ +static NSString *const kTestIDTokenResponse = @"fakeToken"; + +/** @var kTestEmailResponse + @brief A fake email in the server response. + */ +static NSString *const kTestEmailResponse = @"fake email"; + +/** @var kTestRefreshToken + @brief A fake refresh token in the server response. + */ +static NSString *const kTestRefreshToken = @"testRefreshToken"; + +/** @var kInvalidEmailErrorMessage + @brief The error returned by the server if the email is invalid. + */ +static NSString *const kInvalidEmailErrorMessage = @"INVALID_EMAIL"; + +/** @var kTestTokenExpirationTimeInterval + @brief The fake time interval that it takes a token to expire. + */ +static const NSTimeInterval kTestTokenExpirationTimeInterval = 55 * 60; + +/** @var kMaxDifferenceBetweenDates + @brief The maximum difference between time two dates (in seconds), after which they will be + considered different. + */ +static const NSTimeInterval kMaxDifferenceBetweenDates = 0.0001; + +/** @var kFakeIsNewUSerFlag + @brief The fake fake isNewUser flag in the response. + */ +static const BOOL kFakeIsNewUSerFlag = YES; + +/** @class FIREmailLinkRequestTests + @brief Tests for @c FIREmailLinkRequests. + */ +@interface FIREmailLinkSignInResponseTests : XCTestCase +@end + +@implementation FIREmailLinkSignInResponseTests { + /** @var _RPCIssuer + @brief This backend RPC issuer is used to fake network responses for each test in the suite. + In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it. + */ + FIRFakeBackendRPCIssuer *_RPCIssuer; + + /** @var _requestConfiguration + @brief This is the request configuration used for testing. + */ + FIRAuthRequestConfiguration *_requestConfiguration; +} + +- (void)setUp { + [super setUp]; + FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init]; + [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer]; + _RPCIssuer = RPCIssuer; + _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:kTestAPIKey]; +} + +/** @fn testFailedEmailLinkSignInResponse + @brief Tests a failed email link sign-in response. + */ +- (void)testFailedEmailLinkSignInResponse { + FIREmailLinkSignInRequest *request = + [[FIREmailLinkSignInRequest alloc] initWithEmail:kTestEmail + oobCode:kTestOOBCode + requestConfiguration:_requestConfiguration]; + + __block BOOL callbackInvoked = NO; + __block FIREmailLinkSignInResponse *RPCResponse; + __block NSError *RPCError; + [FIRAuthBackend emailLinkSignin:request + callback:^(FIREmailLinkSignInResponse *_Nullable response, + NSError *_Nullable error) { + callbackInvoked = YES; + RPCResponse = response; + RPCError = error; + }]; + + [_RPCIssuer respondWithServerErrorMessage:kInvalidEmailErrorMessage]; + + XCTAssert(callbackInvoked); + XCTAssertNil(RPCResponse); + XCTAssertEqual(RPCError.code, FIRAuthErrorCodeInvalidEmail); +} + +/** @fn testSuccessfulEmailLinkSignInResponse + @brief Tests a succesful email link sign-in response. + */ +- (void)testSuccessfulEmailLinkSignInResponse { + FIREmailLinkSignInRequest *request = + [[FIREmailLinkSignInRequest alloc] initWithEmail:kTestEmail + oobCode:kTestOOBCode + requestConfiguration:_requestConfiguration]; + + __block BOOL callbackInvoked = NO; + __block FIREmailLinkSignInResponse *RPCResponse; + __block NSError *RPCError; + [FIRAuthBackend emailLinkSignin:request + callback:^(FIREmailLinkSignInResponse *_Nullable response, + NSError *_Nullable error) { + callbackInvoked = YES; + RPCResponse = response; + RPCError = error; + }]; + + [_RPCIssuer respondWithJSON:@{ + @"idToken" : kTestIDTokenResponse, + @"email" : kTestEmailResponse, + @"isNewUser" : kFakeIsNewUSerFlag ? @YES : @NO, + @"expiresIn" : [NSString stringWithFormat:@"%f",kTestTokenExpirationTimeInterval], + @"refreshToken" : kTestRefreshToken, + }]; + + XCTAssert(callbackInvoked); + XCTAssertNil(RPCError); + XCTAssertNotNil(RPCResponse); + XCTAssertEqualObjects(RPCResponse.IDToken, kTestIDTokenResponse); + XCTAssertEqualObjects(RPCResponse.email, kTestEmailResponse); + XCTAssertEqualObjects(RPCResponse.refreshToken, kTestRefreshToken); + XCTAssertTrue(RPCResponse.isNewUser); + NSTimeInterval expirationTimeInterval = + [RPCResponse.approximateExpirationDate timeIntervalSinceNow]; + NSTimeInterval testTimeInterval = + [[NSDate dateWithTimeIntervalSinceNow:kTestTokenExpirationTimeInterval] timeIntervalSinceNow]; + NSTimeInterval timeIntervalDifference = + fabs(expirationTimeInterval - testTimeInterval); + XCTAssert(timeIntervalDifference < kMaxDifferenceBetweenDates); +} + +@end diff --git a/Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m b/Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m index 965af8a..b11c759 100644 --- a/Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m +++ b/Example/Auth/Tests/FIRGetOOBConfirmationCodeRequestTests.m @@ -49,6 +49,11 @@ static NSString *const kPasswordResetRequestTypeValue = @"PASSWORD_RESET"; */ static NSString *const kVerifyEmailRequestTypeValue = @"VERIFY_EMAIL"; +/** @var kEmailLinkSignInTypeValue + @brief The value for the "EMAIL_SIGNIN" request type. + */ +static NSString *const kEmailLinkSignInTypeValue = @"EMAIL_SIGNIN"; + /** @var kEmailKey @brief The name of the "email" property in the request. */ @@ -124,6 +129,7 @@ static NSString *const kCanHandleCodeInAppKey = @"canHandleCodeInApp"; /** @class FIRGetOOBConfirmationCodeRequestTests @brief Tests for @c FIRGetOOBConfirmationCodeRequest. */ + @interface FIRGetOOBConfirmationCodeRequestTests : XCTestCase @end @implementation FIRGetOOBConfirmationCodeRequestTests { @@ -190,6 +196,43 @@ static NSString *const kCanHandleCodeInAppKey = @"canHandleCodeInApp"; [NSNumber numberWithBool:YES]); } +/** @fn testSignInWithEmailLinkRequest + @brief Tests the encoding of a email sign-in link request. + */ +- (void)testSignInWithEmailLinkRequest { + FIRGetOOBConfirmationCodeRequest *request = + [FIRGetOOBConfirmationCodeRequest signInWithEmailLinkRequest:kTestEmail + actionCodeSettings:[self fakeActionCodeSettings] + requestConfiguration:_requestConfiguration]; + + __block BOOL callbackInvoked; + __block FIRGetOOBConfirmationCodeResponse *RPCResponse; + __block NSError *RPCError; + [FIRAuthBackend getOOBConfirmationCode:request + callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response, + NSError *_Nullable error) { + callbackInvoked = YES; + RPCResponse = response; + RPCError = error; + }]; + + XCTAssertEqualObjects(_RPCIssuer.requestURL.absoluteString, kExpectedAPIURL); + XCTAssertNotNil(_RPCIssuer.decodedRequest); + XCTAssert([_RPCIssuer.decodedRequest isKindOfClass:[NSDictionary class]]); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kEmailKey], kTestEmail); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kRequestTypeKey], kEmailLinkSignInTypeValue); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kContinueURLKey], kContinueURL); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kIosBundleIDKey], kIosBundleID); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kAndroidPackageNameKey], kAndroidPackageName); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kAndroidMinimumVersionKey], + kAndroidMinimumVersion); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kAndroidInstallAppKey], + [NSNumber numberWithBool:YES]); + XCTAssertEqualObjects(_RPCIssuer.decodedRequest[kCanHandleCodeInAppKey], + [NSNumber numberWithBool:YES]); +} + + /** @fn testEmailVerificationRequest @brief Tests the encoding of an email verification request. */ -- cgit v1.2.3