mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-21 11:35:34 +01:00
[AC-2302] Extract device approve/deny logic into a service (#8818)
* [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 <hello@addisonbeck.com> Co-authored-by: Thomas Rittson <trittson@bitwarden.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
parent
4131aa9803
commit
adf7a38f87
@ -262,6 +262,12 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["bitwarden_license/bit-common/src/**/*.ts"],
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-imports": ["error", { "patterns": ["@bitwarden/bit-common/*", "src/**/*"] }]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -27,12 +27,7 @@
|
|||||||
"strictTemplates": true,
|
"strictTemplates": true,
|
||||||
"preserveWhitespaces": true
|
"preserveWhitespaces": true
|
||||||
},
|
},
|
||||||
"files": [
|
"files": ["src/polyfills.ts", "src/main.ts", "src/theme.ts"],
|
||||||
"src/polyfills.ts",
|
|
||||||
"src/main.ts",
|
|
||||||
"../../bitwarden_license/bit-web/src/main.ts",
|
|
||||||
"src/theme.ts"
|
|
||||||
],
|
|
||||||
"include": [
|
"include": [
|
||||||
"src/connectors/*.ts",
|
"src/connectors/*.ts",
|
||||||
"src/**/*.stories.ts",
|
"src/**/*.stories.ts",
|
||||||
|
16
bitwarden_license/bit-common/jest.config.js
Normal file
16
bitwarden_license/bit-common/jest.config.js
Normal file
@ -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: "<rootDir>/",
|
||||||
|
}),
|
||||||
|
};
|
@ -1,17 +1,13 @@
|
|||||||
import { Injectable } from "@angular/core";
|
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
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 { AdminAuthRequestUpdateRequest } from "./admin-auth-request-update.request";
|
||||||
import { BulkDenyAuthRequestsRequest } from "./bulk-deny-auth-requests.request";
|
import { BulkDenyAuthRequestsRequest } from "./bulk-deny-auth-requests.request";
|
||||||
|
import { PendingAuthRequestView } from "./pending-auth-request.view";
|
||||||
import { PendingOrganizationAuthRequestResponse } from "./pending-organization-auth-request.response";
|
import { PendingOrganizationAuthRequestResponse } from "./pending-organization-auth-request.response";
|
||||||
|
|
||||||
@Injectable()
|
export class OrganizationAuthRequestApiService {
|
||||||
export class OrganizationAuthRequestService {
|
|
||||||
constructor(private apiService: ApiService) {}
|
constructor(private apiService: ApiService) {}
|
||||||
|
|
||||||
async listPendingRequests(organizationId: string): Promise<PendingAuthRequestView[]> {
|
async listPendingRequests(organizationId: string): Promise<PendingAuthRequestView[]> {
|
@ -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<OrganizationAuthRequestApiService>;
|
||||||
|
let cryptoService: MockProxy<CryptoService>;
|
||||||
|
let organizationUserService: MockProxy<OrganizationUserService>;
|
||||||
|
let organizationAuthRequestService: OrganizationAuthRequestService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
organizationAuthRequestApiService = mock<OrganizationAuthRequestApiService>();
|
||||||
|
cryptoService = mock<CryptoService>();
|
||||||
|
organizationUserService = mock<OrganizationUserService>();
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -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<PendingAuthRequestView[]> {
|
||||||
|
return await this.organizationAuthRequestApiService.listPendingRequests(organizationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async denyPendingRequests(organizationId: string, ...requestIds: string[]): Promise<void> {
|
||||||
|
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<EncString> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { View } from "@bitwarden/common/models/view/view";
|
import { View } from "@bitwarden/common/models/view/view";
|
||||||
|
|
||||||
import { PendingOrganizationAuthRequestResponse } from "../services/auth-requests";
|
import { PendingOrganizationAuthRequestResponse } from ".";
|
||||||
|
|
||||||
export class PendingAuthRequestView implements View {
|
export class PendingAuthRequestView implements View {
|
||||||
id: string;
|
id: string;
|
24
bitwarden_license/bit-common/tsconfig.json
Normal file
24
bitwarden_license/bit-common/tsconfig.json
Normal file
@ -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/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
bitwarden_license/bit-common/tsconfig.spec.json
Normal file
3
bitwarden_license/bit-common/tsconfig.spec.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json"
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
import { NgModule } from "@angular/core";
|
|
||||||
|
|
||||||
import { OrganizationAuthRequestService } from "./services/auth-requests";
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
providers: [OrganizationAuthRequestService],
|
|
||||||
})
|
|
||||||
export class CoreOrganizationModule {}
|
|
@ -1 +0,0 @@
|
|||||||
export * from "./core-organization.module";
|
|
@ -2,25 +2,37 @@ import { Component, OnDestroy, OnInit } from "@angular/core";
|
|||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { BehaviorSubject, Subject, switchMap, takeUntil, tap } from "rxjs";
|
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 { 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 { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { TableDataSource, NoItemsModule } from "@bitwarden/components";
|
||||||
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 { Devices } from "@bitwarden/web-vault/app/admin-console/icons";
|
import { Devices } from "@bitwarden/web-vault/app/admin-console/icons";
|
||||||
|
import { LooseComponentsModule } from "@bitwarden/web-vault/app/shared";
|
||||||
import { OrganizationAuthRequestService } from "../../core/services/auth-requests";
|
import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module";
|
||||||
import { PendingAuthRequestView } from "../../core/views/pending-auth-request.view";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-org-device-approvals",
|
selector: "app-org-device-approvals",
|
||||||
templateUrl: "./device-approvals.component.html",
|
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 {
|
export class DeviceApprovalsComponent implements OnInit, OnDestroy {
|
||||||
tableDataSource = new TableDataSource<PendingAuthRequestView>();
|
tableDataSource = new TableDataSource<PendingAuthRequestView>();
|
||||||
@ -35,8 +47,6 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private organizationAuthRequestService: OrganizationAuthRequestService,
|
private organizationAuthRequestService: OrganizationAuthRequestService,
|
||||||
private organizationUserService: OrganizationUserService,
|
|
||||||
private cryptoService: CryptoService,
|
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
@ -64,58 +74,12 @@ 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<EncString> {
|
|
||||||
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) {
|
async approveRequest(authRequest: PendingAuthRequestView) {
|
||||||
await this.performAsyncAction(async () => {
|
await this.performAsyncAction(async () => {
|
||||||
const details = await this.organizationUserService.getOrganizationUserResetPasswordDetails(
|
try {
|
||||||
this.organizationId,
|
|
||||||
authRequest.organizationUserId,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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(
|
|
||||||
"error",
|
|
||||||
null,
|
|
||||||
this.i18nService.t("resetPasswordDetailsError"),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const encryptedKey = await this.getEncryptedUserKey(authRequest.publicKey, details);
|
|
||||||
|
|
||||||
await this.organizationAuthRequestService.approvePendingRequest(
|
await this.organizationAuthRequestService.approvePendingRequest(
|
||||||
this.organizationId,
|
this.organizationId,
|
||||||
authRequest.id,
|
authRequest,
|
||||||
encryptedKey,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
@ -123,6 +87,13 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy {
|
|||||||
null,
|
null,
|
||||||
this.i18nService.t("loginRequestApproved"),
|
this.i18nService.t("loginRequestApproved"),
|
||||||
);
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("resetPasswordDetailsError"),
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/admin-cons
|
|||||||
|
|
||||||
import { SsoComponent } from "../../auth/sso/sso.component";
|
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 { DomainVerificationComponent } from "./manage/domain-verification/domain-verification.component";
|
||||||
import { ScimComponent } from "./manage/scim.component";
|
import { ScimComponent } from "./manage/scim.component";
|
||||||
|
|
||||||
@ -55,7 +54,10 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "device-approvals",
|
path: "device-approvals",
|
||||||
component: DeviceApprovalsComponent,
|
loadComponent: () =>
|
||||||
|
import("./manage/device-approvals/device-approvals.component").then(
|
||||||
|
(mod) => mod.DeviceApprovalsComponent,
|
||||||
|
),
|
||||||
canActivate: [OrganizationPermissionsGuard],
|
canActivate: [OrganizationPermissionsGuard],
|
||||||
data: {
|
data: {
|
||||||
organizationPermissions: (org: Organization) => org.canManageDeviceApprovals,
|
organizationPermissions: (org: Organization) => org.canManageDeviceApprovals,
|
||||||
|
@ -1,32 +1,22 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
import { NoItemsModule } from "@bitwarden/components";
|
|
||||||
import { LooseComponentsModule } from "@bitwarden/web-vault/app/shared";
|
import { LooseComponentsModule } from "@bitwarden/web-vault/app/shared";
|
||||||
import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module";
|
import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module";
|
||||||
|
|
||||||
import { SsoComponent } from "../../auth/sso/sso.component";
|
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 { DomainAddEditDialogComponent } from "./manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component";
|
||||||
import { DomainVerificationComponent } from "./manage/domain-verification/domain-verification.component";
|
import { DomainVerificationComponent } from "./manage/domain-verification/domain-verification.component";
|
||||||
import { ScimComponent } from "./manage/scim.component";
|
import { ScimComponent } from "./manage/scim.component";
|
||||||
import { OrganizationsRoutingModule } from "./organizations-routing.module";
|
import { OrganizationsRoutingModule } from "./organizations-routing.module";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [SharedModule, OrganizationsRoutingModule, LooseComponentsModule],
|
||||||
SharedModule,
|
|
||||||
CoreOrganizationModule,
|
|
||||||
OrganizationsRoutingModule,
|
|
||||||
NoItemsModule,
|
|
||||||
LooseComponentsModule,
|
|
||||||
],
|
|
||||||
declarations: [
|
declarations: [
|
||||||
SsoComponent,
|
SsoComponent,
|
||||||
ScimComponent,
|
ScimComponent,
|
||||||
DomainVerificationComponent,
|
DomainVerificationComponent,
|
||||||
DomainAddEditDialogComponent,
|
DomainAddEditDialogComponent,
|
||||||
DeviceApprovalsComponent,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class OrganizationsModule {}
|
export class OrganizationsModule {}
|
||||||
|
@ -1,21 +1,43 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../apps/web/tsconfig",
|
"extends": "../../apps/web/tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"module": "ES2020",
|
||||||
|
"resolveJsonModule": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@bitwarden/admin-console": ["../../libs/admin-console/src"],
|
"@bitwarden/admin-console": ["../../libs/admin-console/src"],
|
||||||
"@bitwarden/angular/*": ["../../libs/angular/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/billing": ["../../libs/billing/src"],
|
||||||
"@bitwarden/common/*": ["../../libs/common/src/*"],
|
"@bitwarden/common/*": ["../../libs/common/src/*"],
|
||||||
"@bitwarden/components": ["../../libs/components/src"],
|
"@bitwarden/components": ["../../libs/components/src"],
|
||||||
"@bitwarden/vault-export-core": [
|
"@bitwarden/vault-export-core": [
|
||||||
"../../libs/tools/export/vault-export/vault-export-core/src"
|
"../../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/platform": ["../../libs/platform/src"],
|
||||||
"@bitwarden/vault": ["../../libs/vault/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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ const webpackConfig = require("../../apps/web/webpack.config");
|
|||||||
|
|
||||||
webpackConfig.entry["app/main"] = "../../bitwarden_license/bit-web/src/main.ts";
|
webpackConfig.entry["app/main"] = "../../bitwarden_license/bit-web/src/main.ts";
|
||||||
webpackConfig.plugins[webpackConfig.plugins.length - 1] = new AngularWebpackPlugin({
|
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",
|
entryModule: "bitwarden_license/src/app/app.module#AppModule",
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
});
|
});
|
||||||
|
@ -32,6 +32,10 @@
|
|||||||
"name": "libs",
|
"name": "libs",
|
||||||
"path": "libs",
|
"path": "libs",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "common (bit)",
|
||||||
|
"path": "bitwarden_license/bit-common",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"eslint.options": {
|
"eslint.options": {
|
||||||
|
@ -22,6 +22,7 @@ module.exports = {
|
|||||||
"<rootDir>/apps/web/jest.config.js",
|
"<rootDir>/apps/web/jest.config.js",
|
||||||
"<rootDir>/bitwarden_license/bit-web/jest.config.js",
|
"<rootDir>/bitwarden_license/bit-web/jest.config.js",
|
||||||
"<rootDir>/bitwarden_license/bit-cli/jest.config.js",
|
"<rootDir>/bitwarden_license/bit-cli/jest.config.js",
|
||||||
|
"<rootDir>/bitwarden_license/bit-common/jest.config.js",
|
||||||
|
|
||||||
"<rootDir>/libs/admin-console/jest.config.js",
|
"<rootDir>/libs/admin-console/jest.config.js",
|
||||||
"<rootDir>/libs/angular/jest.config.js",
|
"<rootDir>/libs/angular/jest.config.js",
|
||||||
|
@ -27,7 +27,8 @@
|
|||||||
"@bitwarden/importer/ui": ["./libs/importer/src/components"],
|
"@bitwarden/importer/ui": ["./libs/importer/src/components"],
|
||||||
"@bitwarden/platform": ["./libs/platform/src"],
|
"@bitwarden/platform": ["./libs/platform/src"],
|
||||||
"@bitwarden/node/*": ["./libs/node/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": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
@ -29,7 +29,8 @@
|
|||||||
"@bitwarden/platform": ["./libs/platform/src"],
|
"@bitwarden/platform": ["./libs/platform/src"],
|
||||||
"@bitwarden/node/*": ["./libs/node/src/*"],
|
"@bitwarden/node/*": ["./libs/node/src/*"],
|
||||||
"@bitwarden/web-vault/*": ["./apps/web/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": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@ -42,7 +43,8 @@
|
|||||||
"apps/web/src/**/*",
|
"apps/web/src/**/*",
|
||||||
"apps/browser/src/**/*",
|
"apps/browser/src/**/*",
|
||||||
"libs/*/src/**/*",
|
"libs/*/src/**/*",
|
||||||
"bitwarden_license/bit-web/src/**/*"
|
"bitwarden_license/bit-web/src/**/*",
|
||||||
|
"bitwarden_license/bit-common/src/**/*"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"apps/web/src/**/*.spec.ts",
|
"apps/web/src/**/*.spec.ts",
|
||||||
|
Loading…
Reference in New Issue
Block a user