1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-23 16:13:21 +00:00

Merge branch 'main' into ps/extension-refresh

This commit is contained in:
Victoria League
2024-10-31 09:37:34 -04:00
committed by GitHub
68 changed files with 720 additions and 263 deletions

View File

@@ -1,7 +1,11 @@
import { matches, mock } from "jest-mock-extended";
import { BehaviorSubject, ReplaySubject, firstValueFrom, of, timeout } from "rxjs";
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import {
Account,
AccountInfo,
AccountService,
} from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
@@ -14,7 +18,7 @@ import { AccountSwitcherService } from "./account-switcher.service";
describe("AccountSwitcherService", () => {
let accountsSubject: BehaviorSubject<Record<UserId, AccountInfo>>;
let activeAccountSubject: BehaviorSubject<{ id: UserId } & AccountInfo>;
let activeAccountSubject: BehaviorSubject<Account | null>;
let authStatusSubject: ReplaySubject<Record<UserId, AuthenticationStatus>>;
const accountService = mock<AccountService>();
@@ -29,7 +33,7 @@ describe("AccountSwitcherService", () => {
beforeEach(() => {
jest.resetAllMocks();
accountsSubject = new BehaviorSubject<Record<UserId, AccountInfo>>(null);
activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>(null);
activeAccountSubject = new BehaviorSubject<Account | null>(null);
authStatusSubject = new ReplaySubject<Record<UserId, AuthenticationStatus>>(1);
// Use subject to allow for easy updates

View File

@@ -940,13 +940,16 @@ export default class AutofillService implements AutofillServiceInterface {
if (options.allowTotpAutofill) {
await Promise.all(
totps.map(async (t) => {
totps.map(async (t, i) => {
if (Object.prototype.hasOwnProperty.call(filledFields, t.opid)) {
return;
}
filledFields[t.opid] = t;
const totpValue = await this.totpService.getCode(login.totp);
let totpValue = await this.totpService.getCode(login.totp);
if (totpValue.length == totps.length) {
totpValue = totpValue.charAt(i);
}
AutofillService.fillByOpid(fillScript, t, totpValue);
}),
);

View File

@@ -1347,14 +1347,17 @@ export default class MainBackground {
if (flagEnabled("sdk")) {
// Warn if the SDK for some reason can't be initialized
let supported = false;
let error: Error;
try {
supported = await firstValueFrom(this.sdkService.supported$);
} catch (e) {
// Do nothing.
error = e;
}
if (!supported) {
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
this.sdkService
.failedToInitialize("background", error)
.catch((e) => this.logService.error(e));
}
}

View File

@@ -1,7 +1,7 @@
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, inject } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap, catchError, of } from "rxjs";
import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap } from "rxjs";
import { LogoutReason } from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -71,21 +71,24 @@ export class AppComponent implements OnInit, OnDestroy {
) {
if (flagEnabled("sdk")) {
// Warn if the SDK for some reason can't be initialized
this.sdkService.supported$
.pipe(
takeUntilDestroyed(),
catchError(() => {
return of(false);
}),
)
.subscribe((supported) => {
this.sdkService.supported$.pipe(takeUntilDestroyed()).subscribe({
next: (supported) => {
if (!supported) {
this.logService.debug("SDK is not supported");
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
this.sdkService
.failedToInitialize("popup", undefined)
.catch((e) => this.logService.error(e));
} else {
this.logService.debug("SDK is supported");
}
});
},
error: (e: unknown) => {
this.sdkService
.failedToInitialize("popup", e as Error)
.catch((e) => this.logService.error(e));
this.logService.error(e);
},
});
}
}

View File

@@ -872,7 +872,7 @@ export class ServiceContainer {
}
if (!supported) {
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
this.sdkService.failedToInitialize("cli").catch((e) => this.logService.error(e));
}
}
}

View File

@@ -180,7 +180,7 @@ export class AppComponent implements OnInit, OnDestroy {
.subscribe((supported) => {
if (!supported) {
this.logService.debug("SDK is not supported");
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
this.sdkService.failedToInitialize("desktop").catch((e) => this.logService.error(e));
} else {
this.logService.debug("SDK is supported");
}

View File

@@ -104,7 +104,7 @@ export class AppComponent implements OnDestroy, OnInit {
.subscribe((supported) => {
if (!supported) {
this.logService.debug("SDK is not supported");
this.sdkService.failedToInitialize().catch((e) => this.logService.error(e));
this.sdkService.failedToInitialize("web").catch((e) => this.logService.error(e));
} else {
this.logService.debug("SDK is supported");
}

View File

@@ -1,9 +1,15 @@
<h1 bitTypography="h1" class="tw-mt-16 tw-pb-2.5 !tw-text-danger">{{ "dangerZone" | i18n }}</h1>
<div class="tw-rounded tw-border tw-border-solid tw-border-danger-600 tw-p-5">
<p>{{ "dangerZoneDesc" | i18n }}</p>
<p>
{{
(accountDeprovisioningEnabled$ | async) && content.children.length === 1
? ("dangerZoneDescSingular" | i18n)
: ("dangerZoneDesc" | i18n)
}}
</p>
<div class="tw-flex tw-flex-row tw-gap-2">
<div #content class="tw-flex tw-flex-row tw-gap-2">
<ng-content></ng-content>
</div>
</div>

View File

@@ -1,6 +1,10 @@
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { TypographyModule } from "@bitwarden/components";
/**
@@ -10,6 +14,15 @@ import { TypographyModule } from "@bitwarden/components";
selector: "app-danger-zone",
templateUrl: "danger-zone.component.html",
standalone: true,
imports: [TypographyModule, JslibModule],
imports: [TypographyModule, JslibModule, CommonModule],
})
export class DangerZoneComponent {}
export class DangerZoneComponent implements OnInit {
constructor(private configService: ConfigService) {}
accountDeprovisioningEnabled$: Observable<boolean>;
ngOnInit(): void {
this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.AccountDeprovisioning,
);
}
}

View File

@@ -36,6 +36,12 @@
Customize
</button>
</div>
<div *ngIf="managingOrganization$ | async as managingOrganization">
{{ "accountIsManagedMessage" | i18n: managingOrganization?.name }}
<a href="https://bitwarden.com/help/claimed-accounts">
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<app-account-fingerprint
[fingerprintMaterial]="fingerprintMaterial"
fingerprintLabel="{{ 'yourAccountsFingerprint' | i18n }}"

View File

@@ -1,11 +1,15 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { firstValueFrom, map, Subject, takeUntil } from "rxjs";
import { firstValueFrom, map, Observable, of, Subject, switchMap, takeUntil } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DialogService, ToastService } from "@bitwarden/components";
@@ -19,6 +23,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
loading = true;
profile: ProfileResponse;
fingerprintMaterial: string;
managingOrganization$: Observable<Organization>;
private destroy$ = new Subject<void>();
protected formGroup = new FormGroup({
@@ -32,6 +37,8 @@ export class ProfileComponent implements OnInit, OnDestroy {
private accountService: AccountService,
private dialogService: DialogService,
private toastService: ToastService,
private configService: ConfigService,
private organizationService: OrganizationService,
) {}
async ngOnInit() {
@@ -40,6 +47,19 @@ export class ProfileComponent implements OnInit, OnDestroy {
this.fingerprintMaterial = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
this.managingOrganization$ = this.configService
.getFeatureFlag$(FeatureFlag.AccountDeprovisioning)
.pipe(
switchMap((isAccountDeprovisioningEnabled) =>
isAccountDeprovisioningEnabled
? this.organizationService.organizations$.pipe(
map((organizations) =>
organizations.find((o) => o.userIsManagedByOrganization === true),
),
)
: of(null),
),
);
this.formGroup.get("name").setValue(this.profile.name);
this.formGroup.get("email").setValue(this.profile.email);

View File

@@ -1,7 +1,7 @@
import { Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { AccountInfo } from "@bitwarden/common/auth/abstractions/account.service";
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
@@ -46,10 +46,7 @@ export class UserKeyRotationService {
* Creates a new user key and re-encrypts all required data with the it.
* @param masterPassword current master password (used for validation)
*/
async rotateUserKeyAndEncryptedData(
masterPassword: string,
user: { id: UserId } & AccountInfo,
): Promise<void> {
async rotateUserKeyAndEncryptedData(masterPassword: string, user: Account): Promise<void> {
this.logService.info("[Userkey rotation] Starting user key rotation...");
if (!masterPassword) {
this.logService.info("[Userkey rotation] Invalid master password provided. Aborting!");

View File

@@ -22,6 +22,9 @@
<bit-tab label="Raw Data + members">
<tools-password-health-members></tools-password-health-members>
</bit-tab>
<bit-tab label="Raw Data + uri">
<tools-password-health-members-uri></tools-password-health-members-uri>
</bit-tab>
<!-- <bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
<h2 bitTypography="h2">{{ "allApplications" | i18n }}</h2>
<tools-application-table></tools-application-table>

View File

@@ -11,6 +11,7 @@ import { HeaderModule } from "../../layouts/header/header.module";
import { ApplicationTableComponent } from "./application-table.component";
import { NotifiedMembersTableComponent } from "./notified-members-table.component";
import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component";
import { PasswordHealthMembersComponent } from "./password-health-members.component";
import { PasswordHealthComponent } from "./password-health.component";
@@ -32,6 +33,7 @@ export enum AccessIntelligenceTabType {
HeaderModule,
PasswordHealthComponent,
PasswordHealthMembersComponent,
PasswordHealthMembersURIComponent,
NotifiedMembersTableComponent,
TabsModule,
],

View File

@@ -11,5 +11,5 @@ import { ButtonModule, NoItemsModule, Icons } from "@bitwarden/components";
imports: [ButtonModule, CommonModule, JslibModule, NoItemsModule],
})
export class NoPriorityAppsComponent {
noItemsIcon = Icons.NoResults;
noItemsIcon = Icons.Security;
}

View File

@@ -0,0 +1,55 @@
<bit-container>
<p>{{ "passwordsReportDesc" | i18n }}</p>
<div *ngIf="loading">
<i
class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="tw-mt-4" *ngIf="!loading">
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr bitRow>
<th bitCell bitSortable="hostURI">{{ "application" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "totalMembers" | i18n }}</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>
<ng-container>
<span>{{ r.hostURI }}</span>
</ng-container>
</td>
<td bitCell class="tw-text-right">
<span
bitBadge
*ngIf="passwordStrengthMap.has(r.id)"
[variant]="passwordStrengthMap.get(r.id)[1]"
>
{{ passwordStrengthMap.get(r.id)[0] | i18n }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="passwordUseMap.has(r.login.password)" variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="exposedPasswordMap.has(r.id)" variant="warning">
{{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }}
</span>
</td>
<td bitCell class="tw-text-right" data-testid="total-membership">
{{ totalMembersMap.get(r.id) || 0 }}
</td>
</tr>
</ng-template>
</bit-table>
</div>
</bit-container>

View File

@@ -0,0 +1,61 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ActivatedRoute, convertToParamMap } from "@angular/router";
import { mock, MockProxy } from "jest-mock-extended";
import { of } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/access-intelligence";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { TableModule } from "@bitwarden/components";
import { LooseComponentsModule } from "../../shared";
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component";
describe("PasswordHealthMembersUriComponent", () => {
let component: PasswordHealthMembersURIComponent;
let fixture: ComponentFixture<PasswordHealthMembersURIComponent>;
let cipherServiceMock: MockProxy<CipherService>;
const passwordHealthServiceMock = mock<PasswordHealthService>();
const activeRouteParams = convertToParamMap({ organizationId: "orgId" });
beforeEach(async () => {
cipherServiceMock = mock<CipherService>();
await TestBed.configureTestingModule({
imports: [PasswordHealthMembersURIComponent, PipesModule, TableModule, LooseComponentsModule],
providers: [
{ provide: CipherService, useValue: cipherServiceMock },
{ provide: I18nService, useValue: mock<I18nService>() },
{ provide: AuditService, useValue: mock<AuditService>() },
{ provide: OrganizationService, useValue: mock<OrganizationService>() },
{
provide: PasswordStrengthServiceAbstraction,
useValue: mock<PasswordStrengthServiceAbstraction>(),
},
{ provide: PasswordHealthService, useValue: passwordHealthServiceMock },
{
provide: ActivatedRoute,
useValue: {
paramMap: of(activeRouteParams),
url: of([]),
},
},
],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PasswordHealthMembersURIComponent);
component = fixture.componentInstance;
});
it("should initialize component", () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,108 @@
import { CommonModule } from "@angular/common";
import { Component, DestroyRef, inject, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from "@angular/router";
import { map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
// eslint-disable-next-line no-restricted-imports
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/access-intelligence";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
BadgeModule,
BadgeVariant,
ContainerComponent,
TableDataSource,
TableModule,
} from "@bitwarden/components";
// eslint-disable-next-line no-restricted-imports
import { HeaderModule } from "../../layouts/header/header.module";
// eslint-disable-next-line no-restricted-imports
import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module";
// eslint-disable-next-line no-restricted-imports
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
@Component({
standalone: true,
selector: "tools-password-health-members-uri",
templateUrl: "password-health-members-uri.component.html",
imports: [
BadgeModule,
OrganizationBadgeModule,
CommonModule,
ContainerComponent,
PipesModule,
JslibModule,
HeaderModule,
TableModule,
],
providers: [PasswordHealthService],
})
export class PasswordHealthMembersURIComponent implements OnInit {
passwordStrengthMap = new Map<string, [string, BadgeVariant]>();
weakPasswordCiphers: CipherView[] = [];
passwordUseMap = new Map<string, number>();
exposedPasswordMap = new Map<string, number>();
totalMembersMap = new Map<string, number>();
dataSource = new TableDataSource<CipherView>();
reportCiphers: (CipherView & { hostURI: string })[] = [];
reportCipherURIs: string[] = [];
organization: Organization;
loading = true;
private destroyRef = inject(DestroyRef);
constructor(
protected cipherService: CipherService,
protected passwordStrengthService: PasswordStrengthServiceAbstraction,
protected organizationService: OrganizationService,
protected auditService: AuditService,
protected i18nService: I18nService,
protected activatedRoute: ActivatedRoute,
) {}
ngOnInit() {
this.activatedRoute.paramMap
.pipe(
takeUntilDestroyed(this.destroyRef),
map(async (params) => {
const organizationId = params.get("organizationId");
await this.setCiphers(organizationId);
}),
)
.subscribe();
}
async setCiphers(organizationId: string) {
const passwordHealthService = new PasswordHealthService(
this.passwordStrengthService,
this.auditService,
this.cipherService,
organizationId,
);
await passwordHealthService.generateReport();
this.dataSource.data = passwordHealthService.groupCiphersByLoginUri();
this.exposedPasswordMap = passwordHealthService.exposedPasswordMap;
this.passwordStrengthMap = passwordHealthService.passwordStrengthMap;
this.passwordUseMap = passwordHealthService.passwordUseMap;
this.totalMembersMap = passwordHealthService.totalMembersMap;
this.loading = false;
}
}

View File

@@ -1,15 +1,38 @@
<bit-container>
<p>{{ "passwordsReportDesc" | i18n }}</p>
<div *ngIf="loading">
<i
class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
<p>{{ "passwordsReportDesc" | i18n }}</p>
<div *ngIf="loading">
<i
class="bwi bwi-spinner bwi-spin tw-text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="tw-mt-4" *ngIf="!dataSource.data.length">
<tools-no-priority-apps></tools-no-priority-apps>
</div>
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
<div class="tw-flex tw-gap-6">
<tools-card
class="tw-flex-1"
[title]="'atRiskMembers' | i18n"
[value]="totalMembersMap.size - 3"
[maxValue]="totalMembersMap.size"
>
</tools-card>
<tools-card
class="tw-flex-1"
[title]="'atRiskApplications' | i18n"
[value]="totalMembersMap.size - 1"
[maxValue]="totalMembersMap.size"
>
</tools-card>
</div>
<div *ngIf="!dataSource.data.length">
<tools-no-priority-apps></tools-no-priority-apps>
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
<bit-search class="tw-grow" [formControl]="searchControl"></bit-search>
<button class="tw-rounded-lg" type="button" buttonType="secondary" bitButton>
<i class="bwi bwi-star-f tw-mr-2"></i>
{{ "markAppAsCritical" | i18n }}
</button>
</div>
<div class="tw-mt-4 tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
<div class="tw-flex tw-gap-6">
@@ -30,7 +53,15 @@
</div>
<div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4">
<bit-search class="tw-grow" [formControl]="searchControl"></bit-search>
<button class="tw-rounded-lg" type="button" buttonType="secondary" bitButton>
<button
class="tw-rounded-lg"
type="button"
buttonType="secondary"
[disabled]="!selectedIds.size"
bitButton
[bitAction]="markAppsAsCritical"
appA11yTitle="{{ 'markAppAsCritical' | i18n }}"
>
<i class="bwi bwi-star-f tw-mr-2"></i>
{{ "markAppAsCritical" | i18n }}
</button>
@@ -47,9 +78,14 @@
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<tr bitRow *ngFor="let r of rows$ | async; trackBy: trackByFunction">
<td bitCell>
<app-vault-icon [cipher]="r"></app-vault-icon>
<input
bitCheckbox
type="checkbox"
[checked]="selectedIds.has(r.id)"
(change)="onCheckboxChange(r.id, $event)"
/>
</td>
<td bitCell>
<ng-container>
@@ -84,4 +120,4 @@
</ng-template>
</bit-table>
</div>
</bit-container>
</div>

View File

@@ -11,7 +11,13 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { BadgeVariant, SearchModule, TableDataSource, TableModule } from "@bitwarden/components";
import {
BadgeVariant,
SearchModule,
TableDataSource,
TableModule,
ToastService,
} from "@bitwarden/components";
import { CardComponent } from "@bitwarden/tools-card";
import { HeaderModule } from "../../layouts/header/header.module";
@@ -53,6 +59,8 @@ export class PasswordHealthMembersComponent implements OnInit {
loading = true;
selectedIds: Set<number> = new Set<number>();
protected searchControl = new FormControl("", { nonNullable: true });
private destroyRef = inject(DestroyRef);
@@ -63,6 +71,7 @@ export class PasswordHealthMembersComponent implements OnInit {
protected auditService: AuditService,
protected i18nService: I18nService,
protected activatedRoute: ActivatedRoute,
protected toastService: ToastService,
) {
this.searchControl.valueChanges
.pipe(debounceTime(200), takeUntilDestroyed())
@@ -91,7 +100,7 @@ export class PasswordHealthMembersComponent implements OnInit {
await passwordHealthService.generateReport();
this.dataSource.data = passwordHealthService.reportCiphers;
this.dataSource.data = []; //passwordHealthService.reportCiphers;
this.exposedPasswordMap = passwordHealthService.exposedPasswordMap;
this.passwordStrengthMap = passwordHealthService.passwordStrengthMap;
@@ -99,4 +108,32 @@ export class PasswordHealthMembersComponent implements OnInit {
this.totalMembersMap = passwordHealthService.totalMembersMap;
this.loading = false;
}
markAppsAsCritical = async () => {
// TODO: Send to API once implemented
return new Promise((resolve) => {
setTimeout(() => {
this.selectedIds.clear();
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("appsMarkedAsCritical"),
});
resolve(true);
}, 1000);
});
};
trackByFunction(_: number, item: CipherView) {
return item.id;
}
onCheckboxChange(id: number, event: Event) {
const isChecked = (event.target as HTMLInputElement).checked;
if (isChecked) {
this.selectedIds.add(id);
} else {
this.selectedIds.delete(id);
}
}
}

View File

@@ -65,6 +65,9 @@
"markAppAsCritical": {
"message": "Mark app as critical"
},
"appsMarkedAsCritical": {
"message": "Apps marked as critical"
},
"application": {
"message": "Application"
},
@@ -1713,6 +1716,9 @@
"dangerZoneDesc": {
"message": "Careful, these actions are not reversible!"
},
"dangerZoneDescSingular": {
"message": "Careful, this action is not reversible!"
},
"deauthorizeSessions": {
"message": "Deauthorize sessions"
},
@@ -1725,6 +1731,15 @@
"sessionsDeauthorized": {
"message": "All sessions deauthorized"
},
"accountIsManagedMessage": {
"message": "This account is managed by $ORGANIZATIONNAME$",
"placeholders": {
"organizationName": {
"content": "$1",
"example": "Organization"
}
}
},
"purgeVault": {
"message": "Purge vault"
},