Move fingerprint validated to biometric state povider (#8058)
This commit is contained in:
parent
eedd6f0881
commit
f83dcf2b24
|
@ -814,6 +814,7 @@ export default class MainBackground {
|
||||||
this.stateService,
|
this.stateService,
|
||||||
this.autofillSettingsService,
|
this.autofillSettingsService,
|
||||||
this.vaultTimeoutSettingsService,
|
this.vaultTimeoutSettingsService,
|
||||||
|
this.biometricStateService,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Other fields
|
// Other fields
|
||||||
|
|
|
@ -84,10 +84,6 @@ export class NativeMessagingBackground {
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private biometricStateService: BiometricStateService,
|
private biometricStateService: BiometricStateService,
|
||||||
) {
|
) {
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
this.stateService.setBiometricFingerprintValidated(false);
|
|
||||||
|
|
||||||
if (chrome?.permissions?.onAdded) {
|
if (chrome?.permissions?.onAdded) {
|
||||||
// Reload extension to activate nativeMessaging
|
// Reload extension to activate nativeMessaging
|
||||||
chrome.permissions.onAdded.addListener((permissions) => {
|
chrome.permissions.onAdded.addListener((permissions) => {
|
||||||
|
@ -100,9 +96,7 @@ export class NativeMessagingBackground {
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
this.appId = await this.appIdService.getAppId();
|
this.appId = await this.appIdService.getAppId();
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await this.biometricStateService.setFingerprintValidated(false);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
this.stateService.setBiometricFingerprintValidated(false);
|
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
this.port = BrowserApi.connectNative("com.8bit.bitwarden");
|
this.port = BrowserApi.connectNative("com.8bit.bitwarden");
|
||||||
|
@ -148,9 +142,7 @@ export class NativeMessagingBackground {
|
||||||
|
|
||||||
if (this.validatingFingerprint) {
|
if (this.validatingFingerprint) {
|
||||||
this.validatingFingerprint = false;
|
this.validatingFingerprint = false;
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await this.biometricStateService.setFingerprintValidated(true);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
this.stateService.setBiometricFingerprintValidated(true);
|
|
||||||
}
|
}
|
||||||
this.sharedSecret = new SymmetricCryptoKey(decrypted);
|
this.sharedSecret = new SymmetricCryptoKey(decrypted);
|
||||||
this.secureSetupResolve();
|
this.secureSetupResolve();
|
||||||
|
|
|
@ -415,7 +415,7 @@ export class SettingsComponent implements OnInit {
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
await this.biometricStateService.setBiometricUnlockEnabled(false);
|
await this.biometricStateService.setBiometricUnlockEnabled(false);
|
||||||
await this.stateService.setBiometricFingerprintValidated(false);
|
await this.biometricStateService.setFingerprintValidated(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,6 +126,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
|
||||||
StateServiceAbstraction,
|
StateServiceAbstraction,
|
||||||
AutofillSettingsServiceAbstraction,
|
AutofillSettingsServiceAbstraction,
|
||||||
VaultTimeoutSettingsService,
|
VaultTimeoutSettingsService,
|
||||||
|
BiometricStateService,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
BIOMETRIC_UNLOCK_ENABLED,
|
BIOMETRIC_UNLOCK_ENABLED,
|
||||||
DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT,
|
DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT,
|
||||||
ENCRYPTED_CLIENT_KEY_HALF,
|
ENCRYPTED_CLIENT_KEY_HALF,
|
||||||
|
FINGERPRINT_VALIDATED,
|
||||||
PROMPT_AUTOMATICALLY,
|
PROMPT_AUTOMATICALLY,
|
||||||
PROMPT_CANCELLED,
|
PROMPT_CANCELLED,
|
||||||
REQUIRE_PASSWORD_ON_START,
|
REQUIRE_PASSWORD_ON_START,
|
||||||
|
@ -67,6 +68,19 @@ describe("BiometricStateService", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("fingerprintValidated$", () => {
|
||||||
|
it("emits when the fingerprint validated state changes", async () => {
|
||||||
|
const state = stateProvider.global.getFake(FINGERPRINT_VALIDATED);
|
||||||
|
state.stateSubject.next(undefined);
|
||||||
|
|
||||||
|
expect(await firstValueFrom(sut.fingerprintValidated$)).toBe(false);
|
||||||
|
|
||||||
|
state.stateSubject.next(true);
|
||||||
|
|
||||||
|
expect(await firstValueFrom(sut.fingerprintValidated$)).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("setEncryptedClientKeyHalf", () => {
|
describe("setEncryptedClientKeyHalf", () => {
|
||||||
it("updates encryptedClientKeyHalf$", async () => {
|
it("updates encryptedClientKeyHalf$", async () => {
|
||||||
await sut.setEncryptedClientKeyHalf(encClientKeyHalf);
|
await sut.setEncryptedClientKeyHalf(encClientKeyHalf);
|
||||||
|
@ -207,4 +221,20 @@ describe("BiometricStateService", () => {
|
||||||
expect(await sut.getBiometricUnlockEnabled(userId)).toBe(false);
|
expect(await sut.getBiometricUnlockEnabled(userId)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("setFingerprintValidated", () => {
|
||||||
|
it("updates fingerprintValidated$", async () => {
|
||||||
|
await sut.setFingerprintValidated(true);
|
||||||
|
|
||||||
|
expect(await firstValueFrom(sut.fingerprintValidated$)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates state", async () => {
|
||||||
|
await sut.setFingerprintValidated(true);
|
||||||
|
|
||||||
|
expect(stateProvider.global.getFake(FINGERPRINT_VALIDATED).nextMock).toHaveBeenCalledWith(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Observable, firstValueFrom, map } from "rxjs";
|
||||||
|
|
||||||
import { UserId } from "../../types/guid";
|
import { UserId } from "../../types/guid";
|
||||||
import { EncryptedString, EncString } from "../models/domain/enc-string";
|
import { EncryptedString, EncString } from "../models/domain/enc-string";
|
||||||
import { ActiveUserState, StateProvider } from "../state";
|
import { ActiveUserState, GlobalState, StateProvider } from "../state";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BIOMETRIC_UNLOCK_ENABLED,
|
BIOMETRIC_UNLOCK_ENABLED,
|
||||||
|
@ -11,6 +11,7 @@ import {
|
||||||
DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT,
|
DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT,
|
||||||
PROMPT_AUTOMATICALLY,
|
PROMPT_AUTOMATICALLY,
|
||||||
PROMPT_CANCELLED,
|
PROMPT_CANCELLED,
|
||||||
|
FINGERPRINT_VALIDATED,
|
||||||
} from "./biometric.state";
|
} from "./biometric.state";
|
||||||
|
|
||||||
export abstract class BiometricStateService {
|
export abstract class BiometricStateService {
|
||||||
|
@ -49,6 +50,10 @@ export abstract class BiometricStateService {
|
||||||
* tracks the currently active user
|
* tracks the currently active user
|
||||||
*/
|
*/
|
||||||
promptAutomatically$: Observable<boolean>;
|
promptAutomatically$: Observable<boolean>;
|
||||||
|
/**
|
||||||
|
* Whether or not IPC fingerprint has been validated by the user this session.
|
||||||
|
*/
|
||||||
|
fingerprintValidated$: Observable<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the require password on start state for the currently active user.
|
* Updates the require password on start state for the currently active user.
|
||||||
|
@ -88,6 +93,11 @@ export abstract class BiometricStateService {
|
||||||
* @param prompt Whether or not to prompt for biometrics on application start.
|
* @param prompt Whether or not to prompt for biometrics on application start.
|
||||||
*/
|
*/
|
||||||
abstract setPromptAutomatically(prompt: boolean): Promise<void>;
|
abstract setPromptAutomatically(prompt: boolean): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Updates whether or not IPC has been validated by the user this session
|
||||||
|
* @param validated the value to save
|
||||||
|
*/
|
||||||
|
abstract setFingerprintValidated(validated: boolean): Promise<void>;
|
||||||
|
|
||||||
abstract logout(userId: UserId): Promise<void>;
|
abstract logout(userId: UserId): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -99,12 +109,14 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||||
private dismissedRequirePasswordOnStartCalloutState: ActiveUserState<boolean>;
|
private dismissedRequirePasswordOnStartCalloutState: ActiveUserState<boolean>;
|
||||||
private promptCancelledState: ActiveUserState<boolean>;
|
private promptCancelledState: ActiveUserState<boolean>;
|
||||||
private promptAutomaticallyState: ActiveUserState<boolean>;
|
private promptAutomaticallyState: ActiveUserState<boolean>;
|
||||||
|
private fingerprintValidatedState: GlobalState<boolean>;
|
||||||
biometricUnlockEnabled$: Observable<boolean>;
|
biometricUnlockEnabled$: Observable<boolean>;
|
||||||
encryptedClientKeyHalf$: Observable<EncString | undefined>;
|
encryptedClientKeyHalf$: Observable<EncString | undefined>;
|
||||||
requirePasswordOnStart$: Observable<boolean>;
|
requirePasswordOnStart$: Observable<boolean>;
|
||||||
dismissedRequirePasswordOnStartCallout$: Observable<boolean>;
|
dismissedRequirePasswordOnStartCallout$: Observable<boolean>;
|
||||||
promptCancelled$: Observable<boolean>;
|
promptCancelled$: Observable<boolean>;
|
||||||
promptAutomatically$: Observable<boolean>;
|
promptAutomatically$: Observable<boolean>;
|
||||||
|
fingerprintValidated$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(private stateProvider: StateProvider) {
|
constructor(private stateProvider: StateProvider) {
|
||||||
this.biometricUnlockEnabledState = this.stateProvider.getActive(BIOMETRIC_UNLOCK_ENABLED);
|
this.biometricUnlockEnabledState = this.stateProvider.getActive(BIOMETRIC_UNLOCK_ENABLED);
|
||||||
|
@ -130,6 +142,9 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||||
this.promptCancelled$ = this.promptCancelledState.state$.pipe(map(Boolean));
|
this.promptCancelled$ = this.promptCancelledState.state$.pipe(map(Boolean));
|
||||||
this.promptAutomaticallyState = this.stateProvider.getActive(PROMPT_AUTOMATICALLY);
|
this.promptAutomaticallyState = this.stateProvider.getActive(PROMPT_AUTOMATICALLY);
|
||||||
this.promptAutomatically$ = this.promptAutomaticallyState.state$.pipe(map(Boolean));
|
this.promptAutomatically$ = this.promptAutomaticallyState.state$.pipe(map(Boolean));
|
||||||
|
|
||||||
|
this.fingerprintValidatedState = this.stateProvider.getGlobal(FINGERPRINT_VALIDATED);
|
||||||
|
this.fingerprintValidated$ = this.fingerprintValidatedState.state$.pipe(map(Boolean));
|
||||||
}
|
}
|
||||||
|
|
||||||
async setBiometricUnlockEnabled(enabled: boolean): Promise<void> {
|
async setBiometricUnlockEnabled(enabled: boolean): Promise<void> {
|
||||||
|
@ -207,6 +222,10 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||||
async setPromptAutomatically(prompt: boolean): Promise<void> {
|
async setPromptAutomatically(prompt: boolean): Promise<void> {
|
||||||
await this.promptAutomaticallyState.update(() => prompt);
|
await this.promptAutomaticallyState.update(() => prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setFingerprintValidated(validated: boolean): Promise<void> {
|
||||||
|
await this.fingerprintValidatedState.update(() => validated);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptedClientKeyHalfToEncString(
|
function encryptedClientKeyHalfToEncString(
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
BIOMETRIC_UNLOCK_ENABLED,
|
BIOMETRIC_UNLOCK_ENABLED,
|
||||||
DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT,
|
DISMISSED_REQUIRE_PASSWORD_ON_START_CALLOUT,
|
||||||
ENCRYPTED_CLIENT_KEY_HALF,
|
ENCRYPTED_CLIENT_KEY_HALF,
|
||||||
|
FINGERPRINT_VALIDATED,
|
||||||
PROMPT_AUTOMATICALLY,
|
PROMPT_AUTOMATICALLY,
|
||||||
PROMPT_CANCELLED,
|
PROMPT_CANCELLED,
|
||||||
REQUIRE_PASSWORD_ON_START,
|
REQUIRE_PASSWORD_ON_START,
|
||||||
|
@ -16,7 +17,8 @@ describe.each([
|
||||||
[PROMPT_CANCELLED, true],
|
[PROMPT_CANCELLED, true],
|
||||||
[PROMPT_AUTOMATICALLY, true],
|
[PROMPT_AUTOMATICALLY, true],
|
||||||
[REQUIRE_PASSWORD_ON_START, true],
|
[REQUIRE_PASSWORD_ON_START, true],
|
||||||
[BIOMETRIC_UNLOCK_ENABLED, "test"],
|
[BIOMETRIC_UNLOCK_ENABLED, true],
|
||||||
|
[FINGERPRINT_VALIDATED, true],
|
||||||
])(
|
])(
|
||||||
"deserializes state %s",
|
"deserializes state %s",
|
||||||
(
|
(
|
||||||
|
|
|
@ -74,3 +74,14 @@ export const PROMPT_AUTOMATICALLY = new KeyDefinition<boolean>(
|
||||||
deserializer: (obj) => obj,
|
deserializer: (obj) => obj,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores whether or not IPC handshake has been validated this session.
|
||||||
|
*/
|
||||||
|
export const FINGERPRINT_VALIDATED = new KeyDefinition<boolean>(
|
||||||
|
BIOMETRIC_SETTINGS_DISK,
|
||||||
|
"fingerprintValidated",
|
||||||
|
{
|
||||||
|
deserializer: (obj) => obj,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { MessagingService } from "../abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from "../abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../abstractions/platform-utils.service";
|
||||||
import { StateService } from "../abstractions/state.service";
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { SystemService as SystemServiceAbstraction } from "../abstractions/system.service";
|
import { SystemService as SystemServiceAbstraction } from "../abstractions/system.service";
|
||||||
|
import { BiometricStateService } from "../biometrics/biometric-state.service";
|
||||||
import { Utils } from "../misc/utils";
|
import { Utils } from "../misc/utils";
|
||||||
|
|
||||||
export class SystemService implements SystemServiceAbstraction {
|
export class SystemService implements SystemServiceAbstraction {
|
||||||
|
@ -23,6 +24,7 @@ export class SystemService implements SystemServiceAbstraction {
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
private autofillSettingsService: AutofillSettingsServiceAbstraction,
|
||||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
|
private biometricStateService: BiometricStateService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async startProcessReload(authService: AuthService): Promise<void> {
|
async startProcessReload(authService: AuthService): Promise<void> {
|
||||||
|
@ -54,8 +56,9 @@ export class SystemService implements SystemServiceAbstraction {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async executeProcessReload() {
|
private async executeProcessReload() {
|
||||||
const biometricLockedFingerprintValidated =
|
const biometricLockedFingerprintValidated = await firstValueFrom(
|
||||||
await this.stateService.getBiometricFingerprintValidated();
|
this.biometricStateService.fingerprintValidated$,
|
||||||
|
);
|
||||||
if (!biometricLockedFingerprintValidated) {
|
if (!biometricLockedFingerprintValidated) {
|
||||||
clearInterval(this.reloadInterval);
|
clearInterval(this.reloadInterval);
|
||||||
this.reloadInterval = null;
|
this.reloadInterval = null;
|
||||||
|
|
Loading…
Reference in New Issue