mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-10741] Refactor biometrics interface & add dynamic status (#10973)
This commit is contained in:
@@ -7,14 +7,17 @@ import {
|
||||
UserDecryptionOptions,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { KdfConfig, KeyService } from "@bitwarden/key-management";
|
||||
import {
|
||||
BiometricsService,
|
||||
BiometricsStatus,
|
||||
KdfConfig,
|
||||
KeyService,
|
||||
} from "@bitwarden/key-management";
|
||||
|
||||
import { KdfConfigService } from "../../../../../key-management/src/abstractions/kdf-config.service";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "../../../../spec";
|
||||
import { VaultTimeoutSettingsService } from "../../../abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { I18nService } from "../../../platform/abstractions/i18n.service";
|
||||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
|
||||
import { HashPurpose } from "../../../platform/enums";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { UserId } from "../../../types/guid";
|
||||
@@ -36,10 +39,9 @@ describe("UserVerificationService", () => {
|
||||
const userVerificationApiService = mock<UserVerificationApiServiceAbstraction>();
|
||||
const userDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>();
|
||||
const pinService = mock<PinServiceAbstraction>();
|
||||
const logService = mock<LogService>();
|
||||
const vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
||||
const platformUtilsService = mock<PlatformUtilsService>();
|
||||
const kdfConfigService = mock<KdfConfigService>();
|
||||
const biometricsService = mock<BiometricsService>();
|
||||
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
let accountService: FakeAccountService;
|
||||
@@ -56,10 +58,8 @@ describe("UserVerificationService", () => {
|
||||
userVerificationApiService,
|
||||
userDecryptionOptionsService,
|
||||
pinService,
|
||||
logService,
|
||||
vaultTimeoutSettingsService,
|
||||
platformUtilsService,
|
||||
kdfConfigService,
|
||||
biometricsService,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -113,26 +113,15 @@ describe("UserVerificationService", () => {
|
||||
);
|
||||
|
||||
test.each([
|
||||
[true, true, true, true],
|
||||
[true, true, true, false],
|
||||
[true, true, false, false],
|
||||
[false, true, false, true],
|
||||
[false, false, false, false],
|
||||
[false, false, true, false],
|
||||
[false, false, false, true],
|
||||
[true, BiometricsStatus.Available],
|
||||
[false, BiometricsStatus.DesktopDisconnected],
|
||||
[false, BiometricsStatus.HardwareUnavailable],
|
||||
])(
|
||||
"returns %s for biometrics availability when isBiometricLockSet is %s, hasUserKeyStored is %s, and supportsSecureStorage is %s",
|
||||
async (
|
||||
expectedReturn: boolean,
|
||||
isBiometricsLockSet: boolean,
|
||||
isBiometricsUserKeyStored: boolean,
|
||||
platformSupportSecureStorage: boolean,
|
||||
) => {
|
||||
async (expectedReturn: boolean, biometricsStatus: BiometricsStatus) => {
|
||||
setMasterPasswordAvailability(false);
|
||||
setPinAvailability("DISABLED");
|
||||
vaultTimeoutSettingsService.isBiometricLockSet.mockResolvedValue(isBiometricsLockSet);
|
||||
keyService.hasUserKeyStored.mockResolvedValue(isBiometricsUserKeyStored);
|
||||
platformUtilsService.supportsSecureStorage.mockReturnValue(platformSupportSecureStorage);
|
||||
biometricsService.getBiometricsStatus.mockResolvedValue(biometricsStatus);
|
||||
|
||||
const result = await sut.getAvailableVerificationOptions("client");
|
||||
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
|
||||
import {
|
||||
BiometricsService,
|
||||
BiometricsStatus,
|
||||
KdfConfigService,
|
||||
KeyService,
|
||||
} from "@bitwarden/key-management";
|
||||
|
||||
import { PinServiceAbstraction } from "../../../../../auth/src/common/abstractions/pin.service.abstraction";
|
||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../../abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { I18nService } from "../../../platform/abstractions/i18n.service";
|
||||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
|
||||
import { HashPurpose } from "../../../platform/enums";
|
||||
import { KeySuffixOptions } from "../../../platform/enums/key-suffix-options.enum";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { UserKey } from "../../../types/key";
|
||||
import { AccountService } from "../../abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "../../abstractions/master-password.service.abstraction";
|
||||
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
|
||||
@@ -47,10 +47,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||
private userVerificationApiService: UserVerificationApiServiceAbstraction,
|
||||
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
private pinService: PinServiceAbstraction,
|
||||
private logService: LogService,
|
||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private kdfConfigService: KdfConfigService,
|
||||
private biometricsService: BiometricsService,
|
||||
) {}
|
||||
|
||||
async getAvailableVerificationOptions(
|
||||
@@ -58,17 +56,13 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||
): Promise<UserVerificationOptions> {
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
if (verificationType === "client") {
|
||||
const [
|
||||
userHasMasterPassword,
|
||||
isPinDecryptionAvailable,
|
||||
biometricsLockSet,
|
||||
biometricsUserKeyStored,
|
||||
] = await Promise.all([
|
||||
this.hasMasterPasswordAndMasterKeyHash(userId),
|
||||
this.pinService.isPinDecryptionAvailable(userId),
|
||||
this.vaultTimeoutSettingsService.isBiometricLockSet(userId),
|
||||
this.keyService.hasUserKeyStored(KeySuffixOptions.Biometric, userId),
|
||||
]);
|
||||
const [userHasMasterPassword, isPinDecryptionAvailable, biometricsStatus] = await Promise.all(
|
||||
[
|
||||
this.hasMasterPasswordAndMasterKeyHash(userId),
|
||||
this.pinService.isPinDecryptionAvailable(userId),
|
||||
this.biometricsService.getBiometricsStatus(),
|
||||
],
|
||||
);
|
||||
|
||||
// note: we do not need to check this.platformUtilsService.supportsBiometric() because
|
||||
// we can just use the logic below which works for both desktop & the browser extension.
|
||||
@@ -77,9 +71,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||
client: {
|
||||
masterPassword: userHasMasterPassword,
|
||||
pin: isPinDecryptionAvailable,
|
||||
biometrics:
|
||||
biometricsLockSet &&
|
||||
(biometricsUserKeyStored || !this.platformUtilsService.supportsSecureStorage()),
|
||||
biometrics: biometricsStatus === BiometricsStatus.Available,
|
||||
},
|
||||
server: {
|
||||
masterPassword: false,
|
||||
@@ -253,17 +245,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
|
||||
}
|
||||
|
||||
private async verifyUserByBiometrics(): Promise<boolean> {
|
||||
let userKey: UserKey;
|
||||
// Biometrics crashes and doesn't return a value if the user cancels the prompt
|
||||
try {
|
||||
userKey = await this.keyService.getUserKeyFromStorage(KeySuffixOptions.Biometric);
|
||||
} catch (e) {
|
||||
this.logService.error(`Biometrics User Verification failed: ${e.message}`);
|
||||
// So, any failures should be treated as a failed verification
|
||||
return false;
|
||||
}
|
||||
|
||||
return userKey != null;
|
||||
return this.biometricsService.authenticateWithBiometrics();
|
||||
}
|
||||
|
||||
async requestOTP() {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// @ts-strict-ignore
|
||||
import { firstValueFrom, map, timeout } from "rxjs";
|
||||
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { BiometricStateService } from "@bitwarden/key-management";
|
||||
|
||||
@@ -24,6 +25,7 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract
|
||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||
private biometricStateService: BiometricStateService,
|
||||
private accountService: AccountService,
|
||||
private logService: LogService,
|
||||
) {}
|
||||
|
||||
async startProcessReload(authService: AuthService): Promise<void> {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export enum KeySuffixOptions {
|
||||
Auto = "auto",
|
||||
Biometric = "biometric",
|
||||
Pin = "pin",
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { LogoutReason } from "@bitwarden/auth/common";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling";
|
||||
import { BiometricsService } from "@bitwarden/key-management";
|
||||
|
||||
import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service";
|
||||
import { SearchService } from "../../abstractions/search.service";
|
||||
@@ -41,6 +42,7 @@ describe("VaultTimeoutService", () => {
|
||||
let stateEventRunnerService: MockProxy<StateEventRunnerService>;
|
||||
let taskSchedulerService: MockProxy<TaskSchedulerService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let biometricsService: MockProxy<BiometricsService>;
|
||||
let lockedCallback: jest.Mock<Promise<void>, [userId: string]>;
|
||||
let loggedOutCallback: jest.Mock<Promise<void>, [logoutReason: LogoutReason, userId?: string]>;
|
||||
|
||||
@@ -66,6 +68,7 @@ describe("VaultTimeoutService", () => {
|
||||
stateEventRunnerService = mock();
|
||||
taskSchedulerService = mock<TaskSchedulerService>();
|
||||
logService = mock<LogService>();
|
||||
biometricsService = mock<BiometricsService>();
|
||||
|
||||
lockedCallback = jest.fn();
|
||||
loggedOutCallback = jest.fn();
|
||||
@@ -93,6 +96,7 @@ describe("VaultTimeoutService", () => {
|
||||
stateEventRunnerService,
|
||||
taskSchedulerService,
|
||||
logService,
|
||||
biometricsService,
|
||||
lockedCallback,
|
||||
loggedOutCallback,
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { LogoutReason } from "@bitwarden/auth/common";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling";
|
||||
import { BiometricsService } from "@bitwarden/key-management";
|
||||
|
||||
import { SearchService } from "../../abstractions/search.service";
|
||||
import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
@@ -41,6 +42,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
||||
private stateEventRunnerService: StateEventRunnerService,
|
||||
private taskSchedulerService: TaskSchedulerService,
|
||||
protected logService: LogService,
|
||||
private biometricService: BiometricsService,
|
||||
private lockedCallback: (userId?: string) => Promise<void> = null,
|
||||
private loggedOutCallback: (
|
||||
logoutReason: LogoutReason,
|
||||
@@ -98,6 +100,8 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
||||
}
|
||||
|
||||
async lock(userId?: UserId): Promise<void> {
|
||||
await this.biometricService.setShouldAutopromptNow(false);
|
||||
|
||||
const authed = await this.stateService.getIsAuthenticated({ userId: userId });
|
||||
if (!authed) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user