Remove the reset subcommand that was broken and unnecessary.
This commit is contained in:
@@ -13,18 +13,14 @@ from paskia import globals as _globals
|
|||||||
from paskia.bootstrap import bootstrap_if_needed
|
from paskia.bootstrap import bootstrap_if_needed
|
||||||
from paskia.config import PaskiaConfig
|
from paskia.config import PaskiaConfig
|
||||||
from paskia.db.background import flush
|
from paskia.db.background import flush
|
||||||
from paskia.fastapi import reset as reset_cmd
|
|
||||||
from paskia.util import startupbox
|
from paskia.util import startupbox
|
||||||
from paskia.util.hostutil import normalize_origin
|
from paskia.util.hostutil import normalize_origin
|
||||||
|
|
||||||
DEFAULT_PORT = 4401
|
DEFAULT_PORT = 4401
|
||||||
|
|
||||||
EPILOG = """\
|
EPILOG = """\
|
||||||
Examples:
|
Example:
|
||||||
paskia # localhost:4401
|
paskia --rp-id example.com --rp-name "Example Corporation" --auth-host auth.example.com
|
||||||
paskia -l :8080 # All interfaces, port 8080
|
|
||||||
paskia -l /tmp/paskia.sock # Unix socket
|
|
||||||
paskia reset [user] # Generate passkey reset link
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -63,10 +59,7 @@ def add_common_options(p: argparse.ArgumentParser) -> None:
|
|||||||
)
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--auth-host",
|
"--auth-host",
|
||||||
help=(
|
help=("Dedicated authentication site (optionally with scheme/port)"),
|
||||||
"Dedicated host (optionally with scheme/port) to serve the auth UI at the root,"
|
|
||||||
" e.g. auth.example.com or https://auth.example.com"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -81,17 +74,6 @@ def main():
|
|||||||
epilog=EPILOG,
|
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(
|
parser.add_argument(
|
||||||
"-l",
|
"-l",
|
||||||
"--listen",
|
"--listen",
|
||||||
@@ -105,16 +87,8 @@ def main():
|
|||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Detect "reset" subcommand
|
# Parse endpoint using fastapi_vue.hostutil
|
||||||
is_reset = args.command == "reset"
|
endpoints = parse_endpoint(args.listen, DEFAULT_PORT)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Extract host/port/uds from first endpoint for config display and site_url
|
# Extract host/port/uds from first endpoint for config display and site_url
|
||||||
ep = endpoints[0] if endpoints else {}
|
ep = endpoints[0] if endpoints else {}
|
||||||
@@ -218,10 +192,6 @@ def main():
|
|||||||
await bootstrap_if_needed()
|
await bootstrap_if_needed()
|
||||||
await flush()
|
await flush()
|
||||||
|
|
||||||
if is_reset:
|
|
||||||
exit_code = reset_cmd.run(args.reset_query)
|
|
||||||
raise SystemExit(exit_code)
|
|
||||||
|
|
||||||
if len(endpoints) > 1:
|
if len(endpoints) > 1:
|
||||||
async with asyncio.TaskGroup() as tg:
|
async with asyncio.TaskGroup() as tg:
|
||||||
for ep in endpoints:
|
for ep in endpoints:
|
||||||
|
|||||||
@@ -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"]
|
|
||||||
Reference in New Issue
Block a user