mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[PM-14445] TS strict for Key Management, Keys and Lock component (#13121)
* PM-14445: TS strict for Key Management Biometrics * formatting * callbacks not null expectations * state nullability expectations updates * unit tests fix * secure channel naming, explicit null check on messageId * KM-14445: TS strict for Key Management, Keys and Lock component * conflicts resolution, new strict check failures * null simplifications * migrate legacy encryption when no active user throw error instead of hiding it * throw instead of return
This commit is contained in:
@@ -117,40 +117,40 @@ describe("keyService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.each(["hasUserKey", "hasUserKeyInMemory"])(
|
||||
`%s`,
|
||||
(method: "hasUserKey" | "hasUserKeyInMemory") => {
|
||||
let mockUserKey: UserKey;
|
||||
describe.each(["hasUserKey", "hasUserKeyInMemory"])(`%s`, (methodName: string) => {
|
||||
let mockUserKey: UserKey;
|
||||
let method: (userId?: UserId) => Promise<boolean>;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||
});
|
||||
beforeEach(() => {
|
||||
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
|
||||
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
|
||||
method =
|
||||
methodName === "hasUserKey"
|
||||
? keyService.hasUserKey.bind(keyService)
|
||||
: keyService.hasUserKeyInMemory.bind(keyService);
|
||||
});
|
||||
|
||||
it.each([true, false])("returns %s if the user key is set", async (hasKey) => {
|
||||
it.each([true, false])("returns %s if the user key is set", async (hasKey) => {
|
||||
stateProvider.singleUser.getFake(mockUserId, USER_KEY).nextState(hasKey ? mockUserKey : null);
|
||||
expect(await method(mockUserId)).toBe(hasKey);
|
||||
});
|
||||
|
||||
it("returns false when no active userId is set", async () => {
|
||||
accountService.activeAccountSubject.next(null);
|
||||
expect(await method()).toBe(false);
|
||||
});
|
||||
|
||||
it.each([true, false])(
|
||||
"resolves %s for active user id when none is provided",
|
||||
async (hasKey) => {
|
||||
stateProvider.activeUserId$ = of(mockUserId);
|
||||
stateProvider.singleUser
|
||||
.getFake(mockUserId, USER_KEY)
|
||||
.nextState(hasKey ? mockUserKey : null);
|
||||
expect(await keyService[method](mockUserId)).toBe(hasKey);
|
||||
});
|
||||
|
||||
it("returns false when no active userId is set", async () => {
|
||||
accountService.activeAccountSubject.next(null);
|
||||
expect(await keyService[method]()).toBe(false);
|
||||
});
|
||||
|
||||
it.each([true, false])(
|
||||
"resolves %s for active user id when none is provided",
|
||||
async (hasKey) => {
|
||||
stateProvider.activeUserId$ = of(mockUserId);
|
||||
stateProvider.singleUser
|
||||
.getFake(mockUserId, USER_KEY)
|
||||
.nextState(hasKey ? mockUserKey : null);
|
||||
expect(await keyService[method]()).toBe(hasKey);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
expect(await method()).toBe(hasKey);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("getUserKeyWithLegacySupport", () => {
|
||||
let mockUserKey: UserKey;
|
||||
@@ -263,11 +263,15 @@ describe("keyService", () => {
|
||||
});
|
||||
|
||||
it("throws if key is null", async () => {
|
||||
await expect(keyService.setUserKey(null, mockUserId)).rejects.toThrow("No key provided.");
|
||||
await expect(keyService.setUserKey(null as unknown as UserKey, mockUserId)).rejects.toThrow(
|
||||
"No key provided.",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws if userId is null", async () => {
|
||||
await expect(keyService.setUserKey(mockUserKey, null)).rejects.toThrow("No userId provided.");
|
||||
await expect(keyService.setUserKey(mockUserKey, null as unknown as UserId)).rejects.toThrow(
|
||||
"No userId provided.",
|
||||
);
|
||||
});
|
||||
|
||||
describe("Pin Key refresh", () => {
|
||||
@@ -338,21 +342,21 @@ describe("keyService", () => {
|
||||
});
|
||||
|
||||
it("throws if userKey is null", async () => {
|
||||
await expect(keyService.setUserKeys(null, mockEncPrivateKey, mockUserId)).rejects.toThrow(
|
||||
"No userKey provided.",
|
||||
);
|
||||
await expect(
|
||||
keyService.setUserKeys(null as unknown as UserKey, mockEncPrivateKey, mockUserId),
|
||||
).rejects.toThrow("No userKey provided.");
|
||||
});
|
||||
|
||||
it("throws if encPrivateKey is null", async () => {
|
||||
await expect(keyService.setUserKeys(mockUserKey, null, mockUserId)).rejects.toThrow(
|
||||
"No encPrivateKey provided.",
|
||||
);
|
||||
await expect(
|
||||
keyService.setUserKeys(mockUserKey, null as unknown as EncryptedString, mockUserId),
|
||||
).rejects.toThrow("No encPrivateKey provided.");
|
||||
});
|
||||
|
||||
it("throws if userId is null", async () => {
|
||||
await expect(keyService.setUserKeys(mockUserKey, mockEncPrivateKey, null)).rejects.toThrow(
|
||||
"No userId provided.",
|
||||
);
|
||||
await expect(
|
||||
keyService.setUserKeys(mockUserKey, mockEncPrivateKey, null as unknown as UserId),
|
||||
).rejects.toThrow("No userId provided.");
|
||||
});
|
||||
|
||||
it("throws if encPrivateKey cannot be decrypted with the userKey", async () => {
|
||||
@@ -388,7 +392,7 @@ describe("keyService", () => {
|
||||
let callCount = 0;
|
||||
stateProvider.activeUserId$ = stateProvider.activeUserId$.pipe(tap(() => callCount++));
|
||||
|
||||
await keyService.clearKeys(null);
|
||||
await keyService.clearKeys();
|
||||
expect(callCount).toBe(1);
|
||||
|
||||
// revert to the original state
|
||||
@@ -402,7 +406,7 @@ describe("keyService", () => {
|
||||
USER_KEY,
|
||||
])("key removal", (key: UserKeyDefinition<unknown>) => {
|
||||
it(`clears ${key.key} for active user when unspecified`, async () => {
|
||||
await keyService.clearKeys(null);
|
||||
await keyService.clearKeys();
|
||||
|
||||
const encryptedOrgKeyState = stateProvider.singleUser.getFake(mockUserId, key);
|
||||
expect(encryptedOrgKeyState.nextMock).toHaveBeenCalledTimes(1);
|
||||
@@ -426,7 +430,10 @@ describe("keyService", () => {
|
||||
makeUserKey: boolean;
|
||||
};
|
||||
|
||||
function setupKeys({ makeMasterKey, makeUserKey }: SetupKeysParams): [UserKey, MasterKey] {
|
||||
function setupKeys({
|
||||
makeMasterKey,
|
||||
makeUserKey,
|
||||
}: SetupKeysParams): [UserKey | null, MasterKey | null] {
|
||||
const userKeyState = stateProvider.singleUser.getFake(mockUserId, USER_KEY);
|
||||
const fakeMasterKey = makeMasterKey ? makeSymmetricCryptoKey<MasterKey>(64) : null;
|
||||
masterPasswordService.masterKeySubject.next(fakeMasterKey);
|
||||
@@ -449,7 +456,7 @@ describe("keyService", () => {
|
||||
|
||||
const fakeEncryptedUserPrivateKey = makeEncString("1");
|
||||
|
||||
userEncryptedPrivateKeyState.nextState(fakeEncryptedUserPrivateKey.encryptedString);
|
||||
userEncryptedPrivateKeyState.nextState(fakeEncryptedUserPrivateKey.encryptedString!);
|
||||
|
||||
// Decryption of the user private key
|
||||
const fakeDecryptedUserPrivateKey = makeStaticByteArray(10, 1);
|
||||
@@ -526,7 +533,7 @@ describe("keyService", () => {
|
||||
function updateKeys(keys: Partial<UpdateKeysParams> = {}) {
|
||||
if ("userKey" in keys) {
|
||||
const userKeyState = stateProvider.singleUser.getFake(mockUserId, USER_KEY);
|
||||
userKeyState.nextState(keys.userKey);
|
||||
userKeyState.nextState(keys.userKey!);
|
||||
}
|
||||
|
||||
if ("encryptedPrivateKey" in keys) {
|
||||
@@ -534,7 +541,7 @@ describe("keyService", () => {
|
||||
mockUserId,
|
||||
USER_ENCRYPTED_PRIVATE_KEY,
|
||||
);
|
||||
userEncryptedPrivateKey.nextState(keys.encryptedPrivateKey.encryptedString);
|
||||
userEncryptedPrivateKey.nextState(keys.encryptedPrivateKey!.encryptedString!);
|
||||
}
|
||||
|
||||
if ("orgKeys" in keys) {
|
||||
@@ -542,7 +549,7 @@ describe("keyService", () => {
|
||||
mockUserId,
|
||||
USER_ENCRYPTED_ORGANIZATION_KEYS,
|
||||
);
|
||||
orgKeysState.nextState(keys.orgKeys);
|
||||
orgKeysState.nextState(keys.orgKeys!);
|
||||
}
|
||||
|
||||
if ("providerKeys" in keys) {
|
||||
@@ -550,7 +557,7 @@ describe("keyService", () => {
|
||||
mockUserId,
|
||||
USER_ENCRYPTED_PROVIDER_KEYS,
|
||||
);
|
||||
providerKeysState.nextState(keys.providerKeys);
|
||||
providerKeysState.nextState(keys.providerKeys!);
|
||||
}
|
||||
|
||||
encryptService.decryptToBytes.mockImplementation((encryptedPrivateKey, userKey) => {
|
||||
@@ -572,8 +579,8 @@ describe("keyService", () => {
|
||||
const decryptionKeys = await firstValueFrom(keyService.cipherDecryptionKeys$(mockUserId));
|
||||
|
||||
expect(decryptionKeys).not.toBeNull();
|
||||
expect(decryptionKeys.userKey).not.toBeNull();
|
||||
expect(decryptionKeys.orgKeys).toEqual({});
|
||||
expect(decryptionKeys!.userKey).not.toBeNull();
|
||||
expect(decryptionKeys!.orgKeys).toEqual({});
|
||||
});
|
||||
|
||||
it("returns decryption keys when there are org keys", async () => {
|
||||
@@ -581,18 +588,18 @@ describe("keyService", () => {
|
||||
userKey: makeSymmetricCryptoKey<UserKey>(64),
|
||||
encryptedPrivateKey: makeEncString("privateKey"),
|
||||
orgKeys: {
|
||||
[org1Id]: { type: "organization", key: makeEncString("org1Key").encryptedString },
|
||||
[org1Id]: { type: "organization", key: makeEncString("org1Key").encryptedString! },
|
||||
},
|
||||
});
|
||||
|
||||
const decryptionKeys = await firstValueFrom(keyService.cipherDecryptionKeys$(mockUserId));
|
||||
|
||||
expect(decryptionKeys).not.toBeNull();
|
||||
expect(decryptionKeys.userKey).not.toBeNull();
|
||||
expect(decryptionKeys.orgKeys).not.toBeNull();
|
||||
expect(Object.keys(decryptionKeys.orgKeys)).toHaveLength(1);
|
||||
expect(decryptionKeys.orgKeys[org1Id]).not.toBeNull();
|
||||
const orgKey = decryptionKeys.orgKeys[org1Id];
|
||||
expect(decryptionKeys!.userKey).not.toBeNull();
|
||||
expect(decryptionKeys!.orgKeys).not.toBeNull();
|
||||
expect(Object.keys(decryptionKeys!.orgKeys!)).toHaveLength(1);
|
||||
expect(decryptionKeys!.orgKeys![org1Id]).not.toBeNull();
|
||||
const orgKey = decryptionKeys!.orgKeys![org1Id];
|
||||
expect(orgKey.keyB64).toContain("org1Key");
|
||||
});
|
||||
|
||||
@@ -601,7 +608,7 @@ describe("keyService", () => {
|
||||
userKey: makeSymmetricCryptoKey<UserKey>(64),
|
||||
encryptedPrivateKey: makeEncString("privateKey"),
|
||||
orgKeys: {
|
||||
[org1Id]: { type: "organization", key: makeEncString("org1Key").encryptedString },
|
||||
[org1Id]: { type: "organization", key: makeEncString("org1Key").encryptedString! },
|
||||
},
|
||||
providerKeys: {},
|
||||
});
|
||||
@@ -609,11 +616,11 @@ describe("keyService", () => {
|
||||
const decryptionKeys = await firstValueFrom(keyService.cipherDecryptionKeys$(mockUserId));
|
||||
|
||||
expect(decryptionKeys).not.toBeNull();
|
||||
expect(decryptionKeys.userKey).not.toBeNull();
|
||||
expect(decryptionKeys.orgKeys).not.toBeNull();
|
||||
expect(Object.keys(decryptionKeys.orgKeys)).toHaveLength(1);
|
||||
expect(decryptionKeys.orgKeys[org1Id]).not.toBeNull();
|
||||
const orgKey = decryptionKeys.orgKeys[org1Id];
|
||||
expect(decryptionKeys!.userKey).not.toBeNull();
|
||||
expect(decryptionKeys!.orgKeys).not.toBeNull();
|
||||
expect(Object.keys(decryptionKeys!.orgKeys!)).toHaveLength(1);
|
||||
expect(decryptionKeys!.orgKeys![org1Id]).not.toBeNull();
|
||||
const orgKey = decryptionKeys!.orgKeys![org1Id];
|
||||
expect(orgKey.keyB64).toContain("org1Key");
|
||||
});
|
||||
|
||||
@@ -623,30 +630,30 @@ describe("keyService", () => {
|
||||
userKey: makeSymmetricCryptoKey<UserKey>(64),
|
||||
encryptedPrivateKey: makeEncString("privateKey"),
|
||||
orgKeys: {
|
||||
[org1Id]: { type: "organization", key: makeEncString("org1Key").encryptedString },
|
||||
[org1Id]: { type: "organization", key: makeEncString("org1Key").encryptedString! },
|
||||
[org2Id]: {
|
||||
type: "provider",
|
||||
key: makeEncString("provider1Key").encryptedString,
|
||||
key: makeEncString("provider1Key").encryptedString!,
|
||||
providerId: "provider1",
|
||||
},
|
||||
},
|
||||
providerKeys: {
|
||||
provider1: makeEncString("provider1Key").encryptedString,
|
||||
provider1: makeEncString("provider1Key").encryptedString!,
|
||||
},
|
||||
});
|
||||
|
||||
const decryptionKeys = await firstValueFrom(keyService.cipherDecryptionKeys$(mockUserId));
|
||||
|
||||
expect(decryptionKeys).not.toBeNull();
|
||||
expect(decryptionKeys.userKey).not.toBeNull();
|
||||
expect(decryptionKeys.orgKeys).not.toBeNull();
|
||||
expect(Object.keys(decryptionKeys.orgKeys)).toHaveLength(2);
|
||||
expect(decryptionKeys!.userKey).not.toBeNull();
|
||||
expect(decryptionKeys!.orgKeys).not.toBeNull();
|
||||
expect(Object.keys(decryptionKeys!.orgKeys!)).toHaveLength(2);
|
||||
|
||||
const orgKey = decryptionKeys.orgKeys[org1Id];
|
||||
const orgKey = decryptionKeys!.orgKeys![org1Id];
|
||||
expect(orgKey).not.toBeNull();
|
||||
expect(orgKey.keyB64).toContain("org1Key");
|
||||
|
||||
const org2Key = decryptionKeys.orgKeys[org2Id];
|
||||
const org2Key = decryptionKeys!.orgKeys![org2Id];
|
||||
expect(org2Key).not.toBeNull();
|
||||
expect(org2Key.keyB64).toContain("provider1Key");
|
||||
});
|
||||
@@ -686,7 +693,7 @@ describe("keyService", () => {
|
||||
// User has their org keys set
|
||||
updateKeys({
|
||||
orgKeys: {
|
||||
[org1Id]: { type: "organization", key: makeEncString("org1Key").encryptedString },
|
||||
[org1Id]: { type: "organization", key: makeEncString("org1Key").encryptedString! },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -741,7 +748,7 @@ describe("keyService", () => {
|
||||
type TestCase = {
|
||||
masterKey: MasterKey;
|
||||
masterPassword: string | null;
|
||||
storedMasterKeyHash: string;
|
||||
storedMasterKeyHash: string | null;
|
||||
mockReturnedHash: string;
|
||||
expectedToMatch: boolean;
|
||||
};
|
||||
@@ -782,7 +789,7 @@ describe("keyService", () => {
|
||||
masterPasswordService.masterKeyHashSubject.next(storedMasterKeyHash);
|
||||
|
||||
cryptoFunctionService.pbkdf2
|
||||
.calledWith(masterKey.key, masterPassword, "sha256", 2)
|
||||
.calledWith(masterKey.key, masterPassword as string, "sha256", 2)
|
||||
.mockResolvedValue(Utils.fromB64ToArray(mockReturnedHash));
|
||||
|
||||
const actualDidMatch = await keyService.compareKeyHash(
|
||||
|
||||
Reference in New Issue
Block a user