diff --git a/libs/common/src/auth/services/token.service.spec.ts b/libs/common/src/auth/services/token.service.spec.ts index f4f24f99e9d..7597d3537dc 100644 --- a/libs/common/src/auth/services/token.service.spec.ts +++ b/libs/common/src/auth/services/token.service.spec.ts @@ -449,174 +449,198 @@ describe("TokenService", () => { }); }); - describe("Disk storage tests (secure storage not supported on platform)", () => { - test.each([ - ["gets the access token from disk when the user id is specified", userIdFromAccessToken], - ["gets the access token from disk when no user id is specified", undefined], - ])("%s", async (_, userId) => { - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) - .nextState(undefined); + describe.each(["not-supported", "needs-configuration"])( + "Disk storage tests (secure storage %s on platform)", + (secureStorageSupport: Exclude) => { + beforeEach(() => { + tokenService = createTokenService({ type: secureStorageSupport, reason: "test" }); + }); - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) - .nextState(accessTokenJwt); + test.each([ + [ + "gets the access token from disk when the user id is specified", + userIdFromAccessToken, + ], + ["gets the access token from disk when no user id is specified", undefined], + ])("%s", async (_, userId) => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) + .nextState(undefined); - // Need to have global active id set to the user id - if (!userId) { - globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken); - } + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) + .nextState(accessTokenJwt); - // Act - const result = await tokenService.getAccessToken(userId); - // Assert - expect(result).toEqual(accessTokenJwt); - }); - }); + // Need to have global active id set to the user id + if (!userId) { + globalStateProvider + .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) + .nextState(userIdFromAccessToken); + } - describe("Disk storage tests (secure storage supported on platform)", () => { - beforeEach(() => { - const supportsSecureStorage = true; - tokenService = createTokenService(supportsSecureStorage); - }); + // Act + const result = await tokenService.getAccessToken(userId); + // Assert + expect(result).toEqual(accessTokenJwt); + }); + }, + ); - test.each([ - [ - "gets the encrypted access token from disk, decrypts it, and returns it when a user id is provided", - userIdFromAccessToken, - ], - [ - "gets the encrypted access token from disk, decrypts it, and returns it when no user id is provided", - undefined, - ], - ])("%s", async (_, userId) => { - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) - .nextState(undefined); + describe.each(["supported", "not-preferred"])( + "Disk storage tests (secure storage %s on platform)", + ( + supportStatus: Exclude, + ) => { + beforeEach(() => { + tokenService = createTokenService({ + type: supportStatus, + service: secureStorageService, + reason: "test", + }); + }); - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) - .nextState("encryptedAccessToken"); + test.each([ + [ + "gets the encrypted access token from disk, decrypts it, and returns it when a user id is provided", + userIdFromAccessToken, + ], + [ + "gets the encrypted access token from disk, decrypts it, and returns it when no user id is provided", + undefined, + ], + ])("%s", async (_, userId) => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) + .nextState(undefined); - secureStorageService.get.mockResolvedValue(accessTokenKeyB64); - encryptService.decryptToUtf8.mockResolvedValue("decryptedAccessToken"); + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) + .nextState("encryptedAccessToken"); - // Need to have global active id set to the user id - if (!userId) { - globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken); - } + secureStorageService.get.mockResolvedValue(accessTokenKeyB64); + encryptService.decryptToUtf8.mockResolvedValue("decryptedAccessToken"); - // Act - const result = await tokenService.getAccessToken(userId); + // Need to have global active id set to the user id + if (!userId) { + globalStateProvider + .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) + .nextState(userIdFromAccessToken); + } - // Assert - expect(result).toEqual("decryptedAccessToken"); - }); + // Act + const result = await tokenService.getAccessToken(userId); - test.each([ - [ - "falls back and gets the unencrypted access token from disk when there isn't an access token key in secure storage and a user id is provided", - userIdFromAccessToken, - ], - [ - "falls back and gets the unencrypted access token from disk when there isn't an access token key in secure storage and no user id is provided", - undefined, - ], - ])("%s", async (_, userId) => { - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) - .nextState(undefined); + // Assert + expect(result).toEqual("decryptedAccessToken"); + }); - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) - .nextState(accessTokenJwt); + test.each([ + [ + "falls back and gets the unencrypted access token from disk when there isn't an access token key in secure storage and a user id is provided", + userIdFromAccessToken, + ], + [ + "falls back and gets the unencrypted access token from disk when there isn't an access token key in secure storage and no user id is provided", + undefined, + ], + ])("%s", async (_, userId) => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) + .nextState(undefined); - // Need to have global active id set to the user id - if (!userId) { - globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken); - } + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) + .nextState(accessTokenJwt); - // No access token key set + // Need to have global active id set to the user id + if (!userId) { + globalStateProvider + .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) + .nextState(userIdFromAccessToken); + } - // Act - const result = await tokenService.getAccessToken(userId); + // No access token key set - // Assert - expect(result).toEqual(accessTokenJwt); - }); + // Act + const result = await tokenService.getAccessToken(userId); - it("logs the error and logs the user out when the access token key cannot be retrieved from secure storage if the access token is encrypted", async () => { - // This tests the intermittent windows 10/11 scenario in which the access token key was stored successfully in secure storage and the - // access token was encrypted with it and stored on disk successfully. However, on retrieval the access token key isn't able to - // retrieved for whatever reason. + // Assert + expect(result).toEqual(accessTokenJwt); + }); - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) - .nextState(undefined); + it("logs the error and logs the user out when the access token key cannot be retrieved from secure storage if the access token is encrypted", async () => { + // This tests the intermittent windows 10/11 scenario in which the access token key was stored successfully in secure storage and the + // access token was encrypted with it and stored on disk successfully. However, on retrieval the access token key isn't able to + // retrieved for whatever reason. - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) - .nextState(encryptedAccessToken); + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) + .nextState(undefined); - // No access token key set + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) + .nextState(encryptedAccessToken); - // Act - const result = await tokenService.getAccessToken(userIdFromAccessToken); + // No access token key set - // Assert - expect(result).toBeNull(); + // Act + const result = await tokenService.getAccessToken(userIdFromAccessToken); - // assert that we logged the error - expect(logService.error).toHaveBeenCalledWith( - "Access token key not found to decrypt encrypted access token. Logging user out.", - ); + // Assert + expect(result).toBeNull(); - // assert that we logged the user out - expect(logoutCallback).toHaveBeenCalledWith( - "accessTokenUnableToBeDecrypted", - userIdFromAccessToken, - ); - }); + // assert that we logged the error + expect(logService.error).toHaveBeenCalledWith( + "Access token key not found to decrypt encrypted access token. Logging user out.", + ); - it("logs the error and logs the user out when secure storage errors on trying to get an access token key", async () => { - // This tests the linux scenario where users might not have secure storage support configured. + // assert that we logged the user out + expect(logoutCallback).toHaveBeenCalledWith( + "accessTokenUnableToBeDecrypted", + userIdFromAccessToken, + ); + }); - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) - .nextState(undefined); + it("logs the error and logs the user out when secure storage errors on trying to get an access token key", async () => { + // This tests the linux scenario where users might not have secure storage support configured. - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) - .nextState(encryptedAccessToken); + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) + .nextState(undefined); - // Mock linux secure storage error - const secureStorageError = "Secure storage error"; - secureStorageService.get.mockRejectedValue(new Error(secureStorageError)); + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) + .nextState(encryptedAccessToken); - // Act - const result = await tokenService.getAccessToken(userIdFromAccessToken); + // Mock linux secure storage error + const secureStorageError = "Secure storage error"; + secureStorageService.get.mockRejectedValue(new Error(secureStorageError)); - // Assert - expect(result).toBeNull(); + // Act + const result = await tokenService.getAccessToken(userIdFromAccessToken); - // assert that we logged the error - expect(logService.error).toHaveBeenCalledWith( - "Access token key retrieval failed. Unable to decrypt encrypted access token. Logging user out.", - new Error(secureStorageError), - ); + // Assert + expect(result).toBeNull(); - // assert that we logged the user out - expect(logoutCallback).toHaveBeenCalledWith( - "accessTokenUnableToBeDecrypted", - userIdFromAccessToken, - ); - }); - }); + // assert that we logged the error + expect(logService.error).toHaveBeenCalledWith( + "Access token key retrieval failed. Unable to decrypt encrypted access token. Logging user out.", + new Error(secureStorageError), + ); + + // assert that we logged the user out + expect(logoutCallback).toHaveBeenCalledWith( + "accessTokenUnableToBeDecrypted", + userIdFromAccessToken, + ); + }); + }, + ); }); describe("clearAccessToken", () => { @@ -628,53 +652,61 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("User id not found. Cannot clear access token."); }); - describe("Secure storage enabled", () => { - beforeEach(() => { - const supportsSecureStorage = true; - tokenService = createTokenService(supportsSecureStorage); - }); + describe.each(["supported", "not-preferred"])( + "Secure storage %s", + (secureStorageSupport: "supported" | "not-preferred") => { + beforeEach(() => { + tokenService = createTokenService({ + type: secureStorageSupport, + service: secureStorageService, + reason: "test", + }); + }); - test.each([ - [ - "clears the access token from all storage locations when a user id is provided", - userIdFromAccessToken, - ], - [ - "clears the access token from all storage locations when there is a global active user", - undefined, - ], - ])("%s", async (_, userId) => { - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) - .nextState(accessTokenJwt); + test.each([ + [ + "clears the access token from all storage locations when a user id is provided", + userIdFromAccessToken, + ], + [ + "clears the access token from all storage locations when there is a global active user", + undefined, + ], + ])("%s", async (_, userId) => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) + .nextState(accessTokenJwt); - singleUserStateProvider - .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) - .nextState(accessTokenJwt); + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) + .nextState(accessTokenJwt); - // Need to have global active id set to the user id - if (!userId) { - globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken); - } + // Need to have global active id set to the user id + if (!userId) { + globalStateProvider + .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) + .nextState(userIdFromAccessToken); + } - // Act - await tokenService.clearAccessToken(userIdFromAccessToken); + // Act + await tokenService.clearAccessToken(userIdFromAccessToken); - // Assert - expect( - singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY).nextMock, - ).toHaveBeenCalledWith(null); - expect( - singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK).nextMock, - ).toHaveBeenCalledWith(null); + // Assert + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY).nextMock, + ).toHaveBeenCalledWith(null); + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK).nextMock, + ).toHaveBeenCalledWith(null); - expect(secureStorageService.remove).toHaveBeenCalledWith( - accessTokenKeySecureStorageKey, - secureStorageOptions, - ); - }); - }); + expect(secureStorageService.remove).toHaveBeenCalledWith( + accessTokenKeySecureStorageKey, + secureStorageOptions, + ); + }); + }, + ); }); describe("decodeAccessToken", () => { @@ -1591,201 +1623,214 @@ describe("TokenService", () => { }); }); - describe("Disk storage tests (secure storage not supported on platform)", () => { - it("gets the refresh token from disk when no user id is specified", async () => { - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) - .nextState(undefined); + describe.each(["not-supported", "needs-configuration"])( + "Disk storage tests (secure storage $s on platform)", + (supportStatus: "not-supported" | "needs-configuration") => { + beforeEach(() => { + tokenService = createTokenService({ type: supportStatus, reason: "test" }); + }); - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) - .nextState(refreshToken); + it("gets the refresh token from disk when no user id is specified", async () => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .nextState(undefined); - // Need to have global active id set to the user id - globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken); + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .nextState(refreshToken); - // Act - const result = await tokenService.getRefreshToken(); - // Assert - expect(result).toEqual(refreshToken); - }); + // Need to have global active id set to the user id + globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken); - it("gets the refresh token from disk when a user id is specified", async () => { - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) - .nextState(undefined); + // Act + const result = await tokenService.getRefreshToken(); + // Assert + expect(result).toEqual(refreshToken); + }); - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) - .nextState(refreshToken); + it("gets the refresh token from disk when a user id is specified", async () => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .nextState(undefined); - // Act - const result = await tokenService.getRefreshToken(userIdFromAccessToken); - // Assert - expect(result).toEqual(refreshToken); - }); - }); + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .nextState(refreshToken); - describe("Disk storage tests (secure storage supported on platform)", () => { - beforeEach(() => { - const supportsSecureStorage = true; - tokenService = createTokenService(supportsSecureStorage); - }); + // Act + const result = await tokenService.getRefreshToken(userIdFromAccessToken); + // Assert + expect(result).toEqual(refreshToken); + }); + }, + ); - it("gets the refresh token from secure storage when no user id is specified", async () => { - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) - .nextState(undefined); + describe.each(["supported", "not-preferred"])( + "Disk storage tests (secure storage supported on platform)", + (supportStatus: "supported" | "not-preferred") => { + beforeEach(() => { + tokenService = createTokenService({ + type: supportStatus, + service: secureStorageService, + reason: "test", + }); + }); - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) - .nextState(undefined); + it("gets the refresh token from secure storage when no user id is specified", async () => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .nextState(undefined); - secureStorageService.get.mockResolvedValue(refreshToken); + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .nextState(undefined); - // Need to have global active id set to the user id - globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken); + secureStorageService.get.mockResolvedValue(refreshToken); - // Act - const result = await tokenService.getRefreshToken(); - // Assert - expect(result).toEqual(refreshToken); - }); + // Need to have global active id set to the user id + globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken); - it("gets the refresh token from secure storage when a user id is specified", async () => { - // Arrange + // Act + const result = await tokenService.getRefreshToken(); + // Assert + expect(result).toEqual(refreshToken); + }); - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) - .nextState(undefined); + it("gets the refresh token from secure storage when a user id is specified", async () => { + // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) - .nextState(undefined); + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .nextState(undefined); - secureStorageService.get.mockResolvedValue(refreshToken); + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .nextState(undefined); - // Act - const result = await tokenService.getRefreshToken(userIdFromAccessToken); - // Assert - expect(result).toEqual(refreshToken); - }); + secureStorageService.get.mockResolvedValue(refreshToken); - it("falls back and gets the refresh token from disk when a user id is specified even if the platform supports secure storage", async () => { - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) - .nextState(undefined); + // Act + const result = await tokenService.getRefreshToken(userIdFromAccessToken); + // Assert + expect(result).toEqual(refreshToken); + }); - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) - .nextState(refreshToken); + it("falls back and gets the refresh token from disk when a user id is specified even if the platform supports secure storage", async () => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .nextState(undefined); - // Act - const result = await tokenService.getRefreshToken(userIdFromAccessToken); + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .nextState(refreshToken); - // Assert - expect(result).toEqual(refreshToken); + // Act + const result = await tokenService.getRefreshToken(userIdFromAccessToken); - // assert that secure storage was not called - expect(secureStorageService.get).not.toHaveBeenCalled(); - }); + // Assert + expect(result).toEqual(refreshToken); - it("falls back and gets the refresh token from disk when no user id is specified even if the platform supports secure storage", async () => { - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) - .nextState(undefined); + // assert that secure storage was not called + expect(secureStorageService.get).not.toHaveBeenCalled(); + }); - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) - .nextState(refreshToken); + it("falls back and gets the refresh token from disk when no user id is specified even if the platform supports secure storage", async () => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .nextState(undefined); - // Need to have global active id set to the user id - globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken); + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .nextState(refreshToken); - // Act - const result = await tokenService.getRefreshToken(); + // Need to have global active id set to the user id + globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken); - // Assert - expect(result).toEqual(refreshToken); + // Act + const result = await tokenService.getRefreshToken(); - // assert that secure storage was not called - expect(secureStorageService.get).not.toHaveBeenCalled(); - }); + // Assert + expect(result).toEqual(refreshToken); - it("returns null when the refresh token is not found in memory, on disk, or in secure storage", async () => { - // Arrange - secureStorageService.get.mockResolvedValue(null); + // assert that secure storage was not called + expect(secureStorageService.get).not.toHaveBeenCalled(); + }); - // Act - const result = await tokenService.getRefreshToken(userIdFromAccessToken); + it("returns null when the refresh token is not found in memory, on disk, or in secure storage", async () => { + // Arrange + secureStorageService.get.mockResolvedValue(null); - // Assert - expect(result).toBeNull(); - }); + // Act + const result = await tokenService.getRefreshToken(userIdFromAccessToken); - it("returns null and logs when the refresh token is not found in secure storage when it should be", async () => { - // This scenario mocks the case where we have intermittent windows 10/11 issues w/ secure storage not - // returning the refresh token when it should be there. - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) - .nextState(undefined); + // Assert + expect(result).toBeNull(); + }); - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) - .nextState(undefined); + it("returns null and logs when the refresh token is not found in secure storage when it should be", async () => { + // This scenario mocks the case where we have intermittent windows 10/11 issues w/ secure storage not + // returning the refresh token when it should be there. + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .nextState(undefined); - secureStorageService.get.mockResolvedValue(null); + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .nextState(undefined); - // Act - const result = await tokenService.getRefreshToken(userIdFromAccessToken); + secureStorageService.get.mockResolvedValue(null); - // Assert - expect(result).toBeNull(); + // Act + const result = await tokenService.getRefreshToken(userIdFromAccessToken); - expect(logService.error).toHaveBeenCalledWith( - "Refresh token not found in secure storage. Access token will fail to refresh upon expiration or manual refresh.", - ); - }); + // Assert + expect(result).toBeNull(); - it("logs out when retrieving the refresh token out of secure storage errors", async () => { - // This scenario mocks the case where linux users don't have secure storage configured. - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) - .nextState(undefined); + expect(logService.error).toHaveBeenCalledWith( + "Refresh token not found in secure storage. Access token will fail to refresh upon expiration or manual refresh.", + ); + }); - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) - .nextState(undefined); + it("logs out when retrieving the refresh token out of secure storage errors", async () => { + // This scenario mocks the case where linux users don't have secure storage configured. + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .nextState(undefined); - const secureStorageSvcMockErrorMsg = "Secure storage retrieval error"; + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .nextState(undefined); - secureStorageService.get.mockRejectedValue(new Error(secureStorageSvcMockErrorMsg)); + const secureStorageSvcMockErrorMsg = "Secure storage retrieval error"; - // Act - const result = await tokenService.getRefreshToken(userIdFromAccessToken); + secureStorageService.get.mockRejectedValue(new Error(secureStorageSvcMockErrorMsg)); - // Assert - expect(result).toBeNull(); + // Act + const result = await tokenService.getRefreshToken(userIdFromAccessToken); - // expect that we logged an error and logged the user out - expect(logService.error).toHaveBeenCalledWith( - `Failed to retrieve refresh token from secure storage`, - new Error(secureStorageSvcMockErrorMsg), - ); + // Assert + expect(result).toBeNull(); - expect(logoutCallback).toHaveBeenCalledWith( - "refreshTokenSecureStorageRetrievalFailure", - userIdFromAccessToken, - ); - }); - }); + // expect that we logged an error and logged the user out + expect(logService.error).toHaveBeenCalledWith( + `Failed to retrieve refresh token from secure storage`, + new Error(secureStorageSvcMockErrorMsg), + ); + + expect(logoutCallback).toHaveBeenCalledWith( + "refreshTokenSecureStorageRetrievalFailure", + userIdFromAccessToken, + ); + }); + }, + ); }); describe("clearRefreshToken", () => { @@ -1797,39 +1842,70 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("User id not found. Cannot clear refresh token."); }); - describe("Secure storage enabled", () => { - beforeEach(() => { - const supportsSecureStorage = true; - tokenService = createTokenService(supportsSecureStorage); - }); + describe.each(["supported", "not-preferred"])( + "Secure storage %s", + (supportStatus: "supported" | "not-preferred") => { + beforeEach(() => { + tokenService = createTokenService({ + type: supportStatus, + service: secureStorageService, + reason: "test", + }); + }); - it("clears the refresh token from all storage locations when given a user id", async () => { - // Arrange - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) - .nextState(refreshToken); + it("clears the refresh token from all storage locations when given a user id", async () => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .nextState(refreshToken); - singleUserStateProvider - .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) - .nextState(refreshToken); + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .nextState(refreshToken); - // Act - await tokenService["clearRefreshToken"](userIdFromAccessToken); + // Act + await tokenService["clearRefreshToken"](userIdFromAccessToken); - // Assert - expect( - singleUserStateProvider.getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY).nextMock, - ).toHaveBeenCalledWith(null); - expect( - singleUserStateProvider.getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK).nextMock, - ).toHaveBeenCalledWith(null); + // Assert + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY).nextMock, + ).toHaveBeenCalledWith(null); + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK).nextMock, + ).toHaveBeenCalledWith(null); - expect(secureStorageService.remove).toHaveBeenCalledWith( - refreshTokenSecureStorageKey, - secureStorageOptions, - ); - }); - }); + expect(secureStorageService.remove).toHaveBeenCalledWith( + refreshTokenSecureStorageKey, + secureStorageOptions, + ); + }); + }, + ); + + describe.each(["not-supported", "needs-configuration"])( + "Secure storage not supported with %s", + (supportStatus: "not-supported" | "needs-configuration") => { + beforeEach(() => { + tokenService = createTokenService({ type: supportStatus, reason: "test" }); + }); + + it("does not call storage service remove", async () => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .nextState(refreshToken); + + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .nextState(refreshToken); + + // Act + await tokenService["clearRefreshToken"](userIdFromAccessToken); + + expect(secureStorageService.remove).not.toHaveBeenCalled(); + }); + }, + ); }); });