diff --git a/apps/web/src/app/auth/core/services/emergency-access/emergency-access.service.spec.ts b/apps/web/src/app/auth/core/services/emergency-access/emergency-access.service.spec.ts index 7b54b497f99..a55869dbe41 100644 --- a/apps/web/src/app/auth/core/services/emergency-access/emergency-access.service.spec.ts +++ b/apps/web/src/app/auth/core/services/emergency-access/emergency-access.service.spec.ts @@ -1,16 +1,80 @@ import { MockProxy } from "jest-mock-extended"; +import mock from "jest-mock-extended/lib/Mock"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { EncryptionType, KdfType } from "@bitwarden/common/enums"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { UserKeyResponse } from "@bitwarden/common/models/response/user-key.response"; +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"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { + UserKey, + SymmetricCryptoKey, +} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "@bitwarden/common/types/csprng"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; + +import { EmergencyAccessStatusType } from "../../enums/emergency-access-status-type"; + import { EmergencyAccessApiService } from "./emergency-access-api.service"; import { EmergencyAccessService } from "./emergency-access.service"; +import { EmergencyAccessUpdateRequest } from "./request/emergency-access-update.request"; +import { + EmergencyAccessGranteeDetailsResponse, + EmergencyAccessTakeoverResponse, +} from "./response/emergency-access.response"; describe("EmergencyAccessService", () => { - let apiService!: MockProxy; + let emergencyAccessApiService: MockProxy; + let apiService: MockProxy; + let cryptoService: MockProxy; + let encryptService: MockProxy; + let cipherService: MockProxy; + let logService: MockProxy; let emergencyAccessService: EmergencyAccessService; beforeAll(() => { - // emergencyAccessService = new EmergencyAccessService(); + emergencyAccessApiService = mock(); + apiService = mock(); + cryptoService = mock(); + encryptService = mock(); + cipherService = mock(); + logService = mock(); + + emergencyAccessService = new EmergencyAccessService( + emergencyAccessApiService, + apiService, + cryptoService, + encryptService, + cipherService, + logService + ); }); - describe("updateEmergencyAccesses", () => { + describe("takeover", () => { + const mockId = "emergencyAccessId"; + const mockEmail = "emergencyAccessEmail"; + const mockName = "emergencyAccessName"; + + it("should not post a new password if decryption fails", async () => { + cryptoService.rsaDecrypt.mockResolvedValueOnce(null); + emergencyAccessApiService.postEmergencyAccessTakeover.mockResolvedValueOnce({ + keyEncrypted: "EncryptedKey", + kdf: KdfType.PBKDF2_SHA256, + kdfIterations: 500, + } as EmergencyAccessTakeoverResponse); + + await expect( + emergencyAccessService.takeover(mockId, mockEmail, mockName) + ).rejects.toThrowError("Failed to decrypt grantor key"); + + expect(emergencyAccessApiService.postEmergencyAccessPassword).not.toHaveBeenCalled(); + }); + }); + + describe("rotate", () => { let mockUserKey: UserKey; beforeEach(() => { @@ -40,7 +104,7 @@ describe("EmergencyAccessService", () => { }); it("Only updates emergency accesses with allowed statuses", async () => { - await migrateFromLegacyEncryptionService.updateEmergencyAccesses(mockUserKey); + await emergencyAccessService.rotate(mockUserKey); expect(emergencyAccessApiService.putEmergencyAccess).not.toHaveBeenCalledWith( "0", @@ -52,14 +116,17 @@ describe("EmergencyAccessService", () => { ); }); }); - - // describe("createCredential", () => { - // it("should return undefined when navigator.credentials throws", async () => { - // credentials.create.mockRejectedValue(new Error("Mocked error")); - // const options = createCredentialCreateOptions(); - - // const result = await webauthnService.createCredential(options); - - // expect(result).toBeUndefined(); - // }); }); + +function createMockEmergencyAccess( + id: string, + name: string, + status: EmergencyAccessStatusType +): EmergencyAccessGranteeDetailsResponse { + const emergencyAccess = new EmergencyAccessGranteeDetailsResponse({}); + emergencyAccess.id = id; + emergencyAccess.name = name; + emergencyAccess.type = 0; + emergencyAccess.status = status; + return emergencyAccess; +} 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 f237b2d52fe..47a0c150169 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 @@ -227,12 +227,12 @@ export class EmergencyAccessService { 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) { + if (grantorKeyBuffer == null) { throw new Error("Failed to decrypt grantor key"); } + const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey; + const masterKey = await this.cryptoService.makeMasterKey( masterPassword, email, @@ -275,12 +275,12 @@ export class EmergencyAccessService { const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); // Encrypt new user key with public key - const encryptedKey = await this.cryptoService.rsaEncrypt(newUserKey.key, publicKey); + const encryptedKey = await this.encryptKey(newUserKey, publicKey); const updateRequest = new EmergencyAccessUpdateRequest(); updateRequest.type = details.type; updateRequest.waitTimeDays = details.waitTimeDays; - updateRequest.keyEncrypted = encryptedKey.encryptedString; + updateRequest.keyEncrypted = encryptedKey; await this.emergencyAccessApiService.putEmergencyAccess(details.id, updateRequest); }