commit 5b566a4529c7af5cb57aec3d72804c4b0c3b71f4 Author: Fabian van Koppen Date: Sun Jul 13 21:07:01 2025 +0200 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9ca76cf --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[build-system] +requires = ["hatchling >= 1.26"] +build-backend = "hatchling.build" + +[project] +name = "bw-get" +version = "0.0.1" +authors = [ + {name="Fabian van Koppen", email="f@bianvk.nl"} +] +description = "Wrapper script voor de Bitwarden CLI" +readme = "README.md" +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", +] +dependencies = [ +"questionary", +"pyotp", +"pyperclip", +"typer", +"python-gnupg", +"typing" +] + +[project.scripts] +bw-get = "bw_get.cli:app" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..33ba0fb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +questionary +pyotp +pyperclip +typer diff --git a/src/bw_get/__init__.py b/src/bw_get/__init__.py new file mode 100644 index 0000000..1e83655 --- /dev/null +++ b/src/bw_get/__init__.py @@ -0,0 +1,3 @@ +if __name__ == "__main__": + from bw_get.cli import app + app() diff --git a/src/bw_get/bw_get.py b/src/bw_get/bw_get.py new file mode 100755 index 0000000..ad9a559 --- /dev/null +++ b/src/bw_get/bw_get.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +import json +import gnupg +import questionary +import pyperclip +import argparse +import typer +from typing_extensions import Annotated +from sys import exit +from os import environ +from subprocess import run, DEVNULL + +home = environ.get('HOME') + +gpg = gnupg.GPG(gnupghome=f"{home}/.gnupg", use_agent=True) + +def check_lock() -> bool: + check = run(['bw', 'unlock', '--check'], stdout=DEVNULL, stderr=DEVNULL) + if check.returncode != 0: + return False + else: return True + +def decrypt_session() -> str: + with open(f"{home}/.secret/bw-session.txt.gpg", 'rb') as f: + master_pw = str(gpg.decrypt_file(f)).strip() + + environ["BW_SESSION"] = f"{master_pw}" + f.close() + +def new_session(): + with open(f"{home}/.secret/bw-master.txt.gpg", 'rb') as f: + password = str(gpg.decrypt_file(f)).strip() + + unlock_command = run(['bw', 'unlock', password, '--raw'], capture_output=True, text=True) + environ["BW_SESSION"] = str(unlock_command.stdout) + if check_lock() is False: + print("Something went wrong unlocking the vault") + exit(1) + + +def get_password(search_string: Annotated[str, typer.Argument(help="The term to search for in Vaultwarden")] = ""): + + decrypt_session() + if check_lock() is False: + new_session() + + list_items_cmd = run(['bw', 'list', 'items'], capture_output=True) + + items = json.loads(list_items_cmd.stdout) + + # Filtering the list based on the search string + filtered_list = [ + d for d in items if any( + search_string in str(value) for key, value in d.items() + if key != 'login' # Exclude the 'login' key entirely + ) or ( + 'login' in d and ( + search_string in str(d['login'].get('username', '')) or + search_string in str(d['login'].get('password', '')) + ) + ) + ] + + choices = [] + + if len(filtered_list) > 1: + for item_index, item in enumerate(filtered_list): + choices.append(questionary.Choice(f"{item['name']} ({item['login']['username']})", item_index)) + + chosen_index = questionary.select("Select item", choices, use_shortcuts=True).ask() + elif (filtered_list) == 1: + chosen_index = 0 + else: chosen_index = None + + if chosen_index is not None: + pyperclip.copy(filtered_list[chosen_index]['login']['password']) + if filtered_list[chosen_index]['login']['totp']: + print("Todo, fix OTP") + else: + print("No item found") + exit(1) diff --git a/src/bw_get/cli.py b/src/bw_get/cli.py new file mode 100644 index 0000000..080824d --- /dev/null +++ b/src/bw_get/cli.py @@ -0,0 +1,9 @@ +import typer + +from .bw_get import get_password + +app = typer.Typer() +app.command()(get_password) + +if __name__ == "__main__": + app()