mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-22 11:45:59 +01:00
[PM-5054] migrate emergency access to CL (#7850)
Co-authored-by: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com>
This commit is contained in:
parent
551e43031e
commit
c09b446e63
@ -1,52 +1,37 @@
|
|||||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="confirmUserTitle">
|
<form [formGroup]="confirmForm" [bitSubmit]="submit">
|
||||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
<bit-dialog dialogSize="large" [loading]="loading">
|
||||||
<form class="modal-content" #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
<span bitDialogTitle>
|
||||||
<div class="modal-header">
|
{{ "confirmUser" | i18n }}
|
||||||
<h1 class="modal-title" id="confirmUserTitle">
|
<small class="tw-text-muted">{{ params.name }}</small>
|
||||||
{{ "confirmUser" | i18n }}
|
</span>
|
||||||
<small class="text-muted" *ngIf="name">{{ name }}</small>
|
<div bitDialogContent>
|
||||||
</h1>
|
<p bitTypography="body1">
|
||||||
<button
|
{{ "fingerprintEnsureIntegrityVerify" | i18n }}
|
||||||
type="button"
|
<a
|
||||||
class="close"
|
bitLink
|
||||||
data-dismiss="modal"
|
href="https://bitwarden.com/help/fingerprint-phrase/"
|
||||||
appA11yTitle="{{ 'close' | i18n }}"
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">×</span>
|
{{ "learnMore" | i18n }}</a
|
||||||
</button>
|
>
|
||||||
</div>
|
</p>
|
||||||
<div class="modal-body">
|
<p bitTypography="body1">
|
||||||
<p>
|
<code>{{ fingerprint }}</code>
|
||||||
{{ "fingerprintEnsureIntegrityVerify" | i18n }}
|
</p>
|
||||||
<a href="https://bitwarden.com/help/fingerprint-phrase/" target="_blank" rel="noreferrer">
|
|
||||||
{{ "learnMore" | i18n }}</a
|
<bit-form-control>
|
||||||
>
|
<input type="checkbox" bitCheckbox formControlName="dontAskAgain" />
|
||||||
</p>
|
<bit-label> {{ "dontAskFingerprintAgain" | i18n }}</bit-label>
|
||||||
<p>
|
</bit-form-control>
|
||||||
<code>{{ fingerprint }}</code>
|
</div>
|
||||||
</p>
|
<div bitDialogFooter>
|
||||||
<div class="form-check">
|
<button type="submit" buttonType="primary" bitButton bitFormButton>
|
||||||
<input
|
<span>{{ "confirm" | i18n }}</span>
|
||||||
class="form-check-input"
|
</button>
|
||||||
type="checkbox"
|
<button bitButton bitFormButton buttonType="secondary" type="button" bitDialogClose>
|
||||||
id="dontAskAgain"
|
{{ "cancel" | i18n }}
|
||||||
name="DontAskAgain"
|
</button>
|
||||||
[(ngModel)]="dontAskAgain"
|
</div>
|
||||||
/>
|
</bit-dialog>
|
||||||
<label class="form-check-label" for="dontAskAgain">
|
</form>
|
||||||
{{ "dontAskFingerprintAgain" | i18n }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
|
||||||
<span>{{ "confirm" | i18n }}</span>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
|
||||||
{{ "cancel" | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
@ -1,39 +1,52 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||||
|
import { Component, OnInit, Inject } from "@angular/core";
|
||||||
|
import { FormBuilder } from "@angular/forms";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
export enum EmergencyAccessConfirmDialogResult {
|
||||||
|
Confirmed = "confirmed",
|
||||||
|
}
|
||||||
|
type EmergencyAccessConfirmDialogData = {
|
||||||
|
/** display name of the account requesting emergency access */
|
||||||
|
name: string;
|
||||||
|
/** identifies the account requesting emergency access */
|
||||||
|
userId: string;
|
||||||
|
/** traces a unique emergency request */
|
||||||
|
emergencyAccessId: string;
|
||||||
|
};
|
||||||
@Component({
|
@Component({
|
||||||
selector: "emergency-access-confirm",
|
selector: "emergency-access-confirm",
|
||||||
templateUrl: "emergency-access-confirm.component.html",
|
templateUrl: "emergency-access-confirm.component.html",
|
||||||
})
|
})
|
||||||
export class EmergencyAccessConfirmComponent implements OnInit {
|
export class EmergencyAccessConfirmComponent implements OnInit {
|
||||||
@Input() name: string;
|
|
||||||
@Input() userId: string;
|
|
||||||
@Input() emergencyAccessId: string;
|
|
||||||
@Input() formPromise: Promise<any>;
|
|
||||||
@Output() onConfirmed = new EventEmitter();
|
|
||||||
|
|
||||||
dontAskAgain = false;
|
|
||||||
loading = true;
|
loading = true;
|
||||||
fingerprint: string;
|
fingerprint: string;
|
||||||
|
confirmForm = this.formBuilder.group({
|
||||||
|
dontAskAgain: [false],
|
||||||
|
});
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DIALOG_DATA) protected params: EmergencyAccessConfirmDialogData,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
|
private dialogRef: DialogRef<EmergencyAccessConfirmDialogResult>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
try {
|
try {
|
||||||
const publicKeyResponse = await this.apiService.getUserPublicKey(this.userId);
|
const publicKeyResponse = await this.apiService.getUserPublicKey(this.params.userId);
|
||||||
if (publicKeyResponse != null) {
|
if (publicKeyResponse != null) {
|
||||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||||
const fingerprint = await this.cryptoService.getFingerprint(this.userId, publicKey);
|
const fingerprint = await this.cryptoService.getFingerprint(this.params.userId, publicKey);
|
||||||
if (fingerprint != null) {
|
if (fingerprint != null) {
|
||||||
this.fingerprint = fingerprint.join("-");
|
this.fingerprint = fingerprint.join("-");
|
||||||
}
|
}
|
||||||
@ -44,19 +57,33 @@ export class EmergencyAccessConfirmComponent implements OnInit {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
submit = async () => {
|
||||||
if (this.loading) {
|
if (this.loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.dontAskAgain) {
|
if (this.confirmForm.get("dontAskAgain").value) {
|
||||||
await this.stateService.setAutoConfirmFingerprints(true);
|
await this.stateService.setAutoConfirmFingerprints(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.onConfirmed.emit();
|
this.dialogRef.close(EmergencyAccessConfirmDialogResult.Confirmed);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Strongly typed helper to open a EmergencyAccessConfirmComponent
|
||||||
|
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||||
|
* @param config Configuration for the dialog
|
||||||
|
*/
|
||||||
|
static open(
|
||||||
|
dialogService: DialogService,
|
||||||
|
config: DialogConfig<EmergencyAccessConfirmDialogData>,
|
||||||
|
) {
|
||||||
|
return dialogService.open<EmergencyAccessConfirmDialogResult>(
|
||||||
|
EmergencyAccessConfirmComponent,
|
||||||
|
config,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,142 +1,68 @@
|
|||||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle">
|
<form [formGroup]="addEditForm" [bitSubmit]="submit">
|
||||||
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
|
<bit-dialog dialogSize="large" [loading]="loading">
|
||||||
<form
|
<span bitDialogTitle>
|
||||||
class="modal-content"
|
<app-premium-badge *ngIf="readOnly"></app-premium-badge>
|
||||||
#form
|
{{ title }}
|
||||||
(ngSubmit)="submit()"
|
<small class="tw-text-muted" *ngIf="params.name">{{ params.name }}</small>
|
||||||
[appApiAction]="formPromise"
|
</span>
|
||||||
ngNativeValidate
|
<ng-container bitDialogContent>
|
||||||
>
|
<ng-container *ngIf="!editMode">
|
||||||
<div class="modal-header">
|
<p bitTypography="body1">{{ "inviteEmergencyContactDesc" | i18n }}</p>
|
||||||
<h1 class="modal-title" id="userAddEditTitle">
|
<bit-form-field>
|
||||||
<app-premium-badge *ngIf="readOnly"></app-premium-badge>
|
<bit-label>{{ "email" | i18n }}</bit-label>
|
||||||
{{ title }}
|
<input bitInput formControlName="email" />
|
||||||
<small class="text-muted" *ngIf="name">{{ name }}</small>
|
</bit-form-field>
|
||||||
</h1>
|
</ng-container>
|
||||||
<button
|
<bit-radio-group formControlName="emergencyAccessType" [block]="true">
|
||||||
type="button"
|
<bit-label>
|
||||||
class="close"
|
|
||||||
data-dismiss="modal"
|
|
||||||
appA11yTitle="{{ 'close' | i18n }}"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" *ngIf="loading">
|
|
||||||
<i
|
|
||||||
class="bwi bwi-spinner bwi-spin text-muted"
|
|
||||||
title="{{ 'loading' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" *ngIf="!loading">
|
|
||||||
<ng-container *ngIf="!editMode">
|
|
||||||
<p>{{ "inviteEmergencyContactDesc" | i18n }}</p>
|
|
||||||
<div class="form-group mb-4">
|
|
||||||
<label for="email">{{ "email" | i18n }}</label>
|
|
||||||
<input
|
|
||||||
id="email"
|
|
||||||
class="form-control"
|
|
||||||
type="text"
|
|
||||||
name="Email"
|
|
||||||
[(ngModel)]="email"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<h3>
|
|
||||||
{{ "userAccess" | i18n }}
|
{{ "userAccess" | i18n }}
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
|
bitLink
|
||||||
|
linkType="primary"
|
||||||
appA11yTitle="{{ 'learnMore' | i18n }}"
|
appA11yTitle="{{ 'learnMore' | i18n }}"
|
||||||
href="https://bitwarden.com/help/emergency-access/#user-access"
|
href="https://bitwarden.com/help/emergency-access/#user-access"
|
||||||
>
|
>
|
||||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</bit-label>
|
||||||
<div class="form-check mt-2 form-check-block">
|
<bit-radio-button id="emergencyTypeView" [value]="emergencyAccessType.View">
|
||||||
<input
|
<bit-label>{{ "view" | i18n }}</bit-label>
|
||||||
class="form-check-input"
|
<bit-hint>{{ "viewDesc" | i18n }}</bit-hint>
|
||||||
type="radio"
|
</bit-radio-button>
|
||||||
name="userType"
|
|
||||||
id="emergencyTypeView"
|
<bit-radio-button id="emergencyTypeTakeover" [value]="emergencyAccessType.Takeover">
|
||||||
[value]="emergencyAccessType.View"
|
<bit-label>{{ "takeover" | i18n }}</bit-label>
|
||||||
[(ngModel)]="type"
|
<bit-hint>{{ "takeoverDesc" | i18n }}</bit-hint>
|
||||||
/>
|
</bit-radio-button>
|
||||||
<label class="form-check-label" for="emergencyTypeView">
|
</bit-radio-group>
|
||||||
{{ "view" | i18n }}
|
|
||||||
<small>{{ "viewDesc" | i18n }}</small>
|
<bit-form-field class="tw-w-1/2 tw-relative tw-px-2.5">
|
||||||
</label>
|
<bit-label>{{ "waitTime" | i18n }}</bit-label>
|
||||||
</div>
|
<bit-select formControlName="waitTime">
|
||||||
<div class="form-check mt-2 form-check-block">
|
<bit-option *ngFor="let o of waitTimes" [value]="o.value" [label]="o.name"></bit-option>
|
||||||
<input
|
</bit-select>
|
||||||
class="form-check-input"
|
<bit-hint class="tw-text-sm">{{ "waitTimeDesc" | i18n }}</bit-hint>
|
||||||
type="radio"
|
</bit-form-field>
|
||||||
name="userType"
|
</ng-container>
|
||||||
id="emergencyTypeTakeover"
|
<ng-container bitDialogFooter>
|
||||||
[value]="emergencyAccessType.Takeover"
|
<button type="submit" buttonType="primary" bitButton bitFormButton [disabled]="readOnly">
|
||||||
[(ngModel)]="type"
|
{{ "save" | i18n }}
|
||||||
[disabled]="readOnly"
|
</button>
|
||||||
/>
|
<button bitButton bitFormButton buttonType="secondary" type="button" bitDialogClose>
|
||||||
<label class="form-check-label" for="emergencyTypeTakeover">
|
{{ "cancel" | i18n }}
|
||||||
{{ "takeover" | i18n }}
|
</button>
|
||||||
<small>{{ "takeoverDesc" | i18n }}</small>
|
<button
|
||||||
</label>
|
type="button"
|
||||||
</div>
|
bitFormButton
|
||||||
<div class="form-group col-6 mt-4">
|
class="tw-ml-auto"
|
||||||
<label for="waitTime">{{ "waitTime" | i18n }}</label>
|
bitIconButton="bwi-trash"
|
||||||
<select
|
buttonType="danger"
|
||||||
id="waitTime"
|
[bitAction]="delete"
|
||||||
name="waitTime"
|
*ngIf="editMode"
|
||||||
[(ngModel)]="waitTime"
|
appA11yTitle="{{ 'delete' | i18n }}"
|
||||||
class="form-control"
|
></button>
|
||||||
[disabled]="readOnly"
|
</ng-container>
|
||||||
>
|
</bit-dialog>
|
||||||
<option *ngFor="let o of waitTimes" [ngValue]="o.value">{{ o.name }}</option>
|
</form>
|
||||||
</select>
|
|
||||||
<small class="text-muted">{{ "waitTimeDesc" | i18n }}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
buttonType="primary"
|
|
||||||
bitButton
|
|
||||||
[loading]="loading || form.loading"
|
|
||||||
[disabled]="readOnly"
|
|
||||||
>
|
|
||||||
{{ "save" | i18n }}
|
|
||||||
</button>
|
|
||||||
<button bitButton buttonType="secondary" type="button" data-dismiss="modal">
|
|
||||||
{{ "cancel" | i18n }}
|
|
||||||
</button>
|
|
||||||
<div class="ml-auto">
|
|
||||||
<button
|
|
||||||
#deleteBtn
|
|
||||||
bitButton
|
|
||||||
buttonType="danger"
|
|
||||||
type="button"
|
|
||||||
(click)="delete()"
|
|
||||||
appA11yTitle="{{ 'delete' | i18n }}"
|
|
||||||
*ngIf="editMode"
|
|
||||||
[disabled]="$any(deleteBtn).loading"
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
class="bwi bwi-trash bwi-lg bwi-fw"
|
|
||||||
[hidden]="$any(deleteBtn).loading"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<i
|
|
||||||
class="bwi bwi-spinner bwi-spin bwi-lg bwi-fw"
|
|
||||||
[hidden]="!$any(deleteBtn).loading"
|
|
||||||
title="{{ 'loading' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
@ -1,45 +1,59 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||||
|
import { Component, Inject, OnInit } from "@angular/core";
|
||||||
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { EmergencyAccessService } from "../../emergency-access";
|
import { EmergencyAccessService } from "../../emergency-access";
|
||||||
import { EmergencyAccessType } from "../../emergency-access/enums/emergency-access-type";
|
import { EmergencyAccessType } from "../../emergency-access/enums/emergency-access-type";
|
||||||
|
|
||||||
|
export type EmergencyAccessAddEditDialogData = {
|
||||||
|
/** display name of the account requesting emergency access */
|
||||||
|
name: string;
|
||||||
|
/** traces a unique emergency request */
|
||||||
|
emergencyAccessId: string;
|
||||||
|
/** A boolean indicating whether the emergency access request is in read-only mode (true for view-only, false for editing). */
|
||||||
|
readOnly: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum EmergencyAccessAddEditDialogResult {
|
||||||
|
Saved = "saved",
|
||||||
|
Canceled = "canceled",
|
||||||
|
Deleted = "deleted",
|
||||||
|
}
|
||||||
@Component({
|
@Component({
|
||||||
selector: "emergency-access-add-edit",
|
selector: "emergency-access-add-edit",
|
||||||
templateUrl: "emergency-access-add-edit.component.html",
|
templateUrl: "emergency-access-add-edit.component.html",
|
||||||
})
|
})
|
||||||
export class EmergencyAccessAddEditComponent implements OnInit {
|
export class EmergencyAccessAddEditComponent implements OnInit {
|
||||||
@Input() name: string;
|
|
||||||
@Input() emergencyAccessId: string;
|
|
||||||
@Output() onSaved = new EventEmitter();
|
|
||||||
@Output() onDeleted = new EventEmitter();
|
|
||||||
|
|
||||||
loading = true;
|
loading = true;
|
||||||
readOnly = false;
|
readOnly = false;
|
||||||
editMode = false;
|
editMode = false;
|
||||||
title: string;
|
title: string;
|
||||||
email: string;
|
|
||||||
type: EmergencyAccessType = EmergencyAccessType.View;
|
type: EmergencyAccessType = EmergencyAccessType.View;
|
||||||
|
|
||||||
formPromise: Promise<any>;
|
|
||||||
|
|
||||||
emergencyAccessType = EmergencyAccessType;
|
emergencyAccessType = EmergencyAccessType;
|
||||||
waitTimes: { name: string; value: number }[];
|
waitTimes: { name: string; value: number }[];
|
||||||
waitTime: number;
|
|
||||||
|
|
||||||
|
addEditForm = this.formBuilder.group({
|
||||||
|
email: ["", [Validators.email, Validators.required]],
|
||||||
|
emergencyAccessType: [this.emergencyAccessType.View],
|
||||||
|
waitTime: [{ value: null, disabled: this.readOnly }, [Validators.required]],
|
||||||
|
});
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DIALOG_DATA) protected params: EmergencyAccessAddEditDialogData,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
private emergencyAccessService: EmergencyAccessService,
|
private emergencyAccessService: EmergencyAccessService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
|
private dialogRef: DialogRef<EmergencyAccessAddEditDialogResult>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.editMode = this.loading = this.emergencyAccessId != null;
|
this.editMode = this.loading = this.params.emergencyAccessId != null;
|
||||||
|
|
||||||
this.waitTimes = [
|
this.waitTimes = [
|
||||||
{ name: this.i18nService.t("oneDay"), value: 1 },
|
{ name: this.i18nService.t("oneDay"), value: 1 },
|
||||||
{ name: this.i18nService.t("days", "2"), value: 2 },
|
{ name: this.i18nService.t("days", "2"), value: 2 },
|
||||||
@ -50,46 +64,72 @@ export class EmergencyAccessAddEditComponent implements OnInit {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (this.editMode) {
|
if (this.editMode) {
|
||||||
this.editMode = true;
|
|
||||||
this.title = this.i18nService.t("editEmergencyContact");
|
this.title = this.i18nService.t("editEmergencyContact");
|
||||||
try {
|
try {
|
||||||
const emergencyAccess = await this.emergencyAccessService.getEmergencyAccess(
|
const emergencyAccess = await this.emergencyAccessService.getEmergencyAccess(
|
||||||
this.emergencyAccessId,
|
this.params.emergencyAccessId,
|
||||||
);
|
);
|
||||||
this.type = emergencyAccess.type;
|
this.addEditForm.patchValue({
|
||||||
this.waitTime = emergencyAccess.waitTimeDays;
|
email: emergencyAccess.email,
|
||||||
|
waitTime: emergencyAccess.waitTimeDays,
|
||||||
|
emergencyAccessType: emergencyAccess.type,
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.title = this.i18nService.t("inviteEmergencyContact");
|
this.title = this.i18nService.t("inviteEmergencyContact");
|
||||||
this.waitTime = this.waitTimes[2].value;
|
this.addEditForm.patchValue({ waitTime: this.waitTimes[2].value });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
submit = async () => {
|
||||||
|
if (this.addEditForm.invalid) {
|
||||||
|
this.addEditForm.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (this.editMode) {
|
if (this.editMode) {
|
||||||
await this.emergencyAccessService.update(this.emergencyAccessId, this.type, this.waitTime);
|
await this.emergencyAccessService.update(
|
||||||
|
this.params.emergencyAccessId,
|
||||||
|
this.addEditForm.value.emergencyAccessType,
|
||||||
|
this.addEditForm.value.waitTime,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await this.emergencyAccessService.invite(this.email, this.type, this.waitTime);
|
await this.emergencyAccessService.invite(
|
||||||
|
this.addEditForm.value.email,
|
||||||
|
this.addEditForm.value.emergencyAccessType,
|
||||||
|
this.addEditForm.value.waitTime,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.formPromise;
|
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"success",
|
"success",
|
||||||
null,
|
null,
|
||||||
this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.name),
|
this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.params.name),
|
||||||
);
|
);
|
||||||
this.onSaved.emit();
|
this.dialogRef.close(EmergencyAccessAddEditDialogResult.Saved);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
async delete() {
|
delete = async () => {
|
||||||
this.onDeleted.emit();
|
this.dialogRef.close(EmergencyAccessAddEditDialogResult.Deleted);
|
||||||
}
|
};
|
||||||
|
/**
|
||||||
|
* Strongly typed helper to open a EmergencyAccessAddEditComponent
|
||||||
|
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||||
|
* @param config Configuration for the dialog
|
||||||
|
*/
|
||||||
|
static open = (
|
||||||
|
dialogService: DialogService,
|
||||||
|
config: DialogConfig<EmergencyAccessAddEditDialogData>,
|
||||||
|
) => {
|
||||||
|
return dialogService.open<EmergencyAccessAddEditDialogResult>(
|
||||||
|
EmergencyAccessAddEditComponent,
|
||||||
|
config,
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,264 +1,276 @@
|
|||||||
<app-header></app-header>
|
<app-header></app-header>
|
||||||
|
|
||||||
<bit-container>
|
<bit-container>
|
||||||
<p>
|
<bit-section>
|
||||||
{{ "emergencyAccessDesc" | i18n }}
|
<p bitTypography="body1">
|
||||||
<a href="https://bitwarden.com/help/emergency-access/" target="_blank" rel="noreferrer">
|
<span class="tw-text-main">{{ "emergencyAccessDesc" | i18n }}</span>
|
||||||
{{ "learnMore" | i18n }}.
|
<a
|
||||||
</a>
|
bitLink
|
||||||
</p>
|
href="https://bitwarden.com/help/emergency-access/"
|
||||||
|
target="_blank"
|
||||||
<p *ngIf="isOrganizationOwner">
|
rel="noreferrer"
|
||||||
<b>{{ "warning" | i18n }}:</b> {{ "emergencyAccessOwnerWarning" | i18n }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="page-header d-flex">
|
|
||||||
<h2>
|
|
||||||
{{ "trustedEmergencyContacts" | i18n }}
|
|
||||||
<app-premium-badge></app-premium-badge>
|
|
||||||
</h2>
|
|
||||||
<div class="ml-auto d-flex">
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-outline-primary ml-3"
|
|
||||||
type="button"
|
|
||||||
(click)="invite()"
|
|
||||||
[disabled]="!canAccessPremium"
|
|
||||||
>
|
>
|
||||||
<i aria-hidden="true" class="bwi bwi-plus bwi-fw"></i>
|
{{ "learnMore" | i18n }}.
|
||||||
{{ "addEmergencyContact" | i18n }}
|
</a>
|
||||||
</button>
|
</p>
|
||||||
|
<bit-callout *ngIf="isOrganizationOwner" type="warning" title="{{ 'warning' | i18n }}">{{
|
||||||
|
"emergencyAccessOwnerWarning" | i18n
|
||||||
|
}}</bit-callout>
|
||||||
|
</bit-section>
|
||||||
|
<bit-section>
|
||||||
|
<div class="tw-flex tw-items-center tw-gap-2 tw-mb-2">
|
||||||
|
<h2 bitTypography="h2" noMargin class="tw-mb-0">
|
||||||
|
{{ "trustedEmergencyContacts" | i18n }}
|
||||||
|
</h2>
|
||||||
|
<app-premium-badge></app-premium-badge>
|
||||||
|
<div class="tw-ml-auto tw-flex">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
bitButton
|
||||||
|
buttonType="primary"
|
||||||
|
[bitAction]="invite"
|
||||||
|
[disabled]="!canAccessPremium"
|
||||||
|
>
|
||||||
|
<i aria-hidden="true" class="bwi bwi-plus bwi-fw"></i>
|
||||||
|
{{ "addEmergencyContact" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<bit-table *ngIf="trustedContacts && trustedContacts.length">
|
||||||
|
<ng-container header>
|
||||||
|
<tr>
|
||||||
|
<th bitCell>{{ "name" | i18n }}</th>
|
||||||
|
<th bitCell>{{ "accessLevel" | i18n }}</th>
|
||||||
|
<th bitCell class="tw-text-right">{{ "options" | i18n }}</th>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template body>
|
||||||
|
<tr bitRow *ngFor="let c of trustedContacts; let i = index">
|
||||||
|
<td bitCell class="tw-flex tw-items-center tw-gap-4">
|
||||||
|
<bit-avatar
|
||||||
|
[text]="c | userName"
|
||||||
|
[id]="c.granteeId"
|
||||||
|
[color]="c.avatarColor"
|
||||||
|
size="small"
|
||||||
|
></bit-avatar>
|
||||||
|
<span>
|
||||||
|
<a bitLink href="#" appStopClick (click)="edit(c)">{{ c.email }}</a>
|
||||||
|
<span
|
||||||
|
bitBadge
|
||||||
|
variant="secondary"
|
||||||
|
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
||||||
|
>{{ "invited" | i18n }}</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
bitBadge
|
||||||
|
variant="warning"
|
||||||
|
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||||
|
>{{ "accepted" | i18n }}</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
bitBadge
|
||||||
|
variant="warning"
|
||||||
|
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||||
|
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||||
|
>
|
||||||
|
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.RecoveryApproved">{{
|
||||||
|
"emergencyAccessRecoveryApproved" | i18n
|
||||||
|
}}</span>
|
||||||
|
|
||||||
<table
|
<small class="tw-text-muted tw-block" *ngIf="c.name">{{ c.name }}</small>
|
||||||
class="table table-hover table-list mb-0"
|
</span>
|
||||||
*ngIf="trustedContacts && trustedContacts.length"
|
</td>
|
||||||
>
|
<td bitCell>
|
||||||
<tbody>
|
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||||
<tr *ngFor="let c of trustedContacts; let i = index">
|
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||||
<td width="30">
|
"takeover" | i18n
|
||||||
<bit-avatar
|
}}</span>
|
||||||
[text]="c | userName"
|
</td>
|
||||||
[id]="c.granteeId"
|
<td bitCell class="tw-text-right">
|
||||||
[color]="c.avatarColor"
|
|
||||||
size="small"
|
|
||||||
></bit-avatar>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="#" appStopClick (click)="edit(c)">{{ c.email }}</a>
|
|
||||||
<span
|
|
||||||
bitBadge
|
|
||||||
variant="secondary"
|
|
||||||
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
|
||||||
>{{ "invited" | i18n }}</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
bitBadge
|
|
||||||
variant="warning"
|
|
||||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
|
||||||
>{{ "accepted" | i18n }}</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
bitBadge
|
|
||||||
variant="warning"
|
|
||||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
|
||||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
|
||||||
>
|
|
||||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.RecoveryApproved">{{
|
|
||||||
"emergencyAccessRecoveryApproved" | i18n
|
|
||||||
}}</span>
|
|
||||||
|
|
||||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
|
||||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
|
||||||
"takeover" | i18n
|
|
||||||
}}</span>
|
|
||||||
|
|
||||||
<small class="text-muted d-block" *ngIf="c.name">{{ c.name }}</small>
|
|
||||||
</td>
|
|
||||||
<td class="table-list-options">
|
|
||||||
<button
|
|
||||||
[bitMenuTriggerFor]="trustedContactOptions"
|
|
||||||
class="tw-border-none tw-bg-transparent tw-text-main"
|
|
||||||
type="button"
|
|
||||||
appA11yTitle="{{ 'options' | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<bit-menu #trustedContactOptions>
|
|
||||||
<button
|
<button
|
||||||
|
[bitMenuTriggerFor]="trustedContactOptions"
|
||||||
type="button"
|
type="button"
|
||||||
bitMenuItem
|
appA11yTitle="{{ 'options' | i18n }}"
|
||||||
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
bitIconButton="bwi-ellipsis-v"
|
||||||
(click)="reinvite(c)"
|
buttonType="main"
|
||||||
>
|
></button>
|
||||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
<bit-menu #trustedContactOptions>
|
||||||
{{ "resendInvitation" | i18n }}
|
<button
|
||||||
</button>
|
type="button"
|
||||||
<button
|
bitMenuItem
|
||||||
type="button"
|
*ngIf="c.status === emergencyAccessStatusType.Invited"
|
||||||
bitMenuItem
|
(click)="reinvite(c)"
|
||||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
>
|
||||||
(click)="confirm(c)"
|
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||||
>
|
{{ "resendInvitation" | i18n }}
|
||||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
</button>
|
||||||
{{ "confirm" | i18n }}
|
<button
|
||||||
</button>
|
type="button"
|
||||||
<button
|
bitMenuItem
|
||||||
type="button"
|
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||||
bitMenuItem
|
(click)="confirm(c)"
|
||||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
>
|
||||||
(click)="approve(c)"
|
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||||
>
|
{{ "confirm" | i18n }}
|
||||||
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
</button>
|
||||||
{{ "approve" | i18n }}
|
<button
|
||||||
</button>
|
type="button"
|
||||||
<button
|
bitMenuItem
|
||||||
type="button"
|
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||||
bitMenuItem
|
(click)="approve(c)"
|
||||||
*ngIf="
|
>
|
||||||
c.status === emergencyAccessStatusType.RecoveryInitiated ||
|
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
|
||||||
c.status === emergencyAccessStatusType.RecoveryApproved
|
{{ "approve" | i18n }}
|
||||||
"
|
</button>
|
||||||
(click)="reject(c)"
|
<button
|
||||||
>
|
type="button"
|
||||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
bitMenuItem
|
||||||
{{ "reject" | i18n }}
|
*ngIf="
|
||||||
</button>
|
c.status === emergencyAccessStatusType.RecoveryInitiated ||
|
||||||
<button type="button" bitMenuItem (click)="remove(c)">
|
c.status === emergencyAccessStatusType.RecoveryApproved
|
||||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
"
|
||||||
{{ "remove" | i18n }}
|
(click)="reject(c)"
|
||||||
</button>
|
>
|
||||||
</bit-menu>
|
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||||
</td>
|
{{ "reject" | i18n }}
|
||||||
</tr>
|
</button>
|
||||||
</tbody>
|
<button type="button" bitMenuItem (click)="remove(c)">
|
||||||
</table>
|
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||||
|
{{ "remove" | i18n }}
|
||||||
<ng-container *ngIf="!trustedContacts || !trustedContacts.length">
|
</button>
|
||||||
<p *ngIf="loaded">{{ "noTrustedContacts" | i18n }}</p>
|
</bit-menu>
|
||||||
<ng-container *ngIf="!loaded">
|
</td>
|
||||||
<i
|
</tr>
|
||||||
class="bwi bwi-spinner bwi-spin text-muted"
|
</ng-template>
|
||||||
title="{{ 'loading' | i18n }}"
|
</bit-table>
|
||||||
aria-hidden="true"
|
<ng-container *ngIf="!trustedContacts || !trustedContacts.length">
|
||||||
></i>
|
<p bitTypography="body1" class="tw-mt-2" *ngIf="loaded">{{ "noTrustedContacts" | i18n }}</p>
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
<ng-container *ngIf="!loaded">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-spinner bwi-spin text-muted"
|
||||||
|
title="{{ 'loading' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</bit-section>
|
||||||
|
|
||||||
<div class="page-header spaced-header">
|
<bit-section>
|
||||||
<h2>{{ "designatedEmergencyContacts" | i18n }}</h2>
|
<h2 bitTypography="h2">{{ "designatedEmergencyContacts" | i18n }}</h2>
|
||||||
</div>
|
|
||||||
|
|
||||||
<table
|
<bit-table *ngIf="grantedContacts && grantedContacts.length">
|
||||||
class="table table-hover table-list mb-0"
|
<ng-container header>
|
||||||
*ngIf="grantedContacts && grantedContacts.length"
|
<tr>
|
||||||
>
|
<th bitCell>{{ "name" | i18n }}</th>
|
||||||
<tbody>
|
<th bitCell>{{ "accessLevel" | i18n }}</th>
|
||||||
<tr *ngFor="let c of grantedContacts; let i = index">
|
<th bitCell class="tw-text-right">{{ "options" | i18n }}</th>
|
||||||
<td width="30">
|
</tr>
|
||||||
<bit-avatar
|
</ng-container>
|
||||||
[text]="c | userName"
|
<ng-template body>
|
||||||
[id]="c.grantorId"
|
<tr bitRow *ngFor="let c of grantedContacts; let i = index">
|
||||||
[color]="c.avatarColor"
|
<td bitCell class="tw-flex tw-items-center tw-gap-4">
|
||||||
size="small"
|
<bit-avatar
|
||||||
></bit-avatar>
|
[text]="c | userName"
|
||||||
</td>
|
[id]="c.grantorId"
|
||||||
<td>
|
[color]="c.avatarColor"
|
||||||
<span>{{ c.email }}</span>
|
size="small"
|
||||||
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.Invited">{{
|
></bit-avatar>
|
||||||
"invited" | i18n
|
<span>
|
||||||
}}</span>
|
<span>{{ c.email }}</span>
|
||||||
<span
|
<span bitBadge *ngIf="c.status === emergencyAccessStatusType.Invited">{{
|
||||||
bitBadge
|
"invited" | i18n
|
||||||
variant="warning"
|
}}</span>
|
||||||
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
<span
|
||||||
>{{ "accepted" | i18n }}</span
|
bitBadge
|
||||||
>
|
variant="warning"
|
||||||
<span
|
*ngIf="c.status === emergencyAccessStatusType.Accepted"
|
||||||
bitBadge
|
>{{ "accepted" | i18n }}</span
|
||||||
variant="warning"
|
>
|
||||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
<span
|
||||||
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
bitBadge
|
||||||
>
|
variant="warning"
|
||||||
<span
|
*ngIf="c.status === emergencyAccessStatusType.RecoveryInitiated"
|
||||||
bitBadge
|
>{{ "emergencyAccessRecoveryInitiated" | i18n }}</span
|
||||||
variant="success"
|
>
|
||||||
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved"
|
<span
|
||||||
>{{ "emergencyAccessRecoveryApproved" | i18n }}</span
|
bitBadge
|
||||||
>
|
variant="success"
|
||||||
|
*ngIf="c.status === emergencyAccessStatusType.RecoveryApproved"
|
||||||
|
>{{ "emergencyAccessRecoveryApproved" | i18n }}</span
|
||||||
|
>
|
||||||
|
|
||||||
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
<small class="tw-text-muted tw-block" *ngIf="c.name">{{ c.name }}</small>
|
||||||
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
</span>
|
||||||
"takeover" | i18n
|
</td>
|
||||||
}}</span>
|
<td bitCell>
|
||||||
|
<span bitBadge *ngIf="c.type === emergencyAccessType.View">{{ "view" | i18n }}</span>
|
||||||
<small class="text-muted d-block" *ngIf="c.name">{{ c.name }}</small>
|
<span bitBadge *ngIf="c.type === emergencyAccessType.Takeover">{{
|
||||||
</td>
|
"takeover" | i18n
|
||||||
<td class="table-list-options">
|
}}</span>
|
||||||
<button
|
</td>
|
||||||
[bitMenuTriggerFor]="grantedContactOptions"
|
<td bitCell class="tw-text-right">
|
||||||
class="tw-border-none tw-bg-transparent tw-text-main"
|
|
||||||
type="button"
|
|
||||||
appA11yTitle="{{ 'options' | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-ellipsis-v bwi-lg" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<bit-menu #grantedContactOptions>
|
|
||||||
<button
|
<button
|
||||||
|
[bitMenuTriggerFor]="grantedContactOptions"
|
||||||
type="button"
|
type="button"
|
||||||
bitMenuItem
|
appA11yTitle="{{ 'options' | i18n }}"
|
||||||
*ngIf="c.status === emergencyAccessStatusType.Confirmed"
|
bitIconButton="bwi-ellipsis-v"
|
||||||
(click)="requestAccess(c)"
|
buttonType="main"
|
||||||
>
|
></button>
|
||||||
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
<bit-menu #grantedContactOptions>
|
||||||
{{ "requestAccess" | i18n }}
|
<button
|
||||||
</button>
|
type="button"
|
||||||
<button
|
bitMenuItem
|
||||||
type="button"
|
*ngIf="c.status === emergencyAccessStatusType.Confirmed"
|
||||||
bitMenuItem
|
(click)="requestAccess(c)"
|
||||||
*ngIf="
|
>
|
||||||
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
<i class="bwi bwi-fw bwi-envelope" aria-hidden="true"></i>
|
||||||
c.type === emergencyAccessType.Takeover
|
{{ "requestAccess" | i18n }}
|
||||||
"
|
</button>
|
||||||
(click)="takeover(c)"
|
<button
|
||||||
>
|
type="button"
|
||||||
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
|
bitMenuItem
|
||||||
{{ "takeover" | i18n }}
|
*ngIf="
|
||||||
</button>
|
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
||||||
<button
|
c.type === emergencyAccessType.Takeover
|
||||||
type="button"
|
"
|
||||||
bitMenuItem
|
(click)="takeover(c)"
|
||||||
*ngIf="
|
>
|
||||||
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
<i class="bwi bwi-fw bwi-key" aria-hidden="true"></i>
|
||||||
c.type === emergencyAccessType.View
|
{{ "takeover" | i18n }}
|
||||||
"
|
</button>
|
||||||
[routerLink]="c.id"
|
<button
|
||||||
>
|
type="button"
|
||||||
<i class="bwi bwi-fw bwi-eye" aria-hidden="true"></i>
|
bitMenuItem
|
||||||
{{ "view" | i18n }}
|
*ngIf="
|
||||||
</button>
|
c.status === emergencyAccessStatusType.RecoveryApproved &&
|
||||||
<button type="button" bitMenuItem (click)="remove(c)">
|
c.type === emergencyAccessType.View
|
||||||
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
"
|
||||||
{{ "remove" | i18n }}
|
[routerLink]="c.id"
|
||||||
</button>
|
>
|
||||||
</bit-menu>
|
<i class="bwi bwi-fw bwi-eye" aria-hidden="true"></i>
|
||||||
</td>
|
{{ "view" | i18n }}
|
||||||
</tr>
|
</button>
|
||||||
</tbody>
|
<button type="button" bitMenuItem (click)="remove(c)">
|
||||||
</table>
|
<i class="bwi bwi-fw bwi-close" aria-hidden="true"></i>
|
||||||
|
{{ "remove" | i18n }}
|
||||||
<ng-container *ngIf="!grantedContacts || !grantedContacts.length">
|
</button>
|
||||||
<p *ngIf="loaded">{{ "noGrantedAccess" | i18n }}</p>
|
</bit-menu>
|
||||||
<ng-container *ngIf="!loaded">
|
</td>
|
||||||
<i
|
</tr>
|
||||||
class="bwi bwi-spinner bwi-spin text-muted"
|
</ng-template>
|
||||||
title="{{ 'loading' | i18n }}"
|
</bit-table>
|
||||||
aria-hidden="true"
|
<ng-container *ngIf="!grantedContacts || !grantedContacts.length">
|
||||||
></i>
|
<p bitTypography="body1" *ngIf="loaded">{{ "noGrantedAccess" | i18n }}</p>
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
<ng-container *ngIf="!loaded">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-spinner bwi-spin text-muted"
|
||||||
|
title="{{ 'loading' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</bit-section>
|
||||||
</bit-container>
|
</bit-container>
|
||||||
|
|
||||||
<ng-template #addEdit></ng-template>
|
<ng-template #addEdit></ng-template>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||||
|
import { lastValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
@ -18,9 +18,18 @@ import {
|
|||||||
GrantorEmergencyAccess,
|
GrantorEmergencyAccess,
|
||||||
} from "../../emergency-access/models/emergency-access";
|
} from "../../emergency-access/models/emergency-access";
|
||||||
|
|
||||||
import { EmergencyAccessConfirmComponent } from "./confirm/emergency-access-confirm.component";
|
import {
|
||||||
import { EmergencyAccessAddEditComponent } from "./emergency-access-add-edit.component";
|
EmergencyAccessConfirmComponent,
|
||||||
import { EmergencyAccessTakeoverComponent } from "./takeover/emergency-access-takeover.component";
|
EmergencyAccessConfirmDialogResult,
|
||||||
|
} from "./confirm/emergency-access-confirm.component";
|
||||||
|
import {
|
||||||
|
EmergencyAccessAddEditComponent,
|
||||||
|
EmergencyAccessAddEditDialogResult,
|
||||||
|
} from "./emergency-access-add-edit.component";
|
||||||
|
import {
|
||||||
|
EmergencyAccessTakeoverComponent,
|
||||||
|
EmergencyAccessTakeoverResultType,
|
||||||
|
} from "./takeover/emergency-access-takeover.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "emergency-access",
|
selector: "emergency-access",
|
||||||
@ -46,7 +55,6 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private emergencyAccessService: EmergencyAccessService,
|
private emergencyAccessService: EmergencyAccessService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private modalService: ModalService,
|
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
private userNamePipe: UserNamePipe,
|
private userNamePipe: UserNamePipe,
|
||||||
@ -78,37 +86,26 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async edit(details: GranteeEmergencyAccess) {
|
edit = async (details: GranteeEmergencyAccess) => {
|
||||||
const [modal] = await this.modalService.openViewRef(
|
const dialogRef = EmergencyAccessAddEditComponent.open(this.dialogService, {
|
||||||
EmergencyAccessAddEditComponent,
|
data: {
|
||||||
this.addEditModalRef,
|
name: this.userNamePipe.transform(details),
|
||||||
(comp) => {
|
emergencyAccessId: details?.id,
|
||||||
comp.name = this.userNamePipe.transform(details);
|
readOnly: !this.canAccessPremium,
|
||||||
comp.emergencyAccessId = details?.id;
|
|
||||||
comp.readOnly = !this.canAccessPremium;
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
|
||||||
comp.onSaved.subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
this.load();
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
|
||||||
comp.onDeleted.subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
this.remove(details);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
invite() {
|
const result = await lastValueFrom(dialogRef.closed);
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
if (result === EmergencyAccessAddEditDialogResult.Saved) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
await this.load();
|
||||||
this.edit(null);
|
} else if (result === EmergencyAccessAddEditDialogResult.Deleted) {
|
||||||
}
|
await this.remove(details);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
invite = async () => {
|
||||||
|
await this.edit(null);
|
||||||
|
};
|
||||||
|
|
||||||
async reinvite(contact: GranteeEmergencyAccess) {
|
async reinvite(contact: GranteeEmergencyAccess) {
|
||||||
if (this.actionPromise != null) {
|
if (this.actionPromise != null) {
|
||||||
@ -135,29 +132,23 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
|
|
||||||
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
|
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
|
||||||
if (autoConfirm == null || !autoConfirm) {
|
if (autoConfirm == null || !autoConfirm) {
|
||||||
const [modal] = await this.modalService.openViewRef(
|
const dialogRef = EmergencyAccessConfirmComponent.open(this.dialogService, {
|
||||||
EmergencyAccessConfirmComponent,
|
data: {
|
||||||
this.confirmModalRef,
|
name: this.userNamePipe.transform(contact),
|
||||||
(comp) => {
|
emergencyAccessId: contact.id,
|
||||||
comp.name = this.userNamePipe.transform(contact);
|
userId: contact?.granteeId,
|
||||||
comp.emergencyAccessId = contact.id;
|
|
||||||
comp.userId = contact?.granteeId;
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
|
||||||
comp.onConfirmed.subscribe(async () => {
|
|
||||||
modal.close();
|
|
||||||
|
|
||||||
comp.formPromise = this.emergencyAccessService.confirm(contact.id, contact.granteeId);
|
|
||||||
await comp.formPromise;
|
|
||||||
|
|
||||||
updateUser();
|
|
||||||
this.platformUtilsService.showToast(
|
|
||||||
"success",
|
|
||||||
null,
|
|
||||||
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(contact)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
|
const result = await lastValueFrom(dialogRef.closed);
|
||||||
|
if (result === EmergencyAccessConfirmDialogResult.Confirmed) {
|
||||||
|
await this.emergencyAccessService.confirm(contact.id, contact.granteeId);
|
||||||
|
updateUser();
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(contact)),
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,27 +258,23 @@ export class EmergencyAccessComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeover(details: GrantorEmergencyAccess) {
|
takeover = async (details: GrantorEmergencyAccess) => {
|
||||||
const [modal] = await this.modalService.openViewRef(
|
const dialogRef = EmergencyAccessTakeoverComponent.open(this.dialogService, {
|
||||||
EmergencyAccessTakeoverComponent,
|
data: {
|
||||||
this.takeoverModalRef,
|
name: this.userNamePipe.transform(details),
|
||||||
(comp) => {
|
email: details.email,
|
||||||
comp.name = this.userNamePipe.transform(details);
|
emergencyAccessId: details.id ?? null,
|
||||||
comp.email = details.email;
|
|
||||||
comp.emergencyAccessId = details != null ? details.id : null;
|
|
||||||
|
|
||||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
|
||||||
comp.onDone.subscribe(() => {
|
|
||||||
modal.close();
|
|
||||||
this.platformUtilsService.showToast(
|
|
||||||
"success",
|
|
||||||
null,
|
|
||||||
this.i18nService.t("passwordResetFor", this.userNamePipe.transform(details)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
}
|
const result = await lastValueFrom(dialogRef.closed);
|
||||||
|
if (result === EmergencyAccessTakeoverResultType.Done) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("passwordResetFor", this.userNamePipe.transform(details)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private removeGrantee(details: GranteeEmergencyAccess) {
|
private removeGrantee(details: GranteeEmergencyAccess) {
|
||||||
const index = this.trustedContacts.indexOf(details);
|
const index = this.trustedContacts.indexOf(details);
|
||||||
|
@ -1,79 +1,54 @@
|
|||||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="userAddEditTitle">
|
<form [formGroup]="takeoverForm" [bitSubmit]="submit">
|
||||||
<div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
|
<bit-dialog dialogSize="large">
|
||||||
<form
|
<span bitDialogTitle>
|
||||||
class="modal-content"
|
{{ "takeover" | i18n }}
|
||||||
#form
|
<small class="tw-text-muted" *ngIf="params.name">{{ params.name }}</small>
|
||||||
(ngSubmit)="submit()"
|
</span>
|
||||||
[appApiAction]="formPromise"
|
<div bitDialogContent>
|
||||||
ngNativeValidate
|
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
|
||||||
>
|
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
|
||||||
<div class="modal-header">
|
</auth-password-callout>
|
||||||
<h1 class="modal-title" id="userAddEditTitle">
|
<div class="tw-w-full tw-flex tw-gap-4">
|
||||||
{{ "takeover" | i18n }}
|
<div class="tw-relative tw-flex-1">
|
||||||
<small class="text-muted" *ngIf="name">{{ name }}</small>
|
<bit-form-field disableMargin class="tw-mb-2">
|
||||||
</h1>
|
<bit-label>{{ "newMasterPass" | i18n }}</bit-label>
|
||||||
<button
|
<input
|
||||||
type="button"
|
bitInput
|
||||||
class="close"
|
type="password"
|
||||||
data-dismiss="modal"
|
autocomplete="new-password"
|
||||||
appA11yTitle="{{ 'close' | i18n }}"
|
formControlName="masterPassword"
|
||||||
>
|
/>
|
||||||
<span aria-hidden="true">×</span>
|
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
|
||||||
</button>
|
</bit-form-field>
|
||||||
</div>
|
<app-password-strength
|
||||||
<div class="modal-body">
|
[password]="takeoverForm.value.masterPassword"
|
||||||
<app-callout type="warning">{{ "loggedOutWarning" | i18n }}</app-callout>
|
[email]="email"
|
||||||
<auth-password-callout [policy]="enforcedPolicyOptions" *ngIf="enforcedPolicyOptions">
|
[showText]="true"
|
||||||
</auth-password-callout>
|
(passwordStrengthResult)="getStrengthResult($event)"
|
||||||
<div class="row">
|
>
|
||||||
<div class="col-6">
|
</app-password-strength>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label for="masterPassword">{{ "newMasterPass" | i18n }}</label>
|
<div class="tw-relative tw-flex-1">
|
||||||
<input
|
<bit-form-field disableMargin class="tw-mb-2">
|
||||||
id="masterPassword"
|
<bit-label>{{ "confirmNewMasterPass" | i18n }}</bit-label>
|
||||||
type="password"
|
<input
|
||||||
name="NewMasterPasswordHash"
|
bitInput
|
||||||
class="form-control mb-1"
|
type="password"
|
||||||
[(ngModel)]="masterPassword"
|
autocomplete="new-password"
|
||||||
required
|
formControlName="masterPasswordRetype"
|
||||||
appInputVerbatim
|
/>
|
||||||
autocomplete="new-password"
|
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
|
||||||
/>
|
</bit-form-field>
|
||||||
<app-password-strength
|
|
||||||
[password]="masterPassword"
|
|
||||||
[email]="email"
|
|
||||||
[showText]="true"
|
|
||||||
(passwordStrengthResult)="getStrengthResult($event)"
|
|
||||||
>
|
|
||||||
</app-password-strength>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="masterPasswordRetype">{{ "confirmNewMasterPass" | i18n }}</label>
|
|
||||||
<input
|
|
||||||
id="masterPasswordRetype"
|
|
||||||
type="password"
|
|
||||||
name="MasterPasswordRetype"
|
|
||||||
class="form-control"
|
|
||||||
[(ngModel)]="masterPasswordRetype"
|
|
||||||
required
|
|
||||||
appInputVerbatim
|
|
||||||
autocomplete="new-password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
<div bitDialogFooter>
|
||||||
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||||
<span>{{ "save" | i18n }}</span>
|
{{ "save" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
<button bitButton bitFormButton type="button" buttonType="secondary" bitDialogClose>
|
||||||
{{ "cancel" | i18n }}
|
{{ "cancel" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</bit-dialog>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||||
|
import { Component, OnDestroy, OnInit, Inject, Input } from "@angular/core";
|
||||||
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
import { takeUntil } from "rxjs";
|
import { takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
|
import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
|
||||||
@ -15,6 +17,17 @@ import { DialogService } from "@bitwarden/components";
|
|||||||
|
|
||||||
import { EmergencyAccessService } from "../../../emergency-access";
|
import { EmergencyAccessService } from "../../../emergency-access";
|
||||||
|
|
||||||
|
export enum EmergencyAccessTakeoverResultType {
|
||||||
|
Done = "done",
|
||||||
|
}
|
||||||
|
type EmergencyAccessTakeoverDialogData = {
|
||||||
|
/** display name of the account requesting emergency access takeover */
|
||||||
|
name: string;
|
||||||
|
/** email of the account requesting emergency access takeover */
|
||||||
|
email: string;
|
||||||
|
/** traces a unique emergency request */
|
||||||
|
emergencyAccessId: string;
|
||||||
|
};
|
||||||
@Component({
|
@Component({
|
||||||
selector: "emergency-access-takeover",
|
selector: "emergency-access-takeover",
|
||||||
templateUrl: "emergency-access-takeover.component.html",
|
templateUrl: "emergency-access-takeover.component.html",
|
||||||
@ -24,16 +37,16 @@ export class EmergencyAccessTakeoverComponent
|
|||||||
extends ChangePasswordComponent
|
extends ChangePasswordComponent
|
||||||
implements OnInit, OnDestroy
|
implements OnInit, OnDestroy
|
||||||
{
|
{
|
||||||
@Output() onDone = new EventEmitter();
|
|
||||||
@Input() emergencyAccessId: string;
|
|
||||||
@Input() name: string;
|
|
||||||
@Input() email: string;
|
|
||||||
@Input() kdf: KdfType;
|
@Input() kdf: KdfType;
|
||||||
@Input() kdfIterations: number;
|
@Input() kdfIterations: number;
|
||||||
|
takeoverForm = this.formBuilder.group({
|
||||||
formPromise: Promise<any>;
|
masterPassword: ["", [Validators.required]],
|
||||||
|
masterPasswordRetype: ["", [Validators.required]],
|
||||||
|
});
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DIALOG_DATA) protected params: EmergencyAccessTakeoverDialogData,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
cryptoService: CryptoService,
|
cryptoService: CryptoService,
|
||||||
messagingService: MessagingService,
|
messagingService: MessagingService,
|
||||||
@ -44,6 +57,7 @@ export class EmergencyAccessTakeoverComponent
|
|||||||
private emergencyAccessService: EmergencyAccessService,
|
private emergencyAccessService: EmergencyAccessService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
dialogService: DialogService,
|
dialogService: DialogService,
|
||||||
|
private dialogRef: DialogRef<EmergencyAccessTakeoverResultType>,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
i18nService,
|
i18nService,
|
||||||
@ -58,7 +72,9 @@ export class EmergencyAccessTakeoverComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const policies = await this.emergencyAccessService.getGrantorPolicies(this.emergencyAccessId);
|
const policies = await this.emergencyAccessService.getGrantorPolicies(
|
||||||
|
this.params.emergencyAccessId,
|
||||||
|
);
|
||||||
this.policyService
|
this.policyService
|
||||||
.masterPasswordPolicyOptions$(policies)
|
.masterPasswordPolicyOptions$(policies)
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
@ -70,18 +86,23 @@ export class EmergencyAccessTakeoverComponent
|
|||||||
super.ngOnDestroy();
|
super.ngOnDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
submit = async () => {
|
||||||
|
if (this.takeoverForm.invalid) {
|
||||||
|
this.takeoverForm.markAllAsTouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.masterPassword = this.takeoverForm.get("masterPassword").value;
|
||||||
|
this.masterPasswordRetype = this.takeoverForm.get("masterPasswordRetype").value;
|
||||||
if (!(await this.strongPassword())) {
|
if (!(await this.strongPassword())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.emergencyAccessService.takeover(
|
await this.emergencyAccessService.takeover(
|
||||||
this.emergencyAccessId,
|
this.params.emergencyAccessId,
|
||||||
this.masterPassword,
|
this.masterPassword,
|
||||||
this.email,
|
this.params.email,
|
||||||
);
|
);
|
||||||
this.onDone.emit();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
@ -90,5 +111,20 @@ export class EmergencyAccessTakeoverComponent
|
|||||||
this.i18nService.t("unexpectedError"),
|
this.i18nService.t("unexpectedError"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
this.dialogRef.close(EmergencyAccessTakeoverResultType.Done);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Strongly typed helper to open a EmergencyAccessTakeoverComponent
|
||||||
|
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||||
|
* @param config Configuration for the dialog
|
||||||
|
*/
|
||||||
|
static open = (
|
||||||
|
dialogService: DialogService,
|
||||||
|
config: DialogConfig<EmergencyAccessTakeoverDialogData>,
|
||||||
|
) => {
|
||||||
|
return dialogService.open<EmergencyAccessTakeoverResultType>(
|
||||||
|
EmergencyAccessTakeoverComponent,
|
||||||
|
config,
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,71 +1,76 @@
|
|||||||
<div class="page-header">
|
<h1 bitTypography="h1">{{ "vault" | i18n }}</h1>
|
||||||
<h1>{{ "vault" | i18n }}</h1>
|
|
||||||
</div>
|
<div class="tw-mt-6">
|
||||||
<div class="mt-4">
|
|
||||||
<ng-container *ngIf="ciphers.length">
|
<ng-container *ngIf="ciphers.length">
|
||||||
<table class="table table-hover table-list table-ciphers">
|
<bit-table>
|
||||||
<tbody>
|
<ng-template body>
|
||||||
<tr *ngFor="let c of ciphers">
|
<tr bitRow *ngFor="let currentCipher of ciphers">
|
||||||
<td class="table-list-icon">
|
<td bitCell>
|
||||||
<app-vault-icon [cipher]="c"></app-vault-icon>
|
<app-vault-icon [cipher]="currentCipher"></app-vault-icon>
|
||||||
</td>
|
</td>
|
||||||
<td class="reduced-lh wrap">
|
<td bitCell class="tw-w-full">
|
||||||
<a href="#" appStopClick (click)="selectCipher(c)" title="{{ 'editItem' | i18n }}">{{
|
<a
|
||||||
c.name
|
bitLink
|
||||||
}}</a>
|
href="#"
|
||||||
<ng-container *ngIf="c.organizationId">
|
appStopClick
|
||||||
|
(click)="selectCipher(currentCipher)"
|
||||||
|
title="{{ 'editItem' | i18n }}"
|
||||||
|
>{{ currentCipher.name }}</a
|
||||||
|
>
|
||||||
|
<ng-container *ngIf="currentCipher.organizationId">
|
||||||
<i
|
<i
|
||||||
class="bwi bwi-collection"
|
class="bwi bwi-collection"
|
||||||
appStopProp
|
appStopProp
|
||||||
title="{{ 'shared' | i18n }}"
|
title="{{ 'shared' | i18n }}"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></i>
|
></i>
|
||||||
<span class="sr-only">{{ "shared" | i18n }}</span>
|
<span class="tw-sr-only">{{ "shared" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="c.hasAttachments">
|
<ng-container *ngIf="currentCipher.hasAttachments">
|
||||||
<i
|
<i
|
||||||
class="bwi bwi-paperclip"
|
class="bwi bwi-paperclip"
|
||||||
appStopProp
|
appStopProp
|
||||||
title="{{ 'attachments' | i18n }}"
|
title="{{ 'attachments' | i18n }}"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></i>
|
></i>
|
||||||
<span class="sr-only">{{ "attachments" | i18n }}</span>
|
<span class="tw-sr-only">{{ "attachments" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<br />
|
<br />
|
||||||
<small>{{ c.subTitle }}</small>
|
<small class="tw-text-xs">{{ currentCipher.subTitle }}</small>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-list-options">
|
<td bitCell>
|
||||||
<div class="dropdown" appListDropdown *ngIf="c.hasAttachments">
|
<div *ngIf="currentCipher.hasAttachments">
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline-secondary dropdown-toggle"
|
[bitMenuTriggerFor]="optionsMenu"
|
||||||
type="button"
|
type="button"
|
||||||
id="dropdownMenuButton"
|
buttonType="main"
|
||||||
data-toggle="dropdown"
|
bitIconButton="bwi-ellipsis-v"
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="false"
|
|
||||||
appA11yTitle="{{ 'options' | i18n }}"
|
appA11yTitle="{{ 'options' | i18n }}"
|
||||||
>
|
></button>
|
||||||
<i class="bwi bwi-cog bwi-lg" aria-hidden="true"></i>
|
<bit-menu #optionsMenu>
|
||||||
</button>
|
<button
|
||||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
|
type="button"
|
||||||
<a class="dropdown-item" href="#" appStopClick (click)="viewAttachments(c)">
|
bitMenuItem
|
||||||
|
appStopClick
|
||||||
|
(click)="viewAttachments(currentCipher)"
|
||||||
|
>
|
||||||
<i class="bwi bwi-fw bwi-paperclip" aria-hidden="true"></i>
|
<i class="bwi bwi-fw bwi-paperclip" aria-hidden="true"></i>
|
||||||
{{ "attachments" | i18n }}
|
{{ "attachments" | i18n }}
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</bit-menu>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</ng-template>
|
||||||
</table>
|
</bit-table>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!loaded">
|
<ng-container *ngIf="!loaded">
|
||||||
<i
|
<i
|
||||||
class="bwi bwi-spinner bwi-spin text-muted"
|
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||||
title="{{ 'loading' | i18n }}"
|
title="{{ 'loading' | i18n }}"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></i>
|
></i>
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<ng-template #cipherAddEdit></ng-template>
|
<ng-template #cipherAddEdit></ng-template>
|
||||||
|
@ -578,6 +578,9 @@
|
|||||||
"access": {
|
"access": {
|
||||||
"message": "Access"
|
"message": "Access"
|
||||||
},
|
},
|
||||||
|
"accessLevel": {
|
||||||
|
"message": "Access level"
|
||||||
|
},
|
||||||
"loggedOut": {
|
"loggedOut": {
|
||||||
"message": "Logged out"
|
"message": "Logged out"
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user