1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

Auth/PM-10601 - Tech Debt Cleanup - Refactor Lock Component and User Verification to use PinService (#10408)

* PM-10601 - PinSvc new unlock check first draft

* PM-10601 - PinSvc - add new method for determining if pin decryption is available.

* PM-10601 - Add more docs on PinSvc

* PM-10601 - Update Lock Comp & User Verification service + tests to use new isPinDecryptionAvailable method
This commit is contained in:
Jared Snider
2024-08-13 15:19:13 -04:00
committed by GitHub
parent a6176aaf2c
commit a13e99ebe9
6 changed files with 115 additions and 12 deletions

View File

@@ -113,9 +113,17 @@ export abstract class PinServiceAbstraction {
/**
* Declares whether or not the user has a PIN set (either persistent or ephemeral).
* Note: for ephemeral, this does not check if we actual have an ephemeral PIN-encrypted UserKey stored in memory.
* Decryption might not be possible even if this returns true. Use {@link isPinDecryptionAvailable} if decryption is required.
*/
abstract isPinSet: (userId: UserId) => Promise<boolean>;
/**
* Checks if PIN-encrypted keys are stored for the user.
* Used for unlock / user verification scenarios where we will need to decrypt the UserKey with the PIN.
*/
abstract isPinDecryptionAvailable: (userId: UserId) => Promise<boolean>;
/**
* Decrypts the UserKey with the provided PIN.
*

View File

@@ -292,6 +292,34 @@ export class PinService implements PinServiceAbstraction {
return (await this.getPinLockType(userId)) !== "DISABLED";
}
async isPinDecryptionAvailable(userId: UserId): Promise<boolean> {
this.validateUserId(userId, "Cannot determine if decryption of user key via PIN is available.");
const pinLockType = await this.getPinLockType(userId);
switch (pinLockType) {
case "DISABLED":
return false;
case "PERSISTENT":
// The above getPinLockType call ensures that we have either a PinKeyEncryptedUserKey or OldPinKeyEncryptedMasterKey set.
return true;
case "EPHEMERAL": {
// The above getPinLockType call ensures that we have a UserKeyEncryptedPin set.
// However, we must additively check to ensure that we have a set PinKeyEncryptedUserKeyEphemeral b/c otherwise
// we cannot take a PIN, derive a PIN key, and decrypt the ephemeral UserKey.
const pinKeyEncryptedUserKeyEphemeral =
await this.getPinKeyEncryptedUserKeyEphemeral(userId);
return Boolean(pinKeyEncryptedUserKeyEphemeral);
}
default: {
// Compile-time check for exhaustive switch
const _exhaustiveCheck: never = pinLockType;
throw new Error(`Unexpected pinLockType: ${_exhaustiveCheck}`);
}
}
}
async decryptUserKeyWithPin(pin: string, userId: UserId): Promise<UserKey | null> {
this.validateUserId(userId, "Cannot decrypt user key with PIN.");

View File

@@ -416,6 +416,66 @@ describe("PinService", () => {
});
});
describe("isPinDecryptionAvailable()", () => {
it("should return false if pinLockType is DISABLED", async () => {
// Arrange
sut.getPinLockType = jest.fn().mockResolvedValue("DISABLED");
// Act
const result = await sut.isPinDecryptionAvailable(mockUserId);
// Assert
expect(result).toBe(false);
});
it("should return true if pinLockType is PERSISTENT", async () => {
// Arrange
sut.getPinLockType = jest.fn().mockResolvedValue("PERSISTENT");
// Act
const result = await sut.isPinDecryptionAvailable(mockUserId);
// Assert
expect(result).toBe(true);
});
it("should return true if pinLockType is EPHEMERAL and we have an ephemeral PIN key encrypted user key", async () => {
// Arrange
sut.getPinLockType = jest.fn().mockResolvedValue("EPHEMERAL");
sut.getPinKeyEncryptedUserKeyEphemeral = jest
.fn()
.mockResolvedValue(pinKeyEncryptedUserKeyEphemeral);
// Act
const result = await sut.isPinDecryptionAvailable(mockUserId);
// Assert
expect(result).toBe(true);
});
it("should return false if pinLockType is EPHEMERAL and we do not have an ephemeral PIN key encrypted user key", async () => {
// Arrange
sut.getPinLockType = jest.fn().mockResolvedValue("EPHEMERAL");
sut.getPinKeyEncryptedUserKeyEphemeral = jest.fn().mockResolvedValue(null);
// Act
const result = await sut.isPinDecryptionAvailable(mockUserId);
// Assert
expect(result).toBe(false);
});
it("should throw an error if an unexpected pinLockType is returned", async () => {
// Arrange
sut.getPinLockType = jest.fn().mockResolvedValue("UNKNOWN");
// Act & Assert
await expect(sut.isPinDecryptionAvailable(mockUserId)).rejects.toThrow(
"Unexpected pinLockType: UNKNOWN",
);
});
});
describe("decryptUserKeyWithPin()", () => {
async function setupDecryptUserKeyWithPinMocks(
pinLockType: PinLockType,