1
0
mirror of https://github.com/bitwarden/cli synced 2025-12-10 13:23:35 +00:00

Apply Prettier (#426)

This commit is contained in:
Oscar Hinton
2021-12-20 18:04:00 +01:00
committed by GitHub
parent ec53a16c00
commit 910b4a24e6
59 changed files with 4630 additions and 4117 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

View File

@@ -1,4 +1,5 @@
## Type of change ## Type of change
- [ ] Bug fix - [ ] Bug fix
- [ ] New feature development - [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
@@ -6,22 +7,22 @@
- [ ] Other - [ ] Other
## Objective ## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding--> <!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code changes ## Code changes
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes--> <!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories--> <!--Also refer to any related changes or PRs in other repositories-->
* **file.ext:** Description of what was changed and why - **file.ext:** Description of what was changed and why
## Testing requirements ## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing--> <!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit ## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required) - [ ] I have checked for **linting** errors (`npm run lint`) (required)
- [ ] This change requires a **documentation update** (notify the documentation team) - [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team) - [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

@@ -129,8 +129,8 @@ jobs:
- name: Install - name: Install
run: npm ci run: npm ci
# - name: Run linter - name: Run linter
# run: npm run lint run: npm run lint
- name: Build & Package - name: Build & Package
run: npm run dist run: npm run dist

6
.vscode/launch.json vendored
View File

@@ -8,11 +8,7 @@
"protocol": "inspector", "protocol": "inspector",
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"program": "${workspaceFolder}/build/bw.js", "program": "${workspaceFolder}/build/bw.js",
"args": [ "args": ["login", "sdfsd@sdfdf.com", "ddddddd"]
"login",
"sdfsd@sdfdf.com",
"ddddddd"
]
} }
] ]
} }

View File

@@ -6,15 +6,11 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera
Here is how you can get involved: Here is how you can get involved:
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one - **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code - **Report a bug or submit a bugfix:** Use Github issues and pull requests
- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Report a bug or submit a bugfix:** Use Github issues and pull requests - **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
## Contributor Agreement ## Contributor Agreement
@@ -22,6 +18,6 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/cli)
## Pull Request Guidelines ## Pull Request Guidelines
* use `npm run lint` and fix any linting suggestions before submitting a pull request - use `npm run lint` and fix any linting suggestions before submitting a pull request
* commit any pull requests against the `master` branch - commit any pull requests against the `master` branch
* include a link to your Community Forums post - include a link to your Community Forums post

300
src/bw.ts
View File

@@ -1,56 +1,56 @@
import * as program from 'commander'; import * as program from "commander";
import * as fs from 'fs'; import * as fs from "fs";
import * as jsdom from 'jsdom'; import * as jsdom from "jsdom";
import * as path from 'path'; import * as path from "path";
import { LogLevelType } from 'jslib-common/enums/logLevelType'; import { LogLevelType } from "jslib-common/enums/logLevelType";
import { AuthService } from 'jslib-common/services/auth.service'; import { AuthService } from "jslib-common/services/auth.service";
import { I18nService } from './services/i18n.service'; import { I18nService } from "./services/i18n.service";
import { NodeEnvSecureStorageService } from './services/nodeEnvSecureStorage.service'; import { NodeEnvSecureStorageService } from "./services/nodeEnvSecureStorage.service";
import { CliPlatformUtilsService } from 'jslib-node/cli/services/cliPlatformUtils.service'; import { CliPlatformUtilsService } from "jslib-node/cli/services/cliPlatformUtils.service";
import { ConsoleLogService } from 'jslib-node/cli/services/consoleLog.service'; import { ConsoleLogService } from "jslib-node/cli/services/consoleLog.service";
import { AppIdService } from 'jslib-common/services/appId.service'; import { AppIdService } from "jslib-common/services/appId.service";
import { AuditService } from 'jslib-common/services/audit.service'; import { AuditService } from "jslib-common/services/audit.service";
import { CipherService } from 'jslib-common/services/cipher.service'; import { CipherService } from "jslib-common/services/cipher.service";
import { CollectionService } from 'jslib-common/services/collection.service'; import { CollectionService } from "jslib-common/services/collection.service";
import { ConstantsService } from 'jslib-common/services/constants.service'; import { ConstantsService } from "jslib-common/services/constants.service";
import { ContainerService } from 'jslib-common/services/container.service'; import { ContainerService } from "jslib-common/services/container.service";
import { CryptoService } from 'jslib-common/services/crypto.service'; import { CryptoService } from "jslib-common/services/crypto.service";
import { EnvironmentService } from 'jslib-common/services/environment.service'; import { EnvironmentService } from "jslib-common/services/environment.service";
import { ExportService } from 'jslib-common/services/export.service'; import { ExportService } from "jslib-common/services/export.service";
import { FileUploadService } from 'jslib-common/services/fileUpload.service'; import { FileUploadService } from "jslib-common/services/fileUpload.service";
import { FolderService } from 'jslib-common/services/folder.service'; import { FolderService } from "jslib-common/services/folder.service";
import { ImportService } from 'jslib-common/services/import.service'; import { ImportService } from "jslib-common/services/import.service";
import { KeyConnectorService } from 'jslib-common/services/keyConnector.service'; import { KeyConnectorService } from "jslib-common/services/keyConnector.service";
import { NoopMessagingService } from 'jslib-common/services/noopMessaging.service'; import { NoopMessagingService } from "jslib-common/services/noopMessaging.service";
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service";
import { PolicyService } from 'jslib-common/services/policy.service'; import { PolicyService } from "jslib-common/services/policy.service";
import { SearchService } from 'jslib-common/services/search.service'; import { SearchService } from "jslib-common/services/search.service";
import { SendService } from 'jslib-common/services/send.service'; import { SendService } from "jslib-common/services/send.service";
import { SettingsService } from 'jslib-common/services/settings.service'; import { SettingsService } from "jslib-common/services/settings.service";
import { SyncService } from 'jslib-common/services/sync.service'; import { SyncService } from "jslib-common/services/sync.service";
import { TokenService } from 'jslib-common/services/token.service'; import { TokenService } from "jslib-common/services/token.service";
import { TotpService } from 'jslib-common/services/totp.service'; import { TotpService } from "jslib-common/services/totp.service";
import { UserService } from 'jslib-common/services/user.service'; import { UserService } from "jslib-common/services/user.service";
import { UserVerificationService } from 'jslib-common/services/userVerification.service'; import { UserVerificationService } from "jslib-common/services/userVerification.service";
import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
import { LowdbStorageService } from 'jslib-node/services/lowdbStorage.service'; import { LowdbStorageService } from "jslib-node/services/lowdbStorage.service";
import { NodeApiService } from 'jslib-node/services/nodeApi.service'; import { NodeApiService } from "jslib-node/services/nodeApi.service";
import { NodeCryptoFunctionService } from 'jslib-node/services/nodeCryptoFunction.service'; import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service";
import { Program } from './program'; import { Program } from "./program";
import { SendProgram } from './send.program'; import { SendProgram } from "./send.program";
import { VaultProgram } from './vault.program'; import { VaultProgram } from "./vault.program";
// Polyfills // Polyfills
(global as any).DOMParser = new jsdom.JSDOM().window.DOMParser; (global as any).DOMParser = new jsdom.JSDOM().window.DOMParser;
// tslint:disable-next-line // tslint:disable-next-line
const packageJson = require('../package.json'); const packageJson = require("../package.json");
export class Main { export class Main {
messagingService: NoopMessagingService; messagingService: NoopMessagingService;
@@ -92,82 +92,188 @@ export class Main {
constructor() { constructor() {
let p = null; let p = null;
const relativeDataDir = path.join(path.dirname(process.execPath), 'bw-data'); const relativeDataDir = path.join(path.dirname(process.execPath), "bw-data");
if (fs.existsSync(relativeDataDir)) { if (fs.existsSync(relativeDataDir)) {
p = relativeDataDir; p = relativeDataDir;
} else if (process.env.BITWARDENCLI_APPDATA_DIR) { } else if (process.env.BITWARDENCLI_APPDATA_DIR) {
p = path.resolve(process.env.BITWARDENCLI_APPDATA_DIR); p = path.resolve(process.env.BITWARDENCLI_APPDATA_DIR);
} else if (process.platform === 'darwin') { } else if (process.platform === "darwin") {
p = path.join(process.env.HOME, 'Library/Application Support/Bitwarden CLI'); p = path.join(process.env.HOME, "Library/Application Support/Bitwarden CLI");
} else if (process.platform === 'win32') { } else if (process.platform === "win32") {
p = path.join(process.env.APPDATA, 'Bitwarden CLI'); p = path.join(process.env.APPDATA, "Bitwarden CLI");
} else if (process.env.XDG_CONFIG_HOME) { } else if (process.env.XDG_CONFIG_HOME) {
p = path.join(process.env.XDG_CONFIG_HOME, 'Bitwarden CLI'); p = path.join(process.env.XDG_CONFIG_HOME, "Bitwarden CLI");
} else { } else {
p = path.join(process.env.HOME, '.config/Bitwarden CLI'); p = path.join(process.env.HOME, ".config/Bitwarden CLI");
} }
this.i18nService = new I18nService('en', './locales'); this.i18nService = new I18nService("en", "./locales");
this.platformUtilsService = new CliPlatformUtilsService('cli', packageJson); this.platformUtilsService = new CliPlatformUtilsService("cli", packageJson);
this.logService = new ConsoleLogService(this.platformUtilsService.isDev(), this.logService = new ConsoleLogService(
level => process.env.BITWARDENCLI_DEBUG !== 'true' && level <= LogLevelType.Info); this.platformUtilsService.isDev(),
(level) => process.env.BITWARDENCLI_DEBUG !== "true" && level <= LogLevelType.Info
);
this.cryptoFunctionService = new NodeCryptoFunctionService(); this.cryptoFunctionService = new NodeCryptoFunctionService();
this.storageService = new LowdbStorageService(this.logService, null, p, true); this.storageService = new LowdbStorageService(this.logService, null, p, true);
this.secureStorageService = new NodeEnvSecureStorageService(this.storageService, this.logService, this.secureStorageService = new NodeEnvSecureStorageService(
() => this.cryptoService); this.storageService,
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService, this.logService,
this.cryptoFunctionService, this.platformUtilsService, this.logService); () => this.cryptoService
);
this.cryptoService = new CryptoService(
this.storageService,
this.secureStorageService,
this.cryptoFunctionService,
this.platformUtilsService,
this.logService
);
this.appIdService = new AppIdService(this.storageService); this.appIdService = new AppIdService(this.storageService);
this.tokenService = new TokenService(this.storageService); this.tokenService = new TokenService(this.storageService);
this.messagingService = new NoopMessagingService(); this.messagingService = new NoopMessagingService();
this.environmentService = new EnvironmentService(this.storageService); this.environmentService = new EnvironmentService(this.storageService);
this.apiService = new NodeApiService(this.tokenService, this.platformUtilsService, this.environmentService, this.apiService = new NodeApiService(
this.tokenService,
this.platformUtilsService,
this.environmentService,
async (expired: boolean) => await this.logout(), async (expired: boolean) => await this.logout(),
'Bitwarden_CLI/' + this.platformUtilsService.getApplicationVersionSync() + "Bitwarden_CLI/" +
' (' + this.platformUtilsService.getDeviceString().toUpperCase() + ')', (clientId, clientSecret) => this.platformUtilsService.getApplicationVersionSync() +
this.authService.logInApiKey(clientId, clientSecret)); " (" +
this.platformUtilsService.getDeviceString().toUpperCase() +
")",
(clientId, clientSecret) => this.authService.logInApiKey(clientId, clientSecret)
);
this.userService = new UserService(this.tokenService, this.storageService); this.userService = new UserService(this.tokenService, this.storageService);
this.containerService = new ContainerService(this.cryptoService); this.containerService = new ContainerService(this.cryptoService);
this.settingsService = new SettingsService(this.userService, this.storageService); this.settingsService = new SettingsService(this.userService, this.storageService);
this.fileUploadService = new FileUploadService(this.logService, this.apiService); this.fileUploadService = new FileUploadService(this.logService, this.apiService);
this.cipherService = new CipherService(this.cryptoService, this.userService, this.settingsService, this.cipherService = new CipherService(
this.apiService, this.fileUploadService, this.storageService, this.i18nService, null, this.logService); this.cryptoService,
this.folderService = new FolderService(this.cryptoService, this.userService, this.apiService, this.userService,
this.storageService, this.i18nService, this.cipherService); this.settingsService,
this.collectionService = new CollectionService(this.cryptoService, this.userService, this.storageService, this.apiService,
this.i18nService); this.fileUploadService,
this.storageService,
this.i18nService,
null,
this.logService
);
this.folderService = new FolderService(
this.cryptoService,
this.userService,
this.apiService,
this.storageService,
this.i18nService,
this.cipherService
);
this.collectionService = new CollectionService(
this.cryptoService,
this.userService,
this.storageService,
this.i18nService
);
this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService); this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService);
this.policyService = new PolicyService(this.userService, this.storageService, this.apiService); this.policyService = new PolicyService(this.userService, this.storageService, this.apiService);
this.sendService = new SendService(this.cryptoService, this.userService, this.apiService, this.fileUploadService, this.sendService = new SendService(
this.storageService, this.i18nService, this.cryptoFunctionService); this.cryptoService,
this.keyConnectorService = new KeyConnectorService(this.storageService, this.userService, this.cryptoService, this.userService,
this.apiService, this.tokenService, this.logService); this.apiService,
this.vaultTimeoutService = new VaultTimeoutService(this.cipherService, this.folderService, this.fileUploadService,
this.collectionService, this.cryptoService, this.platformUtilsService, this.storageService, this.storageService,
this.messagingService, this.searchService, this.userService, this.tokenService, this.policyService, this.i18nService,
this.keyConnectorService, async () => await this.cryptoService.clearStoredKey('auto'), null); this.cryptoFunctionService
this.syncService = new SyncService(this.userService, this.apiService, this.settingsService, );
this.folderService, this.cipherService, this.cryptoService, this.collectionService, this.keyConnectorService = new KeyConnectorService(
this.storageService, this.messagingService, this.policyService, this.sendService, this.storageService,
this.logService, this.tokenService, this.keyConnectorService, this.userService,
async (expired: boolean) => await this.logout()); this.cryptoService,
this.passwordGenerationService = new PasswordGenerationService(this.cryptoService, this.storageService, this.apiService,
this.policyService); this.tokenService,
this.totpService = new TotpService(this.storageService, this.cryptoFunctionService, this.logService); this.logService
this.importService = new ImportService(this.cipherService, this.folderService, this.apiService, );
this.i18nService, this.collectionService, this.platformUtilsService, this.cryptoService); this.vaultTimeoutService = new VaultTimeoutService(
this.exportService = new ExportService(this.folderService, this.cipherService, this.apiService, this.cipherService,
this.cryptoService); this.folderService,
this.authService = new AuthService(this.cryptoService, this.apiService, this.userService, this.tokenService, this.collectionService,
this.appIdService, this.i18nService, this.platformUtilsService, this.messagingService, this.cryptoService,
this.vaultTimeoutService, this.logService, this.cryptoFunctionService, this.environmentService, this.platformUtilsService,
this.keyConnectorService, true); this.storageService,
this.messagingService,
this.searchService,
this.userService,
this.tokenService,
this.policyService,
this.keyConnectorService,
async () => await this.cryptoService.clearStoredKey("auto"),
null
);
this.syncService = new SyncService(
this.userService,
this.apiService,
this.settingsService,
this.folderService,
this.cipherService,
this.cryptoService,
this.collectionService,
this.storageService,
this.messagingService,
this.policyService,
this.sendService,
this.logService,
this.tokenService,
this.keyConnectorService,
async (expired: boolean) => await this.logout()
);
this.passwordGenerationService = new PasswordGenerationService(
this.cryptoService,
this.storageService,
this.policyService
);
this.totpService = new TotpService(
this.storageService,
this.cryptoFunctionService,
this.logService
);
this.importService = new ImportService(
this.cipherService,
this.folderService,
this.apiService,
this.i18nService,
this.collectionService,
this.platformUtilsService,
this.cryptoService
);
this.exportService = new ExportService(
this.folderService,
this.cipherService,
this.apiService,
this.cryptoService
);
this.authService = new AuthService(
this.cryptoService,
this.apiService,
this.userService,
this.tokenService,
this.appIdService,
this.i18nService,
this.platformUtilsService,
this.messagingService,
this.vaultTimeoutService,
this.logService,
this.cryptoFunctionService,
this.environmentService,
this.keyConnectorService,
true
);
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService); this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
this.program = new Program(this); this.program = new Program(this);
this.vaultProgram = new VaultProgram(this); this.vaultProgram = new VaultProgram(this);
this.sendProgram = new SendProgram(this); this.sendProgram = new SendProgram(this);
this.userVerificationService = new UserVerificationService(this.cryptoService, this.i18nService, this.userVerificationService = new UserVerificationService(
this.apiService); this.cryptoService,
this.i18nService,
this.apiService
);
} }
async run() { async run() {
@@ -177,13 +283,11 @@ export class Main {
await this.vaultProgram.register(); await this.vaultProgram.register();
await this.sendProgram.register(); await this.sendProgram.register();
program program.parse(process.argv);
.parse(process.argv);
if (process.argv.slice(2).length === 0) { if (process.argv.slice(2).length === 0) {
program.outputHelp(); program.outputHelp();
} }
} }
async logout() { async logout() {
@@ -217,7 +321,9 @@ export class Main {
await this.i18nService.init(locale); await this.i18nService.init(locale);
this.authService.init(); this.authService.init();
const installedVersion = await this.storageService.get<string>(ConstantsService.installedVersionKey); const installedVersion = await this.storageService.get<string>(
ConstantsService.installedVersionKey
);
const currentVersion = await this.platformUtilsService.getApplicationVersion(); const currentVersion = await this.platformUtilsService.getApplicationVersion();
if (installedVersion == null || installedVersion !== currentVersion) { if (installedVersion == null || installedVersion !== currentVersion) {
await this.storageService.save(ConstantsService.installedVersionKey, currentVersion); await this.storageService.save(ConstantsService.installedVersionKey, currentVersion);

View File

@@ -1,7 +1,7 @@
import * as program from 'commander'; import * as program from "commander";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
interface IOption { interface IOption {
long?: string; long?: string;
@@ -16,24 +16,24 @@ interface ICommand {
_description: string; _description: string;
} }
const validShells = ['zsh']; const validShells = ["zsh"];
export class CompletionCommand { export class CompletionCommand {
async run(options: program.OptionValues) { async run(options: program.OptionValues) {
const shell: typeof validShells[number] = options.shell; const shell: typeof validShells[number] = options.shell;
if (!shell) { if (!shell) {
return Response.badRequest('`shell` was not provided.'); return Response.badRequest("`shell` was not provided.");
} }
if (!validShells.includes(shell)) { if (!validShells.includes(shell)) {
return Response.badRequest('Unsupported shell.'); return Response.badRequest("Unsupported shell.");
} }
let content = ''; let content = "";
if (shell === 'zsh') { if (shell === "zsh") {
content = this.zshCompletion('bw', program as any as ICommand).render(); content = this.zshCompletion("bw", program as any as ICommand).render();
} }
const res = new MessageResponse(content, null); const res = new MessageResponse(content, null);
@@ -45,9 +45,9 @@ export class CompletionCommand {
render: () => { render: () => {
return [ return [
`#compdef _${rootName} ${rootName}`, `#compdef _${rootName} ${rootName}`,
'', "",
this.renderCommandBlock(rootName, rootCommand), this.renderCommandBlock(rootName, rootCommand),
].join('\n'); ].join("\n");
}, },
}; };
} }
@@ -60,18 +60,23 @@ export class CompletionCommand {
const args = options const args = options
.map(({ long, short, description }) => { .map(({ long, short, description }) => {
const aliases = [short, long].filter(Boolean); const aliases = [short, long].filter(Boolean);
const opts = aliases.join(','); const opts = aliases.join(",");
const desc = `[${description.replace(`'`, `'"'"'`)}]`; const desc = `[${description.replace(`'`, `'"'"'`)}]`;
return aliases.length > 1 ? `'(${aliases.join(' ')})'{${opts}}'${desc}'` : `'${opts}${desc}'`; return aliases.length > 1
}).concat(`'(-h --help)'{-h,--help}'[output usage information]'`, ? `'(${aliases.join(" ")})'{${opts}}'${desc}'`
: `'${opts}${desc}'`;
})
.concat(
`'(-h --help)'{-h,--help}'[output usage information]'`,
hasCommands ? '"1: :->cmnds"' : null, hasCommands ? '"1: :->cmnds"' : null,
'"*::arg:->args"', '"*::arg:->args"'
).filter(Boolean); )
.filter(Boolean);
const commandBlockFunctionParts = []; const commandBlockFunctionParts = [];
if (hasCommands) { if (hasCommands) {
commandBlockFunctionParts.push('local -a commands'); commandBlockFunctionParts.push("local -a commands");
} }
if (hasOptions) { if (hasOptions) {
@@ -83,28 +88,32 @@ export class CompletionCommand {
`case $state in `case $state in
cmnds) cmnds)
commands=( commands=(
${commands.map(({ _name, _description }) => `"${_name}:${_description}"`).join('\n ')} ${commands
.map(({ _name, _description }) => `"${_name}:${_description}"`)
.join("\n ")}
) )
_describe "command" commands _describe "command" commands
;; ;;
esac esac
case "$words[1]" in case "$words[1]" in
${commands.map(({ _name }) => [`${_name})`, `_${name}_${_name}`, ';;'].join('\n ')).join('\n ')} ${commands
esac`, .map(({ _name }) => [`${_name})`, `_${name}_${_name}`, ";;"].join("\n "))
.join("\n ")}
esac`
); );
} }
const commandBlocParts = [ const commandBlocParts = [
`function _${name} {\n ${commandBlockFunctionParts.join('\n\n ')}\n}`, `function _${name} {\n ${commandBlockFunctionParts.join("\n\n ")}\n}`,
]; ];
if (hasCommands) { if (hasCommands) {
commandBlocParts.push( commandBlocParts.push(
commands.map(c => this.renderCommandBlock(`${name}_${c._name}`, c)).join('\n\n'), commands.map((c) => this.renderCommandBlock(`${name}_${c._name}`, c)).join("\n\n")
); );
} }
return commandBlocParts.join('\n\n'); return commandBlocParts.join("\n\n");
} }
} }

View File

@@ -1,35 +1,43 @@
import * as program from 'commander'; import * as program from "commander";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse'; import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
export class ConfigCommand { export class ConfigCommand {
constructor(private environmentService: EnvironmentService) { } constructor(private environmentService: EnvironmentService) {}
async run(setting: string, value: string, options: program.OptionValues): Promise<Response> { async run(setting: string, value: string, options: program.OptionValues): Promise<Response> {
setting = setting.toLowerCase(); setting = setting.toLowerCase();
switch (setting) { switch (setting) {
case 'server': case "server":
return await this.getOrSetServer(value, options); return await this.getOrSetServer(value, options);
default: default:
return Response.badRequest('Unknown setting.'); return Response.badRequest("Unknown setting.");
} }
} }
private async getOrSetServer(url: string, options: program.OptionValues): Promise<Response> { private async getOrSetServer(url: string, options: program.OptionValues): Promise<Response> {
if ((url == null || url.trim() === '') && if (
!options.webVault && !options.api && !options.identity && !options.icons && !options.notifications && !options.events) { (url == null || url.trim() === "") &&
!options.webVault &&
!options.api &&
!options.identity &&
!options.icons &&
!options.notifications &&
!options.events
) {
const stringRes = new StringResponse( const stringRes = new StringResponse(
this.environmentService.hasBaseUrl() ? this.environmentService.getUrls().base : 'https://bitwarden.com' this.environmentService.hasBaseUrl()
? this.environmentService.getUrls().base
: "https://bitwarden.com"
); );
return Response.success(stringRes); return Response.success(stringRes);
} }
url = (url === 'null' || url === 'bitwarden.com' || url === 'https://bitwarden.com' ? null : url); url = url === "null" || url === "bitwarden.com" || url === "https://bitwarden.com" ? null : url;
await this.environmentService.setUrls({ await this.environmentService.setUrls({
base: url, base: url,
webVault: options.webVault || null, webVault: options.webVault || null,
@@ -40,7 +48,7 @@ export class ConfigCommand {
events: options.events || null, events: options.events || null,
keyConnector: options.keyConnector || null, keyConnector: options.keyConnector || null,
}); });
const res = new MessageResponse('Saved setting `config`.', null); const res = new MessageResponse("Saved setting `config`.", null);
return Response.success(res); return Response.success(res);
} }
} }

View File

@@ -1,16 +1,16 @@
import * as program from 'commander'; import * as program from "commander";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { OrganizationUserConfirmRequest } from 'jslib-common/models/request/organizationUserConfirmRequest'; import { OrganizationUserConfirmRequest } from "jslib-common/models/request/organizationUserConfirmRequest";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
export class ConfirmCommand { export class ConfirmCommand {
constructor(private apiService: ApiService, private cryptoService: CryptoService) { } constructor(private apiService: ApiService, private cryptoService: CryptoService) {}
async run(object: string, id: string, cmd: program.Command): Promise<Response> { async run(object: string, id: string, cmd: program.Command): Promise<Response> {
if (id != null) { if (id != null) {
@@ -18,31 +18,31 @@ export class ConfirmCommand {
} }
switch (object.toLowerCase()) { switch (object.toLowerCase()) {
case 'org-member': case "org-member":
return await this.confirmOrganizationMember(id, cmd); return await this.confirmOrganizationMember(id, cmd);
default: default:
return Response.badRequest('Unknown object.'); return Response.badRequest("Unknown object.");
} }
} }
private async confirmOrganizationMember(id: string, options: program.OptionValues) { private async confirmOrganizationMember(id: string, options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') { if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest('--organizationid <organizationid> required.'); return Response.badRequest("--organizationid <organizationid> required.");
} }
if (!Utils.isGuid(id)) { if (!Utils.isGuid(id)) {
return Response.error('`' + id + '` is not a GUID.'); return Response.error("`" + id + "` is not a GUID.");
} }
if (!Utils.isGuid(options.organizationid)) { if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.'); return Response.error("`" + options.organizationid + "` is not a GUID.");
} }
try { try {
const orgKey = await this.cryptoService.getOrgKey(options.organizationid); const orgKey = await this.cryptoService.getOrgKey(options.organizationid);
if (orgKey == null) { if (orgKey == null) {
throw new Error('No encryption key for this organization.'); throw new Error("No encryption key for this organization.");
} }
const orgUser = await this.apiService.getOrganizationUser(options.organizationid, id); const orgUser = await this.apiService.getOrganizationUser(options.organizationid, id);
if (orgUser == null) { if (orgUser == null) {
throw new Error('Member id does not exist for this organization.'); throw new Error("Member id does not exist for this organization.");
} }
const publicKeyResponse = await this.apiService.getUserPublicKey(orgUser.userId); const publicKeyResponse = await this.apiService.getUserPublicKey(orgUser.userId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);

View File

@@ -1,67 +1,71 @@
import * as program from 'commander'; import * as program from "commander";
import * as fs from 'fs'; import * as fs from "fs";
import * as path from 'path'; import * as path from "path";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { FolderService } from 'jslib-common/abstractions/folder.service'; import { FolderService } from "jslib-common/abstractions/folder.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { Cipher } from 'jslib-common/models/export/cipher'; import { Cipher } from "jslib-common/models/export/cipher";
import { Collection } from 'jslib-common/models/export/collection'; import { Collection } from "jslib-common/models/export/collection";
import { Folder } from 'jslib-common/models/export/folder'; import { Folder } from "jslib-common/models/export/folder";
import { CollectionRequest } from 'jslib-common/models/request/collectionRequest'; import { CollectionRequest } from "jslib-common/models/request/collectionRequest";
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest'; import { SelectionReadOnlyRequest } from "jslib-common/models/request/selectionReadOnlyRequest";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { CipherResponse } from '../models/response/cipherResponse'; import { CipherResponse } from "../models/response/cipherResponse";
import { FolderResponse } from '../models/response/folderResponse'; import { FolderResponse } from "../models/response/folderResponse";
import { OrganizationCollectionResponse } from '../models/response/organizationCollectionResponse'; import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse";
import { OrganizationCollectionRequest } from '../models/request/organizationCollectionRequest'; import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest";
import { CliUtils } from '../utils'; import { CliUtils } from "../utils";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
export class CreateCommand { export class CreateCommand {
constructor(private cipherService: CipherService, private folderService: FolderService, constructor(
private userService: UserService, private cryptoService: CryptoService, private cipherService: CipherService,
private apiService: ApiService) { } private folderService: FolderService,
private userService: UserService,
private cryptoService: CryptoService,
private apiService: ApiService
) {}
async run(object: string, requestJson: string, cmd: program.Command): Promise<Response> { async run(object: string, requestJson: string, cmd: program.Command): Promise<Response> {
let req: any = null; let req: any = null;
if (object !== 'attachment') { if (object !== "attachment") {
if (requestJson == null || requestJson === '') { if (requestJson == null || requestJson === "") {
requestJson = await CliUtils.readStdin(); requestJson = await CliUtils.readStdin();
} }
if (requestJson == null || requestJson === '') { if (requestJson == null || requestJson === "") {
return Response.badRequest('`requestJson` was not provided.'); return Response.badRequest("`requestJson` was not provided.");
} }
try { try {
const reqJson = Buffer.from(requestJson, 'base64').toString(); const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson); req = JSON.parse(reqJson);
} catch (e) { } catch (e) {
return Response.badRequest('Error parsing the encoded request data.'); return Response.badRequest("Error parsing the encoded request data.");
} }
} }
switch (object.toLowerCase()) { switch (object.toLowerCase()) {
case 'item': case "item":
return await this.createCipher(req); return await this.createCipher(req);
case 'attachment': case "attachment":
return await this.createAttachment(cmd); return await this.createAttachment(cmd);
case 'folder': case "folder":
return await this.createFolder(req); return await this.createFolder(req);
case 'org-collection': case "org-collection":
return await this.createOrganizationCollection(req, cmd); return await this.createOrganizationCollection(req, cmd);
default: default:
return Response.badRequest('Unknown object.'); return Response.badRequest("Unknown object.");
} }
} }
@@ -79,15 +83,15 @@ export class CreateCommand {
} }
private async createAttachment(options: program.OptionValues) { private async createAttachment(options: program.OptionValues) {
if (options.itemid == null || options.itemid === '') { if (options.itemid == null || options.itemid === "") {
return Response.badRequest('--itemid <itemid> required.'); return Response.badRequest("--itemid <itemid> required.");
} }
if (options.file == null || options.file === '') { if (options.file == null || options.file === "") {
return Response.badRequest('--file <file> required.'); return Response.badRequest("--file <file> required.");
} }
const filePath = path.resolve(options.file); const filePath = path.resolve(options.file);
if (!fs.existsSync(options.file)) { if (!fs.existsSync(options.file)) {
return Response.badRequest('Cannot find file at ' + filePath); return Response.badRequest("Cannot find file at " + filePath);
} }
const itemId = options.itemid.toLowerCase(); const itemId = options.itemid.toLowerCase();
@@ -97,19 +101,24 @@ export class CreateCommand {
} }
if (cipher.organizationId == null && !(await this.userService.canAccessPremium())) { if (cipher.organizationId == null && !(await this.userService.canAccessPremium())) {
return Response.error('Premium status is required to use this feature.'); return Response.error("Premium status is required to use this feature.");
} }
const encKey = await this.cryptoService.getEncKey(); const encKey = await this.cryptoService.getEncKey();
if (encKey == null) { if (encKey == null) {
return Response.error('You must update your encryption key before you can use this feature. ' + return Response.error(
'See https://help.bitwarden.com/article/update-encryption-key/'); "You must update your encryption key before you can use this feature. " +
"See https://help.bitwarden.com/article/update-encryption-key/"
);
} }
try { try {
const fileBuf = fs.readFileSync(filePath); const fileBuf = fs.readFileSync(filePath);
await this.cipherService.saveAttachmentRawWithServer(cipher, path.basename(filePath), await this.cipherService.saveAttachmentRawWithServer(
new Uint8Array(fileBuf).buffer); cipher,
path.basename(filePath),
new Uint8Array(fileBuf).buffer
);
const updatedCipher = await this.cipherService.get(cipher.id); const updatedCipher = await this.cipherService.get(cipher.id);
const decCipher = await updatedCipher.decrypt(); const decCipher = await updatedCipher.decrypt();
const res = new CipherResponse(decCipher); const res = new CipherResponse(decCipher);
@@ -132,24 +141,29 @@ export class CreateCommand {
} }
} }
private async createOrganizationCollection(req: OrganizationCollectionRequest, options: program.OptionValues) { private async createOrganizationCollection(
if (options.organizationid == null || options.organizationid === '') { req: OrganizationCollectionRequest,
return Response.badRequest('--organizationid <organizationid> required.'); options: program.OptionValues
) {
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
} }
if (!Utils.isGuid(options.organizationid)) { if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.'); return Response.error("`" + options.organizationid + "` is not a GUID.");
} }
if (options.organizationid !== req.organizationId) { if (options.organizationid !== req.organizationId) {
return Response.error('--organizationid <organizationid> does not match request object.'); return Response.error("--organizationid <organizationid> does not match request object.");
} }
try { try {
const orgKey = await this.cryptoService.getOrgKey(req.organizationId); const orgKey = await this.cryptoService.getOrgKey(req.organizationId);
if (orgKey == null) { if (orgKey == null) {
throw new Error('No encryption key for this organization.'); throw new Error("No encryption key for this organization.");
} }
const groups = req.groups == null ? null : const groups =
req.groups.map(g => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords)); req.groups == null
? null
: req.groups.map((g) => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords));
const request = new CollectionRequest(); const request = new CollectionRequest();
request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString; request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString;
request.externalId = req.externalId; request.externalId = req.externalId;

View File

@@ -1,17 +1,21 @@
import * as program from 'commander'; import * as program from "commander";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { FolderService } from 'jslib-common/abstractions/folder.service'; import { FolderService } from "jslib-common/abstractions/folder.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
export class DeleteCommand { export class DeleteCommand {
constructor(private cipherService: CipherService, private folderService: FolderService, constructor(
private userService: UserService, private apiService: ApiService) { } private cipherService: CipherService,
private folderService: FolderService,
private userService: UserService,
private apiService: ApiService
) {}
async run(object: string, id: string, cmd: program.Command): Promise<Response> { async run(object: string, id: string, cmd: program.Command): Promise<Response> {
if (id != null) { if (id != null) {
@@ -19,16 +23,16 @@ export class DeleteCommand {
} }
switch (object.toLowerCase()) { switch (object.toLowerCase()) {
case 'item': case "item":
return await this.deleteCipher(id, cmd); return await this.deleteCipher(id, cmd);
case 'attachment': case "attachment":
return await this.deleteAttachment(id, cmd); return await this.deleteAttachment(id, cmd);
case 'folder': case "folder":
return await this.deleteFolder(id); return await this.deleteFolder(id);
case 'org-collection': case "org-collection":
return await this.deleteOrganizationCollection(id, cmd); return await this.deleteOrganizationCollection(id, cmd);
default: default:
return Response.badRequest('Unknown object.'); return Response.badRequest("Unknown object.");
} }
} }
@@ -51,8 +55,8 @@ export class DeleteCommand {
} }
private async deleteAttachment(id: string, options: program.OptionValues) { private async deleteAttachment(id: string, options: program.OptionValues) {
if (options.itemid == null || options.itemid === '') { if (options.itemid == null || options.itemid === "") {
return Response.badRequest('--itemid <itemid> required.'); return Response.badRequest("--itemid <itemid> required.");
} }
const itemId = options.itemid.toLowerCase(); const itemId = options.itemid.toLowerCase();
@@ -62,16 +66,16 @@ export class DeleteCommand {
} }
if (cipher.attachments == null || cipher.attachments.length === 0) { if (cipher.attachments == null || cipher.attachments.length === 0) {
return Response.error('No attachments available for this item.'); return Response.error("No attachments available for this item.");
} }
const attachments = cipher.attachments.filter(a => a.id.toLowerCase() === id); const attachments = cipher.attachments.filter((a) => a.id.toLowerCase() === id);
if (attachments.length === 0) { if (attachments.length === 0) {
return Response.error('Attachment `' + id + '` was not found.'); return Response.error("Attachment `" + id + "` was not found.");
} }
if (cipher.organizationId == null && !(await this.userService.canAccessPremium())) { if (cipher.organizationId == null && !(await this.userService.canAccessPremium())) {
return Response.error('Premium status is required to use this feature.'); return Response.error("Premium status is required to use this feature.");
} }
try { try {
@@ -97,14 +101,14 @@ export class DeleteCommand {
} }
private async deleteOrganizationCollection(id: string, options: program.OptionValues) { private async deleteOrganizationCollection(id: string, options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') { if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest('--organizationid <organizationid> required.'); return Response.badRequest("--organizationid <organizationid> required.");
} }
if (!Utils.isGuid(id)) { if (!Utils.isGuid(id)) {
return Response.error('`' + id + '` is not a GUID.'); return Response.error("`" + id + "` is not a GUID.");
} }
if (!Utils.isGuid(options.organizationid)) { if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.'); return Response.error("`" + options.organizationid + "` is not a GUID.");
} }
try { try {
await this.apiService.deleteCollection(options.organizationid, id); await this.apiService.deleteCollection(options.organizationid, id);

View File

@@ -1,20 +1,27 @@
import * as fet from 'node-fetch'; import * as fet from "node-fetch";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { CliUtils } from '../utils'; import { CliUtils } from "../utils";
export abstract class DownloadCommand { export abstract class DownloadCommand {
constructor(protected cryptoService: CryptoService) { } constructor(protected cryptoService: CryptoService) {}
protected async saveAttachmentToFile(url: string, key: SymmetricCryptoKey, fileName: string, output?: string) { protected async saveAttachmentToFile(
const response = await fet.default(new fet.Request(url, { headers: { cache: 'no-cache' } })); url: string,
key: SymmetricCryptoKey,
fileName: string,
output?: string
) {
const response = await fet.default(new fet.Request(url, { headers: { cache: "no-cache" } }));
if (response.status !== 200) { if (response.status !== 200) {
return Response.error('A ' + response.status + ' error occurred while downloading the attachment.'); return Response.error(
"A " + response.status + " error occurred while downloading the attachment."
);
} }
try { try {
@@ -22,10 +29,10 @@ export abstract class DownloadCommand {
const decBuf = await this.cryptoService.decryptFromBytes(buf, key); const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
return await CliUtils.saveResultToFile(Buffer.from(decBuf), output, fileName); return await CliUtils.saveResultToFile(Buffer.from(decBuf), output, fileName);
} catch (e) { } catch (e) {
if (typeof (e) === 'string') { if (typeof e === "string") {
return Response.error(e); return Response.error(e);
} else { } else {
return Response.error('An error occurred while saving the attachment.'); return Response.error("An error occurred while saving the attachment.");
} }
} }
} }

View File

@@ -1,48 +1,57 @@
import * as program from 'commander'; import * as program from "commander";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { FolderService } from 'jslib-common/abstractions/folder.service'; import { FolderService } from "jslib-common/abstractions/folder.service";
import { Cipher } from 'jslib-common/models/export/cipher'; import { Cipher } from "jslib-common/models/export/cipher";
import { Collection } from 'jslib-common/models/export/collection'; import { Collection } from "jslib-common/models/export/collection";
import { Folder } from 'jslib-common/models/export/folder'; import { Folder } from "jslib-common/models/export/folder";
import { CollectionRequest } from 'jslib-common/models/request/collectionRequest'; import { CollectionRequest } from "jslib-common/models/request/collectionRequest";
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest'; import { SelectionReadOnlyRequest } from "jslib-common/models/request/selectionReadOnlyRequest";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { CipherResponse } from '../models/response/cipherResponse'; import { CipherResponse } from "../models/response/cipherResponse";
import { FolderResponse } from '../models/response/folderResponse'; import { FolderResponse } from "../models/response/folderResponse";
import { OrganizationCollectionResponse } from '../models/response/organizationCollectionResponse'; import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse";
import { OrganizationCollectionRequest } from '../models/request/organizationCollectionRequest'; import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest";
import { CliUtils } from '../utils'; import { CliUtils } from "../utils";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
export class EditCommand { export class EditCommand {
constructor(private cipherService: CipherService, private folderService: FolderService, constructor(
private cryptoService: CryptoService, private apiService: ApiService) { } private cipherService: CipherService,
private folderService: FolderService,
private cryptoService: CryptoService,
private apiService: ApiService
) {}
async run(object: string, id: string, requestJson: string, cmd: program.Command): Promise<Response> { async run(
if (requestJson == null || requestJson === '') { object: string,
id: string,
requestJson: string,
cmd: program.Command
): Promise<Response> {
if (requestJson == null || requestJson === "") {
requestJson = await CliUtils.readStdin(); requestJson = await CliUtils.readStdin();
} }
if (requestJson == null || requestJson === '') { if (requestJson == null || requestJson === "") {
return Response.badRequest('`requestJson` was not provided.'); return Response.badRequest("`requestJson` was not provided.");
} }
let req: any = null; let req: any = null;
try { try {
const reqJson = Buffer.from(requestJson, 'base64').toString(); const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson); req = JSON.parse(reqJson);
} catch (e) { } catch (e) {
return Response.badRequest('Error parsing the encoded request data.'); return Response.badRequest("Error parsing the encoded request data.");
} }
if (id != null) { if (id != null) {
@@ -50,16 +59,16 @@ export class EditCommand {
} }
switch (object.toLowerCase()) { switch (object.toLowerCase()) {
case 'item': case "item":
return await this.editCipher(id, req); return await this.editCipher(id, req);
case 'item-collections': case "item-collections":
return await this.editCipherCollections(id, req); return await this.editCipherCollections(id, req);
case 'folder': case "folder":
return await this.editFolder(id, req); return await this.editFolder(id, req);
case 'org-collection': case "org-collection":
return await this.editOrganizationCollection(id, req, cmd); return await this.editOrganizationCollection(id, req, cmd);
default: default:
return Response.badRequest('Unknown object.'); return Response.badRequest("Unknown object.");
} }
} }
@@ -71,7 +80,9 @@ export class EditCommand {
let cipherView = await cipher.decrypt(); let cipherView = await cipher.decrypt();
if (cipherView.isDeleted) { if (cipherView.isDeleted) {
return Response.badRequest('You may not edit a deleted cipher. Use restore item <id> command first.'); return Response.badRequest(
"You may not edit a deleted cipher. Use restore item <id> command first."
);
} }
cipherView = Cipher.toView(req, cipherView); cipherView = Cipher.toView(req, cipherView);
const encCipher = await this.cipherService.encrypt(cipherView); const encCipher = await this.cipherService.encrypt(cipherView);
@@ -92,7 +103,9 @@ export class EditCommand {
return Response.notFound(); return Response.notFound();
} }
if (cipher.organizationId == null) { if (cipher.organizationId == null) {
return Response.badRequest('Item does not belong to an organization. Consider sharing it first.'); return Response.badRequest(
"Item does not belong to an organization. Consider sharing it first."
);
} }
cipher.collectionIds = req; cipher.collectionIds = req;
@@ -127,27 +140,33 @@ export class EditCommand {
} }
} }
private async editOrganizationCollection(id: string, req: OrganizationCollectionRequest, options: program.OptionValues) { private async editOrganizationCollection(
if (options.organizationid == null || options.organizationid === '') { id: string,
return Response.badRequest('--organizationid <organizationid> required.'); req: OrganizationCollectionRequest,
options: program.OptionValues
) {
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
} }
if (!Utils.isGuid(id)) { if (!Utils.isGuid(id)) {
return Response.error('`' + id + '` is not a GUID.'); return Response.error("`" + id + "` is not a GUID.");
} }
if (!Utils.isGuid(options.organizationid)) { if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.'); return Response.error("`" + options.organizationid + "` is not a GUID.");
} }
if (options.organizationid !== req.organizationId) { if (options.organizationid !== req.organizationId) {
return Response.error('--organizationid <organizationid> does not match request object.'); return Response.error("--organizationid <organizationid> does not match request object.");
} }
try { try {
const orgKey = await this.cryptoService.getOrgKey(req.organizationId); const orgKey = await this.cryptoService.getOrgKey(req.organizationId);
if (orgKey == null) { if (orgKey == null) {
throw new Error('No encryption key for this organization.'); throw new Error("No encryption key for this organization.");
} }
const groups = req.groups == null ? null : const groups =
req.groups.map(g => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords)); req.groups == null
? null
: req.groups.map((g) => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords));
const request = new CollectionRequest(); const request = new CollectionRequest();
request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString; request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString;
request.externalId = req.externalId; request.externalId = req.externalId;

View File

@@ -1,17 +1,17 @@
import * as program from 'commander'; import * as program from "commander";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse'; import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
import { CliUtils } from '../utils'; import { CliUtils } from "../utils";
export class EncodeCommand { export class EncodeCommand {
async run(): Promise<Response> { async run(): Promise<Response> {
if (process.stdin.isTTY) { if (process.stdin.isTTY) {
return Response.badRequest('No stdin was piped in.'); return Response.badRequest("No stdin was piped in.");
} }
const input = await CliUtils.readStdin(); const input = await CliUtils.readStdin();
const b64 = Buffer.from(input, 'utf8').toString('base64'); const b64 = Buffer.from(input, "utf8").toString("base64");
const res = new StringResponse(b64); const res = new StringResponse(b64);
return Response.success(res); return Response.success(res);
} }

View File

@@ -1,39 +1,47 @@
import * as program from 'commander'; import * as program from "commander";
import * as inquirer from 'inquirer'; import * as inquirer from "inquirer";
import { ExportService } from 'jslib-common/abstractions/export.service'; import { ExportService } from "jslib-common/abstractions/export.service";
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service'; import { UserVerificationService } from "jslib-common/abstractions/userVerification.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { PolicyType } from 'jslib-common/enums/policyType'; import { PolicyType } from "jslib-common/enums/policyType";
import { VerificationType } from 'jslib-common/enums/verificationType'; import { VerificationType } from "jslib-common/enums/verificationType";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
import { CliUtils } from '../utils'; import { CliUtils } from "../utils";
export class ExportCommand { export class ExportCommand {
constructor(private exportService: ExportService, private policyService: PolicyService, constructor(
private keyConnectorService: KeyConnectorService, private userVerificationService: UserVerificationService) { } private exportService: ExportService,
private policyService: PolicyService,
private keyConnectorService: KeyConnectorService,
private userVerificationService: UserVerificationService
) {}
async run(password: string, options: program.OptionValues): Promise<Response> { async run(password: string, options: program.OptionValues): Promise<Response> {
if (options.organizationid == null && if (
await this.policyService.policyAppliesToUser(PolicyType.DisablePersonalVaultExport)) { options.organizationid == null &&
(await this.policyService.policyAppliesToUser(PolicyType.DisablePersonalVaultExport))
) {
return Response.badRequest( return Response.badRequest(
'One or more organization policies prevents you from exporting your personal vault.' "One or more organization policies prevents you from exporting your personal vault."
); );
} }
const canInteract = process.env.BW_NOINTERACTION !== 'true'; const canInteract = process.env.BW_NOINTERACTION !== "true";
if (!canInteract) { if (!canInteract) {
return Response.badRequest('User verification is required. Try running this command again in interactive mode.'); return Response.badRequest(
"User verification is required. Try running this command again in interactive mode."
);
} }
try { try {
await this.keyConnectorService.getUsesKeyConnector() (await this.keyConnectorService.getUsesKeyConnector())
? await this.verifyOTP() ? await this.verifyOTP()
: await this.verifyMasterPassword(password); : await this.verifyMasterPassword(password);
} catch (e) { } catch (e) {
@@ -41,26 +49,31 @@ export class ExportCommand {
} }
let format = options.format; let format = options.format;
if (format !== 'encrypted_json' && format !== 'json') { if (format !== "encrypted_json" && format !== "json") {
format = 'csv'; format = "csv";
} }
if (options.organizationid != null && !Utils.isGuid(options.organizationid)) { if (options.organizationid != null && !Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.'); return Response.error("`" + options.organizationid + "` is not a GUID.");
} }
let exportContent: string = null; let exportContent: string = null;
try { try {
exportContent = options.organizationid != null ? exportContent =
await this.exportService.getOrganizationExport(options.organizationid, format) : options.organizationid != null
await this.exportService.getExport(format); ? await this.exportService.getOrganizationExport(options.organizationid, format)
: await this.exportService.getExport(format);
} catch (e) { } catch (e) {
return Response.error(e); return Response.error(e);
} }
return await this.saveFile(exportContent, options, format); return await this.saveFile(exportContent, options, format);
} }
async saveFile(exportContent: string, options: program.OptionValues, format: string): Promise<Response> { async saveFile(
exportContent: string,
options: program.OptionValues,
format: string
): Promise<Response> {
try { try {
const fileName = this.getFileName(format, options.organizationid != null ? 'org' : null); const fileName = this.getFileName(format, options.organizationid != null ? "org" : null);
return await CliUtils.saveResultToFile(exportContent, options.output, fileName); return await CliUtils.saveResultToFile(exportContent, options.output, fileName);
} catch (e) { } catch (e) {
return Response.error(e.toString()); return Response.error(e.toString());
@@ -68,23 +81,25 @@ export class ExportCommand {
} }
private getFileName(format: string, prefix?: string) { private getFileName(format: string, prefix?: string) {
if (format === 'encrypted_json') { if (format === "encrypted_json") {
if (prefix == null) { if (prefix == null) {
prefix = 'encrypted'; prefix = "encrypted";
} else { } else {
prefix = 'encrypted_' + prefix; prefix = "encrypted_" + prefix;
} }
format = 'json'; format = "json";
} }
return this.exportService.getFileName(prefix, format); return this.exportService.getFileName(prefix, format);
} }
private async verifyMasterPassword(password: string) { private async verifyMasterPassword(password: string) {
if (password == null || password === '') { if (password == null || password === "") {
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ const answer: inquirer.Answers = await inquirer.createPromptModule({
type: 'password', output: process.stderr,
name: 'password', })({
message: 'Master password:', type: "password",
name: "password",
message: "Master password:",
}); });
password = answer.password; password = answer.password;
} }
@@ -97,10 +112,12 @@ export class ExportCommand {
private async verifyOTP() { private async verifyOTP() {
await this.userVerificationService.requestOTP(); await this.userVerificationService.requestOTP();
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ const answer: inquirer.Answers = await inquirer.createPromptModule({
type: 'password', output: process.stderr,
name: 'otp', })({
message: 'A verification code has been emailed to you.\n Verification code:', type: "password",
name: "otp",
message: "A verification code has been emailed to you.\n Verification code:",
}); });
await this.userVerificationService.verifyUser({ await this.userVerificationService.verifyUser({

View File

@@ -1,12 +1,12 @@
import * as program from 'commander'; import * as program from "commander";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse'; import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
export class GenerateCommand { export class GenerateCommand {
constructor(private passwordGenerationService: PasswordGenerationService) { } constructor(private passwordGenerationService: PasswordGenerationService) {}
async run(cmdOptions: program.OptionValues): Promise<Response> { async run(cmdOptions: program.OptionValues): Promise<Response> {
const options = { const options = {
@@ -15,8 +15,8 @@ export class GenerateCommand {
number: cmdOptions.number || false, number: cmdOptions.number || false,
special: cmdOptions.special || false, special: cmdOptions.special || false,
length: cmdOptions.length || 14, length: cmdOptions.length || 14,
type: cmdOptions.passphrase ? 'passphrase' : 'password', type: cmdOptions.passphrase ? "passphrase" : "password",
wordSeparator: cmdOptions.separator == null ? '-' : cmdOptions.separator, wordSeparator: cmdOptions.separator == null ? "-" : cmdOptions.separator,
numWords: cmdOptions.words || 3, numWords: cmdOptions.words || 3,
capitalize: cmdOptions.capitalize || false, capitalize: cmdOptions.capitalize || false,
includeNumber: cmdOptions.includeNumber || false, includeNumber: cmdOptions.includeNumber || false,
@@ -32,12 +32,13 @@ export class GenerateCommand {
if (options.numWords < 3) { if (options.numWords < 3) {
options.numWords = 3; options.numWords = 3;
} }
if (options.wordSeparator === 'space') { if (options.wordSeparator === "space") {
options.wordSeparator = ' '; options.wordSeparator = " ";
} else if (options.wordSeparator != null && options.wordSeparator.length > 1) { } else if (options.wordSeparator != null && options.wordSeparator.length > 1) {
options.wordSeparator = options.wordSeparator[0]; options.wordSeparator = options.wordSeparator[0];
} }
const enforcedOptions = await this.passwordGenerationService.enforcePasswordGeneratorPoliciesOnOptions(options); const enforcedOptions =
await this.passwordGenerationService.enforcePasswordGeneratorPoliciesOnOptions(options);
const password = await this.passwordGenerationService.generatePassword(enforcedOptions[0]); const password = await this.passwordGenerationService.generatePassword(enforcedOptions[0]);
const res = new StringResponse(password); const res = new StringResponse(password);
return Response.success(res); return Response.success(res);

View File

@@ -1,68 +1,75 @@
import * as program from 'commander'; import * as program from "commander";
import { CipherType } from 'jslib-common/enums/cipherType'; import { CipherType } from "jslib-common/enums/cipherType";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuditService } from 'jslib-common/abstractions/audit.service'; import { AuditService } from "jslib-common/abstractions/audit.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from "jslib-common/abstractions/collection.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { FolderService } from 'jslib-common/abstractions/folder.service'; import { FolderService } from "jslib-common/abstractions/folder.service";
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from "jslib-common/abstractions/search.service";
import { SendService } from 'jslib-common/abstractions/send.service'; import { SendService } from "jslib-common/abstractions/send.service";
import { TotpService } from 'jslib-common/abstractions/totp.service'; import { TotpService } from "jslib-common/abstractions/totp.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { Organization } from 'jslib-common/models/domain/organization'; import { Organization } from "jslib-common/models/domain/organization";
import { Card } from 'jslib-common/models/export/card'; import { Card } from "jslib-common/models/export/card";
import { Cipher } from 'jslib-common/models/export/cipher'; import { Cipher } from "jslib-common/models/export/cipher";
import { Collection } from 'jslib-common/models/export/collection'; import { Collection } from "jslib-common/models/export/collection";
import { Field } from 'jslib-common/models/export/field'; import { Field } from "jslib-common/models/export/field";
import { Folder } from 'jslib-common/models/export/folder'; import { Folder } from "jslib-common/models/export/folder";
import { Identity } from 'jslib-common/models/export/identity'; import { Identity } from "jslib-common/models/export/identity";
import { Login } from 'jslib-common/models/export/login'; import { Login } from "jslib-common/models/export/login";
import { LoginUri } from 'jslib-common/models/export/loginUri'; import { LoginUri } from "jslib-common/models/export/loginUri";
import { SecureNote } from 'jslib-common/models/export/secureNote'; import { SecureNote } from "jslib-common/models/export/secureNote";
import { CipherView } from 'jslib-common/models/view/cipherView'; import { CipherView } from "jslib-common/models/view/cipherView";
import { CollectionView } from 'jslib-common/models/view/collectionView'; import { CollectionView } from "jslib-common/models/view/collectionView";
import { FolderView } from 'jslib-common/models/view/folderView'; import { FolderView } from "jslib-common/models/view/folderView";
import { EncString } from 'jslib-common/models/domain/encString'; import { EncString } from "jslib-common/models/domain/encString";
import { ErrorResponse } from 'jslib-common/models/response/errorResponse'; import { ErrorResponse } from "jslib-common/models/response/errorResponse";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse'; import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
import { SendType } from 'jslib-common/enums/sendType'; import { SendType } from "jslib-common/enums/sendType";
import { CipherResponse } from '../models/response/cipherResponse'; import { CipherResponse } from "../models/response/cipherResponse";
import { CollectionResponse } from '../models/response/collectionResponse'; import { CollectionResponse } from "../models/response/collectionResponse";
import { FolderResponse } from '../models/response/folderResponse'; import { FolderResponse } from "../models/response/folderResponse";
import { OrganizationCollectionResponse } from '../models/response/organizationCollectionResponse'; import { OrganizationCollectionResponse } from "../models/response/organizationCollectionResponse";
import { OrganizationResponse } from '../models/response/organizationResponse'; import { OrganizationResponse } from "../models/response/organizationResponse";
import { SendResponse } from '../models/response/sendResponse'; import { SendResponse } from "../models/response/sendResponse";
import { TemplateResponse } from '../models/response/templateResponse'; import { TemplateResponse } from "../models/response/templateResponse";
import { OrganizationCollectionRequest } from '../models/request/organizationCollectionRequest'; import { OrganizationCollectionRequest } from "../models/request/organizationCollectionRequest";
import { SelectionReadOnly } from '../models/selectionReadOnly'; import { SelectionReadOnly } from "../models/selectionReadOnly";
import { DownloadCommand } from './download.command'; import { DownloadCommand } from "./download.command";
import { CliUtils } from '../utils'; import { CliUtils } from "../utils";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
export class GetCommand extends DownloadCommand { export class GetCommand extends DownloadCommand {
constructor(private cipherService: CipherService, private folderService: FolderService, constructor(
private collectionService: CollectionService, private totpService: TotpService, private cipherService: CipherService,
private auditService: AuditService, cryptoService: CryptoService, private folderService: FolderService,
private userService: UserService, private searchService: SearchService, private collectionService: CollectionService,
private apiService: ApiService, private sendService: SendService, private totpService: TotpService,
private environmentService: EnvironmentService) { private auditService: AuditService,
cryptoService: CryptoService,
private userService: UserService,
private searchService: SearchService,
private apiService: ApiService,
private sendService: SendService,
private environmentService: EnvironmentService
) {
super(cryptoService); super(cryptoService);
} }
@@ -72,36 +79,36 @@ export class GetCommand extends DownloadCommand {
} }
switch (object.toLowerCase()) { switch (object.toLowerCase()) {
case 'item': case "item":
return await this.getCipher(id); return await this.getCipher(id);
case 'username': case "username":
return await this.getUsername(id); return await this.getUsername(id);
case 'password': case "password":
return await this.getPassword(id); return await this.getPassword(id);
case 'uri': case "uri":
return await this.getUri(id); return await this.getUri(id);
case 'totp': case "totp":
return await this.getTotp(id); return await this.getTotp(id);
case 'notes': case "notes":
return await this.getNotes(id); return await this.getNotes(id);
case 'exposed': case "exposed":
return await this.getExposed(id); return await this.getExposed(id);
case 'attachment': case "attachment":
return await this.getAttachment(id, options); return await this.getAttachment(id, options);
case 'folder': case "folder":
return await this.getFolder(id); return await this.getFolder(id);
case 'collection': case "collection":
return await this.getCollection(id); return await this.getCollection(id);
case 'org-collection': case "org-collection":
return await this.getOrganizationCollection(id, options); return await this.getOrganizationCollection(id, options);
case 'organization': case "organization":
return await this.getOrganization(id); return await this.getOrganization(id);
case 'template': case "template":
return await this.getTemplate(id); return await this.getTemplate(id);
case 'fingerprint': case "fingerprint":
return await this.getFingerprint(id); return await this.getFingerprint(id);
default: default:
return Response.badRequest('Unknown object.'); return Response.badRequest("Unknown object.");
} }
} }
@@ -112,7 +119,7 @@ export class GetCommand extends DownloadCommand {
if (cipher != null) { if (cipher != null) {
decCipher = await cipher.decrypt(); decCipher = await cipher.decrypt();
} }
} else if (id.trim() !== '') { } else if (id.trim() !== "") {
let ciphers = await this.cipherService.getAllDecrypted(); let ciphers = await this.cipherService.getAllDecrypted();
ciphers = this.searchService.searchCiphersBasic(ciphers, id); ciphers = this.searchService.searchCiphersBasic(ciphers, id);
if (ciphers.length > 1) { if (ciphers.length > 1) {
@@ -139,7 +146,7 @@ export class GetCommand extends DownloadCommand {
} }
} }
if (Array.isArray(decCipher)) { if (Array.isArray(decCipher)) {
return Response.multipleResults(decCipher.map(c => c.id)); return Response.multipleResults(decCipher.map((c) => c.id));
} }
} }
const res = new CipherResponse(decCipher); const res = new CipherResponse(decCipher);
@@ -147,19 +154,21 @@ export class GetCommand extends DownloadCommand {
} }
private async getUsername(id: string) { private async getUsername(id: string) {
const cipherResponse = await this.getCipher(id, const cipherResponse = await this.getCipher(
c => c.type === CipherType.Login && !Utils.isNullOrWhitespace(c.login.username)); id,
(c) => c.type === CipherType.Login && !Utils.isNullOrWhitespace(c.login.username)
);
if (!cipherResponse.success) { if (!cipherResponse.success) {
return cipherResponse; return cipherResponse;
} }
const cipher = cipherResponse.data as CipherResponse; const cipher = cipherResponse.data as CipherResponse;
if (cipher.type !== CipherType.Login) { if (cipher.type !== CipherType.Login) {
return Response.badRequest('Not a login.'); return Response.badRequest("Not a login.");
} }
if (Utils.isNullOrWhitespace(cipher.login.username)) { if (Utils.isNullOrWhitespace(cipher.login.username)) {
return Response.error('No username available for this login.'); return Response.error("No username available for this login.");
} }
const res = new StringResponse(cipher.login.username); const res = new StringResponse(cipher.login.username);
@@ -167,19 +176,21 @@ export class GetCommand extends DownloadCommand {
} }
private async getPassword(id: string) { private async getPassword(id: string) {
const cipherResponse = await this.getCipher(id, const cipherResponse = await this.getCipher(
c => c.type === CipherType.Login && !Utils.isNullOrWhitespace(c.login.password)); id,
(c) => c.type === CipherType.Login && !Utils.isNullOrWhitespace(c.login.password)
);
if (!cipherResponse.success) { if (!cipherResponse.success) {
return cipherResponse; return cipherResponse;
} }
const cipher = cipherResponse.data as CipherResponse; const cipher = cipherResponse.data as CipherResponse;
if (cipher.type !== CipherType.Login) { if (cipher.type !== CipherType.Login) {
return Response.badRequest('Not a login.'); return Response.badRequest("Not a login.");
} }
if (Utils.isNullOrWhitespace(cipher.login.password)) { if (Utils.isNullOrWhitespace(cipher.login.password)) {
return Response.error('No password available for this login.'); return Response.error("No password available for this login.");
} }
const res = new StringResponse(cipher.login.password); const res = new StringResponse(cipher.login.password);
@@ -187,20 +198,29 @@ export class GetCommand extends DownloadCommand {
} }
private async getUri(id: string) { private async getUri(id: string) {
const cipherResponse = await this.getCipher(id, const cipherResponse = await this.getCipher(
c => c.type === CipherType.Login && c.login.uris != null && c.login.uris.length > 0 && id,
c.login.uris[0].uri !== ''); (c) =>
c.type === CipherType.Login &&
c.login.uris != null &&
c.login.uris.length > 0 &&
c.login.uris[0].uri !== ""
);
if (!cipherResponse.success) { if (!cipherResponse.success) {
return cipherResponse; return cipherResponse;
} }
const cipher = cipherResponse.data as CipherResponse; const cipher = cipherResponse.data as CipherResponse;
if (cipher.type !== CipherType.Login) { if (cipher.type !== CipherType.Login) {
return Response.badRequest('Not a login.'); return Response.badRequest("Not a login.");
} }
if (cipher.login.uris == null || cipher.login.uris.length === 0 || cipher.login.uris[0].uri === '') { if (
return Response.error('No uri available for this login.'); cipher.login.uris == null ||
cipher.login.uris.length === 0 ||
cipher.login.uris[0].uri === ""
) {
return Response.error("No uri available for this login.");
} }
const res = new StringResponse(cipher.login.uris[0].uri); const res = new StringResponse(cipher.login.uris[0].uri);
@@ -208,32 +228,37 @@ export class GetCommand extends DownloadCommand {
} }
private async getTotp(id: string) { private async getTotp(id: string) {
const cipherResponse = await this.getCipher(id, const cipherResponse = await this.getCipher(
c => c.type === CipherType.Login && !Utils.isNullOrWhitespace(c.login.totp)); id,
(c) => c.type === CipherType.Login && !Utils.isNullOrWhitespace(c.login.totp)
);
if (!cipherResponse.success) { if (!cipherResponse.success) {
return cipherResponse; return cipherResponse;
} }
const cipher = cipherResponse.data as CipherResponse; const cipher = cipherResponse.data as CipherResponse;
if (cipher.type !== CipherType.Login) { if (cipher.type !== CipherType.Login) {
return Response.badRequest('Not a login.'); return Response.badRequest("Not a login.");
} }
if (Utils.isNullOrWhitespace(cipher.login.totp)) { if (Utils.isNullOrWhitespace(cipher.login.totp)) {
return Response.error('No TOTP available for this login.'); return Response.error("No TOTP available for this login.");
} }
const totp = await this.totpService.getCode(cipher.login.totp); const totp = await this.totpService.getCode(cipher.login.totp);
if (totp == null) { if (totp == null) {
return Response.error('Couldn\'t generate TOTP code.'); return Response.error("Couldn't generate TOTP code.");
} }
const canAccessPremium = await this.userService.canAccessPremium(); const canAccessPremium = await this.userService.canAccessPremium();
if (!canAccessPremium) { if (!canAccessPremium) {
const originalCipher = await this.cipherService.get(cipher.id); const originalCipher = await this.cipherService.get(cipher.id);
if (originalCipher == null || originalCipher.organizationId == null || if (
!originalCipher.organizationUseTotp) { originalCipher == null ||
return Response.error('Premium status is required to use this feature.'); originalCipher.organizationId == null ||
!originalCipher.organizationUseTotp
) {
return Response.error("Premium status is required to use this feature.");
} }
} }
@@ -242,15 +267,14 @@ export class GetCommand extends DownloadCommand {
} }
private async getNotes(id: string) { private async getNotes(id: string) {
const cipherResponse = await this.getCipher(id, const cipherResponse = await this.getCipher(id, (c) => !Utils.isNullOrWhitespace(c.notes));
c => !Utils.isNullOrWhitespace(c.notes));
if (!cipherResponse.success) { if (!cipherResponse.success) {
return cipherResponse; return cipherResponse;
} }
const cipher = cipherResponse.data as CipherResponse; const cipher = cipherResponse.data as CipherResponse;
if (Utils.isNullOrWhitespace(cipher.notes)) { if (Utils.isNullOrWhitespace(cipher.notes)) {
return Response.error('No notes available for this item.'); return Response.error("No notes available for this item.");
} }
const res = new StringResponse(cipher.notes); const res = new StringResponse(cipher.notes);
@@ -263,14 +287,16 @@ export class GetCommand extends DownloadCommand {
return passwordResponse; return passwordResponse;
} }
const exposedNumber = await this.auditService.passwordLeaked((passwordResponse.data as StringResponse).data); const exposedNumber = await this.auditService.passwordLeaked(
(passwordResponse.data as StringResponse).data
);
const res = new StringResponse(exposedNumber.toString()); const res = new StringResponse(exposedNumber.toString());
return Response.success(res); return Response.success(res);
} }
private async getAttachment(id: string, options: program.OptionValues) { private async getAttachment(id: string, options: program.OptionValues) {
if (options.itemid == null || options.itemid === '') { if (options.itemid == null || options.itemid === "") {
return Response.badRequest('--itemid <itemid> required.'); return Response.badRequest("--itemid <itemid> required.");
} }
const itemId = options.itemid.toLowerCase(); const itemId = options.itemid.toLowerCase();
@@ -280,35 +306,46 @@ export class GetCommand extends DownloadCommand {
} }
const cipher = await this.getCipherView(itemId); const cipher = await this.getCipherView(itemId);
if (cipher == null || Array.isArray(cipher) || cipher.attachments == null || cipher.attachments.length === 0) { if (
return Response.error('No attachments available for this item.'); cipher == null ||
Array.isArray(cipher) ||
cipher.attachments == null ||
cipher.attachments.length === 0
) {
return Response.error("No attachments available for this item.");
} }
let attachments = cipher.attachments.filter(a => a.id.toLowerCase() === id || let attachments = cipher.attachments.filter(
(a.fileName != null && a.fileName.toLowerCase().indexOf(id) > -1)); (a) =>
a.id.toLowerCase() === id ||
(a.fileName != null && a.fileName.toLowerCase().indexOf(id) > -1)
);
if (attachments.length === 0) { if (attachments.length === 0) {
return Response.error('Attachment `' + id + '` was not found.'); return Response.error("Attachment `" + id + "` was not found.");
} }
const exactMatches = attachments.filter(a => a.fileName.toLowerCase() === id); const exactMatches = attachments.filter((a) => a.fileName.toLowerCase() === id);
if (exactMatches.length === 1) { if (exactMatches.length === 1) {
attachments = exactMatches; attachments = exactMatches;
} }
if (attachments.length > 1) { if (attachments.length > 1) {
return Response.multipleResults(attachments.map(a => a.id)); return Response.multipleResults(attachments.map((a) => a.id));
} }
if (!(await this.userService.canAccessPremium())) { if (!(await this.userService.canAccessPremium())) {
const originalCipher = await this.cipherService.get(cipher.id); const originalCipher = await this.cipherService.get(cipher.id);
if (originalCipher == null || originalCipher.organizationId == null) { if (originalCipher == null || originalCipher.organizationId == null) {
return Response.error('Premium status is required to use this feature.'); return Response.error("Premium status is required to use this feature.");
} }
} }
let url: string; let url: string;
try { try {
const attachmentDownloadResponse = await this.apiService.getAttachmentData(cipher.id, attachments[0].id); const attachmentDownloadResponse = await this.apiService.getAttachmentData(
cipher.id,
attachments[0].id
);
url = attachmentDownloadResponse.url; url = attachmentDownloadResponse.url;
} catch (e) { } catch (e) {
if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) {
@@ -320,8 +357,10 @@ export class GetCommand extends DownloadCommand {
} }
} }
const key = attachments[0].key != null ? attachments[0].key : const key =
await this.cryptoService.getOrgKey(cipher.organizationId); attachments[0].key != null
? attachments[0].key
: await this.cryptoService.getOrgKey(cipher.organizationId);
return await this.saveAttachmentToFile(url, key, attachments[0].fileName, options.output); return await this.saveAttachmentToFile(url, key, attachments[0].fileName, options.output);
} }
@@ -332,11 +371,11 @@ export class GetCommand extends DownloadCommand {
if (folder != null) { if (folder != null) {
decFolder = await folder.decrypt(); decFolder = await folder.decrypt();
} }
} else if (id.trim() !== '') { } else if (id.trim() !== "") {
let folders = await this.folderService.getAllDecrypted(); let folders = await this.folderService.getAllDecrypted();
folders = CliUtils.searchFolders(folders, id); folders = CliUtils.searchFolders(folders, id);
if (folders.length > 1) { if (folders.length > 1) {
return Response.multipleResults(folders.map(f => f.id)); return Response.multipleResults(folders.map((f) => f.id));
} }
if (folders.length > 0) { if (folders.length > 0) {
decFolder = folders[0]; decFolder = folders[0];
@@ -357,11 +396,11 @@ export class GetCommand extends DownloadCommand {
if (collection != null) { if (collection != null) {
decCollection = await collection.decrypt(); decCollection = await collection.decrypt();
} }
} else if (id.trim() !== '') { } else if (id.trim() !== "") {
let collections = await this.collectionService.getAllDecrypted(); let collections = await this.collectionService.getAllDecrypted();
collections = CliUtils.searchCollections(collections, id); collections = CliUtils.searchCollections(collections, id);
if (collections.length > 1) { if (collections.length > 1) {
return Response.multipleResults(collections.map(c => c.id)); return Response.multipleResults(collections.map((c) => c.id));
} }
if (collections.length > 0) { if (collections.length > 0) {
decCollection = collections[0]; decCollection = collections[0];
@@ -376,27 +415,31 @@ export class GetCommand extends DownloadCommand {
} }
private async getOrganizationCollection(id: string, options: program.OptionValues) { private async getOrganizationCollection(id: string, options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') { if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest('--organizationid <organizationid> required.'); return Response.badRequest("--organizationid <organizationid> required.");
} }
if (!Utils.isGuid(id)) { if (!Utils.isGuid(id)) {
return Response.error('`' + id + '` is not a GUID.'); return Response.error("`" + id + "` is not a GUID.");
} }
if (!Utils.isGuid(options.organizationid)) { if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.'); return Response.error("`" + options.organizationid + "` is not a GUID.");
} }
try { try {
const orgKey = await this.cryptoService.getOrgKey(options.organizationid); const orgKey = await this.cryptoService.getOrgKey(options.organizationid);
if (orgKey == null) { if (orgKey == null) {
throw new Error('No encryption key for this organization.'); throw new Error("No encryption key for this organization.");
} }
const response = await this.apiService.getCollectionDetails(options.organizationid, id); const response = await this.apiService.getCollectionDetails(options.organizationid, id);
const decCollection = new CollectionView(response); const decCollection = new CollectionView(response);
decCollection.name = await this.cryptoService.decryptToUtf8( decCollection.name = await this.cryptoService.decryptToUtf8(
new EncString(response.name), orgKey); new EncString(response.name),
const groups = response.groups == null ? null : orgKey
response.groups.map(g => new SelectionReadOnly(g.id, g.readOnly, g.hidePasswords)); );
const groups =
response.groups == null
? null
: response.groups.map((g) => new SelectionReadOnly(g.id, g.readOnly, g.hidePasswords));
const res = new OrganizationCollectionResponse(decCollection, groups); const res = new OrganizationCollectionResponse(decCollection, groups);
return Response.success(res); return Response.success(res);
} catch (e) { } catch (e) {
@@ -408,11 +451,11 @@ export class GetCommand extends DownloadCommand {
let org: Organization = null; let org: Organization = null;
if (Utils.isGuid(id)) { if (Utils.isGuid(id)) {
org = await this.userService.getOrganization(id); org = await this.userService.getOrganization(id);
} else if (id.trim() !== '') { } else if (id.trim() !== "") {
let orgs = await this.userService.getAllOrganizations(); let orgs = await this.userService.getAllOrganizations();
orgs = CliUtils.searchOrganizations(orgs, id); orgs = CliUtils.searchOrganizations(orgs, id);
if (orgs.length > 1) { if (orgs.length > 1) {
return Response.multipleResults(orgs.map(c => c.id)); return Response.multipleResults(orgs.map((c) => c.id));
} }
if (orgs.length > 0) { if (orgs.length > 0) {
org = orgs[0]; org = orgs[0];
@@ -429,47 +472,47 @@ export class GetCommand extends DownloadCommand {
private async getTemplate(id: string) { private async getTemplate(id: string) {
let template: any = null; let template: any = null;
switch (id.toLowerCase()) { switch (id.toLowerCase()) {
case 'item': case "item":
template = Cipher.template(); template = Cipher.template();
break; break;
case 'item.field': case "item.field":
template = Field.template(); template = Field.template();
break; break;
case 'item.login': case "item.login":
template = Login.template(); template = Login.template();
break; break;
case 'item.login.uri': case "item.login.uri":
template = LoginUri.template(); template = LoginUri.template();
break; break;
case 'item.card': case "item.card":
template = Card.template(); template = Card.template();
break; break;
case 'item.identity': case "item.identity":
template = Identity.template(); template = Identity.template();
break; break;
case 'item.securenote': case "item.securenote":
template = SecureNote.template(); template = SecureNote.template();
break; break;
case 'folder': case "folder":
template = Folder.template(); template = Folder.template();
break; break;
case 'collection': case "collection":
template = Collection.template(); template = Collection.template();
break; break;
case 'item-collections': case "item-collections":
template = ['collection-id1', 'collection-id2']; template = ["collection-id1", "collection-id2"];
break; break;
case 'org-collection': case "org-collection":
template = OrganizationCollectionRequest.template(); template = OrganizationCollectionRequest.template();
break; break;
case 'send.text': case "send.text":
template = SendResponse.template(SendType.Text); template = SendResponse.template(SendType.Text);
break; break;
case 'send.file': case "send.file":
template = SendResponse.template(SendType.File); template = SendResponse.template(SendType.File);
break; break;
default: default:
return Response.badRequest('Unknown template object.'); return Response.badRequest("Unknown template object.");
} }
const res = new TemplateResponse(template); const res = new TemplateResponse(template);
@@ -478,20 +521,20 @@ export class GetCommand extends DownloadCommand {
private async getFingerprint(id: string) { private async getFingerprint(id: string) {
let fingerprint: string[] = null; let fingerprint: string[] = null;
if (id === 'me') { if (id === "me") {
fingerprint = await this.cryptoService.getFingerprint(await this.userService.getUserId()); fingerprint = await this.cryptoService.getFingerprint(await this.userService.getUserId());
} else if (Utils.isGuid(id)) { } else if (Utils.isGuid(id)) {
try { try {
const response = await this.apiService.getUserPublicKey(id); const response = await this.apiService.getUserPublicKey(id);
const pubKey = Utils.fromB64ToArray(response.publicKey); const pubKey = Utils.fromB64ToArray(response.publicKey);
fingerprint = await this.cryptoService.getFingerprint(id, pubKey.buffer); fingerprint = await this.cryptoService.getFingerprint(id, pubKey.buffer);
} catch { } } catch {}
} }
if (fingerprint == null) { if (fingerprint == null) {
return Response.notFound(); return Response.notFound();
} }
const res = new StringResponse(fingerprint.join('-')); const res = new StringResponse(fingerprint.join("-"));
return Response.success(res); return Response.success(res);
} }
} }

View File

@@ -1,14 +1,14 @@
import * as program from 'commander'; import * as program from "commander";
import { ImportService } from 'jslib-common/abstractions/import.service'; import { ImportService } from "jslib-common/abstractions/import.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { CliUtils } from '../utils'; import { CliUtils } from "../utils";
export class ImportCommand { export class ImportCommand {
constructor(private importService: ImportService, private userService: UserService) { } constructor(private importService: ImportService, private userService: UserService) {}
async run(format: string, filepath: string, options: program.OptionValues): Promise<Response> { async run(format: string, filepath: string, options: program.OptionValues): Promise<Response> {
const organizationId = options.organizationid; const organizationId = options.organizationid;
@@ -16,11 +16,15 @@ export class ImportCommand {
const organization = await this.userService.getOrganization(organizationId); const organization = await this.userService.getOrganization(organizationId);
if (organization == null) { if (organization == null) {
return Response.badRequest(`You do not belong to an organization with the ID of ${organizationId}. Check the organization ID and sync your vault.`); return Response.badRequest(
`You do not belong to an organization with the ID of ${organizationId}. Check the organization ID and sync your vault.`
);
} }
if (!organization.canAccessImportExport) { if (!organization.canAccessImportExport) {
return Response.badRequest('You are not authorized to import into the provided organization.'); return Response.badRequest(
"You are not authorized to import into the provided organization."
);
} }
} }
@@ -32,29 +36,29 @@ export class ImportCommand {
} }
private async import(format: string, filepath: string, organizationId: string) { private async import(format: string, filepath: string, organizationId: string) {
if (format == null || format === '') { if (format == null || format === "") {
return Response.badRequest('`format` was not provided.'); return Response.badRequest("`format` was not provided.");
} }
if (filepath == null || filepath === '') { if (filepath == null || filepath === "") {
return Response.badRequest('`filepath` was not provided.'); return Response.badRequest("`filepath` was not provided.");
} }
const importer = await this.importService.getImporter(format, organizationId); const importer = await this.importService.getImporter(format, organizationId);
if (importer === null) { if (importer === null) {
return Response.badRequest('Proper importer type required.'); return Response.badRequest("Proper importer type required.");
} }
try { try {
const contents = await CliUtils.readFile(filepath); const contents = await CliUtils.readFile(filepath);
if (contents === null || contents === '') { if (contents === null || contents === "") {
return Response.badRequest('Import file was empty.'); return Response.badRequest("Import file was empty.");
} }
const err = await this.importService.import(importer, contents, organizationId); const err = await this.importService.import(importer, contents, organizationId);
if (err != null) { if (err != null) {
return Response.badRequest(err.message); return Response.badRequest(err.message);
} }
const res = new MessageResponse('Imported ' + filepath, null); const res = new MessageResponse("Imported " + filepath, null);
return Response.success(res); return Response.success(res);
} catch (err) { } catch (err) {
return Response.badRequest(err); return Response.badRequest(err);
@@ -62,10 +66,14 @@ export class ImportCommand {
} }
private async list() { private async list() {
const options = this.importService.getImportOptions().sort((a, b) => { const options = this.importService
.getImportOptions()
.sort((a, b) => {
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0; return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
}).map(option => option.id).join('\n'); })
const res = new MessageResponse('Supported input formats:', options); .map((option) => option.id)
.join("\n");
const res = new MessageResponse("Supported input formats:", options);
res.raw = options; res.raw = options;
return Response.success(res); return Response.success(res);
} }

View File

@@ -1,129 +1,146 @@
import * as program from 'commander'; import * as program from "commander";
import { CipherView } from 'jslib-common/models/view/cipherView'; import { CipherView } from "jslib-common/models/view/cipherView";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from 'jslib-common/abstractions/collection.service'; import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from 'jslib-common/abstractions/folder.service'; import { FolderService } from "jslib-common/abstractions/folder.service";
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from "jslib-common/abstractions/search.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { import {
CollectionDetailsResponse as ApiCollectionDetailsResponse, CollectionDetailsResponse as ApiCollectionDetailsResponse,
CollectionResponse as ApiCollectionResponse, CollectionResponse as ApiCollectionResponse,
} from 'jslib-common/models/response/collectionResponse'; } from "jslib-common/models/response/collectionResponse";
import { ListResponse as ApiListResponse } from 'jslib-common/models/response/listResponse'; import { ListResponse as ApiListResponse } from "jslib-common/models/response/listResponse";
import { CollectionData } from 'jslib-common/models/data/collectionData'; import { CollectionData } from "jslib-common/models/data/collectionData";
import { Collection } from 'jslib-common/models/domain/collection'; import { Collection } from "jslib-common/models/domain/collection";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { ListResponse } from 'jslib-node/cli/models/response/listResponse'; import { ListResponse } from "jslib-node/cli/models/response/listResponse";
import { CipherResponse } from '../models/response/cipherResponse'; import { CipherResponse } from "../models/response/cipherResponse";
import { CollectionResponse } from '../models/response/collectionResponse'; import { CollectionResponse } from "../models/response/collectionResponse";
import { FolderResponse } from '../models/response/folderResponse'; import { FolderResponse } from "../models/response/folderResponse";
import { OrganizationResponse } from '../models/response/organizationResponse'; import { OrganizationResponse } from "../models/response/organizationResponse";
import { OrganizationUserResponse } from '../models/response/organizationUserResponse'; import { OrganizationUserResponse } from "../models/response/organizationUserResponse";
import { CliUtils } from '../utils'; import { CliUtils } from "../utils";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
export class ListCommand { export class ListCommand {
constructor(private cipherService: CipherService, private folderService: FolderService, constructor(
private collectionService: CollectionService, private userService: UserService, private cipherService: CipherService,
private searchService: SearchService, private apiService: ApiService) { } private folderService: FolderService,
private collectionService: CollectionService,
private userService: UserService,
private searchService: SearchService,
private apiService: ApiService
) {}
async run(object: string, cmd: program.Command): Promise<Response> { async run(object: string, cmd: program.Command): Promise<Response> {
switch (object.toLowerCase()) { switch (object.toLowerCase()) {
case 'items': case "items":
return await this.listCiphers(cmd); return await this.listCiphers(cmd);
case 'folders': case "folders":
return await this.listFolders(cmd); return await this.listFolders(cmd);
case 'collections': case "collections":
return await this.listCollections(cmd); return await this.listCollections(cmd);
case 'org-collections': case "org-collections":
return await this.listOrganizationCollections(cmd); return await this.listOrganizationCollections(cmd);
case 'org-members': case "org-members":
return await this.listOrganizationMembers(cmd); return await this.listOrganizationMembers(cmd);
case 'organizations': case "organizations":
return await this.listOrganizations(cmd); return await this.listOrganizations(cmd);
default: default:
return Response.badRequest('Unknown object.'); return Response.badRequest("Unknown object.");
} }
} }
private async listCiphers(options: program.OptionValues) { private async listCiphers(options: program.OptionValues) {
let ciphers: CipherView[]; let ciphers: CipherView[];
options.trash = options.trash || false; options.trash = options.trash || false;
if (options.url != null && options.url.trim() !== '') { if (options.url != null && options.url.trim() !== "") {
ciphers = await this.cipherService.getAllDecryptedForUrl(options.url); ciphers = await this.cipherService.getAllDecryptedForUrl(options.url);
} else { } else {
ciphers = await this.cipherService.getAllDecrypted(); ciphers = await this.cipherService.getAllDecrypted();
} }
if (options.folderid != null || options.collectionid != null || options.organizationid != null) { if (
ciphers = ciphers.filter(c => { options.folderid != null ||
options.collectionid != null ||
options.organizationid != null
) {
ciphers = ciphers.filter((c) => {
if (options.trash !== c.isDeleted) { if (options.trash !== c.isDeleted) {
return false; return false;
} }
if (options.folderid != null) { if (options.folderid != null) {
if (options.folderid === 'notnull' && c.folderId != null) { if (options.folderid === "notnull" && c.folderId != null) {
return true; return true;
} }
const folderId = options.folderid === 'null' ? null : options.folderid; const folderId = options.folderid === "null" ? null : options.folderid;
if (folderId === c.folderId) { if (folderId === c.folderId) {
return true; return true;
} }
} }
if (options.organizationid != null) { if (options.organizationid != null) {
if (options.organizationid === 'notnull' && c.organizationId != null) { if (options.organizationid === "notnull" && c.organizationId != null) {
return true; return true;
} }
const organizationId = options.organizationid === 'null' ? null : options.organizationid; const organizationId = options.organizationid === "null" ? null : options.organizationid;
if (organizationId === c.organizationId) { if (organizationId === c.organizationId) {
return true; return true;
} }
} }
if (options.collectionid != null) { if (options.collectionid != null) {
if (options.collectionid === 'notnull' && c.collectionIds != null && c.collectionIds.length > 0) { if (
options.collectionid === "notnull" &&
c.collectionIds != null &&
c.collectionIds.length > 0
) {
return true; return true;
} }
const collectionId = options.collectionid === 'null' ? null : options.collectionid; const collectionId = options.collectionid === "null" ? null : options.collectionid;
if (collectionId == null && (c.collectionIds == null || c.collectionIds.length === 0)) { if (collectionId == null && (c.collectionIds == null || c.collectionIds.length === 0)) {
return true; return true;
} }
if (collectionId != null && c.collectionIds != null && c.collectionIds.indexOf(collectionId) > -1) { if (
collectionId != null &&
c.collectionIds != null &&
c.collectionIds.indexOf(collectionId) > -1
) {
return true; return true;
} }
} }
return false; return false;
}); });
} else if (options.search == null || options.search.trim() === '') { } else if (options.search == null || options.search.trim() === "") {
ciphers = ciphers.filter(c => options.trash === c.isDeleted); ciphers = ciphers.filter((c) => options.trash === c.isDeleted);
} }
if (options.search != null && options.search.trim() !== '') { if (options.search != null && options.search.trim() !== "") {
ciphers = this.searchService.searchCiphersBasic(ciphers, options.search, options.trash); ciphers = this.searchService.searchCiphersBasic(ciphers, options.search, options.trash);
} }
const res = new ListResponse(ciphers.map(o => new CipherResponse(o))); const res = new ListResponse(ciphers.map((o) => new CipherResponse(o)));
return Response.success(res); return Response.success(res);
} }
private async listFolders(options: program.OptionValues) { private async listFolders(options: program.OptionValues) {
let folders = await this.folderService.getAllDecrypted(); let folders = await this.folderService.getAllDecrypted();
if (options.search != null && options.search.trim() !== '') { if (options.search != null && options.search.trim() !== "") {
folders = CliUtils.searchFolders(folders, options.search); folders = CliUtils.searchFolders(folders, options.search);
} }
const res = new ListResponse(folders.map(o => new FolderResponse(o))); const res = new ListResponse(folders.map((o) => new FolderResponse(o)));
return Response.success(res); return Response.success(res);
} }
@@ -131,7 +148,7 @@ export class ListCommand {
let collections = await this.collectionService.getAllDecrypted(); let collections = await this.collectionService.getAllDecrypted();
if (options.organizationid != null) { if (options.organizationid != null) {
collections = collections.filter(c => { collections = collections.filter((c) => {
if (options.organizationid === c.organizationId) { if (options.organizationid === c.organizationId) {
return true; return true;
} }
@@ -139,24 +156,24 @@ export class ListCommand {
}); });
} }
if (options.search != null && options.search.trim() !== '') { if (options.search != null && options.search.trim() !== "") {
collections = CliUtils.searchCollections(collections, options.search); collections = CliUtils.searchCollections(collections, options.search);
} }
const res = new ListResponse(collections.map(o => new CollectionResponse(o))); const res = new ListResponse(collections.map((o) => new CollectionResponse(o)));
return Response.success(res); return Response.success(res);
} }
private async listOrganizationCollections(options: program.OptionValues) { private async listOrganizationCollections(options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') { if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest('--organizationid <organizationid> required.'); return Response.badRequest("--organizationid <organizationid> required.");
} }
if (!Utils.isGuid(options.organizationid)) { if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.'); return Response.error("`" + options.organizationid + "` is not a GUID.");
} }
const organization = await this.userService.getOrganization(options.organizationid); const organization = await this.userService.getOrganization(options.organizationid);
if (organization == null) { if (organization == null) {
return Response.error('Organization not found.'); return Response.error("Organization not found.");
} }
try { try {
@@ -166,13 +183,14 @@ export class ListCommand {
} else { } else {
response = await this.apiService.getUserCollections(); response = await this.apiService.getUserCollections();
} }
const collections = response.data.filter(c => c.organizationId === options.organizationid).map(r => const collections = response.data
new Collection(new CollectionData(r as ApiCollectionDetailsResponse))); .filter((c) => c.organizationId === options.organizationid)
.map((r) => new Collection(new CollectionData(r as ApiCollectionDetailsResponse)));
let decCollections = await this.collectionService.decryptMany(collections); let decCollections = await this.collectionService.decryptMany(collections);
if (options.search != null && options.search.trim() !== '') { if (options.search != null && options.search.trim() !== "") {
decCollections = CliUtils.searchCollections(decCollections, options.search); decCollections = CliUtils.searchCollections(decCollections, options.search);
} }
const res = new ListResponse(decCollections.map(o => new CollectionResponse(o))); const res = new ListResponse(decCollections.map((o) => new CollectionResponse(o)));
return Response.success(res); return Response.success(res);
} catch (e) { } catch (e) {
return Response.error(e); return Response.error(e);
@@ -180,20 +198,21 @@ export class ListCommand {
} }
private async listOrganizationMembers(options: program.OptionValues) { private async listOrganizationMembers(options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') { if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest('--organizationid <organizationid> required.'); return Response.badRequest("--organizationid <organizationid> required.");
} }
if (!Utils.isGuid(options.organizationid)) { if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.'); return Response.error("`" + options.organizationid + "` is not a GUID.");
} }
const organization = await this.userService.getOrganization(options.organizationid); const organization = await this.userService.getOrganization(options.organizationid);
if (organization == null) { if (organization == null) {
return Response.error('Organization not found.'); return Response.error("Organization not found.");
} }
try { try {
const response = await this.apiService.getOrganizationUsers(options.organizationid); const response = await this.apiService.getOrganizationUsers(options.organizationid);
const res = new ListResponse(response.data.map(r => { const res = new ListResponse(
response.data.map((r) => {
const u = new OrganizationUserResponse(); const u = new OrganizationUserResponse();
u.email = r.email; u.email = r.email;
u.name = r.name; u.name = r.name;
@@ -202,7 +221,8 @@ export class ListCommand {
u.type = r.type; u.type = r.type;
u.twoFactorEnabled = r.twoFactorEnabled; u.twoFactorEnabled = r.twoFactorEnabled;
return u; return u;
})); })
);
return Response.success(res); return Response.success(res);
} catch (e) { } catch (e) {
return Response.error(e); return Response.error(e);
@@ -212,11 +232,11 @@ export class ListCommand {
private async listOrganizations(options: program.OptionValues) { private async listOrganizations(options: program.OptionValues) {
let organizations = await this.userService.getAllOrganizations(); let organizations = await this.userService.getAllOrganizations();
if (options.search != null && options.search.trim() !== '') { if (options.search != null && options.search.trim() !== "") {
organizations = CliUtils.searchOrganizations(organizations, options.search); organizations = CliUtils.searchOrganizations(organizations, options.search);
} }
const res = new ListResponse(organizations.map(o => new OrganizationResponse(o))); const res = new ListResponse(organizations.map((o) => new OrganizationResponse(o)));
return Response.success(res); return Response.success(res);
} }
} }

View File

@@ -1,17 +1,17 @@
import * as program from 'commander'; import * as program from "commander";
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
export class LockCommand { export class LockCommand {
constructor(private vaultTimeoutService: VaultTimeoutService) { } constructor(private vaultTimeoutService: VaultTimeoutService) {}
async run(cmd: program.Command) { async run(cmd: program.Command) {
await this.vaultTimeoutService.lock(); await this.vaultTimeoutService.lock();
process.env.BW_SESSION = null; process.env.BW_SESSION = null;
const res = new MessageResponse('Your vault is locked.', null); const res = new MessageResponse("Your vault is locked.", null);
return Response.success(res); return Response.success(res);
} }
} }

View File

@@ -1,37 +1,58 @@
import * as program from 'commander'; import * as program from "commander";
import * as inquirer from 'inquirer'; import * as inquirer from "inquirer";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from 'jslib-common/abstractions/auth.service'; import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { I18nService } from "jslib-common/abstractions/i18n.service";
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from 'jslib-common/abstractions/policy.service'; import { PolicyService } from "jslib-common/abstractions/policy.service";
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from "jslib-common/abstractions/sync.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
import { LoginCommand as BaseLoginCommand } from 'jslib-node/cli/commands/login.command'; import { LoginCommand as BaseLoginCommand } from "jslib-node/cli/commands/login.command";
export class LoginCommand extends BaseLoginCommand { export class LoginCommand extends BaseLoginCommand {
private options: program.OptionValues; private options: program.OptionValues;
constructor(authService: AuthService, apiService: ApiService, constructor(
cryptoFunctionService: CryptoFunctionService, syncService: SyncService, authService: AuthService,
i18nService: I18nService, environmentService: EnvironmentService, apiService: ApiService,
passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService, cryptoFunctionService: CryptoFunctionService,
userService: UserService, cryptoService: CryptoService, policyService: PolicyService, syncService: SyncService,
keyConnectorService: KeyConnectorService, private logoutCallback: () => Promise<void>) { i18nService: I18nService,
super(authService, apiService, i18nService, environmentService, passwordGenerationService, environmentService: EnvironmentService,
cryptoFunctionService, platformUtilsService, userService, cryptoService, policyService, passwordGenerationService: PasswordGenerationService,
'cli', syncService, keyConnectorService); platformUtilsService: PlatformUtilsService,
userService: UserService,
cryptoService: CryptoService,
policyService: PolicyService,
keyConnectorService: KeyConnectorService,
private logoutCallback: () => Promise<void>
) {
super(
authService,
apiService,
i18nService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
platformUtilsService,
userService,
cryptoService,
policyService,
"cli",
syncService,
keyConnectorService
);
this.logout = this.logoutCallback; this.logout = this.logoutCallback;
this.validatedParams = async () => { this.validatedParams = async () => {
const key = await cryptoFunctionService.randomBytes(64); const key = await cryptoFunctionService.randomBytes(64);
@@ -40,18 +61,31 @@ export class LoginCommand extends BaseLoginCommand {
this.success = async () => { this.success = async () => {
const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
if ((this.options.sso != null || this.options.apikey != null) && this.canInteract && !usesKeyConnector) { if (
const res = new MessageResponse('You are logged in!', '\n' + (this.options.sso != null || this.options.apikey != null) &&
'To unlock your vault, use the `unlock` command. ex:\n' + this.canInteract &&
'$ bw unlock'); !usesKeyConnector
) {
const res = new MessageResponse(
"You are logged in!",
"\n" + "To unlock your vault, use the `unlock` command. ex:\n" + "$ bw unlock"
);
return res; return res;
} else { } else {
const res = new MessageResponse('You are logged in!', '\n' + const res = new MessageResponse(
'To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n' + "You are logged in!",
'$ export BW_SESSION="' + process.env.BW_SESSION + '"\n' + "\n" +
'> $env:BW_SESSION="' + process.env.BW_SESSION + '"\n\n' + "To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n" +
'You can also pass the session key to any command with the `--session` option. ex:\n' + '$ export BW_SESSION="' +
'$ bw list items --session ' + process.env.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; res.raw = process.env.BW_SESSION;
return res; return res;
} }

View File

@@ -1,11 +1,11 @@
import * as program from 'commander'; import * as program from "commander";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
export class RestoreCommand { export class RestoreCommand {
constructor(private cipherService: CipherService) { } constructor(private cipherService: CipherService) {}
async run(object: string, id: string, cmd: program.Command): Promise<Response> { async run(object: string, id: string, cmd: program.Command): Promise<Response> {
if (id != null) { if (id != null) {
@@ -13,10 +13,10 @@ export class RestoreCommand {
} }
switch (object.toLowerCase()) { switch (object.toLowerCase()) {
case 'item': case "item":
return await this.restoreCipher(id, cmd); return await this.restoreCipher(id, cmd);
default: default:
return Response.badRequest('Unknown object.'); return Response.badRequest("Unknown object.");
} }
} }
@@ -26,7 +26,7 @@ export class RestoreCommand {
return Response.notFound(); return Response.notFound();
} }
if (cipher.deletedDate == null) { if (cipher.deletedDate == null) {
return Response.badRequest('Cipher is not in trash.'); return Response.badRequest("Cipher is not in trash.");
} }
try { try {

View File

@@ -1,55 +1,61 @@
import * as program from 'commander'; import * as program from "commander";
import * as fs from 'fs'; import * as fs from "fs";
import * as path from 'path'; import * as path from "path";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SendService } from 'jslib-common/abstractions/send.service'; import { SendService } from "jslib-common/abstractions/send.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { SendType } from 'jslib-common/enums/sendType'; import { SendType } from "jslib-common/enums/sendType";
import { NodeUtils } from 'jslib-common/misc/nodeUtils'; import { NodeUtils } from "jslib-common/misc/nodeUtils";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse'; import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
import { SendResponse } from '../../models/response/sendResponse'; import { SendResponse } from "../../models/response/sendResponse";
import { SendTextResponse } from '../../models/response/sendTextResponse'; import { SendTextResponse } from "../../models/response/sendTextResponse";
import { CliUtils } from '../../utils'; import { CliUtils } from "../../utils";
export class SendCreateCommand { export class SendCreateCommand {
constructor(private sendService: SendService, private userService: UserService, constructor(
private environmentService: EnvironmentService) { } private sendService: SendService,
private userService: UserService,
private environmentService: EnvironmentService
) {}
async run(requestJson: string, options: program.OptionValues) { async run(requestJson: string, options: program.OptionValues) {
let req: any = null; let req: any = null;
if (requestJson == null || requestJson === '') { if (requestJson == null || requestJson === "") {
requestJson = await CliUtils.readStdin(); requestJson = await CliUtils.readStdin();
} }
if (requestJson == null || requestJson === '') { if (requestJson == null || requestJson === "") {
return Response.badRequest('`requestJson` was not provided.'); return Response.badRequest("`requestJson` was not provided.");
} }
try { try {
const reqJson = Buffer.from(requestJson, 'base64').toString(); const reqJson = Buffer.from(requestJson, "base64").toString();
req = SendResponse.fromJson(reqJson); req = SendResponse.fromJson(reqJson);
if (req == null) { if (req == null) {
throw new Error('Null request'); throw new Error("Null request");
} }
} catch (e) { } catch (e) {
return Response.badRequest('Error parsing the encoded request data.'); return Response.badRequest("Error parsing the encoded request data.");
} }
if (req.deletionDate == null || isNaN(new Date(req.deletionDate).getTime()) || if (
new Date(req.deletionDate) <= new Date()) { req.deletionDate == null ||
return Response.badRequest('Must specify a valid deletion date after the current time'); isNaN(new Date(req.deletionDate).getTime()) ||
new Date(req.deletionDate) <= new Date()
) {
return Response.badRequest("Must specify a valid deletion date after the current time");
} }
if (req.expirationDate != null && isNaN(new Date(req.expirationDate).getTime())) { if (req.expirationDate != null && isNaN(new Date(req.expirationDate).getTime())) {
return Response.badRequest('Unable to parse expirationDate: ' + req.expirationDate); return Response.badRequest("Unable to parse expirationDate: " + req.expirationDate);
} }
return this.createSend(req, options); return this.createSend(req, options);
@@ -68,25 +74,31 @@ export class SendCreateCommand {
switch (req.type) { switch (req.type) {
case SendType.File: case SendType.File:
if (!(await this.userService.canAccessPremium())) { if (!(await this.userService.canAccessPremium())) {
return Response.error('Premium status is required to use this feature.'); return Response.error("Premium status is required to use this feature.");
} }
if (filePath == null) { if (filePath == null) {
return Response.badRequest('Must specify a file to Send either with the --file option or in the encoded json'); return Response.badRequest(
"Must specify a file to Send either with the --file option or in the encoded json"
);
} }
req.file.fileName = path.basename(filePath); req.file.fileName = path.basename(filePath);
break; break;
case SendType.Text: case SendType.Text:
if (text == null) { if (text == null) {
return Response.badRequest('Must specify text content to Send either with the --text option or in the encoded json'); return Response.badRequest(
"Must specify text content to Send either with the --text option or in the encoded json"
);
} }
req.text = new SendTextResponse(); req.text = new SendTextResponse();
req.text.text = text; req.text.text = text;
req.text.hidden = hidden; req.text.hidden = hidden;
break; break;
default: default:
return Response.badRequest('Unknown Send type ' + SendType[req.type] + 'valid types are: file, text'); return Response.badRequest(
"Unknown Send type " + SendType[req.type] + "valid types are: file, text"
);
} }
try { try {
@@ -105,8 +117,11 @@ export class SendCreateCommand {
const newSend = await this.sendService.get(encSend.id); const newSend = await this.sendService.get(encSend.id);
const decSend = await newSend.decrypt(); const decSend = await newSend.decrypt();
const res = new SendResponse(decSend, this.environmentService.getWebVaultUrl()); const res = new SendResponse(decSend, this.environmentService.getWebVaultUrl());
return Response.success(options.fullObject ? res : return Response.success(
new StringResponse('Send created! It can be accessed at:\n' + res.accessUrl)); options.fullObject
? res
: new StringResponse("Send created! It can be accessed at:\n" + res.accessUrl)
);
} catch (e) { } catch (e) {
return Response.error(e); return Response.error(e);
} }

View File

@@ -1,9 +1,9 @@
import { SendService } from 'jslib-common/abstractions/send.service'; import { SendService } from "jslib-common/abstractions/send.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
export class SendDeleteCommand { export class SendDeleteCommand {
constructor(private sendService: SendService) { } constructor(private sendService: SendService) {}
async run(id: string) { async run(id: string) {
const send = await this.sendService.get(id); const send = await this.sendService.get(id);

View File

@@ -1,36 +1,39 @@
import * as program from 'commander'; import * as program from "commander";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SendService } from 'jslib-common/abstractions/send.service'; import { SendService } from "jslib-common/abstractions/send.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { SendType } from 'jslib-common/enums/sendType'; import { SendType } from "jslib-common/enums/sendType";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { SendResponse } from '../../models/response/sendResponse'; import { SendResponse } from "../../models/response/sendResponse";
import { CliUtils } from '../../utils'; import { CliUtils } from "../../utils";
import { SendGetCommand } from './get.command'; import { SendGetCommand } from "./get.command";
export class SendEditCommand { export class SendEditCommand {
constructor(private sendService: SendService, private userService: UserService, constructor(
private getCommand: SendGetCommand) { } private sendService: SendService,
private userService: UserService,
private getCommand: SendGetCommand
) {}
async run(encodedJson: string, options: program.OptionValues): Promise<Response> { async run(encodedJson: string, options: program.OptionValues): Promise<Response> {
if (encodedJson == null || encodedJson === '') { if (encodedJson == null || encodedJson === "") {
encodedJson = await CliUtils.readStdin(); encodedJson = await CliUtils.readStdin();
} }
if (encodedJson == null || encodedJson === '') { if (encodedJson == null || encodedJson === "") {
return Response.badRequest('`encodedJson` was not provided.'); return Response.badRequest("`encodedJson` was not provided.");
} }
let req: SendResponse = null; let req: SendResponse = null;
try { try {
const reqJson = Buffer.from(encodedJson, 'base64').toString(); const reqJson = Buffer.from(encodedJson, "base64").toString();
req = SendResponse.fromJson(reqJson); req = SendResponse.fromJson(reqJson);
} catch (e) { } catch (e) {
return Response.badRequest('Error parsing the encoded request data.'); return Response.badRequest("Error parsing the encoded request data.");
} }
req.id = options.itemid || req.id; req.id = options.itemid || req.id;
@@ -46,17 +49,17 @@ export class SendEditCommand {
} }
if (send.type !== req.type) { if (send.type !== req.type) {
return Response.badRequest('Cannot change a Send\'s type'); return Response.badRequest("Cannot change a Send's type");
} }
if (send.type === SendType.File && !(await this.userService.canAccessPremium())) { if (send.type === SendType.File && !(await this.userService.canAccessPremium())) {
return Response.error('Premium status is required to use this feature.'); return Response.error("Premium status is required to use this feature.");
} }
let sendView = await send.decrypt(); let sendView = await send.decrypt();
sendView = SendResponse.toView(req, sendView); sendView = SendResponse.toView(req, sendView);
if (typeof (req.password) !== 'string' || req.password === '') { if (typeof req.password !== "string" || req.password === "") {
req.password = null; req.password = null;
} }

View File

@@ -1,24 +1,28 @@
import * as program from 'commander'; import * as program from "commander";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from "jslib-common/abstractions/search.service";
import { SendService } from 'jslib-common/abstractions/send.service'; import { SendService } from "jslib-common/abstractions/send.service";
import { SendView } from 'jslib-common/models/view/sendView'; import { SendView } from "jslib-common/models/view/sendView";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { DownloadCommand } from '../download.command'; import { DownloadCommand } from "../download.command";
import { SendResponse } from '../../models/response/sendResponse'; import { SendResponse } from "../../models/response/sendResponse";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
export class SendGetCommand extends DownloadCommand { export class SendGetCommand extends DownloadCommand {
constructor(private sendService: SendService, private environmentService: EnvironmentService, constructor(
private searchService: SearchService, cryptoService: CryptoService) { private sendService: SendService,
private environmentService: EnvironmentService,
private searchService: SearchService,
cryptoService: CryptoService
) {
super(cryptoService); super(cryptoService);
} }
@@ -30,12 +34,13 @@ export class SendGetCommand extends DownloadCommand {
const webVaultUrl = this.environmentService.getWebVaultUrl(); const webVaultUrl = this.environmentService.getWebVaultUrl();
let filter = (s: SendView) => true; let filter = (s: SendView) => true;
let selector = async (s: SendView): Promise<Response> => Response.success(new SendResponse(s, webVaultUrl)); let selector = async (s: SendView): Promise<Response> =>
Response.success(new SendResponse(s, webVaultUrl));
if (options.text != null) { if (options.text != null) {
filter = s => { filter = (s) => {
return filter(s) && s.text != null; return filter(s) && s.text != null;
}; };
selector = async s => { selector = async (s) => {
// Write to stdout and response success so we get the text string only to stdout // Write to stdout and response success so we get the text string only to stdout
process.stdout.write(s.text.text); process.stdout.write(s.text.text);
return Response.success(); return Response.success();
@@ -47,12 +52,11 @@ export class SendGetCommand extends DownloadCommand {
sends = sends.filter(filter); sends = sends.filter(filter);
} }
if (sends.length > 1) { if (sends.length > 1) {
return Response.multipleResults(sends.map(s => s.id)); return Response.multipleResults(sends.map((s) => s.id));
} }
if (sends.length > 0) { if (sends.length > 0) {
return selector(sends[0]); return selector(sends[0]);
} } else {
else {
return Response.notFound(); return Response.notFound();
} }
} }
@@ -66,7 +70,7 @@ export class SendGetCommand extends DownloadCommand {
if (send != null) { if (send != null) {
return await send.decrypt(); return await send.decrypt();
} }
} else if (id.trim() !== '') { } else if (id.trim() !== "") {
let sends = await this.sendService.getAllDecrypted(); let sends = await this.sendService.getAllDecrypted();
sends = this.searchService.searchSends(sends, id); sends = this.searchService.searchSends(sends, id);
if (sends.length > 1) { if (sends.length > 1) {

View File

@@ -1,28 +1,30 @@
import * as program from 'commander'; import * as program from "commander";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SearchService } from 'jslib-common/abstractions/search.service'; import { SearchService } from "jslib-common/abstractions/search.service";
import { SendService } from 'jslib-common/abstractions/send.service'; import { SendService } from "jslib-common/abstractions/send.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { ListResponse } from 'jslib-node/cli/models/response/listResponse'; import { ListResponse } from "jslib-node/cli/models/response/listResponse";
import { SendResponse } from '../..//models/response/sendResponse'; import { SendResponse } from "../..//models/response/sendResponse";
export class SendListCommand { export class SendListCommand {
constructor(
constructor(private sendService: SendService, private environmentService: EnvironmentService, private sendService: SendService,
private searchService: SearchService) { } private environmentService: EnvironmentService,
private searchService: SearchService
) {}
async run(options: program.OptionValues): Promise<Response> { async run(options: program.OptionValues): Promise<Response> {
let sends = await this.sendService.getAllDecrypted(); let sends = await this.sendService.getAllDecrypted();
if (options.search != null && options.search.trim() !== '') { if (options.search != null && options.search.trim() !== "") {
sends = this.searchService.searchSends(sends, options.search); sends = this.searchService.searchSends(sends, options.search);
} }
const webVaultUrl = this.environmentService.getWebVaultUrl(); const webVaultUrl = this.environmentService.getWebVaultUrl();
const res = new ListResponse(sends.map(s => new SendResponse(s, webVaultUrl))); const res = new ListResponse(sends.map((s) => new SendResponse(s, webVaultUrl)));
return Response.success(res); return Response.success(res);
} }
} }

View File

@@ -1,63 +1,67 @@
import * as program from 'commander'; import * as program from "commander";
import * as inquirer from 'inquirer'; import * as inquirer from "inquirer";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SendAccessRequest } from 'jslib-common/models/request/sendAccessRequest'; import { SendAccessRequest } from "jslib-common/models/request/sendAccessRequest";
import { ErrorResponse } from 'jslib-common/models/response/errorResponse'; import { ErrorResponse } from "jslib-common/models/response/errorResponse";
import { SendAccessView } from 'jslib-common/models/view/sendAccessView'; import { SendAccessView } from "jslib-common/models/view/sendAccessView";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { SendAccess } from 'jslib-common/models/domain/sendAccess'; import { SendAccess } from "jslib-common/models/domain/sendAccess";
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { SendType } from 'jslib-common/enums/sendType'; import { SendType } from "jslib-common/enums/sendType";
import { NodeUtils } from 'jslib-common/misc/nodeUtils'; import { NodeUtils } from "jslib-common/misc/nodeUtils";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
import { SendAccessResponse } from '../../models/response/sendAccessResponse'; import { SendAccessResponse } from "../../models/response/sendAccessResponse";
import { DownloadCommand } from '../download.command'; import { DownloadCommand } from "../download.command";
export class SendReceiveCommand extends DownloadCommand { export class SendReceiveCommand extends DownloadCommand {
private canInteract: boolean; private canInteract: boolean;
private decKey: SymmetricCryptoKey; private decKey: SymmetricCryptoKey;
private sendAccessRequest: SendAccessRequest; private sendAccessRequest: SendAccessRequest;
constructor(private apiService: ApiService, cryptoService: CryptoService, constructor(
private cryptoFunctionService: CryptoFunctionService, private platformUtilsService: PlatformUtilsService, private apiService: ApiService,
private environmentService: EnvironmentService) { cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService,
private platformUtilsService: PlatformUtilsService,
private environmentService: EnvironmentService
) {
super(cryptoService); super(cryptoService);
} }
async run(url: string, options: program.OptionValues): Promise<Response> { async run(url: string, options: program.OptionValues): Promise<Response> {
this.canInteract = process.env.BW_NOINTERACTION !== 'true'; this.canInteract = process.env.BW_NOINTERACTION !== "true";
let urlObject: URL; let urlObject: URL;
try { try {
urlObject = new URL(url); urlObject = new URL(url);
} catch (e) { } catch (e) {
return Response.badRequest('Failed to parse the provided Send url'); return Response.badRequest("Failed to parse the provided Send url");
} }
const apiUrl = this.getApiUrl(urlObject); const apiUrl = this.getApiUrl(urlObject);
const [id, key] = this.getIdAndKey(urlObject); const [id, key] = this.getIdAndKey(urlObject);
if (Utils.isNullOrWhitespace(id) || Utils.isNullOrWhitespace(key)) { if (Utils.isNullOrWhitespace(id) || Utils.isNullOrWhitespace(key)) {
return Response.badRequest('Failed to parse url, the url provided is not a valid Send url'); return Response.badRequest("Failed to parse url, the url provided is not a valid Send url");
} }
const keyArray = Utils.fromUrlB64ToArray(key); const keyArray = Utils.fromUrlB64ToArray(key);
this.sendAccessRequest = new SendAccessRequest(); this.sendAccessRequest = new SendAccessRequest();
let password = options.password; let password = options.password;
if (password == null || password === '') { if (password == null || password === "") {
if (options.passwordfile) { if (options.passwordfile) {
password = await NodeUtils.readFirstLine(options.passwordfile); password = await NodeUtils.readFirstLine(options.passwordfile);
} else if (options.passwordenv && process.env[options.passwordenv]) { } else if (options.passwordenv && process.env[options.passwordenv]) {
@@ -65,7 +69,7 @@ export class SendReceiveCommand extends DownloadCommand {
} }
} }
if (password != null && password !== '') { if (password != null && password !== "") {
this.sendAccessRequest.password = await this.getUnlockedPassword(password, keyArray); this.sendAccessRequest.password = await this.getUnlockedPassword(password, keyArray);
} }
@@ -86,37 +90,55 @@ export class SendReceiveCommand extends DownloadCommand {
process.stdout.write(response?.text?.text); process.stdout.write(response?.text?.text);
return Response.success(); return Response.success();
case SendType.File: case SendType.File:
const downloadData = await this.apiService.getSendFileDownloadData(response, this.sendAccessRequest, apiUrl); const downloadData = await this.apiService.getSendFileDownloadData(
return await this.saveAttachmentToFile(downloadData.url, this.decKey, response?.file?.fileName, options.output); response,
this.sendAccessRequest,
apiUrl
);
return await this.saveAttachmentToFile(
downloadData.url,
this.decKey,
response?.file?.fileName,
options.output
);
default: default:
return Response.success(new SendAccessResponse(response)); return Response.success(new SendAccessResponse(response));
} }
} }
private getIdAndKey(url: URL): [string, string] { private getIdAndKey(url: URL): [string, string] {
const result = url.hash.slice(1).split('/').slice(-2); const result = url.hash.slice(1).split("/").slice(-2);
return [result[0], result[1]]; return [result[0], result[1]];
} }
private getApiUrl(url: URL) { private getApiUrl(url: URL) {
const urls = this.environmentService.getUrls(); const urls = this.environmentService.getUrls();
if (url.origin === 'https://send.bitwarden.com') { if (url.origin === "https://send.bitwarden.com") {
return 'https://vault.bitwarden.com/api'; return "https://vault.bitwarden.com/api";
} else if (url.origin === urls.api) { } else if (url.origin === urls.api) {
return url.origin; return url.origin;
} else if (this.platformUtilsService.isDev() && url.origin === urls.webVault) { } else if (this.platformUtilsService.isDev() && url.origin === urls.webVault) {
return urls.api; return urls.api;
} else { } else {
return url.origin + '/api'; return url.origin + "/api";
} }
} }
private async getUnlockedPassword(password: string, keyArray: ArrayBuffer) { private async getUnlockedPassword(password: string, keyArray: ArrayBuffer) {
const passwordHash = await this.cryptoFunctionService.pbkdf2(password, keyArray, 'sha256', 100000); const passwordHash = await this.cryptoFunctionService.pbkdf2(
password,
keyArray,
"sha256",
100000
);
return Utils.fromBufferToB64(passwordHash); return Utils.fromBufferToB64(passwordHash);
} }
private async sendRequest(url: string, id: string, key: ArrayBuffer): Promise<Response | SendAccessView> { private async sendRequest(
url: string,
id: string,
key: ArrayBuffer
): Promise<Response | SendAccessView> {
try { try {
const sendResponse = await this.apiService.postSendAccess(id, this.sendAccessRequest, url); const sendResponse = await this.apiService.postSendAccess(id, this.sendAccessRequest, url);
@@ -127,10 +149,12 @@ export class SendReceiveCommand extends DownloadCommand {
if (e instanceof ErrorResponse) { if (e instanceof ErrorResponse) {
if (e.statusCode === 401) { if (e.statusCode === 401) {
if (this.canInteract) { if (this.canInteract) {
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ const answer: inquirer.Answers = await inquirer.createPromptModule({
type: 'password', output: process.stderr,
name: 'password', })({
message: 'Send password:', type: "password",
name: "password",
message: "Send password:",
}); });
// reattempt with new password // reattempt with new password
@@ -138,9 +162,9 @@ export class SendReceiveCommand extends DownloadCommand {
return await this.sendRequest(url, id, key); return await this.sendRequest(url, id, key);
} }
return Response.badRequest('Incorrect or missing password'); return Response.badRequest("Incorrect or missing password");
} else if (e.statusCode === 405) { } else if (e.statusCode === 405) {
return Response.badRequest('Bad Request'); return Response.badRequest("Bad Request");
} else if (e.statusCode === 404) { } else if (e.statusCode === 404) {
return Response.notFound(); return Response.notFound();
} }

View File

@@ -1,11 +1,11 @@
import { SendService } from 'jslib-common/abstractions/send.service'; import { SendService } from "jslib-common/abstractions/send.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { SendResponse } from '../../models/response/sendResponse'; import { SendResponse } from "../../models/response/sendResponse";
export class SendRemovePasswordCommand { export class SendRemovePasswordCommand {
constructor(private sendService: SendService) { } constructor(private sendService: SendService) {}
async run(id: string) { async run(id: string) {
try { try {

View File

@@ -1,34 +1,39 @@
import * as program from 'commander'; import * as program from "commander";
import { CipherService } from 'jslib-common/abstractions/cipher.service'; import { CipherService } from "jslib-common/abstractions/cipher.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { CipherResponse } from '../models/response/cipherResponse'; import { CipherResponse } from "../models/response/cipherResponse";
import { CliUtils } from '../utils'; import { CliUtils } from "../utils";
export class ShareCommand { export class ShareCommand {
constructor(private cipherService: CipherService) { } constructor(private cipherService: CipherService) {}
async run(id: string, organizationId: string, requestJson: string, cmd: program.Command): Promise<Response> { async run(
if (requestJson == null || requestJson === '') { id: string,
organizationId: string,
requestJson: string,
cmd: program.Command
): Promise<Response> {
if (requestJson == null || requestJson === "") {
requestJson = await CliUtils.readStdin(); requestJson = await CliUtils.readStdin();
} }
if (requestJson == null || requestJson === '') { if (requestJson == null || requestJson === "") {
return Response.badRequest('`requestJson` was not provided.'); return Response.badRequest("`requestJson` was not provided.");
} }
let req: string[] = []; let req: string[] = [];
try { try {
const reqJson = Buffer.from(requestJson, 'base64').toString(); const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson); req = JSON.parse(reqJson);
if (req == null || req.length === 0) { if (req == null || req.length === 0) {
return Response.badRequest('You must provide at least one collection id for this item.'); return Response.badRequest("You must provide at least one collection id for this item.");
} }
} catch (e) { } catch (e) {
return Response.badRequest('Error parsing the encoded request data.'); return Response.badRequest("Error parsing the encoded request data.");
} }
if (id != null) { if (id != null) {
@@ -43,7 +48,7 @@ export class ShareCommand {
return Response.notFound(); return Response.notFound();
} }
if (cipher.organizationId != null) { if (cipher.organizationId != null) {
return Response.badRequest('This item already belongs to an organization.'); return Response.badRequest("This item already belongs to an organization.");
} }
const cipherView = await cipher.decrypt(); const cipherView = await cipher.decrypt();
try { try {

View File

@@ -1,18 +1,21 @@
import * as program from 'commander'; import * as program from "commander";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from "jslib-common/abstractions/sync.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { TemplateResponse } from '../models/response/templateResponse'; import { TemplateResponse } from "../models/response/templateResponse";
export class StatusCommand { export class StatusCommand {
constructor(private envService: EnvironmentService, private syncService: SyncService, constructor(
private userService: UserService, private vaultTimeoutService: VaultTimeoutService) { private envService: EnvironmentService,
} private syncService: SyncService,
private userService: UserService,
private vaultTimeoutService: VaultTimeoutService
) {}
async run(): Promise<Response> { async run(): Promise<Response> {
try { try {
@@ -22,13 +25,15 @@ export class StatusCommand {
const userId = await this.userService.getUserId(); const userId = await this.userService.getUserId();
const email = await this.userService.getEmail(); const email = await this.userService.getEmail();
return Response.success(new TemplateResponse({ return Response.success(
new TemplateResponse({
serverUrl: baseUrl, serverUrl: baseUrl,
lastSync: lastSync, lastSync: lastSync,
userEmail: email, userEmail: email,
userId: userId, userId: userId,
status: status, status: status,
})); })
);
} catch (e) { } catch (e) {
return Response.error(e); return Response.error(e);
} }
@@ -41,10 +46,10 @@ export class StatusCommand {
private async status(): Promise<string> { private async status(): Promise<string> {
const authed = await this.userService.isAuthenticated(); const authed = await this.userService.isAuthenticated();
if (!authed) { if (!authed) {
return 'unauthenticated'; return "unauthenticated";
} }
const isLocked = await this.vaultTimeoutService.isLocked(); const isLocked = await this.vaultTimeoutService.isLocked();
return isLocked ? 'locked' : 'unlocked'; return isLocked ? "locked" : "unlocked";
} }
} }

View File

@@ -1,13 +1,13 @@
import * as program from 'commander'; import * as program from "commander";
import { SyncService } from 'jslib-common/abstractions/sync.service'; import { SyncService } from "jslib-common/abstractions/sync.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse'; import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
export class SyncCommand { export class SyncCommand {
constructor(private syncService: SyncService) { } constructor(private syncService: SyncService) {}
async run(options: program.OptionValues): Promise<Response> { async run(options: program.OptionValues): Promise<Response> {
if (options.last || false) { if (options.last || false) {
@@ -16,10 +16,10 @@ export class SyncCommand {
try { try {
const result = await this.syncService.fullSync(options.force || false, true); const result = await this.syncService.fullSync(options.force || false, true);
const res = new MessageResponse('Syncing complete.', null); const res = new MessageResponse("Syncing complete.", null);
return Response.success(res); return Response.success(res);
} catch (e) { } catch (e) {
return Response.error('Syncing failed: ' + e.toString()); return Response.error("Syncing failed: " + e.toString());
} }
} }

View File

@@ -1,53 +1,60 @@
import * as program from 'commander'; import * as program from "commander";
import * as inquirer from 'inquirer'; import * as inquirer from "inquirer";
import { ApiService } from 'jslib-common/abstractions/api.service'; import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { UserService } from 'jslib-common/abstractions/user.service'; import { UserService } from "jslib-common/abstractions/user.service";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { SecretVerificationRequest } from 'jslib-common/models/request/secretVerificationRequest'; import { SecretVerificationRequest } from "jslib-common/models/request/secretVerificationRequest";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
import { HashPurpose } from 'jslib-common/enums/hashPurpose'; import { HashPurpose } from "jslib-common/enums/hashPurpose";
import { NodeUtils } from 'jslib-common/misc/nodeUtils'; import { NodeUtils } from "jslib-common/misc/nodeUtils";
import { ConsoleLogService } from 'jslib-common/services/consoleLog.service'; import { ConsoleLogService } from "jslib-common/services/consoleLog.service";
export class UnlockCommand { export class UnlockCommand {
constructor(private cryptoService: CryptoService, private userService: UserService, constructor(
private cryptoFunctionService: CryptoFunctionService, private apiService: ApiService, private cryptoService: CryptoService,
private logService: ConsoleLogService) { private userService: UserService,
} private cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService,
private logService: ConsoleLogService
) {}
async run(password: string, options: program.OptionValues) { async run(password: string, options: program.OptionValues) {
const canInteract = process.env.BW_NOINTERACTION !== 'true'; const canInteract = process.env.BW_NOINTERACTION !== "true";
if (password == null || password === '') { if (password == null || password === "") {
if (options?.passwordfile) { if (options?.passwordfile) {
password = await NodeUtils.readFirstLine(options.passwordfile); password = await NodeUtils.readFirstLine(options.passwordfile);
} else if (options?.passwordenv) { } else if (options?.passwordenv) {
if (process.env[options.passwordenv]) { if (process.env[options.passwordenv]) {
password = process.env[options.passwordenv]; password = process.env[options.passwordenv];
} else { } else {
this.logService.warning(`Warning: Provided passwordenv ${options.passwordenv} is not set`); this.logService.warning(
`Warning: Provided passwordenv ${options.passwordenv} is not set`
);
} }
} }
} }
if (password == null || password === '') { if (password == null || password === "") {
if (canInteract) { if (canInteract) {
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ const answer: inquirer.Answers = await inquirer.createPromptModule({
type: 'password', output: process.stderr,
name: 'password', })({
message: 'Master password:', type: "password",
name: "password",
message: "Master password:",
}); });
password = answer.password; password = answer.password;
} else { } else {
return Response.badRequest('Master password is required.'); return Response.badRequest("Master password is required.");
} }
} }
@@ -63,31 +70,46 @@ export class UnlockCommand {
if (storedKeyHash != null) { if (storedKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, key); passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, key);
} else { } else {
const serverKeyHash = await this.cryptoService.hashPassword(password, key, HashPurpose.ServerAuthorization); const serverKeyHash = await this.cryptoService.hashPassword(
password,
key,
HashPurpose.ServerAuthorization
);
const request = new SecretVerificationRequest(); const request = new SecretVerificationRequest();
request.masterPasswordHash = serverKeyHash; request.masterPasswordHash = serverKeyHash;
try { try {
await this.apiService.postAccountVerifyPassword(request); await this.apiService.postAccountVerifyPassword(request);
passwordValid = true; passwordValid = true;
const localKeyHash = await this.cryptoService.hashPassword(password, key, const localKeyHash = await this.cryptoService.hashPassword(
HashPurpose.LocalAuthorization); password,
key,
HashPurpose.LocalAuthorization
);
await this.cryptoService.setKeyHash(localKeyHash); await this.cryptoService.setKeyHash(localKeyHash);
} catch { } } catch {}
} }
} }
if (passwordValid) { if (passwordValid) {
await this.cryptoService.setKey(key); await this.cryptoService.setKey(key);
const res = new MessageResponse('Your vault is now unlocked!', '\n' + const res = new MessageResponse(
'To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n' + "Your vault is now unlocked!",
'$ export BW_SESSION="' + process.env.BW_SESSION + '"\n' + "\n" +
'> $env:BW_SESSION="' + process.env.BW_SESSION + '"\n\n' + "To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n" +
'You can also pass the session key to any command with the `--session` option. ex:\n' + '$ export BW_SESSION="' +
'$ bw list items --session ' + process.env.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; res.raw = process.env.BW_SESSION;
return Response.success(res); return Response.success(res);
} else { } else {
return Response.error('Invalid master password.'); return Response.error("Invalid master password.");
} }
} }

View File

@@ -1,12 +1,12 @@
import { Collection } from 'jslib-common/models/export/collection'; import { Collection } from "jslib-common/models/export/collection";
import { SelectionReadOnly } from '../selectionReadOnly'; import { SelectionReadOnly } from "../selectionReadOnly";
export class OrganizationCollectionRequest extends Collection { export class OrganizationCollectionRequest extends Collection {
static template(): OrganizationCollectionRequest { static template(): OrganizationCollectionRequest {
const req = new OrganizationCollectionRequest(); const req = new OrganizationCollectionRequest();
req.organizationId = '00000000-0000-0000-0000-000000000000'; req.organizationId = "00000000-0000-0000-0000-000000000000";
req.name = 'Collection name'; req.name = "Collection name";
req.externalId = null; req.externalId = null;
req.groups = [SelectionReadOnly.template(), SelectionReadOnly.template()]; req.groups = [SelectionReadOnly.template(), SelectionReadOnly.template()];
return req; return req;

View File

@@ -1,4 +1,4 @@
import { AttachmentView } from 'jslib-common/models/view/attachmentView'; import { AttachmentView } from "jslib-common/models/view/attachmentView";
export class AttachmentResponse { export class AttachmentResponse {
id: string; id: string;

View File

@@ -1,13 +1,13 @@
import { CipherWithIds } from 'jslib-common/models/export/cipherWithIds'; import { CipherWithIds } from "jslib-common/models/export/cipherWithIds";
import { CipherView } from 'jslib-common/models/view/cipherView'; import { CipherView } from "jslib-common/models/view/cipherView";
import { BaseResponse } from 'jslib-node/cli/models/response/baseResponse'; import { BaseResponse } from "jslib-node/cli/models/response/baseResponse";
import { AttachmentResponse } from './attachmentResponse'; import { AttachmentResponse } from "./attachmentResponse";
import { LoginResponse } from './loginResponse'; import { LoginResponse } from "./loginResponse";
import { PasswordHistoryResponse } from './passwordHistoryResponse'; import { PasswordHistoryResponse } from "./passwordHistoryResponse";
import { CipherType } from 'jslib-common/enums/cipherType'; import { CipherType } from "jslib-common/enums/cipherType";
export class CipherResponse extends CipherWithIds implements BaseResponse { export class CipherResponse extends CipherWithIds implements BaseResponse {
object: string; object: string;
@@ -17,14 +17,14 @@ export class CipherResponse extends CipherWithIds implements BaseResponse {
constructor(o: CipherView) { constructor(o: CipherView) {
super(); super();
this.object = 'item'; this.object = "item";
this.build(o); this.build(o);
if (o.attachments != null) { if (o.attachments != null) {
this.attachments = o.attachments.map(a => new AttachmentResponse(a)); this.attachments = o.attachments.map((a) => new AttachmentResponse(a));
} }
this.revisionDate = o.revisionDate; this.revisionDate = o.revisionDate;
if (o.passwordHistory != null) { if (o.passwordHistory != null) {
this.passwordHistory = o.passwordHistory.map(h => new PasswordHistoryResponse(h)); this.passwordHistory = o.passwordHistory.map((h) => new PasswordHistoryResponse(h));
} }
if (o.type === CipherType.Login && o.login != null) { if (o.type === CipherType.Login && o.login != null) {
this.login = new LoginResponse(o.login); this.login = new LoginResponse(o.login);

View File

@@ -1,14 +1,14 @@
import { CollectionWithId } from 'jslib-common/models/export/collectionWithId'; import { CollectionWithId } from "jslib-common/models/export/collectionWithId";
import { CollectionView } from 'jslib-common/models/view/collectionView'; import { CollectionView } from "jslib-common/models/view/collectionView";
import { BaseResponse } from 'jslib-node/cli/models/response/baseResponse'; import { BaseResponse } from "jslib-node/cli/models/response/baseResponse";
export class CollectionResponse extends CollectionWithId implements BaseResponse { export class CollectionResponse extends CollectionWithId implements BaseResponse {
object: string; object: string;
constructor(o: CollectionView) { constructor(o: CollectionView) {
super(); super();
this.object = 'collection'; this.object = "collection";
this.build(o); this.build(o);
} }
} }

View File

@@ -1,14 +1,14 @@
import { FolderWithId } from 'jslib-common/models/export/folderWithId'; import { FolderWithId } from "jslib-common/models/export/folderWithId";
import { FolderView } from 'jslib-common/models/view/folderView'; import { FolderView } from "jslib-common/models/view/folderView";
import { BaseResponse } from 'jslib-node/cli/models/response/baseResponse'; import { BaseResponse } from "jslib-node/cli/models/response/baseResponse";
export class FolderResponse extends FolderWithId implements BaseResponse { export class FolderResponse extends FolderWithId implements BaseResponse {
object: string; object: string;
constructor(o: FolderView) { constructor(o: FolderView) {
super(); super();
this.object = 'folder'; this.object = "folder";
this.build(o); this.build(o);
} }
} }

View File

@@ -1,5 +1,5 @@
import { Login } from 'jslib-common/models/export/login'; import { Login } from "jslib-common/models/export/login";
import { LoginView } from 'jslib-common/models/view/loginView'; import { LoginView } from "jslib-common/models/view/loginView";
export class LoginResponse extends Login { export class LoginResponse extends Login {
passwordRevisionDate: Date; passwordRevisionDate: Date;

View File

@@ -1,15 +1,15 @@
import { CollectionView } from 'jslib-common/models/view/collectionView'; import { CollectionView } from "jslib-common/models/view/collectionView";
import { SelectionReadOnly } from '../selectionReadOnly'; import { SelectionReadOnly } from "../selectionReadOnly";
import { CollectionResponse } from './collectionResponse'; import { CollectionResponse } from "./collectionResponse";
export class OrganizationCollectionResponse extends CollectionResponse { export class OrganizationCollectionResponse extends CollectionResponse {
groups: SelectionReadOnly[]; groups: SelectionReadOnly[];
constructor(o: CollectionView, groups: SelectionReadOnly[]) { constructor(o: CollectionView, groups: SelectionReadOnly[]) {
super(o); super(o);
this.object = 'org-collection'; this.object = "org-collection";
this.groups = groups; this.groups = groups;
} }
} }

View File

@@ -1,9 +1,9 @@
import { BaseResponse } from 'jslib-node/cli/models/response/baseResponse'; import { BaseResponse } from "jslib-node/cli/models/response/baseResponse";
import { Organization } from 'jslib-common/models/domain/organization'; import { Organization } from "jslib-common/models/domain/organization";
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType'; import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType'; import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
export class OrganizationResponse implements BaseResponse { export class OrganizationResponse implements BaseResponse {
object: string; object: string;
@@ -14,7 +14,7 @@ export class OrganizationResponse implements BaseResponse {
enabled: boolean; enabled: boolean;
constructor(o: Organization) { constructor(o: Organization) {
this.object = 'organization'; this.object = "organization";
this.id = o.id; this.id = o.id;
this.name = o.name; this.name = o.name;
this.status = o.status; this.status = o.status;

View File

@@ -1,7 +1,7 @@
import { BaseResponse } from 'jslib-node/cli/models/response/baseResponse'; import { BaseResponse } from "jslib-node/cli/models/response/baseResponse";
import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType'; import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { OrganizationUserType } from 'jslib-common/enums/organizationUserType'; import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
export class OrganizationUserResponse implements BaseResponse { export class OrganizationUserResponse implements BaseResponse {
object: string; object: string;
@@ -13,6 +13,6 @@ export class OrganizationUserResponse implements BaseResponse {
twoFactorEnabled: boolean; twoFactorEnabled: boolean;
constructor() { constructor() {
this.object = 'org-member'; this.object = "org-member";
} }
} }

View File

@@ -1,4 +1,4 @@
import { PasswordHistoryView } from 'jslib-common/models/view/passwordHistoryView'; import { PasswordHistoryView } from "jslib-common/models/view/passwordHistoryView";
export class PasswordHistoryResponse { export class PasswordHistoryResponse {
lastUsedDate: Date; lastUsedDate: Date;

View File

@@ -1,23 +1,23 @@
import { SendType } from 'jslib-common/enums/sendType'; import { SendType } from "jslib-common/enums/sendType";
import { SendAccessView } from 'jslib-common/models/view/sendAccessView'; import { SendAccessView } from "jslib-common/models/view/sendAccessView";
import { BaseResponse } from 'jslib-node/cli/models/response/baseResponse'; import { BaseResponse } from "jslib-node/cli/models/response/baseResponse";
import { SendFileResponse } from './sendFileResponse'; import { SendFileResponse } from "./sendFileResponse";
import { SendTextResponse } from './sendTextResponse'; import { SendTextResponse } from "./sendTextResponse";
export class SendAccessResponse implements BaseResponse { export class SendAccessResponse implements BaseResponse {
static template(): SendAccessResponse { static template(): SendAccessResponse {
const req = new SendAccessResponse(); const req = new SendAccessResponse();
req.name = 'Send name'; req.name = "Send name";
req.type = SendType.Text; req.type = SendType.Text;
req.text = null; req.text = null;
req.file = null; req.file = null;
return req; return req;
} }
object = 'send-access'; object = "send-access";
id: string; id: string;
name: string; name: string;
type: SendType; type: SendType;

View File

@@ -1,7 +1,7 @@
import { SendFileView } from 'jslib-common/models/view/sendFileView'; import { SendFileView } from "jslib-common/models/view/sendFileView";
export class SendFileResponse { export class SendFileResponse {
static template(fileName = 'file attachment location'): SendFileResponse { static template(fileName = "file attachment location"): SendFileResponse {
const req = new SendFileResponse(); const req = new SendFileResponse();
req.fileName = fileName; req.fileName = fileName;
return req; return req;

View File

@@ -1,23 +1,24 @@
import { SendView } from 'jslib-common/models/view/sendView'; import { SendView } from "jslib-common/models/view/sendView";
import { BaseResponse } from 'jslib-node/cli/models/response/baseResponse'; import { BaseResponse } from "jslib-node/cli/models/response/baseResponse";
import { SendType } from 'jslib-common/enums/sendType'; import { SendType } from "jslib-common/enums/sendType";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
import { SendFileResponse } from './sendFileResponse'; import { SendFileResponse } from "./sendFileResponse";
import { SendTextResponse } from './sendTextResponse'; import { SendTextResponse } from "./sendTextResponse";
const dateProperties: string[] = [
const dateProperties: string[] = [Utils.nameOf<SendResponse>('deletionDate'), Utils.nameOf<SendResponse>('expirationDate')]; Utils.nameOf<SendResponse>("deletionDate"),
Utils.nameOf<SendResponse>("expirationDate"),
];
export class SendResponse implements BaseResponse { export class SendResponse implements BaseResponse {
static template(sendType?: SendType, deleteInDays = 7): SendResponse { static template(sendType?: SendType, deleteInDays = 7): SendResponse {
const req = new SendResponse(); const req = new SendResponse();
req.name = 'Send name'; req.name = "Send name";
req.notes = 'Some notes about this send.'; req.notes = "Some notes about this send.";
req.type = sendType === SendType.File ? SendType.File : SendType.Text; req.type = sendType === SendType.File ? SendType.File : SendType.Text;
req.text = sendType === SendType.Text ? SendTextResponse.template() : null; req.text = sendType === SendType.Text ? SendTextResponse.template() : null;
req.file = sendType === SendType.File ? SendFileResponse.template() : null; req.file = sendType === SendType.File ? SendFileResponse.template() : null;
@@ -65,11 +66,11 @@ export class SendResponse implements BaseResponse {
private static getStandardDeletionDate(days: number) { private static getStandardDeletionDate(days: number) {
const d = new Date(); const d = new Date();
d.setTime(d.getTime() + (days * 86400000)); // ms per day d.setTime(d.getTime() + days * 86400000); // ms per day
return d; return d;
} }
object = 'send'; object = "send";
id: string; id: string;
accessId: string; accessId: string;
accessUrl: string; accessUrl: string;
@@ -97,11 +98,11 @@ export class SendResponse implements BaseResponse {
this.accessId = o.accessId; this.accessId = o.accessId;
let sendLinkBaseUrl = webVaultUrl; let sendLinkBaseUrl = webVaultUrl;
if (sendLinkBaseUrl == null) { if (sendLinkBaseUrl == null) {
sendLinkBaseUrl = 'https://send.bitwarden.com/#'; sendLinkBaseUrl = "https://send.bitwarden.com/#";
} else { } else {
sendLinkBaseUrl += '/#/send/'; sendLinkBaseUrl += "/#/send/";
} }
this.accessUrl = sendLinkBaseUrl + this.accessId + '/' + o.urlB64Key; this.accessUrl = sendLinkBaseUrl + this.accessId + "/" + o.urlB64Key;
this.name = o.name; this.name = o.name;
this.notes = o.notes; this.notes = o.notes;
this.key = Utils.fromBufferToB64(o.key); this.key = Utils.fromBufferToB64(o.key);

View File

@@ -1,7 +1,7 @@
import { SendTextView } from 'jslib-common/models/view/sendTextView'; import { SendTextView } from "jslib-common/models/view/sendTextView";
export class SendTextResponse { export class SendTextResponse {
static template(text = 'Text contained in the send.', hidden = false): SendTextResponse { static template(text = "Text contained in the send.", hidden = false): SendTextResponse {
const req = new SendTextResponse(); const req = new SendTextResponse();
req.text = text; req.text = text;
req.hidden = hidden; req.hidden = hidden;

View File

@@ -1,11 +1,11 @@
import { BaseResponse } from 'jslib-node/cli/models/response/baseResponse'; import { BaseResponse } from "jslib-node/cli/models/response/baseResponse";
export class TemplateResponse implements BaseResponse { export class TemplateResponse implements BaseResponse {
object: string; object: string;
template: any; template: any;
constructor(template: any) { constructor(template: any) {
this.object = 'template'; this.object = "template";
this.template = template; this.template = template;
} }
} }

View File

@@ -1,6 +1,6 @@
export class SelectionReadOnly { export class SelectionReadOnly {
static template(): SelectionReadOnly { static template(): SelectionReadOnly {
return new SelectionReadOnly('00000000-0000-0000-0000-000000000000', false, false); return new SelectionReadOnly("00000000-0000-0000-0000-000000000000", false, false);
} }
id: string; id: string;

View File

@@ -1,29 +1,29 @@
import * as chalk from 'chalk'; import * as chalk from "chalk";
import * as program from 'commander'; import * as program from "commander";
import { Main } from './bw'; import { Main } from "./bw";
import { ConfigCommand } from './commands/config.command'; import { ConfigCommand } from "./commands/config.command";
import { EncodeCommand } from './commands/encode.command'; import { EncodeCommand } from "./commands/encode.command";
import { GenerateCommand } from './commands/generate.command'; import { GenerateCommand } from "./commands/generate.command";
import { LockCommand } from './commands/lock.command'; import { LockCommand } from "./commands/lock.command";
import { LoginCommand } from './commands/login.command'; import { LoginCommand } from "./commands/login.command";
import { StatusCommand } from './commands/status.command'; import { StatusCommand } from "./commands/status.command";
import { SyncCommand } from './commands/sync.command'; import { SyncCommand } from "./commands/sync.command";
import { UnlockCommand } from './commands/unlock.command'; import { UnlockCommand } from "./commands/unlock.command";
import { CompletionCommand } from './commands/completion.command'; import { CompletionCommand } from "./commands/completion.command";
import { LogoutCommand } from 'jslib-node/cli/commands/logout.command'; import { LogoutCommand } from "jslib-node/cli/commands/logout.command";
import { UpdateCommand } from 'jslib-node/cli/commands/update.command'; import { UpdateCommand } from "jslib-node/cli/commands/update.command";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { TemplateResponse } from './models/response/templateResponse'; import { TemplateResponse } from "./models/response/templateResponse";
import { CliUtils } from './utils'; import { CliUtils } from "./utils";
import { BaseProgram } from 'jslib-node/cli/baseProgram'; import { BaseProgram } from "jslib-node/cli/baseProgram";
const writeLn = CliUtils.writeLn; const writeLn = CliUtils.writeLn;
@@ -34,152 +34,180 @@ export class Program extends BaseProgram {
async register() { async register() {
program program
.option('--pretty', 'Format output. JSON is tabbed with two spaces.') .option("--pretty", "Format output. JSON is tabbed with two spaces.")
.option('--raw', 'Return raw output instead of a descriptive message.') .option("--raw", "Return raw output instead of a descriptive message.")
.option('--response', 'Return a JSON formatted version of response output.') .option("--response", "Return a JSON formatted version of response output.")
.option('--cleanexit', 'Exit with a success exit code (0) unless an error is thrown.') .option("--cleanexit", "Exit with a success exit code (0) unless an error is thrown.")
.option('--quiet', 'Don\'t return anything to stdout.') .option("--quiet", "Don't return anything to stdout.")
.option('--nointeraction', 'Do not prompt for interactive user input.') .option("--nointeraction", "Do not prompt for interactive user input.")
.option('--session <session>', 'Pass session key instead of reading from env.') .option("--session <session>", "Pass session key instead of reading from env.")
.version(await this.main.platformUtilsService.getApplicationVersion(), '-v, --version'); .version(await this.main.platformUtilsService.getApplicationVersion(), "-v, --version");
program.on('option:pretty', () => { program.on("option:pretty", () => {
process.env.BW_PRETTY = 'true'; process.env.BW_PRETTY = "true";
}); });
program.on('option:raw', () => { program.on("option:raw", () => {
process.env.BW_RAW = 'true'; process.env.BW_RAW = "true";
}); });
program.on('option:quiet', () => { program.on("option:quiet", () => {
process.env.BW_QUIET = 'true'; process.env.BW_QUIET = "true";
}); });
program.on('option:response', () => { program.on("option:response", () => {
process.env.BW_RESPONSE = 'true'; process.env.BW_RESPONSE = "true";
}); });
program.on('option:cleanexit', () => { program.on("option:cleanexit", () => {
process.env.BW_CLEANEXIT = 'true'; process.env.BW_CLEANEXIT = "true";
}); });
program.on('option:nointeraction', () => { program.on("option:nointeraction", () => {
process.env.BW_NOINTERACTION = 'true'; process.env.BW_NOINTERACTION = "true";
}); });
program.on('option:session', key => { program.on("option:session", (key) => {
process.env.BW_SESSION = key; process.env.BW_SESSION = key;
}); });
program.on('command:*', () => { program.on("command:*", () => {
writeLn(chalk.redBright('Invalid command: ' + program.args.join(' ')), false, true); writeLn(chalk.redBright("Invalid command: " + program.args.join(" ")), false, true);
writeLn('See --help for a list of available commands.', true, true); writeLn("See --help for a list of available commands.", true, true);
process.exitCode = 1; process.exitCode = 1;
}); });
program.on('--help', () => { program.on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bw login'); writeLn(" bw login");
writeLn(' bw lock'); writeLn(" bw lock");
writeLn(' bw unlock myPassword321'); writeLn(" bw unlock myPassword321");
writeLn(' bw list --help'); writeLn(" bw list --help");
writeLn(' bw list items --search google'); writeLn(" bw list items --search google");
writeLn(' bw get item 99ee88d2-6046-4ea7-92c2-acac464b1412'); writeLn(" bw get item 99ee88d2-6046-4ea7-92c2-acac464b1412");
writeLn(' bw get password google.com'); writeLn(" bw get password google.com");
writeLn(' echo \'{"name":"My Folder"}\' | bw encode'); writeLn(' echo \'{"name":"My Folder"}\' | bw encode');
writeLn(' bw create folder eyJuYW1lIjoiTXkgRm9sZGVyIn0K'); writeLn(" bw create folder eyJuYW1lIjoiTXkgRm9sZGVyIn0K");
writeLn(' bw edit folder c7c7b60b-9c61-40f2-8ccd-36c49595ed72 eyJuYW1lIjoiTXkgRm9sZGVyMiJ9Cg=='); writeLn(
writeLn(' bw delete item 99ee88d2-6046-4ea7-92c2-acac464b1412'); " bw edit folder c7c7b60b-9c61-40f2-8ccd-36c49595ed72 eyJuYW1lIjoiTXkgRm9sZGVyMiJ9Cg=="
writeLn(' bw generate -lusn --length 18'); );
writeLn(' bw config server https://bitwarden.example.com'); writeLn(" bw delete item 99ee88d2-6046-4ea7-92c2-acac464b1412");
writeLn(' bw send -f ./file.ext'); writeLn(" bw generate -lusn --length 18");
writeLn(" bw config server https://bitwarden.example.com");
writeLn(" bw send -f ./file.ext");
writeLn(' bw send "text to send"'); writeLn(' bw send "text to send"');
writeLn(' echo "text to send" | bw send'); writeLn(' echo "text to send" | bw send');
writeLn(' bw receive https://vault.bitwarden.com/#/send/rg3iuoS_Akm2gqy6ADRHmg/Ht7dYjsqjmgqUM3rjzZDSQ'); writeLn(
writeLn('', true); " bw receive https://vault.bitwarden.com/#/send/rg3iuoS_Akm2gqy6ADRHmg/Ht7dYjsqjmgqUM3rjzZDSQ"
);
writeLn("", true);
}); });
program program
.command('login [email] [password]') .command("login [email] [password]")
.description('Log into a user account.') .description("Log into a user account.")
.option('--method <method>', 'Two-step login method.') .option("--method <method>", "Two-step login method.")
.option('--code <code>', 'Two-step login code.') .option("--code <code>", "Two-step login code.")
.option('--sso', 'Log in with Single-Sign On.') .option("--sso", "Log in with Single-Sign On.")
.option('--apikey', 'Log in with an Api Key.') .option("--apikey", "Log in with an Api Key.")
.option('--passwordenv <passwordenv>', 'Environment variable storing your password') .option("--passwordenv <passwordenv>", "Environment variable storing your password")
.option('--passwordfile <passwordfile>', 'Path to a file containing your password as its first line') .option(
.option('--check', 'Check login status.', async () => { "--passwordfile <passwordfile>",
"Path to a file containing your password as its first line"
)
.option("--check", "Check login status.", async () => {
const authed = await this.main.userService.isAuthenticated(); const authed = await this.main.userService.isAuthenticated();
if (authed) { if (authed) {
const res = new MessageResponse('You are logged in!', null); const res = new MessageResponse("You are logged in!", null);
this.processResponse(Response.success(res), true); this.processResponse(Response.success(res), true);
} }
this.processResponse(Response.error('You are not logged in.'), true); this.processResponse(Response.error("You are not logged in."), true);
}) })
.on('--help', () => { .on("--help", () => {
writeLn('\n Notes:'); writeLn("\n Notes:");
writeLn(''); writeLn("");
writeLn(' See docs for valid `method` enum values.'); writeLn(" See docs for valid `method` enum values.");
writeLn(''); writeLn("");
writeLn(' Pass `--raw` option to only return the session key.'); writeLn(" Pass `--raw` option to only return the session key.");
writeLn(''); writeLn("");
writeLn(' Examples:'); writeLn(" Examples:");
writeLn(''); writeLn("");
writeLn(' bw login'); writeLn(" bw login");
writeLn(' bw login john@example.com myPassword321 --raw'); writeLn(" bw login john@example.com myPassword321 --raw");
writeLn(' bw login john@example.com myPassword321 --method 1 --code 249213'); writeLn(" bw login john@example.com myPassword321 --method 1 --code 249213");
writeLn(' bw login --sso'); writeLn(" bw login --sso");
writeLn('', true); writeLn("", true);
}) })
.action(async (email: string, password: string, options: program.OptionValues) => { .action(async (email: string, password: string, options: program.OptionValues) => {
if (!options.check) { if (!options.check) {
await this.exitIfAuthed(); await this.exitIfAuthed();
const command = new LoginCommand(this.main.authService, this.main.apiService, const command = new LoginCommand(
this.main.cryptoFunctionService, this.main.syncService, this.main.i18nService, this.main.authService,
this.main.environmentService, this.main.passwordGenerationService, this.main.apiService,
this.main.platformUtilsService, this.main.userService, this.main.cryptoService, this.main.cryptoFunctionService,
this.main.policyService, this.main.keyConnectorService, async () => await this.main.logout()); this.main.syncService,
this.main.i18nService,
this.main.environmentService,
this.main.passwordGenerationService,
this.main.platformUtilsService,
this.main.userService,
this.main.cryptoService,
this.main.policyService,
this.main.keyConnectorService,
async () => await this.main.logout()
);
const response = await command.run(email, password, options); const response = await command.run(email, password, options);
this.processResponse(response); this.processResponse(response);
} }
}); });
program program
.command('logout') .command("logout")
.description('Log out of the current user account.') .description("Log out of the current user account.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bw logout'); writeLn(" bw logout");
writeLn('', true); writeLn("", true);
}) })
.action(async cmd => { .action(async (cmd) => {
await this.exitIfNotAuthed(); await this.exitIfNotAuthed();
const command = new LogoutCommand(this.main.authService, this.main.i18nService, const command = new LogoutCommand(
async () => await this.main.logout()); this.main.authService,
this.main.i18nService,
async () => await this.main.logout()
);
const response = await command.run(); const response = await command.run();
this.processResponse(response); this.processResponse(response);
}); });
program program
.command('lock') .command("lock")
.description('Lock the vault and destroy active session keys.') .description("Lock the vault and destroy active session keys.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bw lock'); writeLn(" bw lock");
writeLn('', true); writeLn("", true);
}) })
.action(async cmd => { .action(async (cmd) => {
await this.exitIfNotAuthed(); await this.exitIfNotAuthed();
if (this.main.keyConnectorService.getUsesKeyConnector()) { if (this.main.keyConnectorService.getUsesKeyConnector()) {
const logoutCommand = new LogoutCommand(this.main.authService, this.main.i18nService, const logoutCommand = new LogoutCommand(
async () => await this.main.logout()); this.main.authService,
this.main.i18nService,
async () => await this.main.logout()
);
await logoutCommand.run(); await logoutCommand.run();
this.processResponse(Response.error('You cannot lock your vault because you are using Key Connector. ' + this.processResponse(
'To protect your vault, you have been logged out.'), true); Response.error(
"You cannot lock your vault because you are using Key Connector. " +
"To protect your vault, you have been logged out."
),
true
);
return; return;
} }
@@ -189,56 +217,64 @@ export class Program extends BaseProgram {
}); });
program program
.command('unlock [password]') .command("unlock [password]")
.description('Unlock the vault and return a new session key.') .description("Unlock the vault and return a new session key.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Notes:'); writeLn("\n Notes:");
writeLn(''); writeLn("");
writeLn(' After unlocking, any previous session keys will no longer be valid.'); writeLn(" After unlocking, any previous session keys will no longer be valid.");
writeLn(''); writeLn("");
writeLn(' Pass `--raw` option to only return the session key.'); writeLn(" Pass `--raw` option to only return the session key.");
writeLn(''); writeLn("");
writeLn(' Examples:'); writeLn(" Examples:");
writeLn(''); writeLn("");
writeLn(' bw unlock'); writeLn(" bw unlock");
writeLn(' bw unlock myPassword321'); writeLn(" bw unlock myPassword321");
writeLn(' bw unlock myPassword321 --raw'); writeLn(" bw unlock myPassword321 --raw");
writeLn('', true); writeLn("", true);
}) })
.option('--check', 'Check lock status.', async () => { .option("--check", "Check lock status.", async () => {
const locked = await this.main.vaultTimeoutService.isLocked(); const locked = await this.main.vaultTimeoutService.isLocked();
if (!locked) { if (!locked) {
const res = new MessageResponse('Vault is unlocked!', null); const res = new MessageResponse("Vault is unlocked!", null);
this.processResponse(Response.success(res), true); this.processResponse(Response.success(res), true);
} }
this.processResponse(Response.error('Vault is locked.'), true); this.processResponse(Response.error("Vault is locked."), true);
}) })
.option('--passwordenv <passwordenv>', 'Environment variable storing your password') .option("--passwordenv <passwordenv>", "Environment variable storing your password")
.option('--passwordfile <passwordfile>', 'Path to a file containing your password as its first line') .option(
"--passwordfile <passwordfile>",
"Path to a file containing your password as its first line"
)
.action(async (password, cmd) => { .action(async (password, cmd) => {
if (!cmd.check) { if (!cmd.check) {
await this.exitIfNotAuthed(); await this.exitIfNotAuthed();
const command = new UnlockCommand(this.main.cryptoService, this.main.userService, const command = new UnlockCommand(
this.main.cryptoFunctionService, this.main.apiService, this.main.logService); this.main.cryptoService,
this.main.userService,
this.main.cryptoFunctionService,
this.main.apiService,
this.main.logService
);
const response = await command.run(password, cmd); const response = await command.run(password, cmd);
this.processResponse(response); this.processResponse(response);
} }
}); });
program program
.command('sync') .command("sync")
.description('Pull the latest vault data from server.') .description("Pull the latest vault data from server.")
.option('-f, --force', 'Force a full sync.') .option("-f, --force", "Force a full sync.")
.option('--last', 'Get the last sync date.') .option("--last", "Get the last sync date.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bw sync'); writeLn(" bw sync");
writeLn(' bw sync -f'); writeLn(" bw sync -f");
writeLn(' bw sync --last'); writeLn(" bw sync --last");
writeLn('', true); writeLn("", true);
}) })
.action(async cmd => { .action(async (cmd) => {
await this.exitIfLocked(); await this.exitIfLocked();
const command = new SyncCommand(this.main.syncService); const command = new SyncCommand(this.main.syncService);
const response = await command.run(cmd); const response = await command.run(cmd);
@@ -246,55 +282,55 @@ export class Program extends BaseProgram {
}); });
program program
.command('generate') .command("generate")
.description('Generate a password/passphrase.') .description("Generate a password/passphrase.")
.option('-u, --uppercase', 'Include uppercase characters.') .option("-u, --uppercase", "Include uppercase characters.")
.option('-l, --lowercase', 'Include lowercase characters.') .option("-l, --lowercase", "Include lowercase characters.")
.option('-n, --number', 'Include numeric characters.') .option("-n, --number", "Include numeric characters.")
.option('-s, --special', 'Include special characters.') .option("-s, --special", "Include special characters.")
.option('-p, --passphrase', 'Generate a passphrase.') .option("-p, --passphrase", "Generate a passphrase.")
.option('--length <length>', 'Length of the password.') .option("--length <length>", "Length of the password.")
.option('--words <words>', 'Number of words.') .option("--words <words>", "Number of words.")
.option('--separator <separator>', 'Word separator.') .option("--separator <separator>", "Word separator.")
.option('-c, --capitalize', 'Title case passphrase.') .option("-c, --capitalize", "Title case passphrase.")
.option('--includeNumber', 'Passphrase includes number.') .option("--includeNumber", "Passphrase includes number.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Notes:'); writeLn("\n Notes:");
writeLn(''); writeLn("");
writeLn(' Default options are `-uln --length 14`.'); writeLn(" Default options are `-uln --length 14`.");
writeLn(''); writeLn("");
writeLn(' Minimum `length` is 5.'); writeLn(" Minimum `length` is 5.");
writeLn(''); writeLn("");
writeLn(' Minimum `words` is 3.'); writeLn(" Minimum `words` is 3.");
writeLn(''); writeLn("");
writeLn(' Examples:'); writeLn(" Examples:");
writeLn(''); writeLn("");
writeLn(' bw generate'); writeLn(" bw generate");
writeLn(' bw generate -u -l --length 18'); writeLn(" bw generate -u -l --length 18");
writeLn(' bw generate -ulns --length 25'); writeLn(" bw generate -ulns --length 25");
writeLn(' bw generate -ul'); writeLn(" bw generate -ul");
writeLn(' bw generate -p --separator _'); writeLn(" bw generate -p --separator _");
writeLn(' bw generate -p --words 5 --separator space'); writeLn(" bw generate -p --words 5 --separator space");
writeLn('', true); writeLn("", true);
}) })
.action(async options => { .action(async (options) => {
const command = new GenerateCommand(this.main.passwordGenerationService); const command = new GenerateCommand(this.main.passwordGenerationService);
const response = await command.run(options); const response = await command.run(options);
this.processResponse(response); this.processResponse(response);
}); });
program program
.command('encode') .command("encode")
.description('Base 64 encode stdin.') .description("Base 64 encode stdin.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Notes:'); writeLn("\n Notes:");
writeLn(''); writeLn("");
writeLn(' Use to create `encodedJson` for `create` and `edit` commands.'); writeLn(" Use to create `encodedJson` for `create` and `edit` commands.");
writeLn(''); writeLn("");
writeLn(' Examples:'); writeLn(" Examples:");
writeLn(''); writeLn("");
writeLn(' echo \'{"name":"My Folder"}\' | bw encode'); writeLn(' echo \'{"name":"My Folder"}\' | bw encode');
writeLn('', true); writeLn("", true);
}) })
.action(async () => { .action(async () => {
const command = new EncodeCommand(); const command = new EncodeCommand();
@@ -303,27 +339,38 @@ export class Program extends BaseProgram {
}); });
program program
.command('config <setting> [value]') .command("config <setting> [value]")
.description('Configure CLI settings.') .description("Configure CLI settings.")
.option('--web-vault <url>', 'Provides a custom web vault URL that differs from the base URL.') .option(
.option('--api <url>', 'Provides a custom API URL that differs from the base URL.') "--web-vault <url>",
.option('--identity <url>', 'Provides a custom identity URL that differs from the base URL.') "Provides a custom web vault URL that differs from the base URL."
.option('--icons <url>', 'Provides a custom icons service URL that differs from the base URL.') )
.option('--notifications <url>', 'Provides a custom notifications URL that differs from the base URL.') .option("--api <url>", "Provides a custom API URL that differs from the base URL.")
.option('--events <url>', 'Provides a custom events URL that differs from the base URL.') .option("--identity <url>", "Provides a custom identity URL that differs from the base URL.")
.option('--key-connector <url>', 'Provides the URL for your Key Connector server.') .option(
.on('--help', () => { "--icons <url>",
writeLn('\n Settings:'); "Provides a custom icons service URL that differs from the base URL."
writeLn(''); )
writeLn(' server - On-premises hosted installation URL.'); .option(
writeLn(''); "--notifications <url>",
writeLn(' Examples:'); "Provides a custom notifications URL that differs from the base URL."
writeLn(''); )
writeLn(' bw config server'); .option("--events <url>", "Provides a custom events URL that differs from the base URL.")
writeLn(' bw config server https://bw.company.com'); .option("--key-connector <url>", "Provides the URL for your Key Connector server.")
writeLn(' bw config server bitwarden.com'); .on("--help", () => {
writeLn(' bw config server --api http://localhost:4000 --identity http://localhost:33656'); writeLn("\n Settings:");
writeLn('', true); writeLn("");
writeLn(" server - On-premises hosted installation URL.");
writeLn("");
writeLn(" Examples:");
writeLn("");
writeLn(" bw config server");
writeLn(" bw config server https://bw.company.com");
writeLn(" bw config server bitwarden.com");
writeLn(
" bw config server --api http://localhost:4000 --identity http://localhost:33656"
);
writeLn("", true);
}) })
.action(async (setting, value, options) => { .action(async (setting, value, options) => {
const command = new ConfigCommand(this.main.environmentService); const command = new ConfigCommand(this.main.environmentService);
@@ -332,41 +379,46 @@ export class Program extends BaseProgram {
}); });
program program
.command('update') .command("update")
.description('Check for updates.') .description("Check for updates.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Notes:'); writeLn("\n Notes:");
writeLn(''); writeLn("");
writeLn(' Returns the URL to download the newest version of this CLI tool.'); writeLn(" Returns the URL to download the newest version of this CLI tool.");
writeLn(''); writeLn("");
writeLn(' Use the `--raw` option to return only the download URL for the update.'); writeLn(" Use the `--raw` option to return only the download URL for the update.");
writeLn(''); writeLn("");
writeLn(' Examples:'); writeLn(" Examples:");
writeLn(''); writeLn("");
writeLn(' bw update'); writeLn(" bw update");
writeLn(' bw update --raw'); writeLn(" bw update --raw");
writeLn('', true); writeLn("", true);
}) })
.action(async () => { .action(async () => {
const command = new UpdateCommand(this.main.platformUtilsService, this.main.i18nService, const command = new UpdateCommand(
'cli', 'bw', true); this.main.platformUtilsService,
this.main.i18nService,
"cli",
"bw",
true
);
const response = await command.run(); const response = await command.run();
this.processResponse(response); this.processResponse(response);
}); });
program program
.command('completion') .command("completion")
.description('Generate shell completions.') .description("Generate shell completions.")
.option('--shell <shell>', 'Shell to generate completions for.') .option("--shell <shell>", "Shell to generate completions for.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Notes:'); writeLn("\n Notes:");
writeLn(''); writeLn("");
writeLn(' Valid shells are `zsh`.'); writeLn(" Valid shells are `zsh`.");
writeLn(''); writeLn("");
writeLn(' Examples:'); writeLn(" Examples:");
writeLn(''); writeLn("");
writeLn(' bw completion --shell zsh'); writeLn(" bw completion --shell zsh");
writeLn('', true); writeLn("", true);
}) })
.action(async (options: program.OptionValues, cmd: program.Command) => { .action(async (options: program.OptionValues, cmd: program.Command) => {
const command = new CompletionCommand(); const command = new CompletionCommand();
@@ -375,44 +427,44 @@ export class Program extends BaseProgram {
}); });
program program
.command('status') .command("status")
.description('Show server, last sync, user information, and vault status.') .description("Show server, last sync, user information, and vault status.")
.on('--help', () => { .on("--help", () => {
writeLn(''); writeLn("");
writeLn(''); writeLn("");
writeLn(' Example return value:'); writeLn(" Example return value:");
writeLn(''); writeLn("");
writeLn(' {'); writeLn(" {");
writeLn(' "serverUrl": "https://bitwarden.example.com",'); writeLn(' "serverUrl": "https://bitwarden.example.com",');
writeLn(' "lastSync": "2020-06-16T06:33:51.419Z",'); writeLn(' "lastSync": "2020-06-16T06:33:51.419Z",');
writeLn(' "userEmail": "user@example.com,'); writeLn(' "userEmail": "user@example.com,');
writeLn(' "userId": "00000000-0000-0000-0000-000000000000",'); writeLn(' "userId": "00000000-0000-0000-0000-000000000000",');
writeLn(' "status": "locked"'); writeLn(' "status": "locked"');
writeLn(' }'); writeLn(" }");
writeLn(''); writeLn("");
writeLn(' Notes:'); writeLn(" Notes:");
writeLn(''); writeLn("");
writeLn(' `status` is one of:'); writeLn(" `status` is one of:");
writeLn(' - `unauthenticated` when you are not logged in'); writeLn(" - `unauthenticated` when you are not logged in");
writeLn(' - `locked` when you are logged in and the vault is locked'); writeLn(" - `locked` when you are logged in and the vault is locked");
writeLn(' - `unlocked` when you are logged in and the vault is unlocked'); writeLn(" - `unlocked` when you are logged in and the vault is unlocked");
writeLn('', true); writeLn("", true);
}) })
.action(async () => { .action(async () => {
const command = new StatusCommand( const command = new StatusCommand(
this.main.environmentService, this.main.environmentService,
this.main.syncService, this.main.syncService,
this.main.userService, this.main.userService,
this.main.vaultTimeoutService); this.main.vaultTimeoutService
);
const response = await command.run(); const response = await command.run();
this.processResponse(response); this.processResponse(response);
}); });
} }
protected processResponse(response: Response, exitImmediately = false) { protected processResponse(response: Response, exitImmediately = false) {
super.processResponse(response, exitImmediately, () => { super.processResponse(response, exitImmediately, () => {
if (response.data.object === 'template') { if (response.data.object === "template") {
return this.getJson((response.data as TemplateResponse).template); return this.getJson((response.data as TemplateResponse).template);
} }
return null; return null;
@@ -423,28 +475,34 @@ export class Program extends BaseProgram {
await this.exitIfNotAuthed(); await this.exitIfNotAuthed();
const hasKey = await this.main.cryptoService.hasKey(); const hasKey = await this.main.cryptoService.hasKey();
if (!hasKey) { if (!hasKey) {
const canInteract = process.env.BW_NOINTERACTION !== 'true'; const canInteract = process.env.BW_NOINTERACTION !== "true";
if (canInteract) { if (canInteract) {
const usesKeyConnector = await this.main.keyConnectorService.getUsesKeyConnector(); const usesKeyConnector = await this.main.keyConnectorService.getUsesKeyConnector();
if (usesKeyConnector) { if (usesKeyConnector) {
const response = Response.error('Your vault is locked. You must unlock your vault using your session key.\n' + const response = Response.error(
'If you do not have your session key, you can get a new one by logging out and logging in again.'); "Your vault is locked. You must unlock your vault using your session key.\n" +
"If you do not have your session key, you can get a new one by logging out and logging in again."
);
this.processResponse(response, true); this.processResponse(response, true);
} else { } else {
const command = new UnlockCommand(this.main.cryptoService, this.main.userService, const command = new UnlockCommand(
this.main.cryptoFunctionService, this.main.apiService, this.main.logService); this.main.cryptoService,
this.main.userService,
this.main.cryptoFunctionService,
this.main.apiService,
this.main.logService
);
const response = await command.run(null, null); const response = await command.run(null, null);
if (!response.success) { if (!response.success) {
this.processResponse(response, true); this.processResponse(response, true);
} }
} }
} else { } else {
this.processResponse(Response.error('Vault is locked.'), true); this.processResponse(Response.error("Vault is locked."), true);
} }
} else if (!this.main.cryptoService.hasKeyInMemory()) { } else if (!this.main.cryptoService.hasKeyInMemory()) {
await this.main.cryptoService.getKey(); await this.main.cryptoService.getKey();
} }
} }
} }

View File

@@ -1,30 +1,30 @@
import * as chalk from 'chalk'; import * as chalk from "chalk";
import * as program from 'commander'; import * as program from "commander";
import * as fs from 'fs'; import * as fs from "fs";
import * as path from 'path'; import * as path from "path";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { SendType } from 'jslib-common/enums/sendType'; import { SendType } from "jslib-common/enums/sendType";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
import { GetCommand } from './commands/get.command'; import { GetCommand } from "./commands/get.command";
import { SendCreateCommand } from './commands/send/create.command'; import { SendCreateCommand } from "./commands/send/create.command";
import { SendDeleteCommand } from './commands/send/delete.command'; import { SendDeleteCommand } from "./commands/send/delete.command";
import { SendEditCommand } from './commands/send/edit.command'; import { SendEditCommand } from "./commands/send/edit.command";
import { SendGetCommand } from './commands/send/get.command'; import { SendGetCommand } from "./commands/send/get.command";
import { SendListCommand } from './commands/send/list.command'; import { SendListCommand } from "./commands/send/list.command";
import { SendReceiveCommand } from './commands/send/receive.command'; import { SendReceiveCommand } from "./commands/send/receive.command";
import { SendRemovePasswordCommand } from './commands/send/removePassword.command'; import { SendRemovePasswordCommand } from "./commands/send/removePassword.command";
import { SendFileResponse } from './models/response/sendFileResponse'; import { SendFileResponse } from "./models/response/sendFileResponse";
import { SendResponse } from './models/response/sendResponse'; import { SendResponse } from "./models/response/sendResponse";
import { SendTextResponse } from './models/response/sendTextResponse'; import { SendTextResponse } from "./models/response/sendTextResponse";
import { Main } from './bw'; import { Main } from "./bw";
import { Program } from './program'; import { Program } from "./program";
import { CliUtils } from './utils'; import { CliUtils } from "./utils";
const writeLn = CliUtils.writeLn; const writeLn = CliUtils.writeLn;
@@ -40,18 +40,31 @@ export class SendProgram extends Program {
} }
private sendCommand(): program.Command { private sendCommand(): program.Command {
return new program.Command('send') return new program.Command("send")
.arguments('<data>') .arguments("<data>")
.description('Work with Bitwarden sends. A Send can be quickly created using this command or subcommands can be used to fine-tune the Send', { .description(
data: 'The data to Send. Specify as a filepath with the --file option', "Work with Bitwarden sends. A Send can be quickly created using this command or subcommands can be used to fine-tune the Send",
}) {
.option('-f, --file', 'Specifies that <data> is a filepath') data: "The data to Send. Specify as a filepath with the --file option",
.option('-d, --deleteInDays <days>', 'The number of days in the future to set deletion date, defaults to 7', '7') }
.option('-a, --maxAccessCount <amount>', 'The amount of max possible accesses.') )
.option('--hidden', 'Hide <data> in web by default. Valid only if --file is not set.') .option("-f, --file", "Specifies that <data> is a filepath")
.option('-n, --name <name>', 'The name of the Send. Defaults to a guid for text Sends and the filename for files.') .option(
.option('--notes <notes>', 'Notes to add to the Send.') "-d, --deleteInDays <days>",
.option('--fullObject', 'Specifies that the full Send object should be returned rather than just the access url.') "The number of days in the future to set deletion date, defaults to 7",
"7"
)
.option("-a, --maxAccessCount <amount>", "The amount of max possible accesses.")
.option("--hidden", "Hide <data> in web by default. Valid only if --file is not set.")
.option(
"-n, --name <name>",
"The name of the Send. Defaults to a guid for text Sends and the filename for files."
)
.option("--notes <notes>", "Notes to add to the Send.")
.option(
"--fullObject",
"Specifies that the full Send object should be returned rather than just the access url."
)
.addCommand(this.listCommand()) .addCommand(this.listCommand())
.addCommand(this.templateCommand()) .addCommand(this.templateCommand())
.addCommand(this.getCommand()) .addCommand(this.getCommand())
@@ -75,108 +88,144 @@ export class SendProgram extends Program {
} }
private receiveCommand(): program.Command { private receiveCommand(): program.Command {
return new program.Command('receive') return new program.Command("receive")
.arguments('<url>') .arguments("<url>")
.description('Access a Bitwarden Send from a url') .description("Access a Bitwarden Send from a url")
.option('--password <password>', 'Password needed to access the Send.') .option("--password <password>", "Password needed to access the Send.")
.option('--passwordenv <passwordenv>', 'Environment variable storing the Send\'s password') .option("--passwordenv <passwordenv>", "Environment variable storing the Send's password")
.option('--passwordfile <passwordfile>', 'Path to a file containing the Send\s password as its first line') .option(
.option('--obj', 'Return the Send\'s json object rather than the Send\'s content') "--passwordfile <passwordfile>",
.option('--output <location>', 'Specify a file path to save a File-type Send to') "Path to a file containing the Sends password as its first line"
.on('--help', () => { )
writeLn(''); .option("--obj", "Return the Send's json object rather than the Send's content")
writeLn('If a password is required, the provided password is used or the user is prompted.'); .option("--output <location>", "Specify a file path to save a File-type Send to")
writeLn('', true); .on("--help", () => {
writeLn("");
writeLn(
"If a password is required, the provided password is used or the user is prompted."
);
writeLn("", true);
}) })
.action(async (url: string, options: program.OptionValues) => { .action(async (url: string, options: program.OptionValues) => {
const cmd = new SendReceiveCommand(this.main.apiService, this.main.cryptoService, const cmd = new SendReceiveCommand(
this.main.cryptoFunctionService, this.main.platformUtilsService, this.main.environmentService); this.main.apiService,
this.main.cryptoService,
this.main.cryptoFunctionService,
this.main.platformUtilsService,
this.main.environmentService
);
const response = await cmd.run(url, options); const response = await cmd.run(url, options);
this.processResponse(response); this.processResponse(response);
}); });
} }
private listCommand(): program.Command { private listCommand(): program.Command {
return new program.Command('list') return new program.Command("list")
.description('List all the Sends owned by you') .description("List all the Sends owned by you")
.on('--help', () => { writeLn(chalk('This is in the list command')); }) .on("--help", () => {
writeLn(chalk("This is in the list command"));
})
.action(async (options: program.OptionValues) => { .action(async (options: program.OptionValues) => {
await this.exitIfLocked(); await this.exitIfLocked();
const cmd = new SendListCommand(this.main.sendService, this.main.environmentService, const cmd = new SendListCommand(
this.main.searchService); this.main.sendService,
this.main.environmentService,
this.main.searchService
);
const response = await cmd.run(options); const response = await cmd.run(options);
this.processResponse(response); this.processResponse(response);
}); });
} }
private templateCommand(): program.Command { private templateCommand(): program.Command {
return new program.Command('template') return new program.Command("template")
.arguments('<object>') .arguments("<object>")
.description('Get json templates for send objects', { .description("Get json templates for send objects", {
object: 'Valid objects are: send, send.text, send.file', object: "Valid objects are: send, send.text, send.file",
}) })
.action(async object => { .action(async (object) => {
const cmd = new GetCommand(this.main.cipherService, this.main.folderService, const cmd = new GetCommand(
this.main.collectionService, this.main.totpService, this.main.auditService, this.main.cryptoService, this.main.cipherService,
this.main.userService, this.main.searchService, this.main.apiService, this.main.sendService, this.main.folderService,
this.main.environmentService); this.main.collectionService,
const response = await cmd.run('template', object, null); this.main.totpService,
this.main.auditService,
this.main.cryptoService,
this.main.userService,
this.main.searchService,
this.main.apiService,
this.main.sendService,
this.main.environmentService
);
const response = await cmd.run("template", object, null);
this.processResponse(response); this.processResponse(response);
}); });
} }
private getCommand(): program.Command { private getCommand(): program.Command {
return new program.Command('get') return new program.Command("get")
.arguments('<id>') .arguments("<id>")
.description('Get Sends owned by you.') .description("Get Sends owned by you.")
.option('--output <output>', 'Output directory or filename for attachment.') .option("--output <output>", "Output directory or filename for attachment.")
.option('--text', 'Specifies to return the text content of a Send') .option("--text", "Specifies to return the text content of a Send")
.on('--help', () => { .on("--help", () => {
writeLn(''); writeLn("");
writeLn(' Id:'); writeLn(" Id:");
writeLn(''); writeLn("");
writeLn(' Search term or Send\'s globally unique `id`.'); writeLn(" Search term or Send's globally unique `id`.");
writeLn(''); writeLn("");
writeLn(' If raw output is specified and no output filename or directory is given for'); writeLn(" If raw output is specified and no output filename or directory is given for");
writeLn(' an attachment query, the attachment content is written to stdout.'); writeLn(" an attachment query, the attachment content is written to stdout.");
writeLn(''); writeLn("");
writeLn(' Examples:'); writeLn(" Examples:");
writeLn(''); writeLn("");
writeLn(' bw get send searchText'); writeLn(" bw get send searchText");
writeLn(' bw get send id'); writeLn(" bw get send id");
writeLn(' bw get send searchText --text'); writeLn(" bw get send searchText --text");
writeLn(' bw get send searchText --file'); writeLn(" bw get send searchText --file");
writeLn(' bw get send searchText --file --output ../Photos/photo.jpg'); writeLn(" bw get send searchText --file --output ../Photos/photo.jpg");
writeLn(' bw get send searchText --file --raw'); writeLn(" bw get send searchText --file --raw");
writeLn('', true); writeLn("", true);
}) })
.action(async (id: string, options: program.OptionValues) => { .action(async (id: string, options: program.OptionValues) => {
await this.exitIfLocked(); await this.exitIfLocked();
const cmd = new SendGetCommand(this.main.sendService, this.main.environmentService, const cmd = new SendGetCommand(
this.main.searchService, this.main.cryptoService); this.main.sendService,
this.main.environmentService,
this.main.searchService,
this.main.cryptoService
);
const response = await cmd.run(id, options); const response = await cmd.run(id, options);
this.processResponse(response); this.processResponse(response);
}); });
} }
private createCommand(): program.Command { private createCommand(): program.Command {
return new program.Command('create') return new program.Command("create")
.arguments('[encodedJson]') .arguments("[encodedJson]")
.description('create a Send', { .description("create a Send", {
encodedJson: 'JSON object to upload. Can also be piped in through stdin.', encodedJson: "JSON object to upload. Can also be piped in through stdin.",
}) })
.option('--file <path>', 'file to Send. Can also be specified in parent\'s JSON.') .option("--file <path>", "file to Send. Can also be specified in parent's JSON.")
.option('--text <text>', 'text to Send. Can also be specified in parent\'s JSON.') .option("--text <text>", "text to Send. Can also be specified in parent's JSON.")
.option('--hidden', 'text hidden flag. Valid only with the --text option.') .option("--hidden", "text hidden flag. Valid only with the --text option.")
.option('--password <password>', 'optional password to access this Send. Can also be specified in JSON') .option(
.on('--help', () => { "--password <password>",
writeLn(''); "optional password to access this Send. Can also be specified in JSON"
writeLn('Note:'); )
writeLn(' Options specified in JSON take precedence over command options'); .on("--help", () => {
writeLn('', true); writeLn("");
writeLn("Note:");
writeLn(" Options specified in JSON take precedence over command options");
writeLn("", true);
}) })
.action(async (encodedJson: string, options: program.OptionValues, args: { parent: program.Command }) => { .action(
async (
encodedJson: string,
options: program.OptionValues,
args: { parent: program.Command }
) => {
// Work-around to support `--fullObject` option for `send create --fullObject` // Work-around to support `--fullObject` option for `send create --fullObject`
// Calling `option('--fullObject', ...)` above won't work due to Commander doesn't like same option // Calling `option('--fullObject', ...)` above won't work due to Commander doesn't like same option
// to be defind on both parent-command and sub-command // to be defind on both parent-command and sub-command
@@ -188,26 +237,32 @@ export class SendProgram extends Program {
const response = await this.runCreate(encodedJson, mergedOptions); const response = await this.runCreate(encodedJson, mergedOptions);
this.processResponse(response); this.processResponse(response);
}); }
);
} }
private editCommand(): program.Command { private editCommand(): program.Command {
return new program.Command('edit') return new program.Command("edit")
.arguments('[encodedJson]') .arguments("[encodedJson]")
.description('edit a Send', { .description("edit a Send", {
encodedJson: 'Updated JSON object to save. If not provided, encodedJson is read from stdin.', encodedJson:
"Updated JSON object to save. If not provided, encodedJson is read from stdin.",
}) })
.option('--itemid <itemid>', 'Overrides the itemId provided in [encodedJson]') .option("--itemid <itemid>", "Overrides the itemId provided in [encodedJson]")
.on('--help', () => { .on("--help", () => {
writeLn(''); writeLn("");
writeLn('Note:'); writeLn("Note:");
writeLn(' You cannot update a File-type Send\'s file. Just delete and remake it'); writeLn(" You cannot update a File-type Send's file. Just delete and remake it");
writeLn('', true); writeLn("", true);
}) })
.action(async (encodedJson: string, options: program.OptionValues) => { .action(async (encodedJson: string, options: program.OptionValues) => {
await this.exitIfLocked(); await this.exitIfLocked();
const getCmd = new SendGetCommand(this.main.sendService, this.main.environmentService, const getCmd = new SendGetCommand(
this.main.searchService, this.main.cryptoService); this.main.sendService,
this.main.environmentService,
this.main.searchService,
this.main.cryptoService
);
const cmd = new SendEditCommand(this.main.sendService, this.main.userService, getCmd); const cmd = new SendEditCommand(this.main.sendService, this.main.userService, getCmd);
const response = await cmd.run(encodedJson, options); const response = await cmd.run(encodedJson, options);
this.processResponse(response); this.processResponse(response);
@@ -215,10 +270,10 @@ export class SendProgram extends Program {
} }
private deleteCommand(): program.Command { private deleteCommand(): program.Command {
return new program.Command('delete') return new program.Command("delete")
.arguments('<id>') .arguments("<id>")
.description('delete a Send', { .description("delete a Send", {
id: 'The id of the Send to delete.', id: "The id of the Send to delete.",
}) })
.action(async (id: string) => { .action(async (id: string) => {
await this.exitIfLocked(); await this.exitIfLocked();
@@ -229,10 +284,10 @@ export class SendProgram extends Program {
} }
private removePasswordCommand(): program.Command { private removePasswordCommand(): program.Command {
return new program.Command('remove-password') return new program.Command("remove-password")
.arguments('<id>') .arguments("<id>")
.description('removes the saved password from a Send.', { .description("removes the saved password from a Send.", {
id: 'The id of the Send to alter.', id: "The id of the Send to alter.",
}) })
.action(async (id: string) => { .action(async (id: string) => {
await this.exitIfLocked(); await this.exitIfLocked();
@@ -250,7 +305,7 @@ export class SendProgram extends Program {
if (options.file != null) { if (options.file != null) {
data = path.resolve(data); data = path.resolve(data);
if (!fs.existsSync(data)) { if (!fs.existsSync(data)) {
return Response.badRequest('data path does not exist'); return Response.badRequest("data path does not exist");
} }
sendFile = SendFileResponse.template(data); sendFile = SendFileResponse.template(data);
@@ -268,13 +323,16 @@ export class SendProgram extends Program {
type: type, type: type,
}); });
return Buffer.from(JSON.stringify(template), 'utf8').toString('base64'); return Buffer.from(JSON.stringify(template), "utf8").toString("base64");
} }
private async runCreate(encodedJson: string, options: program.OptionValues) { private async runCreate(encodedJson: string, options: program.OptionValues) {
await this.exitIfLocked(); await this.exitIfLocked();
const cmd = new SendCreateCommand(this.main.sendService, this.main.userService, const cmd = new SendCreateCommand(
this.main.environmentService); this.main.sendService,
this.main.userService,
this.main.environmentService
);
return await cmd.run(encodedJson, options); return await cmd.run(encodedJson, options);
} }
} }

View File

@@ -1,19 +1,20 @@
import * as fs from 'fs'; import * as fs from "fs";
import * as path from 'path'; import * as path from "path";
import { I18nService as BaseI18nService } from 'jslib-common/services/i18n.service'; import { I18nService as BaseI18nService } from "jslib-common/services/i18n.service";
export class I18nService extends BaseI18nService { export class I18nService extends BaseI18nService {
constructor(systemLanguage: string, localesDirectory: string) { constructor(systemLanguage: string, localesDirectory: string) {
super(systemLanguage, localesDirectory, (formattedLocale: string) => { super(systemLanguage, localesDirectory, (formattedLocale: string) => {
const filePath = path.join(__dirname, this.localesDirectory + '/' + formattedLocale + '/messages.json'); const filePath = path.join(
const localesJson = fs.readFileSync(filePath, 'utf8'); __dirname,
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, '')); // strip the BOM this.localesDirectory + "/" + formattedLocale + "/messages.json"
);
const localesJson = fs.readFileSync(filePath, "utf8");
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, "")); // strip the BOM
return Promise.resolve(locales); return Promise.resolve(locales);
}); });
this.supportedTranslationLocales = [ this.supportedTranslationLocales = ["en"];
'en',
];
} }
} }

View File

@@ -1,13 +1,16 @@
import { CryptoService } from 'jslib-common/abstractions/crypto.service'; import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { LogService } from 'jslib-common/abstractions/log.service'; import { LogService } from "jslib-common/abstractions/log.service";
import { StorageService } from 'jslib-common/abstractions/storage.service'; import { StorageService } from "jslib-common/abstractions/storage.service";
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { Utils } from 'jslib-common/misc/utils'; import { Utils } from "jslib-common/misc/utils";
export class NodeEnvSecureStorageService implements StorageService { export class NodeEnvSecureStorageService implements StorageService {
constructor(private storageService: StorageService, private logService: LogService, constructor(
private cryptoService: () => CryptoService) { } private storageService: StorageService,
private logService: LogService,
private cryptoService: () => CryptoService
) {}
async get<T>(key: string): Promise<T> { async get<T>(key: string): Promise<T> {
const value = await this.storageService.get<string>(this.makeProtectedStorageKey(key)); const value = await this.storageService.get<string>(this.makeProtectedStorageKey(key));
@@ -23,8 +26,8 @@ export class NodeEnvSecureStorageService implements StorageService {
} }
async save(key: string, obj: any): Promise<any> { async save(key: string, obj: any): Promise<any> {
if (typeof (obj) !== 'string') { if (typeof obj !== "string") {
throw new Error('Only string storage is allowed.'); throw new Error("Only string storage is allowed.");
} }
const protectedObj = await this.encrypt(obj); const protectedObj = await this.encrypt(obj);
await this.storageService.save(this.makeProtectedStorageKey(key), protectedObj); await this.storageService.save(this.makeProtectedStorageKey(key), protectedObj);
@@ -37,12 +40,14 @@ export class NodeEnvSecureStorageService implements StorageService {
private async encrypt(plainValue: string): Promise<string> { private async encrypt(plainValue: string): Promise<string> {
const sessionKey = this.getSessionKey(); const sessionKey = this.getSessionKey();
if (sessionKey == null) { if (sessionKey == null) {
throw new Error('No session key available.'); throw new Error("No session key available.");
} }
const encValue = await this.cryptoService().encryptToBytes( const encValue = await this.cryptoService().encryptToBytes(
Utils.fromB64ToArray(plainValue).buffer, sessionKey); Utils.fromB64ToArray(plainValue).buffer,
sessionKey
);
if (encValue == null) { if (encValue == null) {
throw new Error('Value didn\'t encrypt.'); throw new Error("Value didn't encrypt.");
} }
return Utils.fromBufferToB64(encValue.buffer); return Utils.fromBufferToB64(encValue.buffer);
@@ -56,15 +61,17 @@ export class NodeEnvSecureStorageService implements StorageService {
} }
const decValue = await this.cryptoService().decryptFromBytes( const decValue = await this.cryptoService().decryptFromBytes(
Utils.fromB64ToArray(encValue).buffer, sessionKey); Utils.fromB64ToArray(encValue).buffer,
sessionKey
);
if (decValue == null) { if (decValue == null) {
this.logService.info('Failed to decrypt.'); this.logService.info("Failed to decrypt.");
return null; return null;
} }
return Utils.fromBufferToB64(decValue); return Utils.fromBufferToB64(decValue);
} catch (e) { } catch (e) {
this.logService.info('Decrypt error.'); this.logService.info("Decrypt error.");
return null; return null;
} }
} }
@@ -81,13 +88,13 @@ export class NodeEnvSecureStorageService implements StorageService {
} }
} }
} catch (e) { } catch (e) {
this.logService.info('Session key is invalid.'); this.logService.info("Session key is invalid.");
} }
return null; return null;
} }
private makeProtectedStorageKey(key: string) { private makeProtectedStorageKey(key: string) {
return '__PROTECTED__' + key; return "__PROTECTED__" + key;
} }
} }

View File

@@ -1,29 +1,29 @@
import * as fs from 'fs'; import * as fs from "fs";
import * as path from 'path'; import * as path from "path";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'; import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { Organization } from 'jslib-common/models/domain/organization'; import { Organization } from "jslib-common/models/domain/organization";
import { CollectionView } from 'jslib-common/models/view/collectionView'; import { CollectionView } from "jslib-common/models/view/collectionView";
import { FolderView } from 'jslib-common/models/view/folderView'; import { FolderView } from "jslib-common/models/view/folderView";
import { NodeUtils } from 'jslib-common/misc/nodeUtils'; import { NodeUtils } from "jslib-common/misc/nodeUtils";
export class CliUtils { export class CliUtils {
static writeLn(s: string, finalLine: boolean = false, error: boolean = false) { static writeLn(s: string, finalLine: boolean = false, error: boolean = false) {
const stream = error ? process.stderr : process.stdout; const stream = error ? process.stderr : process.stdout;
if (finalLine && (process.platform === 'win32' || !stream.isTTY)) { if (finalLine && (process.platform === "win32" || !stream.isTTY)) {
stream.write(s); stream.write(s);
} else { } else {
stream.write(s + '\n'); stream.write(s + "\n");
} }
} }
static readFile(input: string): Promise<string> { static readFile(input: string): Promise<string> {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
let p: string = null; let p: string = null;
if (input != null && input !== '') { if (input != null && input !== "") {
const osInput = path.join(input); const osInput = path.join(input);
if (osInput.indexOf(path.sep) === -1) { if (osInput.indexOf(path.sep) === -1) {
p = path.join(process.cwd(), osInput); p = path.join(process.cwd(), osInput);
@@ -31,9 +31,9 @@ export class CliUtils {
p = osInput; p = osInput;
} }
} else { } else {
reject('You must specify a file path.'); reject("You must specify a file path.");
} }
fs.readFile(p, 'utf8', (err, data) => { fs.readFile(p, "utf8", (err, data) => {
if (err != null) { if (err != null) {
reject(err.message); reject(err.message);
} }
@@ -55,7 +55,7 @@ export class CliUtils {
static saveFile(data: string | Buffer, output: string, defaultFileName: string) { static saveFile(data: string | Buffer, output: string, defaultFileName: string) {
let p: string = null; let p: string = null;
let mkdir = false; let mkdir = false;
if (output != null && output !== '') { if (output != null && output !== "") {
const osOutput = path.join(output); const osOutput = path.join(output);
if (osOutput.indexOf(path.sep) === -1) { if (osOutput.indexOf(path.sep) === -1) {
p = path.join(process.cwd(), osOutput); p = path.join(process.cwd(), osOutput);
@@ -75,14 +75,14 @@ export class CliUtils {
if (mkdir) { if (mkdir) {
const dir = p.substring(0, p.lastIndexOf(path.sep)); const dir = p.substring(0, p.lastIndexOf(path.sep));
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
NodeUtils.mkdirpSync(dir, '700'); NodeUtils.mkdirpSync(dir, "700");
} }
} }
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
fs.writeFile(p, data, { encoding: 'utf8', mode: 0o600 }, err => { fs.writeFile(p, data, { encoding: "utf8", mode: 0o600 }, (err) => {
if (err != null) { if (err != null) {
reject('Cannot save file to ' + p); reject("Cannot save file to " + p);
} }
resolve(p); resolve(p);
}); });
@@ -102,7 +102,7 @@ export class CliUtils {
* @return an empty [Response] if written to stdout or a [Response] with the chosen output file otherwise. * @return an empty [Response] if written to stdout or a [Response] with the chosen output file otherwise.
*/ */
static async saveResultToFile(data: string | Buffer, output: string, defaultFileName: string) { static async saveResultToFile(data: string | Buffer, output: string, defaultFileName: string) {
if ((output == null || output === '') && process.env.BW_RAW === 'true') { if ((output == null || output === "") && process.env.BW_RAW === "true") {
// No output is given and the user expects raw output. Since the command result is about content, // No output is given and the user expects raw output. Since the command result is about content,
// we directly return the command result to stdout (and suppress further messages). // we directly return the command result to stdout (and suppress further messages).
process.stdout.write(data); process.stdout.write(data);
@@ -110,22 +110,22 @@ export class CliUtils {
} }
const filePath = await this.saveFile(data, output, defaultFileName); const filePath = await this.saveFile(data, output, defaultFileName);
const res = new MessageResponse('Saved ' + filePath, null); const res = new MessageResponse("Saved " + filePath, null);
res.raw = filePath; res.raw = filePath;
return Response.success(res); return Response.success(res);
} }
static readStdin(): Promise<string> { static readStdin(): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let input: string = ''; let input: string = "";
if (process.stdin.isTTY) { if (process.stdin.isTTY) {
resolve(input); resolve(input);
return; return;
} }
process.stdin.setEncoding('utf8'); process.stdin.setEncoding("utf8");
process.stdin.on('readable', () => { process.stdin.on("readable", () => {
while (true) { while (true) {
const chunk = process.stdin.read(); const chunk = process.stdin.read();
if (chunk == null) { if (chunk == null) {
@@ -135,7 +135,7 @@ export class CliUtils {
} }
}); });
process.stdin.on('end', () => { process.stdin.on("end", () => {
resolve(input); resolve(input);
}); });
}); });
@@ -143,7 +143,7 @@ export class CliUtils {
static searchFolders(folders: FolderView[], search: string) { static searchFolders(folders: FolderView[], search: string) {
search = search.toLowerCase(); search = search.toLowerCase();
return folders.filter(f => { return folders.filter((f) => {
if (f.name != null && f.name.toLowerCase().indexOf(search) > -1) { if (f.name != null && f.name.toLowerCase().indexOf(search) > -1) {
return true; return true;
} }
@@ -153,7 +153,7 @@ export class CliUtils {
static searchCollections(collections: CollectionView[], search: string) { static searchCollections(collections: CollectionView[], search: string) {
search = search.toLowerCase(); search = search.toLowerCase();
return collections.filter(c => { return collections.filter((c) => {
if (c.name != null && c.name.toLowerCase().indexOf(search) > -1) { if (c.name != null && c.name.toLowerCase().indexOf(search) > -1) {
return true; return true;
} }
@@ -163,7 +163,7 @@ export class CliUtils {
static searchOrganizations(organizations: Organization[], search: string) { static searchOrganizations(organizations: Organization[], search: string) {
search = search.toLowerCase(); search = search.toLowerCase();
return organizations.filter(o => { return organizations.filter((o) => {
if (o.name != null && o.name.toLowerCase().indexOf(search) > -1) { if (o.name != null && o.name.toLowerCase().indexOf(search) > -1) {
return true; return true;
} }

View File

@@ -1,24 +1,24 @@
import * as chalk from 'chalk'; import * as chalk from "chalk";
import * as program from 'commander'; import * as program from "commander";
import { Response } from 'jslib-node/cli/models/response'; import { Response } from "jslib-node/cli/models/response";
import { Main } from './bw'; import { Main } from "./bw";
import { ConfirmCommand } from './commands/confirm.command'; import { ConfirmCommand } from "./commands/confirm.command";
import { CreateCommand } from './commands/create.command'; import { CreateCommand } from "./commands/create.command";
import { DeleteCommand } from './commands/delete.command'; import { DeleteCommand } from "./commands/delete.command";
import { EditCommand } from './commands/edit.command'; import { EditCommand } from "./commands/edit.command";
import { ExportCommand } from './commands/export.command'; import { ExportCommand } from "./commands/export.command";
import { GetCommand } from './commands/get.command'; import { GetCommand } from "./commands/get.command";
import { ImportCommand } from './commands/import.command'; import { ImportCommand } from "./commands/import.command";
import { ListCommand } from './commands/list.command'; import { ListCommand } from "./commands/list.command";
import { RestoreCommand } from './commands/restore.command'; import { RestoreCommand } from "./commands/restore.command";
import { ShareCommand } from './commands/share.command'; import { ShareCommand } from "./commands/share.command";
import { CliUtils } from './utils'; import { CliUtils } from "./utils";
import { Program } from './program'; import { Program } from "./program";
const writeLn = CliUtils.writeLn; const writeLn = CliUtils.writeLn;
@@ -35,64 +35,78 @@ export class VaultProgram extends Program {
.addCommand(this.editCommand()) .addCommand(this.editCommand())
.addCommand(this.deleteCommand()) .addCommand(this.deleteCommand())
.addCommand(this.restoreCommand()) .addCommand(this.restoreCommand())
.addCommand(this.shareCommand('move', false)) .addCommand(this.shareCommand("move", false))
.addCommand(this.confirmCommand()) .addCommand(this.confirmCommand())
.addCommand(this.importCommand()) .addCommand(this.importCommand())
.addCommand(this.exportCommand()) .addCommand(this.exportCommand())
.addCommand(this.shareCommand('share', true)); .addCommand(this.shareCommand("share", true));
} }
private validateObject(requestedObject: string, validObjects: string[]): boolean { private validateObject(requestedObject: string, validObjects: string[]): boolean {
let success = true; let success = true;
if (!validObjects.includes(requestedObject)) { if (!validObjects.includes(requestedObject)) {
success = false; success = false;
this.processResponse(Response.badRequest('Unknown object "' + requestedObject + '". Allowed objects are ' + this.processResponse(
validObjects.join(', ') + '.')); Response.badRequest(
'Unknown object "' +
requestedObject +
'". Allowed objects are ' +
validObjects.join(", ") +
"."
)
);
} }
return success; return success;
} }
private listCommand(): program.Command { private listCommand(): program.Command {
const listObjects = [ const listObjects = [
'items', "items",
'folders', "folders",
'collections', "collections",
'org-collections', "org-collections",
'org-members', "org-members",
'organizations', "organizations",
]; ];
return new program.Command('list') return new program.Command("list")
.arguments('<object>') .arguments("<object>")
.description('List an array of objects from the vault.', { .description("List an array of objects from the vault.", {
object: 'Valid objects are: ' + listObjects.join(', '), object: "Valid objects are: " + listObjects.join(", "),
}) })
.option('--search <search>', 'Perform a search on the listed objects.') .option("--search <search>", "Perform a search on the listed objects.")
.option('--url <url>', 'Filter items of type login with a url-match search.') .option("--url <url>", "Filter items of type login with a url-match search.")
.option('--folderid <folderid>', 'Filter items by folder id.') .option("--folderid <folderid>", "Filter items by folder id.")
.option('--collectionid <collectionid>', 'Filter items by collection id.') .option("--collectionid <collectionid>", "Filter items by collection id.")
.option('--organizationid <organizationid>', 'Filter items or collections by organization id.') .option(
.option('--trash', 'Filter items that are deleted and in the trash.') "--organizationid <organizationid>",
.on('--help', () => { "Filter items or collections by organization id."
writeLn('\n Notes:'); )
writeLn(''); .option("--trash", "Filter items that are deleted and in the trash.")
writeLn(' Combining search with a filter performs a logical AND operation.'); .on("--help", () => {
writeLn(''); writeLn("\n Notes:");
writeLn(' Combining multiple filters performs a logical OR operation.'); writeLn("");
writeLn(''); writeLn(" Combining search with a filter performs a logical AND operation.");
writeLn(' Examples:'); writeLn("");
writeLn(''); writeLn(" Combining multiple filters performs a logical OR operation.");
writeLn(' bw list items'); writeLn("");
writeLn(' bw list items --folderid 60556c31-e649-4b5d-8daf-fc1c391a1bf2'); writeLn(" Examples:");
writeLn(' bw list items --search google --folderid 60556c31-e649-4b5d-8daf-fc1c391a1bf2'); writeLn("");
writeLn(' bw list items --url https://google.com'); writeLn(" bw list items");
writeLn(' bw list items --folderid null'); writeLn(" bw list items --folderid 60556c31-e649-4b5d-8daf-fc1c391a1bf2");
writeLn(' bw list items --organizationid notnull'); writeLn(
writeLn(' bw list items --folderid 60556c31-e649-4b5d-8daf-fc1c391a1bf2 --organizationid notnull'); " bw list items --search google --folderid 60556c31-e649-4b5d-8daf-fc1c391a1bf2"
writeLn(' bw list items --trash'); );
writeLn(' bw list folders --search email'); writeLn(" bw list items --url https://google.com");
writeLn(' bw list org-members --organizationid 60556c31-e649-4b5d-8daf-fc1c391a1bf2'); writeLn(" bw list items --folderid null");
writeLn('', true); writeLn(" bw list items --organizationid notnull");
writeLn(
" bw list items --folderid 60556c31-e649-4b5d-8daf-fc1c391a1bf2 --organizationid notnull"
);
writeLn(" bw list items --trash");
writeLn(" bw list folders --search email");
writeLn(" bw list org-members --organizationid 60556c31-e649-4b5d-8daf-fc1c391a1bf2");
writeLn("", true);
}) })
.action(async (object, cmd) => { .action(async (object, cmd) => {
if (!this.validateObject(object, listObjects)) { if (!this.validateObject(object, listObjects)) {
@@ -100,8 +114,14 @@ export class VaultProgram extends Program {
} }
await this.exitIfLocked(); await this.exitIfLocked();
const command = new ListCommand(this.main.cipherService, this.main.folderService, const command = new ListCommand(
this.main.collectionService, this.main.userService, this.main.searchService, this.main.apiService); this.main.cipherService,
this.main.folderService,
this.main.collectionService,
this.main.userService,
this.main.searchService,
this.main.apiService
);
const response = await command.run(object, cmd); const response = await command.run(object, cmd);
this.processResponse(response); this.processResponse(response);
@@ -110,48 +130,52 @@ export class VaultProgram extends Program {
private getCommand(): program.Command { private getCommand(): program.Command {
const getObjects = [ const getObjects = [
'item', "item",
'username', "username",
'password', "password",
'uri', "uri",
'totp', "totp",
'notes', "notes",
'exposed', "exposed",
'attachment', "attachment",
'folder', "folder",
'collection', "collection",
'org-collection', "org-collection",
'organization', "organization",
'template', "template",
'fingerprint', "fingerprint",
'send', "send",
]; ];
return new program.Command('get') return new program.Command("get")
.arguments('<object> <id>') .arguments("<object> <id>")
.description('Get an object from the vault.', { .description("Get an object from the vault.", {
object: 'Valid objects are: ' + getObjects.join(', '), object: "Valid objects are: " + getObjects.join(", "),
id: 'Search term or object\'s globally unique `id`.', id: "Search term or object's globally unique `id`.",
}) })
.option('--itemid <itemid>', 'Attachment\'s item id.') .option("--itemid <itemid>", "Attachment's item id.")
.option('--output <output>', 'Output directory or filename for attachment.') .option("--output <output>", "Output directory or filename for attachment.")
.option('--organizationid <organizationid>', 'Organization id for an organization object.') .option("--organizationid <organizationid>", "Organization id for an organization object.")
.on('--help', () => { .on("--help", () => {
writeLn('\n If raw output is specified and no output filename or directory is given for'); writeLn("\n If raw output is specified and no output filename or directory is given for");
writeLn(' an attachment query, the attachment content is written to stdout.'); writeLn(" an attachment query, the attachment content is written to stdout.");
writeLn(''); writeLn("");
writeLn(' Examples:'); writeLn(" Examples:");
writeLn(''); writeLn("");
writeLn(' bw get item 99ee88d2-6046-4ea7-92c2-acac464b1412'); writeLn(" bw get item 99ee88d2-6046-4ea7-92c2-acac464b1412");
writeLn(' bw get password https://google.com'); writeLn(" bw get password https://google.com");
writeLn(' bw get totp google.com'); writeLn(" bw get totp google.com");
writeLn(' bw get notes google.com'); writeLn(" bw get notes google.com");
writeLn(' bw get exposed yahoo.com'); writeLn(" bw get exposed yahoo.com");
writeLn(' bw get attachment b857igwl1dzrs2 --itemid 99ee88d2-6046-4ea7-92c2-acac464b1412 ' + writeLn(
'--output ./photo.jpg'); " bw get attachment b857igwl1dzrs2 --itemid 99ee88d2-6046-4ea7-92c2-acac464b1412 " +
writeLn(' bw get attachment photo.jpg --itemid 99ee88d2-6046-4ea7-92c2-acac464b1412 --raw'); "--output ./photo.jpg"
writeLn(' bw get folder email'); );
writeLn(' bw get template folder'); writeLn(
writeLn('', true); " bw get attachment photo.jpg --itemid 99ee88d2-6046-4ea7-92c2-acac464b1412 --raw"
);
writeLn(" bw get folder email");
writeLn(" bw get template folder");
writeLn("", true);
}) })
.action(async (object, id, cmd) => { .action(async (object, id, cmd) => {
if (!this.validateObject(object, getObjects)) { if (!this.validateObject(object, getObjects)) {
@@ -159,39 +183,45 @@ export class VaultProgram extends Program {
} }
await this.exitIfLocked(); await this.exitIfLocked();
const command = new GetCommand(this.main.cipherService, this.main.folderService, const command = new GetCommand(
this.main.collectionService, this.main.totpService, this.main.auditService, this.main.cipherService,
this.main.cryptoService, this.main.userService, this.main.searchService, this.main.folderService,
this.main.apiService, this.main.sendService, this.main.environmentService); this.main.collectionService,
this.main.totpService,
this.main.auditService,
this.main.cryptoService,
this.main.userService,
this.main.searchService,
this.main.apiService,
this.main.sendService,
this.main.environmentService
);
const response = await command.run(object, id, cmd); const response = await command.run(object, id, cmd);
this.processResponse(response); this.processResponse(response);
}); });
} }
private createCommand() { private createCommand() {
const createObjects = [ const createObjects = ["item", "attachment", "folder", "org-collection"];
'item', return new program.Command("create")
'attachment', .arguments("<object> [encodedJson]")
'folder', .description("Create an object in the vault.", {
'org-collection', object: "Valid objects are: " + createObjects.join(", "),
]; encodedJson: "Encoded json of the object to create. Can also be piped into stdin.",
return new program.Command('create')
.arguments('<object> [encodedJson]')
.description('Create an object in the vault.', {
object: 'Valid objects are: ' + createObjects.join(', '),
encodedJson: 'Encoded json of the object to create. Can also be piped into stdin.',
}) })
.option('--file <file>', 'Path to file for attachment.') .option("--file <file>", "Path to file for attachment.")
.option('--itemid <itemid>', 'Attachment\'s item id.') .option("--itemid <itemid>", "Attachment's item id.")
.option('--organizationid <organizationid>', 'Organization id for an organization object.') .option("--organizationid <organizationid>", "Organization id for an organization object.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bw create folder eyJuYW1lIjoiTXkgRm9sZGVyIn0K'); writeLn(" bw create folder eyJuYW1lIjoiTXkgRm9sZGVyIn0K");
writeLn(' echo \'eyJuYW1lIjoiTXkgRm9sZGVyIn0K\' | bw create folder'); writeLn(" echo 'eyJuYW1lIjoiTXkgRm9sZGVyIn0K' | bw create folder");
writeLn(' bw create attachment --file ./myfile.csv ' + writeLn(
'--itemid 16b15b89-65b3-4639-ad2a-95052a6d8f66'); " bw create attachment --file ./myfile.csv " +
writeLn('', true); "--itemid 16b15b89-65b3-4639-ad2a-95052a6d8f66"
);
writeLn("", true);
}) })
.action(async (object, encodedJson, cmd) => { .action(async (object, encodedJson, cmd) => {
if (!this.validateObject(object, createObjects)) { if (!this.validateObject(object, createObjects)) {
@@ -199,37 +229,43 @@ export class VaultProgram extends Program {
} }
await this.exitIfLocked(); await this.exitIfLocked();
const command = new CreateCommand(this.main.cipherService, this.main.folderService, const command = new CreateCommand(
this.main.userService, this.main.cryptoService, this.main.apiService); this.main.cipherService,
this.main.folderService,
this.main.userService,
this.main.cryptoService,
this.main.apiService
);
const response = await command.run(object, encodedJson, cmd); const response = await command.run(object, encodedJson, cmd);
this.processResponse(response); this.processResponse(response);
}); });
} }
private editCommand(): program.Command { private editCommand(): program.Command {
const editObjects = [ const editObjects = ["item", "item-collections", "folder", "org-collection"];
'item', return new program.Command("edit")
'item-collections', .arguments("<object> <id> [encodedJson]")
'folder', .description("Edit an object from the vault.", {
'org-collection', object: "Valid objects are: " + editObjects.join(", "),
]; id: "Object's globally unique `id`.",
return new program.Command('edit') encodedJson: "Encoded json of the object to create. Can also be piped into stdin.",
.arguments('<object> <id> [encodedJson]')
.description('Edit an object from the vault.', {
object: 'Valid objects are: ' + editObjects.join(', '),
id: 'Object\'s globally unique `id`.',
encodedJson: 'Encoded json of the object to create. Can also be piped into stdin.',
}) })
.option('--organizationid <organizationid>', 'Organization id for an organization object.') .option("--organizationid <organizationid>", "Organization id for an organization object.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bw edit folder 5cdfbd80-d99f-409b-915b-f4c5d0241b02 eyJuYW1lIjoiTXkgRm9sZGVyMiJ9Cg=='); writeLn(
writeLn(' echo \'eyJuYW1lIjoiTXkgRm9sZGVyMiJ9Cg==\' | ' + " bw edit folder 5cdfbd80-d99f-409b-915b-f4c5d0241b02 eyJuYW1lIjoiTXkgRm9sZGVyMiJ9Cg=="
'bw edit folder 5cdfbd80-d99f-409b-915b-f4c5d0241b02'); );
writeLn(' bw edit item-collections 78307355-fd25-416b-88b8-b33fd0e88c82 ' + writeLn(
'WyI5NzQwNTNkMC0zYjMzLTRiOTgtODg2ZS1mZWNmNWM4ZGJhOTYiXQ=='); " echo 'eyJuYW1lIjoiTXkgRm9sZGVyMiJ9Cg==' | " +
writeLn('', true); "bw edit folder 5cdfbd80-d99f-409b-915b-f4c5d0241b02"
);
writeLn(
" bw edit item-collections 78307355-fd25-416b-88b8-b33fd0e88c82 " +
"WyI5NzQwNTNkMC0zYjMzLTRiOTgtODg2ZS1mZWNmNWM4ZGJhOTYiXQ=="
);
writeLn("", true);
}) })
.action(async (object, id, encodedJson, cmd) => { .action(async (object, id, encodedJson, cmd) => {
if (!this.validateObject(object, editObjects)) { if (!this.validateObject(object, editObjects)) {
@@ -237,37 +273,41 @@ export class VaultProgram extends Program {
} }
await this.exitIfLocked(); await this.exitIfLocked();
const command = new EditCommand(this.main.cipherService, this.main.folderService, const command = new EditCommand(
this.main.cryptoService, this.main.apiService); this.main.cipherService,
this.main.folderService,
this.main.cryptoService,
this.main.apiService
);
const response = await command.run(object, id, encodedJson, cmd); const response = await command.run(object, id, encodedJson, cmd);
this.processResponse(response); this.processResponse(response);
}); });
} }
private deleteCommand(): program.Command { private deleteCommand(): program.Command {
const deleteObjects = [ const deleteObjects = ["item", "attachment", "folder", "org-collection"];
'item', return new program.Command("delete")
'attachment', .arguments("<object> <id>")
'folder', .description("Delete an object from the vault.", {
'org-collection', object: "Valid objects are: " + deleteObjects.join(", "),
]; id: "Object's globally unique `id`.",
return new program.Command('delete')
.arguments('<object> <id>')
.description('Delete an object from the vault.', {
object: 'Valid objects are: ' + deleteObjects.join(', '),
id: 'Object\'s globally unique `id`.',
}) })
.option('--itemid <itemid>', 'Attachment\'s item id.') .option("--itemid <itemid>", "Attachment's item id.")
.option('--organizationid <organizationid>', 'Organization id for an organization object.') .option("--organizationid <organizationid>", "Organization id for an organization object.")
.option('-p, --permanent', 'Permanently deletes the item instead of soft-deleting it (item only).') .option(
.on('--help', () => { "-p, --permanent",
writeLn('\n Examples:'); "Permanently deletes the item instead of soft-deleting it (item only)."
writeLn(''); )
writeLn(' bw delete item 7063feab-4b10-472e-b64c-785e2b870b92'); .on("--help", () => {
writeLn(' bw delete item 89c21cd2-fab0-4f69-8c6e-ab8a0168f69a --permanent'); writeLn("\n Examples:");
writeLn(' bw delete folder 5cdfbd80-d99f-409b-915b-f4c5d0241b02'); writeLn("");
writeLn(' bw delete attachment b857igwl1dzrs2 --itemid 310d5ffd-e9a2-4451-af87-ea054dce0f78'); writeLn(" bw delete item 7063feab-4b10-472e-b64c-785e2b870b92");
writeLn('', true); writeLn(" bw delete item 89c21cd2-fab0-4f69-8c6e-ab8a0168f69a --permanent");
writeLn(" bw delete folder 5cdfbd80-d99f-409b-915b-f4c5d0241b02");
writeLn(
" bw delete attachment b857igwl1dzrs2 --itemid 310d5ffd-e9a2-4451-af87-ea054dce0f78"
);
writeLn("", true);
}) })
.action(async (object, id, cmd) => { .action(async (object, id, cmd) => {
if (!this.validateObject(object, deleteObjects)) { if (!this.validateObject(object, deleteObjects)) {
@@ -275,28 +315,30 @@ export class VaultProgram extends Program {
} }
await this.exitIfLocked(); await this.exitIfLocked();
const command = new DeleteCommand(this.main.cipherService, this.main.folderService, const command = new DeleteCommand(
this.main.userService, this.main.apiService); this.main.cipherService,
this.main.folderService,
this.main.userService,
this.main.apiService
);
const response = await command.run(object, id, cmd); const response = await command.run(object, id, cmd);
this.processResponse(response); this.processResponse(response);
}); });
} }
private restoreCommand(): program.Command { private restoreCommand(): program.Command {
const restoreObjects = [ const restoreObjects = ["item"];
'item', return new program.Command("restore")
]; .arguments("<object> <id>")
return new program.Command('restore') .description("Restores an object from the trash.", {
.arguments('<object> <id>') object: "Valid objects are: " + restoreObjects.join(", "),
.description('Restores an object from the trash.', { id: "Object's globally unique `id`.",
object: 'Valid objects are: ' + restoreObjects.join(', '),
id: 'Object\'s globally unique `id`.',
}) })
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bw restore item 7063feab-4b10-472e-b64c-785e2b870b92'); writeLn(" bw restore item 7063feab-4b10-472e-b64c-785e2b870b92");
writeLn('', true); writeLn("", true);
}) })
.action(async (object, id, cmd) => { .action(async (object, id, cmd) => {
if (!this.validateObject(object, restoreObjects)) { if (!this.validateObject(object, restoreObjects)) {
@@ -312,24 +354,32 @@ export class VaultProgram extends Program {
private shareCommand(commandName: string, deprecated: boolean): program.Command { private shareCommand(commandName: string, deprecated: boolean): program.Command {
return new program.Command(commandName) return new program.Command(commandName)
.arguments('<id> <organizationId> [encodedJson]') .arguments("<id> <organizationId> [encodedJson]")
.description((deprecated ? '--DEPRECATED-- ' : '') + 'Move an item to an organization.', { .description((deprecated ? "--DEPRECATED-- " : "") + "Move an item to an organization.", {
id: 'Object\'s globally unique `id`.', id: "Object's globally unique `id`.",
organizationId: 'Organization\'s globally unique `id`.', organizationId: "Organization's globally unique `id`.",
encodedJson: 'Encoded json of an array of collection ids. Can also be piped into stdin.', encodedJson: "Encoded json of an array of collection ids. Can also be piped into stdin.",
}) })
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bw ' + commandName + ' 4af958ce-96a7-45d9-beed-1e70fabaa27a ' + writeLn(
'6d82949b-b44d-468a-adae-3f3bacb0ea32 WyI5NzQwNTNkMC0zYjMzLTRiOTgtODg2ZS1mZWNmNWM4ZGJhOTYiXQ=='); " bw " +
writeLn(' echo \'["974053d0-3b33-4b98-886e-fecf5c8dba96"]\' | bw encode | ' + commandName +
'bw ' + commandName + ' 4af958ce-96a7-45d9-beed-1e70fabaa27a 6d82949b-b44d-468a-adae-3f3bacb0ea32'); " 4af958ce-96a7-45d9-beed-1e70fabaa27a " +
"6d82949b-b44d-468a-adae-3f3bacb0ea32 WyI5NzQwNTNkMC0zYjMzLTRiOTgtODg2ZS1mZWNmNWM4ZGJhOTYiXQ=="
);
writeLn(
" echo '[\"974053d0-3b33-4b98-886e-fecf5c8dba96\"]' | bw encode | " +
"bw " +
commandName +
" 4af958ce-96a7-45d9-beed-1e70fabaa27a 6d82949b-b44d-468a-adae-3f3bacb0ea32"
);
if (deprecated) { if (deprecated) {
writeLn(''); writeLn("");
writeLn('--DEPRECATED See "bw move" for the current implementation--'); writeLn('--DEPRECATED See "bw move" for the current implementation--');
} }
writeLn('', true); writeLn("", true);
}) })
.action(async (id, organizationId, encodedJson, cmd) => { .action(async (id, organizationId, encodedJson, cmd) => {
await this.exitIfLocked(); await this.exitIfLocked();
@@ -340,22 +390,22 @@ export class VaultProgram extends Program {
} }
private confirmCommand(): program.Command { private confirmCommand(): program.Command {
const confirmObjects = [ const confirmObjects = ["org-member"];
'org-member', return new program.Command("confirm")
]; .arguments("<object> <id>")
return new program.Command('confirm') .description("Confirm an object to the organization.", {
.arguments('<object> <id>') object: "Valid objects are: " + confirmObjects.join(", "),
.description('Confirm an object to the organization.', { id: "Object's globally unique `id`.",
object: 'Valid objects are: ' + confirmObjects.join(', '),
id: 'Object\'s globally unique `id`.',
}) })
.option('--organizationid <organizationid>', 'Organization id for an organization object.') .option("--organizationid <organizationid>", "Organization id for an organization object.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bw confirm org-member 7063feab-4b10-472e-b64c-785e2b870b92 ' + writeLn(
'--organizationid 310d5ffd-e9a2-4451-af87-ea054dce0f78'); " bw confirm org-member 7063feab-4b10-472e-b64c-785e2b870b92 " +
writeLn('', true); "--organizationid 310d5ffd-e9a2-4451-af87-ea054dce0f78"
);
writeLn("", true);
}) })
.action(async (object, id, cmd) => { .action(async (object, id, cmd) => {
if (!this.validateObject(object, confirmObjects)) { if (!this.validateObject(object, confirmObjects)) {
@@ -370,21 +420,23 @@ export class VaultProgram extends Program {
} }
private importCommand(): program.Command { private importCommand(): program.Command {
return new program.Command('import') return new program.Command("import")
.arguments('[format] [input]') .arguments("[format] [input]")
.description('Import vault data from a file.', { .description("Import vault data from a file.", {
format: 'The format of [input]', format: "The format of [input]",
input: 'Filepath to data to import', input: "Filepath to data to import",
}) })
.option('--formats', 'List formats') .option("--formats", "List formats")
.option('--organizationid <organizationid>', 'ID of the organization to import to.') .option("--organizationid <organizationid>", "ID of the organization to import to.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Examples:'); writeLn("\n Examples:");
writeLn(''); writeLn("");
writeLn(' bw import --formats'); writeLn(" bw import --formats");
writeLn(' bw import bitwardencsv ./from/source.csv'); writeLn(" bw import bitwardencsv ./from/source.csv");
writeLn(' bw import keepass2xml keepass_backup.xml'); writeLn(" bw import keepass2xml keepass_backup.xml");
writeLn(' bw import --organizationid cf14adc3-aca5-4573-890a-f6fa231436d9 keepass2xml keepass_backup.xml'); writeLn(
" bw import --organizationid cf14adc3-aca5-4573-890a-f6fa231436d9 keepass2xml keepass_backup.xml"
);
}) })
.action(async (format, filepath, options) => { .action(async (format, filepath, options) => {
await this.exitIfLocked(); await this.exitIfLocked();
@@ -395,37 +447,45 @@ export class VaultProgram extends Program {
} }
private exportCommand(): program.Command { private exportCommand(): program.Command {
return new program.Command('export') return new program.Command("export")
.arguments('[password]') .arguments("[password]")
.description('Export vault data to a CSV or JSON file.', { .description("Export vault data to a CSV or JSON file.", {
password: 'Optional: Your master password.', password: "Optional: Your master password.",
}) })
.option('--output <output>', 'Output directory or filename.') .option("--output <output>", "Output directory or filename.")
.option('--format <format>', 'Export file format.') .option("--format <format>", "Export file format.")
.option('--organizationid <organizationid>', 'Organization id for an organization.') .option("--organizationid <organizationid>", "Organization id for an organization.")
.on('--help', () => { .on("--help", () => {
writeLn('\n Notes:'); writeLn("\n Notes:");
writeLn(''); writeLn("");
writeLn(' Valid formats are `csv`, `json`, `encrypted_json`. Default format is `csv`.'); writeLn(" Valid formats are `csv`, `json`, `encrypted_json`. Default format is `csv`.");
writeLn(''); writeLn("");
writeLn(' If --raw option is specified and no output filename or directory is given, the'); writeLn(
writeLn(' result is written to stdout.'); " If --raw option is specified and no output filename or directory is given, the"
writeLn(''); );
writeLn(' Examples:'); writeLn(" result is written to stdout.");
writeLn(''); writeLn("");
writeLn(' bw export'); writeLn(" Examples:");
writeLn(' bw --raw export'); writeLn("");
writeLn(' bw export myPassword321'); writeLn(" bw export");
writeLn(' bw export myPassword321 --format json'); writeLn(" bw --raw export");
writeLn(' bw export --output ./exp/bw.csv'); writeLn(" bw export myPassword321");
writeLn(' bw export myPassword321 --output bw.json --format json'); writeLn(" bw export myPassword321 --format json");
writeLn(' bw export myPassword321 --organizationid 7063feab-4b10-472e-b64c-785e2b870b92'); writeLn(" bw export --output ./exp/bw.csv");
writeLn('', true); writeLn(" bw export myPassword321 --output bw.json --format json");
writeLn(
" bw export myPassword321 --organizationid 7063feab-4b10-472e-b64c-785e2b870b92"
);
writeLn("", true);
}) })
.action(async (password, options) => { .action(async (password, options) => {
await this.exitIfLocked(); await this.exitIfLocked();
const command = new ExportCommand(this.main.exportService, this.main.policyService, const command = new ExportCommand(
this.main.keyConnectorService, this.main.userVerificationService); this.main.exportService,
this.main.policyService,
this.main.keyConnectorService,
this.main.userVerificationService
);
const response = await command.run(password, options); const response = await command.run(password, options);
this.processResponse(response); this.processResponse(response);
}); });

View File

@@ -11,15 +11,9 @@
"sourceMap": true, "sourceMap": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"jslib-common/*": [ "jslib-common/*": ["jslib/common/src/*"],
"jslib/common/src/*" "jslib-node/*": ["jslib/node/src/*"]
],
"jslib-node/*": [
"jslib/node/src/*"
]
} }
}, },
"include": [ "include": ["src"]
"src"
]
} }

View File

@@ -1,39 +1,17 @@
{ {
"extends": "tslint:recommended", "extends": "tslint:recommended",
"rules": { "rules": {
"align": [ "align": [true, "statements", "members"],
true,
"statements",
"members"
],
"ban-types": { "ban-types": {
"options": [ "options": [
[ ["Object", "Avoid using the `Object` type. Did you mean `object`?"],
"Object", ["Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?"],
"Avoid using the `Object` type. Did you mean `object`?" ["Number", "Avoid using the `Number` type. Did you mean `number`?"],
], ["String", "Avoid using the `String` type. Did you mean `string`?"],
[ ["Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?"]
"Boolean",
"Avoid using the `Boolean` type. Did you mean `boolean`?"
],
[
"Number",
"Avoid using the `Number` type. Did you mean `number`?"
],
[
"String",
"Avoid using the `String` type. Did you mean `string`?"
],
[
"Symbol",
"Avoid using the `Symbol` type. Did you mean `symbol`?"
]
] ]
}, },
"member-access": [ "member-access": [true, "no-public"],
true,
"no-public"
],
"member-ordering": [ "member-ordering": [
true, true,
{ {
@@ -56,15 +34,9 @@
] ]
} }
], ],
"no-empty": [ "no-empty": [true, "allow-empty-catch"],
true,
"allow-empty-catch"
],
"object-literal-sort-keys": false, "object-literal-sort-keys": false,
"object-literal-shorthand": [ "object-literal-shorthand": [true, "never"],
true,
"never"
],
"prefer-for-of": false, "prefer-for-of": false,
"whitespace": [ "whitespace": [
true, true,
@@ -78,7 +50,7 @@
], ],
"max-classes-per-file": false, "max-classes-per-file": false,
"ordered-imports": true, "ordered-imports": true,
"arrow-parens": [ true ], "arrow-parens": [true],
"trailing-comma": [ "trailing-comma": [
true, true,
{ {

View File

@@ -1,68 +1,66 @@
const path = require('path'); const path = require("path");
const webpack = require('webpack'); const webpack = require("webpack");
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require("copy-webpack-plugin");
const nodeExternals = require('webpack-node-externals'); const nodeExternals = require("webpack-node-externals");
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
if (process.env.NODE_ENV == null) { if (process.env.NODE_ENV == null) {
process.env.NODE_ENV = 'development'; process.env.NODE_ENV = "development";
} }
const ENV = process.env.ENV = process.env.NODE_ENV; const ENV = (process.env.ENV = process.env.NODE_ENV);
const moduleRules = [ const moduleRules = [
{ {
test: /\.ts$/, test: /\.ts$/,
enforce: 'pre', enforce: "pre",
loader: 'tslint-loader', loader: "tslint-loader",
}, },
{ {
test: /\.ts$/, test: /\.ts$/,
loaders: ['ts-loader'], loaders: ["ts-loader"],
exclude: path.resolve(__dirname, 'node_modules'), exclude: path.resolve(__dirname, "node_modules"),
}, },
]; ];
const plugins = [ const plugins = [
new CleanWebpackPlugin(), new CleanWebpackPlugin(),
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: [ patterns: [{ from: "./src/locales", to: "locales" }],
{ from: './src/locales', to: 'locales' },
]
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env.BWCLI_ENV': JSON.stringify(ENV), "process.env.BWCLI_ENV": JSON.stringify(ENV),
}), }),
new webpack.BannerPlugin({ new webpack.BannerPlugin({
banner: '#!/usr/bin/env node', banner: "#!/usr/bin/env node",
raw: true raw: true,
}), }),
new webpack.IgnorePlugin(/^encoding$/, /node-fetch/), new webpack.IgnorePlugin(/^encoding$/, /node-fetch/),
]; ];
const config = { const config = {
mode: ENV, mode: ENV,
target: 'node', target: "node",
devtool: ENV === 'development' ? 'eval-source-map' : 'source-map', devtool: ENV === "development" ? "eval-source-map" : "source-map",
node: { node: {
__dirname: false, __dirname: false,
__filename: false, __filename: false,
}, },
entry: { entry: {
'bw': './src/bw.ts', bw: "./src/bw.ts",
}, },
optimization: { optimization: {
minimize: false, minimize: false,
}, },
resolve: { resolve: {
extensions: ['.ts', '.js'], extensions: [".ts", ".js"],
symlinks: false, symlinks: false,
modules: [path.resolve('node_modules')], modules: [path.resolve("node_modules")],
plugins: [new TsconfigPathsPlugin({ configFile: './tsconfig.json' })], plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })],
}, },
output: { output: {
filename: '[name].js', filename: "[name].js",
path: path.resolve(__dirname, 'build'), path: path.resolve(__dirname, "build"),
}, },
module: { rules: moduleRules }, module: { rules: moduleRules },
plugins: plugins, plugins: plugins,