1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

[PM-21705] Require userID for refreshAdditionalKeys() on key-service (#14810)

* Require userID for refreshAdditionalKeys()

* Add error handling to desktop Unlock settings

* Add more unit test coverage
This commit is contained in:
Thomas Avery
2025-06-06 13:38:25 -05:00
committed by GitHub
parent 3e4c37b8b3
commit 9d743a7ee0
8 changed files with 553 additions and 76 deletions

View File

@@ -92,7 +92,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
clientSecret,
]);
await this.keyService.refreshAdditionalKeys();
await this.keyService.refreshAdditionalKeys(userId);
}
availableVaultTimeoutActions$(userId?: string): Observable<VaultTimeoutAction[]> {

View File

@@ -83,8 +83,11 @@ export abstract class KeyService {
* Gets the user key from memory and sets it again,
* kicking off a refresh of any additional keys
* (such as auto, biometrics, or pin)
* @param userId The target user to refresh keys for.
* @throws Error when userId is null or undefined.
* @throws When userKey doesn't exist in memory for the target user.
*/
abstract refreshAdditionalKeys(): Promise<void>;
abstract refreshAdditionalKeys(userId: UserId): Promise<void>;
/**
* Observable value that returns whether or not the user has ever had a userKey,

View File

@@ -90,6 +90,35 @@ describe("keyService", () => {
expect(keyService).not.toBeFalsy();
});
describe("refreshAdditionalKeys", () => {
test.each([null as unknown as UserId, undefined as unknown as UserId])(
"throws when the provided userId is %s",
async (userId) => {
await expect(keyService.refreshAdditionalKeys(userId)).rejects.toThrow(
"UserId is required",
);
},
);
it("throws error if user key not found", async () => {
stateProvider.singleUser.getFake(mockUserId, USER_KEY).nextState(null);
await expect(keyService.refreshAdditionalKeys(mockUserId)).rejects.toThrow(
"No user key found for: " + mockUserId,
);
});
it("refreshes additional keys when user key is available", async () => {
const mockUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey;
stateProvider.singleUser.getFake(mockUserId, USER_KEY).nextState(mockUserKey);
const setUserKeySpy = jest.spyOn(keyService, "setUserKey");
await keyService.refreshAdditionalKeys(mockUserId);
expect(setUserKeySpy).toHaveBeenCalledWith(mockUserKey, mockUserId);
});
});
describe("getUserKey", () => {
let mockUserKey: UserKey;

View File

@@ -122,15 +122,17 @@ export class DefaultKeyService implements KeyServiceAbstraction {
await this.setPrivateKey(encPrivateKey, userId);
}
async refreshAdditionalKeys(): Promise<void> {
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
if (activeUserId == null) {
throw new Error("Can only refresh keys while there is an active user.");
async refreshAdditionalKeys(userId: UserId): Promise<void> {
if (userId == null) {
throw new Error("UserId is required.");
}
const key = await this.getUserKey(activeUserId);
await this.setUserKey(key, activeUserId);
const key = await firstValueFrom(this.userKey$(userId));
if (key == null) {
throw new Error("No user key found for: " + userId);
}
await this.setUserKey(key, userId);
}
everHadUserKey$(userId: UserId): Observable<boolean> {