1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-21 03:43:58 +00:00

created at risk password callout service to hold state for callout data. wip

This commit is contained in:
jng
2025-07-18 12:05:35 -04:00
parent a8c6e3ffe2
commit bbfd0d5f97
4 changed files with 87 additions and 58 deletions

View File

@@ -1,5 +1,5 @@
@if (currentPendingTasks()) {
<bit-callout *ngIf="(pendingTasks$ | async)?.length as taskCount" type="warning" [title]="''">
@if (currentPendingTasks().length > 0) {
<bit-callout *ngIf="currentPendingTasks().length as taskCount" type="warning" [title]="''">
<i class="bwi bwi-exclamation-triangle tw-text-warning" aria-hidden="true"></i>
<a bitLink [routerLink]="'/at-risk-passwords'">
{{

View File

@@ -2,35 +2,17 @@ import { CommonModule } from "@angular/common";
import { Component, computed, inject, effect } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { RouterModule } from "@angular/router";
import { combineLatest, map, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import {
StateProvider,
UserKeyDefinition,
VAULT_AT_RISK_PASSWORDS_DISK,
} from "@bitwarden/common/platform/state";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SecurityTaskType, TaskService } from "@bitwarden/common/vault/tasks";
import { StateProvider } from "@bitwarden/common/platform/state";
import { AnchorLinkDirective, CalloutModule, BannerModule } from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";
import { UserId } from "@bitwarden/user-core";
// Move this state provider code and methods into a new at risk password callout service
// a show/hide boolean for the congrats banner
// a dismissed boolean for the banner dismissal
// a hadPendingTasks boolean to track if the user had pending tasks previously
const AT_RISK_ITEMS = new UserKeyDefinition<boolean>(
VAULT_AT_RISK_PASSWORDS_DISK,
"atRiskPasswords",
{
deserializer: (atRiskItems) => atRiskItems,
clearOn: ["logout", "lock"],
},
);
import {
AtRiskPasswordCalloutData,
AtRiskPasswordCalloutService,
} from "@bitwarden/web-vault/app/vault/services/at-risk-password-callout.service";
@Component({
selector: "vault-at-risk-password-callout",
@@ -43,39 +25,24 @@ const AT_RISK_ITEMS = new UserKeyDefinition<boolean>(
BannerModule,
JslibModule,
],
providers: [AtRiskPasswordCalloutService],
templateUrl: "./at-risk-password-callout.component.html",
})
export class AtRiskPasswordCalloutComponent {
private taskService = inject(TaskService);
private cipherService = inject(CipherService);
private activeAccount$ = inject(AccountService).activeAccount$.pipe(getUserId);
private atRiskPasswordCalloutService = inject(AtRiskPasswordCalloutService);
private userIdSignal = toSignal(this.activeAccount$, { initialValue: null });
protected pendingTasks$ = this.activeAccount$.pipe(
switchMap((userId) =>
combineLatest([
this.taskService.pendingTasks$(userId),
this.cipherService.cipherViews$(userId),
]),
),
map(([tasks, ciphers]) =>
tasks.filter((t) => {
const associatedCipher = ciphers.find((c) => c.id === t.cipherId);
return (
t.type === SecurityTaskType.UpdateAtRiskCredential &&
associatedCipher &&
!associatedCipher.isDeleted
);
}),
),
);
private atRiskPasswordStateSignal = toSignal(
this.atRiskPasswordState(this.userIdSignal()!).state$,
this.atRiskPasswordCalloutService.atRiskPasswordState(this.userIdSignal()!).state$,
);
currentPendingTasks = toSignal(this.pendingTasks$, { initialValue: [] });
currentPendingTasks = toSignal(
this.atRiskPasswordCalloutService.pendingTasks$(this.userIdSignal()!),
{
initialValue: [],
},
);
showTasksResolved = computed(() => {
if (this.atRiskPasswordStateSignal() && this.currentPendingTasks().length === 0) {
@@ -86,16 +53,16 @@ export class AtRiskPasswordCalloutComponent {
constructor(private stateProvider: StateProvider) {
effect(() => {
if (this.currentPendingTasks().length > 0) {
this.updateAtRiskPasswordState(this.userIdSignal()!, true);
const updateObject: AtRiskPasswordCalloutData = {
hadPendingTasks: true,
showTasksCompleteBanner: false,
tasksBannerDismissed: false,
};
this.atRiskPasswordCalloutService.updateAtRiskPasswordState(
this.userIdSignal()!,
updateObject,
);
}
});
}
private atRiskPasswordState(userId: UserId) {
return this.stateProvider.getUser(userId, AT_RISK_ITEMS);
}
private updateAtRiskPasswordState(userId: UserId, hasAtRiskPassword: boolean) {
void this.atRiskPasswordState(userId).update(() => hasAtRiskPassword);
}
}

View File

@@ -0,0 +1,62 @@
import { Injectable } from "@angular/core";
import { combineLatest, map, Observable } from "rxjs";
import {
StateProvider,
UserKeyDefinition,
VAULT_AT_RISK_PASSWORDS_DISK,
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { SecurityTask, SecurityTaskType, TaskService } from "@bitwarden/common/vault/tasks";
export type AtRiskPasswordCalloutData = {
hadPendingTasks: boolean;
showTasksCompleteBanner: boolean;
tasksBannerDismissed: boolean;
};
const AT_RISK_PASSWORD_CALLOUT_KEY = new UserKeyDefinition<AtRiskPasswordCalloutData>(
VAULT_AT_RISK_PASSWORDS_DISK,
"atRiskPasswords",
{
deserializer: (jsonData) => jsonData,
clearOn: ["logout", "lock"],
},
);
@Injectable()
export class AtRiskPasswordCalloutService {
constructor(
private taskService: TaskService,
private cipherService: CipherService,
private stateProvider: StateProvider,
) {}
pendingTasks$(userId: UserId): Observable<SecurityTask[]> {
return combineLatest([
this.taskService.pendingTasks$(userId),
this.cipherService.cipherViews$(userId),
]).pipe(
map(([tasks, ciphers]) =>
tasks.filter((t: SecurityTask) => {
const associatedCipher = ciphers.find((c) => c.id === t.cipherId);
return (
t.type === SecurityTaskType.UpdateAtRiskCredential &&
associatedCipher &&
!associatedCipher.isDeleted
);
}),
),
);
}
atRiskPasswordState(userId: UserId) {
return this.stateProvider.getUser(userId, AT_RISK_PASSWORD_CALLOUT_KEY);
}
updateAtRiskPasswordState(userId: UserId, updatedState: AtRiskPasswordCalloutData): void {
void this.atRiskPasswordState(userId).update(() => updatedState);
}
}