astock-agent/backend/app/api/auth.py
2026-04-08 00:28:01 +08:00

212 lines
6.2 KiB
Python

"""认证 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": "请妥善保管新密码,此密码仅显示一次",
}