From adf7a38f874e74f764079b521a1a3e13bac57bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Mon, 20 May 2024 11:18:23 +0100 Subject: [PATCH] =?UTF-8?q?[AC-2302]=C2=A0Extract=20device=20approve/deny?= =?UTF-8?q?=20logic=20into=20a=20service=20(#8818)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [AC-2302] Move organization-auth-request.service to bit-common folder * [AC-2302] Rename organization-auth-request.service to organization-auth-request-api.service * [AC-2302] Move logic from component to organization-auth-request.service * [AC-2302] Fix import path in OrganizationAuthRequestService * [AC-2302] Move imports to OrganizationsModule and delete unused CoreOrganizationModule * [AC-2302] Move the call to get userResetPasswordDetails into OrganizationAuthRequestService * [AC-2302] Remove @Injectable() and manually configure dependencies * [AC-2302] Add OrganizationAuthRequestService unit tests first draft * [AC-2302] Refactor device-approvals.component.ts to remove unused imports * [AC-2302] Set up jest on bit-common and add unit tests for OrganizationAuthRequestService * [AC-2302] Add bit-common to jest.config.js * [AC-2302] Update organizations.module.ts to include safeProviders declared in variable * [AC-2302] Remove services and views folders from bit-common * [AC-2302] Define path mapping * Adjust an import path The import path of `PendingAuthRequestView` in `OrganizationAuthRequestApiService` was pointing to the wrong place. I think this file was just recently moved, and the import didn't get updated. * Get paths working * Fix import * Update jest config to use ts-jest adn jsdom * Copy-paste path mappings from bit-web * Remove unnecessary test setup file * Undo unnecessary change * Fix remaining path mappings * Remove Bitwarden License mapping from OSS code * Fix bit-web so it uses its own tsconfig * Fix import path * Remove web-bit entrypoint from OSS tsconfig * Make DeviceApprovalsComponent standalone * Remove organization-auth-request-api.service export * Remove OrganizationsRoutingModule from DeviceApprovalsComponent imports * Remove CoreOrganizationModule from OrganizationsModule imports * Remove NoItemsModule from OrganizationsModule imports * Use ApiService from JslibServicesModule * Update providers in device-approvals.component.ts --------- Co-authored-by: Addison Beck Co-authored-by: Thomas Rittson Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --- .eslintrc.json | 6 + apps/web/tsconfig.json | 7 +- bitwarden_license/bit-common/jest.config.js | 16 +++ .../admin-auth-request-update.request.ts | 0 .../bulk-deny-auth-requests.request.ts | 0 .../src/admin-console}/auth-requests/index.ts | 0 .../organization-auth-request-api.service.ts} | 8 +- .../organization-auth-request.service.spec.ts | 132 ++++++++++++++++++ .../organization-auth-request.service.ts | 81 +++++++++++ .../pending-auth-request.view.ts | 2 +- ...ding-organization-auth-request.response.ts | 0 bitwarden_license/bit-common/tsconfig.json | 24 ++++ .../bit-common/tsconfig.spec.json | 3 + .../core/core-organization.module.ts | 8 -- .../admin-console/organizations/core/index.ts | 1 - .../device-approvals.component.ts | 91 ++++-------- .../organizations-routing.module.ts | 6 +- .../organizations/organizations.module.ts | 12 +- bitwarden_license/bit-web/tsconfig.json | 30 +++- bitwarden_license/bit-web/webpack.config.js | 2 +- clients.code-workspace | 4 + jest.config.js | 1 + tsconfig.eslint.json | 3 +- tsconfig.json | 6 +- 24 files changed, 340 insertions(+), 103 deletions(-) create mode 100644 bitwarden_license/bit-common/jest.config.js rename bitwarden_license/{bit-web/src/app/admin-console/organizations/core/services => bit-common/src/admin-console}/auth-requests/admin-auth-request-update.request.ts (100%) rename bitwarden_license/{bit-web/src/app/admin-console/organizations/core/services => bit-common/src/admin-console}/auth-requests/bulk-deny-auth-requests.request.ts (100%) rename bitwarden_license/{bit-web/src/app/admin-console/organizations/core/services => bit-common/src/admin-console}/auth-requests/index.ts (100%) rename bitwarden_license/{bit-web/src/app/admin-console/organizations/core/services/auth-requests/organization-auth-request.service.ts => bit-common/src/admin-console/auth-requests/organization-auth-request-api.service.ts} (89%) create mode 100644 bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts create mode 100644 bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts rename bitwarden_license/{bit-web/src/app/admin-console/organizations/core/views => bit-common/src/admin-console/auth-requests}/pending-auth-request.view.ts (87%) rename bitwarden_license/{bit-web/src/app/admin-console/organizations/core/services => bit-common/src/admin-console}/auth-requests/pending-organization-auth-request.response.ts (100%) create mode 100644 bitwarden_license/bit-common/tsconfig.json create mode 100644 bitwarden_license/bit-common/tsconfig.spec.json delete mode 100644 bitwarden_license/bit-web/src/app/admin-console/organizations/core/core-organization.module.ts delete mode 100644 bitwarden_license/bit-web/src/app/admin-console/organizations/core/index.ts diff --git a/.eslintrc.json b/.eslintrc.json index 61bebbf483..2b52485689 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -262,6 +262,12 @@ } ] } + }, + { + "files": ["bitwarden_license/bit-common/src/**/*.ts"], + "rules": { + "no-restricted-imports": ["error", { "patterns": ["@bitwarden/bit-common/*", "src/**/*"] }] + } } ] } diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 543d7f25b1..07d244ee60 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -27,12 +27,7 @@ "strictTemplates": true, "preserveWhitespaces": true }, - "files": [ - "src/polyfills.ts", - "src/main.ts", - "../../bitwarden_license/bit-web/src/main.ts", - "src/theme.ts" - ], + "files": ["src/polyfills.ts", "src/main.ts", "src/theme.ts"], "include": [ "src/connectors/*.ts", "src/**/*.stories.ts", diff --git a/bitwarden_license/bit-common/jest.config.js b/bitwarden_license/bit-common/jest.config.js new file mode 100644 index 0000000000..d79f8ae619 --- /dev/null +++ b/bitwarden_license/bit-common/jest.config.js @@ -0,0 +1,16 @@ +const { pathsToModuleNameMapper } = require("ts-jest"); + +const { compilerOptions } = require("./tsconfig"); + +const sharedConfig = require("../../libs/shared/jest.config.angular"); + +/** @type {import('jest').Config} */ +module.exports = { + ...sharedConfig, + displayName: "bit-common tests", + preset: "ts-jest", + testEnvironment: "jsdom", + moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { + prefix: "/", + }), +}; diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/services/auth-requests/admin-auth-request-update.request.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/admin-auth-request-update.request.ts similarity index 100% rename from bitwarden_license/bit-web/src/app/admin-console/organizations/core/services/auth-requests/admin-auth-request-update.request.ts rename to bitwarden_license/bit-common/src/admin-console/auth-requests/admin-auth-request-update.request.ts diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/services/auth-requests/bulk-deny-auth-requests.request.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/bulk-deny-auth-requests.request.ts similarity index 100% rename from bitwarden_license/bit-web/src/app/admin-console/organizations/core/services/auth-requests/bulk-deny-auth-requests.request.ts rename to bitwarden_license/bit-common/src/admin-console/auth-requests/bulk-deny-auth-requests.request.ts diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/services/auth-requests/index.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/index.ts similarity index 100% rename from bitwarden_license/bit-web/src/app/admin-console/organizations/core/services/auth-requests/index.ts rename to bitwarden_license/bit-common/src/admin-console/auth-requests/index.ts diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/services/auth-requests/organization-auth-request.service.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request-api.service.ts similarity index 89% rename from bitwarden_license/bit-web/src/app/admin-console/organizations/core/services/auth-requests/organization-auth-request.service.ts rename to bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request-api.service.ts index d9de9fe297..9b8e4c246d 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/services/auth-requests/organization-auth-request.service.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request-api.service.ts @@ -1,17 +1,13 @@ -import { Injectable } from "@angular/core"; - import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { PendingAuthRequestView } from "../../views/pending-auth-request.view"; - import { AdminAuthRequestUpdateRequest } from "./admin-auth-request-update.request"; import { BulkDenyAuthRequestsRequest } from "./bulk-deny-auth-requests.request"; +import { PendingAuthRequestView } from "./pending-auth-request.view"; import { PendingOrganizationAuthRequestResponse } from "./pending-organization-auth-request.response"; -@Injectable() -export class OrganizationAuthRequestService { +export class OrganizationAuthRequestApiService { constructor(private apiService: ApiService) {} async listPendingRequests(organizationId: string): Promise { diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts new file mode 100644 index 0000000000..24c6f06be3 --- /dev/null +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts @@ -0,0 +1,132 @@ +import { MockProxy, mock } from "jest-mock-extended"; + +import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; +import { OrganizationUserResetPasswordDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; + +import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service"; +import { OrganizationAuthRequestService } from "./organization-auth-request.service"; +import { PendingAuthRequestView } from "./pending-auth-request.view"; + +describe("OrganizationAuthRequestService", () => { + let organizationAuthRequestApiService: MockProxy; + let cryptoService: MockProxy; + let organizationUserService: MockProxy; + let organizationAuthRequestService: OrganizationAuthRequestService; + + beforeEach(() => { + organizationAuthRequestApiService = mock(); + cryptoService = mock(); + organizationUserService = mock(); + organizationAuthRequestService = new OrganizationAuthRequestService( + organizationAuthRequestApiService, + cryptoService, + organizationUserService, + ); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("listPendingRequests", () => { + it("should return a list of pending auth requests", async () => { + jest.spyOn(organizationAuthRequestApiService, "listPendingRequests"); + + const organizationId = "organizationId"; + + const pendingAuthRequest = new PendingAuthRequestView(); + pendingAuthRequest.id = "requestId1"; + pendingAuthRequest.userId = "userId1"; + pendingAuthRequest.organizationUserId = "userId1"; + pendingAuthRequest.email = "email1"; + pendingAuthRequest.publicKey = "publicKey1"; + pendingAuthRequest.requestDeviceIdentifier = "requestDeviceIdentifier1"; + pendingAuthRequest.requestDeviceType = "requestDeviceType1"; + pendingAuthRequest.requestIpAddress = "requestIpAddress1"; + pendingAuthRequest.creationDate = new Date(); + const mockPendingAuthRequests = [pendingAuthRequest]; + organizationAuthRequestApiService.listPendingRequests + .calledWith(organizationId) + .mockResolvedValue(mockPendingAuthRequests); + + const result = await organizationAuthRequestService.listPendingRequests(organizationId); + + expect(result).toHaveLength(1); + expect(result).toEqual(mockPendingAuthRequests); + expect(organizationAuthRequestApiService.listPendingRequests).toHaveBeenCalledWith( + organizationId, + ); + }); + + it("should return an empty list", async () => { + jest.spyOn(organizationAuthRequestApiService, "listPendingRequests"); + + const invalidOrganizationId = "invalidOrganizationId"; + const result = + await organizationAuthRequestService.listPendingRequests("invalidOrganizationId"); + + expect(result).toBeUndefined(); + expect(organizationAuthRequestApiService.listPendingRequests).toHaveBeenCalledWith( + invalidOrganizationId, + ); + }); + }); + + describe("denyPendingRequests", () => { + it("should deny the specified pending auth requests", async () => { + jest.spyOn(organizationAuthRequestApiService, "denyPendingRequests"); + + await organizationAuthRequestService.denyPendingRequests( + "organizationId", + "requestId1", + "requestId2", + ); + + expect(organizationAuthRequestApiService.denyPendingRequests).toHaveBeenCalledWith( + "organizationId", + "requestId1", + "requestId2", + ); + }); + }); + + describe("approvePendingRequest", () => { + it("should approve the specified pending auth request", async () => { + jest.spyOn(organizationAuthRequestApiService, "approvePendingRequest"); + + const organizationId = "organizationId"; + + const organizationUserResetPasswordDetailsResponse = + new OrganizationUserResetPasswordDetailsResponse({ + resetPasswordKey: "resetPasswordKey", + encryptedPrivateKey: "encryptedPrivateKey", + }); + + organizationUserService.getOrganizationUserResetPasswordDetails.mockResolvedValue( + organizationUserResetPasswordDetailsResponse, + ); + + const encryptedUserKey = new EncString("encryptedUserKey"); + cryptoService.rsaDecrypt.mockResolvedValue(new Uint8Array(32)); + cryptoService.rsaEncrypt.mockResolvedValue(encryptedUserKey); + + const mockPendingAuthRequest = new PendingAuthRequestView(); + mockPendingAuthRequest.id = "requestId1"; + mockPendingAuthRequest.organizationUserId = "organizationUserId1"; + mockPendingAuthRequest.publicKey = "publicKey1"; + + await organizationAuthRequestService.approvePendingRequest( + organizationId, + mockPendingAuthRequest, + ); + + expect(organizationAuthRequestApiService.approvePendingRequest).toHaveBeenCalledWith( + organizationId, + mockPendingAuthRequest.id, + encryptedUserKey, + ); + }); + }); +}); diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts new file mode 100644 index 0000000000..efc2cdcedb --- /dev/null +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts @@ -0,0 +1,81 @@ +import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; +import { OrganizationUserResetPasswordDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; + +import { OrganizationAuthRequestApiService } from "./organization-auth-request-api.service"; +import { PendingAuthRequestView } from "./pending-auth-request.view"; + +export class OrganizationAuthRequestService { + constructor( + private organizationAuthRequestApiService: OrganizationAuthRequestApiService, + private cryptoService: CryptoService, + private organizationUserService: OrganizationUserService, + ) {} + + async listPendingRequests(organizationId: string): Promise { + return await this.organizationAuthRequestApiService.listPendingRequests(organizationId); + } + + async denyPendingRequests(organizationId: string, ...requestIds: string[]): Promise { + await this.organizationAuthRequestApiService.denyPendingRequests(organizationId, ...requestIds); + } + + async approvePendingRequest(organizationId: string, authRequest: PendingAuthRequestView) { + const details = await this.organizationUserService.getOrganizationUserResetPasswordDetails( + organizationId, + authRequest.organizationUserId, + ); + + if (details == null || details.resetPasswordKey == null) { + throw new Error( + "The user must be enrolled in account recovery (password reset) in order for the request to be approved.", + ); + } + + const encryptedKey = await this.getEncryptedUserKey( + organizationId, + authRequest.publicKey, + details, + ); + + await this.organizationAuthRequestApiService.approvePendingRequest( + organizationId, + authRequest.id, + encryptedKey, + ); + } + + /** + * Creates a copy of the user key that has been encrypted with the provided device's public key. + * @param organizationId + * @param devicePublicKey + * @param resetPasswordDetails + * @private + */ + private async getEncryptedUserKey( + organizationId: string, + devicePublicKey: string, + resetPasswordDetails: OrganizationUserResetPasswordDetailsResponse, + ): Promise { + const encryptedUserKey = resetPasswordDetails.resetPasswordKey; + const encryptedOrgPrivateKey = resetPasswordDetails.encryptedPrivateKey; + const devicePubKey = Utils.fromB64ToArray(devicePublicKey); + + // Decrypt Organization's encrypted Private Key with org key + const orgSymKey = await this.cryptoService.getOrgKey(organizationId); + const decOrgPrivateKey = await this.cryptoService.decryptToBytes( + new EncString(encryptedOrgPrivateKey), + orgSymKey, + ); + + // Decrypt user key with decrypted org private key + const decValue = await this.cryptoService.rsaDecrypt(encryptedUserKey, decOrgPrivateKey); + const userKey = new SymmetricCryptoKey(decValue); + + // Re-encrypt user Key with the Device Public Key + return await this.cryptoService.rsaEncrypt(userKey.key, devicePubKey); + } +} diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/views/pending-auth-request.view.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-auth-request.view.ts similarity index 87% rename from bitwarden_license/bit-web/src/app/admin-console/organizations/core/views/pending-auth-request.view.ts rename to bitwarden_license/bit-common/src/admin-console/auth-requests/pending-auth-request.view.ts index 8f3415a236..d9f3601352 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/views/pending-auth-request.view.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-auth-request.view.ts @@ -1,6 +1,6 @@ import { View } from "@bitwarden/common/models/view/view"; -import { PendingOrganizationAuthRequestResponse } from "../services/auth-requests"; +import { PendingOrganizationAuthRequestResponse } from "."; export class PendingAuthRequestView implements View { id: string; diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/services/auth-requests/pending-organization-auth-request.response.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/pending-organization-auth-request.response.ts similarity index 100% rename from bitwarden_license/bit-web/src/app/admin-console/organizations/core/services/auth-requests/pending-organization-auth-request.response.ts rename to bitwarden_license/bit-common/src/admin-console/auth-requests/pending-organization-auth-request.response.ts diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json new file mode 100644 index 0000000000..6b40d44741 --- /dev/null +++ b/bitwarden_license/bit-common/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../libs/shared/tsconfig.libs", + "include": ["src", "spec"], + "exclude": ["node_modules", "dist"], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@bitwarden/admin-console": ["../../libs/admin-console/src"], + "@bitwarden/angular/*": ["../../libs/angular/src/*"], + "@bitwarden/auth": ["../../libs/auth/src"], + "@bitwarden/billing": ["../../libs/billing/src"], + "@bitwarden/common/*": ["../../libs/common/src/*"], + "@bitwarden/components": ["../../libs/components/src"], + "@bitwarden/vault-export-core": [ + "../../libs/tools/export/vault-export/vault-export-core/src" + ], + "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-core/src"], + "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/vault": ["../../libs/vault/src"], + "@bitwarden/web-vault/*": ["../../apps/web/src/*"], + "@bitwarden/bit-common/*": ["../bit-common/src/*"] + } + } +} diff --git a/bitwarden_license/bit-common/tsconfig.spec.json b/bitwarden_license/bit-common/tsconfig.spec.json new file mode 100644 index 0000000000..fc8520e737 --- /dev/null +++ b/bitwarden_license/bit-common/tsconfig.spec.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/core-organization.module.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/core/core-organization.module.ts deleted file mode 100644 index bba3bfce93..0000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/core-organization.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NgModule } from "@angular/core"; - -import { OrganizationAuthRequestService } from "./services/auth-requests"; - -@NgModule({ - providers: [OrganizationAuthRequestService], -}) -export class CoreOrganizationModule {} diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/index.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/core/index.ts deleted file mode 100644 index 4d758be8c6..0000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/core/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./core-organization.module"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts index dec65df9fb..a1ffd91a1c 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts @@ -2,25 +2,37 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { BehaviorSubject, Subject, switchMap, takeUntil, tap } from "rxjs"; +import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; +import { OrganizationAuthRequestApiService } from "@bitwarden/bit-common/admin-console/auth-requests/organization-auth-request-api.service"; +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests/organization-auth-request.service"; +import { PendingAuthRequestView } from "@bitwarden/bit-common/admin-console/auth-requests/pending-auth-request.view"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserResetPasswordDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; 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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { TableDataSource } from "@bitwarden/components"; +import { TableDataSource, NoItemsModule } from "@bitwarden/components"; import { Devices } from "@bitwarden/web-vault/app/admin-console/icons"; - -import { OrganizationAuthRequestService } from "../../core/services/auth-requests"; -import { PendingAuthRequestView } from "../../core/views/pending-auth-request.view"; +import { LooseComponentsModule } from "@bitwarden/web-vault/app/shared"; +import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module"; @Component({ selector: "app-org-device-approvals", templateUrl: "./device-approvals.component.html", + standalone: true, + providers: [ + safeProvider({ + provide: OrganizationAuthRequestApiService, + deps: [ApiService], + }), + safeProvider({ + provide: OrganizationAuthRequestService, + deps: [OrganizationAuthRequestApiService, CryptoService, OrganizationUserService], + }), + ] satisfies SafeProvider[], + imports: [SharedModule, NoItemsModule, LooseComponentsModule], }) export class DeviceApprovalsComponent implements OnInit, OnDestroy { tableDataSource = new TableDataSource(); @@ -35,8 +47,6 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy { constructor( private organizationAuthRequestService: OrganizationAuthRequestService, - private organizationUserService: OrganizationUserService, - private cryptoService: CryptoService, private route: ActivatedRoute, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, @@ -64,65 +74,26 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy { }); } - /** - * Creates a copy of the user key that has been encrypted with the provided device's public key. - * @param devicePublicKey - * @param resetPasswordDetails - * @private - */ - private async getEncryptedUserKey( - devicePublicKey: string, - resetPasswordDetails: OrganizationUserResetPasswordDetailsResponse, - ): Promise { - const encryptedUserKey = resetPasswordDetails.resetPasswordKey; - const encryptedOrgPrivateKey = resetPasswordDetails.encryptedPrivateKey; - const devicePubKey = Utils.fromB64ToArray(devicePublicKey); - - // Decrypt Organization's encrypted Private Key with org key - const orgSymKey = await this.cryptoService.getOrgKey(this.organizationId); - const decOrgPrivateKey = await this.cryptoService.decryptToBytes( - new EncString(encryptedOrgPrivateKey), - orgSymKey, - ); - - // Decrypt user key with decrypted org private key - const decValue = await this.cryptoService.rsaDecrypt(encryptedUserKey, decOrgPrivateKey); - const userKey = new SymmetricCryptoKey(decValue); - - // Re-encrypt user Key with the Device Public Key - return await this.cryptoService.rsaEncrypt(userKey.key, devicePubKey); - } - async approveRequest(authRequest: PendingAuthRequestView) { await this.performAsyncAction(async () => { - const details = await this.organizationUserService.getOrganizationUserResetPasswordDetails( - this.organizationId, - authRequest.organizationUserId, - ); + try { + await this.organizationAuthRequestService.approvePendingRequest( + this.organizationId, + authRequest, + ); - // The user must be enrolled in account recovery (password reset) in order for the request to be approved. - if (details == null || details.resetPasswordKey == null) { + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("loginRequestApproved"), + ); + } catch (error) { this.platformUtilsService.showToast( "error", null, this.i18nService.t("resetPasswordDetailsError"), ); - return; } - - const encryptedKey = await this.getEncryptedUserKey(authRequest.publicKey, details); - - await this.organizationAuthRequestService.approvePendingRequest( - this.organizationId, - authRequest.id, - encryptedKey, - ); - - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("loginRequestApproved"), - ); }); } diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts index a116d2244c..ce97f54374 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations-routing.module.ts @@ -9,7 +9,6 @@ import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/admin-cons import { SsoComponent } from "../../auth/sso/sso.component"; -import { DeviceApprovalsComponent } from "./manage/device-approvals/device-approvals.component"; import { DomainVerificationComponent } from "./manage/domain-verification/domain-verification.component"; import { ScimComponent } from "./manage/scim.component"; @@ -55,7 +54,10 @@ const routes: Routes = [ }, { path: "device-approvals", - component: DeviceApprovalsComponent, + loadComponent: () => + import("./manage/device-approvals/device-approvals.component").then( + (mod) => mod.DeviceApprovalsComponent, + ), canActivate: [OrganizationPermissionsGuard], data: { organizationPermissions: (org: Organization) => org.canManageDeviceApprovals, diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations.module.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations.module.ts index 98982dae24..e19d028b00 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/organizations.module.ts @@ -1,32 +1,22 @@ import { NgModule } from "@angular/core"; -import { NoItemsModule } from "@bitwarden/components"; import { LooseComponentsModule } from "@bitwarden/web-vault/app/shared"; import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module"; import { SsoComponent } from "../../auth/sso/sso.component"; -import { CoreOrganizationModule } from "./core"; -import { DeviceApprovalsComponent } from "./manage/device-approvals/device-approvals.component"; import { DomainAddEditDialogComponent } from "./manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component"; import { DomainVerificationComponent } from "./manage/domain-verification/domain-verification.component"; import { ScimComponent } from "./manage/scim.component"; import { OrganizationsRoutingModule } from "./organizations-routing.module"; @NgModule({ - imports: [ - SharedModule, - CoreOrganizationModule, - OrganizationsRoutingModule, - NoItemsModule, - LooseComponentsModule, - ], + imports: [SharedModule, OrganizationsRoutingModule, LooseComponentsModule], declarations: [ SsoComponent, ScimComponent, DomainVerificationComponent, DomainAddEditDialogComponent, - DeviceApprovalsComponent, ], }) export class OrganizationsModule {} diff --git a/bitwarden_license/bit-web/tsconfig.json b/bitwarden_license/bit-web/tsconfig.json index 0f19c6736a..27095e1da9 100644 --- a/bitwarden_license/bit-web/tsconfig.json +++ b/bitwarden_license/bit-web/tsconfig.json @@ -1,21 +1,43 @@ { "extends": "../../apps/web/tsconfig", "compilerOptions": { + "baseUrl": ".", + "module": "ES2020", + "resolveJsonModule": true, "paths": { "@bitwarden/admin-console": ["../../libs/admin-console/src"], "@bitwarden/angular/*": ["../../libs/angular/src/*"], - "@bitwarden/auth": ["../../libs/auth/src"], + "@bitwarden/auth/common": ["../../libs/auth/src/common"], + "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], "@bitwarden/billing": ["../../libs/billing/src"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-core/src"], + "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], + "@bitwarden/importer/core": ["../../libs/importer/src"], + "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/platform": ["../../libs/platform/src"], "@bitwarden/vault": ["../../libs/vault/src"], - "@bitwarden/web-vault/*": ["../../apps/web/src/*"] + "@bitwarden/web-vault/*": ["../../apps/web/src/*"], + + "@bitwarden/bit-common/*": ["../bit-common/src/*"] } }, - "include": ["src/**/*.stories.ts"] + "files": [ + "../../apps/web/src/polyfills.ts", + "../../apps/web/src/main.ts", + "../../apps/web/src/theme.ts", + + "../../bitwarden_license/bit-web/src/main.ts" + ], + "include": [ + "../../apps/web/src/connectors/*.ts", + "../../apps/web/src/**/*.stories.ts", + "../../apps/web/src/**/*.spec.ts", + "../../libs/common/src/platform/services/**/*.worker.ts", + + "src/**/*.stories.ts" + ] } diff --git a/bitwarden_license/bit-web/webpack.config.js b/bitwarden_license/bit-web/webpack.config.js index 155b572b42..bf192b5411 100644 --- a/bitwarden_license/bit-web/webpack.config.js +++ b/bitwarden_license/bit-web/webpack.config.js @@ -4,7 +4,7 @@ const webpackConfig = require("../../apps/web/webpack.config"); webpackConfig.entry["app/main"] = "../../bitwarden_license/bit-web/src/main.ts"; webpackConfig.plugins[webpackConfig.plugins.length - 1] = new AngularWebpackPlugin({ - tsConfigPath: "tsconfig.json", + tsconfig: "../../bitwarden_license/bit-web/tsconfig.json", entryModule: "bitwarden_license/src/app/app.module#AppModule", sourceMap: true, }); diff --git a/clients.code-workspace b/clients.code-workspace index c7075c04ea..a424f91eeb 100644 --- a/clients.code-workspace +++ b/clients.code-workspace @@ -32,6 +32,10 @@ "name": "libs", "path": "libs", }, + { + "name": "common (bit)", + "path": "bitwarden_license/bit-common", + }, ], "settings": { "eslint.options": { diff --git a/jest.config.js b/jest.config.js index 1ff25c3beb..e2c50553d8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -22,6 +22,7 @@ module.exports = { "/apps/web/jest.config.js", "/bitwarden_license/bit-web/jest.config.js", "/bitwarden_license/bit-cli/jest.config.js", + "/bitwarden_license/bit-common/jest.config.js", "/libs/admin-console/jest.config.js", "/libs/angular/jest.config.js", diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 19d35b2896..7d4aee3fb3 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -27,7 +27,8 @@ "@bitwarden/importer/ui": ["./libs/importer/src/components"], "@bitwarden/platform": ["./libs/platform/src"], "@bitwarden/node/*": ["./libs/node/src/*"], - "@bitwarden/vault": ["./libs/vault/src"] + "@bitwarden/vault": ["./libs/vault/src"], + "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"] }, "plugins": [ { diff --git a/tsconfig.json b/tsconfig.json index 60dc9d223e..0a519a5b7e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,8 @@ "@bitwarden/platform": ["./libs/platform/src"], "@bitwarden/node/*": ["./libs/node/src/*"], "@bitwarden/web-vault/*": ["./apps/web/src/*"], - "@bitwarden/vault": ["./libs/vault/src"] + "@bitwarden/vault": ["./libs/vault/src"], + "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"] }, "plugins": [ { @@ -42,7 +43,8 @@ "apps/web/src/**/*", "apps/browser/src/**/*", "libs/*/src/**/*", - "bitwarden_license/bit-web/src/**/*" + "bitwarden_license/bit-web/src/**/*", + "bitwarden_license/bit-common/src/**/*" ], "exclude": [ "apps/web/src/**/*.spec.ts",