78 lines
3.1 KiB
Python
78 lines
3.1 KiB
Python
"""Administrative commands run inside the backend container."""
|
|
|
|
import argparse
|
|
import asyncio
|
|
import getpass
|
|
|
|
from sqlalchemy import delete, update
|
|
|
|
from .config import Settings
|
|
from .db import create_database
|
|
from .models import Admin, LoginSession, MCPToken, now
|
|
from .security import password_hasher
|
|
|
|
|
|
async def reset_password():
|
|
password = getpass.getpass("New admin password (12+ characters): ")
|
|
if len(password) < 12 or password != getpass.getpass("Confirm password: "):
|
|
raise SystemExit("Password too short or confirmation does not match")
|
|
engine, sessions = create_database(Settings().database_url)
|
|
async with sessions() as db:
|
|
admin = await db.get(Admin, 1)
|
|
admin.password_hash = password_hasher.hash(password)
|
|
await db.execute(delete(LoginSession))
|
|
await db.execute(update(MCPToken).where(MCPToken.revoked_at.is_(None)).values(revoked_at=now()))
|
|
await db.commit()
|
|
await engine.dispose()
|
|
print("Admin password updated; all system sessions and MCP tokens revoked.")
|
|
|
|
|
|
async def token_command(args):
|
|
import json
|
|
|
|
from sqlalchemy import select
|
|
|
|
from .mcp_api.auth import create_token
|
|
from .models import MCPToken, now
|
|
from .research.serialization import encode_snapshot
|
|
|
|
engine, sessions = create_database(Settings().database_url)
|
|
try:
|
|
async with sessions.begin() as db:
|
|
if args.command == "mcp-token-create":
|
|
row, secret = await create_token(db, args.name, args.scope, args.days)
|
|
result = {"id": row.id, "name": row.name, "scopes": row.scopes,
|
|
"expires_at": row.expires_at, "token": secret}
|
|
elif args.command == "mcp-token-revoke":
|
|
row = await db.get(MCPToken, args.token_id)
|
|
if not row:
|
|
raise ValueError("令牌不存在")
|
|
row.revoked_at = row.revoked_at or now()
|
|
result = {"id": row.id, "revoked": True}
|
|
else:
|
|
rows = list(await db.scalars(select(MCPToken).order_by(MCPToken.created_at.desc())))
|
|
result = [{k: getattr(row, k) for k in
|
|
("id", "name", "scopes", "created_at", "expires_at", "revoked_at")} for row in rows]
|
|
# Reveal only after the transaction has committed successfully.
|
|
print(json.dumps(encode_snapshot(result), ensure_ascii=False, indent=2))
|
|
finally:
|
|
await engine.dispose()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser()
|
|
commands = parser.add_subparsers(dest="command", required=True)
|
|
commands.add_parser("reset-password")
|
|
create = commands.add_parser("mcp-token-create")
|
|
create.add_argument("--name", required=True)
|
|
create.add_argument("--scope", action="append", default=None)
|
|
create.add_argument("--days", type=int, default=90)
|
|
commands.add_parser("mcp-token-list")
|
|
revoke = commands.add_parser("mcp-token-revoke")
|
|
revoke.add_argument("token_id")
|
|
args = parser.parse_args()
|
|
try:
|
|
asyncio.run(reset_password() if args.command == "reset-password" else token_command(args))
|
|
except ValueError as exc:
|
|
parser.error(str(exc))
|