refactor: move clipboard to frontend, fix cipher type mapping, add context menu

This commit is contained in:
2026-08-26 15:28:35 +02:00
parent a3e9f9ab0e
commit deba9a419d
7 changed files with 140 additions and 118 deletions
+5 -46
View File
@@ -15,21 +15,6 @@ from cryptography.hazmat.primitives import hashes
log = logging.getLogger("decky-vaultwarden")
_DEBUG_LOG = "/tmp/decky-vaultwarden-debug.log"
def _debug(msg: str):
"""Write debug info to file for easy inspection."""
import datetime
ts = datetime.datetime.now().isoformat()
line = f"[{ts}] {msg}\n"
try:
with open(_DEBUG_LOG, "a") as f:
f.write(line)
f.flush()
except Exception:
pass
from crypto import BitwardenCrypto, PinCrypto
@@ -125,9 +110,7 @@ class BitwardenClient:
method, url, headers=headers, json=data, params=params, ssl=_get_ssl_context()
) as resp:
text = await resp.text()
_debug(f"[REQUEST] {method} {url} -> {resp.status}")
if resp.status >= 400:
_debug(f"[REQUEST] error body: {text[:500]}")
try:
error_data = json.loads(text)
message = error_data.get("error_model", {}).get(
@@ -144,10 +127,7 @@ class BitwardenClient:
"""Get KDF settings for the user."""
url = f"{self._get_api_url()}/accounts/prelogin"
data = {"email": email}
_debug(f"[PRELOGIN] POST {url}")
_debug(f"[PRELOGIN] request data: {json.dumps(data)}")
result = await self._request("POST", url, data=data)
_debug(f"[PRELOGIN] response: {json.dumps(result)}")
self.kdf_type = result.get("kdf") or result.get("Kdf", 0)
self.kdf_iterations = result.get("kdfIterations") or result.get("KdfIterations", 600000)
@@ -155,7 +135,6 @@ class BitwardenClient:
self.kdf_parallelism = result.get("kdfParallelism") or result.get("KdfParallelism")
self.email = email
_debug(f"[PRELOGIN] parsed kdf_type={self.kdf_type}, kdf_iterations={self.kdf_iterations}, kdf_memory={self.kdf_memory}, kdf_parallelism={self.kdf_parallelism}")
return result
@@ -163,7 +142,6 @@ class BitwardenClient:
self, email: str, password: str, two_factor_token: Optional[str] = None
) -> dict:
"""Login with email and master password."""
_debug(f"[LOGIN] Starting password login for: {email}")
await self.prelogin(email)
# Derive master key
@@ -182,7 +160,6 @@ class BitwardenClient:
else:
raise ValueError(f"Unsupported KDF type: {self.kdf_type}")
_debug(f"[LOGIN] master_key (first 8 bytes b64): {b64encode(master_key[:8]).decode()}")
# Hash master password for auth (Bitwarden uses PBKDF2 with 1 iteration, NOT HMAC)
_pwd_hash_kdf = PBKDF2HMAC(
@@ -194,7 +171,6 @@ class BitwardenClient:
)
master_password_hash = b64encode(_pwd_hash_kdf.derive(master_key)).decode("utf-8")
_debug(f"[LOGIN] master_password_hash: {master_password_hash}")
# Build auth request
url = f"{self.identity_url}/connect/token"
@@ -217,9 +193,6 @@ class BitwardenClient:
data["two_factor_provider"] = "0" # Authenticator
data["two_factor_remember"] = "1"
_debug(f"[LOGIN] POST {url}")
_debug(f"[LOGIN] form data (excl password): {json.dumps({k: v for k, v in data.items() if k != 'password'}, indent=2)}")
_debug(f"[LOGIN] password field (master_password_hash): {data['password']}")
# Use form data instead of JSON
async with aiohttp.ClientSession() as session:
@@ -227,12 +200,9 @@ class BitwardenClient:
url, data=data, headers=headers, ssl=_get_ssl_context()
) as resp:
text = await resp.text()
_debug(f"[LOGIN] response status: {resp.status}")
_debug(f"[LOGIN] response body: {text[:500]}")
if resp.status >= 400:
try:
error_data = json.loads(text)
_debug(f"[LOGIN] error response: {json.dumps(error_data, indent=2)}")
# Check if 2FA is required
if error_data.get("error") == "invalid_grant" and "twoFactor" in text:
return {"two_factor_required": True}
@@ -240,32 +210,20 @@ class BitwardenClient:
"message", text
)
except (json.JSONDecodeError, KeyError):
_debug(f"[LOGIN] raw error text: {text}")
message = text
raise Exception(f"Login failed: {message}")
result = json.loads(text)
_debug("[LOGIN] SUCCESS - token received")
self.access_token = result.get("access_token")
self.refresh_token = result.get("refresh_token")
self.user_id = result.get("Profile", {}).get("id") or result.get("sub")
# Get encrypted user key
enc_user_key = result.get("Key")
_debug(f"[LOGIN] enc_user_key present: {enc_user_key is not None}")
if enc_user_key:
_debug(f"[LOGIN] enc_user_key (first 40): {enc_user_key[:40]}...")
_debug(f"[LOGIN] attempting to decrypt user key with master_key...")
try:
self.enc_key, self.mac_key = self.crypto.decrypt_user_key(
enc_user_key, master_key
)
_debug(f"[LOGIN] user key decrypted successfully!")
_debug(f"[LOGIN] enc_key (first 8 b64): {b64encode(self.enc_key[:8]).decode()}")
_debug(f"[LOGIN] mac_key (first 8 b64): {b64encode(self.mac_key[:8]).decode()}")
except Exception as e:
_debug(f"[LOGIN] user key decryption FAILED: {e}")
raise
self.enc_key, self.mac_key = self.crypto.decrypt_user_key(
enc_user_key, master_key
)
return {
"success": True,
"master_key": master_key,
@@ -383,7 +341,8 @@ class BitwardenClient:
# Decrypt ciphers
decrypted_ciphers = []
for cipher in ciphers:
if cipher.get("type") in [0, 1, 2, 3]: # Login, Note, Card, Identity
ctype = cipher.get("type")
if ctype in [1, 2, 3, 4]: # Login, Note, Card, Identity
decrypted = self.crypto.decrypt_cipher(
cipher, enc_key, mac_key
)