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:
parent
ec53a16c00
commit
910b4a24e6
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
11
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -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)
|
||||
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -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
28
.vscode/launch.json
vendored
@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
510
src/bw.ts
@ -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();
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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, {});
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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[];
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
908
src/program.ts
908
src/program.ts
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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"];
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
304
src/utils.ts
304
src/utils.ts
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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"]
|
||||
}
|
||||
|
48
tslint.json
48
tslint.json
@ -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,
|
||||
{
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user