"""The account the TUI syncs with: server, API token and the book key. Token and key are stored protected (secretstore.py), the server and user name in the clear. A config from before 2026-10 (token and key as plain JSON fields) is still read and is rewritten protected at once. """ from __future__ import annotations import json import stat from dataclasses import dataclass from pathlib import Path from . import paths, secretstore _CONFIG_DIR = paths.config_dir() _CONFIG_FILE = _CONFIG_DIR / "config.json" @dataclass class RemoteConfig: server_url: str api_token: str enc_key_b64: str username: str = "" def load() -> RemoteConfig | None: try: data = json.loads(_CONFIG_FILE.read_text()) except (OSError, ValueError): return None if not isinstance(data, dict): return None try: if "secret" in data: secret = json.loads(secretstore.unprotect(data["secret"])) return RemoteConfig(data["server_url"], secret["token"], secret["key"], data.get("username", "")) cfg = RemoteConfig(data["server_url"], data["api_token"], data["enc_key_b64"], data.get("username", "")) except (KeyError, ValueError, OSError): return None save(cfg) # a legacy plaintext config: protect it now return cfg def save(cfg: RemoteConfig) -> None: _CONFIG_DIR.mkdir(parents=True, exist_ok=True) secret = json.dumps({"token": cfg.api_token, "key": cfg.enc_key_b64}).encode() _CONFIG_FILE.write_text(json.dumps({ "server_url": cfg.server_url, "username": cfg.username, "secret": secretstore.protect(secret), }, indent=2)) try: _CONFIG_FILE.chmod(stat.S_IRUSR | stat.S_IWUSR) except OSError: pass def forget() -> bool: try: _CONFIG_FILE.unlink() return True except OSError: return False