1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 05:13:29 +00:00

[PM-22419] dismiss account nudge when biometric unlock is set (#15139)

* update account-security-nudge service to look at biomentricUnlockEnabled$ observable, add success toast for biometric unlock
This commit is contained in:
Jason Ng
2025-06-17 14:47:10 -04:00
committed by GitHub
parent 05b34e9d00
commit 71bc68444d
4 changed files with 69 additions and 10 deletions

View File

@@ -5062,6 +5062,9 @@
"unlockPinSet": {
"message": "Unlock PIN set"
},
"unlockBiometricSet": {
"message": "Unlock biometrics set"
},
"authenticating": {
"message": "Authenticating"
},

View File

@@ -534,6 +534,11 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
if (!successful) {
await this.biometricStateService.setFingerprintValidated(false);
}
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("unlockBiometricSet"),
});
} catch (error) {
this.form.controls.biometric.setValue(false);
this.validationService.showError(error);

View File

@@ -1,14 +1,18 @@
import { Injectable, inject } from "@angular/core";
import { Observable, combineLatest, from, of } from "rxjs";
import { catchError, map } from "rxjs/operators";
import { catchError, switchMap } from "rxjs/operators";
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { UserId } from "@bitwarden/common/types/guid";
import { BiometricStateService } from "@bitwarden/key-management";
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
import { NudgeStatus, NudgeType } from "../nudges.service";
@@ -21,6 +25,9 @@ export class AccountSecurityNudgeService extends DefaultSingleNudgeService {
private logService = inject(LogService);
private pinService = inject(PinServiceAbstraction);
private vaultTimeoutSettingsService = inject(VaultTimeoutSettingsService);
private biometricStateService = inject(BiometricStateService);
private policyService = inject(PolicyService);
private organizationService = inject(OrganizationService);
nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable<NudgeStatus> {
const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe(
@@ -36,16 +43,45 @@ export class AccountSecurityNudgeService extends DefaultSingleNudgeService {
this.getNudgeStatus$(nudgeType, userId),
of(Date.now() - THIRTY_DAYS_MS),
from(this.pinService.isPinSet(userId)),
from(this.vaultTimeoutSettingsService.isBiometricLockSet(userId)),
this.biometricStateService.biometricUnlockEnabled$,
this.organizationService.organizations$(userId),
this.policyService.policiesByType$(PolicyType.RemoveUnlockWithPin, userId),
]).pipe(
map(([profileCreationDate, status, profileCutoff, isPinSet, isBiometricLockSet]) => {
const profileOlderThanCutoff = profileCreationDate.getTime() < profileCutoff;
const hideNudge = profileOlderThanCutoff || isPinSet || isBiometricLockSet;
return {
hasBadgeDismissed: status.hasBadgeDismissed || hideNudge,
hasSpotlightDismissed: status.hasSpotlightDismissed || hideNudge,
};
}),
switchMap(
async ([
profileCreationDate,
status,
profileCutoff,
isPinSet,
biometricUnlockEnabled,
organizations,
policies,
]) => {
const profileOlderThanCutoff = profileCreationDate.getTime() < profileCutoff;
const hasOrgWithRemovePinPolicyOn = organizations.some((org) => {
return policies.some(
(p) => p.type === PolicyType.RemoveUnlockWithPin && p.organizationId === org.id,
);
});
const hideNudge =
profileOlderThanCutoff ||
isPinSet ||
biometricUnlockEnabled ||
hasOrgWithRemovePinPolicyOn;
const acctSecurityNudgeStatus = {
hasBadgeDismissed: status.hasBadgeDismissed || hideNudge,
hasSpotlightDismissed: status.hasSpotlightDismissed || hideNudge,
};
if (isPinSet || biometricUnlockEnabled || hasOrgWithRemovePinPolicyOn) {
await this.setNudgeStatus(nudgeType, acctSecurityNudgeStatus, userId);
}
return acctSecurityNudgeStatus;
},
),
);
}
}

View File

@@ -6,6 +6,8 @@ import { firstValueFrom, of } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@@ -13,6 +15,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { StateProvider } from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { BiometricStateService } from "@bitwarden/key-management";
import { FakeStateProvider, mockAccountServiceWith } from "../../../../../libs/common/spec";
@@ -91,6 +94,18 @@ describe("Vault Nudges Service", () => {
provide: VaultTimeoutSettingsService,
useValue: mock<VaultTimeoutSettingsService>(),
},
{
provide: BiometricStateService,
useValue: mock<BiometricStateService>(),
},
{
provide: PolicyService,
useValue: mock<PolicyService>(),
},
{
provide: OrganizationService,
useValue: mock<OrganizationService>(),
},
],
});
});