From bf550507ffa8beee149383a5bf1e2363bccefbb4 Mon Sep 17 00:00:00 2001 From: Xiangtian Dai Date: Mon, 2 Oct 2017 14:15:05 -0700 Subject: Automatically signs user out if the token is no longer valid. (#323) --- Example/Auth/Tests/FIRUserTests.m | 279 ++++++++++++++++++++- Firebase/Auth/Source/FIRAuth.m | 7 - Firebase/Auth/Source/FIRUser.m | 37 ++- .../Auth/Source/RPCs/FIRVerifyPhoneNumberRequest.m | 2 +- 4 files changed, 308 insertions(+), 17 deletions(-) diff --git a/Example/Auth/Tests/FIRUserTests.m b/Example/Auth/Tests/FIRUserTests.m index 3384885..820ade1 100644 --- a/Example/Auth/Tests/FIRUserTests.m +++ b/Example/Auth/Tests/FIRUserTests.m @@ -506,9 +506,41 @@ static const NSTimeInterval kExpectationTimeout = 1; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils invalidEmailErrorWithMessage:nil]); [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidEmail); // Email should not have changed on the client side. XCTAssertEqualObjects(user.email, kEmail); + // User is still signed in. + XCTAssertEqual([FIRAuth auth].currentUser, user); + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testUpdateEmailAutoSignOut + @brief Tests the flow of a failed @c updateEmail:completion: call that automatically signs out. + */ +- (void)testUpdateEmailAutoSignOut { + id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); + OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); + OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); + OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); + OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser + completion:^(FIRUser *user) { + [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; + OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) + .andDispatchError2([FIRAuthErrorUtils invalidUserTokenErrorWithMessage:nil]); + [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidUserToken); + // Email should not have changed on the client side. + XCTAssertEqualObjects(user.email, kEmail); + // User is no longer signed in. + XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; @@ -545,6 +577,7 @@ static const NSTimeInterval kExpectationTimeout = 1; verificationCode:kVerificationCode]; [user updatePhoneNumberCredential:credential completion:^(NSError * _Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects([FIRAuth auth].currentUser.phoneNumber, kPhoneNumber); [expectation fulfill]; @@ -572,7 +605,38 @@ static const NSTimeInterval kExpectationTimeout = 1; [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID verificationCode:kVerificationCode]; [user updatePhoneNumberCredential:credential completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeInvalidPhoneNumber); + XCTAssertEqual([FIRAuth auth].currentUser, user); + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testUpdatePhoneNumberFailureAutoSignOut + @brief Tests the flow of a failed @c updatePhoneNumberCredential:completion: call that + automatically signs out. + */ +- (void)testUpdatePhoneNumberFailureAutoSignOut { + id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); + OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); + OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); + OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); + OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser + completion:^(FIRUser *user) { + OCMExpect([_mockBackend verifyPhoneNumber:[OCMArg any] callback:[OCMArg any]]) + .andDispatchError2([FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]); + FIRPhoneAuthCredential *credential = + [[FIRPhoneAuthProvider provider] credentialWithVerificationID:kVerificationID + verificationCode:kVerificationCode]; + [user updatePhoneNumberCredential:credential completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertEqual(error.code, FIRAuthErrorCodeUserTokenExpired); + XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; @@ -614,6 +678,7 @@ static const NSTimeInterval kExpectationTimeout = 1; }); }); [user updatePassword:kNewPassword completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertFalse(user.isAnonymous); [expectation fulfill]; @@ -637,9 +702,11 @@ static const NSTimeInterval kExpectationTimeout = 1; completion:^(FIRUser *user) { [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) - .andDispatchError2([FIRAuthErrorUtils userDisabledErrorWithMessage:nil]); + .andDispatchError2([FIRAuthErrorUtils requiresRecentLoginErrorWithMessage:nil]); [user updatePassword:kNewPassword completion:^(NSError *_Nullable error) { - XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); + XCTAssertTrue([NSThread isMainThread]); + XCTAssertEqual(error.code, FIRAuthErrorCodeRequiresRecentLogin); + XCTAssertEqual([FIRAuth auth].currentUser, user); [expectation fulfill]; }]; }]; @@ -660,6 +727,7 @@ static const NSTimeInterval kExpectationTimeout = 1; [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser completion:^(FIRUser *user) { [user updatePassword:@"" completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeWeakPassword); [expectation fulfill]; }]; @@ -667,6 +735,33 @@ static const NSTimeInterval kExpectationTimeout = 1; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; } +/** @fn testUpdatePasswordFailureAutoSignOut + @brief Tests the flow of a failed @c updatePassword:completion: call that automatically signs + out. + */ +- (void)testUpdatePasswordFailureAutoSignOut { + id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); + OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); + OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); + OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); + OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser + completion:^(FIRUser *user) { + [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; + OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) + .andDispatchError2([FIRAuthErrorUtils userDisabledErrorWithMessage:nil]); + [user updatePassword:kNewPassword completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); + XCTAssertNil([FIRAuth auth].currentUser); + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + /** @fn testChangeProfileSuccess @brief Tests a successful user profile change flow. */ @@ -704,6 +799,7 @@ static const NSTimeInterval kExpectationTimeout = 1; profileChange.photoURL = [NSURL URLWithString:kNewPhotoURL]; profileChange.displayName = kNewDisplayName; [profileChange commitChangesWithCompletion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(user.displayName, kNewDisplayName); XCTAssertEqualObjects(user.photoURL, [NSURL URLWithString:kNewPhotoURL]); @@ -732,8 +828,39 @@ static const NSTimeInterval kExpectationTimeout = 1; FIRUserProfileChangeRequest *profileChange = [user profileChangeRequest]; profileChange.displayName = kNewDisplayName; [profileChange commitChangesWithCompletion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests); XCTAssertEqualObjects(user.displayName, kGoogleDisplayName); + XCTAssertEqual([FIRAuth auth].currentUser, user); + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testChangeProfileFailureAutoSignOut + @brief Tests a failed user profile change flow that automatically signs out. + */ +- (void)testChangeProfileFailureAutoSignOut { + id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); + OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); + OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); + OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kGoogleDisplayName); + OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser + completion:^(FIRUser *user) { + [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUser]; + OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) + .andDispatchError2([FIRAuthErrorUtils userNotFoundErrorWithMessage:nil]); + FIRUserProfileChangeRequest *profileChange = [user profileChangeRequest]; + profileChange.displayName = kNewDisplayName; + [profileChange commitChangesWithCompletion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertEqual(error.code, FIRAuthErrorCodeUserNotFound); + XCTAssertEqualObjects(user.displayName, kGoogleDisplayName); + XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; @@ -760,6 +887,7 @@ static const NSTimeInterval kExpectationTimeout = 1; OCMStub([mockGetAccountInfoResponseUserNew passwordHash]).andReturn(kPasswordHash); [self expectGetAccountInfoWithMockUserInfoResponse:mockGetAccountInfoResponseUserNew]; [user reloadWithCompletion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(user.email, kNewEmail); XCTAssertEqualObjects(user.displayName, kNewDisplayName); @@ -774,6 +902,30 @@ static const NSTimeInterval kExpectationTimeout = 1; @brief Tests the flow of a failed @c reloadWithCompletion: call. */ - (void)testReloadFailure { + id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); + OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); + OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); + OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [self signInWithEmailPasswordWithMockUserInfoResponse:mockGetAccountInfoResponseUser + completion:^(FIRUser *user) { + OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]]) + .andDispatchError2([FIRAuthErrorUtils quotaExceededErrorWithMessage:nil]); + [user reloadWithCompletion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertEqual(error.code, FIRAuthErrorCodeQuotaExceeded); + XCTAssertEqual([FIRAuth auth].currentUser, user); + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testReloadFailureAutoSignOut + @brief Tests the flow of a failed @c reloadWithCompletion: call that automtatically signs out. + */ +- (void)testReloadFailureAutoSignOut { id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]); OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID); OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail); @@ -784,7 +936,9 @@ static const NSTimeInterval kExpectationTimeout = 1; OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]]) .andDispatchError2([FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]); [user reloadWithCompletion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertEqual(error.code, FIRAuthErrorCodeUserTokenExpired); + XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; @@ -832,6 +986,7 @@ static const NSTimeInterval kExpectationTimeout = 1; FIRAuthCredential *emailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; [user reauthenticateWithCredential:emailCredential completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); // Verify that the current user is unchanged. XCTAssertEqual([FIRAuth auth].currentUser, user); @@ -882,6 +1037,7 @@ static const NSTimeInterval kExpectationTimeout = 1; completion:^(FIRAuthDataResult *_Nullable reauthenticateAuthResult, NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); // Verify that the current user is unchanged. XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); @@ -946,6 +1102,7 @@ static const NSTimeInterval kExpectationTimeout = 1; FIRAuthCredential *emailCredential = [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; [user reauthenticateWithCredential:emailCredential completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); // Verify user mismatch error. XCTAssertEqual(error.code, FIRAuthErrorCodeUserMismatch); // Verify that the current user is unchanged. @@ -980,6 +1137,7 @@ static const NSTimeInterval kExpectationTimeout = 1; FIRAuthCredential *googleCredential = [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; [user reauthenticateWithCredential:googleCredential completion:^(NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); // Verify user mismatch error. XCTAssertEqual(error.code, FIRAuthErrorCodeUserMismatch); // Verify that the current user is unchanged. @@ -1030,6 +1188,7 @@ static const NSTimeInterval kExpectationTimeout = 1; completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); // Verify that the current user is unchanged. XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); @@ -1081,7 +1240,8 @@ static const NSTimeInterval kExpectationTimeout = 1; .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request, FIRVerifyAssertionResponseCallback callback) { dispatch_async(FIRAuthGlobalWorkQueue(), ^() { - callback(nil, [FIRAuthErrorUtils userDisabledErrorWithMessage:nil]); + callback(nil, + [FIRAuthErrorUtils accountExistsWithDifferentCredentialErrorWithEmail:kEmail]); }); }); @@ -1091,8 +1251,11 @@ static const NSTimeInterval kExpectationTimeout = 1; completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); - XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); + XCTAssertEqual(error.code, FIRAuthErrorCodeAccountExistsWithDifferentCredential); + XCTAssertEqual(error.userInfo[FIRAuthErrorUserInfoEmailKey], kEmail); + XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); [expectation fulfill]; }]; }]; @@ -1131,8 +1294,60 @@ static const NSTimeInterval kExpectationTimeout = 1; completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked); + XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testlinkAndRetrieveDataErrorAutoSignOut + @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion: + call that automatically signs out. + */ +- (void)testlinkAndRetrieveDataErrorAutoSignOut { + [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID + federatedID:kFacebookID + displayName:kFacebookDisplayName + profile:[[self class] googleProfile] + providerIDToken:kFacebookIDToken + providerAccessToken:kFacebookAccessToken]; + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] signOut:NULL]; + FIRAuthCredential *facebookCredential = + [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; + [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + [self assertUserFacebook:authResult.user]; + XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); + XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); + XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); + XCTAssertNil(error); + + OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]]) + .andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request, + FIRVerifyAssertionResponseCallback callback) { + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + callback(nil, [FIRAuthErrorUtils userDisabledErrorWithMessage:nil]); + }); + }); + + FIRAuthCredential *linkGoogleCredential = + [FIRGoogleAuthProvider credentialWithIDToken:kGoogleIDToken accessToken:kGoogleAccessToken]; + [authResult.user linkAndRetrieveDataWithCredential:linkGoogleCredential + completion:^(FIRAuthDataResult *_Nullable + linkAuthResult, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertNil(linkAuthResult); + XCTAssertEqual(error.code, FIRAuthErrorCodeUserDisabled); + XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; @@ -1195,6 +1410,7 @@ static const NSTimeInterval kExpectationTimeout = 1; completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(error); XCTAssertEqualObjects(linkAuthResult.user.email, kEmail); XCTAssertEqualObjects(linkAuthResult.user.displayName, kEmailDisplayName); @@ -1271,6 +1487,7 @@ static const NSTimeInterval kExpectationTimeout = 1; completion:^(FIRAuthDataResult *_Nullable linkAuthResult, NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeProviderAlreadyLinked); [expectation fulfill]; @@ -1327,6 +1544,60 @@ static const NSTimeInterval kExpectationTimeout = 1; XCTAssertTrue([NSThread isMainThread]); XCTAssertNil(linkAuthResult); XCTAssertEqual(error.code, FIRAuthErrorCodeTooManyRequests); + XCTAssertEqual([FIRAuth auth].currentUser, authResult.user); + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(_mockBackend); +} + +/** @fn testlinkEmailAndRetrieveDataError + @brief Tests the flow of an unsuccessful @c linkAndRetrieveDataWithCredential:completion: + invocation that automatically signs out. + */ +- (void)testlinkEmailAndRetrieveDataErrorAutoSignOut { + [self expectVerifyAssertionRequest:FIRFacebookAuthProviderID + federatedID:kFacebookID + displayName:kFacebookDisplayName + profile:[[self class] googleProfile] + providerIDToken:kFacebookIDToken + providerAccessToken:kFacebookAccessToken]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] signOut:NULL]; + FIRAuthCredential *facebookCredential = + [FIRFacebookAuthProvider credentialWithAccessToken:kFacebookAccessToken]; + [[FIRAuth auth] signInAndRetrieveDataWithCredential:facebookCredential + completion:^(FIRAuthDataResult *_Nullable authResult, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + [self assertUserFacebook:authResult.user]; + XCTAssertEqualObjects(authResult.additionalUserInfo.profile, [[self class] googleProfile]); + XCTAssertEqualObjects(authResult.additionalUserInfo.username, kUserName); + XCTAssertEqualObjects(authResult.additionalUserInfo.providerID, FIRFacebookAuthProviderID); + XCTAssertNil(error); + + OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]]) + .andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request, + FIRGetAccountInfoResponseCallback callback) { + XCTAssertEqualObjects(request.APIKey, kAPIKey); + XCTAssertEqualObjects(request.accessToken, kAccessToken); + dispatch_async(FIRAuthGlobalWorkQueue(), ^() { + callback(nil, [FIRAuthErrorUtils userTokenExpiredErrorWithMessage:nil]); + }); + }); + + FIRAuthCredential *linkEmailCredential = + [FIREmailAuthProvider credentialWithEmail:kEmail password:kFakePassword]; + [authResult.user linkAndRetrieveDataWithCredential:linkEmailCredential + completion:^(FIRAuthDataResult *_Nullable + linkAuthResult, + NSError *_Nullable error) { + XCTAssertTrue([NSThread isMainThread]); + XCTAssertNil(linkAuthResult); + XCTAssertEqual(error.code, FIRAuthErrorCodeUserTokenExpired); + XCTAssertNil([FIRAuth auth].currentUser); [expectation fulfill]; }]; }]; diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/FIRAuth.m index f0a069c..245e600 100644 --- a/Firebase/Auth/Source/FIRAuth.m +++ b/Firebase/Auth/Source/FIRAuth.m @@ -1207,13 +1207,6 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; if (![strongSelf->_currentUser.uid isEqualToString:uid]) { return; } - // If the error is an invalid token, sign the user out. - if (error.code == FIRAuthErrorCodeInvalidUserToken) { - FIRLogNotice(kFIRLoggerAuth, @"I-AUT000005", - @"Invalid refresh token detected, user is automatically signed out."); - [strongSelf signOutByForceWithUserID:uid error:nil]; - return; - } if (error) { // Kicks off exponential back off logic to retry failed attempt. Starts with one minute // delay (60 seconds) if this is the first failed attempt. diff --git a/Firebase/Auth/Source/FIRUser.m b/Firebase/Auth/Source/FIRUser.m index ef42a29..c4396ad 100644 --- a/Firebase/Auth/Source/FIRUser.m +++ b/Firebase/Auth/Source/FIRUser.m @@ -18,8 +18,6 @@ #import "FIRUser_Internal.h" -#import "AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h" -#import "FIREmailAuthProvider.h" #import "FIRAdditionalUserInfo_Internal.h" #import "FIRAuth.h" #import "FIRAuthCredential_Internal.h" @@ -29,18 +27,21 @@ #import "FIRAuthSerialTaskQueue.h" #import "FIRAuthOperationType.h" #import "FIRAuth_Internal.h" -#import "FIRSecureTokenService.h" -#import "FIRUserInfoImpl.h" #import "FIRAuthBackend.h" #import "FIRAuthRequestConfiguration.h" #import "FIRDeleteAccountRequest.h" #import "FIRDeleteAccountResponse.h" +#import "FIREmailAuthProvider.h" +#import "FIREmailPasswordAuthCredential.h" #import "FIRGetAccountInfoRequest.h" #import "FIRGetAccountInfoResponse.h" #import "FIRGetOOBConfirmationCodeRequest.h" #import "FIRGetOOBConfirmationCodeResponse.h" +#import "FIRLogger.h" +#import "FIRSecureTokenService.h" #import "FIRSetAccountInfoRequest.h" #import "FIRSetAccountInfoResponse.h" +#import "FIRUserInfoImpl.h" #import "FIRUserMetadata_Internal.h" #import "FIRVerifyAssertionRequest.h" #import "FIRVerifyAssertionResponse.h" @@ -261,6 +262,7 @@ static void callInMainThreadWithAuthDataResultAndError( callback:^(FIRGetAccountInfoResponse *_Nullable response, NSError *_Nullable error) { if (error) { + // No need to sign out user here for errors because the user hasn't been signed in yet. callback(nil, error); return; } @@ -386,6 +388,7 @@ static void callInMainThreadWithAuthDataResultAndError( callback:^(FIRGetAccountInfoResponse *_Nullable response, NSError *_Nullable error) { if (error) { + [self signOutIfTokenIsInvalidWithError:error]; callback(nil, error); return; } @@ -459,6 +462,7 @@ static void callInMainThreadWithAuthDataResultAndError( callback:^(FIRSetAccountInfoResponse *_Nullable response, NSError *_Nullable error) { if (error) { + [self signOutIfTokenIsInvalidWithError:error]; complete(); callback(error); return; @@ -576,6 +580,7 @@ static void callInMainThreadWithAuthDataResultAndError( callback:^(FIRGetAccountInfoResponse *_Nullable response, NSError *_Nullable error) { if (error) { + [self signOutIfTokenIsInvalidWithError:error]; callback(error); return; } @@ -648,7 +653,8 @@ static void callInMainThreadWithAuthDataResultAndError( callback:^(FIRVerifyPhoneNumberResponse *_Nullable response, NSError *_Nullable error) { if (error) { - completion(error);; + [self signOutIfTokenIsInvalidWithError:error]; + completion(error); return; } // Get account info to update cached user info. @@ -805,6 +811,7 @@ static void callInMainThreadWithAuthDataResultAndError( NSError *_Nullable error, BOOL tokenUpdated) { if (error) { + [self signOutIfTokenIsInvalidWithError:error]; callback(nil, error); return; } @@ -897,6 +904,7 @@ static void callInMainThreadWithAuthDataResultAndError( [FIRAuthBackend verifyAssertion:request callback:^(FIRVerifyAssertionResponse *response, NSError *error) { if (error) { + [self signOutIfTokenIsInvalidWithError:error]; completeWithError(nil, error); return; } @@ -923,6 +931,7 @@ static void callInMainThreadWithAuthDataResultAndError( callback:^(FIRGetAccountInfoResponse *_Nullable response, NSError *_Nullable error) { if (error) { + [self signOutIfTokenIsInvalidWithError:error]; completeWithError(nil, error); return; } @@ -976,6 +985,7 @@ static void callInMainThreadWithAuthDataResultAndError( callback:^(FIRSetAccountInfoResponse *_Nullable response, NSError *_Nullable error) { if (error) { + [self signOutIfTokenIsInvalidWithError:error]; completeAndCallbackWithError(error); return; } @@ -1055,6 +1065,7 @@ static void callInMainThreadWithAuthDataResultAndError( callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response, NSError *_Nullable error) { + [self signOutIfTokenIsInvalidWithError:error]; callInMainThreadWithError(completion, error); }]; }]; @@ -1088,6 +1099,22 @@ static void callInMainThreadWithAuthDataResultAndError( }); } +/** @fn signOutIfTokenIsInvalidWithError: + @brief Signs out this user if the user or the token is invalid. + @param error The error from the server. + */ +- (void)signOutIfTokenIsInvalidWithError:(nullable NSError *)error { + NSInteger errorCode = error.code; + if (errorCode == FIRAuthErrorCodeUserNotFound || + errorCode == FIRAuthErrorCodeUserDisabled || + errorCode == FIRAuthErrorCodeInvalidUserToken || + errorCode == FIRAuthErrorCodeUserTokenExpired) { + FIRLogNotice(kFIRLoggerAuth, @"I-AUT000016", + @"Invalid user token detected, user is automatically signed out."); + [_auth signOutByForceWithUserID:_userID error:NULL]; + } +} + @end @implementation FIRUserProfileChangeRequest { diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberRequest.m b/Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberRequest.m index 8af4c4e..022ab9e 100644 --- a/Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberRequest.m +++ b/Firebase/Auth/Source/RPCs/FIRVerifyPhoneNumberRequest.m @@ -86,7 +86,7 @@ static NSString *const kOperationKey = @"operation"; } /** @fn FIRAuthOperationString - @brief Returns a string object corresponding to the provided FIRAuthOperationType value. + @brief Returns a string object corresponding to the provided FIRAuthOperationType value. @param operationType The value of the FIRAuthOperationType enum which will be translated to its corresponding string value. @return The string value corresponding to the FIRAuthOperationType argument. -- cgit v1.2.3