1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-10-28 07:49:41 +01: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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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

28
.vscode/launch.json vendored
View File

@ -1,18 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"protocol": "inspector",
"cwd": "${workspaceRoot}",
"program": "${workspaceFolder}/build/bw.js",
"args": [
"login",
"sdfsd@sdfdf.com",
"ddddddd"
]
}
]
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"protocol": "inspector",
"cwd": "${workspaceRoot}",
"program": "${workspaceFolder}/build/bw.js",
"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

View File

@ -7,7 +7,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
effort to quickly resolve the issue.
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
third-party. We may publicly disclose the issue before resolving it, if appropriate.
third-party. We may publicly disclose the issue before resolving it, if appropriate.
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
degradation of our service. Only interact with accounts you own or with explicit permission of the
account holder.

510
src/bw.ts
View File

@ -1,228 +1,334 @@
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;
storageService: LowdbStorageService;
secureStorageService: NodeEnvSecureStorageService;
i18nService: I18nService;
platformUtilsService: CliPlatformUtilsService;
constantsService: ConstantsService;
cryptoService: CryptoService;
tokenService: TokenService;
appIdService: AppIdService;
apiService: NodeApiService;
environmentService: EnvironmentService;
userService: UserService;
settingsService: SettingsService;
cipherService: CipherService;
folderService: FolderService;
collectionService: CollectionService;
vaultTimeoutService: VaultTimeoutService;
syncService: SyncService;
passwordGenerationService: PasswordGenerationService;
totpService: TotpService;
containerService: ContainerService;
auditService: AuditService;
importService: ImportService;
exportService: ExportService;
searchService: SearchService;
cryptoFunctionService: NodeCryptoFunctionService;
authService: AuthService;
policyService: PolicyService;
program: Program;
vaultProgram: VaultProgram;
sendProgram: SendProgram;
logService: ConsoleLogService;
sendService: SendService;
fileUploadService: FileUploadService;
keyConnectorService: KeyConnectorService;
userVerificationService: UserVerificationService;
messagingService: NoopMessagingService;
storageService: LowdbStorageService;
secureStorageService: NodeEnvSecureStorageService;
i18nService: I18nService;
platformUtilsService: CliPlatformUtilsService;
constantsService: ConstantsService;
cryptoService: CryptoService;
tokenService: TokenService;
appIdService: AppIdService;
apiService: NodeApiService;
environmentService: EnvironmentService;
userService: UserService;
settingsService: SettingsService;
cipherService: CipherService;
folderService: FolderService;
collectionService: CollectionService;
vaultTimeoutService: VaultTimeoutService;
syncService: SyncService;
passwordGenerationService: PasswordGenerationService;
totpService: TotpService;
containerService: ContainerService;
auditService: AuditService;
importService: ImportService;
exportService: ExportService;
searchService: SearchService;
cryptoFunctionService: NodeCryptoFunctionService;
authService: AuthService;
policyService: PolicyService;
program: Program;
vaultProgram: VaultProgram;
sendProgram: SendProgram;
logService: ConsoleLogService;
sendService: SendService;
fileUploadService: FileUploadService;
keyConnectorService: KeyConnectorService;
userVerificationService: UserVerificationService;
constructor() {
let p = null;
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.env.XDG_CONFIG_HOME) {
p = path.join(process.env.XDG_CONFIG_HOME, 'Bitwarden CLI');
} else {
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.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.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,
async (expired: boolean) => await this.logout(),
'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.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.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);
constructor() {
let p = null;
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.env.XDG_CONFIG_HOME) {
p = path.join(process.env.XDG_CONFIG_HOME, "Bitwarden CLI");
} else {
p = path.join(process.env.HOME, ".config/Bitwarden CLI");
}
async run() {
await this.init();
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.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,
async (expired: boolean) => await this.logout(),
"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.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.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
);
}
await this.program.register();
await this.vaultProgram.register();
await this.sendProgram.register();
async run() {
await this.init();
program
.parse(process.argv);
await this.program.register();
await this.vaultProgram.register();
await this.sendProgram.register();
if (process.argv.slice(2).length === 0) {
program.outputHelp();
}
program.parse(process.argv);
if (process.argv.slice(2).length === 0) {
program.outputHelp();
}
}
async logout() {
const userId = await this.userService.getUserId();
await Promise.all([
this.syncService.setLastSync(new Date(0)),
this.tokenService.clearToken(),
this.cryptoService.clearKeys(),
this.userService.clear(),
this.settingsService.clear(userId),
this.cipherService.clear(userId),
this.folderService.clear(userId),
this.collectionService.clear(userId),
this.policyService.clear(userId),
this.passwordGenerationService.clear(),
]);
process.env.BW_SESSION = null;
}
private async init() {
await this.storageService.init();
this.containerService.attachToWindow(global);
await this.environmentService.setUrlsFromStorage();
// Dev Server URLs. Comment out the line above.
// this.apiService.setUrls({
// base: null,
// api: 'http://localhost:4000',
// identity: 'http://localhost:33656',
// });
const locale = await this.storageService.get<string>(ConstantsService.localeKey);
await this.i18nService.init(locale);
this.authService.init();
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);
}
async logout() {
const userId = await this.userService.getUserId();
await Promise.all([
this.syncService.setLastSync(new Date(0)),
this.tokenService.clearToken(),
this.cryptoService.clearKeys(),
this.userService.clear(),
this.settingsService.clear(userId),
this.cipherService.clear(userId),
this.folderService.clear(userId),
this.collectionService.clear(userId),
this.policyService.clear(userId),
this.passwordGenerationService.clear(),
]);
process.env.BW_SESSION = null;
}
private async init() {
await this.storageService.init();
this.containerService.attachToWindow(global);
await this.environmentService.setUrlsFromStorage();
// Dev Server URLs. Comment out the line above.
// this.apiService.setUrls({
// base: null,
// api: 'http://localhost:4000',
// identity: 'http://localhost:33656',
// });
const locale = await this.storageService.get<string>(ConstantsService.localeKey);
await this.i18nService.init(locale);
this.authService.init();
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);
}
}
}
const main = new Main();

View File

@ -1,110 +1,119 @@
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;
short?: string;
description: string;
long?: string;
short?: string;
description: string;
}
interface ICommand {
commands?: ICommand[];
options?: IOption[];
_name: string;
_description: string;
commands?: ICommand[];
options?: IOption[];
_name: string;
_description: string;
}
const validShells = ['zsh'];
const validShells = ["zsh"];
export class CompletionCommand {
async run(options: program.OptionValues) {
const shell: typeof validShells[number] = options.shell;
async run(options: program.OptionValues) {
const shell: typeof validShells[number] = options.shell;
if (!shell) {
return Response.badRequest('`shell` was not provided.');
}
if (!validShells.includes(shell)) {
return Response.badRequest('Unsupported shell.');
}
let content = '';
if (shell === 'zsh') {
content = this.zshCompletion('bw', program as any as ICommand).render();
}
const res = new MessageResponse(content, null);
return Response.success(res);
if (!shell) {
return Response.badRequest("`shell` was not provided.");
}
private zshCompletion(rootName: string, rootCommand: ICommand) {
return {
render: () => {
return [
`#compdef _${rootName} ${rootName}`,
'',
this.renderCommandBlock(rootName, rootCommand),
].join('\n');
},
};
if (!validShells.includes(shell)) {
return Response.badRequest("Unsupported shell.");
}
private renderCommandBlock(name: string, command: ICommand): string {
const { commands = [], options = [] } = command;
const hasOptions = options.length > 0;
const hasCommands = commands.length > 0;
let content = "";
const args = options
.map(({ long, short, description }) => {
const aliases = [short, long].filter(Boolean);
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]'`,
hasCommands ? '"1: :->cmnds"' : null,
'"*::arg:->args"',
).filter(Boolean);
if (shell === "zsh") {
content = this.zshCompletion("bw", program as any as ICommand).render();
}
const commandBlockFunctionParts = [];
const res = new MessageResponse(content, null);
return Response.success(res);
}
if (hasCommands) {
commandBlockFunctionParts.push('local -a commands');
}
private zshCompletion(rootName: string, rootCommand: ICommand) {
return {
render: () => {
return [
`#compdef _${rootName} ${rootName}`,
"",
this.renderCommandBlock(rootName, rootCommand),
].join("\n");
},
};
}
if (hasOptions) {
commandBlockFunctionParts.push(`_arguments -C \\\n ${args.join(` \\\n `)}`);
}
private renderCommandBlock(name: string, command: ICommand): string {
const { commands = [], options = [] } = command;
const hasOptions = options.length > 0;
const hasCommands = commands.length > 0;
if (hasCommands) {
commandBlockFunctionParts.push(
`case $state in
const args = options
.map(({ long, short, description }) => {
const aliases = [short, long].filter(Boolean);
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]'`,
hasCommands ? '"1: :->cmnds"' : null,
'"*::arg:->args"'
)
.filter(Boolean);
const commandBlockFunctionParts = [];
if (hasCommands) {
commandBlockFunctionParts.push("local -a commands");
}
if (hasOptions) {
commandBlockFunctionParts.push(`_arguments -C \\\n ${args.join(` \\\n `)}`);
}
if (hasCommands) {
commandBlockFunctionParts.push(
`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`,
);
}
const commandBlocParts = [
`function _${name} {\n ${commandBlockFunctionParts.join('\n\n ')}\n}`,
];
if (hasCommands) {
commandBlocParts.push(
commands.map(c => this.renderCommandBlock(`${name}_${c._name}`, c)).join('\n\n'),
);
}
return commandBlocParts.join('\n\n');
${commands
.map(({ _name }) => [`${_name})`, `_${name}_${_name}`, ";;"].join("\n "))
.join("\n ")}
esac`
);
}
const commandBlocParts = [
`function _${name} {\n ${commandBlockFunctionParts.join("\n\n ")}\n}`,
];
if (hasCommands) {
commandBlocParts.push(
commands.map((c) => this.renderCommandBlock(`${name}_${c._name}`, c)).join("\n\n")
);
}
return commandBlocParts.join("\n\n");
}
}

View File

@ -1,46 +1,54 @@
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) { }
constructor(private environmentService: EnvironmentService) {}
async run(setting: string, value: string, options: program.OptionValues): Promise<Response> {
setting = setting.toLowerCase();
switch (setting) {
case 'server':
return await this.getOrSetServer(value, options);
default:
return Response.badRequest('Unknown setting.');
}
async run(setting: string, value: string, options: program.OptionValues): Promise<Response> {
setting = setting.toLowerCase();
switch (setting) {
case "server":
return await this.getOrSetServer(value, options);
default:
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
) {
const stringRes = new StringResponse(
this.environmentService.hasBaseUrl()
? this.environmentService.getUrls().base
: "https://bitwarden.com"
);
return Response.success(stringRes);
}
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) {
const stringRes = new StringResponse(
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);
await this.environmentService.setUrls({
base: url,
webVault: options.webVault || null,
api: options.api || null,
identity: options.identity || null,
icons: options.icons || null,
notifications: options.notifications || null,
events: options.events || null,
keyConnector: options.keyConnector || null,
});
const res = new MessageResponse('Saved setting `config`.', null);
return Response.success(res);
}
url = url === "null" || url === "bitwarden.com" || url === "https://bitwarden.com" ? null : url;
await this.environmentService.setUrls({
base: url,
webVault: options.webVault || null,
api: options.api || null,
identity: options.identity || null,
icons: options.icons || null,
notifications: options.notifications || null,
events: options.events || null,
keyConnector: options.keyConnector || null,
});
const res = new MessageResponse("Saved setting `config`.", null);
return Response.success(res);
}
}

View File

@ -1,58 +1,58 @@
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) { }
constructor(private apiService: ApiService, private cryptoService: CryptoService) {}
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
switch (object.toLowerCase()) {
case 'org-member':
return await this.confirmOrganizationMember(id, cmd);
default:
return Response.badRequest('Unknown object.');
}
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
private async confirmOrganizationMember(id: string, 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.');
}
if (!Utils.isGuid(options.organizationid)) {
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.');
}
const orgUser = await this.apiService.getOrganizationUser(options.organizationid, id);
if (orgUser == null) {
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);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const req = new OrganizationUserConfirmRequest();
req.key = key.encryptedString;
await this.apiService.postOrganizationUserConfirm(options.organizationid, id, req);
return Response.success();
} catch (e) {
return Response.error(e);
}
switch (object.toLowerCase()) {
case "org-member":
return await this.confirmOrganizationMember(id, cmd);
default:
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 (!Utils.isGuid(id)) {
return Response.error("`" + id + "` is not a GUID.");
}
if (!Utils.isGuid(options.organizationid)) {
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.");
}
const orgUser = await this.apiService.getOrganizationUser(options.organizationid, id);
if (orgUser == null) {
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);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const req = new OrganizationUserConfirmRequest();
req.key = key.encryptedString;
await this.apiService.postOrganizationUserConfirm(options.organizationid, id, req);
return Response.success();
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -1,166 +1,180 @@
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 === '') {
requestJson = await CliUtils.readStdin();
}
async run(object: string, requestJson: string, cmd: program.Command): Promise<Response> {
let req: any = null;
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();
req = JSON.parse(reqJson);
} catch (e) {
return Response.badRequest('Error parsing the encoded request data.');
}
}
switch (object.toLowerCase()) {
case 'item':
return await this.createCipher(req);
case 'attachment':
return await this.createAttachment(cmd);
case 'folder':
return await this.createFolder(req);
case 'org-collection':
return await this.createOrganizationCollection(req, cmd);
default:
return Response.badRequest('Unknown object.');
}
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson);
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
}
}
private async createCipher(req: Cipher) {
const cipher = await this.cipherService.encrypt(Cipher.toView(req));
try {
await this.cipherService.saveWithServer(cipher);
const newCipher = await this.cipherService.get(cipher.id);
const decCipher = await newCipher.decrypt();
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
switch (object.toLowerCase()) {
case "item":
return await this.createCipher(req);
case "attachment":
return await this.createAttachment(cmd);
case "folder":
return await this.createFolder(req);
case "org-collection":
return await this.createOrganizationCollection(req, cmd);
default:
return Response.badRequest("Unknown object.");
}
}
private async createCipher(req: Cipher) {
const cipher = await this.cipherService.encrypt(Cipher.toView(req));
try {
await this.cipherService.saveWithServer(cipher);
const newCipher = await this.cipherService.get(cipher.id);
const decCipher = await newCipher.decrypt();
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
private async createAttachment(options: program.OptionValues) {
if (options.itemid == null || options.itemid === "") {
return Response.badRequest("--itemid <itemid> 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);
}
private async createAttachment(options: program.OptionValues) {
if (options.itemid == null || options.itemid === '') {
return Response.badRequest('--itemid <itemid> 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);
}
const itemId = options.itemid.toLowerCase();
const cipher = await this.cipherService.get(itemId);
if (cipher == null) {
return Response.notFound();
}
if (cipher.organizationId == null && !(await this.userService.canAccessPremium())) {
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/');
}
try {
const fileBuf = fs.readFileSync(filePath);
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);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
const itemId = options.itemid.toLowerCase();
const cipher = await this.cipherService.get(itemId);
if (cipher == null) {
return Response.notFound();
}
private async createFolder(req: Folder) {
const folder = await this.folderService.encrypt(Folder.toView(req));
try {
await this.folderService.saveWithServer(folder);
const newFolder = await this.folderService.get(folder.id);
const decFolder = await newFolder.decrypt();
const res = new FolderResponse(decFolder);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
if (cipher.organizationId == null && !(await this.userService.canAccessPremium())) {
return Response.error("Premium status is required to use this feature.");
}
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.');
}
if (options.organizationid !== req.organizationId) {
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.');
}
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;
request.groups = groups;
const response = await this.apiService.postCollection(req.organizationId, request);
const view = Collection.toView(req);
view.id = response.id;
const res = new OrganizationCollectionResponse(view, groups);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
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/"
);
}
try {
const fileBuf = fs.readFileSync(filePath);
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);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
private async createFolder(req: Folder) {
const folder = await this.folderService.encrypt(Folder.toView(req));
try {
await this.folderService.saveWithServer(folder);
const newFolder = await this.folderService.get(folder.id);
const decFolder = await newFolder.decrypt();
const res = new FolderResponse(decFolder);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
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.");
}
if (options.organizationid !== req.organizationId) {
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.");
}
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;
request.groups = groups;
const response = await this.apiService.postCollection(req.organizationId, request);
const view = Collection.toView(req);
view.id = response.id;
const res = new OrganizationCollectionResponse(view, groups);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -1,116 +1,120 @@
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) {
id = id.toLowerCase();
}
switch (object.toLowerCase()) {
case 'item':
return await this.deleteCipher(id, cmd);
case 'attachment':
return await this.deleteAttachment(id, cmd);
case 'folder':
return await this.deleteFolder(id);
case 'org-collection':
return await this.deleteOrganizationCollection(id, cmd);
default:
return Response.badRequest('Unknown object.');
}
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
private async deleteCipher(id: string, options: program.OptionValues) {
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
}
switch (object.toLowerCase()) {
case "item":
return await this.deleteCipher(id, cmd);
case "attachment":
return await this.deleteAttachment(id, cmd);
case "folder":
return await this.deleteFolder(id);
case "org-collection":
return await this.deleteOrganizationCollection(id, cmd);
default:
return Response.badRequest("Unknown object.");
}
}
try {
if (options.permanent) {
await this.cipherService.deleteWithServer(id);
} else {
await this.cipherService.softDeleteWithServer(id);
}
return Response.success();
} catch (e) {
return Response.error(e);
}
private async deleteCipher(id: string, options: program.OptionValues) {
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
}
private async deleteAttachment(id: string, options: program.OptionValues) {
if (options.itemid == null || options.itemid === '') {
return Response.badRequest('--itemid <itemid> required.');
}
try {
if (options.permanent) {
await this.cipherService.deleteWithServer(id);
} else {
await this.cipherService.softDeleteWithServer(id);
}
return Response.success();
} catch (e) {
return Response.error(e);
}
}
const itemId = options.itemid.toLowerCase();
const cipher = await this.cipherService.get(itemId);
if (cipher == null) {
return Response.notFound();
}
if (cipher.attachments == null || cipher.attachments.length === 0) {
return Response.error('No attachments available for this item.');
}
const attachments = cipher.attachments.filter(a => a.id.toLowerCase() === id);
if (attachments.length === 0) {
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.');
}
try {
await this.cipherService.deleteAttachmentWithServer(cipher.id, attachments[0].id);
return Response.success();
} catch (e) {
return Response.error(e);
}
private async deleteAttachment(id: string, options: program.OptionValues) {
if (options.itemid == null || options.itemid === "") {
return Response.badRequest("--itemid <itemid> required.");
}
private async deleteFolder(id: string) {
const folder = await this.folderService.get(id);
if (folder == null) {
return Response.notFound();
}
try {
await this.folderService.deleteWithServer(id);
return Response.success();
} catch (e) {
return Response.error(e);
}
const itemId = options.itemid.toLowerCase();
const cipher = await this.cipherService.get(itemId);
if (cipher == null) {
return Response.notFound();
}
private async deleteOrganizationCollection(id: string, 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.');
}
if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.');
}
try {
await this.apiService.deleteCollection(options.organizationid, id);
return Response.success();
} catch (e) {
return Response.error(e);
}
if (cipher.attachments == null || cipher.attachments.length === 0) {
return Response.error("No attachments available for this item.");
}
const attachments = cipher.attachments.filter((a) => a.id.toLowerCase() === id);
if (attachments.length === 0) {
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.");
}
try {
await this.cipherService.deleteAttachmentWithServer(cipher.id, attachments[0].id);
return Response.success();
} catch (e) {
return Response.error(e);
}
}
private async deleteFolder(id: string) {
const folder = await this.folderService.get(id);
if (folder == null) {
return Response.notFound();
}
try {
await this.folderService.deleteWithServer(id);
return Response.success();
} catch (e) {
return Response.error(e);
}
}
private async deleteOrganizationCollection(id: string, 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.");
}
if (!Utils.isGuid(options.organizationid)) {
return Response.error("`" + options.organizationid + "` is not a GUID.");
}
try {
await this.apiService.deleteCollection(options.organizationid, id);
return Response.success();
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -1,32 +1,39 @@
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) { }
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' } }));
if (response.status !== 200) {
return Response.error('A ' + response.status + ' error occurred while downloading the attachment.');
}
try {
const buf = await response.arrayBuffer();
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
return await CliUtils.saveResultToFile(Buffer.from(decBuf), output, fileName);
} catch (e) {
if (typeof (e) === 'string') {
return Response.error(e);
} else {
return Response.error('An error occurred while saving the attachment.');
}
}
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."
);
}
try {
const buf = await response.arrayBuffer();
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
return await CliUtils.saveResultToFile(Buffer.from(decBuf), output, fileName);
} catch (e) {
if (typeof e === "string") {
return Response.error(e);
} else {
return Response.error("An error occurred while saving the attachment.");
}
}
}
}

View File

@ -1,164 +1,183 @@
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 === '') {
requestJson = await CliUtils.readStdin();
}
if (requestJson == null || requestJson === '') {
return Response.badRequest('`requestJson` was not provided.');
}
let req: any = null;
try {
const reqJson = Buffer.from(requestJson, 'base64').toString();
req = JSON.parse(reqJson);
} catch (e) {
return Response.badRequest('Error parsing the encoded request data.');
}
if (id != null) {
id = id.toLowerCase();
}
switch (object.toLowerCase()) {
case 'item':
return await this.editCipher(id, req);
case 'item-collections':
return await this.editCipherCollections(id, req);
case 'folder':
return await this.editFolder(id, req);
case 'org-collection':
return await this.editOrganizationCollection(id, req, cmd);
default:
return Response.badRequest('Unknown object.');
}
async run(
object: string,
id: string,
requestJson: string,
cmd: program.Command
): Promise<Response> {
if (requestJson == null || requestJson === "") {
requestJson = await CliUtils.readStdin();
}
private async editCipher(id: string, req: Cipher) {
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
}
let cipherView = await cipher.decrypt();
if (cipherView.isDeleted) {
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);
try {
await this.cipherService.saveWithServer(encCipher);
const updatedCipher = await this.cipherService.get(cipher.id);
const decCipher = await updatedCipher.decrypt();
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
if (requestJson == null || requestJson === "") {
return Response.badRequest("`requestJson` was not provided.");
}
private async editCipherCollections(id: string, req: string[]) {
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
}
if (cipher.organizationId == null) {
return Response.badRequest('Item does not belong to an organization. Consider sharing it first.');
}
cipher.collectionIds = req;
try {
await this.cipherService.saveCollectionsWithServer(cipher);
const updatedCipher = await this.cipherService.get(cipher.id);
const decCipher = await updatedCipher.decrypt();
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
let req: any = null;
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson);
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
}
private async editFolder(id: string, req: Folder) {
const folder = await this.folderService.get(id);
if (folder == null) {
return Response.notFound();
}
let folderView = await folder.decrypt();
folderView = Folder.toView(req, folderView);
const encFolder = await this.folderService.encrypt(folderView);
try {
await this.folderService.saveWithServer(encFolder);
const updatedFolder = await this.folderService.get(folder.id);
const decFolder = await updatedFolder.decrypt();
const res = new FolderResponse(decFolder);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
if (id != null) {
id = id.toLowerCase();
}
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.');
}
if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.');
}
if (options.organizationid !== req.organizationId) {
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.');
}
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;
request.groups = groups;
const response = await this.apiService.putCollection(req.organizationId, id, request);
const view = Collection.toView(req);
view.id = response.id;
const res = new OrganizationCollectionResponse(view, groups);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
switch (object.toLowerCase()) {
case "item":
return await this.editCipher(id, req);
case "item-collections":
return await this.editCipherCollections(id, req);
case "folder":
return await this.editFolder(id, req);
case "org-collection":
return await this.editOrganizationCollection(id, req, cmd);
default:
return Response.badRequest("Unknown object.");
}
}
private async editCipher(id: string, req: Cipher) {
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
}
let cipherView = await cipher.decrypt();
if (cipherView.isDeleted) {
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);
try {
await this.cipherService.saveWithServer(encCipher);
const updatedCipher = await this.cipherService.get(cipher.id);
const decCipher = await updatedCipher.decrypt();
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
private async editCipherCollections(id: string, req: string[]) {
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
}
if (cipher.organizationId == null) {
return Response.badRequest(
"Item does not belong to an organization. Consider sharing it first."
);
}
cipher.collectionIds = req;
try {
await this.cipherService.saveCollectionsWithServer(cipher);
const updatedCipher = await this.cipherService.get(cipher.id);
const decCipher = await updatedCipher.decrypt();
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
private async editFolder(id: string, req: Folder) {
const folder = await this.folderService.get(id);
if (folder == null) {
return Response.notFound();
}
let folderView = await folder.decrypt();
folderView = Folder.toView(req, folderView);
const encFolder = await this.folderService.encrypt(folderView);
try {
await this.folderService.saveWithServer(encFolder);
const updatedFolder = await this.folderService.get(folder.id);
const decFolder = await updatedFolder.decrypt();
const res = new FolderResponse(decFolder);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
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.");
}
if (!Utils.isGuid(options.organizationid)) {
return Response.error("`" + options.organizationid + "` is not a GUID.");
}
if (options.organizationid !== req.organizationId) {
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.");
}
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;
request.groups = groups;
const response = await this.apiService.putCollection(req.organizationId, id, request);
const view = Collection.toView(req);
view.id = response.id;
const res = new OrganizationCollectionResponse(view, groups);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -1,18 +1,18 @@
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.');
}
const input = await CliUtils.readStdin();
const b64 = Buffer.from(input, 'utf8').toString('base64');
const res = new StringResponse(b64);
return Response.success(res);
async run(): Promise<Response> {
if (process.stdin.isTTY) {
return Response.badRequest("No stdin was piped in.");
}
const input = await CliUtils.readStdin();
const b64 = Buffer.from(input, "utf8").toString("base64");
const res = new StringResponse(b64);
return Response.success(res);
}
}

View File

@ -1,111 +1,128 @@
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)) {
return Response.badRequest(
'One or more organization policies prevents you from exporting your personal vault.'
);
}
const canInteract = process.env.BW_NOINTERACTION !== 'true';
if (!canInteract) {
return Response.badRequest('User verification is required. Try running this command again in interactive mode.');
}
try {
await this.keyConnectorService.getUsesKeyConnector()
? await this.verifyOTP()
: await this.verifyMasterPassword(password);
} catch (e) {
return Response.badRequest(e.message);
}
let format = options.format;
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.');
}
let exportContent: string = null;
try {
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 run(password: string, options: program.OptionValues): Promise<Response> {
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."
);
}
async saveFile(exportContent: string, options: program.OptionValues, format: string): Promise<Response> {
try {
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());
}
const canInteract = process.env.BW_NOINTERACTION !== "true";
if (!canInteract) {
return Response.badRequest(
"User verification is required. Try running this command again in interactive mode."
);
}
private getFileName(format: string, prefix?: string) {
if (format === 'encrypted_json') {
if (prefix == null) {
prefix = 'encrypted';
} else {
prefix = 'encrypted_' + prefix;
}
format = 'json';
}
return this.exportService.getFileName(prefix, format);
try {
(await this.keyConnectorService.getUsesKeyConnector())
? await this.verifyOTP()
: await this.verifyMasterPassword(password);
} catch (e) {
return Response.badRequest(e.message);
}
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:',
});
password = answer.password;
}
let format = options.format;
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.");
}
let exportContent: string = null;
try {
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);
}
await this.userVerificationService.verifyUser({
type: VerificationType.MasterPassword,
secret: password,
});
async saveFile(
exportContent: string,
options: program.OptionValues,
format: string
): Promise<Response> {
try {
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());
}
}
private getFileName(format: string, prefix?: string) {
if (format === "encrypted_json") {
if (prefix == null) {
prefix = "encrypted";
} else {
prefix = "encrypted_" + prefix;
}
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:",
});
password = answer.password;
}
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:',
});
await this.userVerificationService.verifyUser({
type: VerificationType.MasterPassword,
secret: password,
});
}
await this.userVerificationService.verifyUser({
type: VerificationType.OTP,
secret: answer.otp,
});
}
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:",
});
await this.userVerificationService.verifyUser({
type: VerificationType.OTP,
secret: answer.otp,
});
}
}

View File

@ -1,45 +1,46 @@
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) { }
constructor(private passwordGenerationService: PasswordGenerationService) {}
async run(cmdOptions: program.OptionValues): Promise<Response> {
const options = {
uppercase: cmdOptions.uppercase || false,
lowercase: cmdOptions.lowercase || false,
number: cmdOptions.number || false,
special: cmdOptions.special || false,
length: cmdOptions.length || 14,
type: cmdOptions.passphrase ? 'passphrase' : 'password',
wordSeparator: cmdOptions.separator == null ? '-' : cmdOptions.separator,
numWords: cmdOptions.words || 3,
capitalize: cmdOptions.capitalize || false,
includeNumber: cmdOptions.includeNumber || false,
};
if (!options.uppercase && !options.lowercase && !options.special && !options.number) {
options.lowercase = true;
options.uppercase = true;
options.number = true;
}
if (options.length < 5) {
options.length = 5;
}
if (options.numWords < 3) {
options.numWords = 3;
}
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 password = await this.passwordGenerationService.generatePassword(enforcedOptions[0]);
const res = new StringResponse(password);
return Response.success(res);
async run(cmdOptions: program.OptionValues): Promise<Response> {
const options = {
uppercase: cmdOptions.uppercase || false,
lowercase: cmdOptions.lowercase || false,
number: cmdOptions.number || false,
special: cmdOptions.special || false,
length: cmdOptions.length || 14,
type: cmdOptions.passphrase ? "passphrase" : "password",
wordSeparator: cmdOptions.separator == null ? "-" : cmdOptions.separator,
numWords: cmdOptions.words || 3,
capitalize: cmdOptions.capitalize || false,
includeNumber: cmdOptions.includeNumber || false,
};
if (!options.uppercase && !options.lowercase && !options.special && !options.number) {
options.lowercase = true;
options.uppercase = true;
options.number = true;
}
if (options.length < 5) {
options.length = 5;
}
if (options.numWords < 3) {
options.numWords = 3;
}
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 password = await this.passwordGenerationService.generatePassword(enforcedOptions[0]);
const res = new StringResponse(password);
return Response.success(res);
}
}

View File

@ -1,497 +1,540 @@
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) {
super(cryptoService);
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);
}
async run(object: string, id: string, options: program.OptionValues): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
async run(object: string, id: string, options: program.OptionValues): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
switch (object.toLowerCase()) {
case "item":
return await this.getCipher(id);
case "username":
return await this.getUsername(id);
case "password":
return await this.getPassword(id);
case "uri":
return await this.getUri(id);
case "totp":
return await this.getTotp(id);
case "notes":
return await this.getNotes(id);
case "exposed":
return await this.getExposed(id);
case "attachment":
return await this.getAttachment(id, options);
case "folder":
return await this.getFolder(id);
case "collection":
return await this.getCollection(id);
case "org-collection":
return await this.getOrganizationCollection(id, options);
case "organization":
return await this.getOrganization(id);
case "template":
return await this.getTemplate(id);
case "fingerprint":
return await this.getFingerprint(id);
default:
return Response.badRequest("Unknown object.");
}
}
switch (object.toLowerCase()) {
case 'item':
return await this.getCipher(id);
case 'username':
return await this.getUsername(id);
case 'password':
return await this.getPassword(id);
case 'uri':
return await this.getUri(id);
case 'totp':
return await this.getTotp(id);
case 'notes':
return await this.getNotes(id);
case 'exposed':
return await this.getExposed(id);
case 'attachment':
return await this.getAttachment(id, options);
case 'folder':
return await this.getFolder(id);
case 'collection':
return await this.getCollection(id);
case 'org-collection':
return await this.getOrganizationCollection(id, options);
case 'organization':
return await this.getOrganization(id);
case 'template':
return await this.getTemplate(id);
case 'fingerprint':
return await this.getFingerprint(id);
default:
return Response.badRequest('Unknown object.');
}
private async getCipherView(id: string): Promise<CipherView | CipherView[]> {
let decCipher: CipherView = null;
if (Utils.isGuid(id)) {
const cipher = await this.cipherService.get(id);
if (cipher != null) {
decCipher = await cipher.decrypt();
}
} else if (id.trim() !== "") {
let ciphers = await this.cipherService.getAllDecrypted();
ciphers = this.searchService.searchCiphersBasic(ciphers, id);
if (ciphers.length > 1) {
return ciphers;
}
if (ciphers.length > 0) {
decCipher = ciphers[0];
}
}
private async getCipherView(id: string): Promise<CipherView | CipherView[]> {
let decCipher: CipherView = null;
if (Utils.isGuid(id)) {
const cipher = await this.cipherService.get(id);
if (cipher != null) {
decCipher = await cipher.decrypt();
}
} else if (id.trim() !== '') {
let ciphers = await this.cipherService.getAllDecrypted();
ciphers = this.searchService.searchCiphersBasic(ciphers, id);
if (ciphers.length > 1) {
return ciphers;
}
if (ciphers.length > 0) {
decCipher = ciphers[0];
}
}
return decCipher;
}
return decCipher;
private async getCipher(id: string, filter?: (c: CipherView) => boolean) {
let decCipher = await this.getCipherView(id);
if (decCipher == null) {
return Response.notFound();
}
if (Array.isArray(decCipher)) {
if (filter != null) {
decCipher = decCipher.filter(filter);
if (decCipher.length === 1) {
decCipher = decCipher[0];
}
}
if (Array.isArray(decCipher)) {
return Response.multipleResults(decCipher.map((c) => c.id));
}
}
const res = new CipherResponse(decCipher);
return Response.success(res);
}
private async getUsername(id: string) {
const cipherResponse = await this.getCipher(
id,
(c) => c.type === CipherType.Login && !Utils.isNullOrWhitespace(c.login.username)
);
if (!cipherResponse.success) {
return cipherResponse;
}
private async getCipher(id: string, filter?: (c: CipherView) => boolean) {
let decCipher = await this.getCipherView(id);
if (decCipher == null) {
return Response.notFound();
}
if (Array.isArray(decCipher)) {
if (filter != null) {
decCipher = decCipher.filter(filter);
if (decCipher.length === 1) {
decCipher = decCipher[0];
}
}
if (Array.isArray(decCipher)) {
return Response.multipleResults(decCipher.map(c => c.id));
}
}
const res = new CipherResponse(decCipher);
return Response.success(res);
const cipher = cipherResponse.data as CipherResponse;
if (cipher.type !== CipherType.Login) {
return Response.badRequest("Not a login.");
}
private async getUsername(id: string) {
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.');
}
if (Utils.isNullOrWhitespace(cipher.login.username)) {
return Response.error('No username available for this login.');
}
const res = new StringResponse(cipher.login.username);
return Response.success(res);
if (Utils.isNullOrWhitespace(cipher.login.username)) {
return Response.error("No username available for this login.");
}
private async getPassword(id: string) {
const cipherResponse = await this.getCipher(id,
c => c.type === CipherType.Login && !Utils.isNullOrWhitespace(c.login.password));
if (!cipherResponse.success) {
return cipherResponse;
}
const res = new StringResponse(cipher.login.username);
return Response.success(res);
}
const cipher = cipherResponse.data as CipherResponse;
if (cipher.type !== CipherType.Login) {
return Response.badRequest('Not a login.');
}
if (Utils.isNullOrWhitespace(cipher.login.password)) {
return Response.error('No password available for this login.');
}
const res = new StringResponse(cipher.login.password);
return Response.success(res);
private async getPassword(id: string) {
const cipherResponse = await this.getCipher(
id,
(c) => c.type === CipherType.Login && !Utils.isNullOrWhitespace(c.login.password)
);
if (!cipherResponse.success) {
return cipherResponse;
}
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 !== '');
if (!cipherResponse.success) {
return cipherResponse;
}
const cipher = cipherResponse.data as CipherResponse;
if (cipher.type !== CipherType.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.');
}
const res = new StringResponse(cipher.login.uris[0].uri);
return Response.success(res);
const cipher = cipherResponse.data as CipherResponse;
if (cipher.type !== CipherType.Login) {
return Response.badRequest("Not a login.");
}
private async getTotp(id: string) {
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.');
}
if (Utils.isNullOrWhitespace(cipher.login.totp)) {
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.');
}
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.');
}
}
const res = new StringResponse(totp);
return Response.success(res);
if (Utils.isNullOrWhitespace(cipher.login.password)) {
return Response.error("No password available for this login.");
}
private async getNotes(id: string) {
const cipherResponse = await this.getCipher(id,
c => !Utils.isNullOrWhitespace(c.notes));
if (!cipherResponse.success) {
return cipherResponse;
}
const res = new StringResponse(cipher.login.password);
return Response.success(res);
}
const cipher = cipherResponse.data as CipherResponse;
if (Utils.isNullOrWhitespace(cipher.notes)) {
return Response.error('No notes available for this item.');
}
const res = new StringResponse(cipher.notes);
return Response.success(res);
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 !== ""
);
if (!cipherResponse.success) {
return cipherResponse;
}
private async getExposed(id: string) {
const passwordResponse = await this.getPassword(id);
if (!passwordResponse.success) {
return passwordResponse;
}
const exposedNumber = await this.auditService.passwordLeaked((passwordResponse.data as StringResponse).data);
const res = new StringResponse(exposedNumber.toString());
return Response.success(res);
const cipher = cipherResponse.data as CipherResponse;
if (cipher.type !== CipherType.Login) {
return Response.badRequest("Not a login.");
}
private async getAttachment(id: string, options: program.OptionValues) {
if (options.itemid == null || options.itemid === '') {
return Response.badRequest('--itemid <itemid> required.');
}
const itemId = options.itemid.toLowerCase();
const cipherResponse = await this.getCipher(itemId);
if (!cipherResponse.success) {
return cipherResponse;
}
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.');
}
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.');
}
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));
}
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.');
}
}
let url: string;
try {
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) {
url = attachments[0].url;
} else if (e instanceof ErrorResponse) {
throw new Error((e as ErrorResponse).getSingleMessage());
} else {
throw e;
}
}
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);
if (
cipher.login.uris == null ||
cipher.login.uris.length === 0 ||
cipher.login.uris[0].uri === ""
) {
return Response.error("No uri available for this login.");
}
private async getFolder(id: string) {
let decFolder: FolderView = null;
if (Utils.isGuid(id)) {
const folder = await this.folderService.get(id);
if (folder != null) {
decFolder = await folder.decrypt();
}
} 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));
}
if (folders.length > 0) {
decFolder = folders[0];
}
}
const res = new StringResponse(cipher.login.uris[0].uri);
return Response.success(res);
}
if (decFolder == null) {
return Response.notFound();
}
const res = new FolderResponse(decFolder);
return Response.success(res);
private async getTotp(id: string) {
const cipherResponse = await this.getCipher(
id,
(c) => c.type === CipherType.Login && !Utils.isNullOrWhitespace(c.login.totp)
);
if (!cipherResponse.success) {
return cipherResponse;
}
private async getCollection(id: string) {
let decCollection: CollectionView = null;
if (Utils.isGuid(id)) {
const collection = await this.collectionService.get(id);
if (collection != null) {
decCollection = await collection.decrypt();
}
} 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));
}
if (collections.length > 0) {
decCollection = collections[0];
}
}
if (decCollection == null) {
return Response.notFound();
}
const res = new CollectionResponse(decCollection);
return Response.success(res);
const cipher = cipherResponse.data as CipherResponse;
if (cipher.type !== CipherType.Login) {
return Response.badRequest("Not a login.");
}
private async getOrganizationCollection(id: string, 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.');
}
if (!Utils.isGuid(options.organizationid)) {
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.');
}
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));
const res = new OrganizationCollectionResponse(decCollection, groups);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
if (Utils.isNullOrWhitespace(cipher.login.totp)) {
return Response.error("No TOTP available for this login.");
}
private async getOrganization(id: string) {
let org: Organization = null;
if (Utils.isGuid(id)) {
org = await this.userService.getOrganization(id);
} 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));
}
if (orgs.length > 0) {
org = orgs[0];
}
}
if (org == null) {
return Response.notFound();
}
const res = new OrganizationResponse(org);
return Response.success(res);
const totp = await this.totpService.getCode(cipher.login.totp);
if (totp == null) {
return Response.error("Couldn't generate TOTP code.");
}
private async getTemplate(id: string) {
let template: any = null;
switch (id.toLowerCase()) {
case 'item':
template = Cipher.template();
break;
case 'item.field':
template = Field.template();
break;
case 'item.login':
template = Login.template();
break;
case 'item.login.uri':
template = LoginUri.template();
break;
case 'item.card':
template = Card.template();
break;
case 'item.identity':
template = Identity.template();
break;
case 'item.securenote':
template = SecureNote.template();
break;
case 'folder':
template = Folder.template();
break;
case 'collection':
template = Collection.template();
break;
case 'item-collections':
template = ['collection-id1', 'collection-id2'];
break;
case 'org-collection':
template = OrganizationCollectionRequest.template();
break;
case 'send.text':
template = SendResponse.template(SendType.Text);
break;
case 'send.file':
template = SendResponse.template(SendType.File);
break;
default:
return Response.badRequest('Unknown template object.');
}
const res = new TemplateResponse(template);
return Response.success(res);
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.");
}
}
private async getFingerprint(id: string) {
let fingerprint: string[] = null;
if (id === 'me') {
fingerprint = await this.cryptoService.getFingerprint(await this.userService.getUserId());
} else if (Utils.isGuid(id)) {
try {
const response = await this.apiService.getUserPublicKey(id);
const pubKey = Utils.fromB64ToArray(response.publicKey);
fingerprint = await this.cryptoService.getFingerprint(id, pubKey.buffer);
} catch { }
}
const res = new StringResponse(totp);
return Response.success(res);
}
if (fingerprint == null) {
return Response.notFound();
}
const res = new StringResponse(fingerprint.join('-'));
return Response.success(res);
private async getNotes(id: string) {
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.");
}
const res = new StringResponse(cipher.notes);
return Response.success(res);
}
private async getExposed(id: string) {
const passwordResponse = await this.getPassword(id);
if (!passwordResponse.success) {
return passwordResponse;
}
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.");
}
const itemId = options.itemid.toLowerCase();
const cipherResponse = await this.getCipher(itemId);
if (!cipherResponse.success) {
return cipherResponse;
}
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.");
}
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.");
}
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));
}
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.");
}
}
let url: string;
try {
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) {
url = attachments[0].url;
} else if (e instanceof ErrorResponse) {
throw new Error((e as ErrorResponse).getSingleMessage());
} else {
throw e;
}
}
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);
}
private async getFolder(id: string) {
let decFolder: FolderView = null;
if (Utils.isGuid(id)) {
const folder = await this.folderService.get(id);
if (folder != null) {
decFolder = await folder.decrypt();
}
} 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));
}
if (folders.length > 0) {
decFolder = folders[0];
}
}
if (decFolder == null) {
return Response.notFound();
}
const res = new FolderResponse(decFolder);
return Response.success(res);
}
private async getCollection(id: string) {
let decCollection: CollectionView = null;
if (Utils.isGuid(id)) {
const collection = await this.collectionService.get(id);
if (collection != null) {
decCollection = await collection.decrypt();
}
} 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));
}
if (collections.length > 0) {
decCollection = collections[0];
}
}
if (decCollection == null) {
return Response.notFound();
}
const res = new CollectionResponse(decCollection);
return Response.success(res);
}
private async getOrganizationCollection(id: string, 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.");
}
if (!Utils.isGuid(options.organizationid)) {
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.");
}
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));
const res = new OrganizationCollectionResponse(decCollection, groups);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
private async getOrganization(id: string) {
let org: Organization = null;
if (Utils.isGuid(id)) {
org = await this.userService.getOrganization(id);
} 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));
}
if (orgs.length > 0) {
org = orgs[0];
}
}
if (org == null) {
return Response.notFound();
}
const res = new OrganizationResponse(org);
return Response.success(res);
}
private async getTemplate(id: string) {
let template: any = null;
switch (id.toLowerCase()) {
case "item":
template = Cipher.template();
break;
case "item.field":
template = Field.template();
break;
case "item.login":
template = Login.template();
break;
case "item.login.uri":
template = LoginUri.template();
break;
case "item.card":
template = Card.template();
break;
case "item.identity":
template = Identity.template();
break;
case "item.securenote":
template = SecureNote.template();
break;
case "folder":
template = Folder.template();
break;
case "collection":
template = Collection.template();
break;
case "item-collections":
template = ["collection-id1", "collection-id2"];
break;
case "org-collection":
template = OrganizationCollectionRequest.template();
break;
case "send.text":
template = SendResponse.template(SendType.Text);
break;
case "send.file":
template = SendResponse.template(SendType.File);
break;
default:
return Response.badRequest("Unknown template object.");
}
const res = new TemplateResponse(template);
return Response.success(res);
}
private async getFingerprint(id: string) {
let fingerprint: string[] = null;
if (id === "me") {
fingerprint = await this.cryptoService.getFingerprint(await this.userService.getUserId());
} else if (Utils.isGuid(id)) {
try {
const response = await this.apiService.getUserPublicKey(id);
const pubKey = Utils.fromB64ToArray(response.publicKey);
fingerprint = await this.cryptoService.getFingerprint(id, pubKey.buffer);
} catch {}
}
if (fingerprint == null) {
return Response.notFound();
}
const res = new StringResponse(fingerprint.join("-"));
return Response.success(res);
}
}

View File

@ -1,72 +1,80 @@
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) { }
constructor(private importService: ImportService, private userService: UserService) {}
async run(format: string, filepath: string, options: program.OptionValues): Promise<Response> {
const organizationId = options.organizationid;
if (organizationId != null) {
const organization = await this.userService.getOrganization(organizationId);
async run(format: string, filepath: string, options: program.OptionValues): Promise<Response> {
const organizationId = options.organizationid;
if (organizationId != null) {
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.`);
}
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.`
);
}
if (!organization.canAccessImportExport) {
return Response.badRequest('You are not authorized to import into the provided organization.');
}
}
if (options.formats || false) {
return await this.list();
} else {
return await this.import(format, filepath, organizationId);
}
if (!organization.canAccessImportExport) {
return Response.badRequest(
"You are not authorized to import into the provided organization."
);
}
}
private async import(format: string, filepath: string, organizationId: string) {
if (format == null || format === '') {
return Response.badRequest('`format` was not provided.');
}
if (filepath == null || filepath === '') {
return Response.badRequest('`filepath` was not provided.');
}
if (options.formats || false) {
return await this.list();
} else {
return await this.import(format, filepath, organizationId);
}
}
const importer = await this.importService.getImporter(format, organizationId);
if (importer === null) {
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.');
}
const err = await this.importService.import(importer, contents, organizationId);
if (err != null) {
return Response.badRequest(err.message);
}
const res = new MessageResponse('Imported ' + filepath, null);
return Response.success(res);
} catch (err) {
return Response.badRequest(err);
}
private async import(format: string, filepath: string, organizationId: string) {
if (format == null || format === "") {
return Response.badRequest("`format` was not provided.");
}
if (filepath == null || filepath === "") {
return Response.badRequest("`filepath` was not provided.");
}
private async list() {
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);
res.raw = options;
return Response.success(res);
const importer = await this.importService.getImporter(format, organizationId);
if (importer === null) {
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.");
}
const err = await this.importService.import(importer, contents, organizationId);
if (err != null) {
return Response.badRequest(err.message);
}
const res = new MessageResponse("Imported " + filepath, null);
return Response.success(res);
} catch (err) {
return Response.badRequest(err);
}
}
private async list() {
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);
res.raw = options;
return Response.success(res);
}
}

View File

@ -1,222 +1,242 @@
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';
CollectionDetailsResponse as ApiCollectionDetailsResponse,
CollectionResponse as ApiCollectionResponse,
} 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':
return await this.listCiphers(cmd);
case 'folders':
return await this.listFolders(cmd);
case 'collections':
return await this.listCollections(cmd);
case 'org-collections':
return await this.listOrganizationCollections(cmd);
case 'org-members':
return await this.listOrganizationMembers(cmd);
case 'organizations':
return await this.listOrganizations(cmd);
default:
return Response.badRequest('Unknown object.');
}
async run(object: string, cmd: program.Command): Promise<Response> {
switch (object.toLowerCase()) {
case "items":
return await this.listCiphers(cmd);
case "folders":
return await this.listFolders(cmd);
case "collections":
return await this.listCollections(cmd);
case "org-collections":
return await this.listOrganizationCollections(cmd);
case "org-members":
return await this.listOrganizationMembers(cmd);
case "organizations":
return await this.listOrganizations(cmd);
default:
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() !== "") {
ciphers = await this.cipherService.getAllDecryptedForUrl(options.url);
} else {
ciphers = await this.cipherService.getAllDecrypted();
}
private async listCiphers(options: program.OptionValues) {
let ciphers: CipherView[];
options.trash = options.trash || false;
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.trash !== c.isDeleted) {
return false;
}
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) {
return true;
}
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) {
return true;
}
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) {
return true;
}
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) {
return true;
}
}
return false;
});
} else if (options.search == null || options.search.trim() === '') {
ciphers = ciphers.filter(c => options.trash === c.isDeleted);
if (options.folderid != null) {
if (options.folderid === "notnull" && c.folderId != null) {
return true;
}
const folderId = options.folderid === "null" ? null : options.folderid;
if (folderId === c.folderId) {
return true;
}
}
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)));
return Response.success(res);
}
private async listFolders(options: program.OptionValues) {
let folders = await this.folderService.getAllDecrypted();
if (options.search != null && options.search.trim() !== '') {
folders = CliUtils.searchFolders(folders, options.search);
}
const res = new ListResponse(folders.map(o => new FolderResponse(o)));
return Response.success(res);
}
private async listCollections(options: program.OptionValues) {
let collections = await this.collectionService.getAllDecrypted();
if (options.organizationid != null) {
collections = collections.filter(c => {
if (options.organizationid === c.organizationId) {
return true;
}
return false;
});
if (options.organizationid === "notnull" && c.organizationId != null) {
return true;
}
const organizationId = options.organizationid === "null" ? null : options.organizationid;
if (organizationId === c.organizationId) {
return true;
}
}
if (options.search != null && options.search.trim() !== '') {
collections = CliUtils.searchCollections(collections, options.search);
if (options.collectionid != null) {
if (
options.collectionid === "notnull" &&
c.collectionIds != null &&
c.collectionIds.length > 0
) {
return true;
}
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
) {
return true;
}
}
const res = new ListResponse(collections.map(o => new CollectionResponse(o)));
return Response.success(res);
return false;
});
} else if (options.search == null || options.search.trim() === "") {
ciphers = ciphers.filter((c) => options.trash === c.isDeleted);
}
private async listOrganizationCollections(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.');
}
const organization = await this.userService.getOrganization(options.organizationid);
if (organization == null) {
return Response.error('Organization not found.');
}
try {
let response: ApiListResponse<ApiCollectionResponse>;
if (organization.canViewAllCollections) {
response = await this.apiService.getCollections(options.organizationid);
} 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)));
let decCollections = await this.collectionService.decryptMany(collections);
if (options.search != null && options.search.trim() !== '') {
decCollections = CliUtils.searchCollections(decCollections, options.search);
}
const res = new ListResponse(decCollections.map(o => new CollectionResponse(o)));
return Response.success(res);
} catch (e) {
return Response.error(e);
}
if (options.search != null && options.search.trim() !== "") {
ciphers = this.searchService.searchCiphersBasic(ciphers, options.search, options.trash);
}
private async listOrganizationMembers(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.');
}
const organization = await this.userService.getOrganization(options.organizationid);
if (organization == null) {
return Response.error('Organization not found.');
}
const res = new ListResponse(ciphers.map((o) => new CipherResponse(o)));
return Response.success(res);
}
try {
const response = await this.apiService.getOrganizationUsers(options.organizationid);
const res = new ListResponse(response.data.map(r => {
const u = new OrganizationUserResponse();
u.email = r.email;
u.name = r.name;
u.id = r.id;
u.status = r.status;
u.type = r.type;
u.twoFactorEnabled = r.twoFactorEnabled;
return u;
}));
return Response.success(res);
} catch (e) {
return Response.error(e);
}
private async listFolders(options: program.OptionValues) {
let folders = await this.folderService.getAllDecrypted();
if (options.search != null && options.search.trim() !== "") {
folders = CliUtils.searchFolders(folders, options.search);
}
private async listOrganizations(options: program.OptionValues) {
let organizations = await this.userService.getAllOrganizations();
const res = new ListResponse(folders.map((o) => new FolderResponse(o)));
return Response.success(res);
}
if (options.search != null && options.search.trim() !== '') {
organizations = CliUtils.searchOrganizations(organizations, options.search);
private async listCollections(options: program.OptionValues) {
let collections = await this.collectionService.getAllDecrypted();
if (options.organizationid != null) {
collections = collections.filter((c) => {
if (options.organizationid === c.organizationId) {
return true;
}
const res = new ListResponse(organizations.map(o => new OrganizationResponse(o)));
return Response.success(res);
return false;
});
}
if (options.search != null && options.search.trim() !== "") {
collections = CliUtils.searchCollections(collections, options.search);
}
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 (!Utils.isGuid(options.organizationid)) {
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.");
}
try {
let response: ApiListResponse<ApiCollectionResponse>;
if (organization.canViewAllCollections) {
response = await this.apiService.getCollections(options.organizationid);
} 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)));
let decCollections = await this.collectionService.decryptMany(collections);
if (options.search != null && options.search.trim() !== "") {
decCollections = CliUtils.searchCollections(decCollections, options.search);
}
const res = new ListResponse(decCollections.map((o) => new CollectionResponse(o)));
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
private async listOrganizationMembers(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.");
}
const organization = await this.userService.getOrganization(options.organizationid);
if (organization == null) {
return Response.error("Organization not found.");
}
try {
const response = await this.apiService.getOrganizationUsers(options.organizationid);
const res = new ListResponse(
response.data.map((r) => {
const u = new OrganizationUserResponse();
u.email = r.email;
u.name = r.name;
u.id = r.id;
u.status = r.status;
u.type = r.type;
u.twoFactorEnabled = r.twoFactorEnabled;
return u;
})
);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
private async listOrganizations(options: program.OptionValues) {
let organizations = await this.userService.getAllOrganizations();
if (options.search != null && options.search.trim() !== "") {
organizations = CliUtils.searchOrganizations(organizations, options.search);
}
const res = new ListResponse(organizations.map((o) => new OrganizationResponse(o)));
return Response.success(res);
}
}

View File

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

View File

@ -1,66 +1,100 @@
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;
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);
this.logout = this.logoutCallback;
this.validatedParams = async () => {
const key = await cryptoFunctionService.randomBytes(64);
process.env.BW_SESSION = Utils.fromBufferToB64(key);
};
this.success = async () => {
const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
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);
process.env.BW_SESSION = Utils.fromBufferToB64(key);
};
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');
return res;
} else {
const res = new MessageResponse('You are logged in!', '\n' +
'To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n' +
'$ export BW_SESSION="' + process.env.BW_SESSION + '"\n' +
'> $env:BW_SESSION="' + process.env.BW_SESSION + '"\n\n' +
'You can also pass the session key to any command with the `--session` option. ex:\n' +
'$ bw list items --session ' + process.env.BW_SESSION);
res.raw = process.env.BW_SESSION;
return res;
}
};
}
if (
(this.options.sso != null || this.options.apikey != null) &&
this.canInteract &&
!usesKeyConnector
) {
const res = new MessageResponse(
"You are logged in!",
"\n" + "To unlock your vault, use the `unlock` command. ex:\n" + "$ bw unlock"
);
return res;
} else {
const res = new MessageResponse(
"You are logged in!",
"\n" +
"To unlock your vault, set your session key to the `BW_SESSION` environment variable. ex:\n" +
'$ export BW_SESSION="' +
process.env.BW_SESSION +
'"\n' +
'> $env:BW_SESSION="' +
process.env.BW_SESSION +
'"\n\n' +
"You can also pass the session key to any command with the `--session` option. ex:\n" +
"$ bw list items --session " +
process.env.BW_SESSION
);
res.raw = process.env.BW_SESSION;
return res;
}
};
}
run(email: string, password: string, options: program.OptionValues) {
this.options = options;
this.email = email;
return super.run(email, password, options);
}
run(email: string, password: string, options: program.OptionValues) {
this.options = options;
this.email = email;
return super.run(email, password, options);
}
}

View File

@ -1,39 +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";
export class RestoreCommand {
constructor(private cipherService: CipherService) { }
constructor(private cipherService: CipherService) {}
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
switch (object.toLowerCase()) {
case 'item':
return await this.restoreCipher(id, cmd);
default:
return Response.badRequest('Unknown object.');
}
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
private async restoreCipher(id: string, cmd: program.Command) {
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
}
if (cipher.deletedDate == null) {
return Response.badRequest('Cipher is not in trash.');
}
try {
await this.cipherService.restoreWithServer(id);
return Response.success();
} catch (e) {
return Response.error(e);
}
switch (object.toLowerCase()) {
case "item":
return await this.restoreCipher(id, cmd);
default:
return Response.badRequest("Unknown object.");
}
}
private async restoreCipher(id: string, cmd: program.Command) {
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
}
if (cipher.deletedDate == null) {
return Response.badRequest("Cipher is not in trash.");
}
try {
await this.cipherService.restoreWithServer(id);
return Response.success();
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -1,114 +1,129 @@
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 === '') {
requestJson = await CliUtils.readStdin();
}
if (requestJson == null || requestJson === '') {
return Response.badRequest('`requestJson` was not provided.');
}
try {
const reqJson = Buffer.from(requestJson, 'base64').toString();
req = SendResponse.fromJson(reqJson);
if (req == null) {
throw new Error('Null request');
}
} catch (e) {
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.expirationDate != null && isNaN(new Date(req.expirationDate).getTime())) {
return Response.badRequest('Unable to parse expirationDate: ' + req.expirationDate);
}
return this.createSend(req, options);
async run(requestJson: string, options: program.OptionValues) {
let req: any = null;
if (requestJson == null || requestJson === "") {
requestJson = await CliUtils.readStdin();
}
private async createSend(req: SendResponse, options: program.OptionValues) {
const filePath = req.file?.fileName ?? options.file;
const text = req.text?.text ?? options.text;
const hidden = req.text?.hidden ?? options.hidden;
const password = req.password ?? options.password;
const maxAccessCount = req.maxAccessCount ?? options.maxAccessCount;
req.key = null;
req.maxAccessCount = maxAccessCount;
switch (req.type) {
case SendType.File:
if (!(await this.userService.canAccessPremium())) {
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');
}
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');
}
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');
}
try {
let fileBuffer: ArrayBuffer = null;
if (req.type === SendType.File) {
fileBuffer = NodeUtils.bufferToArrayBuffer(fs.readFileSync(filePath));
}
const sendView = SendResponse.toView(req);
const [encSend, fileData] = await this.sendService.encrypt(sendView, fileBuffer, password);
// Add dates from template
encSend.deletionDate = sendView.deletionDate;
encSend.expirationDate = sendView.expirationDate;
await this.sendService.saveWithServer([encSend, fileData]);
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));
} catch (e) {
return Response.error(e);
}
if (requestJson == null || requestJson === "") {
return Response.badRequest("`requestJson` was not provided.");
}
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = SendResponse.fromJson(reqJson);
if (req == null) {
throw new Error("Null request");
}
} catch (e) {
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.expirationDate != null && isNaN(new Date(req.expirationDate).getTime())) {
return Response.badRequest("Unable to parse expirationDate: " + req.expirationDate);
}
return this.createSend(req, options);
}
private async createSend(req: SendResponse, options: program.OptionValues) {
const filePath = req.file?.fileName ?? options.file;
const text = req.text?.text ?? options.text;
const hidden = req.text?.hidden ?? options.hidden;
const password = req.password ?? options.password;
const maxAccessCount = req.maxAccessCount ?? options.maxAccessCount;
req.key = null;
req.maxAccessCount = maxAccessCount;
switch (req.type) {
case SendType.File:
if (!(await this.userService.canAccessPremium())) {
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"
);
}
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"
);
}
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"
);
}
try {
let fileBuffer: ArrayBuffer = null;
if (req.type === SendType.File) {
fileBuffer = NodeUtils.bufferToArrayBuffer(fs.readFileSync(filePath));
}
const sendView = SendResponse.toView(req);
const [encSend, fileData] = await this.sendService.encrypt(sendView, fileBuffer, password);
// Add dates from template
encSend.deletionDate = sendView.deletionDate;
encSend.expirationDate = sendView.expirationDate;
await this.sendService.saveWithServer([encSend, fileData]);
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)
);
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -1,22 +1,22 @@
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) { }
constructor(private sendService: SendService) {}
async run(id: string) {
const send = await this.sendService.get(id);
async run(id: string) {
const send = await this.sendService.get(id);
if (send == null) {
return Response.notFound();
}
try {
this.sendService.deleteWithServer(id);
return Response.success();
} catch (e) {
return Response.error(e);
}
if (send == null) {
return Response.notFound();
}
try {
this.sendService.deleteWithServer(id);
return Response.success();
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -1,76 +1,79 @@
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 === '') {
encodedJson = await CliUtils.readStdin();
}
if (encodedJson == null || encodedJson === '') {
return Response.badRequest('`encodedJson` was not provided.');
}
let req: SendResponse = null;
try {
const reqJson = Buffer.from(encodedJson, 'base64').toString();
req = SendResponse.fromJson(reqJson);
} catch (e) {
return Response.badRequest('Error parsing the encoded request data.');
}
req.id = options.itemid || req.id;
if (req.id != null) {
req.id = req.id.toLowerCase();
}
const send = await this.sendService.get(req.id);
if (send == null) {
return Response.notFound();
}
if (send.type !== req.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.');
}
let sendView = await send.decrypt();
sendView = SendResponse.toView(req, sendView);
if (typeof (req.password) !== 'string' || req.password === '') {
req.password = null;
}
try {
const [encSend, encFileData] = await this.sendService.encrypt(sendView, null, req.password);
// Add dates from template
encSend.deletionDate = sendView.deletionDate;
encSend.expirationDate = sendView.expirationDate;
await this.sendService.saveWithServer([encSend, encFileData]);
} catch (e) {
return Response.error(e);
}
return await this.getCommand.run(send.id, {});
async run(encodedJson: string, options: program.OptionValues): Promise<Response> {
if (encodedJson == null || encodedJson === "") {
encodedJson = await CliUtils.readStdin();
}
if (encodedJson == null || encodedJson === "") {
return Response.badRequest("`encodedJson` was not provided.");
}
let req: SendResponse = null;
try {
const reqJson = Buffer.from(encodedJson, "base64").toString();
req = SendResponse.fromJson(reqJson);
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
}
req.id = options.itemid || req.id;
if (req.id != null) {
req.id = req.id.toLowerCase();
}
const send = await this.sendService.get(req.id);
if (send == null) {
return Response.notFound();
}
if (send.type !== req.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.");
}
let sendView = await send.decrypt();
sendView = SendResponse.toView(req, sendView);
if (typeof req.password !== "string" || req.password === "") {
req.password = null;
}
try {
const [encSend, encFileData] = await this.sendService.encrypt(sendView, null, req.password);
// Add dates from template
encSend.deletionDate = sendView.deletionDate;
encSend.expirationDate = sendView.expirationDate;
await this.sendService.saveWithServer([encSend, encFileData]);
} catch (e) {
return Response.error(e);
}
return await this.getCommand.run(send.id, {});
}
}

View File

@ -1,79 +1,83 @@
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) {
super(cryptoService);
constructor(
private sendService: SendService,
private environmentService: EnvironmentService,
private searchService: SearchService,
cryptoService: CryptoService
) {
super(cryptoService);
}
async run(id: string, options: program.OptionValues) {
let sends = await this.getSendView(id);
if (sends == null) {
return Response.notFound();
}
async run(id: string, options: program.OptionValues) {
let sends = await this.getSendView(id);
if (sends == null) {
return Response.notFound();
}
const webVaultUrl = this.environmentService.getWebVaultUrl();
let filter = (s: SendView) => true;
let selector = async (s: SendView): Promise<Response> => Response.success(new SendResponse(s, webVaultUrl));
if (options.text != null) {
filter = s => {
return filter(s) && s.text != null;
};
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();
};
}
if (Array.isArray(sends)) {
if (filter != null) {
sends = sends.filter(filter);
}
if (sends.length > 1) {
return Response.multipleResults(sends.map(s => s.id));
}
if (sends.length > 0) {
return selector(sends[0]);
}
else {
return Response.notFound();
}
}
return selector(sends);
const webVaultUrl = this.environmentService.getWebVaultUrl();
let filter = (s: SendView) => true;
let selector = async (s: SendView): Promise<Response> =>
Response.success(new SendResponse(s, webVaultUrl));
if (options.text != null) {
filter = (s) => {
return filter(s) && s.text != null;
};
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();
};
}
private async getSendView(id: string): Promise<SendView | SendView[]> {
if (Utils.isGuid(id)) {
const send = await this.sendService.get(id);
if (send != null) {
return await send.decrypt();
}
} else if (id.trim() !== '') {
let sends = await this.sendService.getAllDecrypted();
sends = this.searchService.searchSends(sends, id);
if (sends.length > 1) {
return sends;
} else if (sends.length > 0) {
return sends[0];
}
}
if (Array.isArray(sends)) {
if (filter != null) {
sends = sends.filter(filter);
}
if (sends.length > 1) {
return Response.multipleResults(sends.map((s) => s.id));
}
if (sends.length > 0) {
return selector(sends[0]);
} else {
return Response.notFound();
}
}
return selector(sends);
}
private async getSendView(id: string): Promise<SendView | SendView[]> {
if (Utils.isGuid(id)) {
const send = await this.sendService.get(id);
if (send != null) {
return await send.decrypt();
}
} else if (id.trim() !== "") {
let sends = await this.sendService.getAllDecrypted();
sends = this.searchService.searchSends(sends, id);
if (sends.length > 1) {
return sends;
} else if (sends.length > 0) {
return sends[0];
}
}
}
}

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();
async run(options: program.OptionValues): Promise<Response> {
let sends = await this.sendService.getAllDecrypted();
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)));
return Response.success(res);
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)));
return Response.success(res);
}
}

View File

@ -1,151 +1,175 @@
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;
private canInteract: boolean;
private decKey: SymmetricCryptoKey;
private sendAccessRequest: SendAccessRequest;
constructor(private apiService: ApiService, cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService, private platformUtilsService: PlatformUtilsService,
private environmentService: EnvironmentService) {
super(cryptoService);
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";
let urlObject: URL;
try {
urlObject = new URL(url);
} catch (e) {
return Response.badRequest("Failed to parse the provided Send url");
}
async run(url: string, options: program.OptionValues): Promise<Response> {
this.canInteract = process.env.BW_NOINTERACTION !== 'true';
const apiUrl = this.getApiUrl(urlObject);
const [id, key] = this.getIdAndKey(urlObject);
let urlObject: URL;
try {
urlObject = new URL(url);
} catch (e) {
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');
}
const keyArray = Utils.fromUrlB64ToArray(key);
this.sendAccessRequest = new SendAccessRequest();
let password = options.password;
if (password == null || password === '') {
if (options.passwordfile) {
password = await NodeUtils.readFirstLine(options.passwordfile);
} else if (options.passwordenv && process.env[options.passwordenv]) {
password = process.env[options.passwordenv];
}
}
if (password != null && password !== '') {
this.sendAccessRequest.password = await this.getUnlockedPassword(password, keyArray);
}
const response = await this.sendRequest(apiUrl, id, keyArray);
if (response instanceof Response) {
// Error scenario
return response;
}
if (options.obj != null) {
return Response.success(new SendAccessResponse(response));
}
switch (response.type) {
case SendType.Text:
// Write to stdout and response success so we get the text string only to stdout
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);
default:
return Response.success(new SendAccessResponse(response));
}
if (Utils.isNullOrWhitespace(id) || Utils.isNullOrWhitespace(key)) {
return Response.badRequest("Failed to parse url, the url provided is not a valid Send url");
}
private getIdAndKey(url: URL): [string, string] {
const result = url.hash.slice(1).split('/').slice(-2);
return [result[0], result[1]];
const keyArray = Utils.fromUrlB64ToArray(key);
this.sendAccessRequest = new SendAccessRequest();
let password = options.password;
if (password == null || password === "") {
if (options.passwordfile) {
password = await NodeUtils.readFirstLine(options.passwordfile);
} else if (options.passwordenv && process.env[options.passwordenv]) {
password = process.env[options.passwordenv];
}
}
private getApiUrl(url: URL) {
const urls = this.environmentService.getUrls();
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';
}
}
private async getUnlockedPassword(password: string, keyArray: ArrayBuffer) {
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> {
try {
const sendResponse = await this.apiService.postSendAccess(id, this.sendAccessRequest, url);
const sendAccess = new SendAccess(sendResponse);
this.decKey = await this.cryptoService.makeSendKey(key);
return await sendAccess.decrypt(this.decKey);
} catch (e) {
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:',
});
// reattempt with new password
this.sendAccessRequest.password = await this.getUnlockedPassword(answer.password, key);
return await this.sendRequest(url, id, key);
}
return Response.badRequest('Incorrect or missing password');
} else if (e.statusCode === 405) {
return Response.badRequest('Bad Request');
} else if (e.statusCode === 404) {
return Response.notFound();
}
}
return Response.error(e);
if (password != null && password !== "") {
this.sendAccessRequest.password = await this.getUnlockedPassword(password, keyArray);
}
const response = await this.sendRequest(apiUrl, id, keyArray);
if (response instanceof Response) {
// Error scenario
return response;
}
if (options.obj != null) {
return Response.success(new SendAccessResponse(response));
}
switch (response.type) {
case SendType.Text:
// Write to stdout and response success so we get the text string only to stdout
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
);
default:
return Response.success(new SendAccessResponse(response));
}
}
private getIdAndKey(url: URL): [string, string] {
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";
} 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";
}
}
private async getUnlockedPassword(password: string, keyArray: ArrayBuffer) {
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> {
try {
const sendResponse = await this.apiService.postSendAccess(id, this.sendAccessRequest, url);
const sendAccess = new SendAccess(sendResponse);
this.decKey = await this.cryptoService.makeSendKey(key);
return await sendAccess.decrypt(this.decKey);
} catch (e) {
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:",
});
// reattempt with new password
this.sendAccessRequest.password = await this.getUnlockedPassword(answer.password, key);
return await this.sendRequest(url, id, key);
}
return Response.badRequest("Incorrect or missing password");
} else if (e.statusCode === 405) {
return Response.badRequest("Bad Request");
} else if (e.statusCode === 404) {
return Response.notFound();
}
}
return Response.error(e);
}
}
}

View File

@ -1,22 +1,22 @@
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) { }
constructor(private sendService: SendService) {}
async run(id: string) {
try {
await this.sendService.removePasswordWithServer(id);
async run(id: string) {
try {
await this.sendService.removePasswordWithServer(id);
const updatedSend = await this.sendService.get(id);
const decSend = await updatedSend.decrypt();
const res = new SendResponse(decSend);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
const updatedSend = await this.sendService.get(id);
const decSend = await updatedSend.decrypt();
const res = new SendResponse(decSend);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -1,59 +1,64 @@
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) { }
constructor(private cipherService: CipherService) {}
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.');
}
let req: string[] = [];
try {
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.');
}
} catch (e) {
return Response.badRequest('Error parsing the encoded request data.');
}
if (id != null) {
id = id.toLowerCase();
}
if (organizationId != null) {
organizationId = organizationId.toLowerCase();
}
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
}
if (cipher.organizationId != null) {
return Response.badRequest('This item already belongs to an organization.');
}
const cipherView = await cipher.decrypt();
try {
await this.cipherService.shareWithServer(cipherView, organizationId, req);
const updatedCipher = await this.cipherService.get(cipher.id);
const decCipher = await updatedCipher.decrypt();
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
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.");
}
let req: string[] = [];
try {
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.");
}
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
}
if (id != null) {
id = id.toLowerCase();
}
if (organizationId != null) {
organizationId = organizationId.toLowerCase();
}
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
}
if (cipher.organizationId != null) {
return Response.badRequest("This item already belongs to an organization.");
}
const cipherView = await cipher.decrypt();
try {
await this.cipherService.shareWithServer(cipherView, organizationId, req);
const updatedCipher = await this.cipherService.get(cipher.id);
const decCipher = await updatedCipher.decrypt();
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -1,50 +1,55 @@
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 {
const baseUrl = this.baseUrl();
const status = await this.status();
const lastSync = await this.syncService.getLastSync();
const userId = await this.userService.getUserId();
const email = await this.userService.getEmail();
return Response.success(
new TemplateResponse({
serverUrl: baseUrl,
lastSync: lastSync,
userEmail: email,
userId: userId,
status: status,
})
);
} catch (e) {
return Response.error(e);
}
}
private baseUrl(): string {
return this.envService.getUrls().base;
}
private async status(): Promise<string> {
const authed = await this.userService.isAuthenticated();
if (!authed) {
return "unauthenticated";
}
async run(): Promise<Response> {
try {
const baseUrl = this.baseUrl();
const status = await this.status();
const lastSync = await this.syncService.getLastSync();
const userId = await this.userService.getUserId();
const email = await this.userService.getEmail();
return Response.success(new TemplateResponse({
serverUrl: baseUrl,
lastSync: lastSync,
userEmail: email,
userId: userId,
status: status,
}));
} catch (e) {
return Response.error(e);
}
}
private baseUrl(): string {
return this.envService.getUrls().base;
}
private async status(): Promise<string> {
const authed = await this.userService.isAuthenticated();
if (!authed) {
return 'unauthenticated';
}
const isLocked = await this.vaultTimeoutService.isLocked();
return isLocked ? 'locked' : 'unlocked';
}
const isLocked = await this.vaultTimeoutService.isLocked();
return isLocked ? "locked" : "unlocked";
}
}

View File

@ -1,31 +1,31 @@
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) { }
constructor(private syncService: SyncService) {}
async run(options: program.OptionValues): Promise<Response> {
if (options.last || false) {
return await this.getLastSync();
}
try {
const result = await this.syncService.fullSync(options.force || false, true);
const res = new MessageResponse('Syncing complete.', null);
return Response.success(res);
} catch (e) {
return Response.error('Syncing failed: ' + e.toString());
}
async run(options: program.OptionValues): Promise<Response> {
if (options.last || false) {
return await this.getLastSync();
}
private async getLastSync() {
const lastSyncDate = await this.syncService.getLastSync();
const res = new StringResponse(lastSyncDate == null ? null : lastSyncDate.toISOString());
return Response.success(res);
try {
const result = await this.syncService.fullSync(options.force || false, true);
const res = new MessageResponse("Syncing complete.", null);
return Response.success(res);
} catch (e) {
return Response.error("Syncing failed: " + e.toString());
}
}
private async getLastSync() {
const lastSyncDate = await this.syncService.getLastSync();
const res = new StringResponse(lastSyncDate == null ? null : lastSyncDate.toISOString());
return Response.success(res);
}
}

View File

@ -1,98 +1,120 @@
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 === '') {
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`);
}
}
}
if (password == null || password === '') {
if (canInteract) {
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.');
}
}
this.setNewSessionKey();
const email = await this.userService.getEmail();
const kdf = await this.userService.getKdf();
const kdfIterations = await this.userService.getKdfIterations();
const key = await this.cryptoService.makeKey(password, email, kdf, kdfIterations);
const storedKeyHash = await this.cryptoService.getKeyHash();
let passwordValid = false;
if (key != null) {
if (storedKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, key);
} else {
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);
await this.cryptoService.setKeyHash(localKeyHash);
} catch { }
}
}
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);
res.raw = process.env.BW_SESSION;
return Response.success(res);
async run(password: string, options: program.OptionValues) {
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 {
return Response.error('Invalid master password.');
this.logService.warning(
`Warning: Provided passwordenv ${options.passwordenv} is not set`
);
}
}
}
private async setNewSessionKey() {
const key = await this.cryptoFunctionService.randomBytes(64);
process.env.BW_SESSION = Utils.fromBufferToB64(key);
if (password == null || password === "") {
if (canInteract) {
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.");
}
}
this.setNewSessionKey();
const email = await this.userService.getEmail();
const kdf = await this.userService.getKdf();
const kdfIterations = await this.userService.getKdfIterations();
const key = await this.cryptoService.makeKey(password, email, kdf, kdfIterations);
const storedKeyHash = await this.cryptoService.getKeyHash();
let passwordValid = false;
if (key != null) {
if (storedKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, key);
} else {
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
);
await this.cryptoService.setKeyHash(localKeyHash);
} catch {}
}
}
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
);
res.raw = process.env.BW_SESSION;
return Response.success(res);
} else {
return Response.error("Invalid master password.");
}
}
private async setNewSessionKey() {
const key = await this.cryptoFunctionService.randomBytes(64);
process.env.BW_SESSION = Utils.fromBufferToB64(key);
}
}

View File

@ -1,16 +1,16 @@
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.externalId = null;
req.groups = [SelectionReadOnly.template(), SelectionReadOnly.template()];
return req;
}
static template(): OrganizationCollectionRequest {
const req = new OrganizationCollectionRequest();
req.organizationId = "00000000-0000-0000-0000-000000000000";
req.name = "Collection name";
req.externalId = null;
req.groups = [SelectionReadOnly.template(), SelectionReadOnly.template()];
return req;
}
groups: SelectionReadOnly[];
groups: SelectionReadOnly[];
}

View File

@ -1,17 +1,17 @@
import { AttachmentView } from 'jslib-common/models/view/attachmentView';
import { AttachmentView } from "jslib-common/models/view/attachmentView";
export class AttachmentResponse {
id: string;
fileName: string;
size: string;
sizeName: string;
url: string;
id: string;
fileName: string;
size: string;
sizeName: string;
url: string;
constructor(o: AttachmentView) {
this.id = o.id;
this.fileName = o.fileName;
this.size = o.size;
this.sizeName = o.sizeName;
this.url = o.url;
}
constructor(o: AttachmentView) {
this.id = o.id;
this.fileName = o.fileName;
this.size = o.size;
this.sizeName = o.sizeName;
this.url = o.url;
}
}

View File

@ -1,33 +1,33 @@
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;
attachments: AttachmentResponse[];
revisionDate: Date;
passwordHistory: PasswordHistoryResponse[];
object: string;
attachments: AttachmentResponse[];
revisionDate: Date;
passwordHistory: PasswordHistoryResponse[];
constructor(o: CipherView) {
super();
this.object = 'item';
this.build(o);
if (o.attachments != null) {
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));
}
if (o.type === CipherType.Login && o.login != null) {
this.login = new LoginResponse(o.login);
}
constructor(o: CipherView) {
super();
this.object = "item";
this.build(o);
if (o.attachments != null) {
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));
}
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;
object: string;
constructor(o: CollectionView) {
super();
this.object = 'collection';
this.build(o);
}
constructor(o: CollectionView) {
super();
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;
object: string;
constructor(o: FolderView) {
super();
this.object = 'folder';
this.build(o);
}
constructor(o: FolderView) {
super();
this.object = "folder";
this.build(o);
}
}

View File

@ -1,11 +1,11 @@
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;
passwordRevisionDate: Date;
constructor(o: LoginView) {
super(o);
this.passwordRevisionDate = o.passwordRevisionDate != null ? o.passwordRevisionDate : null;
}
constructor(o: LoginView) {
super(o);
this.passwordRevisionDate = o.passwordRevisionDate != null ? o.passwordRevisionDate : null;
}
}

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[];
groups: SelectionReadOnly[];
constructor(o: CollectionView, groups: SelectionReadOnly[]) {
super(o);
this.object = 'org-collection';
this.groups = groups;
}
constructor(o: CollectionView, groups: SelectionReadOnly[]) {
super(o);
this.object = "org-collection";
this.groups = groups;
}
}

View File

@ -1,24 +1,24 @@
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;
id: string;
name: string;
status: OrganizationUserStatusType;
type: OrganizationUserType;
enabled: boolean;
object: string;
id: string;
name: string;
status: OrganizationUserStatusType;
type: OrganizationUserType;
enabled: boolean;
constructor(o: Organization) {
this.object = 'organization';
this.id = o.id;
this.name = o.name;
this.status = o.status;
this.type = o.type;
this.enabled = o.enabled;
}
constructor(o: Organization) {
this.object = "organization";
this.id = o.id;
this.name = o.name;
this.status = o.status;
this.type = o.type;
this.enabled = o.enabled;
}
}

View File

@ -1,18 +1,18 @@
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;
id: string;
email: string;
name: string;
status: OrganizationUserStatusType;
type: OrganizationUserType;
twoFactorEnabled: boolean;
object: string;
id: string;
email: string;
name: string;
status: OrganizationUserStatusType;
type: OrganizationUserType;
twoFactorEnabled: boolean;
constructor() {
this.object = 'org-member';
}
constructor() {
this.object = "org-member";
}
}

View File

@ -1,11 +1,11 @@
import { PasswordHistoryView } from 'jslib-common/models/view/passwordHistoryView';
import { PasswordHistoryView } from "jslib-common/models/view/passwordHistoryView";
export class PasswordHistoryResponse {
lastUsedDate: Date;
password: string;
lastUsedDate: Date;
password: string;
constructor(o: PasswordHistoryView) {
this.lastUsedDate = o.lastUsedDate;
this.password = o.password;
}
constructor(o: PasswordHistoryView) {
this.lastUsedDate = o.lastUsedDate;
this.password = o.password;
}
}

View File

@ -1,42 +1,42 @@
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.type = SendType.Text;
req.text = null;
req.file = null;
return req;
static template(): SendAccessResponse {
const req = new SendAccessResponse();
req.name = "Send name";
req.type = SendType.Text;
req.text = null;
req.file = null;
return req;
}
object = "send-access";
id: string;
name: string;
type: SendType;
text: SendTextResponse;
file: SendFileResponse;
constructor(o?: SendAccessView) {
if (o == null) {
return;
}
this.id = o.id;
this.name = o.name;
this.type = o.type;
object = 'send-access';
id: string;
name: string;
type: SendType;
text: SendTextResponse;
file: SendFileResponse;
constructor(o?: SendAccessView) {
if (o == null) {
return;
}
this.id = o.id;
this.name = o.name;
this.type = o.type;
if (o.type === SendType.Text && o.text != null) {
this.text = new SendTextResponse(o.text);
}
if (o.type === SendType.File && o.file != null) {
this.file = new SendFileResponse(o.file);
}
if (o.type === SendType.Text && o.text != null) {
this.text = new SendTextResponse(o.text);
}
if (o.type === SendType.File && o.file != null) {
this.file = new SendFileResponse(o.file);
}
}
}

View File

@ -1,36 +1,36 @@
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 {
const req = new SendFileResponse();
req.fileName = fileName;
return req;
static template(fileName = "file attachment location"): SendFileResponse {
const req = new SendFileResponse();
req.fileName = fileName;
return req;
}
static toView(file: SendFileResponse, view = new SendFileView()) {
if (file == null) {
return null;
}
static toView(file: SendFileResponse, view = new SendFileView()) {
if (file == null) {
return null;
}
view.id = file.id;
view.size = file.size;
view.sizeName = file.sizeName;
view.fileName = file.fileName;
return view;
}
view.id = file.id;
view.size = file.size;
view.sizeName = file.sizeName;
view.fileName = file.fileName;
return view;
}
id: string;
size: string;
sizeName: string;
fileName: string;
constructor(o?: SendFileView) {
if (o == null) {
return;
}
this.id = o.id;
this.size = o.size;
this.sizeName = o.sizeName;
this.fileName = o.fileName;
id: string;
size: string;
sizeName: string;
fileName: string;
constructor(o?: SendFileView) {
if (o == null) {
return;
}
this.id = o.id;
this.size = o.size;
this.sizeName = o.sizeName;
this.fileName = o.fileName;
}
}

View File

@ -1,125 +1,126 @@
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.type = sendType === SendType.File ? SendType.File : SendType.Text;
req.text = sendType === SendType.Text ? SendTextResponse.template() : null;
req.file = sendType === SendType.File ? SendFileResponse.template() : null;
req.maxAccessCount = null;
req.deletionDate = this.getStandardDeletionDate(deleteInDays);
req.expirationDate = null;
req.password = null;
req.disabled = false;
req.hideEmail = false;
return req;
}
static template(sendType?: SendType, deleteInDays = 7): SendResponse {
const req = new SendResponse();
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;
req.maxAccessCount = null;
req.deletionDate = this.getStandardDeletionDate(deleteInDays);
req.expirationDate = null;
req.password = null;
req.disabled = false;
req.hideEmail = false;
return req;
static toView(send: SendResponse, view = new SendView()): SendView {
if (send == null) {
return null;
}
static toView(send: SendResponse, view = new SendView()): SendView {
if (send == null) {
return null;
}
view.id = send.id;
view.accessId = send.accessId;
view.name = send.name;
view.notes = send.notes;
view.key = send.key == null ? null : Utils.fromB64ToArray(send.key);
view.type = send.type;
view.file = SendFileResponse.toView(send.file);
view.text = SendTextResponse.toView(send.text);
view.maxAccessCount = send.maxAccessCount;
view.accessCount = send.accessCount;
view.revisionDate = send.revisionDate;
view.deletionDate = send.deletionDate;
view.expirationDate = send.expirationDate;
view.password = send.password;
view.disabled = send.disabled;
view.hideEmail = send.hideEmail;
return view;
}
view.id = send.id;
view.accessId = send.accessId;
view.name = send.name;
view.notes = send.notes;
view.key = send.key == null ? null : Utils.fromB64ToArray(send.key);
view.type = send.type;
view.file = SendFileResponse.toView(send.file);
view.text = SendTextResponse.toView(send.text);
view.maxAccessCount = send.maxAccessCount;
view.accessCount = send.accessCount;
view.revisionDate = send.revisionDate;
view.deletionDate = send.deletionDate;
view.expirationDate = send.expirationDate;
view.password = send.password;
view.disabled = send.disabled;
view.hideEmail = send.hideEmail;
return view;
static fromJson(json: string) {
return JSON.parse(json, (key, value) => {
if (dateProperties.includes(key)) {
return value == null ? null : new Date(value);
}
return value;
});
}
private static getStandardDeletionDate(days: number) {
const d = new Date();
d.setTime(d.getTime() + days * 86400000); // ms per day
return d;
}
object = "send";
id: string;
accessId: string;
accessUrl: string;
name: string;
notes: string;
key: string;
type: SendType;
text: SendTextResponse;
file: SendFileResponse;
maxAccessCount?: number;
accessCount: number;
revisionDate: Date;
deletionDate: Date;
expirationDate: Date;
password: string;
passwordSet: boolean;
disabled: boolean;
hideEmail: boolean;
constructor(o?: SendView, webVaultUrl?: string) {
if (o == null) {
return;
}
static fromJson(json: string) {
return JSON.parse(json, (key, value) => {
if (dateProperties.includes(key)) {
return value == null ? null : new Date(value);
}
return value;
});
this.id = o.id;
this.accessId = o.accessId;
let sendLinkBaseUrl = webVaultUrl;
if (sendLinkBaseUrl == null) {
sendLinkBaseUrl = "https://send.bitwarden.com/#";
} else {
sendLinkBaseUrl += "/#/send/";
}
this.accessUrl = sendLinkBaseUrl + this.accessId + "/" + o.urlB64Key;
this.name = o.name;
this.notes = o.notes;
this.key = Utils.fromBufferToB64(o.key);
this.type = o.type;
this.maxAccessCount = o.maxAccessCount;
this.accessCount = o.accessCount;
this.revisionDate = o.revisionDate;
this.deletionDate = o.deletionDate;
this.expirationDate = o.expirationDate;
this.passwordSet = o.password != null;
this.disabled = o.disabled;
this.hideEmail = o.hideEmail;
private static getStandardDeletionDate(days: number) {
const d = new Date();
d.setTime(d.getTime() + (days * 86400000)); // ms per day
return d;
if (o.type === SendType.Text && o.text != null) {
this.text = new SendTextResponse(o.text);
}
object = 'send';
id: string;
accessId: string;
accessUrl: string;
name: string;
notes: string;
key: string;
type: SendType;
text: SendTextResponse;
file: SendFileResponse;
maxAccessCount?: number;
accessCount: number;
revisionDate: Date;
deletionDate: Date;
expirationDate: Date;
password: string;
passwordSet: boolean;
disabled: boolean;
hideEmail: boolean;
constructor(o?: SendView, webVaultUrl?: string) {
if (o == null) {
return;
}
this.id = o.id;
this.accessId = o.accessId;
let sendLinkBaseUrl = webVaultUrl;
if (sendLinkBaseUrl == null) {
sendLinkBaseUrl = 'https://send.bitwarden.com/#';
} else {
sendLinkBaseUrl += '/#/send/';
}
this.accessUrl = sendLinkBaseUrl + this.accessId + '/' + o.urlB64Key;
this.name = o.name;
this.notes = o.notes;
this.key = Utils.fromBufferToB64(o.key);
this.type = o.type;
this.maxAccessCount = o.maxAccessCount;
this.accessCount = o.accessCount;
this.revisionDate = o.revisionDate;
this.deletionDate = o.deletionDate;
this.expirationDate = o.expirationDate;
this.passwordSet = o.password != null;
this.disabled = o.disabled;
this.hideEmail = o.hideEmail;
if (o.type === SendType.Text && o.text != null) {
this.text = new SendTextResponse(o.text);
}
if (o.type === SendType.File && o.file != null) {
this.file = new SendFileResponse(o.file);
}
if (o.type === SendType.File && o.file != null) {
this.file = new SendFileResponse(o.file);
}
}
}

View File

@ -1,30 +1,30 @@
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 {
const req = new SendTextResponse();
req.text = text;
req.hidden = hidden;
return req;
static template(text = "Text contained in the send.", hidden = false): SendTextResponse {
const req = new SendTextResponse();
req.text = text;
req.hidden = hidden;
return req;
}
static toView(text: SendTextResponse, view = new SendTextView()) {
if (text == null) {
return null;
}
static toView(text: SendTextResponse, view = new SendTextView()) {
if (text == null) {
return null;
}
view.text = text.text;
view.hidden = text.hidden;
return view;
}
text: string;
hidden: boolean;
view.text = text.text;
view.hidden = text.hidden;
return view;
}
text: string;
hidden: boolean;
constructor(o?: SendTextView) {
if (o == null) {
return;
}
this.text = o.text;
this.hidden = o.hidden;
constructor(o?: SendTextView) {
if (o == null) {
return;
}
this.text = o.text;
this.hidden = o.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;
object: string;
template: any;
constructor(template: any) {
this.object = 'template';
this.template = template;
}
constructor(template: any) {
this.object = "template";
this.template = template;
}
}

View File

@ -1,15 +1,15 @@
export class SelectionReadOnly {
static template(): SelectionReadOnly {
return new SelectionReadOnly('00000000-0000-0000-0000-000000000000', false, false);
}
static template(): SelectionReadOnly {
return new SelectionReadOnly("00000000-0000-0000-0000-000000000000", false, false);
}
id: string;
readOnly: boolean;
hidePasswords: boolean;
id: string;
readOnly: boolean;
hidePasswords: boolean;
constructor(id: string, readOnly: boolean, hidePasswords: boolean) {
this.id = id;
this.readOnly = readOnly;
this.hidePasswords = hidePasswords || false;
}
constructor(id: string, readOnly: boolean, hidePasswords: boolean) {
this.id = id;
this.readOnly = readOnly;
this.hidePasswords = hidePasswords || false;
}
}

View File

@ -1,450 +1,508 @@
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;
export class Program extends BaseProgram {
constructor(protected main: Main) {
super(main.userService, writeLn);
}
constructor(protected main: Main) {
super(main.userService, writeLn);
}
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');
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");
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 => {
process.env.BW_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);
process.exitCode = 1;
});
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');
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 send "text to send"');
writeLn(' echo "text to send" | bw send');
writeLn(' bw receive https://vault.bitwarden.com/#/send/rg3iuoS_Akm2gqy6ADRHmg/Ht7dYjsqjmgqUM3rjzZDSQ');
writeLn('', true);
});
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 send "text to send"');
writeLn(' echo "text to send" | bw send');
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 () => {
const authed = await this.main.userService.isAuthenticated();
if (authed) {
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);
})
.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 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);
})
.action(async cmd => {
await this.exitIfNotAuthed();
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);
})
.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());
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);
return;
}
const command = new LockCommand(this.main.vaultTimeoutService);
const response = await command.run(cmd);
this.processResponse(response);
});
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);
})
.option('--check', 'Check lock status.', async () => {
const locked = await this.main.vaultTimeoutService.isLocked();
if (!locked) {
const res = new MessageResponse('Vault is unlocked!', null);
this.processResponse(Response.success(res), 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')
.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 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);
})
.action(async cmd => {
await this.exitIfLocked();
const command = new SyncCommand(this.main.syncService);
const response = await command.run(cmd);
this.processResponse(response);
});
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);
})
.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('');
writeLn(' echo \'{"name":"My Folder"}\' | bw encode');
writeLn('', true);
})
.action(async () => {
const command = new EncodeCommand();
const response = await command.run();
this.processResponse(response);
});
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);
})
.action(async (setting, value, options) => {
const command = new ConfigCommand(this.main.environmentService);
const response = await command.run(setting, value, options);
this.processResponse(response);
});
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);
})
.action(async () => {
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);
})
.action(async (options: program.OptionValues, cmd: program.Command) => {
const command = new CompletionCommand();
const response = await command.run(options);
this.processResponse(response);
});
program
.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);
})
.action(async () => {
const command = new StatusCommand(
this.main.environmentService,
this.main.syncService,
this.main.userService,
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') {
return this.getJson((response.data as TemplateResponse).template);
}
return null;
});
}
protected async exitIfLocked() {
await this.exitIfNotAuthed();
const hasKey = await this.main.cryptoService.hasKey();
if (!hasKey) {
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.');
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 response = await command.run(null, null);
if (!response.success) {
this.processResponse(response, true);
}
}
} else {
this.processResponse(Response.error('Vault is locked.'), true);
}
} else if (!this.main.cryptoService.hasKeyInMemory()) {
await this.main.cryptoService.getKey();
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 () => {
const authed = await this.main.userService.isAuthenticated();
if (authed) {
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);
})
.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 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);
})
.action(async (cmd) => {
await this.exitIfNotAuthed();
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);
})
.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()
);
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
);
return;
}
const command = new LockCommand(this.main.vaultTimeoutService);
const response = await command.run(cmd);
this.processResponse(response);
});
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);
})
.option("--check", "Check lock status.", async () => {
const locked = await this.main.vaultTimeoutService.isLocked();
if (!locked) {
const res = new MessageResponse("Vault is unlocked!", null);
this.processResponse(Response.success(res), 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"
)
.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 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);
})
.action(async (cmd) => {
await this.exitIfLocked();
const command = new SyncCommand(this.main.syncService);
const response = await command.run(cmd);
this.processResponse(response);
});
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);
})
.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("");
writeLn(' echo \'{"name":"My Folder"}\' | bw encode');
writeLn("", true);
})
.action(async () => {
const command = new EncodeCommand();
const response = await command.run();
this.processResponse(response);
});
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);
})
.action(async (setting, value, options) => {
const command = new ConfigCommand(this.main.environmentService);
const response = await command.run(setting, value, options);
this.processResponse(response);
});
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);
})
.action(async () => {
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);
})
.action(async (options: program.OptionValues, cmd: program.Command) => {
const command = new CompletionCommand();
const response = await command.run(options);
this.processResponse(response);
});
program
.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);
})
.action(async () => {
const command = new StatusCommand(
this.main.environmentService,
this.main.syncService,
this.main.userService,
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") {
return this.getJson((response.data as TemplateResponse).template);
}
return null;
});
}
protected async exitIfLocked() {
await this.exitIfNotAuthed();
const hasKey = await this.main.cryptoService.hasKey();
if (!hasKey) {
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."
);
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 response = await command.run(null, null);
if (!response.success) {
this.processResponse(response, true);
}
}
} else {
this.processResponse(Response.error("Vault is locked."), true);
}
} else if (!this.main.cryptoService.hasKeyInMemory()) {
await this.main.cryptoService.getKey();
}
}
}

View File

@ -1,280 +1,338 @@
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;
export class SendProgram extends Program {
constructor(main: Main) {
super(main);
}
constructor(main: Main) {
super(main);
}
async register() {
program.addCommand(this.sendCommand());
// receive is accessible both at `bw receive` and `bw send receive`
program.addCommand(this.receiveCommand());
}
async register() {
program.addCommand(this.sendCommand());
// receive is accessible both at `bw receive` and `bw send receive`
program.addCommand(this.receiveCommand());
}
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.')
.addCommand(this.listCommand())
.addCommand(this.templateCommand())
.addCommand(this.getCommand())
.addCommand(this.receiveCommand())
.addCommand(this.createCommand())
.addCommand(this.editCommand())
.addCommand(this.removePasswordCommand())
.addCommand(this.deleteCommand())
.action(async (data: string, options: program.OptionValues) => {
const encodedJson = this.makeSendJson(data, options);
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."
)
.addCommand(this.listCommand())
.addCommand(this.templateCommand())
.addCommand(this.getCommand())
.addCommand(this.receiveCommand())
.addCommand(this.createCommand())
.addCommand(this.editCommand())
.addCommand(this.removePasswordCommand())
.addCommand(this.deleteCommand())
.action(async (data: string, options: program.OptionValues) => {
const encodedJson = this.makeSendJson(data, options);
let response: Response;
if (encodedJson instanceof Response) {
response = encodedJson;
} else {
response = await this.runCreate(encodedJson, options);
}
this.processResponse(response);
});
}
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);
})
.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 response = await cmd.run(url, options);
this.processResponse(response);
});
}
private listCommand(): program.Command {
return new program.Command('list')
.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 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',
})
.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);
})
.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 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.',
})
.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 }) => {
// 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
const { fullObject = false } = args.parent.opts();
const mergedOptions = {
...options,
fullObject: fullObject,
};
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.',
})
.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 cmd = new SendEditCommand(this.main.sendService, this.main.userService, getCmd);
const response = await cmd.run(encodedJson, options);
this.processResponse(response);
});
}
private deleteCommand(): program.Command {
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();
const cmd = new SendDeleteCommand(this.main.sendService);
const response = await cmd.run(id);
this.processResponse(response);
});
}
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.',
})
.action(async (id: string) => {
await this.exitIfLocked();
const cmd = new SendRemovePasswordCommand(this.main.sendService);
const response = await cmd.run(id);
this.processResponse(response);
});
}
private makeSendJson(data: string, options: program.OptionValues) {
let sendFile = null;
let sendText = null;
let name = Utils.newGuid();
let type = SendType.Text;
if (options.file != null) {
data = path.resolve(data);
if (!fs.existsSync(data)) {
return Response.badRequest('data path does not exist');
}
sendFile = SendFileResponse.template(data);
name = path.basename(data);
type = SendType.File;
let response: Response;
if (encodedJson instanceof Response) {
response = encodedJson;
} else {
sendText = SendTextResponse.template(data, options.hidden);
response = await this.runCreate(encodedJson, options);
}
const template = Utils.assign(SendResponse.template(null, options.deleteInDays), {
name: options.name ?? name,
notes: options.notes,
file: sendFile,
text: sendText,
type: type,
});
this.processResponse(response);
});
}
return Buffer.from(JSON.stringify(template), 'utf8').toString('base64');
}
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 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 response = await cmd.run(url, options);
this.processResponse(response);
});
}
private async runCreate(encodedJson: string, options: program.OptionValues) {
private listCommand(): program.Command {
return new program.Command("list")
.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 SendCreateCommand(this.main.sendService, this.main.userService,
this.main.environmentService);
return await cmd.run(encodedJson, options);
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",
})
.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);
})
.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 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.",
})
.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 }
) => {
// 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
const { fullObject = false } = args.parent.opts();
const mergedOptions = {
...options,
fullObject: fullObject,
};
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.",
})
.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 cmd = new SendEditCommand(this.main.sendService, this.main.userService, getCmd);
const response = await cmd.run(encodedJson, options);
this.processResponse(response);
});
}
private deleteCommand(): program.Command {
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();
const cmd = new SendDeleteCommand(this.main.sendService);
const response = await cmd.run(id);
this.processResponse(response);
});
}
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.",
})
.action(async (id: string) => {
await this.exitIfLocked();
const cmd = new SendRemovePasswordCommand(this.main.sendService);
const response = await cmd.run(id);
this.processResponse(response);
});
}
private makeSendJson(data: string, options: program.OptionValues) {
let sendFile = null;
let sendText = null;
let name = Utils.newGuid();
let type = SendType.Text;
if (options.file != null) {
data = path.resolve(data);
if (!fs.existsSync(data)) {
return Response.badRequest("data path does not exist");
}
sendFile = SendFileResponse.template(data);
name = path.basename(data);
type = SendType.File;
} else {
sendText = SendTextResponse.template(data, options.hidden);
}
const template = Utils.assign(SendResponse.template(null, options.deleteInDays), {
name: options.name ?? name,
notes: options.notes,
file: sendFile,
text: sendText,
type: type,
});
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
);
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
return Promise.resolve(locales);
});
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
return Promise.resolve(locales);
});
this.supportedTranslationLocales = [
'en',
];
}
this.supportedTranslationLocales = ["en"];
}
}

View File

@ -1,93 +1,100 @@
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));
if (value == null) {
return null;
}
const obj = await this.decrypt(value);
return obj as any;
async get<T>(key: string): Promise<T> {
const value = await this.storageService.get<string>(this.makeProtectedStorageKey(key));
if (value == null) {
return null;
}
const obj = await this.decrypt(value);
return obj as any;
}
async has(key: string): Promise<boolean> {
return (await this.get(key)) != null;
}
async save(key: string, obj: any): Promise<any> {
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);
}
remove(key: string): Promise<any> {
return this.storageService.remove(this.makeProtectedStorageKey(key));
}
private async encrypt(plainValue: string): Promise<string> {
const sessionKey = this.getSessionKey();
if (sessionKey == null) {
throw new Error("No session key available.");
}
const encValue = await this.cryptoService().encryptToBytes(
Utils.fromB64ToArray(plainValue).buffer,
sessionKey
);
if (encValue == null) {
throw new Error("Value didn't encrypt.");
}
async has(key: string): Promise<boolean> {
return (await this.get(key)) != null;
}
async save(key: string, obj: any): Promise<any> {
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);
}
remove(key: string): Promise<any> {
return this.storageService.remove(this.makeProtectedStorageKey(key));
}
private async encrypt(plainValue: string): Promise<string> {
const sessionKey = this.getSessionKey();
if (sessionKey == null) {
throw new Error('No session key available.');
}
const encValue = await this.cryptoService().encryptToBytes(
Utils.fromB64ToArray(plainValue).buffer, sessionKey);
if (encValue == null) {
throw new Error('Value didn\'t encrypt.');
}
return Utils.fromBufferToB64(encValue.buffer);
}
private async decrypt(encValue: string): Promise<string> {
try {
const sessionKey = this.getSessionKey();
if (sessionKey == null) {
return null;
}
const decValue = await this.cryptoService().decryptFromBytes(
Utils.fromB64ToArray(encValue).buffer, sessionKey);
if (decValue == null) {
this.logService.info('Failed to decrypt.');
return null;
}
return Utils.fromBufferToB64(decValue);
} catch (e) {
this.logService.info('Decrypt error.');
return null;
}
}
private getSessionKey() {
try {
if (process.env.BW_SESSION != null) {
const sessionBuffer = Utils.fromB64ToArray(process.env.BW_SESSION).buffer;
if (sessionBuffer != null) {
const sessionKey = new SymmetricCryptoKey(sessionBuffer);
if (sessionBuffer != null) {
return sessionKey;
}
}
}
} catch (e) {
this.logService.info('Session key is invalid.');
}
return Utils.fromBufferToB64(encValue.buffer);
}
private async decrypt(encValue: string): Promise<string> {
try {
const sessionKey = this.getSessionKey();
if (sessionKey == null) {
return null;
}
const decValue = await this.cryptoService().decryptFromBytes(
Utils.fromB64ToArray(encValue).buffer,
sessionKey
);
if (decValue == null) {
this.logService.info("Failed to decrypt.");
return null;
}
return Utils.fromBufferToB64(decValue);
} catch (e) {
this.logService.info("Decrypt error.");
return null;
}
}
private getSessionKey() {
try {
if (process.env.BW_SESSION != null) {
const sessionBuffer = Utils.fromB64ToArray(process.env.BW_SESSION).buffer;
if (sessionBuffer != null) {
const sessionKey = new SymmetricCryptoKey(sessionBuffer);
if (sessionBuffer != null) {
return sessionKey;
}
}
}
} catch (e) {
this.logService.info("Session key is invalid.");
}
private makeProtectedStorageKey(key: string) {
return '__PROTECTED__' + key;
}
return null;
}
private makeProtectedStorageKey(key: string) {
return "__PROTECTED__" + key;
}
}

View File

@ -1,173 +1,173 @@
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)) {
stream.write(s);
static writeLn(s: string, finalLine: boolean = false, error: boolean = false) {
const stream = error ? process.stderr : process.stdout;
if (finalLine && (process.platform === "win32" || !stream.isTTY)) {
stream.write(s);
} else {
stream.write(s + "\n");
}
}
static readFile(input: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
let p: string = null;
if (input != null && input !== "") {
const osInput = path.join(input);
if (osInput.indexOf(path.sep) === -1) {
p = path.join(process.cwd(), osInput);
} else {
stream.write(s + '\n');
p = osInput;
}
}
} else {
reject("You must specify a file path.");
}
fs.readFile(p, "utf8", (err, data) => {
if (err != null) {
reject(err.message);
}
resolve(data);
});
});
}
static readFile(input: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
let p: string = null;
if (input != null && input !== '') {
const osInput = path.join(input);
if (osInput.indexOf(path.sep) === -1) {
p = path.join(process.cwd(), osInput);
} else {
p = osInput;
}
} else {
reject('You must specify a file path.');
}
fs.readFile(p, 'utf8', (err, data) => {
if (err != null) {
reject(err.message);
}
resolve(data);
});
});
}
/**
* Save the given data to a file and determine the target file if necessary.
* If output is non-empty, it is used as target filename. Otherwise the target filename is
* built from the current working directory and the given defaultFileName.
*
* @param data to be written to the file.
* @param output file to write to or empty to choose automatically.
* @param defaultFileName to use when no explicit output filename is given.
* @return the chosen output file.
*/
static saveFile(data: string | Buffer, output: string, defaultFileName: string) {
let p: string = null;
let mkdir = false;
if (output != null && output !== '') {
const osOutput = path.join(output);
if (osOutput.indexOf(path.sep) === -1) {
p = path.join(process.cwd(), osOutput);
} else {
mkdir = true;
if (osOutput.endsWith(path.sep)) {
p = path.join(osOutput, defaultFileName);
} else {
p = osOutput;
}
}
/**
* Save the given data to a file and determine the target file if necessary.
* If output is non-empty, it is used as target filename. Otherwise the target filename is
* built from the current working directory and the given defaultFileName.
*
* @param data to be written to the file.
* @param output file to write to or empty to choose automatically.
* @param defaultFileName to use when no explicit output filename is given.
* @return the chosen output file.
*/
static saveFile(data: string | Buffer, output: string, defaultFileName: string) {
let p: string = null;
let mkdir = false;
if (output != null && output !== "") {
const osOutput = path.join(output);
if (osOutput.indexOf(path.sep) === -1) {
p = path.join(process.cwd(), osOutput);
} else {
mkdir = true;
if (osOutput.endsWith(path.sep)) {
p = path.join(osOutput, defaultFileName);
} else {
p = path.join(process.cwd(), defaultFileName);
p = osOutput;
}
}
} else {
p = path.join(process.cwd(), defaultFileName);
}
p = path.resolve(p);
if (mkdir) {
const dir = p.substring(0, p.lastIndexOf(path.sep));
if (!fs.existsSync(dir)) {
NodeUtils.mkdirpSync(dir, '700');
}
p = path.resolve(p);
if (mkdir) {
const dir = p.substring(0, p.lastIndexOf(path.sep));
if (!fs.existsSync(dir)) {
NodeUtils.mkdirpSync(dir, "700");
}
}
return new Promise<string>((resolve, reject) => {
fs.writeFile(p, data, { encoding: "utf8", mode: 0o600 }, (err) => {
if (err != null) {
reject("Cannot save file to " + p);
}
resolve(p);
});
});
}
return new Promise<string>((resolve, reject) => {
fs.writeFile(p, data, { encoding: 'utf8', mode: 0o600 }, err => {
if (err != null) {
reject('Cannot save file to ' + p);
}
resolve(p);
});
});
/**
* Process the given data and write it to a file if possible. If the user requested RAW output and
* no output name is given, the file is directly written to stdout. The resulting Response contains
* an otherwise empty message then to prevent writing other information to stdout.
*
* If an output is given or no RAW output is requested, the rules from [saveFile] apply.
*
* @param data to be written to the file or stdout.
* @param output file to write to or empty to choose automatically.
* @param defaultFileName to use when no explicit output filename is given.
* @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") {
// 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);
return Response.success();
}
/**
* Process the given data and write it to a file if possible. If the user requested RAW output and
* no output name is given, the file is directly written to stdout. The resulting Response contains
* an otherwise empty message then to prevent writing other information to stdout.
*
* If an output is given or no RAW output is requested, the rules from [saveFile] apply.
*
* @param data to be written to the file or stdout.
* @param output file to write to or empty to choose automatically.
* @param defaultFileName to use when no explicit output filename is given.
* @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') {
// 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);
return Response.success();
const filePath = await this.saveFile(data, output, defaultFileName);
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 = "";
if (process.stdin.isTTY) {
resolve(input);
return;
}
process.stdin.setEncoding("utf8");
process.stdin.on("readable", () => {
while (true) {
const chunk = process.stdin.read();
if (chunk == null) {
break;
}
input += chunk;
}
});
const filePath = await this.saveFile(data, output, defaultFileName);
const res = new MessageResponse('Saved ' + filePath, null);
res.raw = filePath;
return Response.success(res);
}
process.stdin.on("end", () => {
resolve(input);
});
});
}
static readStdin(): Promise<string> {
return new Promise((resolve, reject) => {
let input: string = '';
static searchFolders(folders: FolderView[], search: string) {
search = search.toLowerCase();
return folders.filter((f) => {
if (f.name != null && f.name.toLowerCase().indexOf(search) > -1) {
return true;
}
return false;
});
}
if (process.stdin.isTTY) {
resolve(input);
return;
}
static searchCollections(collections: CollectionView[], search: string) {
search = search.toLowerCase();
return collections.filter((c) => {
if (c.name != null && c.name.toLowerCase().indexOf(search) > -1) {
return true;
}
return false;
});
}
process.stdin.setEncoding('utf8');
process.stdin.on('readable', () => {
while (true) {
const chunk = process.stdin.read();
if (chunk == null) {
break;
}
input += chunk;
}
});
process.stdin.on('end', () => {
resolve(input);
});
});
}
static searchFolders(folders: FolderView[], search: string) {
search = search.toLowerCase();
return folders.filter(f => {
if (f.name != null && f.name.toLowerCase().indexOf(search) > -1) {
return true;
}
return false;
});
}
static searchCollections(collections: CollectionView[], search: string) {
search = search.toLowerCase();
return collections.filter(c => {
if (c.name != null && c.name.toLowerCase().indexOf(search) > -1) {
return true;
}
return false;
});
}
static searchOrganizations(organizations: Organization[], search: string) {
search = search.toLowerCase();
return organizations.filter(o => {
if (o.name != null && o.name.toLowerCase().indexOf(search) > -1) {
return true;
}
return false;
});
}
static searchOrganizations(organizations: Organization[], search: string) {
search = search.toLowerCase();
return organizations.filter((o) => {
if (o.name != null && o.name.toLowerCase().indexOf(search) > -1) {
return true;
}
return false;
});
}
}

View File

@ -1,433 +1,493 @@
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;
export class VaultProgram extends Program {
constructor(protected main: Main) {
super(main);
}
constructor(protected main: Main) {
super(main);
}
async register() {
program
.addCommand(this.listCommand())
.addCommand(this.getCommand())
.addCommand(this.createCommand())
.addCommand(this.editCommand())
.addCommand(this.deleteCommand())
.addCommand(this.restoreCommand())
.addCommand(this.shareCommand('move', false))
.addCommand(this.confirmCommand())
.addCommand(this.importCommand())
.addCommand(this.exportCommand())
.addCommand(this.shareCommand('share', true));
}
async register() {
program
.addCommand(this.listCommand())
.addCommand(this.getCommand())
.addCommand(this.createCommand())
.addCommand(this.editCommand())
.addCommand(this.deleteCommand())
.addCommand(this.restoreCommand())
.addCommand(this.shareCommand("move", false))
.addCommand(this.confirmCommand())
.addCommand(this.importCommand())
.addCommand(this.exportCommand())
.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(', ') + '.'));
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(", ") +
"."
)
);
}
return success;
}
private listCommand(): program.Command {
const listObjects = [
"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(", "),
})
.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)) {
return;
}
return success;
}
private listCommand(): program.Command {
const listObjects = [
'items',
'folders',
'collections',
'org-collections',
'org-members',
'organizations',
];
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 response = await command.run(object, cmd);
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);
})
.action(async (object, cmd) => {
if (!this.validateObject(object, listObjects)) {
return;
}
this.processResponse(response);
});
}
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 response = await command.run(object, cmd);
private getCommand(): program.Command {
const getObjects = [
"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`.",
})
.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)) {
return;
}
this.processResponse(response);
});
}
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 response = await command.run(object, id, cmd);
this.processResponse(response);
});
}
private getCommand(): program.Command {
const getObjects = [
'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`.',
})
.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)) {
return;
}
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.",
})
.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)) {
return;
}
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 response = await command.run(object, id, cmd);
this.processResponse(response);
});
}
await this.exitIfLocked();
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 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.',
})
.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)) {
return;
}
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.",
})
.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)) {
return;
}
await this.exitIfLocked();
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);
});
}
await this.exitIfLocked();
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 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.',
})
.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)) {
return;
}
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`.",
})
.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)) {
return;
}
await this.exitIfLocked();
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);
});
}
await this.exitIfLocked();
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 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`.',
})
.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)) {
return;
}
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`.",
})
.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)) {
return;
}
await this.exitIfLocked();
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);
});
}
await this.exitIfLocked();
const command = new RestoreCommand(this.main.cipherService);
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`.',
})
.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)) {
return;
}
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.",
})
.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('--DEPRECATED See "bw move" for the current implementation--');
}
writeLn("", true);
})
.action(async (id, organizationId, encodedJson, cmd) => {
await this.exitIfLocked();
const command = new ShareCommand(this.main.cipherService);
const response = await command.run(id, organizationId, encodedJson, cmd);
this.processResponse(response);
});
}
await this.exitIfLocked();
const command = new RestoreCommand(this.main.cipherService);
const response = await command.run(object, id, cmd);
this.processResponse(response);
});
}
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`.",
})
.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)) {
return;
}
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.',
})
.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('--DEPRECATED See "bw move" for the current implementation--');
}
writeLn('', true);
})
.action(async (id, organizationId, encodedJson, cmd) => {
await this.exitIfLocked();
const command = new ShareCommand(this.main.cipherService);
const response = await command.run(id, organizationId, encodedJson, cmd);
this.processResponse(response);
});
}
await this.exitIfLocked();
const command = new ConfirmCommand(this.main.apiService, this.main.cryptoService);
const response = await command.run(object, id, cmd);
this.processResponse(response);
});
}
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`.',
})
.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)) {
return;
}
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",
})
.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();
const command = new ImportCommand(this.main.importService, this.main.userService);
const response = await command.run(format, filepath, options);
this.processResponse(response);
});
}
await this.exitIfLocked();
const command = new ConfirmCommand(this.main.apiService, this.main.cryptoService);
const response = await command.run(object, id, cmd);
this.processResponse(response);
});
}
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',
})
.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();
const command = new ImportCommand(this.main.importService, this.main.userService);
const response = await command.run(format, filepath, options);
this.processResponse(response);
});
}
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.',
})
.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 response = await command.run(password, options);
this.processResponse(response);
});
}
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.",
})
.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 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,
@ -78,7 +50,7 @@
],
"max-classes-per-file": false,
"ordered-imports": true,
"arrow-parens": [ true ],
"arrow-parens": [true],
"trailing-comma": [
true,
{

View File

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