"""认证 API 登录、密码修改、用户管理(管理员) """ import secrets import logging from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel from sqlalchemy import select, update from app.core.auth import hash_password, verify_password, create_access_token from app.core.deps import get_current_user, get_current_admin from app.db.database import get_db from app.db.tables import users_table logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/auth", tags=["auth"]) # ---------- Request/Response Models ---------- class LoginRequest(BaseModel): username: str password: str class ChangePasswordRequest(BaseModel): old_password: str new_password: str class CreateUserRequest(BaseModel): username: str role: str = "user" # ---------- Public Endpoints ---------- @router.post("/login") async def login(req: LoginRequest): """用户登录,返回 JWT token""" async with get_db() as db: result = await db.execute( select(users_table).where(users_table.c.username == req.username) ) user = result.mappings().first() if user is None or not user["is_active"]: raise HTTPException(status_code=401, detail="用户名或密码错误") if not verify_password(req.password, user["password_hash"]): raise HTTPException(status_code=401, detail="用户名或密码错误") token = create_access_token({"sub": str(user["id"]), "role": user["role"]}) return { "token": token, "user": { "id": user["id"], "username": user["username"], "role": user["role"], }, } # ---------- Authenticated Endpoints ---------- @router.get("/me") async def get_me(current_user: dict = Depends(get_current_user)): """获取当前用户信息""" return { "id": current_user["id"], "username": current_user["username"], "role": current_user["role"], "is_active": current_user["is_active"], } @router.post("/change-password") async def change_password( req: ChangePasswordRequest, current_user: dict = Depends(get_current_user), ): """修改自己的密码""" if not verify_password(req.old_password, current_user["password_hash"]): raise HTTPException(status_code=400, detail="旧密码错误") new_hash = hash_password(req.new_password) async with get_db() as db: await db.execute( update(users_table) .where(users_table.c.id == current_user["id"]) .values(password_hash=new_hash) ) await db.commit() return {"message": "密码修改成功"} # ---------- Admin Endpoints ---------- @router.get("/users") async def list_users(admin: dict = Depends(get_current_admin)): """列出所有用户(管理员)""" async with get_db() as db: result = await db.execute( select(users_table).order_by(users_table.c.id) ) rows = result.mappings().all() return [ { "id": r["id"], "username": r["username"], "role": r["role"], "is_active": r["is_active"], "created_at": r["created_at"].isoformat() if r["created_at"] else None, } for r in rows ] @router.post("/users") async def create_user(req: CreateUserRequest, admin: dict = Depends(get_current_admin)): """创建新用户(管理员),自动生成随机密码""" # 检查用户名是否已存在 async with get_db() as db: result = await db.execute( select(users_table).where(users_table.c.username == req.username) ) if result.first(): raise HTTPException(status_code=400, detail="用户名已存在") if req.role not in ("admin", "user"): raise HTTPException(status_code=400, detail="角色必须是 admin 或 user") # 生成 12 位随机密码 raw_password = secrets.token_urlsafe(9) password_hash = hash_password(raw_password) await db.execute( users_table.insert().values( username=req.username, password_hash=password_hash, role=req.role, ) ) await db.commit() logger.info(f"管理员 {admin['username']} 创建了用户 {req.username} ({req.role})") return { "username": req.username, "password": raw_password, "role": req.role, "message": "请妥善保管密码,此密码仅显示一次", } @router.delete("/users/{user_id}") async def disable_user(user_id: int, admin: dict = Depends(get_current_admin)): """禁用用户(软删除)""" if user_id == admin["id"]: raise HTTPException(status_code=400, detail="不能禁用自己") async with get_db() as db: result = await db.execute( select(users_table).where(users_table.c.id == user_id) ) user = result.mappings().first() if not user: raise HTTPException(status_code=404, detail="用户不存在") await db.execute( update(users_table) .where(users_table.c.id == user_id) .values(is_active=False) ) await db.commit() return {"message": f"用户 {user['username']} 已禁用"} @router.post("/users/{user_id}/reset-password") async def reset_password(user_id: int, admin: dict = Depends(get_current_admin)): """重置用户密码(管理员),生成新的随机密码""" async with get_db() as db: result = await db.execute( select(users_table).where(users_table.c.id == user_id) ) user = result.mappings().first() if not user: raise HTTPException(status_code=404, detail="用户不存在") raw_password = secrets.token_urlsafe(9) password_hash = hash_password(raw_password) await db.execute( update(users_table) .where(users_table.c.id == user_id) .values(password_hash=password_hash) ) await db.commit() return { "username": user["username"], "password": raw_password, "message": "请妥善保管新密码,此密码仅显示一次", }