1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-11 05:53:42 +00:00

Add More SecureStorage Tests

This commit is contained in:
Justin Baur
2025-01-24 12:56:12 -05:00
parent c9f88c2145
commit 7d7ad9f3c9

View File

@@ -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<SupportStatus["type"], "supported" | "not-preferred">) => {
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<SupportStatus["type"], "not-supported" | "needs-configuration">,
) => {
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();
});
},
);
});
});