from datetime import datetime, timedelta, timezone from typing import Any, Dict, Optional from fastapi import HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from passlib.context import CryptContext from app.core.config import settings ALGORITHM = "HS256" pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login", scheme_name="Bearer") def create_access_token( *, user_id: str, expires_minutes: Optional[int] = None, session_start: Optional[datetime] = None, ) -> str: now = datetime.now(timezone.utc) expire = now + timedelta(minutes=expires_minutes or settings.JWT_EXPIRE_MINUTES) session_start_time = session_start or now to_encode: Dict[str, Any] = { "sub": user_id, "exp": expire, "iat": int(now.timestamp()), "orig_iat": int(session_start_time.timestamp()), } return jwt.encode(to_encode, settings.JWT_SECRET_KEY, algorithm=ALGORITHM) def decode_token(token: str) -> Dict[str, Any]: try: payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[ALGORITHM]) except JWTError as exc: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无法验证登录凭据", headers={"WWW-Authenticate": "Bearer"}, ) from exc return payload def decode_token_allow_expired(token: str) -> Dict[str, Any]: try: payload = jwt.decode( token, settings.JWT_SECRET_KEY, algorithms=[ALGORITHM], options={"verify_exp": False}, ) except JWTError as exc: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无法验证登录凭据", headers={"WWW-Authenticate": "Bearer"}, ) from exc return payload def hash_password(plain_password: str) -> str: return pwd_context.hash(plain_password) def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password)