Files
diary-news/backend/app/api/auth.py
Mavis 5109d6f824 fix: API 全部改用显式两步走 await session.execute + result.scalars()
之前 (await ...).scalars() 链式在 SQLAlchemy 2.0 async 下报
'coroutine' has no attribute 'scalars' 错误。改为先 await 拿 result
再 .scalars(),这是 SQLAlchemy 2.0 推荐的 async 写法。
2026-06-07 23:22:56 +08:00

60 lines
2.2 KiB
Python

"""登录/刷新/登出。"""
from __future__ import annotations
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException, status
from jwt.exceptions import InvalidTokenError
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.core.security import (
create_access_token,
create_refresh_token,
decode_token,
verify_password,
)
from app.database import get_session
from app.models.user import User
from app.schemas.auth import LoginRequest, RefreshRequest, TokenPair
router = APIRouter(prefix="/auth", tags=["auth"])
def _pair_for(user: User) -> TokenPair:
access = create_access_token(user.id, extra={"role": user.role.value})
refresh = create_refresh_token(user.id)
return TokenPair(
access_token=access,
refresh_token=refresh,
expires_in=settings.access_token_ttl_min * 60,
)
@router.post("/login", response_model=TokenPair)
async def login(body: LoginRequest, session: AsyncSession = Depends(get_session)):
result = await session.execute(select(User).where(User.username == body.username))
user = result.scalars().first()
if not user or not user.is_active or not verify_password(body.password, user.password_hash):
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid credentials")
user.last_login_at = datetime.now(timezone.utc)
await session.commit()
return _pair_for(user)
@router.post("/refresh", response_model=TokenPair)
async def refresh(body: RefreshRequest, session: AsyncSession = Depends(get_session)):
try:
payload = decode_token(body.refresh_token)
if payload.get("type") != "refresh":
raise InvalidTokenError("wrong type")
uid = int(payload["sub"])
except (InvalidTokenError, KeyError, ValueError):
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid refresh token")
result = await session.execute(select(User).where(User.id == uid, User.is_active.is_(True)))
user = result.scalars().first()
if not user:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "User not found")
return _pair_for(user)