""" Local Terminal Recovery Script — Reset Master Password. Security Invariant: • Executes ONLY on the local host via terminal/CLI. • Directly modifies the local SQLite app.db — never exposed over HTTP/network. """ from __future__ import annotations import argparse import getpass import sqlite3 import sys from datetime import UTC, datetime from pathlib import Path import bcrypt ROOT_DIR = Path(__file__).resolve().parents[1] APP_DB = ROOT_DIR / "app.db" def reset_password(new_password: str) -> None: if len(new_password) < 8: print("Error: Password must be at least 8 characters.", file=sys.stderr) sys.exit(1) APP_DB.parent.mkdir(parents=True, exist_ok=True) conn = sqlite3.connect(str(APP_DB)) cur = conn.cursor() cur.execute( """CREATE TABLE IF NOT EXISTS auth_config ( key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL )""" ) pw_bytes = new_password.encode("utf-8")[:72] pw_hash = bcrypt.hashpw(pw_bytes, bcrypt.gensalt()).decode("utf-8") now = datetime.now(UTC).isoformat() cur.execute( """INSERT INTO auth_config(key, value, updated_at) VALUES (?,?,?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at""", ("master_password_hash", pw_hash, now), ) conn.commit() conn.close() print("[OK] Master password reset successfully in app.db.") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Reset PA/POS master password locally from the server console.") parser.add_argument("--password", type=str, help="New master password (optional; prompted securely if omitted)") args = parser.parse_args() password = args.password if not password: password = getpass.getpass("Enter new master password (min 8 chars): ") confirm = getpass.getpass("Confirm new master password: ") if password != confirm: print("Error: Passwords do not match.", file=sys.stderr) sys.exit(1) reset_password(password)