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:
@@ -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();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user