1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-22 02:21:34 +01:00

[PM-11442] Emergency Cipher Viewing (#12054)

* force viewOnly to be true for emergency access

* add input to hide password history, applicable when the view is used from emergency view

* add extension refresh version of the emergency view dialog

* allow emergency access to view password history

- `ViewPasswordHistoryService` accepts a cipher id or CipherView. When a CipherView is included, the history component no longer has to fetch the cipher.

* remove unused comments

* Add fixme comment for removing non-extension refresh code

* refactor password history component to accept a full cipher view

- Remove the option to pass in only an id
This commit is contained in:
Nick Krantz 2024-12-19 09:42:37 -06:00 committed by GitHub
parent 1d04a0a399
commit 0f3803ac91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 337 additions and 83 deletions

View File

@ -1,8 +1,8 @@
<popup-page>
<popup-page [loading]="!cipher">
<popup-header slot="header" pageTitle="{{ 'passwordHistory' | i18n }}" showBackButton>
<ng-container slot="end">
<app-pop-out></app-pop-out>
</ng-container>
</popup-header>
<vault-password-history-view *ngIf="cipherId" [cipherId]="cipherId" />
<vault-password-history-view *ngIf="cipher" [cipher]="cipher" />
</popup-page>

View File

@ -1,27 +1,40 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
import { ActivatedRoute } from "@angular/router";
import { mock } from "jest-mock-extended";
import { Subject } from "rxjs";
import { BehaviorSubject, Subject } from "rxjs";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service";
import { PasswordHistoryV2Component } from "./vault-password-history-v2.component";
describe("PasswordHistoryV2Component", () => {
let component: PasswordHistoryV2Component;
let fixture: ComponentFixture<PasswordHistoryV2Component>;
const params$ = new Subject();
const mockCipherView = {
id: "111-222-333",
name: "cipher one",
} as CipherView;
const mockCipher = {
decrypt: jest.fn().mockResolvedValue(mockCipherView),
} as unknown as Cipher;
const back = jest.fn().mockResolvedValue(undefined);
const getCipher = jest.fn().mockResolvedValue(mockCipher);
beforeEach(async () => {
back.mockClear();
getCipher.mockClear();
await TestBed.configureTestingModule({
imports: [PasswordHistoryV2Component],
@ -29,8 +42,13 @@ describe("PasswordHistoryV2Component", () => {
{ provide: WINDOW, useValue: window },
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
{ provide: ConfigService, useValue: mock<ConfigService>() },
{ provide: CipherService, useValue: mock<CipherService>() },
{ provide: AccountService, useValue: mock<AccountService>() },
{ provide: CipherService, useValue: mock<CipherService>({ get: getCipher }) },
{
provide: AccountService,
useValue: mock<AccountService>({
activeAccount$: new BehaviorSubject({ id: "acct-1" } as Account),
}),
},
{ provide: PopupRouterCacheService, useValue: { back } },
{ provide: ActivatedRoute, useValue: { queryParams: params$ } },
{ provide: I18nService, useValue: { t: (key: string) => key } },
@ -38,19 +56,21 @@ describe("PasswordHistoryV2Component", () => {
}).compileComponents();
fixture = TestBed.createComponent(PasswordHistoryV2Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("sets the cipherId from the params", () => {
params$.next({ cipherId: "444-33-33-1111" });
it("loads the cipher from params the cipherId from the params", fakeAsync(() => {
params$.next({ cipherId: mockCipherView.id });
expect(component["cipherId"]).toBe("444-33-33-1111");
});
tick(100);
expect(getCipher).toHaveBeenCalledWith(mockCipherView.id);
}));
it("navigates back when a cipherId is not in the params", () => {
params$.next({});
expect(back).toHaveBeenCalledTimes(1);
expect(getCipher).not.toHaveBeenCalled();
});
});

View File

@ -3,10 +3,14 @@
import { NgIf } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { first } from "rxjs/operators";
import { firstValueFrom } from "rxjs";
import { first, map } from "rxjs/operators";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { CipherId } from "@bitwarden/common/types/guid";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { PasswordHistoryViewComponent } from "../../../../../../../../libs/vault/src/components/password-history-view/password-history-view.component";
import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component";
@ -28,18 +32,20 @@ import { PopupRouterCacheService } from "../../../../../platform/popup/view-cach
],
})
export class PasswordHistoryV2Component implements OnInit {
protected cipherId: CipherId;
protected cipher: CipherView;
constructor(
private browserRouterHistory: PopupRouterCacheService,
private route: ActivatedRoute,
private cipherService: CipherService,
private accountService: AccountService,
) {}
ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.route.queryParams.pipe(first()).subscribe((params) => {
if (params.cipherId) {
this.cipherId = params.cipherId;
void this.loadCipher(params.cipherId);
} else {
this.close();
}
@ -49,4 +55,22 @@ export class PasswordHistoryV2Component implements OnInit {
close() {
void this.browserRouterHistory.back();
}
/** Load the cipher based on the given Id */
private async loadCipher(cipherId: string) {
const cipher = await this.cipherService.get(cipherId);
const activeAccount = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)),
);
if (!activeAccount?.id) {
throw new Error("Active account is not available.");
}
const activeUserId = activeAccount.id as UserId;
this.cipher = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
}
}

View File

@ -2,6 +2,8 @@ import { TestBed } from "@angular/core/testing";
import { Router } from "@angular/router";
import { mock, MockProxy } from "jest-mock-extended";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { BrowserViewPasswordHistoryService } from "./browser-view-password-history.service";
describe("BrowserViewPasswordHistoryService", () => {
@ -19,9 +21,9 @@ describe("BrowserViewPasswordHistoryService", () => {
describe("viewPasswordHistory", () => {
it("navigates to the password history screen", async () => {
await service.viewPasswordHistory("test");
await service.viewPasswordHistory({ id: "cipher-id" } as CipherView);
expect(router.navigate).toHaveBeenCalledWith(["/cipher-password-history"], {
queryParams: { cipherId: "test" },
queryParams: { cipherId: "cipher-id" },
});
});
});

View File

@ -4,6 +4,7 @@ import { inject } from "@angular/core";
import { Router } from "@angular/router";
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
/**
* This class handles the premium upgrade process for the browser extension.
@ -14,7 +15,9 @@ export class BrowserViewPasswordHistoryService implements ViewPasswordHistorySer
/**
* Navigates to the password history screen.
*/
async viewPasswordHistory(cipherId: string) {
await this.router.navigate(["/cipher-password-history"], { queryParams: { cipherId } });
async viewPasswordHistory(cipher: CipherView) {
await this.router.navigate(["/cipher-password-history"], {
queryParams: { cipherId: cipher.id },
});
}
}

View File

@ -4,16 +4,22 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { DialogService } from "@bitwarden/components";
import { CipherFormConfigService, DefaultCipherFormConfigService } from "@bitwarden/vault";
import { EmergencyAccessService } from "../../../emergency-access";
import { EmergencyAccessAttachmentsComponent } from "../attachments/emergency-access-attachments.component";
import { EmergencyAddEditCipherComponent } from "./emergency-add-edit-cipher.component";
import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component";
@Component({
selector: "emergency-access-view",
templateUrl: "emergency-access-view.component.html",
providers: [{ provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService }],
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class EmergencyAccessViewComponent implements OnInit {
@ -31,6 +37,8 @@ export class EmergencyAccessViewComponent implements OnInit {
private router: Router,
private route: ActivatedRoute,
private emergencyAccessService: EmergencyAccessService,
private configService: ConfigService,
private dialogService: DialogService,
) {}
ngOnInit() {
@ -49,6 +57,19 @@ export class EmergencyAccessViewComponent implements OnInit {
}
async selectCipher(cipher: CipherView) {
const browserRefreshEnabled = await this.configService.getFeatureFlag(
FeatureFlag.ExtensionRefresh,
);
if (browserRefreshEnabled) {
EmergencyViewDialogComponent.open(this.dialogService, {
cipher,
});
return;
}
// FIXME PM-15385: Remove below dialog service logic once extension refresh is live.
// eslint-disable-next-line
const [_, childComponent] = await this.modalService.openViewRef(
EmergencyAddEditCipherComponent,

View File

@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { DatePipe } from "@angular/common";
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { CollectionService } from "@bitwarden/admin-console/common";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
@ -30,7 +30,7 @@ import { AddEditComponent as BaseAddEditComponent } from "../../../../vault/indi
selector: "app-org-vault-add-edit",
templateUrl: "../../../../vault/individual-vault/add-edit.component.html",
})
export class EmergencyAddEditCipherComponent extends BaseAddEditComponent {
export class EmergencyAddEditCipherComponent extends BaseAddEditComponent implements OnInit {
originalCipher: Cipher = null;
viewOnly = true;
protected override componentName = "app-org-vault-add-edit";
@ -85,6 +85,14 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent {
this.title = this.i18nService.t("viewItem");
}
async ngOnInit(): Promise<void> {
await super.ngOnInit();
// The base component `ngOnInit` calculates the `viewOnly` property based on cipher properties
// In the case of emergency access, `viewOnly` should always be true, set it manually here after
// the base `ngOnInit` is complete.
this.viewOnly = true;
}
protected async loadCipher() {
return Promise.resolve(this.originalCipher);
}

View File

@ -0,0 +1,13 @@
<bit-dialog dialogSize="large" background="alt" #dialog>
<span bitDialogTitle aria-live="polite">
{{ title }}
</span>
<div bitDialogContent #dialogContent>
<app-cipher-view [cipher]="cipher"></app-cipher-view>
</div>
<ng-container bitDialogFooter>
<button bitButton type="button" buttonType="secondary" (click)="cancel()">
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>

View File

@ -0,0 +1,108 @@
import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { mock } from "jest-mock-extended";
import { CollectionService } from "@bitwarden/admin-console/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { DialogService } from "@bitwarden/components";
import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component";
describe("EmergencyViewDialogComponent", () => {
let component: EmergencyViewDialogComponent;
let fixture: ComponentFixture<EmergencyViewDialogComponent>;
const open = jest.fn();
const close = jest.fn();
const mockCipher = {
id: "cipher1",
name: "Cipher",
type: CipherType.Login,
login: { uris: [] },
card: {},
} as CipherView;
beforeEach(async () => {
open.mockClear();
close.mockClear();
await TestBed.configureTestingModule({
imports: [EmergencyViewDialogComponent, NoopAnimationsModule],
providers: [
{ provide: OrganizationService, useValue: mock<OrganizationService>() },
{ provide: CollectionService, useValue: mock<CollectionService>() },
{ provide: FolderService, useValue: mock<FolderService>() },
{ provide: I18nService, useValue: { t: (...keys: string[]) => keys.join(" ") } },
{ provide: DialogService, useValue: { open } },
{ provide: DialogRef, useValue: { close } },
{ provide: DIALOG_DATA, useValue: { cipher: mockCipher } },
],
}).compileComponents();
fixture = TestBed.createComponent(EmergencyViewDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("creates", () => {
expect(component).toBeTruthy();
});
it("opens dialog", () => {
EmergencyViewDialogComponent.open({ open } as unknown as DialogService, { cipher: mockCipher });
expect(open).toHaveBeenCalled();
});
it("closes the dialog", () => {
EmergencyViewDialogComponent.open({ open } as unknown as DialogService, { cipher: mockCipher });
fixture.detectChanges();
const cancelButton = fixture.debugElement.queryAll(By.css("button")).pop();
cancelButton.nativeElement.click();
expect(close).toHaveBeenCalled();
});
describe("updateTitle", () => {
it("sets login title", () => {
mockCipher.type = CipherType.Login;
component["updateTitle"]();
expect(component["title"]).toBe("viewItemType typelogin");
});
it("sets card title", () => {
mockCipher.type = CipherType.Card;
component["updateTitle"]();
expect(component["title"]).toBe("viewItemType typecard");
});
it("sets identity title", () => {
mockCipher.type = CipherType.Identity;
component["updateTitle"]();
expect(component["title"]).toBe("viewItemType typeidentity");
});
it("sets note title", () => {
mockCipher.type = CipherType.SecureNote;
component["updateTitle"]();
expect(component["title"]).toBe("viewItemType note");
});
});
});

View File

@ -0,0 +1,90 @@
import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, Inject } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components";
import { CipherViewComponent } from "@bitwarden/vault";
import { WebViewPasswordHistoryService } from "../../../../vault/services/web-view-password-history.service";
export interface EmergencyViewDialogParams {
/** The cipher being viewed. */
cipher: CipherView;
}
/** Stubbed class, premium upgrade is not applicable for emergency viewing */
class PremiumUpgradePromptNoop implements PremiumUpgradePromptService {
async promptForPremium() {
return Promise.resolve();
}
}
@Component({
selector: "app-emergency-view-dialog",
templateUrl: "emergency-view-dialog.component.html",
standalone: true,
imports: [ButtonModule, CipherViewComponent, DialogModule, CommonModule, JslibModule],
providers: [
{ provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService },
{ provide: PremiumUpgradePromptService, useClass: PremiumUpgradePromptNoop },
],
})
export class EmergencyViewDialogComponent {
/**
* The title of the dialog. Updates based on the cipher type.
* @protected
*/
protected title: string;
constructor(
@Inject(DIALOG_DATA) protected params: EmergencyViewDialogParams,
private dialogRef: DialogRef,
private i18nService: I18nService,
) {
this.updateTitle();
}
get cipher(): CipherView {
return this.params.cipher;
}
cancel = () => {
this.dialogRef.close();
};
private updateTitle() {
const partOne = "viewItemType";
const type = this.cipher.type;
switch (type) {
case CipherType.Login:
this.title = this.i18nService.t(partOne, this.i18nService.t("typeLogin").toLowerCase());
break;
case CipherType.Card:
this.title = this.i18nService.t(partOne, this.i18nService.t("typeCard").toLowerCase());
break;
case CipherType.Identity:
this.title = this.i18nService.t(partOne, this.i18nService.t("typeIdentity").toLowerCase());
break;
case CipherType.SecureNote:
this.title = this.i18nService.t(partOne, this.i18nService.t("note").toLowerCase());
break;
}
}
/**
* Opens the EmergencyViewDialog.
*/
static open(dialogService: DialogService, params: EmergencyViewDialogParams) {
return dialogService.open<EmergencyViewDialogParams>(EmergencyViewDialogComponent, {
data: params,
});
}
}

View File

@ -3,7 +3,7 @@
{{ "passwordHistory" | i18n }}
</span>
<ng-container bitDialogContent>
<vault-password-history-view [cipherId]="cipherId" />
<vault-password-history-view [cipher]="cipher" />
</ng-container>
<ng-container bitDialogFooter>
<button bitButton (click)="close()" buttonType="primary" type="button">

View File

@ -4,8 +4,7 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Inject, Component } from "@angular/core";
import { CipherId } from "@bitwarden/common/types/guid";
import { PasswordHistoryView } from "@bitwarden/common/vault/models/view/password-history.view";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { AsyncActionsModule, DialogModule, DialogService } from "@bitwarden/components";
import { PasswordHistoryViewComponent } from "@bitwarden/vault";
@ -15,7 +14,7 @@ import { SharedModule } from "../../shared/shared.module";
* The parameters for the password history dialog.
*/
export interface ViewPasswordHistoryDialogParams {
cipherId: CipherId;
cipher: CipherView;
}
/**
@ -35,14 +34,9 @@ export interface ViewPasswordHistoryDialogParams {
})
export class PasswordHistoryComponent {
/**
* The ID of the cipher to display the password history for.
* The cipher to display the password history for.
*/
cipherId: CipherId;
/**
* The password history for the cipher.
*/
history: PasswordHistoryView[] = [];
cipher: CipherView;
/**
* The constructor for the password history dialog component.
@ -54,9 +48,9 @@ export class PasswordHistoryComponent {
private dialogRef: DialogRef<PasswordHistoryComponent>,
) {
/**
* Set the cipher ID from the parameters.
* Set the cipher from the parameters.
*/
this.cipherId = params.cipherId;
this.cipher = params.cipher;
}
/**

View File

@ -1,7 +1,7 @@
import { Overlay } from "@angular/cdk/overlay";
import { TestBed } from "@angular/core/testing";
import { CipherId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { DialogService } from "@bitwarden/components";
import { openPasswordHistoryDialog } from "../individual-vault/password-history.component";
@ -35,10 +35,10 @@ describe("WebViewPasswordHistoryService", () => {
describe("viewPasswordHistory", () => {
it("calls openPasswordHistoryDialog with the correct parameters", async () => {
const mockCipherId = "cipher-id" as CipherId;
await service.viewPasswordHistory(mockCipherId);
const mockCipher = { id: "cipher-id" } as CipherView;
await service.viewPasswordHistory(mockCipher);
expect(openPasswordHistoryDialog).toHaveBeenCalledWith(dialogService, {
data: { cipherId: mockCipherId },
data: { cipher: mockCipher },
});
});
});

View File

@ -1,6 +1,6 @@
import { Injectable } from "@angular/core";
import { CipherId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { DialogService } from "@bitwarden/components";
import { ViewPasswordHistoryService } from "../../../../../../libs/common/src/vault/abstractions/view-password-history.service";
@ -17,7 +17,7 @@ export class WebViewPasswordHistoryService implements ViewPasswordHistoryService
* Opens the password history dialog for the given cipher ID.
* @param cipherId The ID of the cipher to view the password history for.
*/
async viewPasswordHistory(cipherId: CipherId) {
openPasswordHistoryDialog(this.dialogService, { data: { cipherId } });
async viewPasswordHistory(cipher: CipherView) {
openPasswordHistoryDialog(this.dialogService, { data: { cipher } });
}
}

View File

@ -1,8 +1,8 @@
import { CipherId } from "../../types/guid";
import { CipherView } from "../models/view/cipher.view";
/**
* The ViewPasswordHistoryService is responsible for displaying the password history for a cipher.
*/
export abstract class ViewPasswordHistoryService {
abstract viewPasswordHistory(cipherId?: CipherId): Promise<void>;
abstract viewPasswordHistory(cipher: CipherView): Promise<void>;
}

View File

@ -5,7 +5,6 @@ import { Component, Input } from "@angular/core";
import { RouterModule } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { CipherId } from "@bitwarden/common/types/guid";
import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@ -45,6 +44,6 @@ export class ItemHistoryV2Component {
* View the password history for the cipher.
*/
async viewPasswordHistory() {
await this.viewPasswordHistoryService.viewPasswordHistory(this.cipher?.id as CipherId);
await this.viewPasswordHistoryService.viewPasswordHistory(this.cipher);
}
}

View File

@ -46,6 +46,7 @@ describe("PasswordHistoryViewComponent", () => {
fixture = TestBed.createComponent(PasswordHistoryViewComponent);
component = fixture.componentInstance;
component.cipher = mockCipher;
fixture.detectChanges();
});
@ -60,8 +61,8 @@ describe("PasswordHistoryViewComponent", () => {
beforeEach(async () => {
mockCipher.passwordHistory = [password1, password2];
mockCipherService.get.mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) });
await component.ngOnInit();
component.cipher = mockCipher;
component.ngOnInit();
fixture.detectChanges();
});

View File

@ -2,13 +2,9 @@
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { OnInit, Component, Input } from "@angular/core";
import { firstValueFrom, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { PasswordHistoryView } from "@bitwarden/common/vault/models/view/password-history.view";
import { ItemModule, ColorPasswordModule, IconButtonModule } from "@bitwarden/components";
@ -20,39 +16,14 @@ import { ItemModule, ColorPasswordModule, IconButtonModule } from "@bitwarden/co
})
export class PasswordHistoryViewComponent implements OnInit {
/**
* The ID of the cipher to display the password history for.
* Optional cipher view. When included `cipherId` is ignored.
*/
@Input({ required: true }) cipherId: CipherId;
@Input({ required: true }) cipher: CipherView;
/** The password history for the cipher. */
history: PasswordHistoryView[] = [];
constructor(
protected cipherService: CipherService,
protected i18nService: I18nService,
protected accountService: AccountService,
) {}
async ngOnInit() {
await this.init();
}
/** Retrieve the password history for the given cipher */
protected async init() {
const cipher = await this.cipherService.get(this.cipherId);
const activeAccount = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)),
);
if (!activeAccount?.id) {
throw new Error("Active account is not available.");
}
const activeUserId = activeAccount.id as UserId;
const decCipher = await cipher.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId),
);
this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory;
ngOnInit() {
this.history = this.cipher.passwordHistory == null ? [] : this.cipher.passwordHistory;
}
}