|
#!/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()
|
|
})
|
|
})
|
|
|
|
async function runBitwardenDmenu() {
|
|
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: sessionTimeout, 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
|
|
})
|
|
})()
|