#!/usr/bin/env node const os = require('os') const path = require('path') const { exec } = require('child_process') const minimist = require('minimist') const packageJson = require('../package.json') 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( `bitwarden-dmenu v${packageJson.version} 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 }) })()