mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 22:33:35 +00:00
[PM-22133] Require userID for clearStoredUserKey (#14973)
This commit is contained in:
@@ -437,7 +437,7 @@ export default class MainBackground {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Services
|
// Services
|
||||||
const lockedCallback = async (userId?: string) => {
|
const lockedCallback = async (userId: UserId) => {
|
||||||
await this.refreshBadge();
|
await this.refreshBadge();
|
||||||
await this.refreshMenu(true);
|
await this.refreshMenu(true);
|
||||||
if (this.systemService != null) {
|
if (this.systemService != null) {
|
||||||
|
|||||||
@@ -715,8 +715,8 @@ export class ServiceContainer {
|
|||||||
|
|
||||||
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
||||||
|
|
||||||
const lockedCallback = async (userId?: string) =>
|
const lockedCallback = async (userId: UserId) =>
|
||||||
await this.keyService.clearStoredUserKey(KeySuffixOptions.Auto);
|
await this.keyService.clearStoredUserKey(KeySuffixOptions.Auto, userId);
|
||||||
|
|
||||||
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
|
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export class ElectronKeyService extends DefaultKeyService {
|
|||||||
return super.hasUserKeyStored(keySuffix, userId);
|
return super.hasUserKeyStored(keySuffix, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise<void> {
|
override async clearStoredUserKey(keySuffix: KeySuffixOptions, userId: UserId): Promise<void> {
|
||||||
await super.clearStoredUserKey(keySuffix, userId);
|
await super.clearStoredUserKey(keySuffix, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -417,16 +417,12 @@ describe("VaultTimeoutService", () => {
|
|||||||
expect(stateEventRunnerService.handleEvent).toHaveBeenCalledWith("lock", "user1");
|
expect(stateEventRunnerService.handleEvent).toHaveBeenCalledWith("lock", "user1");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call locked callback if no user passed into lock", async () => {
|
it("should call locked callback with the locking user if no userID is passed in.", async () => {
|
||||||
setupLock();
|
setupLock();
|
||||||
|
|
||||||
await vaultTimeoutService.lock();
|
await vaultTimeoutService.lock();
|
||||||
|
|
||||||
// Currently these pass `undefined` (or what they were given) as the userId back
|
expect(lockedCallback).toHaveBeenCalledWith("user1");
|
||||||
// but we could change this to give the user that was locked (active) to these methods
|
|
||||||
// so they don't have to get it their own way, but that is a behavioral change that needs
|
|
||||||
// to be tested.
|
|
||||||
expect(lockedCallback).toHaveBeenCalledWith(undefined);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call state event runner with user passed into lock", async () => {
|
it("should call state event runner with user passed into lock", async () => {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
private taskSchedulerService: TaskSchedulerService,
|
private taskSchedulerService: TaskSchedulerService,
|
||||||
protected logService: LogService,
|
protected logService: LogService,
|
||||||
private biometricService: BiometricsService,
|
private biometricService: BiometricsService,
|
||||||
private lockedCallback: (userId?: string) => Promise<void> = null,
|
private lockedCallback: (userId: UserId) => Promise<void> = null,
|
||||||
private loggedOutCallback: (
|
private loggedOutCallback: (
|
||||||
logoutReason: LogoutReason,
|
logoutReason: LogoutReason,
|
||||||
userId?: string,
|
userId?: string,
|
||||||
@@ -166,7 +166,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
this.messagingService.send("locked", { userId: lockingUserId });
|
this.messagingService.send("locked", { userId: lockingUserId });
|
||||||
|
|
||||||
if (this.lockedCallback != null) {
|
if (this.lockedCallback != null) {
|
||||||
await this.lockedCallback(userId);
|
await this.lockedCallback(lockingUserId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,8 +167,9 @@ export abstract class KeyService {
|
|||||||
* Clears the user's stored version of the user key
|
* Clears the user's stored version of the user key
|
||||||
* @param keySuffix The desired version of the key to clear
|
* @param keySuffix The desired version of the key to clear
|
||||||
* @param userId The desired user
|
* @param userId The desired user
|
||||||
|
* @throws Error when userId is null or undefined.
|
||||||
*/
|
*/
|
||||||
abstract clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: string): Promise<void>;
|
abstract clearStoredUserKey(keySuffix: KeySuffixOptions, userId: string): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Stores the master key encrypted user key
|
* Stores the master key encrypted user key
|
||||||
* @throws Error when userId is null and there is no active user.
|
* @throws Error when userId is null and there is no active user.
|
||||||
|
|||||||
@@ -410,6 +410,45 @@ describe("keyService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("clearStoredUserKey", () => {
|
||||||
|
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.clearStoredUserKey(keySuffix, userId)).rejects.toThrow(
|
||||||
|
"UserId is required",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with Auto key suffix", () => {
|
||||||
|
it("UserKeyAutoUnlock is cleared and pin keys are not cleared", async () => {
|
||||||
|
await keyService.clearStoredUserKey(KeySuffixOptions.Auto, mockUserId);
|
||||||
|
|
||||||
|
expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, {
|
||||||
|
userId: mockUserId,
|
||||||
|
});
|
||||||
|
expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with PIN key suffix", () => {
|
||||||
|
it("pin keys are cleared and user key auto unlock not", async () => {
|
||||||
|
await keyService.clearStoredUserKey(KeySuffixOptions.Pin, mockUserId);
|
||||||
|
|
||||||
|
expect(stateService.setUserKeyAutoUnlock).not.toHaveBeenCalled();
|
||||||
|
expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).toHaveBeenCalledWith(mockUserId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("clearKeys", () => {
|
describe("clearKeys", () => {
|
||||||
test.each([null as unknown as UserId, undefined as unknown as UserId])(
|
test.each([null as unknown as UserId, undefined as unknown as UserId])(
|
||||||
"throws when the provided userId is %s",
|
"throws when the provided userId is %s",
|
||||||
|
|||||||
@@ -250,16 +250,16 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
|||||||
await this.clearAllStoredUserKeys(userId);
|
await this.clearAllStoredUserKeys(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise<void> {
|
async clearStoredUserKey(keySuffix: KeySuffixOptions, userId: UserId): Promise<void> {
|
||||||
if (keySuffix === KeySuffixOptions.Auto) {
|
if (userId == null) {
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
throw new Error("UserId is required");
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
this.stateService.setUserKeyAutoUnlock(null, { userId: userId });
|
|
||||||
}
|
}
|
||||||
if (keySuffix === KeySuffixOptions.Pin && userId != null) {
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
if (keySuffix === KeySuffixOptions.Auto) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
await this.stateService.setUserKeyAutoUnlock(null, { userId: userId });
|
||||||
this.pinService.clearPinKeyEncryptedUserKeyEphemeral(userId);
|
}
|
||||||
|
if (keySuffix === KeySuffixOptions.Pin) {
|
||||||
|
await this.pinService.clearPinKeyEncryptedUserKeyEphemeral(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user