From 0030c5c35d8535ddd6524cd6da9c1162a2e39441 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Thu, 5 Feb 2026 21:53:30 +0000 Subject: [PATCH] Remove the reset subcommand that was broken and unnecessary. --- paskia/fastapi/__main__.py | 40 ++------------ paskia/fastapi/reset.py | 106 ------------------------------------- 2 files changed, 5 insertions(+), 141 deletions(-) delete mode 100644 paskia/fastapi/reset.py diff --git a/paskia/fastapi/__main__.py b/paskia/fastapi/__main__.py index 936a20d..ab4b9ef 100644 --- a/paskia/fastapi/__main__.py +++ b/paskia/fastapi/__main__.py @@ -13,18 +13,14 @@ from paskia import globals as _globals from paskia.bootstrap import bootstrap_if_needed from paskia.config import PaskiaConfig from paskia.db.background import flush -from paskia.fastapi import reset as reset_cmd from paskia.util import startupbox from paskia.util.hostutil import normalize_origin DEFAULT_PORT = 4401 EPILOG = """\ -Examples: - paskia # localhost:4401 - paskia -l :8080 # All interfaces, port 8080 - paskia -l /tmp/paskia.sock # Unix socket - paskia reset [user] # Generate passkey reset link +Example: + paskia --rp-id example.com --rp-name "Example Corporation" --auth-host auth.example.com """ @@ -63,10 +59,7 @@ def add_common_options(p: argparse.ArgumentParser) -> None: ) p.add_argument( "--auth-host", - help=( - "Dedicated host (optionally with scheme/port) to serve the auth UI at the root," - " e.g. auth.example.com or https://auth.example.com" - ), + help=("Dedicated authentication site (optionally with scheme/port)"), ) @@ -81,17 +74,6 @@ def main(): epilog=EPILOG, ) - # Subcommand for reset - parser.add_argument( - "command", - nargs="?", - help="Command: 'reset' for credential reset, or omit to run server", - ) - parser.add_argument( - "reset_query", - nargs="?", - help="For 'reset' command: user UUID or substring of display name", - ) parser.add_argument( "-l", "--listen", @@ -105,16 +87,8 @@ def main(): args = parser.parse_args() - # Detect "reset" subcommand - is_reset = args.command == "reset" - - if is_reset: - endpoints = [] - else: - if args.command is not None: - raise SystemExit(f"Unknown command: {args.command}") - # Parse endpoint using fastapi_vue.hostutil - endpoints = parse_endpoint(args.listen, DEFAULT_PORT) + # Parse endpoint using fastapi_vue.hostutil + endpoints = parse_endpoint(args.listen, DEFAULT_PORT) # Extract host/port/uds from first endpoint for config display and site_url ep = endpoints[0] if endpoints else {} @@ -218,10 +192,6 @@ def main(): await bootstrap_if_needed() await flush() - if is_reset: - exit_code = reset_cmd.run(args.reset_query) - raise SystemExit(exit_code) - if len(endpoints) > 1: async with asyncio.TaskGroup() as tg: for ep in endpoints: diff --git a/paskia/fastapi/reset.py b/paskia/fastapi/reset.py deleted file mode 100644 index 6eb20bb..0000000 --- a/paskia/fastapi/reset.py +++ /dev/null @@ -1,106 +0,0 @@ -"""CLI support for creating user credential reset links. - -Usage (via main CLI): - paskia reset [query] - -If query is omitted, the master admin (first Administration role user in -an organization granting auth:admin) is targeted. Otherwise query is -matched as either an exact UUID or a case-insensitive substring of the -display name. If multiple users match, they are listed and the command -aborts. A new one-time reset link is always created. -""" - -import asyncio -from uuid import UUID - -from paskia import authsession as _authsession -from paskia import db -from paskia.util import hostutil - - -async def _resolve_targets(query: str | None): - if query: - # Try UUID - targets: list[tuple] = [] - try: - q_uuid = UUID(query) - p = next( - (p for p in db.data().permissions.values() if p.scope == "auth:admin"), - None, - ) - if p: - for org_uuid in p.orgs: - users = db.get_organization_users(org_uuid) - for u, role_name in users: - if u.uuid == q_uuid: - return [(u, role_name)] - # UUID not found among admin orgs -> fall back to substring search (rare case) - except ValueError: - pass - # Substring search - needle = query.lower() - p = next( - (p for p in db.data().permissions.values() if p.scope == "auth:admin"), None - ) - if p: - for org_uuid in p.orgs: - users = db.get_organization_users(org_uuid) - for u, role_name in users: - if needle in (u.display_name or "").lower(): - targets.append((u, role_name)) - # De-duplicate - seen = set() - deduped = [] - for u, role_name in targets: - if u.uuid not in seen: - seen.add(u.uuid) - deduped.append((u, role_name)) - return deduped - # No query -> master admin - p = next( - (p for p in db.data().permissions.values() if p.scope == "auth:admin"), None - ) - if not p or not p.orgs: - return [] - first_org_uuid = next(iter(p.orgs)) - users = db.get_organization_users(first_org_uuid) - admin_users = [pair for pair in users if pair[1] == "Administration"] - return admin_users[:1] - - -async def _create_reset(user, role_name: str): - expiry = _authsession.reset_expires() - token = db.create_reset_token( - user_uuid=user.uuid, - expiry=expiry, - token_type="manual reset", - ) - return hostutil.reset_link_url(token), token - - -async def _main(query: str | None) -> int: - try: - candidates = await _resolve_targets(query) - if not candidates: - print("No matching users found") - return 1 - if len(candidates) > 1: - print("Multiple matches. Refine your query:") - for u, role_name in candidates: - print(f" - {u.display_name} ({u.uuid}) role={role_name}") - return 2 - user, role_name = candidates[0] - link, token = await _create_reset(user, role_name) - print(f"Reset link for {user.display_name} ({user.uuid}):\n{link}\n") - return 0 - except Exception as e: # pragma: no cover - print("Failed to create reset link:", e) - return 1 - - -def run(query: str | None) -> int: - """Synchronous wrapper for CLI entrypoint.""" - return asyncio.run(_main(query)) - - -__all__ = ["run"]