From 80f5a883e088e941c415f224d1fa7af3dc2b6cd7 Mon Sep 17 00:00:00 2001 From: Daniel James Smith Date: Fri, 18 Nov 2022 13:20:19 +0100 Subject: [PATCH] [PS-1884] [TDL-189] [TDL-203] Move libs/node files to CLI and rename per ADR12 (#4069) * Extract files only used in cli out of libs/node Move commands from libs/node to cli Move program from libs/node to cli Move services from libs/node to cli Move specs from libs/node to cli Naming changes based on ADR 12 Rename commands Rename models/request Rename models/response Remove entries from whitelist-capital-letters.txt * Merge lowDbStorageService into base class Move logic from extended lowdbStorage.service.ts into base-lowdb-storage.service.ts Delete lowdb-storage.service.ts Rename base-lowdb-storage.service.ts to lowdb-storage.service.ts * Merge login.command with base class program.ts - changed import temporarily to make it easier to review Remove passing in clientId, set "cli" when constructing ssoRedirectUri call Remove setting callbacks, use private methods instead Remove i18nService from constructor params Add syncService, keyConnectorService and logoutCallback to constructor Merge successCallback with handleSuccessResponse Remove validatedParams callback and added private method Move options(program.OptionValues) and set in run() Delete login.command.ts * Rename base-login.command.ts to login.command.ts * Merge base.program.ts with program.ts --- .github/whitelist-capital-letters.txt | 31 - apps/cli/jest.config.js | 5 +- apps/cli/src/bw.ts | 10 +- apps/cli/src/commands/completion.command.ts | 4 +- apps/cli/src/commands/config.command.ts | 7 +- apps/cli/src/commands/confirm.command.ts | 3 +- ...ts => convert-to-key-connector.command.ts} | 5 +- apps/cli/src/commands/create.command.ts | 10 +- apps/cli/src/commands/delete.command.ts | 2 +- apps/cli/src/commands/download.command.ts | 4 +- apps/cli/src/commands/edit.command.ts | 10 +- apps/cli/src/commands/encode.command.ts | 5 +- apps/cli/src/commands/export.command.ts | 2 +- apps/cli/src/commands/generate.command.ts | 4 +- apps/cli/src/commands/get.command.ts | 22 +- apps/cli/src/commands/import.command.ts | 4 +- apps/cli/src/commands/list.command.ts | 14 +- apps/cli/src/commands/lock.command.ts | 5 +- apps/cli/src/commands/login.command.ts | 711 ++++++++++++++++-- .../cli/src}/commands/logout.command.ts | 2 +- apps/cli/src/commands/restore.command.ts | 3 +- apps/cli/src/commands/send/create.command.ts | 6 +- apps/cli/src/commands/send/delete.command.ts | 3 +- apps/cli/src/commands/send/edit.command.ts | 4 +- apps/cli/src/commands/send/get.command.ts | 4 +- apps/cli/src/commands/send/list.command.ts | 6 +- apps/cli/src/commands/send/receive.command.ts | 4 +- ....command.ts => remove-password.command.ts} | 4 +- apps/cli/src/commands/serve.command.ts | 6 +- apps/cli/src/commands/share.command.ts | 4 +- apps/cli/src/commands/status.command.ts | 4 +- apps/cli/src/commands/sync.command.ts | 6 +- apps/cli/src/commands/unlock.command.ts | 6 +- .../cli/src}/commands/update.command.ts | 2 +- ....ts => organization-collection.request.ts} | 2 +- .../cli => apps/cli/src}/models/response.ts | 2 +- ...mentResponse.ts => attachment.response.ts} | 0 .../cli/src/models/response/base.response.ts | 0 .../{cipherResponse.ts => cipher.response.ts} | 8 +- ...tionResponse.ts => collection.response.ts} | 3 +- .../cli/src/models/response/file.response.ts | 2 +- .../{folderResponse.ts => folder.response.ts} | 3 +- .../cli/src/models/response/list.response.ts | 2 +- .../{loginResponse.ts => login.response.ts} | 0 .../src/models/response/message.response.ts | 2 +- ...ts => organization-collection.response.ts} | 4 +- ...ponse.ts => organization-user.response.ts} | 3 +- ...onResponse.ts => organization.response.ts} | 3 +- ...sponse.ts => password-history.response.ts} | 0 ...essResponse.ts => send-access.response.ts} | 6 +- ...dFileResponse.ts => send-file.response.ts} | 0 ...dTextResponse.ts => send-text.response.ts} | 0 .../{sendResponse.ts => send.response.ts} | 6 +- .../src/models/response/string.response.ts | 2 +- ...mplateResponse.ts => template.response.ts} | 2 +- ...tionReadOnly.ts => selection-read-only.ts} | 0 apps/cli/src/program.ts | 120 ++- apps/cli/src/send.program.ts | 10 +- .../services/cli-platform-utils.service.ts | 0 .../src/services/console-log.service.spec.ts | 4 +- .../cli/src/services/console-log.service.ts | 0 .../cli/src/services/lowdb-storage.service.ts | 26 +- apps/cli/src/services/lowdbStorage.service.ts | 40 - .../cli/src/services/node-api.service.ts | 0 ....ts => node-env-secure-storage.service.ts} | 0 apps/cli/src/utils.ts | 5 +- apps/cli/src/vault.program.ts | 3 +- apps/cli/tsconfig.json | 3 +- libs/node/src/cli/baseProgram.ts | 113 --- libs/node/src/cli/commands/login.command.ts | 632 ---------------- 70 files changed, 896 insertions(+), 1032 deletions(-) rename apps/cli/src/commands/{convertToKeyConnector.command.ts => convert-to-key-connector.command.ts} (95%) rename {libs/node/src/cli => apps/cli/src}/commands/logout.command.ts (89%) rename apps/cli/src/commands/send/{removePassword.command.ts => remove-password.command.ts} (79%) rename {libs/node/src/cli => apps/cli/src}/commands/update.command.ts (97%) rename apps/cli/src/models/request/{organizationCollectionRequest.ts => organization-collection.request.ts} (89%) rename {libs/node/src/cli => apps/cli/src}/models/response.ts (95%) rename apps/cli/src/models/response/{attachmentResponse.ts => attachment.response.ts} (100%) rename libs/node/src/cli/models/response/baseResponse.ts => apps/cli/src/models/response/base.response.ts (100%) rename apps/cli/src/models/response/{cipherResponse.ts => cipher.response.ts} (80%) rename apps/cli/src/models/response/{collectionResponse.ts => collection.response.ts} (82%) rename libs/node/src/cli/models/response/fileResponse.ts => apps/cli/src/models/response/file.response.ts (83%) rename apps/cli/src/models/response/{folderResponse.ts => folder.response.ts} (81%) rename libs/node/src/cli/models/response/listResponse.ts => apps/cli/src/models/response/list.response.ts (79%) rename apps/cli/src/models/response/{loginResponse.ts => login.response.ts} (100%) rename libs/node/src/cli/models/response/messageResponse.ts => apps/cli/src/models/response/message.response.ts (85%) rename apps/cli/src/models/response/{organizationCollectionResponse.ts => organization-collection.response.ts} (73%) rename apps/cli/src/models/response/{organizationUserResponse.ts => organization-user.response.ts} (85%) rename apps/cli/src/models/response/{organizationResponse.ts => organization.response.ts} (89%) rename apps/cli/src/models/response/{passwordHistoryResponse.ts => password-history.response.ts} (100%) rename apps/cli/src/models/response/{sendAccessResponse.ts => send-access.response.ts} (82%) rename apps/cli/src/models/response/{sendFileResponse.ts => send-file.response.ts} (100%) rename apps/cli/src/models/response/{sendTextResponse.ts => send-text.response.ts} (100%) rename apps/cli/src/models/response/{sendResponse.ts => send.response.ts} (95%) rename libs/node/src/cli/models/response/stringResponse.ts => apps/cli/src/models/response/string.response.ts (78%) rename apps/cli/src/models/response/{templateResponse.ts => template.response.ts} (70%) rename apps/cli/src/models/{selectionReadOnly.ts => selection-read-only.ts} (100%) rename libs/node/src/cli/services/cliPlatformUtils.service.ts => apps/cli/src/services/cli-platform-utils.service.ts (100%) rename libs/node/spec/cli/consoleLog.service.spec.ts => apps/cli/src/services/console-log.service.spec.ts (84%) rename libs/node/src/cli/services/consoleLog.service.ts => apps/cli/src/services/console-log.service.ts (100%) rename libs/node/src/services/lowdbStorage.service.ts => apps/cli/src/services/lowdb-storage.service.ts (87%) delete mode 100644 apps/cli/src/services/lowdbStorage.service.ts rename libs/node/src/services/nodeApi.service.ts => apps/cli/src/services/node-api.service.ts (100%) rename apps/cli/src/services/{nodeEnvSecureStorage.service.ts => node-env-secure-storage.service.ts} (100%) delete mode 100644 libs/node/src/cli/baseProgram.ts delete mode 100644 libs/node/src/cli/commands/login.command.ts diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index e1c74a70943..5dd8cd621ba 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -162,17 +162,6 @@ ./libs/common/src/services/bitwardenFileUpload.service.ts ./libs/common/src/services/webCryptoFunction.service.ts ./libs/common/src/interfaces/IEncrypted.ts -./libs/node/spec/cli/consoleLog.service.spec.ts -./libs/node/src/cli/models/response/baseResponse.ts -./libs/node/src/cli/models/response/stringResponse.ts -./libs/node/src/cli/models/response/fileResponse.ts -./libs/node/src/cli/models/response/messageResponse.ts -./libs/node/src/cli/models/response/listResponse.ts -./libs/node/src/cli/baseProgram.ts -./libs/node/src/cli/services/consoleLog.service.ts -./libs/node/src/cli/services/cliPlatformUtils.service.ts -./libs/node/src/services/nodeApi.service.ts -./libs/node/src/services/lowdbStorage.service.ts ./libs/electron/spec/services/electronLog.service.spec.ts ./libs/electron/src/baseMenu.ts ./libs/electron/src/services/electronLog.service.ts @@ -235,26 +224,6 @@ ./apps/desktop/src/services/nativeMessageHandler.service.ts ./apps/cli/stores/chocolatey/tools/VERIFICATION.txt ./apps/cli/README.md -./apps/cli/src/models/response/sendFileResponse.ts -./apps/cli/src/models/response/organizationCollectionResponse.ts -./apps/cli/src/models/response/collectionResponse.ts -./apps/cli/src/models/response/templateResponse.ts -./apps/cli/src/models/response/passwordHistoryResponse.ts -./apps/cli/src/models/response/folderResponse.ts -./apps/cli/src/models/response/loginResponse.ts -./apps/cli/src/models/response/sendAccessResponse.ts -./apps/cli/src/models/response/sendResponse.ts -./apps/cli/src/models/response/cipherResponse.ts -./apps/cli/src/models/response/organizationResponse.ts -./apps/cli/src/models/response/organizationUserResponse.ts -./apps/cli/src/models/response/sendTextResponse.ts -./apps/cli/src/models/response/attachmentResponse.ts -./apps/cli/src/models/selectionReadOnly.ts -./apps/cli/src/models/request/organizationCollectionRequest.ts -./apps/cli/src/commands/convertToKeyConnector.command.ts -./apps/cli/src/commands/send/removePassword.command.ts -./apps/cli/src/services/lowdbStorage.service.ts -./apps/cli/src/services/nodeEnvSecureStorage.service.ts ./apps/browser/README.md ./apps/browser/store/windows/AppxManifest.xml ./apps/browser/src/background/nativeMessaging.background.ts diff --git a/apps/cli/jest.config.js b/apps/cli/jest.config.js index 4f0dab845ec..67116d394b3 100644 --- a/apps/cli/jest.config.js +++ b/apps/cli/jest.config.js @@ -1,11 +1,12 @@ const { pathsToModuleNameMapper } = require("ts-jest"); - -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("./tsconfig.json"); const sharedConfig = require("../../libs/shared/jest.config.base"); module.exports = { + ...sharedConfig, preset: "ts-jest", + testEnvironment: "node", setupFilesAfterEnv: ["/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { prefix: "/", diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 7efee8eb3e5..8f877e7fbf0 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -49,16 +49,16 @@ import { UserVerificationApiService } from "@bitwarden/common/services/userVerif import { UserVerificationService } from "@bitwarden/common/services/userVerification/userVerification.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service"; -import { CliPlatformUtilsService } from "@bitwarden/node/cli/services/cliPlatformUtils.service"; -import { ConsoleLogService } from "@bitwarden/node/cli/services/consoleLog.service"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; -import { NodeApiService } from "@bitwarden/node/services/nodeApi.service"; import { Program } from "./program"; import { SendProgram } from "./send.program"; +import { CliPlatformUtilsService } from "./services/cli-platform-utils.service"; +import { ConsoleLogService } from "./services/console-log.service"; import { I18nService } from "./services/i18n.service"; -import { LowdbStorageService } from "./services/lowdbStorage.service"; -import { NodeEnvSecureStorageService } from "./services/nodeEnvSecureStorage.service"; +import { LowdbStorageService } from "./services/lowdb-storage.service"; +import { NodeApiService } from "./services/node-api.service"; +import { NodeEnvSecureStorageService } from "./services/node-env-secure-storage.service"; import { VaultProgram } from "./vault.program"; // Polyfills diff --git a/apps/cli/src/commands/completion.command.ts b/apps/cli/src/commands/completion.command.ts index c175f9b6a13..17a3eff5a83 100644 --- a/apps/cli/src/commands/completion.command.ts +++ b/apps/cli/src/commands/completion.command.ts @@ -1,7 +1,7 @@ import * as program from "commander"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; interface IOption { long?: string; diff --git a/apps/cli/src/commands/config.command.ts b/apps/cli/src/commands/config.command.ts index 2fa33c9edc5..9110b46bf29 100644 --- a/apps/cli/src/commands/config.command.ts +++ b/apps/cli/src/commands/config.command.ts @@ -1,9 +1,10 @@ import * as program from "commander"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; + +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; +import { StringResponse } from "../models/response/string.response"; export class ConfigCommand { constructor(private environmentService: EnvironmentService) {} diff --git a/apps/cli/src/commands/confirm.command.ts b/apps/cli/src/commands/confirm.command.ts index 79d30dfec65..824bd5c8e85 100644 --- a/apps/cli/src/commands/confirm.command.ts +++ b/apps/cli/src/commands/confirm.command.ts @@ -2,7 +2,8 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { Utils } from "@bitwarden/common/misc/utils"; import { OrganizationUserConfirmRequest } from "@bitwarden/common/models/request/organization-user-confirm.request"; -import { Response } from "@bitwarden/node/cli/models/response"; + +import { Response } from "../models/response"; export class ConfirmCommand { constructor(private apiService: ApiService, private cryptoService: CryptoService) {} diff --git a/apps/cli/src/commands/convertToKeyConnector.command.ts b/apps/cli/src/commands/convert-to-key-connector.command.ts similarity index 95% rename from apps/cli/src/commands/convertToKeyConnector.command.ts rename to apps/cli/src/commands/convert-to-key-connector.command.ts index 00fb2f72910..82756edc985 100644 --- a/apps/cli/src/commands/convertToKeyConnector.command.ts +++ b/apps/cli/src/commands/convert-to-key-connector.command.ts @@ -4,8 +4,9 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; + +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; export class ConvertToKeyConnectorCommand { constructor( diff --git a/apps/cli/src/commands/create.command.ts b/apps/cli/src/commands/create.command.ts index 334573b3dfc..92855ee6f79 100644 --- a/apps/cli/src/commands/create.command.ts +++ b/apps/cli/src/commands/create.command.ts @@ -13,12 +13,12 @@ import { CollectionExport } from "@bitwarden/common/models/export/collection.exp import { FolderExport } from "@bitwarden/common/models/export/folder.export"; import { CollectionRequest } from "@bitwarden/common/models/request/collection.request"; import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse"; +import { OrganizationCollectionRequest } from "../models/request/organization-collection.request"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { OrganizationCollectionResponse } from "../models/response/organization-collection.response"; import { CliUtils } from "../utils"; export class CreateCommand { diff --git a/apps/cli/src/commands/delete.command.ts b/apps/cli/src/commands/delete.command.ts index 5866e2a6fd6..86bfc8cbbc2 100644 --- a/apps/cli/src/commands/delete.command.ts +++ b/apps/cli/src/commands/delete.command.ts @@ -4,8 +4,8 @@ import { FolderApiServiceAbstraction } from "@bitwarden/common/abstractions/fold import { FolderService } from "@bitwarden/common/abstractions/folder/folder.service.abstraction"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; +import { Response } from "../models/response"; import { CliUtils } from "../utils"; export class DeleteCommand { diff --git a/apps/cli/src/commands/download.command.ts b/apps/cli/src/commands/download.command.ts index eb40cf5a2d7..8d7a6130c0d 100644 --- a/apps/cli/src/commands/download.command.ts +++ b/apps/cli/src/commands/download.command.ts @@ -3,9 +3,9 @@ import * as fet from "node-fetch"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { EncArrayBuffer } from "@bitwarden/common/models/domain/enc-array-buffer"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { FileResponse } from "@bitwarden/node/cli/models/response/fileResponse"; +import { Response } from "../models/response"; +import { FileResponse } from "../models/response/file.response"; import { CliUtils } from "../utils"; export abstract class DownloadCommand { diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index f486acbe991..d6cd120da0d 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -9,12 +9,12 @@ import { CollectionExport } from "@bitwarden/common/models/export/collection.exp import { FolderExport } from "@bitwarden/common/models/export/folder.export"; import { CollectionRequest } from "@bitwarden/common/models/request/collection.request"; import { SelectionReadOnlyRequest } from "@bitwarden/common/models/request/selection-read-only.request"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse"; +import { OrganizationCollectionRequest } from "../models/request/organization-collection.request"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { OrganizationCollectionResponse } from "../models/response/organization-collection.response"; import { CliUtils } from "../utils"; export class EditCommand { diff --git a/apps/cli/src/commands/encode.command.ts b/apps/cli/src/commands/encode.command.ts index 1c957d26fa6..4598e1b230c 100644 --- a/apps/cli/src/commands/encode.command.ts +++ b/apps/cli/src/commands/encode.command.ts @@ -1,6 +1,5 @@ -import { Response } from "@bitwarden/node/cli/models/response"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; - +import { Response } from "../models/response"; +import { StringResponse } from "../models/response/string.response"; import { CliUtils } from "../utils"; export class EncodeCommand { diff --git a/apps/cli/src/commands/export.command.ts b/apps/cli/src/commands/export.command.ts index 50446d7e027..9e9594c6ca0 100644 --- a/apps/cli/src/commands/export.command.ts +++ b/apps/cli/src/commands/export.command.ts @@ -5,8 +5,8 @@ import { ExportFormat, ExportService } from "@bitwarden/common/abstractions/expo import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/enums/policyType"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; +import { Response } from "../models/response"; import { CliUtils } from "../utils"; export class ExportCommand { diff --git a/apps/cli/src/commands/generate.command.ts b/apps/cli/src/commands/generate.command.ts index 3fb8a7fd8b9..aebc084a06b 100644 --- a/apps/cli/src/commands/generate.command.ts +++ b/apps/cli/src/commands/generate.command.ts @@ -1,8 +1,8 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; +import { Response } from "../models/response"; +import { StringResponse } from "../models/response/string.response"; import { CliUtils } from "../utils"; export class GenerateCommand { diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 594dc5c183d..6cd57fe7cc1 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -26,18 +26,18 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { CipherView } from "@bitwarden/common/models/view/cipher.view"; import { CollectionView } from "@bitwarden/common/models/view/collection.view"; import { FolderView } from "@bitwarden/common/models/view/folder.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; -import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { CollectionResponse } from "../models/response/collectionResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse"; -import { OrganizationResponse } from "../models/response/organizationResponse"; -import { SendResponse } from "../models/response/sendResponse"; -import { TemplateResponse } from "../models/response/templateResponse"; -import { SelectionReadOnly } from "../models/selectionReadOnly"; +import { OrganizationCollectionRequest } from "../models/request/organization-collection.request"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { CollectionResponse } from "../models/response/collection.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { OrganizationCollectionResponse } from "../models/response/organization-collection.response"; +import { OrganizationResponse } from "../models/response/organization.response"; +import { SendResponse } from "../models/response/send.response"; +import { StringResponse } from "../models/response/string.response"; +import { TemplateResponse } from "../models/response/template.response"; +import { SelectionReadOnly } from "../models/selection-read-only"; import { CliUtils } from "../utils"; import { DownloadCommand } from "./download.command"; diff --git a/apps/cli/src/commands/import.command.ts b/apps/cli/src/commands/import.command.ts index fcb2e215bbc..97145771ef1 100644 --- a/apps/cli/src/commands/import.command.ts +++ b/apps/cli/src/commands/import.command.ts @@ -5,9 +5,9 @@ import { ImportService } from "@bitwarden/common/abstractions/import.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { ImportType } from "@bitwarden/common/enums/importOptions"; import { Importer } from "@bitwarden/common/importers/importer"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; import { CliUtils } from "../utils"; export class ImportCommand { diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index 5322026284d..cc40c5bc79f 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -13,14 +13,14 @@ import { } from "@bitwarden/common/models/response/collection.response"; import { ListResponse as ApiListResponse } from "@bitwarden/common/models/response/list.response"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { ListResponse } from "@bitwarden/node/cli/models/response/listResponse"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { CollectionResponse } from "../models/response/collectionResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { OrganizationResponse } from "../models/response/organizationResponse"; -import { OrganizationUserResponse } from "../models/response/organizationUserResponse"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; +import { CollectionResponse } from "../models/response/collection.response"; +import { FolderResponse } from "../models/response/folder.response"; +import { ListResponse } from "../models/response/list.response"; +import { OrganizationUserResponse } from "../models/response/organization-user.response"; +import { OrganizationResponse } from "../models/response/organization.response"; import { CliUtils } from "../utils"; export class ListCommand { diff --git a/apps/cli/src/commands/lock.command.ts b/apps/cli/src/commands/lock.command.ts index ee993376fd8..f9f136a6b92 100644 --- a/apps/cli/src/commands/lock.command.ts +++ b/apps/cli/src/commands/lock.command.ts @@ -1,6 +1,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; + +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; export class LockCommand { constructor(private vaultTimeoutService: VaultTimeoutService) {} diff --git a/apps/cli/src/commands/login.command.ts b/apps/cli/src/commands/login.command.ts index d88a4d6d2f0..6284f8b94b5 100644 --- a/apps/cli/src/commands/login.command.ts +++ b/apps/cli/src/commands/login.command.ts @@ -1,11 +1,15 @@ +import * as http from "http"; + import * as program from "commander"; +import * as inquirer from "inquirer"; +import Separator from "inquirer/lib/objects/separator"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service"; import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; @@ -13,87 +17,644 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.serv import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; +import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; +import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; import { Utils } from "@bitwarden/common/misc/utils"; -import { LoginCommand as BaseLoginCommand } from "@bitwarden/node/cli/commands/login.command"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { AuthResult } from "@bitwarden/common/models/domain/auth-result"; +import { + UserApiLogInCredentials, + PasswordLogInCredentials, + SsoLogInCredentials, +} from "@bitwarden/common/models/domain/log-in-credentials"; +import { TokenTwoFactorRequest } from "@bitwarden/common/models/request/identity-token/token-two-factor.request"; +import { TwoFactorEmailRequest } from "@bitwarden/common/models/request/two-factor-email.request"; +import { UpdateTempPasswordRequest } from "@bitwarden/common/models/request/update-temp-password.request"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -export class LoginCommand extends BaseLoginCommand { +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; + +export class LoginCommand { + protected canInteract: boolean; + protected clientSecret: string; + protected email: string; + + private ssoRedirectUri: string = null; private options: program.OptionValues; constructor( - authService: AuthService, - apiService: ApiService, - cryptoFunctionService: CryptoFunctionService, - i18nService: I18nService, - environmentService: EnvironmentService, - passwordGenerationService: PasswordGenerationService, - platformUtilsService: PlatformUtilsService, - stateService: StateService, - cryptoService: CryptoService, - policyService: PolicyService, - twoFactorService: TwoFactorService, - private syncService: SyncService, - private keyConnectorService: KeyConnectorService, - private logoutCallback: () => Promise - ) { - super( - authService, - apiService, - i18nService, - environmentService, - passwordGenerationService, - cryptoFunctionService, - platformUtilsService, - stateService, - cryptoService, - policyService, - twoFactorService, - "cli" - ); - this.logout = this.logoutCallback; - this.validatedParams = async () => { - const key = await cryptoFunctionService.randomBytes(64); - process.env.BW_SESSION = Utils.fromBufferToB64(key); - }; - this.success = async () => { - await this.syncService.fullSync(true); + protected authService: AuthService, + protected apiService: ApiService, + protected cryptoFunctionService: CryptoFunctionService, + protected environmentService: EnvironmentService, + protected passwordGenerationService: PasswordGenerationService, + protected platformUtilsService: PlatformUtilsService, + protected stateService: StateService, + protected cryptoService: CryptoService, + protected policyService: PolicyService, + protected twoFactorService: TwoFactorService, + protected syncService: SyncService, + protected keyConnectorService: KeyConnectorService, + protected logoutCallback: () => Promise + ) {} - const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); - - if ( - (this.options.sso != null || this.options.apikey != null) && - this.canInteract && - !usesKeyConnector - ) { - const res = new MessageResponse( - "You are logged in!", - "\n" + "To unlock your vault, use the `unlock` command. ex:\n" + "$ bw unlock" - ); - return res; - } else { - const res = new MessageResponse( - "You are logged in!", - "\n" + - "To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n" + - '$ export BW_SESSION="' + - process.env.BW_SESSION + - '"\n' + - '> $env:BW_SESSION="' + - process.env.BW_SESSION + - '"\n\n' + - "You can also pass the session key to any command with the `--session` option. ex:\n" + - "$ bw list items --session " + - process.env.BW_SESSION - ); - res.raw = process.env.BW_SESSION; - return res; - } - }; - } - - run(email: string, password: string, options: program.OptionValues) { + async run(email: string, password: string, options: program.OptionValues) { this.options = options; this.email = email; - return super.run(email, password, options); + + this.canInteract = process.env.BW_NOINTERACTION !== "true"; + + let ssoCodeVerifier: string = null; + let ssoCode: string = null; + let orgIdentifier: string = null; + + let clientId: string = null; + let clientSecret: string = null; + + let selectedProvider: any = null; + + if (options.apikey != null) { + const apiIdentifiers = await this.apiIdentifiers(); + clientId = apiIdentifiers.clientId; + clientSecret = apiIdentifiers.clientSecret; + } else if (options.sso != null && this.canInteract) { + const passwordOptions: any = { + type: "password", + length: 64, + uppercase: true, + lowercase: true, + numbers: true, + special: false, + }; + const state = await this.passwordGenerationService.generatePassword(passwordOptions); + ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); + const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); + const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); + try { + const ssoParams = await this.openSsoPrompt(codeChallenge, state); + ssoCode = ssoParams.ssoCode; + orgIdentifier = ssoParams.orgIdentifier; + } catch { + return Response.badRequest("Something went wrong. Try again."); + } + } else { + if ((email == null || email === "") && this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "email", + message: "Email address:", + }); + email = answer.email; + } + if (email == null || email.trim() === "") { + return Response.badRequest("Email address is required."); + } + if (email.indexOf("@") === -1) { + return Response.badRequest("Email address is invalid."); + } + this.email = email; + + if (password == null || password === "") { + if (options.passwordfile) { + password = await NodeUtils.readFirstLine(options.passwordfile); + } else if (options.passwordenv && process.env[options.passwordenv]) { + password = process.env[options.passwordenv]; + } else if (this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "password", + name: "password", + message: "Master password:", + }); + password = answer.password; + } + } + + if (password == null || password === "") { + return Response.badRequest("Master password is required."); + } + } + + let twoFactorToken: string = options.code; + let twoFactorMethod: TwoFactorProviderType = null; + try { + if (options.method != null) { + twoFactorMethod = parseInt(options.method, null); + } + } catch (e) { + return Response.error("Invalid two-step login method."); + } + + const twoFactor = + twoFactorToken == null + ? null + : new TokenTwoFactorRequest(twoFactorMethod, twoFactorToken, false); + + try { + await this.validatedParams(); + + let response: AuthResult = null; + if (clientId != null && clientSecret != null) { + if (!clientId.startsWith("user")) { + return Response.error("Invalid API Key; Organization API Key currently not supported"); + } + response = await this.authService.logIn( + new UserApiLogInCredentials(clientId, clientSecret) + ); + } else if (ssoCode != null && ssoCodeVerifier != null) { + response = await this.authService.logIn( + new SsoLogInCredentials( + ssoCode, + ssoCodeVerifier, + this.ssoRedirectUri, + orgIdentifier, + twoFactor + ) + ); + } else { + response = await this.authService.logIn( + new PasswordLogInCredentials(email, password, null, twoFactor) + ); + } + if (response.captchaSiteKey) { + const credentials = new PasswordLogInCredentials(email, password); + const handledResponse = await this.handleCaptchaRequired(twoFactor, credentials); + + // Error Response + if (handledResponse instanceof Response) { + return handledResponse; + } else { + response = handledResponse; + } + } + if (response.requiresTwoFactor) { + const twoFactorProviders = this.twoFactorService.getSupportedProviders(null); + if (twoFactorProviders.length === 0) { + return Response.badRequest("No providers available for this client."); + } + + if (twoFactorMethod != null) { + try { + selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; + } catch (e) { + return Response.error("Invalid two-step login method."); + } + } + + if (selectedProvider == null) { + if (twoFactorProviders.length === 1) { + selectedProvider = twoFactorProviders[0]; + } else if (this.canInteract) { + const twoFactorOptions: (string | Separator)[] = twoFactorProviders.map((p) => p.name); + twoFactorOptions.push(new inquirer.Separator()); + twoFactorOptions.push("Cancel"); + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "list", + name: "method", + message: "Two-step login method:", + choices: twoFactorOptions, + }); + const i = twoFactorOptions.indexOf(answer.method); + if (i === twoFactorOptions.length - 1) { + return Response.error("Login failed."); + } + selectedProvider = twoFactorProviders[i]; + } + if (selectedProvider == null) { + return Response.error("Login failed. No provider selected."); + } + } + + if ( + twoFactorToken == null && + response.twoFactorProviders.size > 1 && + selectedProvider.type === TwoFactorProviderType.Email + ) { + const emailReq = new TwoFactorEmailRequest(); + emailReq.email = this.authService.email; + emailReq.masterPasswordHash = this.authService.masterPasswordHash; + await this.apiService.postTwoFactorEmail(emailReq); + } + + if (twoFactorToken == null) { + if (this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "token", + message: "Two-step login code:", + }); + twoFactorToken = answer.token; + } + if (twoFactorToken == null || twoFactorToken === "") { + return Response.badRequest("Code is required."); + } + } + + response = await this.authService.logInTwoFactor( + new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken), + null + ); + } + + if (response.captchaSiteKey) { + const twoFactorRequest = new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken); + const handledResponse = await this.handleCaptchaRequired(twoFactorRequest); + + // Error Response + if (handledResponse instanceof Response) { + return handledResponse; + } else { + response = handledResponse; + } + } + + if (response.requiresTwoFactor) { + return Response.error("Login failed."); + } + + if (response.resetMasterPassword) { + return Response.error( + "In order to log in with SSO from the CLI, you must first log in" + + " through the web vault to set your master password." + ); + } + + // Handle Updating Temp Password if NOT using an API Key for authentication + if (response.forcePasswordReset && clientId == null && clientSecret == null) { + return await this.updateTempPassword(); + } + + return await this.handleSuccessResponse(); + } catch (e) { + return Response.error(e); + } + } + + private async validatedParams() { + const key = await this.cryptoFunctionService.randomBytes(64); + process.env.BW_SESSION = Utils.fromBufferToB64(key); + } + + private async handleSuccessResponse(): Promise { + await this.syncService.fullSync(true); + + const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); + + if ( + (this.options.sso != null || this.options.apikey != null) && + this.canInteract && + !usesKeyConnector + ) { + const res = new MessageResponse( + "You are logged in!", + "\n" + "To unlock your vault, use the `unlock` command. ex:\n" + "$ bw unlock" + ); + return Response.success(res); + } + + const res = new MessageResponse( + "You are logged in!", + "\n" + + "To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n" + + '$ export BW_SESSION="' + + process.env.BW_SESSION + + '"\n' + + '> $env:BW_SESSION="' + + process.env.BW_SESSION + + '"\n\n' + + "You can also pass the session key to any command with the `--session` option. ex:\n" + + "$ bw list items --session " + + process.env.BW_SESSION + ); + res.raw = process.env.BW_SESSION; + return Response.success(res); + } + + private async updateTempPassword(error?: string): Promise { + // If no interaction available, alert user to use web vault + if (!this.canInteract) { + await this.logoutCallback(); + this.authService.logOut(() => { + /* Do nothing */ + }); + return Response.error( + new MessageResponse( + "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now via the web vault. You have been logged out.", + null + ) + ); + } + + if (this.email == null || this.email === "undefined") { + this.email = await this.stateService.getEmail(); + } + + // Get New Master Password + const baseMessage = + "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now.\n" + + "Master password: "; + const firstMessage = error != null ? error + baseMessage : baseMessage; + const mp: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ + type: "password", + name: "password", + message: firstMessage, + }); + const masterPassword = mp.password; + + // Master Password Validation + if (masterPassword == null || masterPassword === "") { + return this.updateTempPassword("Master password is required.\n"); + } + + if (masterPassword.length < 8) { + return this.updateTempPassword("Master password must be at least 8 characters long.\n"); + } + + // Strength & Policy Validation + const strengthResult = this.passwordGenerationService.passwordStrength( + masterPassword, + this.getPasswordStrengthUserInput() + ); + + // Get New Master Password Re-type + const reTypeMessage = "Re-type New Master password (Strength: " + strengthResult.score + ")"; + const retype: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ + type: "password", + name: "password", + message: reTypeMessage, + }); + const masterPasswordRetype = retype.password; + + // Re-type Validation + if (masterPassword !== masterPasswordRetype) { + return this.updateTempPassword("Master password confirmation does not match.\n"); + } + + // Get Hint (optional) + const hint: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ + type: "input", + name: "input", + message: "Master Password Hint (optional):", + }); + const masterPasswordHint = hint.input; + + // Retrieve details for key generation + const enforcedPolicyOptions = await firstValueFrom( + this.policyService.masterPasswordPolicyOptions$() + ); + const kdf = await this.stateService.getKdfType(); + const kdfIterations = await this.stateService.getKdfIterations(); + + if ( + enforcedPolicyOptions != null && + !this.policyService.evaluateMasterPassword( + strengthResult.score, + masterPassword, + enforcedPolicyOptions + ) + ) { + return this.updateTempPassword( + "Your new master password does not meet the policy requirements.\n" + ); + } + + try { + // Create new key and hash new password + const newKey = await this.cryptoService.makeKey( + masterPassword, + this.email.trim().toLowerCase(), + kdf, + kdfIterations + ); + const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey); + + // Grab user's current enc key + const userEncKey = await this.cryptoService.getEncKey(); + + // Create new encKey for the User + const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey); + + // Create request + const request = new UpdateTempPasswordRequest(); + request.key = newEncKey[1].encryptedString; + request.newMasterPasswordHash = newPasswordHash; + request.masterPasswordHint = masterPasswordHint; + + // Update user's password + await this.apiService.putUpdateTempPassword(request); + return this.handleSuccessResponse(); + } catch (e) { + await this.logoutCallback(); + this.authService.logOut(() => { + /* Do nothing */ + }); + return Response.error(e); + } + } + + private async handleCaptchaRequired( + twoFactorRequest: TokenTwoFactorRequest, + credentials: PasswordLogInCredentials = null + ): Promise { + const badCaptcha = Response.badRequest( + "Your authentication request has been flagged and will require user interaction to proceed.\n" + + "Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" + + "(https://bitwarden.com/help/cli-auth-challenges)" + ); + + try { + const captchaClientSecret = await this.apiClientSecret(true); + if (Utils.isNullOrWhitespace(captchaClientSecret)) { + return badCaptcha; + } + + let authResultResponse: AuthResult = null; + if (credentials != null) { + credentials.captchaToken = captchaClientSecret; + credentials.twoFactor = twoFactorRequest; + authResultResponse = await this.authService.logIn(credentials); + } else { + authResultResponse = await this.authService.logInTwoFactor( + twoFactorRequest, + captchaClientSecret + ); + } + + return authResultResponse; + } catch (e) { + if ( + e instanceof ErrorResponse || + (e.constructor.name === ErrorResponse.name && + (e as ErrorResponse).message.includes("Captcha is invalid")) + ) { + return badCaptcha; + } else { + return Response.error(e); + } + } + } + + private getPasswordStrengthUserInput() { + let userInput: string[] = []; + const atPosition = this.email.indexOf("@"); + if (atPosition > -1) { + userInput = userInput.concat( + this.email + .substr(0, atPosition) + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/) + ); + } + return userInput; + } + + private async apiClientId(): Promise { + let clientId: string = null; + + const storedClientId: string = process.env.BW_CLIENTID; + if (storedClientId == null) { + if (this.canInteract) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "clientId", + message: "client_id:", + }); + clientId = answer.clientId; + } else { + clientId = null; + } + } else { + clientId = storedClientId; + } + + return clientId; + } + + private async apiClientSecret(isAdditionalAuthentication = false): Promise { + const additionalAuthenticationMessage = "Additional authentication required.\nAPI key "; + let clientSecret: string = null; + + const storedClientSecret: string = this.clientSecret || process.env.BW_CLIENTSECRET; + if (this.canInteract && storedClientSecret == null) { + const answer: inquirer.Answers = await inquirer.createPromptModule({ + output: process.stderr, + })({ + type: "input", + name: "clientSecret", + message: + (isAdditionalAuthentication ? additionalAuthenticationMessage : "") + "client_secret:", + }); + clientSecret = answer.clientSecret; + } else { + clientSecret = storedClientSecret; + } + + return clientSecret; + } + + private async apiIdentifiers(): Promise<{ clientId: string; clientSecret: string }> { + return { + clientId: await this.apiClientId(), + clientSecret: await this.apiClientSecret(), + }; + } + + private async openSsoPrompt( + codeChallenge: string, + state: string + ): Promise<{ ssoCode: string; orgIdentifier: string }> { + return new Promise((resolve, reject) => { + const callbackServer = http.createServer((req, res) => { + const urlString = "http://localhost" + req.url; + const url = new URL(urlString); + const code = url.searchParams.get("code"); + const receivedState = url.searchParams.get("state"); + const orgIdentifier = this.getOrgIdentifierFromState(receivedState); + res.setHeader("Content-Type", "text/html"); + if (code != null && receivedState != null && this.checkState(receivedState, state)) { + res.writeHead(200); + res.end( + "Success | Bitwarden CLI" + + "

Successfully authenticated with the Bitwarden CLI

" + + "

You may now close this tab and return to the terminal.

" + + "" + ); + callbackServer.close(() => + resolve({ + ssoCode: code, + orgIdentifier: orgIdentifier, + }) + ); + } else { + res.writeHead(400); + res.end( + "Failed | Bitwarden CLI" + + "

Something went wrong logging into the Bitwarden CLI

" + + "

You may now close this tab and return to the terminal.

" + + "" + ); + callbackServer.close(() => reject()); + } + }); + let foundPort = false; + const webUrl = this.environmentService.getWebVaultUrl(); + for (let port = 8065; port <= 8070; port++) { + try { + this.ssoRedirectUri = "http://localhost:" + port; + callbackServer.listen(port, () => { + this.platformUtilsService.launchUri( + webUrl + + "/#/sso?clientId=" + + "cli" + + "&redirectUri=" + + encodeURIComponent(this.ssoRedirectUri) + + "&state=" + + state + + "&codeChallenge=" + + codeChallenge + ); + }); + foundPort = true; + break; + } catch { + // Ignore error since we run the same command up to 5 times. + } + } + if (!foundPort) { + reject(); + } + }); + } + + private getOrgIdentifierFromState(state: string): string { + if (state === null || state === undefined) { + return null; + } + + const stateSplit = state.split("_identifier="); + return stateSplit.length > 1 ? stateSplit[1] : null; + } + + private checkState(state: string, checkState: string): boolean { + if (state === null || state === undefined) { + return false; + } + if (checkState === null || checkState === undefined) { + return false; + } + + const stateSplit = state.split("_identifier="); + const checkStateSplit = checkState.split("_identifier="); + return stateSplit[0] === checkStateSplit[0]; } } diff --git a/libs/node/src/cli/commands/logout.command.ts b/apps/cli/src/commands/logout.command.ts similarity index 89% rename from libs/node/src/cli/commands/logout.command.ts rename to apps/cli/src/commands/logout.command.ts index 9f8a544410a..3de76275761 100644 --- a/libs/node/src/cli/commands/logout.command.ts +++ b/apps/cli/src/commands/logout.command.ts @@ -2,7 +2,7 @@ import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { Response } from "../models/response"; -import { MessageResponse } from "../models/response/messageResponse"; +import { MessageResponse } from "../models/response/message.response"; export class LogoutCommand { constructor( diff --git a/apps/cli/src/commands/restore.command.ts b/apps/cli/src/commands/restore.command.ts index 75e50dabfd1..48e6b2aa9cc 100644 --- a/apps/cli/src/commands/restore.command.ts +++ b/apps/cli/src/commands/restore.command.ts @@ -1,5 +1,6 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; -import { Response } from "@bitwarden/node/cli/models/response"; + +import { Response } from "../models/response"; export class RestoreCommand { constructor(private cipherService: CipherService) {} diff --git a/apps/cli/src/commands/send/create.command.ts b/apps/cli/src/commands/send/create.command.ts index cfea2e863cf..2fab074f443 100644 --- a/apps/cli/src/commands/send/create.command.ts +++ b/apps/cli/src/commands/send/create.command.ts @@ -6,10 +6,10 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SendType } from "@bitwarden/common/enums/sendType"; import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; -import { SendTextResponse } from "../../models/response/sendTextResponse"; +import { Response } from "../../models/response"; +import { SendTextResponse } from "../../models/response/send-text.response"; +import { SendResponse } from "../../models/response/send.response"; import { CliUtils } from "../../utils"; export class SendCreateCommand { diff --git a/apps/cli/src/commands/send/delete.command.ts b/apps/cli/src/commands/send/delete.command.ts index 71778a6fc8c..6138382565c 100644 --- a/apps/cli/src/commands/send/delete.command.ts +++ b/apps/cli/src/commands/send/delete.command.ts @@ -1,5 +1,6 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; -import { Response } from "@bitwarden/node/cli/models/response"; + +import { Response } from "../../models/response"; export class SendDeleteCommand { constructor(private sendService: SendService) {} diff --git a/apps/cli/src/commands/send/edit.command.ts b/apps/cli/src/commands/send/edit.command.ts index bb4d467eafe..4fc92fb89a1 100644 --- a/apps/cli/src/commands/send/edit.command.ts +++ b/apps/cli/src/commands/send/edit.command.ts @@ -1,9 +1,9 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SendType } from "@bitwarden/common/enums/sendType"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; +import { Response } from "../../models/response"; +import { SendResponse } from "../../models/response/send.response"; import { CliUtils } from "../../utils"; import { SendGetCommand } from "./get.command"; diff --git a/apps/cli/src/commands/send/get.command.ts b/apps/cli/src/commands/send/get.command.ts index aeae1203a61..37367276a8d 100644 --- a/apps/cli/src/commands/send/get.command.ts +++ b/apps/cli/src/commands/send/get.command.ts @@ -6,9 +6,9 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SendService } from "@bitwarden/common/abstractions/send.service"; import { Utils } from "@bitwarden/common/misc/utils"; import { SendView } from "@bitwarden/common/models/view/send.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; +import { Response } from "../../models/response"; +import { SendResponse } from "../../models/response/send.response"; import { DownloadCommand } from "../download.command"; export class SendGetCommand extends DownloadCommand { diff --git a/apps/cli/src/commands/send/list.command.ts b/apps/cli/src/commands/send/list.command.ts index 149c34d69d1..c37040b2550 100644 --- a/apps/cli/src/commands/send/list.command.ts +++ b/apps/cli/src/commands/send/list.command.ts @@ -1,10 +1,10 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { SendService } from "@bitwarden/common/abstractions/send.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { ListResponse } from "@bitwarden/node/cli/models/response/listResponse"; -import { SendResponse } from "../..//models/response/sendResponse"; +import { Response } from "../../models/response"; +import { ListResponse } from "../../models/response/list.response"; +import { SendResponse } from "../../models/response/send.response"; export class SendListCommand { constructor( diff --git a/apps/cli/src/commands/send/receive.command.ts b/apps/cli/src/commands/send/receive.command.ts index 9a9803e57f5..824f7d1a930 100644 --- a/apps/cli/src/commands/send/receive.command.ts +++ b/apps/cli/src/commands/send/receive.command.ts @@ -14,9 +14,9 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-cr import { SendAccessRequest } from "@bitwarden/common/models/request/send-access.request"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { SendAccessView } from "@bitwarden/common/models/view/send-access.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendAccessResponse } from "../../models/response/sendAccessResponse"; +import { Response } from "../../models/response"; +import { SendAccessResponse } from "../../models/response/send-access.response"; import { DownloadCommand } from "../download.command"; export class SendReceiveCommand extends DownloadCommand { diff --git a/apps/cli/src/commands/send/removePassword.command.ts b/apps/cli/src/commands/send/remove-password.command.ts similarity index 79% rename from apps/cli/src/commands/send/removePassword.command.ts rename to apps/cli/src/commands/send/remove-password.command.ts index 53e82b7d722..53805fa003c 100644 --- a/apps/cli/src/commands/send/removePassword.command.ts +++ b/apps/cli/src/commands/send/remove-password.command.ts @@ -1,7 +1,7 @@ import { SendService } from "@bitwarden/common/abstractions/send.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { SendResponse } from "../../models/response/sendResponse"; +import { Response } from "../../models/response"; +import { SendResponse } from "../../models/response/send.response"; export class SendRemovePasswordCommand { constructor(private sendService: SendService) {} diff --git a/apps/cli/src/commands/serve.command.ts b/apps/cli/src/commands/serve.command.ts index 9b2ea77784c..ddba5acbb7a 100644 --- a/apps/cli/src/commands/serve.command.ts +++ b/apps/cli/src/commands/serve.command.ts @@ -7,10 +7,10 @@ import * as koaJson from "koa-json"; import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { FileResponse } from "@bitwarden/node/cli/models/response/fileResponse"; import { Main } from "../bw"; +import { Response } from "../models/response"; +import { FileResponse } from "../models/response/file.response"; import { ConfirmCommand } from "./confirm.command"; import { CreateCommand } from "./create.command"; @@ -26,7 +26,7 @@ import { SendDeleteCommand } from "./send/delete.command"; import { SendEditCommand } from "./send/edit.command"; import { SendGetCommand } from "./send/get.command"; import { SendListCommand } from "./send/list.command"; -import { SendRemovePasswordCommand } from "./send/removePassword.command"; +import { SendRemovePasswordCommand } from "./send/remove-password.command"; import { ShareCommand } from "./share.command"; import { StatusCommand } from "./status.command"; import { SyncCommand } from "./sync.command"; diff --git a/apps/cli/src/commands/share.command.ts b/apps/cli/src/commands/share.command.ts index 8766baea8f7..c04760b9d93 100644 --- a/apps/cli/src/commands/share.command.ts +++ b/apps/cli/src/commands/share.command.ts @@ -1,7 +1,7 @@ import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { CipherResponse } from "../models/response/cipherResponse"; +import { Response } from "../models/response"; +import { CipherResponse } from "../models/response/cipher.response"; import { CliUtils } from "../utils"; export class ShareCommand { diff --git a/apps/cli/src/commands/status.command.ts b/apps/cli/src/commands/status.command.ts index 1b8d5fa3f56..7cd109309c1 100644 --- a/apps/cli/src/commands/status.command.ts +++ b/apps/cli/src/commands/status.command.ts @@ -3,9 +3,9 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s import { StateService } from "@bitwarden/common/abstractions/state.service"; import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { TemplateResponse } from "../models/response/templateResponse"; +import { Response } from "../models/response"; +import { TemplateResponse } from "../models/response/template.response"; export class StatusCommand { constructor( diff --git a/apps/cli/src/commands/sync.command.ts b/apps/cli/src/commands/sync.command.ts index 6ba4166826f..3d72b6b89ea 100644 --- a/apps/cli/src/commands/sync.command.ts +++ b/apps/cli/src/commands/sync.command.ts @@ -1,8 +1,8 @@ import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; -import { StringResponse } from "@bitwarden/node/cli/models/response/stringResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; +import { StringResponse } from "../models/response/string.response"; import { CliUtils } from "../utils"; export class SyncCommand { diff --git a/apps/cli/src/commands/unlock.command.ts b/apps/cli/src/commands/unlock.command.ts index 83de7a0b1ce..7e2136e6e3e 100644 --- a/apps/cli/src/commands/unlock.command.ts +++ b/apps/cli/src/commands/unlock.command.ts @@ -10,12 +10,12 @@ import { HashPurpose } from "@bitwarden/common/enums/hashPurpose"; import { Utils } from "@bitwarden/common/misc/utils"; import { SecretVerificationRequest } from "@bitwarden/common/models/request/secret-verification.request"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; import { CliUtils } from "../utils"; -import { ConvertToKeyConnectorCommand } from "./convertToKeyConnector.command"; +import { ConvertToKeyConnectorCommand } from "./convert-to-key-connector.command"; export class UnlockCommand { constructor( diff --git a/libs/node/src/cli/commands/update.command.ts b/apps/cli/src/commands/update.command.ts similarity index 97% rename from libs/node/src/cli/commands/update.command.ts rename to apps/cli/src/commands/update.command.ts index 534e11de47d..b058feb72b8 100644 --- a/libs/node/src/cli/commands/update.command.ts +++ b/apps/cli/src/commands/update.command.ts @@ -4,7 +4,7 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { Response } from "../models/response"; -import { MessageResponse } from "../models/response/messageResponse"; +import { MessageResponse } from "../models/response/message.response"; export class UpdateCommand { inPkg = false; diff --git a/apps/cli/src/models/request/organizationCollectionRequest.ts b/apps/cli/src/models/request/organization-collection.request.ts similarity index 89% rename from apps/cli/src/models/request/organizationCollectionRequest.ts rename to apps/cli/src/models/request/organization-collection.request.ts index 7b9f184d855..7546d116092 100644 --- a/apps/cli/src/models/request/organizationCollectionRequest.ts +++ b/apps/cli/src/models/request/organization-collection.request.ts @@ -1,6 +1,6 @@ import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; -import { SelectionReadOnly } from "../selectionReadOnly"; +import { SelectionReadOnly } from "../selection-read-only"; export class OrganizationCollectionRequest extends CollectionExport { static template(): OrganizationCollectionRequest { diff --git a/libs/node/src/cli/models/response.ts b/apps/cli/src/models/response.ts similarity index 95% rename from libs/node/src/cli/models/response.ts rename to apps/cli/src/models/response.ts index d768c51fdeb..ad70c4c8df1 100644 --- a/libs/node/src/cli/models/response.ts +++ b/apps/cli/src/models/response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./response/baseResponse"; +import { BaseResponse } from "./response/base.response"; export class Response { static error(error: any, data?: any): Response { diff --git a/apps/cli/src/models/response/attachmentResponse.ts b/apps/cli/src/models/response/attachment.response.ts similarity index 100% rename from apps/cli/src/models/response/attachmentResponse.ts rename to apps/cli/src/models/response/attachment.response.ts diff --git a/libs/node/src/cli/models/response/baseResponse.ts b/apps/cli/src/models/response/base.response.ts similarity index 100% rename from libs/node/src/cli/models/response/baseResponse.ts rename to apps/cli/src/models/response/base.response.ts diff --git a/apps/cli/src/models/response/cipherResponse.ts b/apps/cli/src/models/response/cipher.response.ts similarity index 80% rename from apps/cli/src/models/response/cipherResponse.ts rename to apps/cli/src/models/response/cipher.response.ts index f1e83ac30e2..182bff6e7f2 100644 --- a/apps/cli/src/models/response/cipherResponse.ts +++ b/apps/cli/src/models/response/cipher.response.ts @@ -1,11 +1,11 @@ import { CipherType } from "@bitwarden/common/enums/cipherType"; import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; -import { AttachmentResponse } from "./attachmentResponse"; -import { LoginResponse } from "./loginResponse"; -import { PasswordHistoryResponse } from "./passwordHistoryResponse"; +import { AttachmentResponse } from "./attachment.response"; +import { BaseResponse } from "./base.response"; +import { LoginResponse } from "./login.response"; +import { PasswordHistoryResponse } from "./password-history.response"; export class CipherResponse extends CipherWithIdExport implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/collectionResponse.ts b/apps/cli/src/models/response/collection.response.ts similarity index 82% rename from apps/cli/src/models/response/collectionResponse.ts rename to apps/cli/src/models/response/collection.response.ts index 1818c946e35..4275415233f 100644 --- a/apps/cli/src/models/response/collectionResponse.ts +++ b/apps/cli/src/models/response/collection.response.ts @@ -1,6 +1,7 @@ import { CollectionWithIdExport } from "@bitwarden/common/models/export/collection-with-id.export"; import { CollectionView } from "@bitwarden/common/models/view/collection.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class CollectionResponse extends CollectionWithIdExport implements BaseResponse { object: string; diff --git a/libs/node/src/cli/models/response/fileResponse.ts b/apps/cli/src/models/response/file.response.ts similarity index 83% rename from libs/node/src/cli/models/response/fileResponse.ts rename to apps/cli/src/models/response/file.response.ts index c16b56e0912..487cb270e2d 100644 --- a/libs/node/src/cli/models/response/fileResponse.ts +++ b/apps/cli/src/models/response/file.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class FileResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/folderResponse.ts b/apps/cli/src/models/response/folder.response.ts similarity index 81% rename from apps/cli/src/models/response/folderResponse.ts rename to apps/cli/src/models/response/folder.response.ts index 196071fbab7..5a73d7feb28 100644 --- a/apps/cli/src/models/response/folderResponse.ts +++ b/apps/cli/src/models/response/folder.response.ts @@ -1,6 +1,7 @@ import { FolderWithIdExport } from "@bitwarden/common/models/export/folder-with-id.export"; import { FolderView } from "@bitwarden/common/models/view/folder.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class FolderResponse extends FolderWithIdExport implements BaseResponse { object: string; diff --git a/libs/node/src/cli/models/response/listResponse.ts b/apps/cli/src/models/response/list.response.ts similarity index 79% rename from libs/node/src/cli/models/response/listResponse.ts rename to apps/cli/src/models/response/list.response.ts index db415bcdb30..da6e7f0999d 100644 --- a/libs/node/src/cli/models/response/listResponse.ts +++ b/apps/cli/src/models/response/list.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class ListResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/loginResponse.ts b/apps/cli/src/models/response/login.response.ts similarity index 100% rename from apps/cli/src/models/response/loginResponse.ts rename to apps/cli/src/models/response/login.response.ts diff --git a/libs/node/src/cli/models/response/messageResponse.ts b/apps/cli/src/models/response/message.response.ts similarity index 85% rename from libs/node/src/cli/models/response/messageResponse.ts rename to apps/cli/src/models/response/message.response.ts index 8612841ac1e..1440f97b646 100644 --- a/libs/node/src/cli/models/response/messageResponse.ts +++ b/apps/cli/src/models/response/message.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class MessageResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/organizationCollectionResponse.ts b/apps/cli/src/models/response/organization-collection.response.ts similarity index 73% rename from apps/cli/src/models/response/organizationCollectionResponse.ts rename to apps/cli/src/models/response/organization-collection.response.ts index 811e4b2a57d..98b5b09c587 100644 --- a/apps/cli/src/models/response/organizationCollectionResponse.ts +++ b/apps/cli/src/models/response/organization-collection.response.ts @@ -1,8 +1,8 @@ import { CollectionView } from "@bitwarden/common/models/view/collection.view"; -import { SelectionReadOnly } from "../selectionReadOnly"; +import { SelectionReadOnly } from "../selection-read-only"; -import { CollectionResponse } from "./collectionResponse"; +import { CollectionResponse } from "./collection.response"; export class OrganizationCollectionResponse extends CollectionResponse { groups: SelectionReadOnly[]; diff --git a/apps/cli/src/models/response/organizationUserResponse.ts b/apps/cli/src/models/response/organization-user.response.ts similarity index 85% rename from apps/cli/src/models/response/organizationUserResponse.ts rename to apps/cli/src/models/response/organization-user.response.ts index 962fdb23bcd..92a8e63821a 100644 --- a/apps/cli/src/models/response/organizationUserResponse.ts +++ b/apps/cli/src/models/response/organization-user.response.ts @@ -1,6 +1,7 @@ import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class OrganizationUserResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/organizationResponse.ts b/apps/cli/src/models/response/organization.response.ts similarity index 89% rename from apps/cli/src/models/response/organizationResponse.ts rename to apps/cli/src/models/response/organization.response.ts index 989623e35f0..1b100687f26 100644 --- a/apps/cli/src/models/response/organizationResponse.ts +++ b/apps/cli/src/models/response/organization.response.ts @@ -1,7 +1,8 @@ import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { OrganizationUserType } from "@bitwarden/common/enums/organizationUserType"; import { Organization } from "@bitwarden/common/models/domain/organization"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; + +import { BaseResponse } from "./base.response"; export class OrganizationResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/passwordHistoryResponse.ts b/apps/cli/src/models/response/password-history.response.ts similarity index 100% rename from apps/cli/src/models/response/passwordHistoryResponse.ts rename to apps/cli/src/models/response/password-history.response.ts diff --git a/apps/cli/src/models/response/sendAccessResponse.ts b/apps/cli/src/models/response/send-access.response.ts similarity index 82% rename from apps/cli/src/models/response/sendAccessResponse.ts rename to apps/cli/src/models/response/send-access.response.ts index a37fd479b7e..18644d67492 100644 --- a/apps/cli/src/models/response/sendAccessResponse.ts +++ b/apps/cli/src/models/response/send-access.response.ts @@ -1,9 +1,9 @@ import { SendType } from "@bitwarden/common/enums/sendType"; import { SendAccessView } from "@bitwarden/common/models/view/send-access.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; -import { SendFileResponse } from "./sendFileResponse"; -import { SendTextResponse } from "./sendTextResponse"; +import { BaseResponse } from "./base.response"; +import { SendFileResponse } from "./send-file.response"; +import { SendTextResponse } from "./send-text.response"; export class SendAccessResponse implements BaseResponse { static template(): SendAccessResponse { diff --git a/apps/cli/src/models/response/sendFileResponse.ts b/apps/cli/src/models/response/send-file.response.ts similarity index 100% rename from apps/cli/src/models/response/sendFileResponse.ts rename to apps/cli/src/models/response/send-file.response.ts diff --git a/apps/cli/src/models/response/sendTextResponse.ts b/apps/cli/src/models/response/send-text.response.ts similarity index 100% rename from apps/cli/src/models/response/sendTextResponse.ts rename to apps/cli/src/models/response/send-text.response.ts diff --git a/apps/cli/src/models/response/sendResponse.ts b/apps/cli/src/models/response/send.response.ts similarity index 95% rename from apps/cli/src/models/response/sendResponse.ts rename to apps/cli/src/models/response/send.response.ts index c2e47fa740e..6d9f555312e 100644 --- a/apps/cli/src/models/response/sendResponse.ts +++ b/apps/cli/src/models/response/send.response.ts @@ -1,10 +1,10 @@ import { SendType } from "@bitwarden/common/enums/sendType"; import { Utils } from "@bitwarden/common/misc/utils"; import { SendView } from "@bitwarden/common/models/view/send.view"; -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; -import { SendFileResponse } from "./sendFileResponse"; -import { SendTextResponse } from "./sendTextResponse"; +import { BaseResponse } from "./base.response"; +import { SendFileResponse } from "./send-file.response"; +import { SendTextResponse } from "./send-text.response"; const dateProperties: string[] = [ Utils.nameOf("deletionDate"), diff --git a/libs/node/src/cli/models/response/stringResponse.ts b/apps/cli/src/models/response/string.response.ts similarity index 78% rename from libs/node/src/cli/models/response/stringResponse.ts rename to apps/cli/src/models/response/string.response.ts index f4becfe84ca..49bb299b669 100644 --- a/libs/node/src/cli/models/response/stringResponse.ts +++ b/apps/cli/src/models/response/string.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "./baseResponse"; +import { BaseResponse } from "./base.response"; export class StringResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/response/templateResponse.ts b/apps/cli/src/models/response/template.response.ts similarity index 70% rename from apps/cli/src/models/response/templateResponse.ts rename to apps/cli/src/models/response/template.response.ts index 15944b0780e..86dc0b15886 100644 --- a/apps/cli/src/models/response/templateResponse.ts +++ b/apps/cli/src/models/response/template.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "@bitwarden/node/cli/models/response/baseResponse"; +import { BaseResponse } from "./base.response"; export class TemplateResponse implements BaseResponse { object: string; diff --git a/apps/cli/src/models/selectionReadOnly.ts b/apps/cli/src/models/selection-read-only.ts similarity index 100% rename from apps/cli/src/models/selectionReadOnly.ts rename to apps/cli/src/models/selection-read-only.ts diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index f80a87068b1..41a99dcd9bc 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -3,11 +3,6 @@ import * as program from "commander"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions"; -import { BaseProgram } from "@bitwarden/node/cli/baseProgram"; -import { LogoutCommand } from "@bitwarden/node/cli/commands/logout.command"; -import { UpdateCommand } from "@bitwarden/node/cli/commands/update.command"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; import { Main } from "./bw"; import { CompletionCommand } from "./commands/completion.command"; @@ -16,19 +11,23 @@ import { EncodeCommand } from "./commands/encode.command"; import { GenerateCommand } from "./commands/generate.command"; import { LockCommand } from "./commands/lock.command"; import { LoginCommand } from "./commands/login.command"; +import { LogoutCommand } from "./commands/logout.command"; import { ServeCommand } from "./commands/serve.command"; import { StatusCommand } from "./commands/status.command"; import { SyncCommand } from "./commands/sync.command"; import { UnlockCommand } from "./commands/unlock.command"; -import { TemplateResponse } from "./models/response/templateResponse"; +import { UpdateCommand } from "./commands/update.command"; +import { Response } from "./models/response"; +import { ListResponse } from "./models/response/list.response"; +import { MessageResponse } from "./models/response/message.response"; +import { StringResponse } from "./models/response/string.response"; +import { TemplateResponse } from "./models/response/template.response"; import { CliUtils } from "./utils"; const writeLn = CliUtils.writeLn; -export class Program extends BaseProgram { - constructor(protected main: Main) { - super(main.stateService, writeLn); - } +export class Program { + constructor(protected main: Main) {} async register() { program @@ -144,7 +143,6 @@ export class Program extends BaseProgram { this.main.authService, this.main.apiService, this.main.cryptoFunctionService, - this.main.i18nService, this.main.environmentService, this.main.passwordGenerationService, this.main.platformUtilsService, @@ -502,12 +500,102 @@ export class Program extends BaseProgram { } protected processResponse(response: Response, exitImmediately = false) { - super.processResponse(response, exitImmediately, () => { - if (response.data.object === "template") { - return this.getJson((response.data as TemplateResponse).template); + if (!response.success) { + if (process.env.BW_QUIET !== "true") { + if (process.env.BW_RESPONSE === "true") { + writeLn(this.getJson(response), true, false); + } else { + writeLn(chalk.redBright(response.message), true, true); + } } - return null; - }); + const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; + if (exitImmediately) { + process.exit(exitCode); + } else { + process.exitCode = exitCode; + } + return; + } + + if (process.env.BW_RESPONSE === "true") { + writeLn(this.getJson(response), true, false); + } else if (response.data != null) { + let out: string = null; + + if (response.data.object === "template") { + out = this.getJson((response.data as TemplateResponse).template); + } + + if (out == null) { + if (response.data.object === "string") { + const data = (response.data as StringResponse).data; + if (data != null) { + out = data; + } + } else if (response.data.object === "list") { + out = this.getJson((response.data as ListResponse).data); + } else if (response.data.object === "message") { + out = this.getMessage(response); + } else { + out = this.getJson(response.data); + } + } + + if (out != null && process.env.BW_QUIET !== "true") { + writeLn(out, true, false); + } + } + if (exitImmediately) { + process.exit(0); + } else { + process.exitCode = 0; + } + } + + private getJson(obj: any): string { + if (process.env.BW_PRETTY === "true") { + return JSON.stringify(obj, null, " "); + } else { + return JSON.stringify(obj); + } + } + + private getMessage(response: Response): string { + const message = response.data as MessageResponse; + if (process.env.BW_RAW === "true") { + return message.raw; + } + + let out = ""; + if (message.title != null) { + if (message.noColor) { + out = message.title; + } else { + out = chalk.greenBright(message.title); + } + } + if (message.message != null) { + if (message.title != null) { + out += "\n"; + } + out += message.message; + } + return out.trim() === "" ? null : out; + } + + private async exitIfAuthed() { + const authed = await this.main.stateService.getIsAuthenticated(); + if (authed) { + const email = await this.main.stateService.getEmail(); + this.processResponse(Response.error("You are already logged in as " + email + "."), true); + } + } + + private async exitIfNotAuthed() { + const authed = await this.main.stateService.getIsAuthenticated(); + if (!authed) { + this.processResponse(Response.error("You are not logged in."), true); + } } protected async exitIfLocked() { diff --git a/apps/cli/src/send.program.ts b/apps/cli/src/send.program.ts index f92e2a305e1..78c607f0419 100644 --- a/apps/cli/src/send.program.ts +++ b/apps/cli/src/send.program.ts @@ -6,7 +6,6 @@ import * as program from "commander"; import { SendType } from "@bitwarden/common/enums/sendType"; import { Utils } from "@bitwarden/common/misc/utils"; -import { Response } from "@bitwarden/node/cli/models/response"; import { Main } from "./bw"; import { GetCommand } from "./commands/get.command"; @@ -16,10 +15,11 @@ import { SendEditCommand } from "./commands/send/edit.command"; import { SendGetCommand } from "./commands/send/get.command"; import { SendListCommand } from "./commands/send/list.command"; import { SendReceiveCommand } from "./commands/send/receive.command"; -import { SendRemovePasswordCommand } from "./commands/send/removePassword.command"; -import { SendFileResponse } from "./models/response/sendFileResponse"; -import { SendResponse } from "./models/response/sendResponse"; -import { SendTextResponse } from "./models/response/sendTextResponse"; +import { SendRemovePasswordCommand } from "./commands/send/remove-password.command"; +import { Response } from "./models/response"; +import { SendFileResponse } from "./models/response/send-file.response"; +import { SendTextResponse } from "./models/response/send-text.response"; +import { SendResponse } from "./models/response/send.response"; import { Program } from "./program"; import { CliUtils } from "./utils"; diff --git a/libs/node/src/cli/services/cliPlatformUtils.service.ts b/apps/cli/src/services/cli-platform-utils.service.ts similarity index 100% rename from libs/node/src/cli/services/cliPlatformUtils.service.ts rename to apps/cli/src/services/cli-platform-utils.service.ts diff --git a/libs/node/spec/cli/consoleLog.service.spec.ts b/apps/cli/src/services/console-log.service.spec.ts similarity index 84% rename from libs/node/spec/cli/consoleLog.service.spec.ts rename to apps/cli/src/services/console-log.service.spec.ts index 656a74ca55d..d629b7c1c06 100644 --- a/libs/node/spec/cli/consoleLog.service.spec.ts +++ b/apps/cli/src/services/console-log.service.spec.ts @@ -1,6 +1,6 @@ -import { ConsoleLogService } from "@bitwarden/node/cli/services/consoleLog.service"; +import { interceptConsole, restoreConsole } from "@bitwarden/common/spec/shared/interceptConsole"; -import { interceptConsole, restoreConsole } from "../../../common/spec/shared/interceptConsole"; +import { ConsoleLogService } from "./console-log.service"; let caughtMessage: any = {}; diff --git a/libs/node/src/cli/services/consoleLog.service.ts b/apps/cli/src/services/console-log.service.ts similarity index 100% rename from libs/node/src/cli/services/consoleLog.service.ts rename to apps/cli/src/services/console-log.service.ts diff --git a/libs/node/src/services/lowdbStorage.service.ts b/apps/cli/src/services/lowdb-storage.service.ts similarity index 87% rename from libs/node/src/services/lowdbStorage.service.ts rename to apps/cli/src/services/lowdb-storage.service.ts index 102168f909b..6e096bd3c37 100644 --- a/libs/node/src/services/lowdbStorage.service.ts +++ b/apps/cli/src/services/lowdb-storage.service.ts @@ -3,6 +3,8 @@ import * as path from "path"; import * as lowdb from "lowdb"; import * as FileSync from "lowdb/adapters/FileSync"; +import * as lock from "proper-lockfile"; +import { OperationOptions } from "retry"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service"; @@ -10,6 +12,13 @@ import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; import { sequentialize } from "@bitwarden/common/misc/sequentialize"; import { Utils } from "@bitwarden/common/misc/utils"; +const retries: OperationOptions = { + retries: 50, + minTimeout: 100, + maxTimeout: 250, + factor: 2, +}; + export class LowdbStorageService implements AbstractStorageService { protected dataFilePath: string; private db: lowdb.LowdbSync; @@ -20,7 +29,8 @@ export class LowdbStorageService implements AbstractStorageService { protected logService: LogService, defaults?: any, private dir?: string, - private allowCache = false + private allowCache = false, + private requireLock = false ) { this.defaults = defaults; } @@ -130,8 +140,18 @@ export class LowdbStorageService implements AbstractStorageService { } protected async lockDbFile(action: () => T): Promise { - // Lock methods implemented in clients - return Promise.resolve(action()); + if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) { + this.logService.info("acquiring db file lock"); + return await lock.lock(this.dataFilePath, { retries: retries }).then((release) => { + try { + return action(); + } finally { + release(); + } + }); + } else { + return action(); + } } private readForNoCache() { diff --git a/apps/cli/src/services/lowdbStorage.service.ts b/apps/cli/src/services/lowdbStorage.service.ts deleted file mode 100644 index e01a161c518..00000000000 --- a/apps/cli/src/services/lowdbStorage.service.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as lock from "proper-lockfile"; -import { OperationOptions } from "retry"; - -import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { Utils } from "@bitwarden/common/misc/utils"; -import { LowdbStorageService as LowdbStorageServiceBase } from "@bitwarden/node/services/lowdbStorage.service"; - -const retries: OperationOptions = { - retries: 50, - minTimeout: 100, - maxTimeout: 250, - factor: 2, -}; - -export class LowdbStorageService extends LowdbStorageServiceBase { - constructor( - logService: LogService, - defaults?: any, - dir?: string, - allowCache = false, - private requireLock = false - ) { - super(logService, defaults, dir, allowCache); - } - - protected async lockDbFile(action: () => T): Promise { - if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) { - this.logService.info("acquiring db file lock"); - return await lock.lock(this.dataFilePath, { retries: retries }).then((release) => { - try { - return action(); - } finally { - release(); - } - }); - } else { - return action(); - } - } -} diff --git a/libs/node/src/services/nodeApi.service.ts b/apps/cli/src/services/node-api.service.ts similarity index 100% rename from libs/node/src/services/nodeApi.service.ts rename to apps/cli/src/services/node-api.service.ts diff --git a/apps/cli/src/services/nodeEnvSecureStorage.service.ts b/apps/cli/src/services/node-env-secure-storage.service.ts similarity index 100% rename from apps/cli/src/services/nodeEnvSecureStorage.service.ts rename to apps/cli/src/services/node-env-secure-storage.service.ts diff --git a/apps/cli/src/utils.ts b/apps/cli/src/utils.ts index 440d02c25d2..e41c1c7896a 100644 --- a/apps/cli/src/utils.ts +++ b/apps/cli/src/utils.ts @@ -10,8 +10,9 @@ import { Utils } from "@bitwarden/common/misc/utils"; import { Organization } from "@bitwarden/common/models/domain/organization"; import { CollectionView } from "@bitwarden/common/models/view/collection.view"; import { FolderView } from "@bitwarden/common/models/view/folder.view"; -import { Response } from "@bitwarden/node/cli/models/response"; -import { MessageResponse } from "@bitwarden/node/cli/models/response/messageResponse"; + +import { Response } from "./models/response"; +import { MessageResponse } from "./models/response/message.response"; export class CliUtils { static writeLn(s: string, finalLine = false, error = false) { diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 616568fe248..a3edf1a9132 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -1,7 +1,5 @@ import * as program from "commander"; -import { Response } from "@bitwarden/node/cli/models/response"; - import { Main } from "./bw"; import { ConfirmCommand } from "./commands/confirm.command"; import { CreateCommand } from "./commands/create.command"; @@ -13,6 +11,7 @@ import { ImportCommand } from "./commands/import.command"; import { ListCommand } from "./commands/list.command"; import { RestoreCommand } from "./commands/restore.command"; import { ShareCommand } from "./commands/share.command"; +import { Response } from "./models/response"; import { Program } from "./program"; import { CliUtils } from "./utils"; diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index da47ac52d8b..845c6b5afcf 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -12,9 +12,10 @@ "sourceMap": true, "baseUrl": ".", "paths": { + "@bitwarden/common/spec/*": ["../../libs/common/spec/*"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/node/*": ["../../libs/node/src/*"] } }, - "include": ["src"] + "include": ["src", "src/**/*.spec.ts"] } diff --git a/libs/node/src/cli/baseProgram.ts b/libs/node/src/cli/baseProgram.ts deleted file mode 100644 index 46e9db3ec98..00000000000 --- a/libs/node/src/cli/baseProgram.ts +++ /dev/null @@ -1,113 +0,0 @@ -import * as chalk from "chalk"; - -import { StateService } from "@bitwarden/common/abstractions/state.service"; - -import { Response } from "./models/response"; -import { ListResponse } from "./models/response/listResponse"; -import { MessageResponse } from "./models/response/messageResponse"; -import { StringResponse } from "./models/response/stringResponse"; - -export abstract class BaseProgram { - constructor( - protected stateService: StateService, - private writeLn: (s: string, finalLine: boolean, error: boolean) => void - ) {} - - protected processResponse( - response: Response, - exitImmediately = false, - dataProcessor: () => string = null - ) { - if (!response.success) { - if (process.env.BW_QUIET !== "true") { - if (process.env.BW_RESPONSE === "true") { - this.writeLn(this.getJson(response), true, false); - } else { - this.writeLn(chalk.redBright(response.message), true, true); - } - } - const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; - if (exitImmediately) { - process.exit(exitCode); - } else { - process.exitCode = exitCode; - } - return; - } - - if (process.env.BW_RESPONSE === "true") { - this.writeLn(this.getJson(response), true, false); - } else if (response.data != null) { - let out: string = dataProcessor != null ? dataProcessor() : null; - if (out == null) { - if (response.data.object === "string") { - const data = (response.data as StringResponse).data; - if (data != null) { - out = data; - } - } else if (response.data.object === "list") { - out = this.getJson((response.data as ListResponse).data); - } else if (response.data.object === "message") { - out = this.getMessage(response); - } else { - out = this.getJson(response.data); - } - } - - if (out != null && process.env.BW_QUIET !== "true") { - this.writeLn(out, true, false); - } - } - if (exitImmediately) { - process.exit(0); - } else { - process.exitCode = 0; - } - } - - protected getJson(obj: any): string { - if (process.env.BW_PRETTY === "true") { - return JSON.stringify(obj, null, " "); - } else { - return JSON.stringify(obj); - } - } - - protected getMessage(response: Response): string { - const message = response.data as MessageResponse; - if (process.env.BW_RAW === "true") { - return message.raw; - } - - let out = ""; - if (message.title != null) { - if (message.noColor) { - out = message.title; - } else { - out = chalk.greenBright(message.title); - } - } - if (message.message != null) { - if (message.title != null) { - out += "\n"; - } - out += message.message; - } - return out.trim() === "" ? null : out; - } - - protected async exitIfAuthed() { - const authed = await this.stateService.getIsAuthenticated(); - if (authed) { - const email = await this.stateService.getEmail(); - this.processResponse(Response.error("You are already logged in as " + email + "."), true); - } - } - - protected async exitIfNotAuthed() { - const authed = await this.stateService.getIsAuthenticated(); - if (!authed) { - this.processResponse(Response.error("You are not logged in."), true); - } - } -} diff --git a/libs/node/src/cli/commands/login.command.ts b/libs/node/src/cli/commands/login.command.ts deleted file mode 100644 index 413b5add5ee..00000000000 --- a/libs/node/src/cli/commands/login.command.ts +++ /dev/null @@ -1,632 +0,0 @@ -import * as http from "http"; - -import * as program from "commander"; -import * as inquirer from "inquirer"; -import Separator from "inquirer/lib/objects/separator"; -import { firstValueFrom } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AuthService } from "@bitwarden/common/abstractions/auth.service"; -import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; -import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; -import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; -import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service"; -import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; -import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service"; -import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType"; -import { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; -import { Utils } from "@bitwarden/common/misc/utils"; -import { AuthResult } from "@bitwarden/common/models/domain/auth-result"; -import { - UserApiLogInCredentials, - PasswordLogInCredentials, - SsoLogInCredentials, -} from "@bitwarden/common/models/domain/log-in-credentials"; -import { TokenTwoFactorRequest } from "@bitwarden/common/models/request/identity-token/token-two-factor.request"; -import { TwoFactorEmailRequest } from "@bitwarden/common/models/request/two-factor-email.request"; -import { UpdateTempPasswordRequest } from "@bitwarden/common/models/request/update-temp-password.request"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; - -import { Response } from "../models/response"; -import { MessageResponse } from "../models/response/messageResponse"; - -export class LoginCommand { - protected validatedParams: () => Promise; - protected success: () => Promise; - protected logout: () => Promise; - protected canInteract: boolean; - protected clientId: string; - protected clientSecret: string; - protected email: string; - - private ssoRedirectUri: string = null; - - constructor( - protected authService: AuthService, - protected apiService: ApiService, - protected i18nService: I18nService, - protected environmentService: EnvironmentService, - protected passwordGenerationService: PasswordGenerationService, - protected cryptoFunctionService: CryptoFunctionService, - protected platformUtilsService: PlatformUtilsService, - protected stateService: StateService, - protected cryptoService: CryptoService, - protected policyService: PolicyService, - protected twoFactorService: TwoFactorService, - clientId: string - ) { - this.clientId = clientId; - } - - async run(email: string, password: string, options: program.OptionValues) { - this.canInteract = process.env.BW_NOINTERACTION !== "true"; - - let ssoCodeVerifier: string = null; - let ssoCode: string = null; - let orgIdentifier: string = null; - - let clientId: string = null; - let clientSecret: string = null; - - let selectedProvider: any = null; - - if (options.apikey != null) { - const apiIdentifiers = await this.apiIdentifiers(); - clientId = apiIdentifiers.clientId; - clientSecret = apiIdentifiers.clientSecret; - } else if (options.sso != null && this.canInteract) { - const passwordOptions: any = { - type: "password", - length: 64, - uppercase: true, - lowercase: true, - numbers: true, - special: false, - }; - const state = await this.passwordGenerationService.generatePassword(passwordOptions); - ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); - const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - try { - const ssoParams = await this.openSsoPrompt(codeChallenge, state); - ssoCode = ssoParams.ssoCode; - orgIdentifier = ssoParams.orgIdentifier; - } catch { - return Response.badRequest("Something went wrong. Try again."); - } - } else { - if ((email == null || email === "") && this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "email", - message: "Email address:", - }); - email = answer.email; - } - if (email == null || email.trim() === "") { - return Response.badRequest("Email address is required."); - } - if (email.indexOf("@") === -1) { - return Response.badRequest("Email address is invalid."); - } - this.email = email; - - if (password == null || password === "") { - if (options.passwordfile) { - password = await NodeUtils.readFirstLine(options.passwordfile); - } else if (options.passwordenv && process.env[options.passwordenv]) { - password = process.env[options.passwordenv]; - } else if (this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "password", - name: "password", - message: "Master password:", - }); - password = answer.password; - } - } - - if (password == null || password === "") { - return Response.badRequest("Master password is required."); - } - } - - let twoFactorToken: string = options.code; - let twoFactorMethod: TwoFactorProviderType = null; - try { - if (options.method != null) { - twoFactorMethod = parseInt(options.method, null); - } - } catch (e) { - return Response.error("Invalid two-step login method."); - } - - const twoFactor = - twoFactorToken == null - ? null - : new TokenTwoFactorRequest(twoFactorMethod, twoFactorToken, false); - - try { - if (this.validatedParams != null) { - await this.validatedParams(); - } - - let response: AuthResult = null; - if (clientId != null && clientSecret != null) { - if (!clientId.startsWith("user")) { - return Response.error("Invalid API Key; Organization API Key currently not supported"); - } - response = await this.authService.logIn( - new UserApiLogInCredentials(clientId, clientSecret) - ); - } else if (ssoCode != null && ssoCodeVerifier != null) { - response = await this.authService.logIn( - new SsoLogInCredentials( - ssoCode, - ssoCodeVerifier, - this.ssoRedirectUri, - orgIdentifier, - twoFactor - ) - ); - } else { - response = await this.authService.logIn( - new PasswordLogInCredentials(email, password, null, twoFactor) - ); - } - if (response.captchaSiteKey) { - const credentials = new PasswordLogInCredentials(email, password); - const handledResponse = await this.handleCaptchaRequired(twoFactor, credentials); - - // Error Response - if (handledResponse instanceof Response) { - return handledResponse; - } else { - response = handledResponse; - } - } - if (response.requiresTwoFactor) { - const twoFactorProviders = this.twoFactorService.getSupportedProviders(null); - if (twoFactorProviders.length === 0) { - return Response.badRequest("No providers available for this client."); - } - - if (twoFactorMethod != null) { - try { - selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; - } catch (e) { - return Response.error("Invalid two-step login method."); - } - } - - if (selectedProvider == null) { - if (twoFactorProviders.length === 1) { - selectedProvider = twoFactorProviders[0]; - } else if (this.canInteract) { - const twoFactorOptions: (string | Separator)[] = twoFactorProviders.map((p) => p.name); - twoFactorOptions.push(new inquirer.Separator()); - twoFactorOptions.push("Cancel"); - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "list", - name: "method", - message: "Two-step login method:", - choices: twoFactorOptions, - }); - const i = twoFactorOptions.indexOf(answer.method); - if (i === twoFactorOptions.length - 1) { - return Response.error("Login failed."); - } - selectedProvider = twoFactorProviders[i]; - } - if (selectedProvider == null) { - return Response.error("Login failed. No provider selected."); - } - } - - if ( - twoFactorToken == null && - response.twoFactorProviders.size > 1 && - selectedProvider.type === TwoFactorProviderType.Email - ) { - const emailReq = new TwoFactorEmailRequest(); - emailReq.email = this.authService.email; - emailReq.masterPasswordHash = this.authService.masterPasswordHash; - await this.apiService.postTwoFactorEmail(emailReq); - } - - if (twoFactorToken == null) { - if (this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "token", - message: "Two-step login code:", - }); - twoFactorToken = answer.token; - } - if (twoFactorToken == null || twoFactorToken === "") { - return Response.badRequest("Code is required."); - } - } - - response = await this.authService.logInTwoFactor( - new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken), - null - ); - } - - if (response.captchaSiteKey) { - const twoFactorRequest = new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken); - const handledResponse = await this.handleCaptchaRequired(twoFactorRequest); - - // Error Response - if (handledResponse instanceof Response) { - return handledResponse; - } else { - response = handledResponse; - } - } - - if (response.requiresTwoFactor) { - return Response.error("Login failed."); - } - - if (response.resetMasterPassword) { - return Response.error( - "In order to log in with SSO from the CLI, you must first log in" + - " through the web vault to set your master password." - ); - } - - // Handle Updating Temp Password if NOT using an API Key for authentication - if (response.forcePasswordReset && clientId == null && clientSecret == null) { - return await this.updateTempPassword(); - } - - return await this.handleSuccessResponse(); - } catch (e) { - return Response.error(e); - } - } - - private async handleSuccessResponse(): Promise { - if (this.success != null) { - const res = await this.success(); - return Response.success(res); - } else { - const res = new MessageResponse("You are logged in!", null); - return Response.success(res); - } - } - - private async updateTempPassword(error?: string): Promise { - // If no interaction available, alert user to use web vault - if (!this.canInteract) { - await this.logout(); - this.authService.logOut(() => { - /* Do nothing */ - }); - return Response.error( - new MessageResponse( - "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now via the web vault. You have been logged out.", - null - ) - ); - } - - if (this.email == null || this.email === "undefined") { - this.email = await this.stateService.getEmail(); - } - - // Get New Master Password - const baseMessage = - "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now.\n" + - "Master password: "; - const firstMessage = error != null ? error + baseMessage : baseMessage; - const mp: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ - type: "password", - name: "password", - message: firstMessage, - }); - const masterPassword = mp.password; - - // Master Password Validation - if (masterPassword == null || masterPassword === "") { - return this.updateTempPassword("Master password is required.\n"); - } - - if (masterPassword.length < 8) { - return this.updateTempPassword("Master password must be at least 8 characters long.\n"); - } - - // Strength & Policy Validation - const strengthResult = this.passwordGenerationService.passwordStrength( - masterPassword, - this.getPasswordStrengthUserInput() - ); - - // Get New Master Password Re-type - const reTypeMessage = "Re-type New Master password (Strength: " + strengthResult.score + ")"; - const retype: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ - type: "password", - name: "password", - message: reTypeMessage, - }); - const masterPasswordRetype = retype.password; - - // Re-type Validation - if (masterPassword !== masterPasswordRetype) { - return this.updateTempPassword("Master password confirmation does not match.\n"); - } - - // Get Hint (optional) - const hint: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ - type: "input", - name: "input", - message: "Master Password Hint (optional):", - }); - const masterPasswordHint = hint.input; - - // Retrieve details for key generation - const enforcedPolicyOptions = await firstValueFrom( - this.policyService.masterPasswordPolicyOptions$() - ); - const kdf = await this.stateService.getKdfType(); - const kdfIterations = await this.stateService.getKdfIterations(); - - if ( - enforcedPolicyOptions != null && - !this.policyService.evaluateMasterPassword( - strengthResult.score, - masterPassword, - enforcedPolicyOptions - ) - ) { - return this.updateTempPassword( - "Your new master password does not meet the policy requirements.\n" - ); - } - - try { - // Create new key and hash new password - const newKey = await this.cryptoService.makeKey( - masterPassword, - this.email.trim().toLowerCase(), - kdf, - kdfIterations - ); - const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey); - - // Grab user's current enc key - const userEncKey = await this.cryptoService.getEncKey(); - - // Create new encKey for the User - const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey); - - // Create request - const request = new UpdateTempPasswordRequest(); - request.key = newEncKey[1].encryptedString; - request.newMasterPasswordHash = newPasswordHash; - request.masterPasswordHint = masterPasswordHint; - - // Update user's password - await this.apiService.putUpdateTempPassword(request); - return this.handleSuccessResponse(); - } catch (e) { - await this.logout(); - this.authService.logOut(() => { - /* Do nothing */ - }); - return Response.error(e); - } - } - - private async handleCaptchaRequired( - twoFactorRequest: TokenTwoFactorRequest, - credentials: PasswordLogInCredentials = null - ): Promise { - const badCaptcha = Response.badRequest( - "Your authentication request has been flagged and will require user interaction to proceed.\n" + - "Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" + - "(https://bitwarden.com/help/cli-auth-challenges)" - ); - - try { - const captchaClientSecret = await this.apiClientSecret(true); - if (Utils.isNullOrWhitespace(captchaClientSecret)) { - return badCaptcha; - } - - let authResultResponse: AuthResult = null; - if (credentials != null) { - credentials.captchaToken = captchaClientSecret; - credentials.twoFactor = twoFactorRequest; - authResultResponse = await this.authService.logIn(credentials); - } else { - authResultResponse = await this.authService.logInTwoFactor( - twoFactorRequest, - captchaClientSecret - ); - } - - return authResultResponse; - } catch (e) { - if ( - e instanceof ErrorResponse || - (e.constructor.name === ErrorResponse.name && - (e as ErrorResponse).message.includes("Captcha is invalid")) - ) { - return badCaptcha; - } else { - return Response.error(e); - } - } - } - - private getPasswordStrengthUserInput() { - let userInput: string[] = []; - const atPosition = this.email.indexOf("@"); - if (atPosition > -1) { - userInput = userInput.concat( - this.email - .substr(0, atPosition) - .trim() - .toLowerCase() - .split(/[^A-Za-z0-9]/) - ); - } - return userInput; - } - - private async apiClientId(): Promise { - let clientId: string = null; - - const storedClientId: string = process.env.BW_CLIENTID; - if (storedClientId == null) { - if (this.canInteract) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "clientId", - message: "client_id:", - }); - clientId = answer.clientId; - } else { - clientId = null; - } - } else { - clientId = storedClientId; - } - - return clientId; - } - - private async apiClientSecret(isAdditionalAuthentication = false): Promise { - const additionalAuthenticationMessage = "Additional authentication required.\nAPI key "; - let clientSecret: string = null; - - const storedClientSecret: string = this.clientSecret || process.env.BW_CLIENTSECRET; - if (this.canInteract && storedClientSecret == null) { - const answer: inquirer.Answers = await inquirer.createPromptModule({ - output: process.stderr, - })({ - type: "input", - name: "clientSecret", - message: - (isAdditionalAuthentication ? additionalAuthenticationMessage : "") + "client_secret:", - }); - clientSecret = answer.clientSecret; - } else { - clientSecret = storedClientSecret; - } - - return clientSecret; - } - - private async apiIdentifiers(): Promise<{ clientId: string; clientSecret: string }> { - return { - clientId: await this.apiClientId(), - clientSecret: await this.apiClientSecret(), - }; - } - - private async openSsoPrompt( - codeChallenge: string, - state: string - ): Promise<{ ssoCode: string; orgIdentifier: string }> { - return new Promise((resolve, reject) => { - const callbackServer = http.createServer((req, res) => { - const urlString = "http://localhost" + req.url; - const url = new URL(urlString); - const code = url.searchParams.get("code"); - const receivedState = url.searchParams.get("state"); - const orgIdentifier = this.getOrgIdentifierFromState(receivedState); - res.setHeader("Content-Type", "text/html"); - if (code != null && receivedState != null && this.checkState(receivedState, state)) { - res.writeHead(200); - res.end( - "Success | Bitwarden CLI" + - "

Successfully authenticated with the Bitwarden CLI

" + - "

You may now close this tab and return to the terminal.

" + - "" - ); - callbackServer.close(() => - resolve({ - ssoCode: code, - orgIdentifier: orgIdentifier, - }) - ); - } else { - res.writeHead(400); - res.end( - "Failed | Bitwarden CLI" + - "

Something went wrong logging into the Bitwarden CLI

" + - "

You may now close this tab and return to the terminal.

" + - "" - ); - callbackServer.close(() => reject()); - } - }); - let foundPort = false; - const webUrl = this.environmentService.getWebVaultUrl(); - for (let port = 8065; port <= 8070; port++) { - try { - this.ssoRedirectUri = "http://localhost:" + port; - callbackServer.listen(port, () => { - this.platformUtilsService.launchUri( - webUrl + - "/#/sso?clientId=" + - this.clientId + - "&redirectUri=" + - encodeURIComponent(this.ssoRedirectUri) + - "&state=" + - state + - "&codeChallenge=" + - codeChallenge - ); - }); - foundPort = true; - break; - } catch { - // Ignore error since we run the same command up to 5 times. - } - } - if (!foundPort) { - reject(); - } - }); - } - - private getOrgIdentifierFromState(state: string): string { - if (state === null || state === undefined) { - return null; - } - - const stateSplit = state.split("_identifier="); - return stateSplit.length > 1 ? stateSplit[1] : null; - } - - private checkState(state: string, checkState: string): boolean { - if (state === null || state === undefined) { - return false; - } - if (checkState === null || checkState === undefined) { - return false; - } - - const stateSplit = state.split("_identifier="); - const checkStateSplit = checkState.split("_identifier="); - return stateSplit[0] === checkStateSplit[0]; - } -}