nex_math/backend/routers/auth.py

93 lines
2.8 KiB
Python

"""登录、会话与个人账号接口。"""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from database import get_db
from dependencies import get_current_user
from models import Role, User, UserRole
from schemas import (
ChangePasswordIn,
LoginIn,
ProfileUpdate,
RoleOut,
TokenOut,
UserOut,
)
from services.security import create_access_token, hash_password, verify_password
router = APIRouter(prefix="/auth", tags=["auth"])
def user_roles(db: Session, user: User) -> list[Role]:
return (
db.query(Role)
.join(UserRole, UserRole.role_id == Role.id)
.filter(UserRole.user_id == user.id)
.order_by(Role.id.asc())
.all()
)
def user_to_out(db: Session, user: User) -> UserOut:
roles = user_roles(db, user)
return UserOut(
id=user.id,
username=user.username,
display_name=user.display_name,
active=user.active,
roles=[RoleOut.model_validate(role) for role in roles],
created_at=user.created_at,
)
@router.post("/login", response_model=TokenOut)
def login(payload: LoginIn, db: Session = Depends(get_db)):
user = db.query(User).filter(User.username == payload.username).first()
if user is None or not verify_password(payload.password, user.password_hash):
raise HTTPException(status_code=401, detail="用户名或密码错误")
if not user.active:
raise HTTPException(status_code=403, detail="账号已被停用")
roles = user_roles(db, user)
if not roles:
raise HTTPException(status_code=403, detail="账号未分配角色")
return TokenOut(
access_token=create_access_token(user.id),
role=roles[0].code,
display_name=user.display_name or user.username,
)
@router.get("/me", response_model=UserOut)
def me(user: User = Depends(get_current_user), db: Session = Depends(get_db)):
return user_to_out(db, user)
@router.post("/change-password")
def change_password(
payload: ChangePasswordIn,
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
if not verify_password(payload.old_password, user.password_hash):
raise HTTPException(status_code=400, detail="原密码不正确")
user.password_hash = hash_password(payload.new_password)
db.commit()
return {"ok": True, "message": "密码已更新,下次登录请使用新密码。"}
@router.put("/profile", response_model=UserOut)
def update_profile(
payload: ProfileUpdate,
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
name = payload.display_name.strip()
if not name:
raise HTTPException(status_code=400, detail="昵称不能为空")
user.display_name = name
db.commit()
return user_to_out(db, user)