diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index e1c74a7094..5dd8cd621b 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 4f0dab845e..67116d394b 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 7efee8eb3e..8f877e7fbf 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 c175f9b6a1..17a3eff5a8 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 2fa33c9edc..9110b46bf2 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 79d30dfec6..824bd5c8e8 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 00fb2f7291..82756edc98 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 334573b3df..92855ee6f7 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 5866e2a6fd..86bfc8cbbc 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 eb40cf5a2d..8d7a6130c0 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 f486acbe99..d6cd120da0 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 1c957d26fa..4598e1b230 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 50446d7e02..9e9594c6ca 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 3fb8a7fd8b..aebc084a06 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 594dc5c183..6cd57fe7cc 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 fcb2e215bb..97145771ef 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 5322026284..cc40c5bc79 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 ee993376fd..f9f136a6b9 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 d88a4d6d2f..6284f8b94b 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 9f8a544410..3de7627576 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 75e50dabfd..48e6b2aa9c 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 cfea2e863c..2fab074f44 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 71778a6fc8..6138382565 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 bb4d467eaf..4fc92fb89a 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 aeae1203a6..37367276a8 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 149c34d69d..c37040b255 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 9a9803e57f..824f7d1a93 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 53e82b7d72..53805fa003 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 9b2ea77784..ddba5acbb7 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 8766baea8f..c04760b9d9 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 1b8d5fa3f5..7cd109309c 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 6ba4166826..3d72b6b89e 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 83de7a0b1c..7e2136e6e3 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 534e11de47..b058feb72b 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 7b9f184d85..7546d11609 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 d768c51fde..ad70c4c8df 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 f1e83ac30e..182bff6e7f 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 1818c946e3..4275415233 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 c16b56e091..487cb270e2 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 196071fbab..5a73d7feb2 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 db415bcdb3..da6e7f0999 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 8612841ac1..1440f97b64 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 811e4b2a57..98b5b09c58 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 962fdb23bc..92a8e63821 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 989623e35f..1b100687f2 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 a37fd479b7..18644d6749 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 c2e47fa740..6d9f555312 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 f4becfe84c..49bb299b66 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 15944b0780..86dc0b1588 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 f80a87068b..41a99dcd9b 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 f92e2a305e..78c607f041 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 656a74ca55..d629b7c1c0 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 102168f909..6e096bd3c3 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 e01a161c51..0000000000 --- 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 440d02c25d..e41c1c7896 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 616568fe24..a3edf1a913 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 da47ac52d8..845c6b5afc 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 46e9db3ec9..0000000000 --- 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 413b5add5e..0000000000 --- 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]; - } -}