qutebrowser/misc/userscripts/qute-1pass

214 lines
6.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set +e
# JS field injection code from https://github.com/qutebrowser/qutebrowser/blob/main/misc/userscripts/password_fill
javascript_escape() {
# print the first argument in an escaped way, such that it can safely
# be used within javascripts double quotes
# shellcheck disable=SC2001
sed "s,[\\\\'\"\/],\\\\&,g" <<< "$1"
}
js() {
cat <<EOF
function isVisible(elem) {
var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null);
if (style.getPropertyValue("visibility") !== "visible" ||
style.getPropertyValue("display") === "none" ||
style.getPropertyValue("opacity") === "0") {
return false;
}
return elem.offsetWidth > 0 && elem.offsetHeight > 0;
};
function hasPasswordField(form) {
var inputs = form.getElementsByTagName("input");
for (var j = 0; j < inputs.length; j++) {
var input = inputs[j];
if (input.type == "password") {
return true;
}
}
return false;
};
function loadData2Form (form) {
var inputs = form.getElementsByTagName("input");
for (var j = 0; j < inputs.length; j++) {
var input = inputs[j];
if (isVisible(input) && (input.type == "text" || input.type == "email")) {
input.focus();
input.value = "$(javascript_escape "${USERNAME}")";
input.dispatchEvent(new Event('change'));
input.blur();
}
if (input.type == "password") {
input.focus();
input.value = "$(javascript_escape "${PASSWORD}")";
input.dispatchEvent(new Event('change'));
input.blur();
}
}
};
var forms = document.getElementsByTagName("form");
if("$(javascript_escape "${QUTE_URL}")" == window.location.href) {
for (i = 0; i < forms.length; i++) {
if (hasPasswordField(forms[i])) {
loadData2Form(forms[i]);
}
}
} else {
alert("Secrets will not be inserted.\nUrl of this page and the one where the user script was started differ.");
}
EOF
}
URL=$(echo "$QUTE_URL" | awk -F/ '{print $3}' | sed 's/www.//g')
TOKEN_TMPDIR="${TMPDIR:-/tmp}"
TOKEN_CACHE="$TOKEN_TMPDIR/1pass.token"
dmenu-prompt() {
local PROMPT="$1"
if [[ -n "$QUTE_1PASS_DMENU_PROMPT" ]]; then
$QUTE_1PASS_DMENU_PROMPT "$PROMPT"
return
fi
if [[ -n "$WAYLAND_DISPLAY" ]] && command -v wofi >/dev/null; then
wofi --dmenu --insensitive --prompt "$PROMPT"
return
fi
rofi -dmenu -i -p "$PROMPT"
}
dmenu-prompt-pass() {
local PROMPT="$1"
if [[ -n "$QUTE_1PASS_DMENU_PROMPT_PASS" ]]; then
$QUTE_1PASS_DMENU_PROMPT_PASS "$PROMPT"
return
fi
if [[ -n "$WAYLAND_DISPLAY" ]] && command -v wofi >/dev/null; then
wofi --lines 1 --dmenu --password --prompt "$PROMPT"
return
fi
rofi -dmenu -password -p "$PROMPT"
}
clipboard-copy() {
if [[ -n "$QUTE_1PASS_CLIPBOARD" ]]; then
$QUTE_1PASS_CLIPBOARD
return
fi
if [[ -n "$WAYLAND_DISPLAY" ]] && command -v wl-copy >/dev/null; then
wl-copy --trim-newline
return
fi
xclip -in -selection clipboard
}
if ! command -v op > /dev/null; then
echo "message-error 'Missing required command-line tool: op'" >> "$QUTE_FIFO"
exit 1
fi
if ! command -v jq > /dev/null; then
echo "message-error 'Missing required command-line tool: jq'" >> "$QUTE_FIFO"
exit 1
fi
OP_VERSION="$(op --version)"
OP_MAJOR_VERSION="$(echo "$OP_VERSION" | grep --only-matching '^[0-9]')"
if [[ "$OP_MAJOR_VERSION" -lt 2 ]]; then
echo "message-error 'Requires op CLI v2.0.0 or higher, but got: $OP_VERSION'" >> "$QUTE_FIFO"
exit 1
fi
echo "message-info 'Looking for password for $URL...'" >> "$QUTE_FIFO"
JQ_TITLE_EXPR='.title + " (in vault \"" + .vault.name + "\")"'
if [ -f "$TOKEN_CACHE" ]; then
TOKEN="$(cat "$TOKEN_CACHE")"
if ! op signin --raw --session="$TOKEN" > /dev/null; then
TOKEN="$(dmenu-prompt-pass "1password: "| op signin --raw)" || TOKEN=""
echo "$TOKEN" > "$TOKEN_CACHE"
fi
else
TOKEN=$(dmenu-prompt-pass "1password: "| op signin --raw) || TOKEN=""
install -m 600 /dev/null "$TOKEN_CACHE"
echo "$TOKEN" > "$TOKEN_CACHE"
fi
if [[ -z "$TOKEN" ]]; then
echo "message-error 'Wrong master password'" >> "$QUTE_FIFO"
exit 1
fi
if ! LIST_ITEM_OUT="$(op item list --cache --session="$TOKEN" --format=json)"; then
echo "message-error 'Failed to list items.'" >> "$QUTE_FIFO"
exit 1
fi
MATCHING_ITEMS="$(echo "$LIST_ITEM_OUT" | jq --arg url "$URL" '[.[] | select((.urls//[])[].href | test($url))]')"
MATCHING_COUNT="$(echo "$MATCHING_ITEMS" | jq -r 'length')"
UUID=""
if [[ "$MATCHING_COUNT" == 1 ]]; then
UUID="$(echo "$MATCHING_ITEMS" | jq -r '.[0].id')"
TITLE="$(echo "$MATCHING_ITEMS" | jq -r '.[0] | '"$JQ_TITLE_EXPR")"
echo "message-info 'Found 1 entry for $URL: $TITLE'" >> "$QUTE_FIFO"
elif [[ "$MATCHING_COUNT" -gt 1 ]]; then
echo "message-info 'Found $MATCHING_COUNT entries for $URL'" >> "$QUTE_FIFO"
TITLE="$(echo "$MATCHING_ITEMS" | jq -r '.[] | '"$JQ_TITLE_EXPR" | dmenu-prompt "Select item for $URL")" || TITLE=""
if [ -n "$TITLE" ]; then
UUID=$(echo "$MATCHING_ITEMS" | jq --arg title "$TITLE" -r '[.[] | select('"$JQ_TITLE_EXPR"' == $title).id] | first // ""') || UUID=""
else
UUID=""
fi
else
echo "message-error 'No entry found for $URL'" >> "$QUTE_FIFO"
TITLE="$(echo "$LIST_ITEM_OUT" | jq -r '.[] | '"$JQ_TITLE_EXPR" | dmenu-prompt)" || TITLE=""
if [ -n "$TITLE" ]; then
UUID=$(echo "$LIST_ITEM_OUT" | jq --arg title "$TITLE" -r '[.[] | select('"$JQ_TITLE_EXPR"' == $title).id] | first // ""') || UUID=""
else
UUID=""
fi
fi
if [[ -z "$UUID" ]];then
echo "message-error 'No item picked.'" >> "$QUTE_FIFO"
exit 1
fi
ITEM="$(op item get --cache --session="$TOKEN" --format=json "$UUID")"
TITLE="$(echo "$ITEM" | jq -r "$JQ_TITLE_EXPR")"
PASSWORD="$(echo "$ITEM" | jq -r '[.fields[] | select(.purpose=="PASSWORD") | .value] | first // ""')"
if [ -z "$PASSWORD" ]; then
echo "message-error 'No password found in $TITLE'" >> "$QUTE_FIFO"
exit 1
fi
USERNAME="$(echo "$ITEM" | jq -r '[.fields[] | select(.purpose=="USERNAME") | .value] | first // ""')"
printjs() {
js | sed 's,//.*$,,' | tr '\n' ' '
}
echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
echo "message-info 'Using credentials from: $TITLE'" >> "$QUTE_FIFO"
TOTP="$(echo "$ITEM" | op item get --cache --session="$TOKEN" --otp "$UUID")" || TOTP=""
if [ -n "$TOTP" ]; then
echo "$TOTP" | clipboard-copy
echo "message-info 'Pasted one time password for $TITLE to clipboard'" >> "$QUTE_FIFO"
fi