"""创建唯一后台用户或重置其密码,不提供 HTTP 注册入口。""" from __future__ import annotations import argparse import asyncio import getpass from datetime import datetime, timezone from sqlalchemy import delete, select from app.auth import hash_password from app.config import settings from app.db import async_session, engine from app.models import AuthSession, User def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="创建后台用户或重置密码") parser.add_argument("--username", default="admin", help="登录用户名,默认 admin") return parser.parse_args() async def upsert_user(username: str, password: str) -> str: now = datetime.now(timezone.utc) async with async_session() as db: user = (await db.execute(select(User).where(User.username == username))).scalar_one_or_none() if user is None: user = User( username=username, password_hash=hash_password(password), password_algo="argon2id", is_active=True, failed_login_count=0, password_changed_at=now, created_at=now, updated_at=now, ) db.add(user) action = "created" else: user.password_hash = hash_password(password) user.password_algo = "argon2id" user.is_active = True user.failed_login_count = 0 user.locked_until = None user.password_changed_at = now user.updated_at = now await db.execute(delete(AuthSession).where(AuthSession.user_id == user.id)) action = "updated" await db.commit() return action async def async_main() -> None: args = parse_args() username = args.username.strip() if not username or len(username) > 64: raise SystemExit("用户名长度必须为 1-64 个字符") password = getpass.getpass("Password: ") confirm = getpass.getpass("Confirm password: ") if password != confirm: raise SystemExit("两次密码输入不一致") if len(password) < settings.auth_min_password_length: raise SystemExit(f"密码至少需要 {settings.auth_min_password_length} 个字符") try: action = await upsert_user(username, password) print(f"User {username!r} {action}. All previous sessions were revoked.") finally: await engine.dispose() def main() -> None: asyncio.run(async_main()) if __name__ == "__main__": main()