diff --git a/README.md b/README.md index d5ce34a..a3d042c 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,14 @@ 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 15s. - -l Sets the -l parameter value passed to dmenu. - Defaults to 0 + --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 0s. --stdout Prints the password and username to stdout @@ -27,7 +31,6 @@ Options: current time. Defaults to 0s. --on-error Arbitrary command to run if the program fails. The thrown error is piped to the given command. Defaults to none. - --url Url to filter by. --verbose Show extra logs useful for debugging. ``` diff --git a/bin/cli.js b/bin/cli.js index 0a57c7d..5cc6ee1 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -7,12 +7,14 @@ const minimist = require('minimist') const menu = require('../src') const scheduleCleanup = require('../src/schedule-cleanup') +const bwListArgsDefault = "" const cachePasswordDefault = 15 +const dmenuArgsDefault = "" +const dmenuPswdArgsDefault = "" const lengthDefault = 0 const sessionTimeoutDefault = 0 const syncVaultAfterDefault = 0 const stdoutDefault = false -const urlFilterDefault = null const args = minimist(process.argv.slice(2)) if (args.help) { @@ -22,10 +24,14 @@ if (args.help) { 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 ${cachePasswordDefault}s. - -l Sets the -l parameter value passed to dmenu. - Defaults to ${lengthDefault} + --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 ${sessionTimeoutDefault}s. --stdout Prints the password and username to stdout @@ -33,7 +39,6 @@ Options: current time. Defaults to ${syncVaultAfterDefault}s. --on-error Arbitrary command to run if the program fails. The thrown error is piped to the given command. Defaults to none. - --url Url to filter by. --verbose Show extra logs useful for debugging. ` @@ -41,13 +46,17 @@ Options: process.exit() } -const clearClipboardAfter = args['clear-clipboard'] || cachePasswordDefault +const bwListArgs = args['bw-list-args'] || bwListArgsDefault +const dmenuArgs = args['dmenu-args'] || dmenuArgsDefault +const dmenuPswdArgs = args['dmenu-pswd-args'] || dmenuPswdArgsDefault const length = args['l'] || lengthDefault const sessionTimeout = args['session-timeout'] || sessionTimeoutDefault const syncVaultAfter = args['sync-vault-after'] || syncVaultAfterDefault const onErrorCommand = args['on-error'] const stdout = args['stdout'] || stdoutDefault -const urlFilter = args['url'] || urlFilterDefault + +// prevent clipboard clearing from locking up process when printing to stdout +const clearClipboardAfter = stdout ? 0 : args['clear-clipboard'] || cachePasswordDefault console.debug = args['verbose'] ? (...msgs) => console.log(...msgs, '\n') @@ -57,7 +66,7 @@ const oldestAllowedVaultSync = syncVaultAfter const saveSession = Boolean(sessionTimeout) const sessionFile = path.resolve(os.tmpdir(), 'bitwarden-session.txt') -menu({ length, saveSession, sessionFile, stdout, oldestAllowedVaultSync, urlFilter }) +menu({ bwListArgs, dmenuArgs, dmenuPswdArgs, saveSession, sessionFile, stdout, oldestAllowedVaultSync }) .then(() => scheduleCleanup({ lockBitwardenAfter: sessionTimeout, diff --git a/src/index.js b/src/index.js index 8068c3b..7e9e0ef 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ const bwRun = require('./exec-bitwarden-cli') const obfuscate = require('./util/obfuscate/object') // get a session token, either from existing sessionFile or by `bw unlock [password]` -const getSessionVar = async ({ saveSession, sessionFile }) => { +const getSessionVar = async ({ dmenuPswdArgs, saveSession, sessionFile }) => { if (saveSession) { console.debug(`checking for session file at ${sessionFile}`) const sessionFileExists = existsSync(sessionFile) @@ -19,7 +19,7 @@ const getSessionVar = async ({ saveSession, sessionFile }) => { } else { console.debug('no session file found.') // prompt for password in dmenu - const password = await dmenuRun('-p Password: -nf black -nb black')('\n') + const password = await dmenuRun(`-p Password: -nf black -nb black ${dmenuPswdArgs}`)('\n') if (!password) throw new Error('no password given!') const session = bwRun('unlock', password, '--raw') writeFileSync(sessionFile, session) @@ -27,7 +27,8 @@ const getSessionVar = async ({ saveSession, sessionFile }) => { return session } } else { - const password = await dmenuRun('-p Password: -nf black -nb black')('\n') + // Why doesn't dmenuRun('...', dmenuPswdArgs)('\n') work here? + const password = await dmenuRun(`-p Password: -nf black -nb black ${dmenuPswdArgs}`)('\n') if (!password) throw new Error('no password given!') const session = bwRun('unlock', password, '--raw') return session @@ -47,23 +48,20 @@ const syncIfNecessary = ({ session, oldestAllowedVaultSync }) => { } // get the list all password accounts in the vault -const getAccounts = ({ session, urlFilter }) => { - const listStr = urlFilter - ? bwRun('list', 'items', `--url=${urlFilter}`, `--session=${session}`) - : bwRun('list', 'items', `--session=${session}`) - +const getAccounts = ({ session, bwListArgs }) => { + const listStr = bwRun('list', 'items', bwListArgs, `--session=${session}`) const list = JSON.parse(listStr) return list } // choose one account with dmenu -const chooseAccount = async ({ list, length }) => { +const chooseAccount = async ({ list, dmenuArgs }) => { const LOGIN_TYPE = 1 const loginList = list.filter(a => a.type === LOGIN_TYPE) const accountNames = loginList.map(a => `${a.name}: ${a.login.username}`) // -i allows case insensitive matching - const selected = await dmenuRun(`-l ${length}`)(accountNames.join('\n')) + const selected = await dmenuRun(dmenuArgs)(accountNames.join('\n')) const index = accountNames.indexOf(selected) // accountNames indexes match loginList indexes const selectedAccount = loginList[index] @@ -72,7 +70,7 @@ const chooseAccount = async ({ list, length }) => { } // choose one field with dmenu -const chooseField = async ({ selectedAccount, length }) => { +const chooseField = async ({ selectedAccount, dmenuArgs }) => { if (!selectedAccount) throw new Error('no account selected!') const copyable = { password: selectedAccount.login.password, @@ -86,36 +84,37 @@ const chooseField = async ({ selectedAccount, length }) => { {} ) } - const field = await dmenuRun(`-l ${length}`)(Object.keys(copyable).join('\n')) + const field = await dmenuRun(dmenuArgs)(Object.keys(copyable).join('\n')) console.debug(`selected field '${field}'`) const valueToCopy = copyable[field] return valueToCopy } module.exports = async ({ - length, + bwListArgs, + dmenuArgs, + dmenuPswdArgs, saveSession, sessionFile, stdout, - oldestAllowedVaultSync, - urlFilter, + oldestAllowedVaultSync }) => { - const session = await getSessionVar({ saveSession, sessionFile }) + const session = await getSessionVar({ dmenuPswdArgs, saveSession, sessionFile }) // bw sync if necessary syncIfNecessary({ session, oldestAllowedVaultSync }) // bw list - const list = getAccounts({ session, urlFilter }) + const list = getAccounts({ session, bwListArgs }) // choose account in dmenu - const selectedAccount = await chooseAccount({ list, length }) + const selectedAccount = await chooseAccount({ list, dmenuArgs }) if(stdout) { console.log(`${selectedAccount.login.username}\n${selectedAccount.login.password}`) } else { // choose field to copy in dmenu - const valueToCopy = await chooseField({ selectedAccount, length }) + const valueToCopy = await chooseField({ selectedAccount, dmenuArgs }) // copy to clipboard clipboardy.writeSync(valueToCopy)