1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 18:23:31 +00:00
Files
browser/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts
2024-03-08 09:15:07 -05:00

293 lines
9.5 KiB
TypeScript

import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { lastValueFrom } from "rxjs";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DialogService } from "@bitwarden/components";
import { EmergencyAccessService } from "../../emergency-access";
import { EmergencyAccessStatusType } from "../../emergency-access/enums/emergency-access-status-type";
import { EmergencyAccessType } from "../../emergency-access/enums/emergency-access-type";
import {
GranteeEmergencyAccess,
GrantorEmergencyAccess,
} from "../../emergency-access/models/emergency-access";
import {
EmergencyAccessConfirmComponent,
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({
selector: "emergency-access",
templateUrl: "emergency-access.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class EmergencyAccessComponent implements OnInit {
@ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
@ViewChild("takeoverTemplate", { read: ViewContainerRef, static: true })
takeoverModalRef: ViewContainerRef;
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
confirmModalRef: ViewContainerRef;
loaded = false;
canAccessPremium: boolean;
trustedContacts: GranteeEmergencyAccess[];
grantedContacts: GrantorEmergencyAccess[];
emergencyAccessType = EmergencyAccessType;
emergencyAccessStatusType = EmergencyAccessStatusType;
actionPromise: Promise<any>;
isOrganizationOwner: boolean;
constructor(
private emergencyAccessService: EmergencyAccessService,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private messagingService: MessagingService,
private userNamePipe: UserNamePipe,
private logService: LogService,
private stateService: StateService,
private organizationService: OrganizationService,
protected dialogService: DialogService,
) {}
async ngOnInit() {
this.canAccessPremium = await this.stateService.getCanAccessPremium();
const orgs = await this.organizationService.getAll();
this.isOrganizationOwner = orgs.some((o) => o.isOwner);
// 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();
}
async load() {
this.trustedContacts = await this.emergencyAccessService.getEmergencyAccessTrusted();
this.grantedContacts = await this.emergencyAccessService.getEmergencyAccessGranted();
this.loaded = true;
}
async premiumRequired() {
if (!this.canAccessPremium) {
this.messagingService.send("premiumRequired");
return;
}
}
edit = async (details: GranteeEmergencyAccess) => {
const dialogRef = EmergencyAccessAddEditComponent.open(this.dialogService, {
data: {
name: this.userNamePipe.transform(details),
emergencyAccessId: details?.id,
readOnly: !this.canAccessPremium,
},
});
const result = await lastValueFrom(dialogRef.closed);
if (result === EmergencyAccessAddEditDialogResult.Saved) {
await this.load();
} else if (result === EmergencyAccessAddEditDialogResult.Deleted) {
await this.remove(details);
}
};
invite = async () => {
await this.edit(null);
};
async reinvite(contact: GranteeEmergencyAccess) {
if (this.actionPromise != null) {
return;
}
this.actionPromise = this.emergencyAccessService.reinvite(contact.id);
await this.actionPromise;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("hasBeenReinvited", contact.email),
);
this.actionPromise = null;
}
async confirm(contact: GranteeEmergencyAccess) {
function updateUser() {
contact.status = EmergencyAccessStatusType.Confirmed;
}
if (this.actionPromise != null) {
return;
}
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
if (autoConfirm == null || !autoConfirm) {
const dialogRef = EmergencyAccessConfirmComponent.open(this.dialogService, {
data: {
name: this.userNamePipe.transform(contact),
emergencyAccessId: contact.id,
userId: contact?.granteeId,
},
});
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;
}
this.actionPromise = this.emergencyAccessService.confirm(contact.id, contact.granteeId);
await this.actionPromise;
updateUser();
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(contact)),
);
this.actionPromise = null;
}
async remove(details: GranteeEmergencyAccess | GrantorEmergencyAccess) {
const confirmed = await this.dialogService.openSimpleDialog({
title: this.userNamePipe.transform(details),
content: { key: "removeUserConfirmation" },
type: "warning",
});
if (!confirmed) {
return false;
}
try {
await this.emergencyAccessService.delete(details.id);
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("removedUserId", this.userNamePipe.transform(details)),
);
if (details instanceof GranteeEmergencyAccess) {
this.removeGrantee(details);
} else {
this.removeGrantor(details);
}
} catch (e) {
this.logService.error(e);
}
}
async requestAccess(details: GrantorEmergencyAccess) {
const confirmed = await this.dialogService.openSimpleDialog({
title: this.userNamePipe.transform(details),
content: {
key: "requestAccessConfirmation",
placeholders: [details.waitTimeDays.toString()],
},
acceptButtonText: { key: "requestAccess" },
type: "warning",
});
if (!confirmed) {
return false;
}
await this.emergencyAccessService.requestAccess(details.id);
details.status = EmergencyAccessStatusType.RecoveryInitiated;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("requestSent", this.userNamePipe.transform(details)),
);
}
async approve(details: GranteeEmergencyAccess) {
const type = this.i18nService.t(
details.type === EmergencyAccessType.View ? "view" : "takeover",
);
const confirmed = await this.dialogService.openSimpleDialog({
title: this.userNamePipe.transform(details),
content: {
key: "approveAccessConfirmation",
placeholders: [this.userNamePipe.transform(details), type],
},
acceptButtonText: { key: "approve" },
type: "warning",
});
if (!confirmed) {
return false;
}
await this.emergencyAccessService.approve(details.id);
details.status = EmergencyAccessStatusType.RecoveryApproved;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("emergencyApproved", this.userNamePipe.transform(details)),
);
}
async reject(details: GranteeEmergencyAccess) {
await this.emergencyAccessService.reject(details.id);
details.status = EmergencyAccessStatusType.Confirmed;
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("emergencyRejected", this.userNamePipe.transform(details)),
);
}
takeover = async (details: GrantorEmergencyAccess) => {
const dialogRef = EmergencyAccessTakeoverComponent.open(this.dialogService, {
data: {
name: this.userNamePipe.transform(details),
email: details.email,
emergencyAccessId: details.id ?? null,
},
});
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) {
const index = this.trustedContacts.indexOf(details);
if (index > -1) {
this.trustedContacts.splice(index, 1);
}
}
private removeGrantor(details: GrantorEmergencyAccess) {
const index = this.grantedContacts.indexOf(details);
if (index > -1) {
this.grantedContacts.splice(index, 1);
}
}
}