Skip to content

Instantly share code, notes, and snippets.

@tree-s
Last active April 9, 2025 16:35
Show Gist options
  • Save tree-s/85cd8c8eff9524e63f76e4aafbb1f6de to your computer and use it in GitHub Desktop.
Save tree-s/85cd8c8eff9524e63f76e4aafbb1f6de to your computer and use it in GitHub Desktop.
Windows compatable version of user login scripts
import argparse
import os
import json
import subprocess
import tempfile
import re
import sys
import tkinter as tk
from urllib.parse import urlparse
from tkinter import simpledialog
# Get environment variables
QUTE_URL = os.getenv("QUTE_URL", "")
QUTE_FIFO = os.getenv("QUTE_FIFO", "")
# Function to execute shell commands and return output
def run_command(command):
try:
result = subprocess.run(command, encoding='utf-8', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout.strip()
except Exception:
return ""
def run_input_command(command, data):
try:
result = subprocess.run(command, input=data, encoding='utf-8', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout.strip()
except Exception:
return ""
# qute command helpers
def qute_command(command):
with open(QUTE_FIFO, 'a') as fifo:
fifo.write(command + '\n')
fifo.flush()
def fake_key_raw(text):
for character in text:
# Escape all characters by default, space requires special handling
sequence = '" "' if character == ' ' else r'\{}'.format(character)
qute_command('fake-key {}'.format(sequence))
# Function to prompt for the master password using a GUI popup
def prompt_password():
root = tk.Tk()
root.withdraw() # Hide the main window
password = simpledialog.askstring("1Password Login", "Enter your 1Password Master Password:", show="*")
root.destroy()
return password
def main(arguments):
# Extract domain from URL
URL = re.sub(r"https?://(www\.)?", "", QUTE_URL).split('/')[0]
# Token cache file location
TOKEN_CACHE = tempfile.gettempdir() + "\\1pass_session.txt"
# Notify user
qute_command(f"message-info 'Looking for password for {URL}...'")
# Get 1Password session token
TOKEN = ""
if os.path.exists(TOKEN_CACHE):
with open(TOKEN_CACHE, "r") as f:
TOKEN = f.read().strip()
if run_command(f"op signin --force --session={TOKEN} --raw") == "":
TOKEN = ""
else:
TOKEN = ""
if not TOKEN:
master_password = prompt_password()
if master_password:
TOKEN = run_input_command( "op signin --force --raw", master_password)
if TOKEN:
with open(TOKEN_CACHE, "w") as f:
f.write(TOKEN)
else:
qute_command("message-error 'Master password required'")
sys.exit(1)
# Fetch login entry
if TOKEN:
command = f'op item list --format=json --session="{TOKEN}"'
items_json = run_command(command)
if items_json:
items = json.loads(items_json)
matching_entries = [
entry for entry in items
if any(
urlparse(url.get("href", "")).netloc == URL
for url in entry.get("urls", [])
)
]
if not matching_entries:
qute_command(f"message-error 'No entry found for {URL}'")
sys.exit(1)
UUID = matching_entries[0]["id"]
# Get item details
item_json = run_command(f'op item get --format=json --session="{TOKEN}" "{UUID}"')
item = json.loads(item_json) if item_json else {}
# Extract credentials
username = ""
password = ""
for field in item.get("fields", []):
if field.get("purpose") == "USERNAME":
username = field.get("value", "")
if field.get("purpose") == "PASSWORD":
password = field.get("value", "")
# Handle TOTP (if available)
totp = run_command(f'op get totp --session="{TOKEN}" "{UUID}"')
if arguments.username_only:
fake_key_raw(username)
elif arguments.password_only:
fake_key_raw(password)
elif arguments.totp_only:
fake_key_raw(totp)
else:
fake_key_raw(username)
qute_command('fake-key <Tab>')
fake_key_raw(password)
else:
qute_command("message-error 'Error retrieving 1Password items'")
else:
qute_command("message-error 'Wrong master password'")
return 0
if __name__ == '__main__':
argument_parser = argparse.ArgumentParser(
description="Insert login information using 1Password CLI with Windows compatibility",
)
argument_parser.add_argument('url', nargs='?', default=QUTE_URL)
argument_parser.add_argument('--totp', '-t', action='store_true',
help="Copy TOTP key to clipboard")
argument_parser.add_argument('--io-encoding', '-i', default='UTF-8',
help='Encoding used to communicate with subprocesses')
group = argument_parser.add_mutually_exclusive_group()
group.add_argument('--username-only', '-e', action='store_true', help='Only insert username')
group.add_argument('--password-only', '-w', action='store_true', help='Only insert password')
group.add_argument('--totp-only', '-T', action='store_true', help='Only insert TOTP code')
arguments = argument_parser.parse_args()
sys.exit(main(arguments))
import argparse
import os
import sys
import json
import tldextract
import tempfile
import subprocess
import pyautogui
import time
import tkinter as tk
from tkinter import simpledialog
# Get environment variables
QUTE_URL = os.getenv("QUTE_URL", "")
QUTE_FIFO = os.getenv("QUTE_FIFO", "")
def qute_command(command):
with open(QUTE_FIFO, 'a') as fifo:
fifo.write(command + '\n')
fifo.flush()
def fake_key_raw(text):
for character in text:
# Escape all characters by default, space requires special handling
sequence = '" "' if character == ' ' else r'\{}'.format(character)
qute_command('fake-key {}'.format(sequence))
def ask_password():
root = tk.Tk()
root.withdraw()
return simpledialog.askstring("Bitwarden", "Enter Master Password:", show='*')
def select_candidate(candidates):
if len(candidates) == 1:
return candidates[0]
root = tk.Tk()
root.title("Select a Bitwarden Entry")
root.geometry("500x100")
selected = []
listbox = tk.Listbox(root, selectmode=tk.SINGLE)
for c in candidates:
entry_display = f"{c['name']} | {c['login']['username']}"
listbox.insert(tk.END, entry_display)
listbox.pack(fill=tk.BOTH, expand=True)
def on_enter_key(event):
selection = listbox.curselection()
if selection:
selected.append(candidates[selection[0]])
root.destroy()
listbox.bind("<Return>", on_enter_key)
root.mainloop()
return selected[0] if selected else None
def store_session_key(session_key):
with open(tempfile.gettempdir() + "\\bitwarden_session.txt", "w") as f:
f.write(session_key)
def get_session_key(auto_lock):
session_file = tempfile.gettempdir() + "\\bitwarden_session.txt"
if os.path.exists(session_file):
with open(session_file, "r") as f:
return f.read().strip()
master_pass = ask_password()
if not master_pass:
sys.exit("No password entered. Exiting.")
process = subprocess.run(['bw', 'unlock', '--raw'],
input=master_pass, encoding='utf-8',
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if process.returncode != 0:
sys.exit("Could not unlock vault")
session_key = process.stdout.strip()
store_session_key(session_key)
return session_key
def pass_(domain, encoding, auto_lock):
session_key = get_session_key(auto_lock)
if not session_key:
return '[]'
process = subprocess.run(['bw', 'list', 'items', '--session', session_key, '--url', domain],
capture_output=True, text=True)
return process.stdout.strip()
def main(arguments):
# Notify user
qute_command(f"message-info 'Looking for password for {arguments.url}...'")
if not arguments.url:
qute_command("message-error 'No URL provided.'")
sys.exit("No URL provided.")
extract_result = tldextract.extract(arguments.url)
domain = extract_result.registered_domain
candidates = json.loads(pass_(domain, arguments.io_encoding, arguments.auto_lock))
if not candidates:
qute_command(f"message-error 'No credentials found for {arguments.url}'")
sys.exit(f'No credentials found for {arguments.url}')
selection = select_candidate(candidates)
if not selection:
qute_command(f"message-error 'No selection made.'")
sys.exit("No selection made.")
username = selection['login']['username']
password = selection['login']['password']
totp = selection.get('login', {}).get('totp')
if arguments.username_only:
fake_key_raw(username)
elif arguments.password_only:
fake_key_raw(password)
elif arguments.totp_only:
fake_key_raw(totp if totp else "No TOTP available")
else:
fake_key_raw(username)
qute_command('fake-key <Tab>')
fake_key_raw(password)
return 0
if __name__ == '__main__':
argument_parser = argparse.ArgumentParser(
description="Insert login information using Bitwarden CLI with Windows compatibility",
)
argument_parser.add_argument('url', nargs='?', default=QUTE_URL)
argument_parser.add_argument('--totp', '-t', action='store_true',
help="Copy TOTP key to clipboard")
argument_parser.add_argument('--io-encoding', '-i', default='UTF-8',
help='Encoding used to communicate with subprocesses')
argument_parser.add_argument('--auto-lock', type=int, default=900,
help='Automatically lock the vault after this many seconds')
group = argument_parser.add_mutually_exclusive_group()
group.add_argument('--username-only', '-e', action='store_true', help='Only insert username')
group.add_argument('--password-only', '-w', action='store_true', help='Only insert password')
group.add_argument('--totp-only', '-T', action='store_true', help='Only insert TOTP code')
arguments = argument_parser.parse_args()
sys.exit(main(arguments))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment