diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index e8834b3ffdb..a9d2d75d64c 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5062,6 +5062,9 @@ "unlockPinSet": { "message": "Unlock PIN set" }, + "unlockBiometricSet": { + "message": "Unlock biometrics set" + }, "authenticating": { "message": "Authenticating" }, diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 1fc4650b6f5..19f2d94e451 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -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); diff --git a/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts b/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts index 30bbd153c5e..99d7f3934ff 100644 --- a/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts +++ b/libs/angular/src/vault/services/custom-nudges-services/account-security-nudge.service.ts @@ -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 { 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; + }, + ), ); } } diff --git a/libs/angular/src/vault/services/nudges.service.spec.ts b/libs/angular/src/vault/services/nudges.service.spec.ts index f18d846232c..bf84674c669 100644 --- a/libs/angular/src/vault/services/nudges.service.spec.ts +++ b/libs/angular/src/vault/services/nudges.service.spec.ts @@ -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(), }, + { + provide: BiometricStateService, + useValue: mock(), + }, + { + provide: PolicyService, + useValue: mock(), + }, + { + provide: OrganizationService, + useValue: mock(), + }, ], }); });