1
0
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:
Jacob Fink
2023-10-05 09:31:04 -04:00
parent d616a5f77d
commit eec0082eef
4 changed files with 167 additions and 62 deletions

View File

@@ -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;
}
}

View File

@@ -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(

View File

@@ -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;

View File

@@ -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);
}
}