dmenu for bitwarden-cli
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

131 lines
4.6 KiB

#!/usr/bin/env node
const os = require('os')
const path = require('path')
const { exec } = require('child_process')
const minimist = require('minimist')
const menu = require('../src')
const obfuscateState = require('../src/util/obfuscate')
const { scheduleCleanup } = require('../src/schedule-cleanup')
const { CancelError, CommandError } = require('../src/util/error')
const BW_LIST_ARGS_DEFAULT = ''
const CACHE_PASSWORD_DEFAULT = 15
const DMENU_ARGS_DEFAULT = ''
const DMENU_PSWD_ARGS_DEFAULT = ''
const SESSION_TIMEOUT_DEFAULT = 0
const SYNC_VAULT_AFTER_DEFAULT = 0
const STDOUT_DEFAULT = false
const args = minimist(process.argv.slice(2))
if (args.help) {
console.log(
`Usage: bitwarden-dmenu [options]
The DMENU_PATH environment variable can be used to point to an alternative dmenu implementation. Defaults to 'dmenu'.
Options:
--bw-list-args Arbitrary arguments to pass to bitwarden's 'list' command
Defaults to nothing.
--clear-clipboard Number of seconds to keep selected field in the clipboard.
Defaults to ${CACHE_PASSWORD_DEFAULT}s.
--dmenu-args Sets arbitrary arguments to pass to dmenu
Defaults to nothing.
--dmenu-pswd-args Sets arbitrary arguments to pass to the dmenu password prompt
Defaults to nothing.
--session-timeout Number of seconds after an unlock that the menu can be accessed
without providing a password again. Defaults to ${SESSION_TIMEOUT_DEFAULT}s.
--stdout Prints the password and username to stdout
--sync-vault-after Number of seconds allowable between last bitwarden sync and
current time. Defaults to ${SYNC_VAULT_AFTER_DEFAULT}s.
--on-error Arbitrary command to run if the program fails. The thrown error
is piped to the given command. Defaults to none.
--stdout Prints the password and username to stdout
--debug Show extra logs useful for debugging.
--debug-unsafe Show debug logs WITHOUT obfuscating your sensitive info. Do not share!
`
)
process.exit()
}
const bwListArgs = args['bw-list-args'] || BW_LIST_ARGS_DEFAULT
const dmenuArgs = args['dmenu-args'] || DMENU_ARGS_DEFAULT
const dmenuPswdArgs = args['dmenu-pswd-args'] || DMENU_PSWD_ARGS_DEFAULT
const unsafeDebug = args['debug-unsafe']
const sessionTimeout = args['session-timeout'] || SESSION_TIMEOUT_DEFAULT
const syncVaultAfter = args['sync-vault-after'] || SYNC_VAULT_AFTER_DEFAULT
const onErrorCommand = args['on-error']
const stdout = args['stdout'] || STDOUT_DEFAULT
// prevent clipboard clearing from locking up process when printing to stdout
const clearClipboardAfter = stdout ? 0 : args['clear-clipboard'] || CACHE_PASSWORD_DEFAULT
console.debug =
args['debug'] || args['debug-unsafe'] ? (...msgs) => console.log(...msgs, '\n') : () => {}
console.info = args['stdout'] ? () => {} : console.info
if (args['debug-unsafe']) obfuscateState.turnOff()
const oldestAllowedVaultSync = syncVaultAfter
const saveSession = Boolean(sessionTimeout)
const sessionFile = path.resolve(os.tmpdir(), 'bitwarden-session.txt')
const dmenuArgsParsed = dmenuArgs ? dmenuArgs.split(/\s+/) : []
const dmenuPswdArgsParsed = ['-p', 'Password:', '-nf', 'black', '-nb', 'black'].concat(
dmenuPswdArgs ? dmenuPswdArgs.split(/\s+/) : []
)
const pipeErrorToCommand = (command, e) =>
new Promise((resolve, reject) => {
const errorCommand = exec(onErrorCommand)
console.log(e)
if (e instanceof CommandError) errorCommand.stdin.write(`'${e.errorMessage}'`)
else errorCommand.stdin.write(`'${e.toString()}'`)
errorCommand.stdin.end()
errorCommand.on('close', status => {
if (status === 0) resolve()
else reject()
})
})
const runBitwardenDmenu = async () => {
try {
await menu({
bwListArgs,
dmenuArgs: dmenuArgsParsed,
dmenuPswdArgs: dmenuPswdArgsParsed,
saveSession,
sessionFile,
stdout,
oldestAllowedVaultSync,
unsafeDebug
})
return { lockBitwardenAfter: sessionTimeout, clearClipboardAfter }
} catch (e) {
if (e instanceof CancelError) {
console.debug('cancelled bitwarden-dmenu early.')
return { lockBitwardenAfter: 0, clearClipboardAfter: null }
} else if (onErrorCommand) await pipeErrorToCommand(onErrorCommand, e)
else {
console.error('an error occurred:')
console.error(e)
}
return { lockBitwardenAfter: 0, clearClipboardAfter: 0 }
}
}
;(async () => {
const { lockBitwardenAfter, clearClipboardAfter } = await runBitwardenDmenu()
await scheduleCleanup({
lockBitwardenAfter,
clearClipboardAfter,
sessionFile,
stdout
})
})()