2021-08-27 14:50:58 +02:00
|
|
|
import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
2020-12-22 16:57:44 +01:00
|
|
|
|
2022-06-14 17:10:53 +02:00
|
|
|
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
|
|
|
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
|
|
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
|
|
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
|
|
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
|
|
|
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
|
|
|
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
|
|
|
import { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
|
|
|
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
|
|
|
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
|
|
|
import { EmergencyAccessStatusType } from "@bitwarden/common/enums/emergencyAccessStatusType";
|
|
|
|
import { EmergencyAccessType } from "@bitwarden/common/enums/emergencyAccessType";
|
|
|
|
import { Utils } from "@bitwarden/common/misc/utils";
|
|
|
|
import { EmergencyAccessConfirmRequest } from "@bitwarden/common/models/request/emergencyAccessConfirmRequest";
|
2021-12-14 17:10:26 +01:00
|
|
|
import {
|
|
|
|
EmergencyAccessGranteeDetailsResponse,
|
|
|
|
EmergencyAccessGrantorDetailsResponse,
|
2022-06-14 17:10:53 +02:00
|
|
|
} from "@bitwarden/common/models/response/emergencyAccessResponse";
|
2020-12-22 16:57:44 +01:00
|
|
|
|
|
|
|
import { EmergencyAccessAddEditComponent } from "./emergency-access-add-edit.component";
|
|
|
|
import { EmergencyAccessConfirmComponent } from "./emergency-access-confirm.component";
|
|
|
|
import { EmergencyAccessTakeoverComponent } from "./emergency-access-takeover.component";
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: "emergency-access",
|
|
|
|
templateUrl: "emergency-access.component.html",
|
|
|
|
})
|
2022-08-26 18:09:28 +02:00
|
|
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
2020-12-22 16:57:44 +01:00
|
|
|
export class EmergencyAccessComponent implements OnInit {
|
|
|
|
@ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef;
|
2021-12-14 17:10:26 +01:00
|
|
|
@ViewChild("takeoverTemplate", { read: ViewContainerRef, static: true })
|
|
|
|
takeoverModalRef: ViewContainerRef;
|
2020-12-22 16:57:44 +01:00
|
|
|
@ViewChild("confirmTemplate", { read: ViewContainerRef, static: true })
|
|
|
|
confirmModalRef: ViewContainerRef;
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2022-06-08 22:17:46 +02:00
|
|
|
loaded = false;
|
2020-12-22 16:57:44 +01:00
|
|
|
canAccessPremium: boolean;
|
|
|
|
trustedContacts: EmergencyAccessGranteeDetailsResponse[];
|
|
|
|
grantedContacts: EmergencyAccessGrantorDetailsResponse[];
|
|
|
|
emergencyAccessType = EmergencyAccessType;
|
|
|
|
emergencyAccessStatusType = EmergencyAccessStatusType;
|
|
|
|
actionPromise: Promise<any>;
|
2021-02-12 00:58:22 +01:00
|
|
|
isOrganizationOwner: boolean;
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2021-12-14 17:10:26 +01:00
|
|
|
constructor(
|
|
|
|
private apiService: ApiService,
|
|
|
|
private i18nService: I18nService,
|
|
|
|
private modalService: ModalService,
|
|
|
|
private platformUtilsService: PlatformUtilsService,
|
2021-12-07 20:41:45 +01:00
|
|
|
private cryptoService: CryptoService,
|
2021-12-14 17:10:26 +01:00
|
|
|
private messagingService: MessagingService,
|
|
|
|
private userNamePipe: UserNamePipe,
|
|
|
|
private logService: LogService,
|
|
|
|
private stateService: StateService,
|
|
|
|
private organizationService: OrganizationService
|
|
|
|
) {}
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
async ngOnInit() {
|
2021-12-14 17:10:26 +01:00
|
|
|
this.canAccessPremium = await this.stateService.getCanAccessPremium();
|
|
|
|
const orgs = await this.organizationService.getAll();
|
2021-02-12 00:58:22 +01:00
|
|
|
this.isOrganizationOwner = orgs.some((o) => o.isOwner);
|
2020-12-22 16:57:44 +01:00
|
|
|
this.load();
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
async load() {
|
|
|
|
this.trustedContacts = (await this.apiService.getEmergencyAccessTrusted()).data;
|
|
|
|
this.grantedContacts = (await this.apiService.getEmergencyAccessGranted()).data;
|
2022-06-08 22:17:46 +02:00
|
|
|
this.loaded = true;
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
async premiumRequired() {
|
|
|
|
if (!this.canAccessPremium) {
|
|
|
|
this.messagingService.send("premiumRequired");
|
2021-12-17 15:57:11 +01:00
|
|
|
return;
|
2020-12-22 16:57:44 +01:00
|
|
|
}
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
|
2021-08-27 14:50:58 +02:00
|
|
|
async edit(details: EmergencyAccessGranteeDetailsResponse) {
|
|
|
|
const [modal] = await this.modalService.openViewRef(
|
|
|
|
EmergencyAccessAddEditComponent,
|
|
|
|
this.addEditModalRef,
|
|
|
|
(comp) => {
|
|
|
|
comp.name = this.userNamePipe.transform(details);
|
|
|
|
comp.emergencyAccessId = details?.id;
|
|
|
|
comp.readOnly = !this.canAccessPremium;
|
2022-08-26 18:09:28 +02:00
|
|
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
2021-08-27 14:50:58 +02:00
|
|
|
comp.onSaved.subscribe(() => {
|
|
|
|
modal.close();
|
|
|
|
this.load();
|
2021-12-17 15:57:11 +01:00
|
|
|
});
|
2022-08-26 18:09:28 +02:00
|
|
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
2021-08-27 14:50:58 +02:00
|
|
|
comp.onDeleted.subscribe(() => {
|
|
|
|
modal.close();
|
|
|
|
this.remove(details);
|
2021-12-17 15:57:11 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2020-12-22 16:57:44 +01:00
|
|
|
|
|
|
|
invite() {
|
|
|
|
this.edit(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
async reinvite(contact: EmergencyAccessGranteeDetailsResponse) {
|
|
|
|
if (this.actionPromise != null) {
|
|
|
|
return;
|
|
|
|
}
|
2021-08-27 14:50:58 +02:00
|
|
|
this.actionPromise = this.apiService.postEmergencyAccessReinvite(contact.id);
|
|
|
|
await this.actionPromise;
|
|
|
|
this.platformUtilsService.showToast(
|
2021-12-17 15:57:11 +01:00
|
|
|
"success",
|
|
|
|
null,
|
2021-08-27 14:50:58 +02:00
|
|
|
this.i18nService.t("hasBeenReinvited", contact.email)
|
|
|
|
);
|
|
|
|
this.actionPromise = null;
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
|
2021-08-27 14:50:58 +02:00
|
|
|
async confirm(contact: EmergencyAccessGranteeDetailsResponse) {
|
|
|
|
function updateUser() {
|
|
|
|
contact.status = EmergencyAccessStatusType.Confirmed;
|
2020-12-22 16:57:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this.actionPromise != null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const autoConfirm = await this.stateService.getAutoConfirmFingerPrints();
|
|
|
|
if (autoConfirm == null || !autoConfirm) {
|
|
|
|
const [modal] = await this.modalService.openViewRef(
|
|
|
|
EmergencyAccessConfirmComponent,
|
|
|
|
this.confirmModalRef,
|
|
|
|
(comp) => {
|
2021-08-27 14:50:58 +02:00
|
|
|
comp.name = this.userNamePipe.transform(contact);
|
2020-12-22 16:57:44 +01:00
|
|
|
comp.emergencyAccessId = contact.id;
|
|
|
|
comp.userId = contact?.granteeId;
|
2022-08-26 18:09:28 +02:00
|
|
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
2020-12-22 16:57:44 +01:00
|
|
|
comp.onConfirmed.subscribe(async () => {
|
2021-08-27 14:50:58 +02:00
|
|
|
modal.close();
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
comp.formPromise = this.doConfirmation(contact);
|
2021-08-27 14:50:58 +02:00
|
|
|
await comp.formPromise;
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
updateUser();
|
2021-12-07 20:41:45 +01:00
|
|
|
this.platformUtilsService.showToast(
|
2021-12-17 15:57:11 +01:00
|
|
|
"success",
|
2020-12-22 16:57:44 +01:00
|
|
|
null,
|
|
|
|
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(contact))
|
|
|
|
);
|
2021-12-17 15:57:11 +01:00
|
|
|
});
|
2020-12-22 16:57:44 +01:00
|
|
|
}
|
|
|
|
);
|
2021-12-07 20:41:45 +01:00
|
|
|
return;
|
2020-12-22 16:57:44 +01:00
|
|
|
}
|
|
|
|
|
2021-12-14 17:10:26 +01:00
|
|
|
this.actionPromise = this.doConfirmation(contact);
|
2021-08-27 14:50:58 +02:00
|
|
|
await this.actionPromise;
|
|
|
|
updateUser();
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2021-08-27 14:50:58 +02:00
|
|
|
this.platformUtilsService.showToast(
|
|
|
|
"success",
|
|
|
|
null,
|
|
|
|
this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(contact))
|
|
|
|
);
|
|
|
|
this.actionPromise = null;
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
|
2021-08-27 14:50:58 +02:00
|
|
|
async remove(
|
2021-12-07 20:41:45 +01:00
|
|
|
details: EmergencyAccessGranteeDetailsResponse | EmergencyAccessGrantorDetailsResponse
|
2021-12-17 15:57:11 +01:00
|
|
|
) {
|
2021-12-07 20:41:45 +01:00
|
|
|
const confirmed = await this.platformUtilsService.showDialog(
|
|
|
|
this.i18nService.t("removeUserConfirmation"),
|
|
|
|
this.userNamePipe.transform(details),
|
|
|
|
this.i18nService.t("yes"),
|
2020-12-22 16:57:44 +01:00
|
|
|
this.i18nService.t("no"),
|
2021-12-17 15:57:11 +01:00
|
|
|
"warning"
|
|
|
|
);
|
2020-12-22 16:57:44 +01:00
|
|
|
if (!confirmed) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.apiService.deleteEmergencyAccess(details.id);
|
2021-12-07 20:41:45 +01:00
|
|
|
this.platformUtilsService.showToast(
|
|
|
|
"success",
|
|
|
|
null,
|
|
|
|
this.i18nService.t("removedUserId", this.userNamePipe.transform(details))
|
|
|
|
);
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
if (details instanceof EmergencyAccessGranteeDetailsResponse) {
|
|
|
|
this.removeGrantee(details);
|
|
|
|
} else {
|
|
|
|
this.removeGrantor(details);
|
|
|
|
}
|
2021-10-20 18:30:04 +02:00
|
|
|
} catch (e) {
|
|
|
|
this.logService.error(e);
|
2020-12-22 16:57:44 +01:00
|
|
|
}
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
async requestAccess(details: EmergencyAccessGrantorDetailsResponse) {
|
|
|
|
const confirmed = await this.platformUtilsService.showDialog(
|
|
|
|
this.i18nService.t("requestAccessConfirmation", details.waitTimeDays.toString()),
|
2021-07-19 10:47:34 +02:00
|
|
|
this.userNamePipe.transform(details),
|
|
|
|
this.i18nService.t("requestAccess"),
|
2020-12-22 16:57:44 +01:00
|
|
|
this.i18nService.t("no"),
|
2021-01-13 21:34:06 +01:00
|
|
|
"warning"
|
2020-12-22 16:57:44 +01:00
|
|
|
);
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
if (!confirmed) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.apiService.postEmergencyAccessInitiate(details.id);
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
details.status = EmergencyAccessStatusType.RecoveryInitiated;
|
|
|
|
this.platformUtilsService.showToast(
|
2021-12-17 15:57:11 +01:00
|
|
|
"success",
|
|
|
|
null,
|
2021-12-07 20:41:45 +01:00
|
|
|
this.i18nService.t("requestSent", this.userNamePipe.transform(details))
|
2021-12-17 15:57:11 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
async approve(details: EmergencyAccessGranteeDetailsResponse) {
|
|
|
|
const type = this.i18nService.t(
|
|
|
|
details.type === EmergencyAccessType.View ? "view" : "takeover"
|
2021-12-07 20:41:45 +01:00
|
|
|
);
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2021-12-07 20:41:45 +01:00
|
|
|
const confirmed = await this.platformUtilsService.showDialog(
|
|
|
|
this.i18nService.t("approveAccessConfirmation", this.userNamePipe.transform(details), type),
|
2021-07-19 10:47:34 +02:00
|
|
|
this.userNamePipe.transform(details),
|
2021-12-07 20:41:45 +01:00
|
|
|
this.i18nService.t("approve"),
|
2020-12-22 16:57:44 +01:00
|
|
|
this.i18nService.t("no"),
|
2021-12-17 15:57:11 +01:00
|
|
|
"warning"
|
|
|
|
);
|
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
if (!confirmed) {
|
2021-12-07 20:41:45 +01:00
|
|
|
return false;
|
2020-12-22 16:57:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
await this.apiService.postEmergencyAccessApprove(details.id);
|
|
|
|
details.status = EmergencyAccessStatusType.RecoveryApproved;
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2021-12-07 20:41:45 +01:00
|
|
|
this.platformUtilsService.showToast(
|
2021-12-17 15:57:11 +01:00
|
|
|
"success",
|
|
|
|
null,
|
2021-12-07 20:41:45 +01:00
|
|
|
this.i18nService.t("emergencyApproved", this.userNamePipe.transform(details))
|
2021-12-17 15:57:11 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
async reject(details: EmergencyAccessGranteeDetailsResponse) {
|
|
|
|
await this.apiService.postEmergencyAccessReject(details.id);
|
|
|
|
details.status = EmergencyAccessStatusType.Confirmed;
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2021-12-07 20:41:45 +01:00
|
|
|
this.platformUtilsService.showToast(
|
2021-12-17 15:57:11 +01:00
|
|
|
"success",
|
|
|
|
null,
|
2021-12-07 20:41:45 +01:00
|
|
|
this.i18nService.t("emergencyRejected", this.userNamePipe.transform(details))
|
2021-12-17 15:57:11 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
async takeover(details: EmergencyAccessGrantorDetailsResponse) {
|
2021-08-27 14:50:58 +02:00
|
|
|
const [modal] = await this.modalService.openViewRef(
|
|
|
|
EmergencyAccessTakeoverComponent,
|
|
|
|
this.takeoverModalRef,
|
|
|
|
(comp) => {
|
|
|
|
comp.name = this.userNamePipe.transform(details);
|
|
|
|
comp.email = details.email;
|
|
|
|
comp.emergencyAccessId = details != null ? details.id : null;
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2022-08-26 18:09:28 +02:00
|
|
|
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
2021-08-27 14:50:58 +02:00
|
|
|
comp.onDone.subscribe(() => {
|
|
|
|
modal.close();
|
2021-12-07 20:41:45 +01:00
|
|
|
this.platformUtilsService.showToast(
|
|
|
|
"success",
|
|
|
|
null,
|
|
|
|
this.i18nService.t("passwordResetFor", this.userNamePipe.transform(details))
|
2021-08-27 14:50:58 +02:00
|
|
|
);
|
2020-12-22 16:57:44 +01:00
|
|
|
});
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
private removeGrantee(details: EmergencyAccessGranteeDetailsResponse) {
|
|
|
|
const index = this.trustedContacts.indexOf(details);
|
|
|
|
if (index > -1) {
|
|
|
|
this.trustedContacts.splice(index, 1);
|
|
|
|
}
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
2020-12-22 16:57:44 +01:00
|
|
|
|
|
|
|
private removeGrantor(details: EmergencyAccessGrantorDetailsResponse) {
|
|
|
|
const index = this.grantedContacts.indexOf(details);
|
|
|
|
if (index > -1) {
|
|
|
|
this.grantedContacts.splice(index, 1);
|
|
|
|
}
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
|
2020-12-22 16:57:44 +01:00
|
|
|
// Encrypt the master password hash using the grantees public key, and send it to bitwarden for escrow.
|
|
|
|
private async doConfirmation(details: EmergencyAccessGranteeDetailsResponse) {
|
|
|
|
const encKey = await this.cryptoService.getEncKey();
|
|
|
|
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
|
|
|
|
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
2021-12-17 15:57:11 +01:00
|
|
|
|
|
|
|
try {
|
2021-10-20 18:30:04 +02:00
|
|
|
this.logService.debug(
|
|
|
|
"User's fingerprint: " +
|
2020-12-22 16:57:44 +01:00
|
|
|
(await this.cryptoService.getFingerprint(details.granteeId, publicKey.buffer)).join("-")
|
2021-12-17 15:57:11 +01:00
|
|
|
);
|
2020-12-22 16:57:44 +01:00
|
|
|
} catch {
|
2021-10-20 18:30:04 +02:00
|
|
|
// Ignore errors since it's just a debug message
|
2020-12-22 16:57:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
|
|
|
|
const request = new EmergencyAccessConfirmRequest();
|
|
|
|
request.key = encryptedKey.encryptedString;
|
|
|
|
await this.apiService.postEmergencyAccessConfirm(details.id, request);
|
|
|
|
}
|
|
|
|
}
|