Spaces:
Sleeping
Sleeping
| from datetime import datetime, timedelta, timezone | |
| from typing import Optional | |
| from jose import JWTError, jwt | |
| from fastapi import Depends, HTTPException, status | |
| from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials | |
| from app.core.config import settings | |
| # Use bcrypt directly to avoid passlib/bcrypt version incompatibility | |
| import bcrypt as _bcrypt | |
| security_scheme = HTTPBearer() | |
| def hash_password(password: str) -> str: | |
| return _bcrypt.hashpw(password.encode("utf-8"), _bcrypt.gensalt()).decode("utf-8") | |
| def verify_password(plain_password: str, hashed_password: str) -> bool: | |
| try: | |
| return _bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8")) | |
| except Exception: | |
| return False | |
| def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: | |
| to_encode = data.copy() | |
| expire = datetime.now(timezone.utc) + ( | |
| expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) | |
| ) | |
| to_encode.update({"exp": expire, "type": "access"}) | |
| return jwt.encode(to_encode, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM) | |
| def create_refresh_token(data: dict) -> str: | |
| to_encode = data.copy() | |
| expire = datetime.now(timezone.utc) + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS) | |
| to_encode.update({"exp": expire, "type": "refresh"}) | |
| return jwt.encode(to_encode, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM) | |
| def decode_token(token: str) -> dict: | |
| try: | |
| payload = jwt.decode(token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM]) | |
| return payload | |
| except JWTError: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid or expired token", | |
| headers={"WWW-Authenticate": "Bearer"}, | |
| ) | |
| async def get_current_user( | |
| credentials: HTTPAuthorizationCredentials = Depends(security_scheme), | |
| ): | |
| payload = decode_token(credentials.credentials) | |
| if payload.get("type") != "access": | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid token type", | |
| ) | |
| user_id = payload.get("sub") | |
| if user_id is None: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid token payload", | |
| ) | |
| return int(user_id) | |
| async def get_optional_current_user( | |
| credentials: Optional[HTTPAuthorizationCredentials] = Depends(HTTPBearer(auto_error=False)), | |
| ) -> Optional[int]: | |
| if credentials is None: | |
| return None | |
| try: | |
| payload = decode_token(credentials.credentials) | |
| if payload.get("type") != "access": | |
| return None | |
| user_id = payload.get("sub") | |
| if user_id is None: | |
| return None | |
| return int(user_id) | |
| except Exception: | |
| return None | |
| async def get_current_user_sse( | |
| token: Optional[str] = None, | |
| credentials: Optional[HTTPAuthorizationCredentials] = Depends(HTTPBearer(auto_error=False)), | |
| ) -> int: | |
| """ | |
| Special version of get_current_user that also checks query parameters for the token. | |
| Required for EventSource (SSE) which doesn't support custom headers easily. | |
| """ | |
| actual_token = token | |
| if not actual_token and credentials: | |
| actual_token = credentials.credentials | |
| if not actual_token: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Not authenticated", | |
| ) | |
| payload = decode_token(actual_token) | |
| user_id = payload.get("sub") | |
| if not user_id: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid token payload", | |
| ) | |
| return int(user_id) | |