mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[PM-21797] Require userID for keyService's getUserKeyFromStorage (#14855)
* require userID for keyService's getUserKeyFromStorage
This commit is contained in:
@@ -128,10 +128,11 @@ export abstract class KeyService {
|
|||||||
* @param keySuffix The desired version of the user's key to retrieve
|
* @param keySuffix The desired version of the user's key to retrieve
|
||||||
* @param userId The desired user
|
* @param userId The desired user
|
||||||
* @returns The user key
|
* @returns The user key
|
||||||
|
* @throws Error when userId is null or undefined.
|
||||||
*/
|
*/
|
||||||
abstract getUserKeyFromStorage(
|
abstract getUserKeyFromStorage(
|
||||||
keySuffix: KeySuffixOptions,
|
keySuffix: KeySuffixOptions,
|
||||||
userId?: string,
|
userId: string,
|
||||||
): Promise<UserKey | null>;
|
): Promise<UserKey | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/ke
|
|||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
import { KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
||||||
import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted";
|
import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { EncString, EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
|
import { EncString, EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
@@ -896,4 +897,84 @@ describe("keyService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getUserKeyFromStorage", () => {
|
||||||
|
let mockUserKey: UserKey;
|
||||||
|
let validateUserKeySpy: jest.SpyInstance;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
|
||||||
|
validateUserKeySpy = jest.spyOn(keyService, "validateUserKey");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
validateUserKeySpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("input validation", () => {
|
||||||
|
const invalidUserIdTestCases = [
|
||||||
|
{ keySuffix: KeySuffixOptions.Auto, userId: null as unknown as UserId },
|
||||||
|
{ keySuffix: KeySuffixOptions.Auto, userId: undefined as unknown as UserId },
|
||||||
|
{ keySuffix: KeySuffixOptions.Pin, userId: null as unknown as UserId },
|
||||||
|
{ keySuffix: KeySuffixOptions.Pin, userId: undefined as unknown as UserId },
|
||||||
|
];
|
||||||
|
|
||||||
|
test.each(invalidUserIdTestCases)(
|
||||||
|
"throws when keySuffix is $keySuffix and userId is $userId",
|
||||||
|
async ({ keySuffix, userId }) => {
|
||||||
|
await expect(keyService.getUserKeyFromStorage(keySuffix, userId)).rejects.toThrow(
|
||||||
|
"UserId is required",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with Pin keySuffix", () => {
|
||||||
|
it("returns null and doesn't validate the key", async () => {
|
||||||
|
const result = await keyService.getUserKeyFromStorage(KeySuffixOptions.Pin, mockUserId);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
expect(validateUserKeySpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with Auto keySuffix", () => {
|
||||||
|
it("returns validated key from storage when key exists and is valid", async () => {
|
||||||
|
stateService.getUserKeyAutoUnlock.mockResolvedValue(mockUserKey.keyB64);
|
||||||
|
validateUserKeySpy.mockResolvedValue(true);
|
||||||
|
|
||||||
|
const result = await keyService.getUserKeyFromStorage(KeySuffixOptions.Auto, mockUserId);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockUserKey);
|
||||||
|
expect(validateUserKeySpy).toHaveBeenCalledWith(mockUserKey, mockUserId);
|
||||||
|
expect(stateService.getUserKeyAutoUnlock).toHaveBeenCalledWith({
|
||||||
|
userId: mockUserId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null when no key is found in storage", async () => {
|
||||||
|
stateService.getUserKeyAutoUnlock.mockResolvedValue(null as unknown as string);
|
||||||
|
|
||||||
|
const result = await keyService.getUserKeyFromStorage(KeySuffixOptions.Auto, mockUserId);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
expect(validateUserKeySpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears stored keys when userKey validation fails", async () => {
|
||||||
|
stateService.getUserKeyAutoUnlock.mockResolvedValue(mockUserKey.keyB64);
|
||||||
|
validateUserKeySpy.mockResolvedValue(false);
|
||||||
|
|
||||||
|
const result = await keyService.getUserKeyFromStorage(KeySuffixOptions.Auto, mockUserId);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockUserKey);
|
||||||
|
expect(validateUserKeySpy).toHaveBeenCalledWith(mockUserKey, mockUserId);
|
||||||
|
expect(logService.warning).toHaveBeenCalledWith("Invalid key, throwing away stored keys");
|
||||||
|
expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).toHaveBeenCalledWith(mockUserId);
|
||||||
|
expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, {
|
||||||
|
userId: mockUserId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -178,11 +178,10 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
|||||||
|
|
||||||
async getUserKeyFromStorage(
|
async getUserKeyFromStorage(
|
||||||
keySuffix: KeySuffixOptions,
|
keySuffix: KeySuffixOptions,
|
||||||
userId?: UserId,
|
userId: UserId,
|
||||||
): Promise<UserKey | null> {
|
): Promise<UserKey | null> {
|
||||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
throw new Error("No active user id found.");
|
throw new Error("UserId is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
const userKey = await this.getKeyFromStorage(keySuffix, userId);
|
const userKey = await this.getKeyFromStorage(keySuffix, userId);
|
||||||
|
|||||||
Reference in New Issue
Block a user