mirror of
https://github.com/bitwarden/browser
synced 2025-12-26 13:13:22 +00:00
feat(tokens): Allow Inactive user authenticated API calls
This commit is contained in:
@@ -72,14 +72,14 @@ export abstract class TokenService {
|
||||
* @param userId - The optional user id to get the access token for; if not provided, the active user is used.
|
||||
* @returns A promise that resolves with the access token or null.
|
||||
*/
|
||||
abstract getAccessToken(userId?: UserId): Promise<string | null>;
|
||||
abstract getAccessToken(userId: UserId): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Gets the refresh token.
|
||||
* @param userId - The optional user id to get the refresh token for; if not provided, the active user is used.
|
||||
* @returns A promise that resolves with the refresh token or null.
|
||||
*/
|
||||
abstract getRefreshToken(userId?: UserId): Promise<string | null>;
|
||||
abstract getRefreshToken(userId: UserId): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Sets the API Key Client ID for the active user id in memory or disk based on the given vaultTimeoutAction and vaultTimeout.
|
||||
@@ -96,10 +96,10 @@ export abstract class TokenService {
|
||||
): Promise<string>;
|
||||
|
||||
/**
|
||||
* Gets the API Key Client ID for the active user.
|
||||
* Gets the API Key Client ID for the given user.
|
||||
* @returns A promise that resolves with the API Key Client ID or undefined
|
||||
*/
|
||||
abstract getClientId(userId?: UserId): Promise<string | undefined>;
|
||||
abstract getClientId(userId: UserId): Promise<string | undefined>;
|
||||
|
||||
/**
|
||||
* Sets the API Key Client Secret for the active user id in memory or disk based on the given vaultTimeoutAction and vaultTimeout.
|
||||
@@ -116,10 +116,10 @@ export abstract class TokenService {
|
||||
): Promise<string>;
|
||||
|
||||
/**
|
||||
* Gets the API Key Client Secret for the active user.
|
||||
* Gets the API Key Client Secret for the given user.
|
||||
* @returns A promise that resolves with the API Key Client Secret or undefined
|
||||
*/
|
||||
abstract getClientSecret(userId?: UserId): Promise<string | undefined>;
|
||||
abstract getClientSecret(userId: UserId): Promise<string | undefined>;
|
||||
|
||||
/**
|
||||
* Sets the two factor token for the given email in global state.
|
||||
@@ -157,7 +157,7 @@ export abstract class TokenService {
|
||||
* Gets the expiration date for the access token. Returns if token can't be decoded or has no expiration
|
||||
* @returns A promise that resolves with the expiration date for the access token.
|
||||
*/
|
||||
abstract getTokenExpirationDate(): Promise<Date | null>;
|
||||
abstract getTokenExpirationDate(userId: UserId): Promise<Date | null>;
|
||||
|
||||
/**
|
||||
* Calculates the adjusted time in seconds until the access token expires, considering an optional offset.
|
||||
@@ -168,14 +168,14 @@ export abstract class TokenService {
|
||||
* based on the actual expiration.
|
||||
* @returns {Promise<number>} Promise resolving to the adjusted seconds remaining.
|
||||
*/
|
||||
abstract tokenSecondsRemaining(offsetSeconds?: number): Promise<number>;
|
||||
abstract tokenSecondsRemaining(userId: UserId, offsetSeconds?: number): Promise<number>;
|
||||
|
||||
/**
|
||||
* Checks if the access token needs to be refreshed.
|
||||
* @param {number} [minutes=5] - Optional number of minutes before the access token expires to consider refreshing it.
|
||||
* @returns A promise that resolves with a boolean indicating if the access token needs to be refreshed.
|
||||
*/
|
||||
abstract tokenNeedsRefresh(minutes?: number): Promise<boolean>;
|
||||
abstract tokenNeedsRefresh(userId: UserId, minutes?: number): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Gets the user id for the active user from the access token.
|
||||
|
||||
@@ -409,28 +409,8 @@ describe("TokenService", () => {
|
||||
});
|
||||
|
||||
describe("getAccessToken", () => {
|
||||
it("returns null when no user id is provided and there is no active user in global state", async () => {
|
||||
// Act
|
||||
const result = await tokenService.getAccessToken();
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when no access token is found in memory, disk, or secure storage", async () => {
|
||||
// Arrange
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getAccessToken();
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
describe("Memory storage tests", () => {
|
||||
test.each([
|
||||
["gets the access token from memory when a user id is provided ", userIdFromAccessToken],
|
||||
["gets the access token from memory when no user id is provided", undefined],
|
||||
])("%s", async (_, userId) => {
|
||||
it("gets the access token from memory when a user id is provided ", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY)
|
||||
@@ -442,12 +422,10 @@ describe("TokenService", () => {
|
||||
.nextState(undefined);
|
||||
|
||||
// Need to have global active id set to the user id
|
||||
if (!userId) {
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
}
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getAccessToken(userId);
|
||||
const result = await tokenService.getAccessToken(userIdFromAccessToken);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(accessTokenJwt);
|
||||
@@ -455,10 +433,7 @@ 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) => {
|
||||
it("gets the access token from disk when the user id is specified", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY)
|
||||
@@ -469,12 +444,10 @@ describe("TokenService", () => {
|
||||
.nextState(accessTokenJwt);
|
||||
|
||||
// Need to have global active id set to the user id
|
||||
if (!userId) {
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
}
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getAccessToken(userId);
|
||||
const result = await tokenService.getAccessToken(userIdFromAccessToken);
|
||||
// Assert
|
||||
expect(result).toEqual(accessTokenJwt);
|
||||
});
|
||||
@@ -486,16 +459,7 @@ describe("TokenService", () => {
|
||||
tokenService = createTokenService(supportsSecureStorage);
|
||||
});
|
||||
|
||||
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) => {
|
||||
it("gets the encrypted access token from disk, decrypts it, and returns it when a user id is provided", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY)
|
||||
@@ -509,27 +473,17 @@ describe("TokenService", () => {
|
||||
encryptService.decryptString.mockResolvedValue("decryptedAccessToken");
|
||||
|
||||
// Need to have global active id set to the user id
|
||||
if (!userId) {
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
}
|
||||
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getAccessToken(userId);
|
||||
const result = await tokenService.getAccessToken(userIdFromAccessToken);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual("decryptedAccessToken");
|
||||
});
|
||||
|
||||
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) => {
|
||||
it("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", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY)
|
||||
@@ -540,14 +494,12 @@ describe("TokenService", () => {
|
||||
.nextState(accessTokenJwt);
|
||||
|
||||
// Need to have global active id set to the user id
|
||||
if (!userId) {
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
}
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// No access token key set
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getAccessToken(userId);
|
||||
const result = await tokenService.getAccessToken(userIdFromAccessToken);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(accessTokenJwt);
|
||||
@@ -738,7 +690,7 @@ describe("TokenService", () => {
|
||||
|
||||
// Act
|
||||
// note: don't await here because we want to test the error
|
||||
const result = tokenService.getTokenExpirationDate();
|
||||
const result = tokenService.getTokenExpirationDate(userIdFromAccessToken);
|
||||
// Assert
|
||||
await expect(result).rejects.toThrow("Failed to decode access token: Mock error");
|
||||
});
|
||||
@@ -748,7 +700,7 @@ describe("TokenService", () => {
|
||||
tokenService.decodeAccessToken = jest.fn().mockResolvedValue(null);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getTokenExpirationDate();
|
||||
const result = await tokenService.getTokenExpirationDate(userIdFromAccessToken);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
@@ -763,7 +715,7 @@ describe("TokenService", () => {
|
||||
.mockResolvedValue(accessTokenDecodedWithoutExp);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getTokenExpirationDate();
|
||||
const result = await tokenService.getTokenExpirationDate(userIdFromAccessToken);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
@@ -777,7 +729,7 @@ describe("TokenService", () => {
|
||||
.mockResolvedValue(accessTokenDecodedWithNonNumericExp);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getTokenExpirationDate();
|
||||
const result = await tokenService.getTokenExpirationDate(userIdFromAccessToken);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
@@ -788,7 +740,7 @@ describe("TokenService", () => {
|
||||
tokenService.decodeAccessToken = jest.fn().mockResolvedValue(accessTokenDecoded);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getTokenExpirationDate();
|
||||
const result = await tokenService.getTokenExpirationDate(userIdFromAccessToken);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(new Date(accessTokenDecoded.exp * 1000));
|
||||
@@ -801,7 +753,7 @@ describe("TokenService", () => {
|
||||
tokenService.getTokenExpirationDate = jest.fn().mockResolvedValue(null);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.tokenSecondsRemaining();
|
||||
const result = await tokenService.tokenSecondsRemaining(userIdFromAccessToken);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(0);
|
||||
@@ -823,7 +775,7 @@ describe("TokenService", () => {
|
||||
tokenService.getTokenExpirationDate = jest.fn().mockResolvedValue(expirationDate);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.tokenSecondsRemaining();
|
||||
const result = await tokenService.tokenSecondsRemaining(userIdFromAccessToken);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedSecondsRemaining);
|
||||
@@ -849,7 +801,10 @@ describe("TokenService", () => {
|
||||
tokenService.getTokenExpirationDate = jest.fn().mockResolvedValue(expirationDate);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.tokenSecondsRemaining(offsetSeconds);
|
||||
const result = await tokenService.tokenSecondsRemaining(
|
||||
userIdFromAccessToken,
|
||||
offsetSeconds,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expectedSecondsRemaining);
|
||||
@@ -866,7 +821,7 @@ describe("TokenService", () => {
|
||||
tokenService.tokenSecondsRemaining = jest.fn().mockResolvedValue(tokenSecondsRemaining);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.tokenNeedsRefresh();
|
||||
const result = await tokenService.tokenNeedsRefresh(userIdFromAccessToken);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(true);
|
||||
@@ -878,7 +833,7 @@ describe("TokenService", () => {
|
||||
tokenService.tokenSecondsRemaining = jest.fn().mockResolvedValue(tokenSecondsRemaining);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.tokenNeedsRefresh();
|
||||
const result = await tokenService.tokenNeedsRefresh(userIdFromAccessToken);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(false);
|
||||
@@ -890,7 +845,7 @@ describe("TokenService", () => {
|
||||
tokenService.tokenSecondsRemaining = jest.fn().mockResolvedValue(tokenSecondsRemaining);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.tokenNeedsRefresh(2);
|
||||
const result = await tokenService.tokenNeedsRefresh(userIdFromAccessToken, 2);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(true);
|
||||
@@ -902,7 +857,7 @@ describe("TokenService", () => {
|
||||
tokenService.tokenSecondsRemaining = jest.fn().mockResolvedValue(tokenSecondsRemaining);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.tokenNeedsRefresh(5);
|
||||
const result = await tokenService.tokenNeedsRefresh(userIdFromAccessToken, 5);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(false);
|
||||
@@ -1565,26 +1520,6 @@ describe("TokenService", () => {
|
||||
});
|
||||
|
||||
describe("Memory storage tests", () => {
|
||||
it("gets the refresh token from memory when no user id is specified (uses global active user)", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY)
|
||||
.nextState(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);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getRefreshToken();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(refreshToken);
|
||||
});
|
||||
|
||||
it("gets the refresh token from memory when a user id is specified", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
@@ -1603,25 +1538,6 @@ 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);
|
||||
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK)
|
||||
.nextState(refreshToken);
|
||||
|
||||
// Need to have global active id set to the user id
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getRefreshToken();
|
||||
// Assert
|
||||
expect(result).toEqual(refreshToken);
|
||||
});
|
||||
|
||||
it("gets the refresh token from disk when a user id is specified", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
@@ -1645,27 +1561,6 @@ describe("TokenService", () => {
|
||||
tokenService = createTokenService(supportsSecureStorage);
|
||||
});
|
||||
|
||||
it("gets the refresh token from secure storage when no user id is specified", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY)
|
||||
.nextState(undefined);
|
||||
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK)
|
||||
.nextState(undefined);
|
||||
|
||||
secureStorageService.get.mockResolvedValue(refreshToken);
|
||||
|
||||
// Need to have global active id set to the user id
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getRefreshToken();
|
||||
// Assert
|
||||
expect(result).toEqual(refreshToken);
|
||||
});
|
||||
|
||||
it("gets the refresh token from secure storage when a user id is specified", async () => {
|
||||
// Arrange
|
||||
|
||||
@@ -1705,29 +1600,6 @@ describe("TokenService", () => {
|
||||
expect(secureStorageService.get).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK)
|
||||
.nextState(refreshToken);
|
||||
|
||||
// Need to have global active id set to the user id
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getRefreshToken();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(refreshToken);
|
||||
|
||||
// assert that secure storage was not called
|
||||
expect(secureStorageService.get).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns null when the refresh token is not found in memory, on disk, or in secure storage", async () => {
|
||||
// Arrange
|
||||
secureStorageService.get.mockResolvedValue(null);
|
||||
@@ -1944,45 +1816,7 @@ describe("TokenService", () => {
|
||||
});
|
||||
|
||||
describe("getClientId", () => {
|
||||
it("returns undefined when no user id is provided and there is no active user in global state", async () => {
|
||||
// Act
|
||||
const result = await tokenService.getClientId();
|
||||
// Assert
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns null when no client id is found in memory or disk", async () => {
|
||||
// Arrange
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getClientId();
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
describe("Memory storage tests", () => {
|
||||
it("gets the client id from memory when no user id is specified (uses global active user)", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, API_KEY_CLIENT_ID_MEMORY)
|
||||
.nextState(clientId);
|
||||
|
||||
// set disk to undefined
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, API_KEY_CLIENT_ID_DISK)
|
||||
.nextState(undefined);
|
||||
|
||||
// Need to have global active id set to the user id
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getClientId();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(clientId);
|
||||
});
|
||||
|
||||
it("gets the client id from memory when given a user id", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
@@ -2002,25 +1836,6 @@ describe("TokenService", () => {
|
||||
});
|
||||
|
||||
describe("Disk storage tests", () => {
|
||||
it("gets the client id from disk when no user id is specified", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, API_KEY_CLIENT_ID_MEMORY)
|
||||
.nextState(undefined);
|
||||
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, API_KEY_CLIENT_ID_DISK)
|
||||
.nextState(clientId);
|
||||
|
||||
// Need to have global active id set to the user id
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getClientId();
|
||||
// Assert
|
||||
expect(result).toEqual(clientId);
|
||||
});
|
||||
|
||||
it("gets the client id from disk when a user id is specified", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
@@ -2215,45 +2030,17 @@ describe("TokenService", () => {
|
||||
});
|
||||
|
||||
describe("getClientSecret", () => {
|
||||
it("returns undefined when no user id is provided and there is no active user in global state", async () => {
|
||||
// Act
|
||||
const result = await tokenService.getClientSecret();
|
||||
// Assert
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns null when no client secret is found in memory or disk", async () => {
|
||||
// Arrange
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getClientSecret();
|
||||
const result = await tokenService.getClientSecret(userIdFromAccessToken);
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
describe("Memory storage tests", () => {
|
||||
it("gets the client secret from memory when no user id is specified (uses global active user)", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, API_KEY_CLIENT_SECRET_MEMORY)
|
||||
.nextState(clientSecret);
|
||||
|
||||
// set disk to undefined
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, API_KEY_CLIENT_SECRET_DISK)
|
||||
.nextState(undefined);
|
||||
|
||||
// Need to have global active id set to the user id
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getClientSecret();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(clientSecret);
|
||||
});
|
||||
|
||||
it("gets the client secret from memory when a user id is specified", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
@@ -2273,25 +2060,6 @@ describe("TokenService", () => {
|
||||
});
|
||||
|
||||
describe("Disk storage tests", () => {
|
||||
it("gets the client secret from disk when no user id specified", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, API_KEY_CLIENT_SECRET_MEMORY)
|
||||
.nextState(undefined);
|
||||
|
||||
singleUserStateProvider
|
||||
.getFake(userIdFromAccessToken, API_KEY_CLIENT_SECRET_DISK)
|
||||
.nextState(clientSecret);
|
||||
|
||||
// Need to have global active id set to the user id
|
||||
globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID).nextState(userIdFromAccessToken);
|
||||
|
||||
// Act
|
||||
const result = await tokenService.getClientSecret();
|
||||
// Assert
|
||||
expect(result).toEqual(clientSecret);
|
||||
});
|
||||
|
||||
it("gets the client secret from disk when a user id is specified", async () => {
|
||||
// Arrange
|
||||
singleUserStateProvider
|
||||
|
||||
@@ -452,9 +452,7 @@ export class TokenService implements TokenServiceAbstraction {
|
||||
await this.singleUserStateProvider.get(userId, ACCESS_TOKEN_MEMORY).update((_) => null);
|
||||
}
|
||||
|
||||
async getAccessToken(userId?: UserId): Promise<string | null> {
|
||||
userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$);
|
||||
|
||||
async getAccessToken(userId: UserId): Promise<string | null> {
|
||||
if (!userId) {
|
||||
return null;
|
||||
}
|
||||
@@ -631,9 +629,7 @@ export class TokenService implements TokenServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async getRefreshToken(userId?: UserId): Promise<string | null> {
|
||||
userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$);
|
||||
|
||||
async getRefreshToken(userId: UserId): Promise<string | null> {
|
||||
if (!userId) {
|
||||
return null;
|
||||
}
|
||||
@@ -746,9 +742,7 @@ export class TokenService implements TokenServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async getClientId(userId?: UserId): Promise<string | undefined> {
|
||||
userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$);
|
||||
|
||||
async getClientId(userId: UserId): Promise<string | undefined> {
|
||||
if (!userId) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -822,9 +816,7 @@ export class TokenService implements TokenServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async getClientSecret(userId?: UserId): Promise<string | undefined> {
|
||||
userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$);
|
||||
|
||||
async getClientSecret(userId: UserId): Promise<string | undefined> {
|
||||
if (!userId) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -915,7 +907,9 @@ export class TokenService implements TokenServiceAbstraction {
|
||||
if (Utils.isGuid(tokenOrUserId)) {
|
||||
token = await this.getAccessToken(tokenOrUserId as UserId);
|
||||
} else {
|
||||
token ??= await this.getAccessToken();
|
||||
token ??= await this.getAccessToken(
|
||||
await firstValueFrom(this.activeUserIdGlobalState.state$),
|
||||
);
|
||||
}
|
||||
|
||||
if (token == null) {
|
||||
@@ -928,10 +922,10 @@ export class TokenService implements TokenServiceAbstraction {
|
||||
// TODO: PM-6678- tech debt - consider consolidating the return types of all these access
|
||||
// token data retrieval methods to return null if something goes wrong instead of throwing an error.
|
||||
|
||||
async getTokenExpirationDate(): Promise<Date | null> {
|
||||
async getTokenExpirationDate(userId: UserId): Promise<Date | null> {
|
||||
let decoded: DecodedAccessToken;
|
||||
try {
|
||||
decoded = await this.decodeAccessToken();
|
||||
decoded = await this.decodeAccessToken(userId);
|
||||
} catch (error) {
|
||||
throw new Error("Failed to decode access token: " + error.message);
|
||||
}
|
||||
@@ -947,8 +941,8 @@ export class TokenService implements TokenServiceAbstraction {
|
||||
return expirationDate;
|
||||
}
|
||||
|
||||
async tokenSecondsRemaining(offsetSeconds = 0): Promise<number> {
|
||||
const date = await this.getTokenExpirationDate();
|
||||
async tokenSecondsRemaining(userId: UserId, offsetSeconds = 0): Promise<number> {
|
||||
const date = await this.getTokenExpirationDate(userId);
|
||||
if (date == null) {
|
||||
return 0;
|
||||
}
|
||||
@@ -957,8 +951,8 @@ export class TokenService implements TokenServiceAbstraction {
|
||||
return Math.round(msRemaining / 1000);
|
||||
}
|
||||
|
||||
async tokenNeedsRefresh(minutes = 5): Promise<boolean> {
|
||||
const sRemaining = await this.tokenSecondsRemaining();
|
||||
async tokenNeedsRefresh(userId: UserId, minutes = 5): Promise<boolean> {
|
||||
const sRemaining = await this.tokenSecondsRemaining(userId);
|
||||
return sRemaining < 60 * minutes;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user