1
0
mirror of https://github.com/bitwarden/cli synced 2025-12-06 04:23:19 +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
- [ ] Bug fix
- [ ] New feature development
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
@@ -6,22 +7,22 @@
- [ ] Other
## Objective
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
## Code 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-->
* **file.ext:** Description of what was changed and why
- **file.ext:** Description of what was changed and why
## Testing requirements
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
## Before you submit
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
- [ ] This change requires a **documentation update** (notify the documentation team)
- [ ] This change has particular **deployment requirements** (notify the DevOps team)

View File

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

6
.vscode/launch.json vendored
View File

@@ -8,11 +8,7 @@
"protocol": "inspector",
"cwd": "${workspaceRoot}",
"program": "${workspaceFolder}/build/bw.js",
"args": [
"login",
"sdfsd@sdfdf.com",
"ddddddd"
]
"args": ["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:
* **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
* **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)
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
- **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
- **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)
- **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
## Contributor Agreement
@@ -22,6 +18,6 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/cli)
## Pull Request Guidelines
* use `npm run lint` and fix any linting suggestions before submitting a pull request
* commit any pull requests against the `master` branch
* include a link to your Community Forums post
- use `npm run lint` and fix any linting suggestions before submitting a pull request
- commit any pull requests against the `master` branch
- 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 fs from 'fs';
import * as jsdom from 'jsdom';
import * as path from 'path';
import * as program from "commander";
import * as fs from "fs";
import * as jsdom from "jsdom";
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 { NodeEnvSecureStorageService } from './services/nodeEnvSecureStorage.service';
import { I18nService } from "./services/i18n.service";
import { NodeEnvSecureStorageService } from "./services/nodeEnvSecureStorage.service";
import { CliPlatformUtilsService } from 'jslib-node/cli/services/cliPlatformUtils.service';
import { ConsoleLogService } from 'jslib-node/cli/services/consoleLog.service';
import { CliPlatformUtilsService } from "jslib-node/cli/services/cliPlatformUtils.service";
import { ConsoleLogService } from "jslib-node/cli/services/consoleLog.service";
import { AppIdService } from 'jslib-common/services/appId.service';
import { AuditService } from 'jslib-common/services/audit.service';
import { CipherService } from 'jslib-common/services/cipher.service';
import { CollectionService } from 'jslib-common/services/collection.service';
import { ConstantsService } from 'jslib-common/services/constants.service';
import { ContainerService } from 'jslib-common/services/container.service';
import { CryptoService } from 'jslib-common/services/crypto.service';
import { EnvironmentService } from 'jslib-common/services/environment.service';
import { ExportService } from 'jslib-common/services/export.service';
import { FileUploadService } from 'jslib-common/services/fileUpload.service';
import { FolderService } from 'jslib-common/services/folder.service';
import { ImportService } from 'jslib-common/services/import.service';
import { KeyConnectorService } from 'jslib-common/services/keyConnector.service';
import { NoopMessagingService } from 'jslib-common/services/noopMessaging.service';
import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service';
import { PolicyService } from 'jslib-common/services/policy.service';
import { SearchService } from 'jslib-common/services/search.service';
import { SendService } from 'jslib-common/services/send.service';
import { SettingsService } from 'jslib-common/services/settings.service';
import { SyncService } from 'jslib-common/services/sync.service';
import { TokenService } from 'jslib-common/services/token.service';
import { TotpService } from 'jslib-common/services/totp.service';
import { UserService } from 'jslib-common/services/user.service';
import { UserVerificationService } from 'jslib-common/services/userVerification.service';
import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.service';
import { LowdbStorageService } from 'jslib-node/services/lowdbStorage.service';
import { NodeApiService } from 'jslib-node/services/nodeApi.service';
import { NodeCryptoFunctionService } from 'jslib-node/services/nodeCryptoFunction.service';
import { AppIdService } from "jslib-common/services/appId.service";
import { AuditService } from "jslib-common/services/audit.service";
import { CipherService } from "jslib-common/services/cipher.service";
import { CollectionService } from "jslib-common/services/collection.service";
import { ConstantsService } from "jslib-common/services/constants.service";
import { ContainerService } from "jslib-common/services/container.service";
import { CryptoService } from "jslib-common/services/crypto.service";
import { EnvironmentService } from "jslib-common/services/environment.service";
import { ExportService } from "jslib-common/services/export.service";
import { FileUploadService } from "jslib-common/services/fileUpload.service";
import { FolderService } from "jslib-common/services/folder.service";
import { ImportService } from "jslib-common/services/import.service";
import { KeyConnectorService } from "jslib-common/services/keyConnector.service";
import { NoopMessagingService } from "jslib-common/services/noopMessaging.service";
import { PasswordGenerationService } from "jslib-common/services/passwordGeneration.service";
import { PolicyService } from "jslib-common/services/policy.service";
import { SearchService } from "jslib-common/services/search.service";
import { SendService } from "jslib-common/services/send.service";
import { SettingsService } from "jslib-common/services/settings.service";
import { SyncService } from "jslib-common/services/sync.service";
import { TokenService } from "jslib-common/services/token.service";
import { TotpService } from "jslib-common/services/totp.service";
import { UserService } from "jslib-common/services/user.service";
import { UserVerificationService } from "jslib-common/services/userVerification.service";
import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service";
import { LowdbStorageService } from "jslib-node/services/lowdbStorage.service";
import { NodeApiService } from "jslib-node/services/nodeApi.service";
import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service";
import { Program } from './program';
import { SendProgram } from './send.program';
import { VaultProgram } from './vault.program';
import { Program } from "./program";
import { SendProgram } from "./send.program";
import { VaultProgram } from "./vault.program";
// Polyfills
(global as any).DOMParser = new jsdom.JSDOM().window.DOMParser;
// tslint:disable-next-line
const packageJson = require('../package.json');
const packageJson = require("../package.json");
export class Main {
messagingService: NoopMessagingService;
@@ -92,82 +92,188 @@ export class Main {
constructor() {
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)) {
p = relativeDataDir;
} else if (process.env.BITWARDENCLI_APPDATA_DIR) {
p = path.resolve(process.env.BITWARDENCLI_APPDATA_DIR);
} else if (process.platform === 'darwin') {
p = path.join(process.env.HOME, 'Library/Application Support/Bitwarden CLI');
} else if (process.platform === 'win32') {
p = path.join(process.env.APPDATA, 'Bitwarden CLI');
} else if (process.platform === "darwin") {
p = path.join(process.env.HOME, "Library/Application Support/Bitwarden CLI");
} else if (process.platform === "win32") {
p = path.join(process.env.APPDATA, "Bitwarden CLI");
} 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 {
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.platformUtilsService = new CliPlatformUtilsService('cli', packageJson);
this.logService = new ConsoleLogService(this.platformUtilsService.isDev(),
level => process.env.BITWARDENCLI_DEBUG !== 'true' && level <= LogLevelType.Info);
this.i18nService = new I18nService("en", "./locales");
this.platformUtilsService = new CliPlatformUtilsService("cli", packageJson);
this.logService = new ConsoleLogService(
this.platformUtilsService.isDev(),
(level) => process.env.BITWARDENCLI_DEBUG !== "true" && level <= LogLevelType.Info
);
this.cryptoFunctionService = new NodeCryptoFunctionService();
this.storageService = new LowdbStorageService(this.logService, null, p, true);
this.secureStorageService = new NodeEnvSecureStorageService(this.storageService, this.logService,
() => this.cryptoService);
this.cryptoService = new CryptoService(this.storageService, this.secureStorageService,
this.cryptoFunctionService, this.platformUtilsService, this.logService);
this.secureStorageService = new NodeEnvSecureStorageService(
this.storageService,
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.tokenService = new TokenService(this.storageService);
this.messagingService = new NoopMessagingService();
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(),
'Bitwarden_CLI/' + this.platformUtilsService.getApplicationVersionSync() +
' (' + this.platformUtilsService.getDeviceString().toUpperCase() + ')', (clientId, clientSecret) =>
this.authService.logInApiKey(clientId, clientSecret));
"Bitwarden_CLI/" +
this.platformUtilsService.getApplicationVersionSync() +
" (" +
this.platformUtilsService.getDeviceString().toUpperCase() +
")",
(clientId, clientSecret) => this.authService.logInApiKey(clientId, clientSecret)
);
this.userService = new UserService(this.tokenService, this.storageService);
this.containerService = new ContainerService(this.cryptoService);
this.settingsService = new SettingsService(this.userService, this.storageService);
this.fileUploadService = new FileUploadService(this.logService, this.apiService);
this.cipherService = new CipherService(this.cryptoService, this.userService, this.settingsService,
this.apiService, 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.cipherService = new CipherService(
this.cryptoService,
this.userService,
this.settingsService,
this.apiService,
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.policyService = new PolicyService(this.userService, this.storageService, this.apiService);
this.sendService = new SendService(this.cryptoService, this.userService, this.apiService, this.fileUploadService,
this.storageService, this.i18nService, this.cryptoFunctionService);
this.keyConnectorService = new KeyConnectorService(this.storageService, this.userService, this.cryptoService,
this.apiService, this.tokenService, this.logService);
this.vaultTimeoutService = new VaultTimeoutService(this.cipherService, this.folderService,
this.collectionService, this.cryptoService, this.platformUtilsService, 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.sendService = new SendService(
this.cryptoService,
this.userService,
this.apiService,
this.fileUploadService,
this.storageService,
this.i18nService,
this.cryptoFunctionService
);
this.keyConnectorService = new KeyConnectorService(
this.storageService,
this.userService,
this.cryptoService,
this.apiService,
this.tokenService,
this.logService
);
this.vaultTimeoutService = new VaultTimeoutService(
this.cipherService,
this.folderService,
this.collectionService,
this.cryptoService,
this.platformUtilsService,
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.program = new Program(this);
this.vaultProgram = new VaultProgram(this);
this.sendProgram = new SendProgram(this);
this.userVerificationService = new UserVerificationService(this.cryptoService, this.i18nService,
this.apiService);
this.userVerificationService = new UserVerificationService(
this.cryptoService,
this.i18nService,
this.apiService
);
}
async run() {
@@ -177,13 +283,11 @@ export class Main {
await this.vaultProgram.register();
await this.sendProgram.register();
program
.parse(process.argv);
program.parse(process.argv);
if (process.argv.slice(2).length === 0) {
program.outputHelp();
}
}
async logout() {
@@ -217,7 +321,9 @@ export class Main {
await this.i18nService.init(locale);
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();
if (installedVersion == null || installedVersion !== 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 { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
interface IOption {
long?: string;
@@ -16,24 +16,24 @@ interface ICommand {
_description: string;
}
const validShells = ['zsh'];
const validShells = ["zsh"];
export class CompletionCommand {
async run(options: program.OptionValues) {
const shell: typeof validShells[number] = options.shell;
if (!shell) {
return Response.badRequest('`shell` was not provided.');
return Response.badRequest("`shell` was not provided.");
}
if (!validShells.includes(shell)) {
return Response.badRequest('Unsupported shell.');
return Response.badRequest("Unsupported shell.");
}
let content = '';
let content = "";
if (shell === 'zsh') {
content = this.zshCompletion('bw', program as any as ICommand).render();
if (shell === "zsh") {
content = this.zshCompletion("bw", program as any as ICommand).render();
}
const res = new MessageResponse(content, null);
@@ -45,9 +45,9 @@ export class CompletionCommand {
render: () => {
return [
`#compdef _${rootName} ${rootName}`,
'',
"",
this.renderCommandBlock(rootName, rootCommand),
].join('\n');
].join("\n");
},
};
}
@@ -60,18 +60,23 @@ export class CompletionCommand {
const args = options
.map(({ long, short, description }) => {
const aliases = [short, long].filter(Boolean);
const opts = aliases.join(',');
const opts = aliases.join(",");
const desc = `[${description.replace(`'`, `'"'"'`)}]`;
return aliases.length > 1 ? `'(${aliases.join(' ')})'{${opts}}'${desc}'` : `'${opts}${desc}'`;
}).concat(`'(-h --help)'{-h,--help}'[output usage information]'`,
return aliases.length > 1
? `'(${aliases.join(" ")})'{${opts}}'${desc}'`
: `'${opts}${desc}'`;
})
.concat(
`'(-h --help)'{-h,--help}'[output usage information]'`,
hasCommands ? '"1: :->cmnds"' : null,
'"*::arg:->args"',
).filter(Boolean);
'"*::arg:->args"'
)
.filter(Boolean);
const commandBlockFunctionParts = [];
if (hasCommands) {
commandBlockFunctionParts.push('local -a commands');
commandBlockFunctionParts.push("local -a commands");
}
if (hasOptions) {
@@ -83,28 +88,32 @@ export class CompletionCommand {
`case $state in
cmnds)
commands=(
${commands.map(({ _name, _description }) => `"${_name}:${_description}"`).join('\n ')}
${commands
.map(({ _name, _description }) => `"${_name}:${_description}"`)
.join("\n ")}
)
_describe "command" commands
;;
esac
case "$words[1]" in
${commands.map(({ _name }) => [`${_name})`, `_${name}_${_name}`, ';;'].join('\n ')).join('\n ')}
esac`,
${commands
.map(({ _name }) => [`${_name})`, `_${name}_${_name}`, ";;"].join("\n "))
.join("\n ")}
esac`
);
}
const commandBlocParts = [
`function _${name} {\n ${commandBlockFunctionParts.join('\n\n ')}\n}`,
`function _${name} {\n ${commandBlockFunctionParts.join("\n\n ")}\n}`,
];
if (hasCommands) {
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,10 +1,10 @@
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 { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse';
import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
export class ConfigCommand {
constructor(private environmentService: EnvironmentService) {}
@@ -12,24 +12,32 @@ export class ConfigCommand {
async run(setting: string, value: string, options: program.OptionValues): Promise<Response> {
setting = setting.toLowerCase();
switch (setting) {
case 'server':
case "server":
return await this.getOrSetServer(value, options);
default:
return Response.badRequest('Unknown setting.');
return Response.badRequest("Unknown setting.");
}
}
private async getOrSetServer(url: string, options: program.OptionValues): Promise<Response> {
if ((url == null || url.trim() === '') &&
!options.webVault && !options.api && !options.identity && !options.icons && !options.notifications && !options.events) {
if (
(url == null || url.trim() === "") &&
!options.webVault &&
!options.api &&
!options.identity &&
!options.icons &&
!options.notifications &&
!options.events
) {
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);
}
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({
base: url,
webVault: options.webVault || null,
@@ -40,7 +48,7 @@ export class ConfigCommand {
events: options.events || 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);
}
}

View File

@@ -1,13 +1,13 @@
import * as program from 'commander';
import * as program from "commander";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { ApiService } from "jslib-common/abstractions/api.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 {
constructor(private apiService: ApiService, private cryptoService: CryptoService) {}
@@ -18,31 +18,31 @@ export class ConfirmCommand {
}
switch (object.toLowerCase()) {
case 'org-member':
case "org-member":
return await this.confirmOrganizationMember(id, cmd);
default:
return Response.badRequest('Unknown object.');
return Response.badRequest("Unknown object.");
}
}
private async confirmOrganizationMember(id: string, options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') {
return Response.badRequest('--organizationid <organizationid> required.');
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
}
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)) {
return Response.error('`' + options.organizationid + '` is not a GUID.');
return Response.error("`" + options.organizationid + "` is not a GUID.");
}
try {
const orgKey = await this.cryptoService.getOrgKey(options.organizationid);
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);
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 publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);

View File

@@ -1,67 +1,71 @@
import * as program from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import * as program from "commander";
import * as fs from "fs";
import * as path from "path";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { UserService } from "jslib-common/abstractions/user.service";
import { Cipher } from 'jslib-common/models/export/cipher';
import { Collection } from 'jslib-common/models/export/collection';
import { Folder } from 'jslib-common/models/export/folder';
import { Cipher } from "jslib-common/models/export/cipher";
import { Collection } from "jslib-common/models/export/collection";
import { Folder } from "jslib-common/models/export/folder";
import { CollectionRequest } from 'jslib-common/models/request/collectionRequest';
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest';
import { CollectionRequest } from "jslib-common/models/request/collectionRequest";
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 { FolderResponse } from '../models/response/folderResponse';
import { OrganizationCollectionResponse } from '../models/response/organizationCollectionResponse';
import { CipherResponse } from "../models/response/cipherResponse";
import { FolderResponse } from "../models/response/folderResponse";
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 {
constructor(private cipherService: CipherService, private folderService: FolderService,
private userService: UserService, private cryptoService: CryptoService,
private apiService: ApiService) { }
constructor(
private cipherService: CipherService,
private folderService: FolderService,
private userService: UserService,
private cryptoService: CryptoService,
private apiService: ApiService
) {}
async run(object: string, requestJson: string, cmd: program.Command): Promise<Response> {
let req: any = null;
if (object !== 'attachment') {
if (requestJson == null || requestJson === '') {
if (object !== "attachment") {
if (requestJson == null || requestJson === "") {
requestJson = await CliUtils.readStdin();
}
if (requestJson == null || requestJson === '') {
return Response.badRequest('`requestJson` was not provided.');
if (requestJson == null || requestJson === "") {
return Response.badRequest("`requestJson` was not provided.");
}
try {
const reqJson = Buffer.from(requestJson, 'base64').toString();
const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson);
} catch (e) {
return Response.badRequest('Error parsing the encoded request data.');
return Response.badRequest("Error parsing the encoded request data.");
}
}
switch (object.toLowerCase()) {
case 'item':
case "item":
return await this.createCipher(req);
case 'attachment':
case "attachment":
return await this.createAttachment(cmd);
case 'folder':
case "folder":
return await this.createFolder(req);
case 'org-collection':
case "org-collection":
return await this.createOrganizationCollection(req, cmd);
default:
return Response.badRequest('Unknown object.');
return Response.badRequest("Unknown object.");
}
}
@@ -79,15 +83,15 @@ export class CreateCommand {
}
private async createAttachment(options: program.OptionValues) {
if (options.itemid == null || options.itemid === '') {
return Response.badRequest('--itemid <itemid> required.');
if (options.itemid == null || options.itemid === "") {
return Response.badRequest("--itemid <itemid> required.");
}
if (options.file == null || options.file === '') {
return Response.badRequest('--file <file> required.');
if (options.file == null || options.file === "") {
return Response.badRequest("--file <file> required.");
}
const filePath = path.resolve(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();
@@ -97,19 +101,24 @@ export class CreateCommand {
}
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();
if (encKey == null) {
return Response.error('You must update your encryption key before you can use this feature. ' +
'See https://help.bitwarden.com/article/update-encryption-key/');
return Response.error(
"You must update your encryption key before you can use this feature. " +
"See https://help.bitwarden.com/article/update-encryption-key/"
);
}
try {
const fileBuf = fs.readFileSync(filePath);
await this.cipherService.saveAttachmentRawWithServer(cipher, path.basename(filePath),
new Uint8Array(fileBuf).buffer);
await this.cipherService.saveAttachmentRawWithServer(
cipher,
path.basename(filePath),
new Uint8Array(fileBuf).buffer
);
const updatedCipher = await this.cipherService.get(cipher.id);
const decCipher = await updatedCipher.decrypt();
const res = new CipherResponse(decCipher);
@@ -132,24 +141,29 @@ export class CreateCommand {
}
}
private async createOrganizationCollection(req: OrganizationCollectionRequest, options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') {
return Response.badRequest('--organizationid <organizationid> required.');
private async createOrganizationCollection(
req: OrganizationCollectionRequest,
options: program.OptionValues
) {
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
}
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) {
return Response.error('--organizationid <organizationid> does not match request object.');
return Response.error("--organizationid <organizationid> does not match request object.");
}
try {
const orgKey = await this.cryptoService.getOrgKey(req.organizationId);
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 :
req.groups.map(g => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords));
const groups =
req.groups == null
? null
: req.groups.map((g) => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords));
const request = new CollectionRequest();
request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString;
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 { CipherService } from 'jslib-common/abstractions/cipher.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { FolderService } from "jslib-common/abstractions/folder.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 {
constructor(private cipherService: CipherService, private folderService: FolderService,
private userService: UserService, private apiService: ApiService) { }
constructor(
private cipherService: CipherService,
private folderService: FolderService,
private userService: UserService,
private apiService: ApiService
) {}
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
if (id != null) {
@@ -19,16 +23,16 @@ export class DeleteCommand {
}
switch (object.toLowerCase()) {
case 'item':
case "item":
return await this.deleteCipher(id, cmd);
case 'attachment':
case "attachment":
return await this.deleteAttachment(id, cmd);
case 'folder':
case "folder":
return await this.deleteFolder(id);
case 'org-collection':
case "org-collection":
return await this.deleteOrganizationCollection(id, cmd);
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) {
if (options.itemid == null || options.itemid === '') {
return Response.badRequest('--itemid <itemid> required.');
if (options.itemid == null || options.itemid === "") {
return Response.badRequest("--itemid <itemid> required.");
}
const itemId = options.itemid.toLowerCase();
@@ -62,16 +66,16 @@ export class DeleteCommand {
}
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) {
return Response.error('Attachment `' + id + '` was not found.');
return Response.error("Attachment `" + id + "` was not found.");
}
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 {
@@ -97,14 +101,14 @@ export class DeleteCommand {
}
private async deleteOrganizationCollection(id: string, options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') {
return Response.badRequest('--organizationid <organizationid> required.');
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
}
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)) {
return Response.error('`' + options.organizationid + '` is not a GUID.');
return Response.error("`" + options.organizationid + "` is not a GUID.");
}
try {
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 {
constructor(protected cryptoService: CryptoService) {}
protected async saveAttachmentToFile(url: string, key: SymmetricCryptoKey, fileName: string, output?: string) {
const response = await fet.default(new fet.Request(url, { headers: { cache: 'no-cache' } }));
protected async saveAttachmentToFile(
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) {
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 {
@@ -22,10 +29,10 @@ export abstract class DownloadCommand {
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
return await CliUtils.saveResultToFile(Buffer.from(decBuf), output, fileName);
} catch (e) {
if (typeof (e) === 'string') {
if (typeof e === "string") {
return Response.error(e);
} 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 { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { Cipher } from 'jslib-common/models/export/cipher';
import { Collection } from 'jslib-common/models/export/collection';
import { Folder } from 'jslib-common/models/export/folder';
import { Cipher } from "jslib-common/models/export/cipher";
import { Collection } from "jslib-common/models/export/collection";
import { Folder } from "jslib-common/models/export/folder";
import { CollectionRequest } from 'jslib-common/models/request/collectionRequest';
import { SelectionReadOnlyRequest } from 'jslib-common/models/request/selectionReadOnlyRequest';
import { CollectionRequest } from "jslib-common/models/request/collectionRequest";
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 { FolderResponse } from '../models/response/folderResponse';
import { OrganizationCollectionResponse } from '../models/response/organizationCollectionResponse';
import { CipherResponse } from "../models/response/cipherResponse";
import { FolderResponse } from "../models/response/folderResponse";
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 {
constructor(private cipherService: CipherService, private folderService: FolderService,
private cryptoService: CryptoService, private apiService: ApiService) { }
constructor(
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> {
if (requestJson == null || requestJson === '') {
async run(
object: string,
id: string,
requestJson: string,
cmd: program.Command
): Promise<Response> {
if (requestJson == null || requestJson === "") {
requestJson = await CliUtils.readStdin();
}
if (requestJson == null || requestJson === '') {
return Response.badRequest('`requestJson` was not provided.');
if (requestJson == null || requestJson === "") {
return Response.badRequest("`requestJson` was not provided.");
}
let req: any = null;
try {
const reqJson = Buffer.from(requestJson, 'base64').toString();
const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson);
} catch (e) {
return Response.badRequest('Error parsing the encoded request data.');
return Response.badRequest("Error parsing the encoded request data.");
}
if (id != null) {
@@ -50,16 +59,16 @@ export class EditCommand {
}
switch (object.toLowerCase()) {
case 'item':
case "item":
return await this.editCipher(id, req);
case 'item-collections':
case "item-collections":
return await this.editCipherCollections(id, req);
case 'folder':
case "folder":
return await this.editFolder(id, req);
case 'org-collection':
case "org-collection":
return await this.editOrganizationCollection(id, req, cmd);
default:
return Response.badRequest('Unknown object.');
return Response.badRequest("Unknown object.");
}
}
@@ -71,7 +80,9 @@ export class EditCommand {
let cipherView = await cipher.decrypt();
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);
const encCipher = await this.cipherService.encrypt(cipherView);
@@ -92,7 +103,9 @@ export class EditCommand {
return Response.notFound();
}
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;
@@ -127,27 +140,33 @@ export class EditCommand {
}
}
private async editOrganizationCollection(id: string, req: OrganizationCollectionRequest, options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') {
return Response.badRequest('--organizationid <organizationid> required.');
private async editOrganizationCollection(
id: string,
req: OrganizationCollectionRequest,
options: program.OptionValues
) {
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
}
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)) {
return Response.error('`' + options.organizationid + '` is not a GUID.');
return Response.error("`" + options.organizationid + "` is not a GUID.");
}
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 {
const orgKey = await this.cryptoService.getOrgKey(req.organizationId);
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 :
req.groups.map(g => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords));
const groups =
req.groups == null
? null
: req.groups.map((g) => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords));
const request = new CollectionRequest();
request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString;
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 { StringResponse } from 'jslib-node/cli/models/response/stringResponse';
import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
import { CliUtils } from '../utils';
import { CliUtils } from "../utils";
export class EncodeCommand {
async run(): Promise<Response> {
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 b64 = Buffer.from(input, 'utf8').toString('base64');
const b64 = Buffer.from(input, "utf8").toString("base64");
const res = new StringResponse(b64);
return Response.success(res);
}

View File

@@ -1,39 +1,47 @@
import * as program from 'commander';
import * as inquirer from 'inquirer';
import * as program from "commander";
import * as inquirer from "inquirer";
import { ExportService } from 'jslib-common/abstractions/export.service';
import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service';
import { PolicyService } from 'jslib-common/abstractions/policy.service';
import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service';
import { ExportService } from "jslib-common/abstractions/export.service";
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
import { PolicyService } from "jslib-common/abstractions/policy.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 { VerificationType } from 'jslib-common/enums/verificationType';
import { PolicyType } from "jslib-common/enums/policyType";
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 {
constructor(private exportService: ExportService, private policyService: PolicyService,
private keyConnectorService: KeyConnectorService, private userVerificationService: UserVerificationService) { }
constructor(
private exportService: ExportService,
private policyService: PolicyService,
private keyConnectorService: KeyConnectorService,
private userVerificationService: UserVerificationService
) {}
async run(password: string, options: program.OptionValues): Promise<Response> {
if (options.organizationid == null &&
await this.policyService.policyAppliesToUser(PolicyType.DisablePersonalVaultExport)) {
if (
options.organizationid == null &&
(await this.policyService.policyAppliesToUser(PolicyType.DisablePersonalVaultExport))
) {
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) {
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 {
await this.keyConnectorService.getUsesKeyConnector()
(await this.keyConnectorService.getUsesKeyConnector())
? await this.verifyOTP()
: await this.verifyMasterPassword(password);
} catch (e) {
@@ -41,26 +49,31 @@ export class ExportCommand {
}
let format = options.format;
if (format !== 'encrypted_json' && format !== 'json') {
format = 'csv';
if (format !== "encrypted_json" && format !== "json") {
format = "csv";
}
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;
try {
exportContent = options.organizationid != null ?
await this.exportService.getOrganizationExport(options.organizationid, format) :
await this.exportService.getExport(format);
exportContent =
options.organizationid != null
? await this.exportService.getOrganizationExport(options.organizationid, format)
: await this.exportService.getExport(format);
} catch (e) {
return Response.error(e);
}
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 {
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);
} catch (e) {
return Response.error(e.toString());
@@ -68,23 +81,25 @@ export class ExportCommand {
}
private getFileName(format: string, prefix?: string) {
if (format === 'encrypted_json') {
if (format === "encrypted_json") {
if (prefix == null) {
prefix = 'encrypted';
prefix = "encrypted";
} else {
prefix = 'encrypted_' + prefix;
prefix = "encrypted_" + prefix;
}
format = 'json';
format = "json";
}
return this.exportService.getFileName(prefix, format);
}
private async verifyMasterPassword(password: string) {
if (password == null || password === '') {
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
type: 'password',
name: 'password',
message: 'Master password:',
if (password == null || password === "") {
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "password",
name: "password",
message: "Master password:",
});
password = answer.password;
}
@@ -97,10 +112,12 @@ export class ExportCommand {
private async verifyOTP() {
await this.userVerificationService.requestOTP();
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
type: 'password',
name: 'otp',
message: 'A verification code has been emailed to you.\n Verification code:',
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "password",
name: "otp",
message: "A verification code has been emailed to you.\n Verification code:",
});
await this.userVerificationService.verifyUser({

View File

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

View File

@@ -1,11 +1,11 @@
import * as program from 'commander';
import { ImportService } from 'jslib-common/abstractions/import.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import * as program from "commander";
import { ImportService } from "jslib-common/abstractions/import.service";
import { UserService } from "jslib-common/abstractions/user.service";
import { Response } from 'jslib-node/cli/models/response';
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { CliUtils } from '../utils';
import { CliUtils } from "../utils";
export class ImportCommand {
constructor(private importService: ImportService, private userService: UserService) {}
@@ -16,11 +16,15 @@ export class ImportCommand {
const organization = await this.userService.getOrganization(organizationId);
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) {
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) {
if (format == null || format === '') {
return Response.badRequest('`format` was not provided.');
if (format == null || format === "") {
return Response.badRequest("`format` was not provided.");
}
if (filepath == null || filepath === '') {
return Response.badRequest('`filepath` was not provided.');
if (filepath == null || filepath === "") {
return Response.badRequest("`filepath` was not provided.");
}
const importer = await this.importService.getImporter(format, organizationId);
if (importer === null) {
return Response.badRequest('Proper importer type required.');
return Response.badRequest("Proper importer type required.");
}
try {
const contents = await CliUtils.readFile(filepath);
if (contents === null || contents === '') {
return Response.badRequest('Import file was empty.');
if (contents === null || contents === "") {
return Response.badRequest("Import file was empty.");
}
const err = await this.importService.import(importer, contents, organizationId);
if (err != null) {
return Response.badRequest(err.message);
}
const res = new MessageResponse('Imported ' + filepath, null);
const res = new MessageResponse("Imported " + filepath, null);
return Response.success(res);
} catch (err) {
return Response.badRequest(err);
@@ -62,10 +66,14 @@ export class ImportCommand {
}
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;
}).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;
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 { CipherService } from 'jslib-common/abstractions/cipher.service';
import { CollectionService } from 'jslib-common/abstractions/collection.service';
import { FolderService } from 'jslib-common/abstractions/folder.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { CollectionService } from "jslib-common/abstractions/collection.service";
import { FolderService } from "jslib-common/abstractions/folder.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { UserService } from "jslib-common/abstractions/user.service";
import {
CollectionDetailsResponse as ApiCollectionDetailsResponse,
CollectionResponse as ApiCollectionResponse,
} from 'jslib-common/models/response/collectionResponse';
import { ListResponse as ApiListResponse } from 'jslib-common/models/response/listResponse';
} from "jslib-common/models/response/collectionResponse";
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 { ListResponse } from 'jslib-node/cli/models/response/listResponse';
import { Response } from "jslib-node/cli/models/response";
import { ListResponse } from "jslib-node/cli/models/response/listResponse";
import { CipherResponse } from '../models/response/cipherResponse';
import { CollectionResponse } from '../models/response/collectionResponse';
import { FolderResponse } from '../models/response/folderResponse';
import { OrganizationResponse } from '../models/response/organizationResponse';
import { OrganizationUserResponse } from '../models/response/organizationUserResponse';
import { CipherResponse } from "../models/response/cipherResponse";
import { CollectionResponse } from "../models/response/collectionResponse";
import { FolderResponse } from "../models/response/folderResponse";
import { OrganizationResponse } from "../models/response/organizationResponse";
import { OrganizationUserResponse } from "../models/response/organizationUserResponse";
import { CliUtils } from '../utils';
import { CliUtils } from "../utils";
import { Utils } from 'jslib-common/misc/utils';
import { Utils } from "jslib-common/misc/utils";
export class ListCommand {
constructor(private cipherService: CipherService, private folderService: FolderService,
private collectionService: CollectionService, private userService: UserService,
private searchService: SearchService, private apiService: ApiService) { }
constructor(
private cipherService: CipherService,
private folderService: FolderService,
private collectionService: CollectionService,
private userService: UserService,
private searchService: SearchService,
private apiService: ApiService
) {}
async run(object: string, cmd: program.Command): Promise<Response> {
switch (object.toLowerCase()) {
case 'items':
case "items":
return await this.listCiphers(cmd);
case 'folders':
case "folders":
return await this.listFolders(cmd);
case 'collections':
case "collections":
return await this.listCollections(cmd);
case 'org-collections':
case "org-collections":
return await this.listOrganizationCollections(cmd);
case 'org-members':
case "org-members":
return await this.listOrganizationMembers(cmd);
case 'organizations':
case "organizations":
return await this.listOrganizations(cmd);
default:
return Response.badRequest('Unknown object.');
return Response.badRequest("Unknown object.");
}
}
private async listCiphers(options: program.OptionValues) {
let ciphers: CipherView[];
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);
} else {
ciphers = await this.cipherService.getAllDecrypted();
}
if (options.folderid != null || options.collectionid != null || options.organizationid != null) {
ciphers = ciphers.filter(c => {
if (
options.folderid != null ||
options.collectionid != null ||
options.organizationid != null
) {
ciphers = ciphers.filter((c) => {
if (options.trash !== c.isDeleted) {
return false;
}
if (options.folderid != null) {
if (options.folderid === 'notnull' && c.folderId != null) {
if (options.folderid === "notnull" && c.folderId != null) {
return true;
}
const folderId = options.folderid === 'null' ? null : options.folderid;
const folderId = options.folderid === "null" ? null : options.folderid;
if (folderId === c.folderId) {
return true;
}
}
if (options.organizationid != null) {
if (options.organizationid === 'notnull' && c.organizationId != null) {
if (options.organizationid === "notnull" && c.organizationId != null) {
return true;
}
const organizationId = options.organizationid === 'null' ? null : options.organizationid;
const organizationId = options.organizationid === "null" ? null : options.organizationid;
if (organizationId === c.organizationId) {
return true;
}
}
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;
}
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)) {
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 false;
});
} else if (options.search == null || options.search.trim() === '') {
ciphers = ciphers.filter(c => options.trash === c.isDeleted);
} else if (options.search == null || options.search.trim() === "") {
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);
}
const res = new ListResponse(ciphers.map(o => new CipherResponse(o)));
const res = new ListResponse(ciphers.map((o) => new CipherResponse(o)));
return Response.success(res);
}
private async listFolders(options: program.OptionValues) {
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);
}
const res = new ListResponse(folders.map(o => new FolderResponse(o)));
const res = new ListResponse(folders.map((o) => new FolderResponse(o)));
return Response.success(res);
}
@@ -131,7 +148,7 @@ export class ListCommand {
let collections = await this.collectionService.getAllDecrypted();
if (options.organizationid != null) {
collections = collections.filter(c => {
collections = collections.filter((c) => {
if (options.organizationid === c.organizationId) {
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);
}
const res = new ListResponse(collections.map(o => new CollectionResponse(o)));
const res = new ListResponse(collections.map((o) => new CollectionResponse(o)));
return Response.success(res);
}
private async listOrganizationCollections(options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') {
return Response.badRequest('--organizationid <organizationid> required.');
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
}
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);
if (organization == null) {
return Response.error('Organization not found.');
return Response.error("Organization not found.");
}
try {
@@ -166,13 +183,14 @@ export class ListCommand {
} else {
response = await this.apiService.getUserCollections();
}
const collections = response.data.filter(c => c.organizationId === options.organizationid).map(r =>
new Collection(new CollectionData(r as ApiCollectionDetailsResponse)));
const collections = response.data
.filter((c) => c.organizationId === options.organizationid)
.map((r) => new Collection(new CollectionData(r as ApiCollectionDetailsResponse)));
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);
}
const res = new ListResponse(decCollections.map(o => new CollectionResponse(o)));
const res = new ListResponse(decCollections.map((o) => new CollectionResponse(o)));
return Response.success(res);
} catch (e) {
return Response.error(e);
@@ -180,20 +198,21 @@ export class ListCommand {
}
private async listOrganizationMembers(options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') {
return Response.badRequest('--organizationid <organizationid> required.');
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
}
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);
if (organization == null) {
return Response.error('Organization not found.');
return Response.error("Organization not found.");
}
try {
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();
u.email = r.email;
u.name = r.name;
@@ -202,7 +221,8 @@ export class ListCommand {
u.type = r.type;
u.twoFactorEnabled = r.twoFactorEnabled;
return u;
}));
})
);
return Response.success(res);
} catch (e) {
return Response.error(e);
@@ -212,11 +232,11 @@ export class ListCommand {
private async listOrganizations(options: program.OptionValues) {
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);
}
const res = new ListResponse(organizations.map(o => new OrganizationResponse(o)));
const res = new ListResponse(organizations.map((o) => new OrganizationResponse(o)));
return Response.success(res);
}
}

View File

@@ -1,9 +1,9 @@
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 { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
export class LockCommand {
constructor(private vaultTimeoutService: VaultTimeoutService) {}
@@ -11,7 +11,7 @@ export class LockCommand {
async run(cmd: program.Command) {
await this.vaultTimeoutService.lock();
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);
}
}

View File

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

View File

@@ -1,8 +1,8 @@
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 {
constructor(private cipherService: CipherService) {}
@@ -13,10 +13,10 @@ export class RestoreCommand {
}
switch (object.toLowerCase()) {
case 'item':
case "item":
return await this.restoreCipher(id, cmd);
default:
return Response.badRequest('Unknown object.');
return Response.badRequest("Unknown object.");
}
}
@@ -26,7 +26,7 @@ export class RestoreCommand {
return Response.notFound();
}
if (cipher.deletedDate == null) {
return Response.badRequest('Cipher is not in trash.');
return Response.badRequest("Cipher is not in trash.");
}
try {

View File

@@ -1,55 +1,61 @@
import * as program from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import * as program from "commander";
import * as fs from "fs";
import * as path from "path";
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SendService } from "jslib-common/abstractions/send.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 { StringResponse } from 'jslib-node/cli/models/response/stringResponse';
import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
import { SendResponse } from '../../models/response/sendResponse';
import { SendTextResponse } from '../../models/response/sendTextResponse';
import { SendResponse } from "../../models/response/sendResponse";
import { SendTextResponse } from "../../models/response/sendTextResponse";
import { CliUtils } from '../../utils';
import { CliUtils } from "../../utils";
export class SendCreateCommand {
constructor(private sendService: SendService, private userService: UserService,
private environmentService: EnvironmentService) { }
constructor(
private sendService: SendService,
private userService: UserService,
private environmentService: EnvironmentService
) {}
async run(requestJson: string, options: program.OptionValues) {
let req: any = null;
if (requestJson == null || requestJson === '') {
if (requestJson == null || requestJson === "") {
requestJson = await CliUtils.readStdin();
}
if (requestJson == null || requestJson === '') {
return Response.badRequest('`requestJson` was not provided.');
if (requestJson == null || requestJson === "") {
return Response.badRequest("`requestJson` was not provided.");
}
try {
const reqJson = Buffer.from(requestJson, 'base64').toString();
const reqJson = Buffer.from(requestJson, "base64").toString();
req = SendResponse.fromJson(reqJson);
if (req == null) {
throw new Error('Null request');
throw new Error("Null request");
}
} 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()) ||
new Date(req.deletionDate) <= new Date()) {
return Response.badRequest('Must specify a valid deletion date after the current time');
if (
req.deletionDate == null ||
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())) {
return Response.badRequest('Unable to parse expirationDate: ' + req.expirationDate);
return Response.badRequest("Unable to parse expirationDate: " + req.expirationDate);
}
return this.createSend(req, options);
@@ -68,25 +74,31 @@ export class SendCreateCommand {
switch (req.type) {
case SendType.File:
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) {
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);
break;
case SendType.Text:
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.text = text;
req.text.hidden = hidden;
break;
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 {
@@ -105,8 +117,11 @@ export class SendCreateCommand {
const newSend = await this.sendService.get(encSend.id);
const decSend = await newSend.decrypt();
const res = new SendResponse(decSend, this.environmentService.getWebVaultUrl());
return Response.success(options.fullObject ? res :
new StringResponse('Send created! It can be accessed at:\n' + res.accessUrl));
return Response.success(
options.fullObject
? res
: new StringResponse("Send created! It can be accessed at:\n" + res.accessUrl)
);
} catch (e) {
return Response.error(e);
}

View File

@@ -1,6 +1,6 @@
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 {
constructor(private sendService: SendService) {}

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 { SendService } from 'jslib-common/abstractions/send.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SendService } from "jslib-common/abstractions/send.service";
import { UserService } from "jslib-common/abstractions/user.service";
import { SendType } from 'jslib-common/enums/sendType';
import { Response } from 'jslib-node/cli/models/response';
import { SendType } from "jslib-common/enums/sendType";
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 { SendGetCommand } from './get.command';
import { CliUtils } from "../../utils";
import { SendGetCommand } from "./get.command";
export class SendEditCommand {
constructor(private sendService: SendService, private userService: UserService,
private getCommand: SendGetCommand) { }
constructor(
private sendService: SendService,
private userService: UserService,
private getCommand: SendGetCommand
) {}
async run(encodedJson: string, options: program.OptionValues): Promise<Response> {
if (encodedJson == null || encodedJson === '') {
if (encodedJson == null || encodedJson === "") {
encodedJson = await CliUtils.readStdin();
}
if (encodedJson == null || encodedJson === '') {
return Response.badRequest('`encodedJson` was not provided.');
if (encodedJson == null || encodedJson === "") {
return Response.badRequest("`encodedJson` was not provided.");
}
let req: SendResponse = null;
try {
const reqJson = Buffer.from(encodedJson, 'base64').toString();
const reqJson = Buffer.from(encodedJson, "base64").toString();
req = SendResponse.fromJson(reqJson);
} 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;
@@ -46,17 +49,17 @@ export class SendEditCommand {
}
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())) {
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();
sendView = SendResponse.toView(req, sendView);
if (typeof (req.password) !== 'string' || req.password === '') {
if (typeof req.password !== "string" || req.password === "") {
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 { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { SearchService } from 'jslib-common/abstractions/search.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SearchService } from "jslib-common/abstractions/search.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 {
constructor(private sendService: SendService, private environmentService: EnvironmentService,
private searchService: SearchService, cryptoService: CryptoService) {
constructor(
private sendService: SendService,
private environmentService: EnvironmentService,
private searchService: SearchService,
cryptoService: CryptoService
) {
super(cryptoService);
}
@@ -30,12 +34,13 @@ export class SendGetCommand extends DownloadCommand {
const webVaultUrl = this.environmentService.getWebVaultUrl();
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) {
filter = s => {
filter = (s) => {
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
process.stdout.write(s.text.text);
return Response.success();
@@ -47,12 +52,11 @@ export class SendGetCommand extends DownloadCommand {
sends = sends.filter(filter);
}
if (sends.length > 1) {
return Response.multipleResults(sends.map(s => s.id));
return Response.multipleResults(sends.map((s) => s.id));
}
if (sends.length > 0) {
return selector(sends[0]);
}
else {
} else {
return Response.notFound();
}
}
@@ -66,7 +70,7 @@ export class SendGetCommand extends DownloadCommand {
if (send != null) {
return await send.decrypt();
}
} else if (id.trim() !== '') {
} else if (id.trim() !== "") {
let sends = await this.sendService.getAllDecrypted();
sends = this.searchService.searchSends(sends, id);
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 { SearchService } from 'jslib-common/abstractions/search.service';
import { SendService } from 'jslib-common/abstractions/send.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { SendService } from "jslib-common/abstractions/send.service";
import { Response } from 'jslib-node/cli/models/response';
import { ListResponse } from 'jslib-node/cli/models/response/listResponse';
import { Response } from "jslib-node/cli/models/response";
import { ListResponse } from "jslib-node/cli/models/response/listResponse";
import { SendResponse } from '../..//models/response/sendResponse';
import { SendResponse } from "../..//models/response/sendResponse";
export class SendListCommand {
constructor(private sendService: SendService, private environmentService: EnvironmentService,
private searchService: SearchService) { }
constructor(
private sendService: SendService,
private environmentService: EnvironmentService,
private searchService: SearchService
) {}
async run(options: program.OptionValues): Promise<Response> {
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);
}
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);
}
}

View File

@@ -1,63 +1,67 @@
import * as program from 'commander';
import * as inquirer from 'inquirer';
import * as program from "commander";
import * as inquirer from "inquirer";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib-common/abstractions/environment.service';
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { SendAccessRequest } from 'jslib-common/models/request/sendAccessRequest';
import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
import { SendAccessView } from 'jslib-common/models/view/sendAccessView';
import { SendAccessRequest } from "jslib-common/models/request/sendAccessRequest";
import { ErrorResponse } from "jslib-common/models/response/errorResponse";
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 { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
import { SendAccess } from "jslib-common/models/domain/sendAccess";
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 { Utils } from 'jslib-common/misc/utils';
import { NodeUtils } from "jslib-common/misc/nodeUtils";
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 {
private canInteract: boolean;
private decKey: SymmetricCryptoKey;
private sendAccessRequest: SendAccessRequest;
constructor(private apiService: ApiService, cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService, private platformUtilsService: PlatformUtilsService,
private environmentService: EnvironmentService) {
constructor(
private apiService: ApiService,
cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService,
private platformUtilsService: PlatformUtilsService,
private environmentService: EnvironmentService
) {
super(cryptoService);
}
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;
try {
urlObject = new URL(url);
} 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 [id, key] = this.getIdAndKey(urlObject);
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);
this.sendAccessRequest = new SendAccessRequest();
let password = options.password;
if (password == null || password === '') {
if (password == null || password === "") {
if (options.passwordfile) {
password = await NodeUtils.readFirstLine(options.passwordfile);
} 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);
}
@@ -86,37 +90,55 @@ export class SendReceiveCommand extends DownloadCommand {
process.stdout.write(response?.text?.text);
return Response.success();
case SendType.File:
const downloadData = await this.apiService.getSendFileDownloadData(response, this.sendAccessRequest, apiUrl);
return await this.saveAttachmentToFile(downloadData.url, this.decKey, response?.file?.fileName, options.output);
const downloadData = await this.apiService.getSendFileDownloadData(
response,
this.sendAccessRequest,
apiUrl
);
return await this.saveAttachmentToFile(
downloadData.url,
this.decKey,
response?.file?.fileName,
options.output
);
default:
return Response.success(new SendAccessResponse(response));
}
}
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]];
}
private getApiUrl(url: URL) {
const urls = this.environmentService.getUrls();
if (url.origin === 'https://send.bitwarden.com') {
return 'https://vault.bitwarden.com/api';
if (url.origin === "https://send.bitwarden.com") {
return "https://vault.bitwarden.com/api";
} else if (url.origin === urls.api) {
return url.origin;
} else if (this.platformUtilsService.isDev() && url.origin === urls.webVault) {
return urls.api;
} else {
return url.origin + '/api';
return url.origin + "/api";
}
}
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);
}
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 {
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.statusCode === 401) {
if (this.canInteract) {
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
type: 'password',
name: 'password',
message: 'Send password:',
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "password",
name: "password",
message: "Send password:",
});
// reattempt with new password
@@ -138,9 +162,9 @@ export class SendReceiveCommand extends DownloadCommand {
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) {
return Response.badRequest('Bad Request');
return Response.badRequest("Bad Request");
} else if (e.statusCode === 404) {
return Response.notFound();
}

View File

@@ -1,8 +1,8 @@
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 {
constructor(private sendService: SendService) {}

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 {
constructor(private cipherService: CipherService) {}
async run(id: string, organizationId: string, requestJson: string, cmd: program.Command): Promise<Response> {
if (requestJson == null || requestJson === '') {
async run(
id: string,
organizationId: string,
requestJson: string,
cmd: program.Command
): Promise<Response> {
if (requestJson == null || requestJson === "") {
requestJson = await CliUtils.readStdin();
}
if (requestJson == null || requestJson === '') {
return Response.badRequest('`requestJson` was not provided.');
if (requestJson == null || requestJson === "") {
return Response.badRequest("`requestJson` was not provided.");
}
let req: string[] = [];
try {
const reqJson = Buffer.from(requestJson, 'base64').toString();
const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson);
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) {
return Response.badRequest('Error parsing the encoded request data.');
return Response.badRequest("Error parsing the encoded request data.");
}
if (id != null) {
@@ -43,7 +48,7 @@ export class ShareCommand {
return Response.notFound();
}
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();
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 { SyncService } from 'jslib-common/abstractions/sync.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { UserService } from "jslib-common/abstractions/user.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 {
constructor(private envService: EnvironmentService, private syncService: SyncService,
private userService: UserService, private vaultTimeoutService: VaultTimeoutService) {
}
constructor(
private envService: EnvironmentService,
private syncService: SyncService,
private userService: UserService,
private vaultTimeoutService: VaultTimeoutService
) {}
async run(): Promise<Response> {
try {
@@ -22,13 +25,15 @@ export class StatusCommand {
const userId = await this.userService.getUserId();
const email = await this.userService.getEmail();
return Response.success(new TemplateResponse({
return Response.success(
new TemplateResponse({
serverUrl: baseUrl,
lastSync: lastSync,
userEmail: email,
userId: userId,
status: status,
}));
})
);
} catch (e) {
return Response.error(e);
}
@@ -41,10 +46,10 @@ export class StatusCommand {
private async status(): Promise<string> {
const authed = await this.userService.isAuthenticated();
if (!authed) {
return 'unauthenticated';
return "unauthenticated";
}
const isLocked = await this.vaultTimeoutService.isLocked();
return isLocked ? 'locked' : 'unlocked';
return isLocked ? "locked" : "unlocked";
}
}

View File

@@ -1,10 +1,10 @@
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 { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
import { StringResponse } from 'jslib-node/cli/models/response/stringResponse';
import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
export class SyncCommand {
constructor(private syncService: SyncService) {}
@@ -16,10 +16,10 @@ export class SyncCommand {
try {
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);
} 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 inquirer from 'inquirer';
import * as program from "commander";
import * as inquirer from "inquirer";
import { ApiService } from 'jslib-common/abstractions/api.service';
import { CryptoService } from 'jslib-common/abstractions/crypto.service';
import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service';
import { UserService } from 'jslib-common/abstractions/user.service';
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { UserService } from "jslib-common/abstractions/user.service";
import { Response } from 'jslib-node/cli/models/response';
import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse';
import { Response } from "jslib-node/cli/models/response";
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 { NodeUtils } from 'jslib-common/misc/nodeUtils';
import { ConsoleLogService } from 'jslib-common/services/consoleLog.service';
import { HashPurpose } from "jslib-common/enums/hashPurpose";
import { NodeUtils } from "jslib-common/misc/nodeUtils";
import { ConsoleLogService } from "jslib-common/services/consoleLog.service";
export class UnlockCommand {
constructor(private cryptoService: CryptoService, private userService: UserService,
private cryptoFunctionService: CryptoFunctionService, private apiService: ApiService,
private logService: ConsoleLogService) {
}
constructor(
private cryptoService: CryptoService,
private userService: UserService,
private cryptoFunctionService: CryptoFunctionService,
private apiService: ApiService,
private logService: ConsoleLogService
) {}
async run(password: string, options: program.OptionValues) {
const canInteract = process.env.BW_NOINTERACTION !== 'true';
if (password == null || password === '') {
const canInteract = process.env.BW_NOINTERACTION !== "true";
if (password == null || password === "") {
if (options?.passwordfile) {
password = await NodeUtils.readFirstLine(options.passwordfile);
} else if (options?.passwordenv) {
if (process.env[options.passwordenv]) {
password = process.env[options.passwordenv];
} 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) {
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
type: 'password',
name: 'password',
message: 'Master password:',
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "password",
name: "password",
message: "Master password:",
});
password = answer.password;
} else {
return Response.badRequest('Master password is required.');
return Response.badRequest("Master password is required.");
}
}
@@ -63,14 +70,21 @@ export class UnlockCommand {
if (storedKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, key);
} 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();
request.masterPasswordHash = serverKeyHash;
try {
await this.apiService.postAccountVerifyPassword(request);
passwordValid = true;
const localKeyHash = await this.cryptoService.hashPassword(password, key,
HashPurpose.LocalAuthorization);
const localKeyHash = await this.cryptoService.hashPassword(
password,
key,
HashPurpose.LocalAuthorization
);
await this.cryptoService.setKeyHash(localKeyHash);
} catch {}
}
@@ -78,16 +92,24 @@ export class UnlockCommand {
if (passwordValid) {
await this.cryptoService.setKey(key);
const res = new MessageResponse('Your vault is now unlocked!', '\n' +
'To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n' +
'$ export BW_SESSION="' + process.env.BW_SESSION + '"\n' +
'> $env:BW_SESSION="' + process.env.BW_SESSION + '"\n\n' +
'You can also pass the session key to any command with the `--session` option. ex:\n' +
'$ bw list items --session ' + process.env.BW_SESSION);
const res = new MessageResponse(
"Your vault is now unlocked!",
"\n" +
"To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n" +
'$ export BW_SESSION="' +
process.env.BW_SESSION +
'"\n' +
'> $env:BW_SESSION="' +
process.env.BW_SESSION +
'"\n\n' +
"You can also pass the session key to any command with the `--session` option. ex:\n" +
"$ bw list items --session " +
process.env.BW_SESSION
);
res.raw = process.env.BW_SESSION;
return Response.success(res);
} 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 {
static template(): OrganizationCollectionRequest {
const req = new OrganizationCollectionRequest();
req.organizationId = '00000000-0000-0000-0000-000000000000';
req.name = 'Collection name';
req.organizationId = "00000000-0000-0000-0000-000000000000";
req.name = "Collection name";
req.externalId = null;
req.groups = [SelectionReadOnly.template(), SelectionReadOnly.template()];
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 {
id: string;

View File

@@ -1,13 +1,13 @@
import { CipherWithIds } from 'jslib-common/models/export/cipherWithIds';
import { CipherView } from 'jslib-common/models/view/cipherView';
import { CipherWithIds } from "jslib-common/models/export/cipherWithIds";
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 { LoginResponse } from './loginResponse';
import { PasswordHistoryResponse } from './passwordHistoryResponse';
import { AttachmentResponse } from "./attachmentResponse";
import { LoginResponse } from "./loginResponse";
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 {
object: string;
@@ -17,14 +17,14 @@ export class CipherResponse extends CipherWithIds implements BaseResponse {
constructor(o: CipherView) {
super();
this.object = 'item';
this.object = "item";
this.build(o);
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;
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) {
this.login = new LoginResponse(o.login);

View File

@@ -1,14 +1,14 @@
import { CollectionWithId } from 'jslib-common/models/export/collectionWithId';
import { CollectionView } from 'jslib-common/models/view/collectionView';
import { CollectionWithId } from "jslib-common/models/export/collectionWithId";
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 {
object: string;
constructor(o: CollectionView) {
super();
this.object = 'collection';
this.object = "collection";
this.build(o);
}
}

View File

@@ -1,14 +1,14 @@
import { FolderWithId } from 'jslib-common/models/export/folderWithId';
import { FolderView } from 'jslib-common/models/view/folderView';
import { FolderWithId } from "jslib-common/models/export/folderWithId";
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 {
object: string;
constructor(o: FolderView) {
super();
this.object = 'folder';
this.object = "folder";
this.build(o);
}
}

View File

@@ -1,5 +1,5 @@
import { Login } from 'jslib-common/models/export/login';
import { LoginView } from 'jslib-common/models/view/loginView';
import { Login } from "jslib-common/models/export/login";
import { LoginView } from "jslib-common/models/view/loginView";
export class LoginResponse extends Login {
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 {
groups: SelectionReadOnly[];
constructor(o: CollectionView, groups: SelectionReadOnly[]) {
super(o);
this.object = 'org-collection';
this.object = "org-collection";
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 { OrganizationUserType } from 'jslib-common/enums/organizationUserType';
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
export class OrganizationResponse implements BaseResponse {
object: string;
@@ -14,7 +14,7 @@ export class OrganizationResponse implements BaseResponse {
enabled: boolean;
constructor(o: Organization) {
this.object = 'organization';
this.object = "organization";
this.id = o.id;
this.name = o.name;
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 { OrganizationUserType } from 'jslib-common/enums/organizationUserType';
import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType";
import { OrganizationUserType } from "jslib-common/enums/organizationUserType";
export class OrganizationUserResponse implements BaseResponse {
object: string;
@@ -13,6 +13,6 @@ export class OrganizationUserResponse implements BaseResponse {
twoFactorEnabled: boolean;
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 {
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 { SendTextResponse } from './sendTextResponse';
import { SendFileResponse } from "./sendFileResponse";
import { SendTextResponse } from "./sendTextResponse";
export class SendAccessResponse implements BaseResponse {
static template(): SendAccessResponse {
const req = new SendAccessResponse();
req.name = 'Send name';
req.name = "Send name";
req.type = SendType.Text;
req.text = null;
req.file = null;
return req;
}
object = 'send-access';
object = "send-access";
id: string;
name: string;
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 {
static template(fileName = 'file attachment location'): SendFileResponse {
static template(fileName = "file attachment location"): SendFileResponse {
const req = new SendFileResponse();
req.fileName = fileName;
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 { SendTextResponse } from './sendTextResponse';
import { SendFileResponse } from "./sendFileResponse";
import { SendTextResponse } from "./sendTextResponse";
const dateProperties: string[] = [Utils.nameOf<SendResponse>('deletionDate'), Utils.nameOf<SendResponse>('expirationDate')];
const dateProperties: string[] = [
Utils.nameOf<SendResponse>("deletionDate"),
Utils.nameOf<SendResponse>("expirationDate"),
];
export class SendResponse implements BaseResponse {
static template(sendType?: SendType, deleteInDays = 7): SendResponse {
const req = new SendResponse();
req.name = 'Send name';
req.notes = 'Some notes about this send.';
req.name = "Send name";
req.notes = "Some notes about this send.";
req.type = sendType === SendType.File ? SendType.File : SendType.Text;
req.text = sendType === SendType.Text ? SendTextResponse.template() : null;
req.file = sendType === SendType.File ? SendFileResponse.template() : null;
@@ -65,11 +66,11 @@ export class SendResponse implements BaseResponse {
private static getStandardDeletionDate(days: number) {
const d = new Date();
d.setTime(d.getTime() + (days * 86400000)); // ms per day
d.setTime(d.getTime() + days * 86400000); // ms per day
return d;
}
object = 'send';
object = "send";
id: string;
accessId: string;
accessUrl: string;
@@ -97,11 +98,11 @@ export class SendResponse implements BaseResponse {
this.accessId = o.accessId;
let sendLinkBaseUrl = webVaultUrl;
if (sendLinkBaseUrl == null) {
sendLinkBaseUrl = 'https://send.bitwarden.com/#';
sendLinkBaseUrl = "https://send.bitwarden.com/#";
} else {
sendLinkBaseUrl += '/#/send/';
sendLinkBaseUrl += "/#/send/";
}
this.accessUrl = sendLinkBaseUrl + this.accessId + '/' + o.urlB64Key;
this.accessUrl = sendLinkBaseUrl + this.accessId + "/" + o.urlB64Key;
this.name = o.name;
this.notes = o.notes;
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 {
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();
req.text = text;
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 {
object: string;
template: any;
constructor(template: any) {
this.object = 'template';
this.object = "template";
this.template = template;
}
}

View File

@@ -1,6 +1,6 @@
export class 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;

View File

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

View File

@@ -1,30 +1,30 @@
import * as chalk from 'chalk';
import * as program from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import * as chalk from "chalk";
import * as program from "commander";
import * as fs from "fs";
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 { SendCreateCommand } from './commands/send/create.command';
import { SendDeleteCommand } from './commands/send/delete.command';
import { SendEditCommand } from './commands/send/edit.command';
import { SendGetCommand } from './commands/send/get.command';
import { SendListCommand } from './commands/send/list.command';
import { SendReceiveCommand } from './commands/send/receive.command';
import { SendRemovePasswordCommand } from './commands/send/removePassword.command';
import { GetCommand } from "./commands/get.command";
import { SendCreateCommand } from "./commands/send/create.command";
import { SendDeleteCommand } from "./commands/send/delete.command";
import { SendEditCommand } from "./commands/send/edit.command";
import { SendGetCommand } from "./commands/send/get.command";
import { SendListCommand } from "./commands/send/list.command";
import { SendReceiveCommand } from "./commands/send/receive.command";
import { SendRemovePasswordCommand } from "./commands/send/removePassword.command";
import { SendFileResponse } from './models/response/sendFileResponse';
import { SendResponse } from './models/response/sendResponse';
import { SendTextResponse } from './models/response/sendTextResponse';
import { SendFileResponse } from "./models/response/sendFileResponse";
import { SendResponse } from "./models/response/sendResponse";
import { SendTextResponse } from "./models/response/sendTextResponse";
import { Main } from './bw';
import { Program } from './program';
import { CliUtils } from './utils';
import { Main } from "./bw";
import { Program } from "./program";
import { CliUtils } from "./utils";
const writeLn = CliUtils.writeLn;
@@ -40,18 +40,31 @@ export class SendProgram extends Program {
}
private sendCommand(): program.Command {
return new program.Command('send')
.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', {
data: 'The data to Send. Specify as a filepath with the --file option',
})
.option('-f, --file', 'Specifies that <data> is a filepath')
.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('-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.')
return new program.Command("send")
.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",
{
data: "The data to Send. Specify as a filepath with the --file option",
}
)
.option("-f, --file", "Specifies that <data> is a filepath")
.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(
"-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.templateCommand())
.addCommand(this.getCommand())
@@ -75,108 +88,144 @@ export class SendProgram extends Program {
}
private receiveCommand(): program.Command {
return new program.Command('receive')
.arguments('<url>')
.description('Access a Bitwarden Send from a url')
.option('--password <password>', 'Password needed to access the Send.')
.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('--obj', 'Return the Send\'s json object rather than the Send\'s content')
.option('--output <location>', 'Specify a file path to save a File-type Send to')
.on('--help', () => {
writeLn('');
writeLn('If a password is required, the provided password is used or the user is prompted.');
writeLn('', true);
return new program.Command("receive")
.arguments("<url>")
.description("Access a Bitwarden Send from a url")
.option("--password <password>", "Password needed to access the Send.")
.option("--passwordenv <passwordenv>", "Environment variable storing the Send's password")
.option(
"--passwordfile <passwordfile>",
"Path to a file containing the Sends password as its first line"
)
.option("--obj", "Return the Send's json object rather than the Send's content")
.option("--output <location>", "Specify a file path to save a File-type Send to")
.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) => {
const cmd = new SendReceiveCommand(this.main.apiService, this.main.cryptoService,
this.main.cryptoFunctionService, this.main.platformUtilsService, this.main.environmentService);
const cmd = new SendReceiveCommand(
this.main.apiService,
this.main.cryptoService,
this.main.cryptoFunctionService,
this.main.platformUtilsService,
this.main.environmentService
);
const response = await cmd.run(url, options);
this.processResponse(response);
});
}
private listCommand(): program.Command {
return new program.Command('list')
return new program.Command("list")
.description('List all the Sends owned by you')
.on('--help', () => { writeLn(chalk('This is in the list command')); })
.description("List all the Sends owned by you")
.on("--help", () => {
writeLn(chalk("This is in the list command"));
})
.action(async (options: program.OptionValues) => {
await this.exitIfLocked();
const cmd = new SendListCommand(this.main.sendService, this.main.environmentService,
this.main.searchService);
const cmd = new SendListCommand(
this.main.sendService,
this.main.environmentService,
this.main.searchService
);
const response = await cmd.run(options);
this.processResponse(response);
});
}
private templateCommand(): program.Command {
return new program.Command('template')
.arguments('<object>')
.description('Get json templates for send objects', {
object: 'Valid objects are: send, send.text, send.file',
return new program.Command("template")
.arguments("<object>")
.description("Get json templates for send objects", {
object: "Valid objects are: send, send.text, send.file",
})
.action(async object => {
const cmd = new GetCommand(this.main.cipherService, this.main.folderService,
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 cmd.run('template', object, null);
.action(async (object) => {
const cmd = new GetCommand(
this.main.cipherService,
this.main.folderService,
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 cmd.run("template", object, null);
this.processResponse(response);
});
}
private getCommand(): program.Command {
return new program.Command('get')
.arguments('<id>')
.description('Get Sends owned by you.')
.option('--output <output>', 'Output directory or filename for attachment.')
.option('--text', 'Specifies to return the text content of a Send')
.on('--help', () => {
writeLn('');
writeLn(' Id:');
writeLn('');
writeLn(' Search term or Send\'s globally unique `id`.');
writeLn('');
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('');
writeLn(' Examples:');
writeLn('');
writeLn(' bw get send searchText');
writeLn(' bw get send id');
writeLn(' bw get send searchText --text');
writeLn(' bw get send searchText --file');
writeLn(' bw get send searchText --file --output ../Photos/photo.jpg');
writeLn(' bw get send searchText --file --raw');
writeLn('', true);
return new program.Command("get")
.arguments("<id>")
.description("Get Sends owned by you.")
.option("--output <output>", "Output directory or filename for attachment.")
.option("--text", "Specifies to return the text content of a Send")
.on("--help", () => {
writeLn("");
writeLn(" Id:");
writeLn("");
writeLn(" Search term or Send's globally unique `id`.");
writeLn("");
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("");
writeLn(" Examples:");
writeLn("");
writeLn(" bw get send searchText");
writeLn(" bw get send id");
writeLn(" bw get send searchText --text");
writeLn(" bw get send searchText --file");
writeLn(" bw get send searchText --file --output ../Photos/photo.jpg");
writeLn(" bw get send searchText --file --raw");
writeLn("", true);
})
.action(async (id: string, options: program.OptionValues) => {
await this.exitIfLocked();
const cmd = new SendGetCommand(this.main.sendService, this.main.environmentService,
this.main.searchService, this.main.cryptoService);
const cmd = new SendGetCommand(
this.main.sendService,
this.main.environmentService,
this.main.searchService,
this.main.cryptoService
);
const response = await cmd.run(id, options);
this.processResponse(response);
});
}
private createCommand(): program.Command {
return new program.Command('create')
.arguments('[encodedJson]')
.description('create a Send', {
encodedJson: 'JSON object to upload. Can also be piped in through stdin.',
return new program.Command("create")
.arguments("[encodedJson]")
.description("create a Send", {
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('--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('--password <password>', 'optional password to access this Send. Can also be specified in JSON')
.on('--help', () => {
writeLn('');
writeLn('Note:');
writeLn(' Options specified in JSON take precedence over command options');
writeLn('', true);
.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("--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"
)
.on("--help", () => {
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`
// 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
@@ -188,26 +237,32 @@ export class SendProgram extends Program {
const response = await this.runCreate(encodedJson, mergedOptions);
this.processResponse(response);
});
}
);
}
private editCommand(): program.Command {
return new program.Command('edit')
.arguments('[encodedJson]')
.description('edit a Send', {
encodedJson: 'Updated JSON object to save. If not provided, encodedJson is read from stdin.',
return new program.Command("edit")
.arguments("[encodedJson]")
.description("edit a Send", {
encodedJson:
"Updated JSON object to save. If not provided, encodedJson is read from stdin.",
})
.option('--itemid <itemid>', 'Overrides the itemId provided in [encodedJson]')
.on('--help', () => {
writeLn('');
writeLn('Note:');
writeLn(' You cannot update a File-type Send\'s file. Just delete and remake it');
writeLn('', true);
.option("--itemid <itemid>", "Overrides the itemId provided in [encodedJson]")
.on("--help", () => {
writeLn("");
writeLn("Note:");
writeLn(" You cannot update a File-type Send's file. Just delete and remake it");
writeLn("", true);
})
.action(async (encodedJson: string, options: program.OptionValues) => {
await this.exitIfLocked();
const getCmd = new SendGetCommand(this.main.sendService, this.main.environmentService,
this.main.searchService, this.main.cryptoService);
const getCmd = new SendGetCommand(
this.main.sendService,
this.main.environmentService,
this.main.searchService,
this.main.cryptoService
);
const cmd = new SendEditCommand(this.main.sendService, this.main.userService, getCmd);
const response = await cmd.run(encodedJson, options);
this.processResponse(response);
@@ -215,10 +270,10 @@ export class SendProgram extends Program {
}
private deleteCommand(): program.Command {
return new program.Command('delete')
.arguments('<id>')
.description('delete a Send', {
id: 'The id of the Send to delete.',
return new program.Command("delete")
.arguments("<id>")
.description("delete a Send", {
id: "The id of the Send to delete.",
})
.action(async (id: string) => {
await this.exitIfLocked();
@@ -229,10 +284,10 @@ export class SendProgram extends Program {
}
private removePasswordCommand(): program.Command {
return new program.Command('remove-password')
.arguments('<id>')
.description('removes the saved password from a Send.', {
id: 'The id of the Send to alter.',
return new program.Command("remove-password")
.arguments("<id>")
.description("removes the saved password from a Send.", {
id: "The id of the Send to alter.",
})
.action(async (id: string) => {
await this.exitIfLocked();
@@ -250,7 +305,7 @@ export class SendProgram extends Program {
if (options.file != null) {
data = path.resolve(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);
@@ -268,13 +323,16 @@ export class SendProgram extends Program {
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) {
await this.exitIfLocked();
const cmd = new SendCreateCommand(this.main.sendService, this.main.userService,
this.main.environmentService);
const cmd = new SendCreateCommand(
this.main.sendService,
this.main.userService,
this.main.environmentService
);
return await cmd.run(encodedJson, options);
}
}

View File

@@ -1,19 +1,20 @@
import * as fs from 'fs';
import * as path from 'path';
import * as fs from "fs";
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 {
constructor(systemLanguage: string, localesDirectory: string) {
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
const filePath = path.join(__dirname, this.localesDirectory + '/' + formattedLocale + '/messages.json');
const localesJson = fs.readFileSync(filePath, 'utf8');
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, '')); // strip the BOM
const filePath = path.join(
__dirname,
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);
});
this.supportedTranslationLocales = [
'en',
];
this.supportedTranslationLocales = ["en"];
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,39 +1,17 @@
{
"extends": "tslint:recommended",
"rules": {
"align": [
true,
"statements",
"members"
],
"align": [true, "statements", "members"],
"ban-types": {
"options": [
[
"Object",
"Avoid using the `Object` type. Did you mean `object`?"
],
[
"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`?"
]
["Object", "Avoid using the `Object` type. Did you mean `object`?"],
["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": [
true,
"no-public"
],
"member-access": [true, "no-public"],
"member-ordering": [
true,
{
@@ -56,15 +34,9 @@
]
}
],
"no-empty": [
true,
"allow-empty-catch"
],
"no-empty": [true, "allow-empty-catch"],
"object-literal-sort-keys": false,
"object-literal-shorthand": [
true,
"never"
],
"object-literal-shorthand": [true, "never"],
"prefer-for-of": false,
"whitespace": [
true,

View File

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