1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-30 13:03:53 +01:00

Add send to cli (#222)

* Add list all sends and filter by search term

* Add get send templates

* Add AccessUrl to send responses

* Add Send to Get command

* Add missing command options to login

These options are already coded to work in the command, but commander
did not know about the options.

* Upgrade Commander to 7.0.0

This is needed to enable the subcommand chaining required by Send.

This commit also adds get send and send receive functionality. get send
will be moved to send get along with send list and any other send commands.

* Use api url for send access url

* Move send commands to send subcommands

* Use webvault access url everywhere

Production instances all have api url located at `baseUrl/api`.
Receive command will parse the webvault url and alter it to an api url.

* Move create and receive commands to send directory

* Separate program concerns

program holds authentication/general program concerns
vault.program holds commands related to the vault
send.program holds commands related to Bitwarden Send

* Fix up imports and lint items

* Add edit command

* Use browser-hrtime

* Add send examples to help text

* Clean up receive help text

* correct help text

* Add delete command

* Code review Cleanup

* Scheme on send receive help text

* PR review items

Move buffer to array buffer to jslib
delete with server
some formatting fixes

* Add remove password command

This is the simplest way to enable removing passwords without
resorting to weird type parsing of piped in Send JSONs in edit

* Default hidden to false like web

* Do not allow password updates that aren't strings or are empty

* Delete appveyor.yml.flagged-for-delete

* Correctly order imports and include tslint rule

* fix npm globbing problem

https://stackoverflow.com/a/34594501
globs work differently in package.json. Encasing the globs in
single quotes expands them in shell rather than in npm

* Remove double slash in path

* Trigger github rebuild
This commit is contained in:
Matt Gibson 2021-02-03 11:44:33 -06:00 committed by GitHub
parent b88091c41f
commit 57f7cf607a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1759 additions and 726 deletions

View File

@ -1,169 +0,0 @@
image:
- Visual Studio 2017
- Ubuntu1804
branches:
except:
- l10n_master
environment:
WIN_PKG: C:\Users\appveyor\.pkg-cache\v2.5\fetched-v10.4.1-win-x64
stack: node 10
init:
- ps: |
if($isWindows -and $env:DEBUG_RDP -eq "true") {
iex ((new-object net.webclient).DownloadString(`
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
}
- ps: |
if($isWindows) {
Install-Product node 10
$env:PATH = "C:\Program Files (x86)\Resource Hacker;${env:PATH}"
}
if($env:APPVEYOR_REPO_TAG -eq "true") {
$tagName = $env:APPVEYOR_REPO_TAG_NAME.TrimStart("v")
$env:RELEASE_NAME = "Version ${tagName}"
}
install:
- ps: |
$env:PACKAGE_VERSION = (Get-Content -Raw -Path .\package.json | ConvertFrom-Json).version
$env:PROD_DEPLOY = "false"
if($env:APPVEYOR_REPO_TAG -eq "true" -and $env:APPVEYOR_RE_BUILD -eq "True") {
$env:PROD_DEPLOY = "true"
echo "This is a production deployment."
}
- ps: |
if($isWindows) {
if(Test-Path -Path $env:WIN_PKG) {
$env:VER_INFO = "true"
}
choco install reshack --no-progress
choco install cloc --no-progress
choco install checksum --no-progress
cloc --include-lang TypeScript,JavaScript --vcs git
.\scripts\make-versioninfo.ps1
}
before_build:
- node --version
- npm --version
# Get new $SNAP_TOKEN with:
# $ snapcraft export-login --snaps bw --acls package_push,package_release -
- sh: |
if [ "${SNAP_TOKEN}" != "" -a "${PROD_DEPLOY}" == "true" ]
then
sudo apt-get update
sudo apt-get -y install snapd
sudo snap install snapcraft --classic
export PATH="$PATH:/snap/bin"
echo "$SNAP_TOKEN" | snapcraft login --with -
fi
- ps: |
if($isWindows -and $env:PROD_DEPLOY -eq "true") {
if($env:CHOCO_API_KEY -ne $null) {
choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/
}
if($env:NPM_TOKEN -ne $null) {
"//registry.npmjs.org/:_authToken=${env:NPM_TOKEN}" | Out-File ".npmrc" -Encoding UTF8
}
}
build_script:
- cmd: |
if defined VER_INFO ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action delete -mask ICONGROUP,1,
if defined VER_INFO ResourceHacker -open version-info.rc -save version-info.res -action compile
if defined VER_INFO ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action addoverwrite -resource version-info.res
- cmd: npm install
- cmd: npm run sub:init
- cmd: npm run dist
- cmd: 7z a ./dist/bw-windows-%PACKAGE_VERSION%.zip ./dist/windows/bw.exe
- cmd: 7z a ./dist/bw-macos-%PACKAGE_VERSION%.zip ./dist/macos/bw
- cmd: 7z a ./dist/bw-linux-%PACKAGE_VERSION%.zip ./dist/linux/bw
- ps: |
if($isWindows) {
Expand-Archive -Path "./dist/bw-windows-${env:PACKAGE_VERSION}.zip" -DestinationPath "./test/windows"
$testVersion = Invoke-Expression '& ./test/windows/bw.exe -v'
if($testVersion -ne $env:PACKAGE_VERSION) {
Throw "Version test failed."
}
}
- ps: |
if($isWindows) {
.\scripts\choco-pack.ps1
checksum -f="./dist/bw-windows-${env:PACKAGE_VERSION}.zip" `
-t sha256 | Out-File -Encoding ASCII ./dist/bw-windows-sha256-${env:PACKAGE_VERSION}.txt
checksum -f="./dist/bw-macos-${env:PACKAGE_VERSION}.zip" `
-t sha256 | Out-File -Encoding ASCII ./dist/bw-macos-sha256-${env:PACKAGE_VERSION}.txt
checksum -f="./dist/bw-linux-${env:PACKAGE_VERSION}.zip" `
-t sha256 | Out-File -Encoding ASCII ./dist/bw-linux-sha256-${env:PACKAGE_VERSION}.txt
if($env:PROD_DEPLOY -ne "true") {
Push-AppveyorArtifact .\dist\bw-windows-${env:PACKAGE_VERSION}.zip
Push-AppveyorArtifact .\dist\bw-macos-${env:PACKAGE_VERSION}.zip
Push-AppveyorArtifact .\dist\bw-linux-${env:PACKAGE_VERSION}.zip
Push-AppveyorArtifact .\dist\bw-windows-sha256-${env:PACKAGE_VERSION}.txt
Push-AppveyorArtifact .\dist\bw-macos-sha256-${env:PACKAGE_VERSION}.txt
Push-AppveyorArtifact .\dist\bw-linux-sha256-${env:PACKAGE_VERSION}.txt
Push-AppveyorArtifact .\dist\chocolatey\bitwarden-cli.${env:PACKAGE_VERSION}.nupkg
}
}
after_build:
- ps: |
if($env:PROD_DEPLOY -eq "true") {
if($isLinux) {
echo "Deploy Linux..."
./scripts/snap-build.ps1 -version $env:PACKAGE_VERSION
sudo snap install ./dist/snap/bw*.snap --dangerous
$testVersion = Invoke-Expression '& bw -v'
if($testVersion -ne $env:PACKAGE_VERSION) {
Throw "Version test failed."
}
sudo snap remove bw
./scripts/snap-update.ps1
Push-AppveyorArtifact ./dist/snap/bw_${env:PACKAGE_VERSION}_amd64.snap
}
else {
echo "Deploy Windows..."
.\scripts\choco-update.ps1 -version $env:PACKAGE_VERSION
}
}
- sh: |
if [ "${SNAP_TOKEN}" != "" -a "${PROD_DEPLOY}" == "true" ]
then
snapcraft logout
fi
- cmd: if ["%PROD_DEPLOY%"] equ ["true"] npm run publish:npm
on_finish:
- ps: |
if($env:DEBUG_RDP -eq "true") {
$blockRdp = $true
iex ((new-object net.webclient).DownloadString(`
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
}
for:
-
matrix:
only:
- image: Visual Studio 2017
cache:
- 'C:\Users\appveyor\.pkg-cache\'
deploy:
tag: $(APPVEYOR_REPO_TAG_NAME)
release: $(RELEASE_NAME)
provider: GitHub
auth_token: $(GH_TOKEN)
artifact: /.*/
force_update: true
on:
branch: master
APPVEYOR_REPO_TAG: true

2
jslib

@ -1 +1 @@
Subproject commit 06239aea2d811852561711bd73e14729fba2071a
Subproject commit 09c444ddd4498b5417769e8a795671a6a8ef6ade

180
package-lock.json generated
View File

@ -1258,9 +1258,9 @@
}
},
"commander": {
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz",
"integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ=="
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.0.0.tgz",
"integrity": "sha512-ovx/7NkTrnPuIV8sqk/GjUIIM1+iUQeqA3ye2VNpq9sVoiZsooObWlQy+OPWGI17GDaEoybuAGJm6U8yC077BA=="
},
"commondir": {
"version": "1.0.1",
@ -2138,24 +2138,32 @@
"dependencies": {
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"bundled": true,
"dev": true,
"optional": true
},
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"bundled": true,
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"bundled": true,
"dev": true,
"optional": true
},
"are-we-there-yet": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"bundled": true,
"dev": true,
"optional": true,
@ -2166,12 +2174,16 @@
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"bundled": true,
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"bundled": true,
"dev": true,
"optional": true,
@ -2182,36 +2194,48 @@
},
"chownr": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
"integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==",
"bundled": true,
"dev": true,
"optional": true
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"bundled": true,
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"bundled": true,
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"bundled": true,
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"bundled": true,
"dev": true,
"optional": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"bundled": true,
"dev": true,
"optional": true,
@ -2221,24 +2245,32 @@
},
"deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"bundled": true,
"dev": true,
"optional": true
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"bundled": true,
"dev": true,
"optional": true
},
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
"bundled": true,
"dev": true,
"optional": true
},
"fs-minipass": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
"integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
"bundled": true,
"dev": true,
"optional": true,
@ -2248,12 +2280,16 @@
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"bundled": true,
"dev": true,
"optional": true
},
"gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"bundled": true,
"dev": true,
"optional": true,
@ -2270,6 +2306,8 @@
},
"glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"bundled": true,
"dev": true,
"optional": true,
@ -2284,12 +2322,16 @@
},
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"bundled": true,
"dev": true,
"optional": true
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"bundled": true,
"dev": true,
"optional": true,
@ -2299,6 +2341,8 @@
},
"ignore-walk": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz",
"integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
"bundled": true,
"dev": true,
"optional": true,
@ -2308,6 +2352,8 @@
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"bundled": true,
"dev": true,
"optional": true,
@ -2318,18 +2364,24 @@
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"bundled": true,
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"bundled": true,
"dev": true,
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"bundled": true,
"dev": true,
"optional": true,
@ -2339,12 +2391,16 @@
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"bundled": true,
"dev": true,
"optional": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"bundled": true,
"dev": true,
"optional": true,
@ -2354,12 +2410,16 @@
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"bundled": true,
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
"integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
"bundled": true,
"dev": true,
"optional": true,
@ -2370,6 +2430,8 @@
},
"minizlib": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz",
"integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
"bundled": true,
"dev": true,
"optional": true,
@ -2379,6 +2441,8 @@
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"bundled": true,
"dev": true,
"optional": true,
@ -2388,12 +2452,16 @@
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"bundled": true,
"dev": true,
"optional": true
},
"needle": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz",
"integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==",
"bundled": true,
"dev": true,
"optional": true,
@ -2405,6 +2473,8 @@
},
"node-pre-gyp": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz",
"integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==",
"bundled": true,
"dev": true,
"optional": true,
@ -2423,6 +2493,8 @@
},
"nopt": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
"integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
"bundled": true,
"dev": true,
"optional": true,
@ -2433,12 +2505,16 @@
},
"npm-bundled": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz",
"integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==",
"bundled": true,
"dev": true,
"optional": true
},
"npm-packlist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.2.0.tgz",
"integrity": "sha512-7Mni4Z8Xkx0/oegoqlcao/JpPCPEMtUvsmB0q7mgvlMinykJLSRTYuFqoQLYgGY8biuxIeiHO+QNJKbCfljewQ==",
"bundled": true,
"dev": true,
"optional": true,
@ -2449,6 +2525,8 @@
},
"npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"bundled": true,
"dev": true,
"optional": true,
@ -2461,18 +2539,24 @@
},
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"bundled": true,
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"bundled": true,
"dev": true,
"optional": true
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"bundled": true,
"dev": true,
"optional": true,
@ -2482,18 +2566,24 @@
},
"os-homedir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"bundled": true,
"dev": true,
"optional": true
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"bundled": true,
"dev": true,
"optional": true
},
"osenv": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"bundled": true,
"dev": true,
"optional": true,
@ -2504,18 +2594,24 @@
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"bundled": true,
"dev": true,
"optional": true
},
"process-nextick-args": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
"bundled": true,
"dev": true,
"optional": true
},
"rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"bundled": true,
"dev": true,
"optional": true,
@ -2528,6 +2624,8 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"bundled": true,
"dev": true,
"optional": true
@ -2536,6 +2634,8 @@
},
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"bundled": true,
"dev": true,
"optional": true,
@ -2551,6 +2651,8 @@
},
"rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"bundled": true,
"dev": true,
"optional": true,
@ -2560,42 +2662,67 @@
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"bundled": true,
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"bundled": true,
"dev": true,
"optional": true
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"bundled": true,
"dev": true,
"optional": true
},
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
"bundled": true,
"dev": true,
"optional": true
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"bundled": true,
"dev": true,
"optional": true
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"bundled": true,
"dev": true,
"optional": true
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "~5.1.0"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"bundled": true,
"dev": true,
"optional": true,
@ -2605,17 +2732,10 @@
"strip-ansi": "^3.0.0"
}
},
"string_decoder": {
"version": "1.1.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "~5.1.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"bundled": true,
"dev": true,
"optional": true,
@ -2625,12 +2745,16 @@
},
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"bundled": true,
"dev": true,
"optional": true
},
"tar": {
"version": "4.4.8",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz",
"integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
"bundled": true,
"dev": true,
"optional": true,
@ -2646,12 +2770,16 @@
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"bundled": true,
"dev": true,
"optional": true
},
"wide-align": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"bundled": true,
"dev": true,
"optional": true,
@ -2661,12 +2789,16 @@
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"bundled": true,
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
"bundled": true,
"dev": true,
"optional": true
@ -5157,15 +5289,6 @@
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
"dev": true
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@ -5175,6 +5298,15 @@
"safe-buffer": "~5.1.0"
}
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"stringstream": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
@ -5715,6 +5847,12 @@
"tsutils": "^2.29.0"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",

View File

@ -39,11 +39,11 @@
"dist:mac": "npm run build:prod && npm run clean && npm run package:mac",
"dist:lin": "npm run build:prod && npm run clean && npm run package:lin",
"publish:npm": "npm run build:prod && npm publish --access public",
"lint": "tslint src/**/*.ts spec/**/*.ts || true",
"lint:fix": "tslint src/**/*.ts spec/**/*.ts --fix"
"lint": "tslint 'src/**/*.ts' 'spec/**/*.ts' || true",
"lint:fix": "tslint 'src/**/*.ts' 'spec/**/*.ts' --fix"
},
"bin": {
"bw": "./build/bw.js"
"bw": "build/bw.js"
},
"pkg": {
"assets": "./build/**/*"
@ -77,7 +77,7 @@
"big-integer": "1.6.36",
"browser-hrtime": "^1.1.8",
"chalk": "2.4.1",
"commander": "2.18.0",
"commander": "7.0.0",
"form-data": "2.3.2",
"https-proxy-agent": "5.0.0",
"inquirer": "6.2.0",

View File

@ -1,3 +1,4 @@
import * as program from 'commander';
import * as fs from 'fs';
import * as jsdom from 'jsdom';
import * as path from 'path';
@ -39,6 +40,8 @@ import { UserService } from 'jslib/services/user.service';
import { VaultTimeoutService } from 'jslib/services/vaultTimeout.service';
import { Program } from './program';
import { SendProgram } from './send.program';
import { VaultProgram } from './vault.program';
// Polyfills
(global as any).DOMParser = new jsdom.JSDOM().window.DOMParser;
@ -76,6 +79,8 @@ export class Main {
authService: AuthService;
policyService: PolicyService;
program: Program;
vaultProgram: VaultProgram;
sendProgram: SendProgram;
logService: ConsoleLogService;
sendService: SendService;
@ -145,11 +150,24 @@ export class Main {
this.vaultTimeoutService, this.logService, true);
this.auditService = new AuditService(this.cryptoFunctionService, this.apiService);
this.program = new Program(this);
this.vaultProgram = new VaultProgram(this);
this.sendProgram = new SendProgram(this);
}
async run() {
await this.init();
this.program.run();
this.program.register();
this.vaultProgram.register();
this.sendProgram.register();
program
.parse(process.argv);
if (process.argv.slice(2).length === 0) {
program.outputHelp();
}
}
async logout() {

View File

@ -4,8 +4,8 @@ import { Response } from 'jslib/cli/models/response';
import { MessageResponse } from 'jslib/cli/models/response/messageResponse';
interface IOption {
long: string;
short: string;
long?: string;
short?: string;
description: string;
}
@ -19,8 +19,8 @@ interface ICommand {
const validShells = ['zsh'];
export class CompletionCommand {
async run(cmd: program.Command) {
const shell: typeof validShells[number] = cmd.shell;
async run(options: program.OptionValues) {
const shell: typeof validShells[number] = options.shell;
if (!shell) {
return Response.badRequest('`shell` was not provided.');
@ -33,7 +33,7 @@ export class CompletionCommand {
let content = '';
if (shell === 'zsh') {
content = this.zshCompletion('bw', cmd.parent).render();
content = this.zshCompletion('bw', program as any as ICommand).render();
}
const res = new MessageResponse(content, null);

View File

@ -9,20 +9,20 @@ import { StringResponse } from 'jslib/cli/models/response/stringResponse';
export class ConfigCommand {
constructor(private environmentService: EnvironmentService) { }
async run(setting: string, value: string, cmd: program.Command): Promise<Response> {
async run(setting: string, value: string, options: program.OptionValues): Promise<Response> {
setting = setting.toLowerCase();
switch (setting) {
case 'server':
return await this.getOrSetServer(value, cmd);
return await this.getOrSetServer(value, options);
default:
return Response.badRequest('Unknown setting.');
}
}
private async getOrSetServer(url: string, cmd: program.Command): Promise<Response> {
private async getOrSetServer(url: string, options: program.OptionValues): Promise<Response> {
if ((url == null || url.trim() === '') &&
!cmd.webVault && !cmd.api && !cmd.identity && !cmd.icons && !cmd.notifications && !cmd.events) {
!options.webVault && !options.api && !options.identity && !options.icons && !options.notifications && !options.events) {
const baseUrl = this.environmentService.baseUrl;
const stringRes = new StringResponse(baseUrl == null ? 'https://bitwarden.com' : baseUrl);
return Response.success(stringRes);
@ -31,12 +31,12 @@ export class ConfigCommand {
url = (url === 'null' || url === 'bitwarden.com' || url === 'https://bitwarden.com' ? null : url);
await this.environmentService.setUrls({
base: url,
webVault: cmd.webVault || null,
api: cmd.api || null,
identity: cmd.identity || null,
icons: cmd.icons || null,
notifications: cmd.notifications || null,
events: cmd.events || null,
webVault: options.webVault || null,
api: options.api || null,
identity: options.identity || null,
icons: options.icons || null,
notifications: options.notifications || null,
events: options.events || null,
});
const res = new MessageResponse('Saved setting `config`.', null);
return Response.success(res);

View File

@ -25,22 +25,22 @@ export class ConfirmCommand {
}
}
private async confirmOrganizationMember(id: string, cmd: program.Command) {
if (cmd.organizationid == null || cmd.organizationid === '') {
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(cmd.organizationid)) {
return Response.error('`' + cmd.organizationid + '` 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(cmd.organizationid);
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(cmd.organizationid, id);
const orgUser = await this.apiService.getOrganizationUser(options.organizationid, id);
if (orgUser == null) {
throw new Error('Member id does not exist for this organization.');
}
@ -49,7 +49,7 @@ export class ConfirmCommand {
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const req = new OrganizationUserConfirmRequest();
req.key = key.encryptedString;
await this.apiService.postOrganizationUserConfirm(cmd.organizationid, id, req);
await this.apiService.postOrganizationUserConfirm(options.organizationid, id, req);
return Response.success();
} catch (e) {
return Response.error(e);

View File

@ -78,19 +78,19 @@ export class CreateCommand {
}
}
private async createAttachment(cmd: program.Command) {
if (cmd.itemid == null || cmd.itemid === '') {
private async createAttachment(options: program.OptionValues) {
if (options.itemid == null || options.itemid === '') {
return Response.badRequest('--itemid <itemid> required.');
}
if (cmd.file == null || cmd.file === '') {
if (options.file == null || options.file === '') {
return Response.badRequest('--file <file> required.');
}
const filePath = path.resolve(cmd.file);
if (!fs.existsSync(cmd.file)) {
const filePath = path.resolve(options.file);
if (!fs.existsSync(options.file)) {
return Response.badRequest('Cannot find file at ' + filePath);
}
const itemId = cmd.itemid.toLowerCase();
const itemId = options.itemid.toLowerCase();
const cipher = await this.cipherService.get(itemId);
if (cipher == null) {
return Response.notFound();
@ -132,14 +132,14 @@ export class CreateCommand {
}
}
private async createOrganizationCollection(req: OrganizationCollectionRequest, cmd: program.Command) {
if (cmd.organizationid == null || cmd.organizationid === '') {
private async createOrganizationCollection(req: OrganizationCollectionRequest, options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') {
return Response.badRequest('--organizationid <organizationid> required.');
}
if (!Utils.isGuid(cmd.organizationid)) {
return Response.error('`' + cmd.organizationid + '` is not a GUID.');
if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.');
}
if (cmd.organizationid !== req.organizationId) {
if (options.organizationid !== req.organizationId) {
return Response.error('--organizationid <organizationid> does not match request object.');
}
try {

View File

@ -32,14 +32,14 @@ export class DeleteCommand {
}
}
private async deleteCipher(id: string, cmd: program.Command) {
private async deleteCipher(id: string, options: program.OptionValues) {
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
}
try {
if (cmd.permanent) {
if (options.permanent) {
await this.cipherService.deleteWithServer(id);
} else {
await this.cipherService.softDeleteWithServer(id);
@ -50,12 +50,12 @@ export class DeleteCommand {
}
}
private async deleteAttachment(id: string, cmd: program.Command) {
if (cmd.itemid == null || cmd.itemid === '') {
private async deleteAttachment(id: string, options: program.OptionValues) {
if (options.itemid == null || options.itemid === '') {
return Response.badRequest('--itemid <itemid> required.');
}
const itemId = cmd.itemid.toLowerCase();
const itemId = options.itemid.toLowerCase();
const cipher = await this.cipherService.get(itemId);
if (cipher == null) {
return Response.notFound();
@ -96,18 +96,18 @@ export class DeleteCommand {
}
}
private async deleteOrganizationCollection(id: string, cmd: program.Command) {
if (cmd.organizationid == null || cmd.organizationid === '') {
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(cmd.organizationid)) {
return Response.error('`' + cmd.organizationid + '` is not a GUID.');
if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.');
}
try {
await this.apiService.deleteCollection(cmd.organizationid, id);
await this.apiService.deleteCollection(options.organizationid, id);
return Response.success();
} catch (e) {
return Response.error(e);

View File

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

View File

@ -127,17 +127,17 @@ export class EditCommand {
}
}
private async editOrganizationCollection(id: string, req: OrganizationCollectionRequest, cmd: program.Command) {
if (cmd.organizationid == null || cmd.organizationid === '') {
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(cmd.organizationid)) {
return Response.error('`' + cmd.organizationid + '` is not a GUID.');
if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.');
}
if (cmd.organizationid !== req.organizationId) {
if (options.organizationid !== req.organizationId) {
return Response.error('--organizationid <organizationid> does not match request object.');
}
try {

View File

@ -6,7 +6,7 @@ import { StringResponse } from 'jslib/cli/models/response/stringResponse';
import { CliUtils } from '../utils';
export class EncodeCommand {
async run(cmd: program.Command): Promise<Response> {
async run(): Promise<Response> {
if (process.stdin.isTTY) {
return Response.badRequest('No stdin was piped in.');
}

View File

@ -14,7 +14,7 @@ import { Utils } from 'jslib/misc/utils';
export class ExportCommand {
constructor(private cryptoService: CryptoService, private exportService: ExportService) { }
async run(password: string, cmd: program.Command): Promise<Response> {
async run(password: string, options: program.OptionValues): Promise<Response> {
const canInteract = process.env.BW_NOINTERACTION !== 'true';
if ((password == null || password === '') && canInteract) {
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
@ -31,31 +31,31 @@ export class ExportCommand {
const keyHash = await this.cryptoService.hashPassword(password, null);
const storedKeyHash = await this.cryptoService.getKeyHash();
if (storedKeyHash != null && keyHash != null && storedKeyHash === keyHash) {
let format = cmd.format;
let format = options.format;
if (format !== 'encrypted_json' && format !== 'json') {
format = 'csv';
}
if (cmd.organizationid != null && !Utils.isGuid(cmd.organizationid)) {
return Response.error('`' + cmd.organizationid + '` is not a GUID.');
if (options.organizationid != null && !Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.');
}
let exportContent: string = null;
try {
exportContent = cmd.organizationid != null ?
await this.exportService.getOrganizationExport(cmd.organizationid, format) :
exportContent = options.organizationid != null ?
await this.exportService.getOrganizationExport(options.organizationid, format) :
await this.exportService.getExport(format);
} catch (e) {
return Response.error(e);
}
return await this.saveFile(exportContent, cmd, format);
return await this.saveFile(exportContent, options, format);
} else {
return Response.error('Invalid master password.');
}
}
async saveFile(exportContent: string, cmd: program.Command, format: string): Promise<Response> {
async saveFile(exportContent: string, options: program.OptionValues, format: string): Promise<Response> {
try {
const fileName = this.exportService.getFileName(cmd.organizationid != null ? 'org' : null, format);
return await CliUtils.saveResultToFile(exportContent, cmd.output, fileName);
const fileName = this.exportService.getFileName(options.organizationid != null ? 'org' : null, format);
return await CliUtils.saveResultToFile(exportContent, options.output, fileName);
} catch (e) {
return Response.error(e.toString());
}

View File

@ -8,16 +8,16 @@ import { StringResponse } from 'jslib/cli/models/response/stringResponse';
export class GenerateCommand {
constructor(private passwordGenerationService: PasswordGenerationService) { }
async run(cmd: program.Command): Promise<Response> {
async run(cmdOptions: program.OptionValues): Promise<Response> {
const options = {
uppercase: cmd.uppercase || false,
lowercase: cmd.lowercase || false,
number: cmd.number || false,
special: cmd.special || false,
length: cmd.length || 14,
type: cmd.passphrase ? 'passphrase' : 'password',
wordSeparator: cmd.separator == null ? '-' : cmd.separator,
numWords: cmd.words || 3,
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,
};
if (!options.uppercase && !options.lowercase && !options.special && !options.number) {
options.lowercase = true;

View File

@ -1,5 +1,4 @@
import * as program from 'commander';
import * as fet from 'node-fetch';
import { CipherType } from 'jslib/enums/cipherType';
@ -8,8 +7,10 @@ import { AuditService } from 'jslib/abstractions/audit.service';
import { CipherService } from 'jslib/abstractions/cipher.service';
import { CollectionService } from 'jslib/abstractions/collection.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { FolderService } from 'jslib/abstractions/folder.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { SendService } from 'jslib/abstractions/send.service';
import { TotpService } from 'jslib/abstractions/totp.service';
import { UserService } from 'jslib/abstractions/user.service';
@ -40,24 +41,32 @@ 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 { SendFileResponse } from '../models/response/sendFileResponse';
import { SendResponse } from '../models/response/sendResponse';
import { SendTextResponse } from '../models/response/sendTextResponse';
import { TemplateResponse } from '../models/response/templateResponse';
import { OrganizationCollectionRequest } from '../models/request/organizationCollectionRequest';
import { SelectionReadOnly } from '../models/selectionReadOnly';
import { DownloadCommand } from './download.command';
import { CliUtils } from '../utils';
import { Utils } from 'jslib/misc/utils';
export class GetCommand {
export class GetCommand extends DownloadCommand {
constructor(private cipherService: CipherService, private folderService: FolderService,
private collectionService: CollectionService, private totpService: TotpService,
private auditService: AuditService, private cryptoService: CryptoService,
private auditService: AuditService, cryptoService: CryptoService,
private userService: UserService, private searchService: SearchService,
private apiService: ApiService) { }
private apiService: ApiService, private sendService: SendService,
private environmentService: EnvironmentService) {
super(cryptoService);
}
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
async run(object: string, id: string, options: program.OptionValues): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
@ -76,13 +85,13 @@ export class GetCommand {
case 'exposed':
return await this.getExposed(id);
case 'attachment':
return await this.getAttachment(id, cmd);
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, cmd);
return await this.getOrganizationCollection(id, options);
case 'organization':
return await this.getOrganization(id);
case 'template':
@ -241,12 +250,12 @@ export class GetCommand {
return Response.success(res);
}
private async getAttachment(id: string, cmd: program.Command) {
if (cmd.itemid == null || cmd.itemid === '') {
private async getAttachment(id: string, options: program.OptionValues) {
if (options.itemid == null || options.itemid === '') {
return Response.badRequest('--itemid <itemid> required.');
}
const itemId = cmd.itemid.toLowerCase();
const itemId = options.itemid.toLowerCase();
const cipherResponse = await this.getCipher(itemId);
if (!cipherResponse.success) {
return cipherResponse;
@ -273,24 +282,9 @@ export class GetCommand {
}
}
const response = await fet.default(new fet.Request(attachments[0].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 key = attachments[0].key != null ? attachments[0].key :
await this.cryptoService.getOrgKey(cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
return await CliUtils.saveResultToFile(Buffer.from(decBuf), cmd.output, attachments[0].fileName);
} catch (e) {
if (typeof (e) === 'string') {
return Response.error(e);
} else {
return Response.error('An error occurred while saving the attachment.');
}
}
const key = attachments[0].key != null ? attachments[0].key :
await this.cryptoService.getOrgKey(cipher.organizationId);
return await this.saveAttachmentToFile(attachments[0].url, key, attachments[0].fileName, options.output);
}
private async getFolder(id: string) {
@ -343,23 +337,23 @@ export class GetCommand {
return Response.success(res);
}
private async getOrganizationCollection(id: string, cmd: program.Command) {
if (cmd.organizationid == null || cmd.organizationid === '') {
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(cmd.organizationid)) {
return Response.error('`' + cmd.organizationid + '` 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(cmd.organizationid);
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(cmd.organizationid, id);
const response = await this.apiService.getCollectionDetails(options.organizationid, id);
const decCollection = new CollectionView(response);
decCollection.name = await this.cryptoService.decryptToUtf8(
new CipherString(response.name), orgKey);
@ -430,6 +424,15 @@ export class GetCommand {
case 'org-collection':
template = OrganizationCollectionRequest.template();
break;
case 'send':
template = SendResponse.template();
break;
case 'send.text':
template = SendTextResponse.template();
break;
case 'send.file':
template = SendFileResponse.template();
break;
default:
return Response.badRequest('Unknown template object.');
}

View File

@ -9,8 +9,8 @@ import { CliUtils } from '../utils';
export class ImportCommand {
constructor(private importService: ImportService) { }
async run(format: string, filepath: string, cmd: program.Command): Promise<Response> {
if (cmd.formats || false) {
async run(format: string, filepath: string, options: program.OptionValues): Promise<Response> {
if (options.formats || false) {
return this.list();
} else {
return this.import(format, filepath);

View File

@ -56,45 +56,45 @@ export class ListCommand {
}
}
private async listCiphers(cmd: program.Command) {
private async listCiphers(options: program.OptionValues) {
let ciphers: CipherView[];
cmd.trash = cmd.trash || false;
if (cmd.url != null && cmd.url.trim() !== '') {
ciphers = await this.cipherService.getAllDecryptedForUrl(cmd.url);
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 (cmd.folderid != null || cmd.collectionid != null || cmd.organizationid != null) {
if (options.folderid != null || options.collectionid != null || options.organizationid != null) {
ciphers = ciphers.filter((c) => {
if (cmd.trash !== c.isDeleted) {
if (options.trash !== c.isDeleted) {
return false;
}
if (cmd.folderid != null) {
if (cmd.folderid === 'notnull' && c.folderId != null) {
if (options.folderid != null) {
if (options.folderid === 'notnull' && c.folderId != null) {
return true;
}
const folderId = cmd.folderid === 'null' ? null : cmd.folderid;
const folderId = options.folderid === 'null' ? null : options.folderid;
if (folderId === c.folderId) {
return true;
}
}
if (cmd.organizationid != null) {
if (cmd.organizationid === 'notnull' && c.organizationId != null) {
if (options.organizationid != null) {
if (options.organizationid === 'notnull' && c.organizationId != null) {
return true;
}
const organizationId = cmd.organizationid === 'null' ? null : cmd.organizationid;
const organizationId = options.organizationid === 'null' ? null : options.organizationid;
if (organizationId === c.organizationId) {
return true;
}
}
if (cmd.collectionid != null) {
if (cmd.collectionid === 'notnull' && c.collectionIds != null && c.collectionIds.length > 0) {
if (options.collectionid != null) {
if (options.collectionid === 'notnull' && c.collectionIds != null && c.collectionIds.length > 0) {
return true;
}
const collectionId = cmd.collectionid === 'null' ? null : cmd.collectionid;
const collectionId = options.collectionid === 'null' ? null : options.collectionid;
if (collectionId == null && (c.collectionIds == null || c.collectionIds.length === 0)) {
return true;
}
@ -104,57 +104,57 @@ export class ListCommand {
}
return false;
});
} else if (cmd.search == null || cmd.search.trim() === '') {
ciphers = ciphers.filter((c) => cmd.trash === c.isDeleted);
} else if (options.search == null || options.search.trim() === '') {
ciphers = ciphers.filter((c) => options.trash === c.isDeleted);
}
if (cmd.search != null && cmd.search.trim() !== '') {
ciphers = this.searchService.searchCiphersBasic(ciphers, cmd.search, cmd.trash);
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(cmd: program.Command) {
private async listFolders(options: program.OptionValues) {
let folders = await this.folderService.getAllDecrypted();
if (cmd.search != null && cmd.search.trim() !== '') {
folders = CliUtils.searchFolders(folders, cmd.search);
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(cmd: program.Command) {
private async listCollections(options: program.OptionValues) {
let collections = await this.collectionService.getAllDecrypted();
if (cmd.organizationid != null) {
if (options.organizationid != null) {
collections = collections.filter((c) => {
if (cmd.organizationid === c.organizationId) {
if (options.organizationid === c.organizationId) {
return true;
}
return false;
});
}
if (cmd.search != null && cmd.search.trim() !== '') {
collections = CliUtils.searchCollections(collections, cmd.search);
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(cmd: program.Command) {
if (cmd.organizationid == null || cmd.organizationid === '') {
private async listOrganizationCollections(options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') {
return Response.badRequest('--organizationid <organizationid> required.');
}
if (!Utils.isGuid(cmd.organizationid)) {
return Response.error('`' + cmd.organizationid + '` is not a GUID.');
if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.');
}
const organization = await this.userService.getOrganization(cmd.organizationid);
const organization = await this.userService.getOrganization(options.organizationid);
if (organization == null) {
return Response.error('Organization not found.');
}
@ -162,15 +162,15 @@ export class ListCommand {
try {
let response: ApiListResponse<ApiCollectionResponse>;
if (organization.canManageAllCollections) {
response = await this.apiService.getCollections(cmd.organizationid);
response = await this.apiService.getCollections(options.organizationid);
} else {
response = await this.apiService.getUserCollections();
}
const collections = response.data.filter((c) => c.organizationId === cmd.organizationid).map((r) =>
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 (cmd.search != null && cmd.search.trim() !== '') {
decCollections = CliUtils.searchCollections(decCollections, cmd.search);
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);
@ -179,20 +179,20 @@ export class ListCommand {
}
}
private async listOrganizationMembers(cmd: program.Command) {
if (cmd.organizationid == null || cmd.organizationid === '') {
private async listOrganizationMembers(options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === '') {
return Response.badRequest('--organizationid <organizationid> required.');
}
if (!Utils.isGuid(cmd.organizationid)) {
return Response.error('`' + cmd.organizationid + '` is not a GUID.');
if (!Utils.isGuid(options.organizationid)) {
return Response.error('`' + options.organizationid + '` is not a GUID.');
}
const organization = await this.userService.getOrganization(cmd.organizationid);
const organization = await this.userService.getOrganization(options.organizationid);
if (organization == null) {
return Response.error('Organization not found.');
}
try {
const response = await this.apiService.getOrganizationUsers(cmd.organizationid);
const response = await this.apiService.getOrganizationUsers(options.organizationid);
const res = new ListResponse(response.data.map((r) => {
const u = new OrganizationUserResponse();
u.email = r.email;
@ -209,11 +209,11 @@ export class ListCommand {
}
}
private async listOrganizations(cmd: program.Command) {
private async listOrganizations(options: program.OptionValues) {
let organizations = await this.userService.getAllOrganizations();
if (cmd.search != null && cmd.search.trim() !== '') {
organizations = CliUtils.searchOrganizations(organizations, cmd.search);
if (options.search != null && options.search.trim() !== '') {
organizations = CliUtils.searchOrganizations(organizations, options.search);
}
const res = new ListResponse(organizations.map((o) => new OrganizationResponse(o)));

View File

@ -16,7 +16,7 @@ import { Utils } from 'jslib/misc/utils';
import { LoginCommand as BaseLoginCommand } from 'jslib/cli/commands/login.command';
export class LoginCommand extends BaseLoginCommand {
private cmd: program.Command;
private options: program.OptionValues;
constructor(authService: AuthService, apiService: ApiService,
cryptoFunctionService: CryptoFunctionService, syncService: SyncService,
@ -30,7 +30,7 @@ export class LoginCommand extends BaseLoginCommand {
};
this.success = async () => {
await syncService.fullSync(true);
if ((this.cmd.sso != null || this.cmd.apikey != null) && this.canInteract) {
if ((this.options.sso != null || this.options.apikey != null) && this.canInteract) {
const res = new MessageResponse('You are logged in!', '\n' +
'To unlock your vault, use the `unlock` command. ex:\n' +
'$ bw unlock');
@ -48,8 +48,8 @@ export class LoginCommand extends BaseLoginCommand {
};
}
run(email: string, password: string, cmd: program.Command) {
this.cmd = cmd;
return super.run(email, password, cmd);
run(email: string, password: string, options: program.OptionValues) {
this.options = options;
return super.run(email, password, options);
}
}

View File

@ -0,0 +1,112 @@
import * as program from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { SendService } from 'jslib/abstractions/send.service';
import { UserService } from 'jslib/abstractions/user.service';
import { SendType } from 'jslib/enums/sendType';
import { NodeUtils } from 'jslib/misc/nodeUtils';
import { Response } from 'jslib/cli/models/response';
import { StringResponse } from 'jslib/cli/models/response/stringResponse';
import { SendResponse } from '../../models/response/sendResponse';
import { SendTextResponse } from '../../models/response/sendTextResponse';
import { CliUtils } from '../../utils';
export class SendCreateCommand {
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);
}
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;
req.key = null;
switch (req.type) {
case SendType.File:
if (!(await this.userService.canAccessPremium())) {
return Response.error('Premium status is required to use this feature.');
}
if (filePath == null) {
return Response.badRequest('Must specify a file to Send either with the --file option or in the encoded json');
}
req.file.fileName = path.basename(filePath)
break;
case SendType.Text:
if (text == null) {
return Response.badRequest('Must specify text content to Send either with the --text option or in the encoded json');
}
req.text = new SendTextResponse();
req.text.text = text;
req.text.hidden = hidden;
break;
default:
return Response.badRequest('Unknown Send type ' + SendType[req.type] + 'valid types are: file, text');
}
try {
let fileBuffer: ArrayBuffer = null;
if (req.type === SendType.File) {
fileBuffer = NodeUtils.bufferToArrayBuffer(fs.readFileSync(filePath));
}
const sendView = SendResponse.toView(req);
const [encSend, fileData] = await this.sendService.encrypt(sendView, fileBuffer, password);
// Add dates from template
encSend.deletionDate = sendView.deletionDate;
encSend.expirationDate = sendView.expirationDate;
await this.sendService.saveWithServer([encSend, fileData]);
const newSend = await this.sendService.get(encSend.id);
const decSend = await newSend.decrypt();
const res = new SendResponse(decSend, this.environmentService.getWebVaultUrl());
return Response.success(options.fullObject ? res :
new StringResponse('Send created! It can be accessed at:\n' + res.accessUrl));
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -0,0 +1,22 @@
import { SendService } from 'jslib/abstractions/send.service';
import { Response } from 'jslib/cli/models/response';
export class SendDeleteCommand {
constructor(private sendService: SendService) { }
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);
}
}
}

View File

@ -0,0 +1,75 @@
import * as program from 'commander';
import { SendService } from 'jslib/abstractions/send.service';
import { UserService } from 'jslib/abstractions/user.service';
import { Response } from 'jslib/cli/models/response';
import { SendType } from 'jslib/enums/sendType';
import { SendResponse } from '../../models/response/sendResponse';
import { CliUtils } from '../../utils';
export class SendEditCommand {
constructor(private sendService: SendService, private userService: UserService) { }
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]);
const updatedSend = await this.sendService.get(send.id);
const decSend = await updatedSend.decrypt();
const res = new SendResponse(decSend);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
}

View File

@ -0,0 +1,84 @@
import * as program from 'commander';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { SearchService } from 'jslib/abstractions/search.service';
import { SendService } from 'jslib/abstractions/send.service';
import { SendView } from 'jslib/models/view/sendView';
import { Response } from 'jslib/cli/models/response';
import { DownloadCommand } from '../download.command';
import { SendResponse } from '../../models/response/sendResponse';
import { Utils } from 'jslib/misc/utils';
export class SendGetCommand extends DownloadCommand {
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();
}
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 (options.file != null) {
filter = s => {
return filter(s) && s.file != null && s.file.url != null;
};
selector = async s => await this.saveAttachmentToFile(s.file.url, s.cryptoKey, s.file.fileName, options.output);
}
if (Array.isArray(sends)) {
if (filter != null) {
sends = sends.filter(filter);
}
if (sends.length > 1) {
return Response.multipleResults(sends.map(s => s.id));
}
if (sends.length > 0) {
return selector(sends[0]);
}
else {
return Response.notFound();
}
}
return selector(sends);
}
private async getSendView(id: string): Promise<SendView | SendView[]> {
if (Utils.isGuid(id)) {
const send = await this.sendService.get(id);
if (send != null) {
return await send.decrypt();
}
} else if (id.trim() !== '') {
let sends = await this.sendService.getAllDecrypted();
sends = this.searchService.searchSends(sends, id);
if (sends.length > 1) {
return sends;
} else if (sends.length > 0) {
return sends[0];
}
}
}
}

View File

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

View File

@ -0,0 +1,147 @@
import * as program from 'commander';
import * as inquirer from 'inquirer';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SendAccessRequest } from 'jslib/models/request/sendAccessRequest';
import { ErrorResponse } from 'jslib/models/response/errorResponse';
import { SendAccessView } from 'jslib/models/view/sendAccessView';
import { Response } from 'jslib/cli/models/response';
import { SendAccess } from 'jslib/models/domain/sendAccess';
import { SymmetricCryptoKey } from 'jslib/models/domain/symmetricCryptoKey';
import { SendType } from 'jslib/enums/sendType';
import { NodeUtils } from 'jslib/misc/nodeUtils';
import { Utils } from 'jslib/misc/utils';
import { SendAccessResponse } from '../../models/response/sendAccessResponse';
import { DownloadCommand } from '../download.command';
export class SendReceiveCommand extends DownloadCommand {
private canInteract: boolean;
private decKey: SymmetricCryptoKey;
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');
}
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);
const request = 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 !== '') {
request.password = await this.getUnlockedPassword(password, keyArray);
}
const response = await this.sendRequest(request, 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:
return await this.saveAttachmentToFile(response?.file?.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.split('/').slice(2);
return [result[0], result[1]];
}
private getApiUrl(url: URL) {
if (url.origin === this.apiService.apiBaseUrl) {
return url.origin;
} else if (this.platformUtilsService.isDev() && url.origin === this.environmentService.getWebVaultUrl()) {
return this.apiService.apiBaseUrl;
} 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(request: SendAccessRequest, url: string, id: string, key: ArrayBuffer): Promise<Response | SendAccessView> {
try {
const sendResponse = await this.apiService.postSendAccess(id, request, 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
request.password = await this.getUnlockedPassword(answer.password, key);
return await this.sendRequest(request, 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();
} else {
return Response.error(e);
}
}
}
}
}

View File

@ -0,0 +1,22 @@
import { SendService } from 'jslib/abstractions/send.service';
import { Response } from 'jslib/cli/models/response';
import { SendResponse } from '../../models/response/sendResponse';
export class SendRemovePasswordCommand {
constructor(private sendService: SendService) { }
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);
}
}
}

View File

@ -14,7 +14,7 @@ export class StatusCommand {
private userService: UserService, private vaultTimeoutService: VaultTimeoutService) {
}
async run(cmd: program.Command): Promise<Response> {
async run(): Promise<Response> {
try {
const baseUrl = this.baseUrl();
const status = await this.status();

View File

@ -9,13 +9,13 @@ import { StringResponse } from 'jslib/cli/models/response/stringResponse';
export class SyncCommand {
constructor(private syncService: SyncService) { }
async run(cmd: program.Command): Promise<Response> {
if (cmd.last || false) {
async run(options: program.OptionValues): Promise<Response> {
if (options.last || false) {
return await this.getLastSync();
}
try {
const result = await this.syncService.fullSync(cmd.force || false, true);
const result = await this.syncService.fullSync(options.force || false, true);
const res = new MessageResponse('Syncing complete.', null);
return Response.success(res);
} catch (e) {

View File

@ -0,0 +1,42 @@
import { SendType } from 'jslib/enums/sendType';
import { SendAccessView } from 'jslib/models/view/sendAccessView';
import { BaseResponse } from 'jslib/cli/models/response/baseResponse';
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;
}
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);
}
}
}

View File

@ -0,0 +1,39 @@
import { SendFileView } from 'jslib/models/view/sendFileView';
export class SendFileResponse {
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;
}
view.id = file.id;
view.url = file.url;
view.size = file.size;
view.sizeName = file.sizeName;
view.fileName = file.fileName;
return view;
}
id: string;
url: string;
size: string;
sizeName: string;
fileName: string;
constructor(o?: SendFileView) {
if (o == null) {
return;
}
this.id = o.id;
this.url = o.url;
this.size = o.size;
this.sizeName = o.sizeName;
this.fileName = o.fileName;
}
}

View File

@ -0,0 +1,115 @@
import { SendView } from 'jslib/models/view/sendView';
import { BaseResponse } from 'jslib/cli/models/response/baseResponse';
import { SendType } from 'jslib/enums/sendType';
import { Utils } from 'jslib/misc/utils';
import { SendFileResponse } from './sendFileResponse';
import { SendTextResponse } from './sendTextResponse';
const dateProperties: string[] = [Utils.nameOf<SendResponse>('deletionDate'), Utils.nameOf<SendResponse>('expirationDate')];
export class SendResponse implements BaseResponse {
static template(deleteInDays = 7): SendResponse {
const req = new SendResponse();
req.name = 'Send name';
req.notes = 'Some notes about this send.';
req.type = SendType.Text;
req.text = null;
req.file = null;
req.maxAccessCount = null;
req.deletionDate = this.getStandardDeletionDate(deleteInDays);
req.expirationDate = null;
req.password = null;
req.disabled = false;
return req;
}
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;
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.setHours(d.getHours() + (days * 24));
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;
constructor(o?: SendView, webVaultUrl?: string) {
if (o == null) {
return;
}
this.id = o.id;
this.accessId = o.accessId;
this.accessUrl = (webVaultUrl ?? 'https://vault.bitwarden.com') + '/#/send/' + 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;
if (o.type === SendType.Text && o.text != null) {
this.text = new SendTextResponse(o.text);
}
if (o.type === SendType.File && o.file != null) {
this.file = new SendFileResponse(o.file);
}
}
}

View File

@ -0,0 +1,30 @@
import { SendTextView } from 'jslib/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 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;
constructor(o?: SendTextView) {
if (o == null) {
return;
}
this.text = o.text;
this.hidden = o.hidden;
}
}

View File

@ -4,20 +4,10 @@ import * as program from 'commander';
import { Main } from './bw';
import { ConfigCommand } from './commands/config.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 { EncodeCommand } from './commands/encode.command';
import { ExportCommand } from './commands/export.command';
import { GenerateCommand } from './commands/generate.command';
import { GetCommand } from './commands/get.command';
import { ImportCommand } from './commands/import.command';
import { ListCommand } from './commands/list.command';
import { LockCommand } from './commands/lock.command';
import { LoginCommand } from './commands/login.command';
import { RestoreCommand } from './commands/restore.command';
import { ShareCommand } from './commands/share.command';
import { StatusCommand } from './commands/status.command';
import { SyncCommand } from './commands/sync.command';
import { UnlockCommand } from './commands/unlock.command';
@ -39,11 +29,11 @@ const chalk = chk.default;
const writeLn = CliUtils.writeLn;
export class Program extends BaseProgram {
constructor(private main: Main) {
constructor(protected main: Main) {
super(main.userService, writeLn);
}
run() {
register() {
program
.option('--pretty', 'Format output. JSON is tabbed with two spaces.')
.option('--raw', 'Return raw output instead of a descriptive message.')
@ -99,6 +89,10 @@ export class Program extends BaseProgram {
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);
});
@ -109,6 +103,8 @@ export class Program extends BaseProgram {
.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) {
@ -132,14 +128,14 @@ export class Program extends BaseProgram {
writeLn(' bw login --sso');
writeLn('', true);
})
.action(async (email: string, password: string, cmd: program.Command) => {
if (!cmd.check) {
.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);
const response = await command.run(email, password, cmd);
const response = await command.run(email, password, options);
this.processResponse(response);
}
});
@ -232,343 +228,6 @@ export class Program extends BaseProgram {
this.processResponse(response);
});
program
.command('list <object>')
.description('List an array of objects from the vault.')
.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 Objects:');
writeLn('');
writeLn(' items');
writeLn(' folders');
writeLn(' collections');
writeLn(' organizations');
writeLn(' org-collections');
writeLn(' org-members');
writeLn('');
writeLn(' 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) => {
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);
this.processResponse(response);
});
program
.command('get <object> <id>')
.description('Get an object from the vault.')
.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 Objects:');
writeLn('');
writeLn(' item');
writeLn(' username');
writeLn(' password');
writeLn(' uri');
writeLn(' totp');
writeLn(' exposed');
writeLn(' attachment');
writeLn(' folder');
writeLn(' collection');
writeLn(' org-collection');
writeLn(' organization');
writeLn(' template');
writeLn(' fingerprint');
writeLn('');
writeLn(' Id:');
writeLn('');
writeLn(' Search term or object\'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 item 99ee88d2-6046-4ea7-92c2-acac464b1412');
writeLn(' bw get password https://google.com');
writeLn(' bw get totp 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) => {
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);
const response = await command.run(object, id, cmd);
this.processResponse(response);
});
program
.command('create <object> [encodedJson]')
.option('--file <file>', 'Path to file for attachment.')
.option('--itemid <itemid>', 'Attachment\'s item id.')
.option('--organizationid <organizationid>', 'Organization id for an organization object.')
.description('Create an object in the vault.')
.on('--help', () => {
writeLn('\n Objects:');
writeLn('');
writeLn(' item');
writeLn(' attachment');
writeLn(' folder');
writeLn(' org-collection');
writeLn('');
writeLn(' Notes:');
writeLn('');
writeLn(' `encodedJson` can also be piped into stdin.');
writeLn('');
writeLn(' 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) => {
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);
});
program
.command('edit <object> <id> [encodedJson]')
.option('--organizationid <organizationid>', 'Organization id for an organization object.')
.description('Edit an object from the vault.')
.on('--help', () => {
writeLn('\n Objects:');
writeLn('');
writeLn(' item');
writeLn(' item-collections');
writeLn(' folder');
writeLn(' org-collection');
writeLn('');
writeLn(' Id:');
writeLn('');
writeLn(' Object\'s globally unique `id`.');
writeLn('');
writeLn(' Notes:');
writeLn('');
writeLn(' `encodedJson` can also be piped into stdin.');
writeLn('');
writeLn(' 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) => {
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);
});
program
.command('delete <object> <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).')
.description('Delete an object from the vault.')
.on('--help', () => {
writeLn('\n Objects:');
writeLn('');
writeLn(' item');
writeLn(' attachment');
writeLn(' folder');
writeLn(' org-collection');
writeLn('');
writeLn(' Id:');
writeLn('');
writeLn(' Object\'s globally unique `id`.');
writeLn('');
writeLn(' 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) => {
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);
});
program
.command('restore <object> <id>')
.description('Restores an object from the trash.')
.on('--help', () => {
writeLn('\n Objects:');
writeLn('');
writeLn(' item');
writeLn('');
writeLn(' Id:');
writeLn('');
writeLn(' Object\'s globally unique `id`.');
writeLn('');
writeLn(' Examples:');
writeLn('');
writeLn(' bw restore item 7063feab-4b10-472e-b64c-785e2b870b92');
writeLn('', true);
})
.action(async (object, id, cmd) => {
await this.exitIfLocked();
const command = new RestoreCommand(this.main.cipherService);
const response = await command.run(object, id, cmd);
this.processResponse(response);
});
program
.command('share <id> <organizationId> [encodedJson]')
.description('Share an item to an organization.')
.on('--help', () => {
writeLn('\n Id:');
writeLn('');
writeLn(' Item\'s globally unique `id`.');
writeLn('');
writeLn(' Organization Id:');
writeLn('');
writeLn(' Organization\'s globally unique `id`.');
writeLn('');
writeLn(' Notes:');
writeLn('');
writeLn(' `encodedJson` can also be piped into stdin. `encodedJson` contains ' +
'an array of collection ids.');
writeLn('');
writeLn(' Examples:');
writeLn('');
writeLn(' bw share 4af958ce-96a7-45d9-beed-1e70fabaa27a 6d82949b-b44d-468a-adae-3f3bacb0ea32 ' +
'WyI5NzQwNTNkMC0zYjMzLTRiOTgtODg2ZS1mZWNmNWM4ZGJhOTYiXQ==');
writeLn(' echo \'["974053d0-3b33-4b98-886e-fecf5c8dba96"]\' | bw encode | ' +
'bw share 4af958ce-96a7-45d9-beed-1e70fabaa27a 6d82949b-b44d-468a-adae-3f3bacb0ea32');
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);
});
program
.command('confirm <object> <id>')
.option('--organizationid <organizationid>', 'Organization id for an organization object.')
.description('Confirm an object to the organization.')
.on('--help', () => {
writeLn('\n Objects:');
writeLn('');
writeLn(' org-member');
writeLn('');
writeLn(' Id:');
writeLn('');
writeLn(' Object\'s globally unique `id`.');
writeLn('');
writeLn(' 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) => {
await this.exitIfLocked();
const command = new ConfirmCommand(this.main.apiService, this.main.cryptoService);
const response = await command.run(object, id, cmd);
this.processResponse(response);
});
program
.command('import [format] [input]')
.description('Import vault data from a file.')
.option('--formats', 'List formats')
.on('--help', () => {
writeLn('\n Examples:');
writeLn('');
writeLn(' bw import --formats');
writeLn(' bw import bitwardencsv ./from/source.csv');
writeLn(' bw import keepass2xml keepass_backup.xml');
})
.action(async (format, filepath, cmd) => {
await this.exitIfLocked();
const command = new ImportCommand(this.main.importService);
const response = await command.run(format, filepath, cmd);
this.processResponse(response);
});
program
.command('export [password]')
.description('Export vault data to a CSV or JSON file.')
.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, cmd) => {
await this.exitIfLocked();
const command = new ExportCommand(this.main.cryptoService, this.main.exportService);
const response = await command.run(password, cmd);
this.processResponse(response);
});
program
.command('generate')
.description('Generate a password/passphrase.')
@ -599,9 +258,9 @@ export class Program extends BaseProgram {
writeLn(' bw generate -p --words 5 --separator space');
writeLn('', true);
})
.action(async (cmd) => {
.action(async (options) => {
const command = new GenerateCommand(this.main.passwordGenerationService);
const response = await command.run(cmd);
const response = await command.run(options);
this.processResponse(response);
});
@ -618,9 +277,9 @@ export class Program extends BaseProgram {
writeLn(' echo \'{"name":"My Folder"}\' | bw encode');
writeLn('', true);
})
.action(async (object, id, cmd) => {
.action(async () => {
const command = new EncodeCommand();
const response = await command.run(cmd);
const response = await command.run();
this.processResponse(response);
});
@ -646,9 +305,9 @@ export class Program extends BaseProgram {
writeLn(' bw config server --api http://localhost:4000 --identity http://localhost:33656');
writeLn('', true);
})
.action(async (setting, value, cmd) => {
.action(async (setting, value, options) => {
const command = new ConfigCommand(this.main.environmentService);
const response = await command.run(setting, value, cmd);
const response = await command.run(setting, value, options);
this.processResponse(response);
});
@ -668,10 +327,10 @@ export class Program extends BaseProgram {
writeLn(' bw update --raw');
writeLn('', true);
})
.action(async (cmd) => {
.action(async () => {
const command = new UpdateCommand(this.main.platformUtilsService, this.main.i18nService,
'cli', 'bw', true);
const response = await command.run(cmd);
const response = await command.run();
this.processResponse(response);
});
@ -689,9 +348,9 @@ export class Program extends BaseProgram {
writeLn(' bw completion --shell zsh');
writeLn('', true);
})
.action(async (cmd: program.Command) => {
.action(async (options: program.OptionValues, cmd: program.Command) => {
const command = new CompletionCommand();
const response = await command.run(cmd);
const response = await command.run(options);
this.processResponse(response);
});
@ -719,22 +378,16 @@ export class Program extends BaseProgram {
writeLn(' - `unlocked` when you are logged in and the vault is unlocked');
writeLn('', true);
})
.action(async (cmd: program.Command) => {
.action(async () => {
const command = new StatusCommand(
this.main.environmentService,
this.main.syncService,
this.main.userService,
this.main.vaultTimeoutService);
const response = await command.run(cmd);
const response = await command.run();
this.processResponse(response);
});
program
.parse(process.argv);
if (process.argv.slice(2).length === 0) {
program.outputHelp();
}
}
protected processResponse(response: Response, exitImmediately = false) {
@ -746,7 +399,7 @@ export class Program extends BaseProgram {
});
}
private async exitIfLocked() {
protected async exitIfLocked() {
await this.exitIfNotAuthed();
const hasKey = await this.main.cryptoService.hasKey();
if (!hasKey) {
@ -763,4 +416,5 @@ export class Program extends BaseProgram {
}
}
}
}

271
src/send.program.ts Normal file
View File

@ -0,0 +1,271 @@
import * as chk from 'chalk';
import * as program from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import { Response } from 'jslib/cli/models/response';
import { SendType } from 'jslib/enums/sendType';
import { Utils } from 'jslib/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 { 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';
const chalk = chk.default;
const writeLn = CliUtils.writeLn;
export class SendProgram extends Program {
constructor(main: Main) {
super(main);
}
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('--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', '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(chk.default('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')
.option('--file', 'Specifies to return the file content of a Send. This can be paired with --output or --raw to output to stdout')
.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')
.option('--fullObject', 'Specifies that the full Send object should be returned rather than just the access url.')
.on('--help', () => {
writeLn('');
writeLn('Note:');
writeLn(' Options specified in JSON take precedence over command options');
writeLn('', true);
})
.action(async (encodedJson: string, options: program.OptionValues) => {
const response = await this.runCreate(encodedJson, options);
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 cmd = new SendEditCommand(this.main.sendService, this.main.userService);
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(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);
}
}

369
src/vault.program.ts Normal file
View File

@ -0,0 +1,369 @@
import * as chk from 'chalk';
import * as program from 'commander';
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 { CliUtils } from './utils';
import { Program } from './program';
const chalk = chk.default;
const writeLn = CliUtils.writeLn;
export class VaultProgram extends Program {
constructor(protected main: Main) {
super(main);
}
register() {
program
.command('list <object>')
.description('List an array of objects from the vault.')
.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 Objects:');
writeLn('');
writeLn(' items');
writeLn(' folders');
writeLn(' collections');
writeLn(' organizations');
writeLn(' org-collections');
writeLn(' org-members');
writeLn('');
writeLn(' 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) => {
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);
this.processResponse(response);
});
program
.command('get <object> <id>')
.description('Get an object from the vault.')
.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 Objects:');
writeLn('');
writeLn(' item');
writeLn(' username');
writeLn(' password');
writeLn(' uri');
writeLn(' totp');
writeLn(' exposed');
writeLn(' attachment');
writeLn(' folder');
writeLn(' collection');
writeLn(' org-collection');
writeLn(' organization');
writeLn(' template');
writeLn(' fingerprint');
writeLn(' send');
writeLn('');
writeLn(' Id:');
writeLn('');
writeLn(' Search term or object\'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 item 99ee88d2-6046-4ea7-92c2-acac464b1412');
writeLn(' bw get password https://google.com');
writeLn(' bw get totp 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) => {
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);
});
program
.command('create <object> [encodedJson]')
.option('--file <file>', 'Path to file for attachment.')
.option('--itemid <itemid>', 'Attachment\'s item id.')
.option('--organizationid <organizationid>', 'Organization id for an organization object.')
.description('Create an object in the vault.')
.on('--help', () => {
writeLn('\n Objects:');
writeLn('');
writeLn(' item');
writeLn(' attachment');
writeLn(' folder');
writeLn(' org-collection');
writeLn('');
writeLn(' Notes:');
writeLn('');
writeLn(' `encodedJson` can also be piped into stdin.');
writeLn('');
writeLn(' 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) => {
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);
});
program
.command('edit <object> <id> [encodedJson]')
.option('--organizationid <organizationid>', 'Organization id for an organization object.')
.description('Edit an object from the vault.')
.on('--help', () => {
writeLn('\n Objects:');
writeLn('');
writeLn(' item');
writeLn(' item-collections');
writeLn(' folder');
writeLn(' org-collection');
writeLn('');
writeLn(' Id:');
writeLn('');
writeLn(' Object\'s globally unique `id`.');
writeLn('');
writeLn(' Notes:');
writeLn('');
writeLn(' `encodedJson` can also be piped into stdin.');
writeLn('');
writeLn(' 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) => {
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);
});
program
.command('delete <object> <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).')
.description('Delete an object from the vault.')
.on('--help', () => {
writeLn('\n Objects:');
writeLn('');
writeLn(' item');
writeLn(' attachment');
writeLn(' folder');
writeLn(' org-collection');
writeLn('');
writeLn(' Id:');
writeLn('');
writeLn(' Object\'s globally unique `id`.');
writeLn('');
writeLn(' 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) => {
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);
});
program
.command('restore <object> <id>')
.description('Restores an object from the trash.')
.on('--help', () => {
writeLn('\n Objects:');
writeLn('');
writeLn(' item');
writeLn('');
writeLn(' Id:');
writeLn('');
writeLn(' Object\'s globally unique `id`.');
writeLn('');
writeLn(' Examples:');
writeLn('');
writeLn(' bw restore item 7063feab-4b10-472e-b64c-785e2b870b92');
writeLn('', true);
})
.action(async (object, id, cmd) => {
await this.exitIfLocked();
const command = new RestoreCommand(this.main.cipherService);
const response = await command.run(object, id, cmd);
this.processResponse(response);
});
program
.command('share <id> <organizationId> [encodedJson]')
.description('Share an item to an organization.')
.on('--help', () => {
writeLn('\n Id:');
writeLn('');
writeLn(' Item\'s globally unique `id`.');
writeLn('');
writeLn(' Organization Id:');
writeLn('');
writeLn(' Organization\'s globally unique `id`.');
writeLn('');
writeLn(' Notes:');
writeLn('');
writeLn(' `encodedJson` can also be piped into stdin. `encodedJson` contains ' +
'an array of collection ids.');
writeLn('');
writeLn(' Examples:');
writeLn('');
writeLn(' bw share 4af958ce-96a7-45d9-beed-1e70fabaa27a 6d82949b-b44d-468a-adae-3f3bacb0ea32 ' +
'WyI5NzQwNTNkMC0zYjMzLTRiOTgtODg2ZS1mZWNmNWM4ZGJhOTYiXQ==');
writeLn(' echo \'["974053d0-3b33-4b98-886e-fecf5c8dba96"]\' | bw encode | ' +
'bw share 4af958ce-96a7-45d9-beed-1e70fabaa27a 6d82949b-b44d-468a-adae-3f3bacb0ea32');
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);
});
program
.command('confirm <object> <id>')
.option('--organizationid <organizationid>', 'Organization id for an organization object.')
.description('Confirm an object to the organization.')
.on('--help', () => {
writeLn('\n Objects:');
writeLn('');
writeLn(' org-member');
writeLn('');
writeLn(' Id:');
writeLn('');
writeLn(' Object\'s globally unique `id`.');
writeLn('');
writeLn(' 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) => {
await this.exitIfLocked();
const command = new ConfirmCommand(this.main.apiService, this.main.cryptoService);
const response = await command.run(object, id, cmd);
this.processResponse(response);
});
program
.command('import [format] [input]')
.description('Import vault data from a file.')
.option('--formats', 'List formats')
.on('--help', () => {
writeLn('\n Examples:');
writeLn('');
writeLn(' bw import --formats');
writeLn(' bw import bitwardencsv ./from/source.csv');
writeLn(' bw import keepass2xml keepass_backup.xml');
})
.action(async (format, filepath, options) => {
await this.exitIfLocked();
const command = new ImportCommand(this.main.importService);
const response = await command.run(format, filepath, options);
this.processResponse(response);
});
program
.command('export [password]')
.description('Export vault data to a CSV or JSON file.')
.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.cryptoService, this.main.exportService);
const response = await command.run(password, options);
this.processResponse(response);
});
}
}

View File

@ -49,6 +49,7 @@
"check-separator",
"check-type"
],
"max-classes-per-file": false
"max-classes-per-file": false,
"ordered-imports": true
}
}