#!/bin/bash
#
# bwm - Bitwarden menu for Wayland
#
# © 2021, Pierre-Albéric TROUPLIN (palb91)
set -e
# Environments variables, default values
declare -A credentials
cmd=wtype
asset=pass
url=
selection=
qutebrowser=false
# /!\ Dirty hack to force the use of a specific keymap /!\
keymap=( fr bepo )
# Utils
help() {
cat <<HELP
bwm - Type a password from bitwarden in a Wayland environment.
Usage:
${0##*/} [-h]
${0##*/} [-u|-b|-o] [URL]
${0##*/} -c [-u|-o] [URL]
Options:
-u, --user Type the username instead of the password.
-b, --both Type the username and the password separated by a <Tab>.
-o, --otp Type the otp code if exists.
-a, --all Type the username, the password and the otp code separated by a
<Tab> (no check on otp existing for the moment).
-c, --copy Copy instead of type.
--qb Qutebrowser mode (start insert mode before).
-h, --help Print this help.
Argument:
URL If any, must be the last parameter. ${0##*/} will try to
find only matching values from \`bw\`.
HELP
exit "${1:-0}"
}
die() {
printf '%s\n\n' "${*}" >&2
help 1
}
check_dep() {
if [ -z "$(command -v "${1}")" ]; then
printf '%s is not installed\n' "${1}" >&2
exit 1
fi
}
menu() {
column -ts$'\t' -R2 | bemenu -pselect:
}
use_Xwayland() {
query='recurse(.nodes[]?, .floating_nodes[]?) | select(.focused).shell'
[ "$(swaymsg -t get_tree | jq -r "${query}")" = 'xwayland' ]
}
# Parse arguments, check dependancies
parse_args() {
while [ "${#}" -gt 0 ]; do
case "${1}" in
-h|--help) help ;;
-c|--copy) cmd=wl-copy ;;
-u|--user) asset=user ;;
-b|--both) asset=both ;;
-o|--otp) asset=totp ;;
-a|--all) asset=all ;;
-cu|-uc) cmd=wl-copy asset=user ;;
-co|-oc) cmd=wl-copy asset=totp ;;
--qb) qutebrowser=true ;;
-*) die "Wrong option: ${1}" ;;
*) url="${1}"; break ;;
esac
shift
done
if [ "${#}" -gt 1 ]; then
die "Wrong number of arguments."
elif [ "${cmd}" = 'wl-copy' ]; then
if [ "${asset}" = 'both' ] || [ "${asset}" = 'all' ]; then
die 'Cannot copy both username and password.'
fi
fi
# Use xvkbd for Xwayland windows
if [ "${cmd}" = 'wtype' ] && use_Xwayland; then
cmd=xvkbd
fi
check_dep "${cmd}"
}
get_credentials() {
# Gather bitwarden entries (no url means all entries)
entries="$( bw list items --url "${url}" )"
# Exit if not entry in bitwarden
if [ "${entries}" = '[]' ]; then
printf 'No credentials in bitwarden...\n' >&2
exit 2
fi
# Gather matching entries
_jq='
if length == 1 then
.[0].id
else
sort_by(.favorite == false)
| .[]
| .folder + "\t" + .name + "\t" + .login.username + 300 * " " + .id
end'
mapfile -t match < <(printf '%s' "${entries}" | jq -r "${_jq}")
# Get id, filter if there are several matchs
id="${match[0]}"
if [ "${#match[@]}" -gt 1 ]; then
check_dep bemenu
id="$(printf "%s\n" "${match[@]}" | menu)"
fi
id="${id##* }"
# Get corresponding json ojbect
selection="$(printf '%s' "${entries}" \
| jq '.[] | select(.id == "'"${id}"'")')"
# Exit if no values
if [ -z "${selection}" ]; then
printf 'Wrong selection. Exiting...\n' >&2
exit 3
fi
# Gather credentials
_jq='.login | .username + "\n" + .password + "\n" + .totp'
item=()
mapfile -t item < <(printf '%s' "${selection}" | jq -r "${_jq}")
credentials[user]="${item[0]}"
credentials[pass]="${item[1]}"
credentials[totp]="${item[2]:+$(bw get totp "${id}")}"
}
# Run wtype, xvkbd or wl-copy with asked content
type_or_copy() {
set -- "${credentials[${asset}]}"
if [ "${cmd}" = 'wtype' ]; then
if [ "${asset}" = 'both' ]; then
set -- "${credentials[user]}" -k Tab "${credentials[pass]}"
elif [ "${asset}" = 'all' ]; then
set -- "${credentials[user]}" -k Tab \
"${credentials[pass]}" -k Tab \
"${credentials[totp]}"
fi
# Add a short delay before typing
set -- -s 150 "${@}"
# Qutebrowser mode, press i and remove it
if "${qutebrowser}"; then
set -- 'i' -k BackSpace "${@}"
fi
elif [ "${cmd}" = 'xvkbd' ]; then
setxkbmap "${keymap[@]}"
if [ "${asset}" = 'both' ]; then
set -- "i\b${credentials[user]}\t${credentials[pass]}"
elif [ "${asset}" = 'all' ]; then
set -- "i\b${credentials[user]}"
set -- "${*}\t${credentials[pass]}"
set -- "${*}\t${credentials[totp]}"
fi
set -- -no-keypad -text "\D2${*}"
fi
exec "${cmd}" "${@}"
}
main() {
parse_args "${@}"
get_credentials
type_or_copy
}
main "${@}"