mirror of
https://github.com/bitwarden/browser
synced 2026-02-05 19:23:19 +00:00
move emergency access logic to service
This commit is contained in:
@@ -1,6 +1,157 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
|
||||
import { EmergencyAccessType } from "@bitwarden/common/auth/enums/emergency-access-type";
|
||||
import { EmergencyAccessAcceptRequest } from "@bitwarden/common/auth/models/request/emergency-access-accept.request";
|
||||
import { EmergencyAccessConfirmRequest } from "@bitwarden/common/auth/models/request/emergency-access-confirm.request";
|
||||
import { EmergencyAccessInviteRequest } from "@bitwarden/common/auth/models/request/emergency-access-invite.request";
|
||||
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { EmergencyAccessApiService } from "./emergency-access-api.service";
|
||||
|
||||
@Injectable()
|
||||
export class EmergencyAccessService {
|
||||
constructor() {}
|
||||
constructor(
|
||||
private emergencyAccessApiService: EmergencyAccessApiService,
|
||||
private apiService: ApiService,
|
||||
private cryptoService: CryptoService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Invites the email address to be an emergency contact
|
||||
* Step 1 of the 3 step setup flow
|
||||
* Performed by grantor
|
||||
* @param email email address of trusted emergency contact
|
||||
* @param type type of emergency access
|
||||
* @param waitTimeDays number of days to wait before granting access
|
||||
*/
|
||||
async invite(email: string, type: EmergencyAccessType, waitTimeDays: number): Promise<void> {
|
||||
const request = new EmergencyAccessInviteRequest();
|
||||
request.email = email.trim();
|
||||
request.type = type;
|
||||
request.waitTimeDays = waitTimeDays;
|
||||
|
||||
await this.emergencyAccessApiService.postEmergencyAccessInvite(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing emergency access
|
||||
* Performed by grantor
|
||||
* @param id emergency access id
|
||||
* @param type type of emergency access
|
||||
* @param waitTimeDays number of days to wait before granting access
|
||||
*/
|
||||
async update(id: string, type: EmergencyAccessType, waitTimeDays: number) {
|
||||
const request = new EmergencyAccessUpdateRequest();
|
||||
request.type = type;
|
||||
request.waitTimeDays = waitTimeDays;
|
||||
|
||||
await this.emergencyAccessApiService.putEmergencyAccess(id, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts an emergency access invitation
|
||||
* Step 2 of the 3 step setup flow
|
||||
* Performed by grantee
|
||||
* @param id emergency access id
|
||||
* @param token secret token provided in email
|
||||
*/
|
||||
async accept(id: string, token: string): Promise<void> {
|
||||
const request = new EmergencyAccessAcceptRequest();
|
||||
request.token = token;
|
||||
|
||||
await this.emergencyAccessApiService.postEmergencyAccessAccept(id, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts user key with grantee's public key and sends to bitwarden
|
||||
* Step 3 of the 3 step setup flow
|
||||
* Performed by grantor
|
||||
* @param id emergency access id
|
||||
* @param token secret token provided in email
|
||||
*/
|
||||
async confirm(id: string, granteeId: string) {
|
||||
const userKey = await this.cryptoService.getUserKey();
|
||||
if (!userKey) {
|
||||
throw new Error("No user key found");
|
||||
}
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(granteeId);
|
||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||
|
||||
try {
|
||||
this.logService.debug(
|
||||
"User's fingerprint: " +
|
||||
(await this.cryptoService.getFingerprint(granteeId, publicKey)).join("-")
|
||||
);
|
||||
} catch {
|
||||
// Ignore errors since it's just a debug message
|
||||
}
|
||||
|
||||
const request = new EmergencyAccessConfirmRequest();
|
||||
request.key = await this.encryptKey(userKey, publicKey);
|
||||
await this.emergencyAccessApiService.postEmergencyAccessConfirm(id, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests access to grantor's vault
|
||||
* Performed by grantee
|
||||
* @param id emergency access id
|
||||
*/
|
||||
requestAccess(id: string): Promise<void> {
|
||||
return this.emergencyAccessApiService.postEmergencyAccessInitiate(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Approves access to grantor's vault
|
||||
* Performed by grantor
|
||||
* @param id emergency access id
|
||||
*/
|
||||
approve(id: string): Promise<void> {
|
||||
return this.emergencyAccessApiService.postEmergencyAccessApprove(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects access to grantor's vault
|
||||
* Performed by grantor
|
||||
* @param id emergency access id
|
||||
*/
|
||||
reject(id: string): Promise<void> {
|
||||
return this.emergencyAccessApiService.postEmergencyAccessReject(id);
|
||||
}
|
||||
|
||||
async rotateEmergencyAccess(newUserKey: UserKey) {
|
||||
const emergencyAccess = await this.emergencyAccessApiService.getEmergencyAccessTrusted();
|
||||
// Any Invited or Accepted requests won't have the key yet, so we don't need to update them
|
||||
const allowedStatuses = new Set([
|
||||
EmergencyAccessStatusType.Confirmed,
|
||||
EmergencyAccessStatusType.RecoveryInitiated,
|
||||
EmergencyAccessStatusType.RecoveryApproved,
|
||||
]);
|
||||
const filteredAccesses = emergencyAccess.data.filter((d) => allowedStatuses.has(d.status));
|
||||
|
||||
for (const details of filteredAccesses) {
|
||||
// Get public key of grantee
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
|
||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||
|
||||
// Encrypt new user key with public key
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(newUserKey.key, publicKey);
|
||||
|
||||
const updateRequest = new EmergencyAccessUpdateRequest();
|
||||
updateRequest.type = details.type;
|
||||
updateRequest.waitTimeDays = details.waitTimeDays;
|
||||
updateRequest.keyEncrypted = encryptedKey.encryptedString;
|
||||
|
||||
await this.emergencyAccessApiService.putEmergencyAccess(details.id, updateRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private async encryptKey(userKey: UserKey, publicKey: Uint8Array): Promise<EncryptedString> {
|
||||
return (await this.cryptoService.rsaEncrypt(userKey.key, publicKey)).encryptedString;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute, Params, Router } from "@angular/router";
|
||||
|
||||
import { EmergencyAccessAcceptRequest } from "@bitwarden/common/auth/models/request/emergency-access-accept.request";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { BaseAcceptComponent } from "../../common/base.accept.component";
|
||||
import { StateService } from "../../core";
|
||||
import { I18nService } from "../../core/i18n.service";
|
||||
import { EmergencyAccessApiService } from "../core/services/emergency-access/emergency-access-api.service";
|
||||
import { EmergencyAccessService } from "../core/services/emergency-access/emergency-access.service";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -25,19 +24,14 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent {
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
private emergencyAccessApiService: EmergencyAccessApiService,
|
||||
stateService: StateService
|
||||
stateService: StateService,
|
||||
private emergencyAccessService: EmergencyAccessService
|
||||
) {
|
||||
super(router, platformUtilsService, i18nService, route, stateService);
|
||||
}
|
||||
|
||||
async authedHandler(qParams: Params): Promise<void> {
|
||||
const request = new EmergencyAccessAcceptRequest();
|
||||
request.token = qParams.token;
|
||||
this.actionPromise = this.emergencyAccessApiService.postEmergencyAccessAccept(
|
||||
qParams.id,
|
||||
request
|
||||
);
|
||||
this.actionPromise = this.emergencyAccessService.accept(qParams.id, qParams.token);
|
||||
await this.actionPromise;
|
||||
await this.stateService.setEmergencyAccessInvitation(null);
|
||||
this.platformUtilService.showToast(
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
|
||||
import { EmergencyAccessType } from "@bitwarden/common/auth/enums/emergency-access-type";
|
||||
import { EmergencyAccessInviteRequest } from "@bitwarden/common/auth/models/request/emergency-access-invite.request";
|
||||
import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { EmergencyAccessApiService } from "../../core/services/emergency-access/emergency-access-api.service";
|
||||
import { EmergencyAccessService } from "../../core/services/emergency-access/emergency-access.service";
|
||||
|
||||
@Component({
|
||||
selector: "emergency-access-add-edit",
|
||||
@@ -33,6 +32,7 @@ export class EmergencyAccessAddEditComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private emergencyAccessApiService: EmergencyAccessApiService,
|
||||
private emergencyAccessService: EmergencyAccessService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService
|
||||
@@ -73,21 +73,9 @@ export class EmergencyAccessAddEditComponent implements OnInit {
|
||||
async submit() {
|
||||
try {
|
||||
if (this.editMode) {
|
||||
const request = new EmergencyAccessUpdateRequest();
|
||||
request.type = this.type;
|
||||
request.waitTimeDays = this.waitTime;
|
||||
|
||||
this.formPromise = this.emergencyAccessApiService.putEmergencyAccess(
|
||||
this.emergencyAccessId,
|
||||
request
|
||||
);
|
||||
await this.emergencyAccessService.update(this.emergencyAccessId, this.type, this.waitTime);
|
||||
} else {
|
||||
const request = new EmergencyAccessInviteRequest();
|
||||
request.email = this.email.trim();
|
||||
request.type = this.type;
|
||||
request.waitTimeDays = this.waitTime;
|
||||
|
||||
this.formPromise = this.emergencyAccessApiService.postEmergencyAccessInvite(request);
|
||||
await this.emergencyAccessService.invite(this.email, this.type, this.waitTime);
|
||||
}
|
||||
|
||||
await this.formPromise;
|
||||
|
||||
@@ -2,24 +2,21 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
|
||||
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 { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type";
|
||||
import { EmergencyAccessType } from "@bitwarden/common/auth/enums/emergency-access-type";
|
||||
import { EmergencyAccessConfirmRequest } from "@bitwarden/common/auth/models/request/emergency-access-confirm.request";
|
||||
import {
|
||||
EmergencyAccessGranteeDetailsResponse,
|
||||
EmergencyAccessGrantorDetailsResponse,
|
||||
} from "@bitwarden/common/auth/models/response/emergency-access.response";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { EmergencyAccessApiService } from "../../core/services/emergency-access/emergency-access-api.service";
|
||||
import { EmergencyAccessService } from "../../core/services/emergency-access/emergency-access.service";
|
||||
|
||||
import { EmergencyAccessAddEditComponent } from "./emergency-access-add-edit.component";
|
||||
import { EmergencyAccessConfirmComponent } from "./emergency-access-confirm.component";
|
||||
@@ -48,11 +45,10 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private emergencyAccessApiService: EmergencyAccessApiService,
|
||||
private apiService: ApiService,
|
||||
private emergencyAccessService: EmergencyAccessService,
|
||||
private i18nService: I18nService,
|
||||
private modalService: ModalService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private cryptoService: CryptoService,
|
||||
private messagingService: MessagingService,
|
||||
private userNamePipe: UserNamePipe,
|
||||
private logService: LogService,
|
||||
@@ -143,7 +139,7 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
comp.onConfirmed.subscribe(async () => {
|
||||
modal.close();
|
||||
|
||||
comp.formPromise = this.doConfirmation(contact);
|
||||
comp.formPromise = this.emergencyAccessService.confirm(contact.id, contact.granteeId);
|
||||
await comp.formPromise;
|
||||
|
||||
updateUser();
|
||||
@@ -158,7 +154,7 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
this.actionPromise = this.doConfirmation(contact);
|
||||
this.actionPromise = this.emergencyAccessService.confirm(contact.id, contact.granteeId);
|
||||
await this.actionPromise;
|
||||
updateUser();
|
||||
|
||||
@@ -216,7 +212,7 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.emergencyAccessApiService.postEmergencyAccessInitiate(details.id);
|
||||
await this.emergencyAccessService.requestAccess(details.id);
|
||||
|
||||
details.status = EmergencyAccessStatusType.RecoveryInitiated;
|
||||
this.platformUtilsService.showToast(
|
||||
@@ -245,7 +241,7 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.emergencyAccessApiService.postEmergencyAccessApprove(details.id);
|
||||
await this.emergencyAccessService.approve(details.id);
|
||||
details.status = EmergencyAccessStatusType.RecoveryApproved;
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
@@ -256,7 +252,7 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
}
|
||||
|
||||
async reject(details: EmergencyAccessGranteeDetailsResponse) {
|
||||
await this.emergencyAccessApiService.postEmergencyAccessReject(details.id);
|
||||
await this.emergencyAccessService.reject(details.id);
|
||||
details.status = EmergencyAccessStatusType.Confirmed;
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
@@ -301,28 +297,4 @@ export class EmergencyAccessComponent implements OnInit {
|
||||
this.grantedContacts.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt the user key with the grantees public key, and send it to bitwarden for escrow.
|
||||
private async doConfirmation(details: EmergencyAccessGranteeDetailsResponse) {
|
||||
const userKey = await this.cryptoService.getUserKey();
|
||||
if (!userKey) {
|
||||
throw new Error("No user key found");
|
||||
}
|
||||
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
|
||||
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
|
||||
|
||||
try {
|
||||
this.logService.debug(
|
||||
"User's fingerprint: " +
|
||||
(await this.cryptoService.getFingerprint(details.granteeId, publicKey)).join("-")
|
||||
);
|
||||
} catch {
|
||||
// Ignore errors since it's just a debug message
|
||||
}
|
||||
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey);
|
||||
const request = new EmergencyAccessConfirmRequest();
|
||||
request.key = encryptedKey.encryptedString;
|
||||
await this.emergencyAccessApiService.postEmergencyAccessConfirm(details.id, request);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user