From ea5b75b685e72e2336731cb70327976a9bc7b92d Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Thu, 17 Jul 2025 19:24:57 +0200 Subject: [PATCH] Fix services --- services/auth/app/core/auth.py | 132 +++++++++++++++++++++++ services/data/requirements.txt | 3 +- services/forecasting/requirements.txt | 1 + services/notification/requirements.txt | 1 + services/tenant/requirements.txt | 1 + services/training/app/models/training.py | 6 +- services/training/requirements.txt | 2 + 7 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 services/auth/app/core/auth.py diff --git a/services/auth/app/core/auth.py b/services/auth/app/core/auth.py new file mode 100644 index 00000000..e87bda16 --- /dev/null +++ b/services/auth/app/core/auth.py @@ -0,0 +1,132 @@ +""" +Authentication dependency for auth service +services/auth/app/core/auth.py +""" + +from fastapi import Depends, HTTPException, status +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from jose import JWTError, jwt +import logging + +from app.core.config import settings +from app.core.database import get_db +from app.models.users import User + +logger = logging.getLogger(__name__) + +security = HTTPBearer() + +async def get_current_user( + credentials: HTTPAuthorizationCredentials = Depends(security), + db: AsyncSession = Depends(get_db) +) -> User: + """ + Dependency to get the current authenticated user + """ + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + + try: + # Decode JWT token + payload = jwt.decode( + credentials.credentials, + settings.JWT_SECRET_KEY, + algorithms=[settings.JWT_ALGORITHM] + ) + + # Get user identifier from token + user_id: str = payload.get("sub") + if user_id is None: + logger.warning("Token payload missing 'sub' field") + raise credentials_exception + + logger.info(f"Authenticating user: {user_id}") + + except JWTError as e: + logger.warning(f"JWT decode error: {e}") + raise credentials_exception + + try: + # Get user from database + result = await db.execute( + select(User).where(User.id == user_id) + ) + user = result.scalar_one_or_none() + + if user is None: + logger.warning(f"User not found for ID: {user_id}") + raise credentials_exception + + if not user.is_active: + logger.warning(f"Inactive user attempted access: {user_id}") + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Inactive user" + ) + + logger.info(f"User authenticated: {user.email} (tenant: {user.tenant_id})") + return user + + except Exception as e: + logger.error(f"Error getting user: {e}") + raise credentials_exception + + +async def get_current_active_user( + current_user: User = Depends(get_current_user) +) -> User: + """ + Dependency to get the current active user + """ + if not current_user.is_active: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Inactive user" + ) + return current_user + + +def verify_password(plain_password: str, hashed_password: str) -> bool: + """Verify a password against its hash""" + from passlib.context import CryptContext + pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + return pwd_context.verify(plain_password, hashed_password) + + +def get_password_hash(password: str) -> str: + """Generate password hash""" + from passlib.context import CryptContext + pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + return pwd_context.hash(password) + + +def create_access_token(data: dict, expires_delta=None): + """Create JWT access token""" + from datetime import datetime, timedelta + + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM) + return encoded_jwt + + +def create_refresh_token(data: dict): + """Create JWT refresh token""" + from datetime import datetime, timedelta + + to_encode = data.copy() + expire = datetime.utcnow() + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS) + to_encode.update({"exp": expire}) + + encoded_jwt = jwt.encode(to_encode, settings.JWT_SECRET_KEY, algorithm=settings.JWT_ALGORITHM) + return encoded_jwt \ No newline at end of file diff --git a/services/data/requirements.txt b/services/data/requirements.txt index affefed0..f8f5f85c 100644 --- a/services/data/requirements.txt +++ b/services/data/requirements.txt @@ -9,5 +9,6 @@ httpx==0.25.2 redis==5.0.1 aio-pika==9.3.0 prometheus-client==0.17.1 -python-json-logger==2.0.4 +python-json-logger==2.0.7 pytz==2023.3 +python-logstash==0.4.8 \ No newline at end of file diff --git a/services/forecasting/requirements.txt b/services/forecasting/requirements.txt index affefed0..384c2672 100644 --- a/services/forecasting/requirements.txt +++ b/services/forecasting/requirements.txt @@ -11,3 +11,4 @@ aio-pika==9.3.0 prometheus-client==0.17.1 python-json-logger==2.0.4 pytz==2023.3 +python-logstash==0.4.8 diff --git a/services/notification/requirements.txt b/services/notification/requirements.txt index affefed0..70787063 100644 --- a/services/notification/requirements.txt +++ b/services/notification/requirements.txt @@ -11,3 +11,4 @@ aio-pika==9.3.0 prometheus-client==0.17.1 python-json-logger==2.0.4 pytz==2023.3 +python-logstash==0.4.8 \ No newline at end of file diff --git a/services/tenant/requirements.txt b/services/tenant/requirements.txt index affefed0..70787063 100644 --- a/services/tenant/requirements.txt +++ b/services/tenant/requirements.txt @@ -11,3 +11,4 @@ aio-pika==9.3.0 prometheus-client==0.17.1 python-json-logger==2.0.4 pytz==2023.3 +python-logstash==0.4.8 \ No newline at end of file diff --git a/services/training/app/models/training.py b/services/training/app/models/training.py index f875806f..69c7216f 100644 --- a/services/training/app/models/training.py +++ b/services/training/app/models/training.py @@ -1,5 +1,5 @@ """ -Training models +Training models - Fixed version """ from sqlalchemy import Column, String, Integer, DateTime, Text, JSON, Boolean, Float @@ -79,7 +79,7 @@ class TrainedModel(Base): return f"" class TrainingLog(Base): - """Training log entries""" + """Training log entries - FIXED: renamed metadata to log_metadata""" __tablename__ = "training_logs" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) @@ -93,7 +93,7 @@ class TrainingLog(Base): # Additional data execution_time = Column(Float) # Time taken for this step memory_usage = Column(Float) # Memory usage in MB - metadata = Column(JSON) # Additional metadata + log_metadata = Column(JSON) # FIXED: renamed from 'metadata' to 'log_metadata' created_at = Column(DateTime, default=datetime.utcnow) diff --git a/services/training/requirements.txt b/services/training/requirements.txt index b75ca9a5..058edf79 100644 --- a/services/training/requirements.txt +++ b/services/training/requirements.txt @@ -22,3 +22,5 @@ scipy==1.11.4 # Utilities pytz==2023.3 python-dateutil==2.8.2 + +python-logstash==0.4.8 \ No newline at end of file