TaskPulse/backend/app/api/auth.py
Steven cd41e59afc feat: add user authentication system
- User model (users table) with bcrypt password hashing
- JWT-based login API (POST /api/auth/login, GET /api/auth/me)
- Default admin account (admin/admin123) auto-created on startup
- Login page with centered dark-themed login card
- Route guards + Axios interceptors for token management
- Login page renders without sidebar (standalone layout)
- Gunicorn --preload to avoid worker DDL race condition
2026-06-14 23:22:53 +08:00

40 lines
1.6 KiB
Python

"""API router — Auth (login, me)."""
from fastapi import APIRouter, Depends, HTTPException, Header
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.schemas import LoginRequest, TokenResponse, UserInfo, UserOut
from app.services.auth import AuthService, create_access_token, decode_access_token
router = APIRouter(prefix="/api/auth", tags=["auth"])
@router.post("/login", response_model=TokenResponse)
async def login(body: LoginRequest, db: AsyncSession = Depends(get_db)):
svc = AuthService(db)
user = await svc.authenticate(body.username, body.password)
if not user:
raise HTTPException(401, detail="用户名或密码错误")
token = create_access_token(user.id, user.username)
return TokenResponse(
access_token=token,
user=UserInfo(id=user.id, username=user.username,
display_name=user.display_name, email=user.email),
)
@router.get("/me", response_model=UserOut)
async def get_me(authorization: str = Header("", alias="Authorization"),
db: AsyncSession = Depends(get_db)):
if not authorization.startswith("Bearer "):
raise HTTPException(401, detail="未登录")
payload = decode_access_token(authorization[7:])
if payload is None:
raise HTTPException(401, detail="登录已过期,请重新登录")
svc = AuthService(db)
user = await svc.get_user_by_id(int(payload["sub"]))
if not user or not user.is_active:
raise HTTPException(401, detail="用户不存在或已停用")
return UserOut(**user.__dict__)