mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-21 16:18:28 +01:00
[PS-1884] [TDL-189] [TDL-203] Move libs/node files to CLI and rename per ADR12 (#4069)
* Extract files only used in cli out of libs/node Move commands from libs/node to cli Move program from libs/node to cli Move services from libs/node to cli Move specs from libs/node to cli Naming changes based on ADR 12 Rename commands Rename models/request Rename models/response Remove entries from whitelist-capital-letters.txt * Merge lowDbStorageService into base class Move logic from extended lowdbStorage.service.ts into base-lowdb-storage.service.ts Delete lowdb-storage.service.ts Rename base-lowdb-storage.service.ts to lowdb-storage.service.ts * Merge login.command with base class program.ts - changed import temporarily to make it easier to review Remove passing in clientId, set "cli" when constructing ssoRedirectUri call Remove setting callbacks, use private methods instead Remove i18nService from constructor params Add syncService, keyConnectorService and logoutCallback to constructor Merge successCallback with handleSuccessResponse Remove validatedParams callback and added private method Move options(program.OptionValues) and set in run() Delete login.command.ts * Rename base-login.command.ts to login.command.ts * Merge base.program.ts with program.ts
This commit is contained in:
parent
166e5a747e
commit
80f5a883e0
31
.github/whitelist-capital-letters.txt
vendored
31
.github/whitelist-capital-letters.txt
vendored
@ -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
|
||||
|
@ -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: ["<rootDir>/test.setup.ts"],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
|
||||
prefix: "<rootDir>/",
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {}
|
||||
|
@ -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) {}
|
||||
|
@ -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(
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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";
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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) {}
|
||||
|
@ -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<void>
|
||||
) {
|
||||
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<void>
|
||||
) {}
|
||||
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
// 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<AuthResult | Response> {
|
||||
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<string> {
|
||||
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<string> {
|
||||
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(
|
||||
"<html><head><title>Success | Bitwarden CLI</title></head><body>" +
|
||||
"<h1>Successfully authenticated with the Bitwarden CLI</h1>" +
|
||||
"<p>You may now close this tab and return to the terminal.</p>" +
|
||||
"</body></html>"
|
||||
);
|
||||
callbackServer.close(() =>
|
||||
resolve({
|
||||
ssoCode: code,
|
||||
orgIdentifier: orgIdentifier,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
"<html><head><title>Failed | Bitwarden CLI</title></head><body>" +
|
||||
"<h1>Something went wrong logging into the Bitwarden CLI</h1>" +
|
||||
"<p>You may now close this tab and return to the terminal.</p>" +
|
||||
"</body></html>"
|
||||
);
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
@ -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) {}
|
||||
|
@ -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 {
|
||||
|
@ -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) {}
|
||||
|
@ -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";
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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) {}
|
@ -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";
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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;
|
@ -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 {
|
@ -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 {
|
@ -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;
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
import { BaseResponse } from "./base.response";
|
||||
|
||||
export class FileResponse implements BaseResponse {
|
||||
object: string;
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
import { BaseResponse } from "./base.response";
|
||||
|
||||
export class ListResponse implements BaseResponse {
|
||||
object: string;
|
@ -1,4 +1,4 @@
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
import { BaseResponse } from "./base.response";
|
||||
|
||||
export class MessageResponse implements BaseResponse {
|
||||
object: string;
|
@ -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[];
|
@ -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;
|
@ -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;
|
@ -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 {
|
@ -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<SendResponse>("deletionDate"),
|
@ -1,4 +1,4 @@
|
||||
import { BaseResponse } from "./baseResponse";
|
||||
import { BaseResponse } from "./base.response";
|
||||
|
||||
export class StringResponse implements BaseResponse {
|
||||
object: string;
|
@ -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;
|
@ -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() {
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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 = {};
|
||||
|
@ -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<any>;
|
||||
@ -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<T>(action: () => T): Promise<T> {
|
||||
// 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() {
|
@ -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<T>(action: () => T): Promise<T> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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"]
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<any>;
|
||||
protected success: () => Promise<MessageResponse>;
|
||||
protected logout: () => Promise<void>;
|
||||
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<Response> {
|
||||
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<Response> {
|
||||
// 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<AuthResult | Response> {
|
||||
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<string> {
|
||||
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<string> {
|
||||
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(
|
||||
"<html><head><title>Success | Bitwarden CLI</title></head><body>" +
|
||||
"<h1>Successfully authenticated with the Bitwarden CLI</h1>" +
|
||||
"<p>You may now close this tab and return to the terminal.</p>" +
|
||||
"</body></html>"
|
||||
);
|
||||
callbackServer.close(() =>
|
||||
resolve({
|
||||
ssoCode: code,
|
||||
orgIdentifier: orgIdentifier,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
res.writeHead(400);
|
||||
res.end(
|
||||
"<html><head><title>Failed | Bitwarden CLI</title></head><body>" +
|
||||
"<h1>Something went wrong logging into the Bitwarden CLI</h1>" +
|
||||
"<p>You may now close this tab and return to the terminal.</p>" +
|
||||
"</body></html>"
|
||||
);
|
||||
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];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user