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:
parent
1d04a0a399
commit
0f3803ac91
@ -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>
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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 },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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>
|
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
@ -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">
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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 } });
|
||||
}
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user