93 lines
2.8 KiB
Python
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)
|