import logging from datetime import datetime, timedelta, timezone from jose import jwt, JWTError import bcrypt from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from sqlalchemy.orm import Session from config import get_settings from database import get_db from models.user import User logger = logging.getLogger(__name__) security = HTTPBearer() optional_security = HTTPBearer(auto_error=False) settings = get_settings() def get_password_hash(password: str) -> str: salt = bcrypt.gensalt() hashed = bcrypt.hashpw(password.encode("utf-8"), salt) return hashed.decode("utf-8") def verify_password(plain_password: str, hashed_password: str) -> bool: return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8")) def create_access_token(user_id: int) -> str: expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) payload = {"sub": str(user_id), "exp": expire, "type": "access"} token = jwt.encode(payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM) logger.info(f"Created token for user {user_id}") return token def create_refresh_token(user_id: int) -> str: expire = datetime.now(timezone.utc) + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS) payload = {"sub": str(user_id), "exp": expire, "type": "refresh"} return jwt.encode(payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM) def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db), ) -> User: token = credentials.credentials try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) user_id = int(payload.get("sub")) logger.info(f"Token decoded for user {user_id}") except JWTError as e: logger.error(f"JWT decode error: {e}") raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的认证凭证") except (TypeError, ValueError) as e: logger.error(f"Invalid sub claim: {e}") raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的认证凭证") user = db.query(User).filter(User.id == user_id).first() if user is None: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="用户不存在") if not user.is_active: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="账号已被禁用") return user def get_optional_current_user( credentials: HTTPAuthorizationCredentials | None = Depends(optional_security), db: Session = Depends(get_db), ) -> User | None: if credentials is None: return None try: payload = jwt.decode(credentials.credentials, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) user_id = int(payload.get("sub")) except (JWTError, TypeError, ValueError): return None user = db.query(User).filter(User.id == user_id).first() if user is None or not user.is_active: return None return user def get_admin_user(user: User = Depends(get_current_user)) -> User: if user.role != "admin": raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="需要管理员权限") return user