80 lines
2.6 KiB
Python
80 lines
2.6 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import getpass
|
|
import sys
|
|
|
|
from .db import connect, init_db, utcnow
|
|
from .services.auth import password_hash
|
|
|
|
|
|
def reset_password(username: str, password: str) -> bool:
|
|
"""Note: Reset the selected user password hash without changing role or permissions."""
|
|
username = (username or "").strip()
|
|
if not username:
|
|
raise ValueError("Username is required")
|
|
if password is None or password == "":
|
|
raise ValueError("Password cannot be empty")
|
|
|
|
init_db()
|
|
now = utcnow()
|
|
hashed = password_hash(password)
|
|
with connect() as conn:
|
|
row = conn.execute("SELECT id FROM users WHERE username=?", (username,)).fetchone()
|
|
if not row:
|
|
return False
|
|
conn.execute(
|
|
"UPDATE users SET password_hash=?, updated_at=? WHERE username=?",
|
|
(hashed, now, username),
|
|
)
|
|
return True
|
|
|
|
|
|
def _password_from_args(args: argparse.Namespace) -> str:
|
|
"""Note: Allow the password to be passed as an argument or entered securely in interactive mode."""
|
|
if args.password is not None:
|
|
return args.password
|
|
first = getpass.getpass("New password: ")
|
|
second = getpass.getpass("Repeat password: ")
|
|
if first != second:
|
|
raise ValueError("Passwords do not match")
|
|
return first
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
"""Note: Define simple administrative commands launched with python -m pytorrent.cli."""
|
|
parser = argparse.ArgumentParser(description="pyTorrent CLI")
|
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
|
|
reset = sub.add_parser("reset-password", help="Reset password for an existing user")
|
|
reset.add_argument("username", help="User login")
|
|
reset.add_argument("password", nargs="?", help="New password; omit to type it interactively")
|
|
reset.set_defaults(func=_cmd_reset_password)
|
|
|
|
return parser
|
|
|
|
|
|
def _cmd_reset_password(args: argparse.Namespace) -> int:
|
|
"""Note: Run the password reset and return a readable terminal status."""
|
|
password = _password_from_args(args)
|
|
if reset_password(args.username, password):
|
|
print(f"Password reset for user: {args.username}")
|
|
return 0
|
|
print(f"User not found: {args.username}", file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
"""Note: Main CLI entrypoint with error handling and without starting the web app."""
|
|
parser = build_parser()
|
|
args = parser.parse_args(argv)
|
|
try:
|
|
return int(args.func(args) or 0)
|
|
except Exception as exc:
|
|
print(f"Error: {exc}", file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|