Datavision / backend /core /auth.py
DataVision CI/CD Bot
release: clean production build for HuggingFace Space
ee7d7b9
Raw
History Blame Contribute Delete
6.12 kB
import os
import bcrypt
from datetime import datetime, timedelta, timezone
from typing import Optional
from jose import jwt, JWTError
from fastapi import Request, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import logging
from authlib.integrations.starlette_client import OAuth
from starlette.config import Config
logger = logging.getLogger(__name__)
# Setup Authlib OAuth — reads from .env file or OS environment variables (HuggingFace Spaces secrets)
oauth_config = Config(".env")
oauth = OAuth(oauth_config)
# Register Google OAuth (only works if GOOGLE_CLIENT_ID is set)
try:
oauth.register(
name='google',
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={
'scope': 'openid email profile'
}
)
except Exception as e:
logger.warning(f"Google OAuth registration failed: {e}")
# Register GitHub OAuth (only works if GITHUB_CLIENT_ID is set)
try:
oauth.register(
name='github',
api_base_url='https://api.github.com/',
access_token_url='https://github.com/login/oauth/access_token',
authorize_url='https://github.com/login/oauth/authorize',
client_kwargs={
'scope': 'user:email'
}
)
except Exception as e:
logger.warning(f"GitHub OAuth registration failed: {e}")
SECRET_KEY = os.environ.get("JWT_SECRET") or os.environ.get("JWT_SECRET_KEY") or "datavision-production-jwt-secret-key-32bytes-long!"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7 days for ease of use
security = HTTPBearer(auto_error=False)
class AuthenticatedUser:
def __init__(self, user_id: str, email: Optional[str] = None, role: str = "authenticated", is_guest: bool = False):
self.id = user_id
self.user_id = user_id
self.email = email
self.role = role
self.is_guest = is_guest
def __repr__(self):
return f"User(id={self.id}, email={self.email}, role={self.role})"
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 get_password_hash(password: str) -> str:
salt = bcrypt.gensalt()
return bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def decode_jwt_token(token: str) -> dict:
"""Decode and validate a JWT token. Standard HS256 verification."""
try:
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
except JWTError as e:
raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}")
def decode_jwt(token: str) -> dict:
return decode_jwt_token(token)
def extract_token_from_request(request: Request) -> Optional[str]:
auth_header = request.headers.get("Authorization", "")
if auth_header.startswith("Bearer "):
return auth_header[7:]
return request.cookies.get("dv-access-token") or request.query_params.get("token")
def generate_guest_id(request: Request) -> str:
import hashlib
ip = request.client.host if request.client else "unknown"
ua = request.headers.get("User-Agent", "unknown")[:100]
fingerprint = hashlib.sha256(f"{ip}:{ua}".encode()).hexdigest()[:12]
return f"guest_{fingerprint}"
async def get_current_user(
request: Request,
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)
) -> AuthenticatedUser:
token = credentials.credentials if credentials else extract_token_from_request(request)
if token and token not in ["null", "undefined", ""]:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id = payload.get("sub")
if not user_id:
raise HTTPException(status_code=401, detail="Token missing user ID")
return AuthenticatedUser(
user_id=user_id,
email=payload.get("email"),
role=payload.get("role", "authenticated"),
is_guest=False
)
except JWTError as e:
logger.warning(f"Token validation failed: {e}")
guest_id = generate_guest_id(request)
return AuthenticatedUser(user_id=guest_id, email=None, role="anon", is_guest=True)
async def get_current_user_optional(request: Request, credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)):
try:
return await get_current_user(request, credentials)
except HTTPException:
return None
async def require_authenticated_user(user: AuthenticatedUser = Depends(get_current_user)):
if user.is_guest:
raise HTTPException(status_code=401, detail="Authentication required")
return user
async def get_user_id_from_token(user: AuthenticatedUser = Depends(get_current_user)) -> str:
return user.id
def get_user_id_from_body_deprecated(body_user_id: Optional[str], user: AuthenticatedUser) -> str:
if not user.is_guest:
return user.id
if body_user_id and body_user_id not in ["default", "guest", "", "null", "undefined"]:
return body_user_id
return user.id
async def get_admin_user(user: AuthenticatedUser = Depends(require_authenticated_user)):
if user.role not in ["admin", "super_admin"]:
raise HTTPException(status_code=403, detail="Admin access required")
return user
async def get_super_admin_user(user: AuthenticatedUser = Depends(require_authenticated_user)):
if user.role != "super_admin":
raise HTTPException(status_code=403, detail="Super admin access required")
return user