From 11a5f1a8a276a8f3e7807ea86d9919b5519786b0 Mon Sep 17 00:00:00 2001 From: Jacob Fink Date: Thu, 5 Oct 2023 17:55:29 -0400 Subject: [PATCH] move takeover logic to service --- .../emergency-access.service.ts | 100 ++++++++++++++---- .../settings/change-password.component.ts | 4 +- .../emergency-access-takeover.component.ts | 53 +++------- .../emergency-access.component.ts | 6 +- 4 files changed, 95 insertions(+), 68 deletions(-) diff --git a/apps/web/src/app/auth/core/services/emergency-access/emergency-access.service.ts b/apps/web/src/app/auth/core/services/emergency-access/emergency-access.service.ts index 4e950e918f2..c9d0935e9b2 100644 --- a/apps/web/src/app/auth/core/services/emergency-access/emergency-access.service.ts +++ b/apps/web/src/app/auth/core/services/emergency-access/emergency-access.service.ts @@ -1,6 +1,7 @@ import { Injectable } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -25,6 +26,7 @@ import { EmergencyAccessApiService } from "./emergency-access-api.service"; import { EmergencyAccessAcceptRequest } from "./request/emergency-access-accept.request"; import { EmergencyAccessConfirmRequest } from "./request/emergency-access-confirm.request"; import { EmergencyAccessInviteRequest } from "./request/emergency-access-invite.request"; +import { EmergencyAccessPasswordRequest } from "./request/emergency-access-password.request"; import { EmergencyAccessUpdateRequest } from "./request/emergency-access-update.request"; @Injectable() @@ -39,23 +41,23 @@ export class EmergencyAccessService { ) {} /** - * Gets all emergency access that the user has been granted + * Gets all emergency access that the user has been granted. */ async getEmergencyAccessTrusted(): Promise { return (await this.emergencyAccessApiService.getEmergencyAccessTrusted()).data; } /** - * Gets all emergency access that the user has granted + * Gets all emergency access that the user has granted. */ async getEmergencyAccessGranted(): Promise { return (await this.emergencyAccessApiService.getEmergencyAccessGranted()).data; } /** - * Invites the email address to be an emergency contact - * Step 1 of the 3 step setup flow - * Intended for grantor + * Invites the email address to be an emergency contact. + * Step 1 of the 3 step setup flow. + * Intended for 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 @@ -70,8 +72,17 @@ export class EmergencyAccessService { } /** - * Edits an existing emergency access - * Intended for grantor + * Sends another email for an existing emergency access invitation. + * Intended for grantor. + * @param id emergency access id + */ + reinvite(id: string): Promise { + return this.emergencyAccessApiService.postEmergencyAccessReinvite(id); + } + + /** + * Edits an existing emergency access. + * Intended for grantor. * @param id emergency access id * @param type type of emergency access * @param waitTimeDays number of days to wait before granting access @@ -85,9 +96,9 @@ export class EmergencyAccessService { } /** - * Accepts an emergency access invitation - * Step 2 of the 3 step setup flow - * Intended for grantee + * Accepts an emergency access invitation. + * Step 2 of the 3 step setup flow. + * Intended for grantee. * @param id emergency access id * @param token secret token provided in email */ @@ -99,9 +110,9 @@ export class EmergencyAccessService { } /** - * Encrypts user key with grantee's public key and sends to bitwarden - * Step 3 of the 3 step setup flow - * Intended for grantor + * Encrypts user key with grantee's public key and sends to bitwarden. + * Step 3 of the 3 step setup flow. + * Intended for grantor. * @param id emergency access id * @param token secret token provided in email */ @@ -128,8 +139,17 @@ export class EmergencyAccessService { } /** - * Requests access to grantor's vault - * Intended for grantee + * Deletes an existing emergency access. + * Intended for either grantor or grantee. + * @param id emergency access id + */ + delete(id: string): Promise { + return this.emergencyAccessApiService.deleteEmergencyAccess(id); + } + + /** + * Requests access to grantor's vault. + * Intended for grantee. * @param id emergency access id */ requestAccess(id: string): Promise { @@ -137,8 +157,8 @@ export class EmergencyAccessService { } /** - * Approves access to grantor's vault - * Intended for grantor + * Approves access to grantor's vault. + * Intended for grantor. * @param id emergency access id */ approve(id: string): Promise { @@ -146,8 +166,8 @@ export class EmergencyAccessService { } /** - * Rejects access to grantor's vault - * Intended for grantor + * Rejects access to grantor's vault. + * Intended for grantor. * @param id emergency access id */ reject(id: string): Promise { @@ -155,8 +175,8 @@ export class EmergencyAccessService { } /** - * Gets the grantor ciphers for an emergency access in view mode - * Intended for grantee + * Gets the grantor ciphers for an emergency access in view mode. + * Intended for grantee. * @param id emergency access id */ async getViewOnlyCiphers(id: string): Promise { @@ -172,6 +192,44 @@ export class EmergencyAccessService { return ciphers.sort(this.cipherService.getLocaleSortingFunction()); } + /** + * Changes the password for an emergency access. + * Intended for grantee. + * @param id emergency access id + * @param masterPassword new master password + * @param email email address of grantee (must be consistent or login will fail) + */ + async takeover(id: string, masterPassword: string, email: string) { + const takeoverResponse = await this.emergencyAccessApiService.postEmergencyAccessTakeover(id); + + const grantorKeyBuffer = await this.cryptoService.rsaDecrypt(takeoverResponse.keyEncrypted); + const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey; + + if (grantorUserKey == null) { + throw new Error("Failed to decrypt grantor key"); + } + + const masterKey = await this.cryptoService.makeMasterKey( + masterPassword, + email, + takeoverResponse.kdf, + new KdfConfig( + takeoverResponse.kdfIterations, + takeoverResponse.kdfMemory, + takeoverResponse.kdfParallelism + ) + ); + const masterKeyHash = await this.cryptoService.hashMasterKey(masterPassword, masterKey); + + const encKey = await this.cryptoService.encryptUserKeyWithMasterKey(masterKey, grantorUserKey); + + const request = new EmergencyAccessPasswordRequest(); + request.newMasterPasswordHash = masterKeyHash; + request.key = encKey[1].encryptedString; + + this.emergencyAccessApiService.postEmergencyAccessPassword(id, request); + } + 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 diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index 9a313fa931b..bb3f11bbee3 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -12,8 +12,6 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { EmergencyAccessStatusType } from "@bitwarden/common/auth/enums/emergency-access-status-type"; -import { EmergencyAccessUpdateRequest } from "@bitwarden/common/auth/models/request/emergency-access-update.request"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request"; @@ -40,6 +38,8 @@ import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/ciph import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request"; import { DialogService } from "@bitwarden/components"; import { EmergencyAccessApiService } from "../core/services/emergency-access/emergency-access-api.service"; +import { EmergencyAccessStatusType } from "../core/enums/emergency-access-status-type"; +import { EmergencyAccessUpdateRequest } from "../core/services/emergency-access/request/emergency-access-update.request"; @Component({ selector: "app-change-password", diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access-takeover.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access-takeover.component.ts index c38d02f7de4..6533481abbd 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access-takeover.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access-takeover.component.ts @@ -6,8 +6,6 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; -import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; -import { EmergencyAccessPasswordRequest } from "@bitwarden/common/auth/models/request/emergency-access-password.request"; import { KdfType } from "@bitwarden/common/enums"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -15,13 +13,11 @@ 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 { - SymmetricCryptoKey, - UserKey, -} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; 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"; @Component({ selector: "emergency-access-takeover", @@ -49,6 +45,7 @@ export class EmergencyAccessTakeoverComponent passwordGenerationService: PasswordGenerationServiceAbstraction, platformUtilsService: PlatformUtilsService, policyService: PolicyService, + private emergencyAccessService: EmergencyAccessService, private emergencyAccessApiService: EmergencyAccessApiService, private logService: LogService, dialogService: DialogService @@ -91,46 +88,20 @@ export class EmergencyAccessTakeoverComponent return; } - const takeoverResponse = await this.emergencyAccessApiService.postEmergencyAccessTakeover( - this.emergencyAccessId - ); - - const oldKeyBuffer = await this.cryptoService.rsaDecrypt(takeoverResponse.keyEncrypted); - const oldUserKey = new SymmetricCryptoKey(oldKeyBuffer) as UserKey; - - if (oldUserKey == null) { + try { + await this.emergencyAccessService.takeover( + this.emergencyAccessId, + this.masterPassword, + this.email + ); + this.onDone.emit(); + } catch (e) { + this.logService.error(e); this.platformUtilsService.showToast( "error", this.i18nService.t("errorOccurred"), this.i18nService.t("unexpectedError") ); - return; - } - - const masterKey = await this.cryptoService.makeMasterKey( - this.masterPassword, - this.email, - takeoverResponse.kdf, - new KdfConfig( - takeoverResponse.kdfIterations, - takeoverResponse.kdfMemory, - takeoverResponse.kdfParallelism - ) - ); - const masterKeyHash = await this.cryptoService.hashMasterKey(this.masterPassword, masterKey); - - const encKey = await this.cryptoService.encryptUserKeyWithMasterKey(masterKey, oldUserKey); - - const request = new EmergencyAccessPasswordRequest(); - request.newMasterPasswordHash = masterKeyHash; - request.key = encKey[1].encryptedString; - - this.emergencyAccessApiService.postEmergencyAccessPassword(this.emergencyAccessId, request); - - try { - this.onDone.emit(); - } catch (e) { - this.logService.error(e); } } } diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts index 9ffb0732ab2..acee518a9b3 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts @@ -12,7 +12,6 @@ import { DialogService } from "@bitwarden/components"; import { EmergencyAccessStatusType } from "../../core/enums/emergency-access-status-type"; import { EmergencyAccessType } from "../../core/enums/emergency-access-type"; -import { EmergencyAccessApiService } from "../../core/services/emergency-access/emergency-access-api.service"; import { EmergencyAccessService } from "../../core/services/emergency-access/emergency-access.service"; import { EmergencyAccessGranteeView, @@ -45,7 +44,6 @@ export class EmergencyAccessComponent implements OnInit { isOrganizationOwner: boolean; constructor( - private emergencyAccessApiService: EmergencyAccessApiService, private emergencyAccessService: EmergencyAccessService, private i18nService: I18nService, private modalService: ModalService, @@ -108,7 +106,7 @@ export class EmergencyAccessComponent implements OnInit { if (this.actionPromise != null) { return; } - this.actionPromise = this.emergencyAccessApiService.postEmergencyAccessReinvite(contact.id); + this.actionPromise = this.emergencyAccessService.reinvite(contact.id); await this.actionPromise; this.platformUtilsService.showToast( "success", @@ -179,7 +177,7 @@ export class EmergencyAccessComponent implements OnInit { } try { - await this.emergencyAccessApiService.deleteEmergencyAccess(details.id); + await this.emergencyAccessService.delete(details.id); this.platformUtilsService.showToast( "success", null,