~palb91/bwm

1308fd183ca5946bc4915e254378365d4d8ea7cc — Pierre-Albéric TROUPLIN 1 year, 4 months ago
init: first commit
3 files changed, 246 insertions(+), 0 deletions(-)

A LICENSE
A README.rst
A bwm
A  => LICENSE +21 -0
@@ 1,21 @@
MIT License

Copyright (c) 2020 Pierre-Albéric TROUPLIN

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

A  => README.rst +55 -0
@@ 1,55 @@
``bwm``
#######

    :Author:       palb91
    :License:      MIT
    :Description:  Type a password from bitwarden in a Wayland environment
    :Dependancies: - bitwarden-cli_;
                   - wtype_;
                   - bemenu_;
                   - wl-clipboard_ (optional).

::

    Type a password from bitwarden in a Wayland environment.

    Usage:
        bwm [-h]
        bwm [-u|-b|-o] [URL]
        bwm -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.
        -c, --copy  Copy instead of type.
        -h, --help  Print this help.

    Argument:
        URL         If any, must be the last parameter. bwm will try to find
                    only matching values from `bw`.

Note:
=====

I mostly use it with qutebrowser_.

I set up qutebrowser as follow (`bwm` should be in your `$PATH`):

.. code:: python

    # in config.py
    config.bind('<Alt-p>', 'mode-enter insert ;; spawn -- bwm {url}')
    config.bind('<Alt-u>', 'mode-enter insert ;; spawn -- bwm -u {url}')
    config.bind('<Alt-l>', 'mode-enter insert ;; spawn -- bwm -b {url}')
    config.bind('<Alt-o>', 'mode-enter insert ;; spawn -- bwm -o {url}')
    config.bind('<Alt-p>', 'spawn -- bwm    {url}', mode='insert')
    config.bind('<Alt-u>', 'spawn -- bwm -u {url}', mode='insert')
    config.bind('<Alt-l>', 'spawn -- bwm -b {url}', mode='insert')
    config.bind('<Alt-o>', 'spawn -- bwm -o {url}', mode='insert')


.. _qutebrowser:  https://github.com/qutebrowser/qutebrowser
.. _wtype:        https://github.com/atx/wtype
.. _bemenu:       https://github.com/Cloudef/bemenu
.. _wl-clipboard: https://github.com/bugaevc/wl-clipboard

A  => bwm +170 -0
@@ 1,170 @@
#!/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=


# 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.
    -c, --copy  Copy instead of type.
    -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' -o' :: ' -R2 \
        | bemenu -pselect:       \
        | sed 's/ .*// ; q'
}


# 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               ;;
            -cu)       cmd=wl-copy asset=user   ;;
            -co)       cmd=wl-copy asset=totp   ;;
            -*)        die "Wrong option: ${1}" ;;
            *)         url="${1}"; break        ;;
        esac

        shift
    done

    [[ "${#}" -gt 1 ]] \
        && die "Wrong number of arguments."

    [[ "${cmd}" = 'wl-copy' ]] && [[ "${asset}" = 'both' ]] \
        && die 'Cannot copy both username and password.'

    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 + "\t" + .name + "\t" + .login.username
         else
           sort_by(.favorite == false)
               | .[]
               | .id + "\t" + .name + "\t" + .login.username
         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%%$'\t'*}"


    # 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 or wl-copy with asked content
type_or_copy() {
    set -- "${credentials[${asset}]}"

    if [[ "${cmd}" = 'wtype' ]]; then
        [[ "${asset}" = 'both' ]] \
            && set -- "${credentials[user]}" -k Tab "${credentials[pass]}"

        # Add a short delay before typing
        set -- -s 150 "${@}"
    fi

    exec "${cmd}" "${@}"
}


main() {
    parse_args "${@}"
    get_credentials
    type_or_copy
}

main "${@}"