Imporve gateway auth for all services

This commit is contained in:
Urtzi Alfaro
2025-07-21 14:41:33 +02:00
parent df7c6e1e00
commit 2d85dd3e9e
6 changed files with 188 additions and 63 deletions

View File

@@ -4,7 +4,7 @@ User management API routes
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from typing import List from typing import Dict, Any
import structlog import structlog
from app.core.database import get_db from app.core.database import get_db
@@ -14,12 +14,19 @@ from app.services.user_service import UserService
from app.core.auth import get_current_user from app.core.auth import get_current_user
from app.models.users import User from app.models.users import User
# Import unified authentication from shared library
from shared.auth.decorators import (
get_current_user_dep,
get_current_tenant_id_dep,
require_role # For admin-only endpoints
)
logger = structlog.get_logger() logger = structlog.get_logger()
router = APIRouter() router = APIRouter()
@router.get("/me", response_model=UserResponse) @router.get("/me", response_model=UserResponse)
async def get_current_user_info( async def get_current_user_info(
current_user: User = Depends(get_current_user), current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db) db: AsyncSession = Depends(get_db)
): ):
"""Get current user information""" """Get current user information"""

View File

@@ -5,7 +5,7 @@
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from typing import List, Optional from typing import List, Dict, Any
from datetime import datetime, timedelta from datetime import datetime, timedelta
import structlog import structlog
@@ -19,6 +19,11 @@ from app.schemas.external import (
DateRangeRequest DateRangeRequest
) )
from shared.auth.decorators import (
get_current_user_dep,
get_current_tenant_id_dep
)
router = APIRouter() router = APIRouter()
traffic_service = TrafficService() traffic_service = TrafficService()
logger = structlog.get_logger() logger = structlog.get_logger()
@@ -27,7 +32,7 @@ logger = structlog.get_logger()
async def get_current_traffic( async def get_current_traffic(
latitude: float = Query(..., description="Latitude"), latitude: float = Query(..., description="Latitude"),
longitude: float = Query(..., description="Longitude"), longitude: float = Query(..., description="Longitude"),
current_user: AuthInfo = Depends(get_current_user) current_user: Dict[str, Any] = Depends(get_current_user_dep),
): ):
"""Get current traffic data for location""" """Get current traffic data for location"""
try: try:
@@ -72,7 +77,7 @@ async def get_historical_traffic(
start_date: datetime = Query(..., description="Start date"), start_date: datetime = Query(..., description="Start date"),
end_date: datetime = Query(..., description="End date"), end_date: datetime = Query(..., description="End date"),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
current_user: AuthInfo = Depends(get_current_user) current_user: Dict[str, Any] = Depends(get_current_user_dep),
): ):
"""Get historical traffic data""" """Get historical traffic data"""
try: try:
@@ -116,7 +121,7 @@ async def store_traffic_data(
latitude: float = Query(..., description="Latitude"), latitude: float = Query(..., description="Latitude"),
longitude: float = Query(..., description="Longitude"), longitude: float = Query(..., description="Longitude"),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
current_user: AuthInfo = Depends(get_current_user) current_user: Dict[str, Any] = Depends(get_current_user_dep)
): ):
"""Store current traffic data to database""" """Store current traffic data to database"""
try: try:

View File

@@ -6,10 +6,9 @@ from typing import List, Optional, Dict, Any
from datetime import datetime, date from datetime import datetime, date
import structlog import structlog
from app.schemas.weather import ( from app.schemas.external import (
WeatherDataResponse, WeatherDataResponse,
WeatherForecastResponse, WeatherForecastResponse
WeatherSummaryResponse
) )
from app.services.weather_service import WeatherService from app.services.weather_service import WeatherService
from app.services.messaging import publish_weather_updated from app.services.messaging import publish_weather_updated
@@ -136,35 +135,6 @@ async def get_weather_history(
logger.error("Failed to get weather history", error=str(e)) logger.error("Failed to get weather history", error=str(e))
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}") raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
@router.get("/summary", response_model=WeatherSummaryResponse)
async def get_weather_summary(
location_id: Optional[str] = Query(None, description="Location ID"),
days: int = Query(30, description="Number of days to summarize"),
tenant_id: str = Depends(get_current_tenant_id_dep),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
):
"""Get weather summary for tenant's location"""
try:
logger.debug("Getting weather summary",
location_id=location_id,
days=days,
tenant_id=tenant_id)
weather_service = WeatherService()
# If no location_id provided, use tenant's default location
if not location_id:
# This would typically fetch from tenant service
location_id = tenant_id # Simplified for example
summary = await weather_service.get_weather_summary(location_id, days)
return summary
except Exception as e:
logger.error("Failed to get weather summary", error=str(e))
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
@router.post("/sync") @router.post("/sync")
async def sync_weather_data( async def sync_weather_data(
background_tasks: BackgroundTasks, background_tasks: BackgroundTasks,

View File

@@ -0,0 +1,72 @@
from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks
from typing import List, Optional, Dict, Any
from datetime import datetime, date
import structlog
from app.schemas.forecast import (
ForecastRequest,
ForecastResponse,
BatchForecastRequest,
ForecastPerformanceResponse
)
from app.services.forecast_service import ForecastService
from app.services.messaging import publish_forecast_generated
# Import unified authentication
from shared.auth.decorators import (
get_current_user_dep,
get_current_tenant_id_dep
)
router = APIRouter(prefix="/forecasts", tags=["forecasting"])
logger = structlog.get_logger()
@router.post("/generate", response_model=ForecastResponse)
async def generate_forecast(
request: ForecastRequest,
background_tasks: BackgroundTasks,
tenant_id: str = Depends(get_current_tenant_id_dep),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
):
"""Generate forecast for products"""
try:
logger.info("Generating forecast",
tenant_id=tenant_id,
user_id=current_user["user_id"],
products=len(request.products) if request.products else "all")
forecast_service = ForecastService()
# Ensure products belong to tenant
if request.products:
valid_products = await forecast_service.validate_products(
tenant_id, request.products
)
if len(valid_products) != len(request.products):
raise HTTPException(
status_code=400,
detail="Some products not found or not accessible"
)
# Generate forecast
forecast = await forecast_service.generate_forecast(
tenant_id=tenant_id,
request=request,
user_id=current_user["user_id"]
)
# Publish event
background_tasks.add_task(
publish_forecast_generated,
forecast_id=forecast.id,
tenant_id=tenant_id,
user_id=current_user["user_id"]
)
return forecast
except HTTPException:
raise
except Exception as e:
logger.error("Failed to generate forecast", error=str(e))
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -0,0 +1,77 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional, Dict, Any
import structlog
from app.schemas.notification import (
NotificationCreate,
NotificationResponse,
NotificationPreferences,
NotificationHistory
)
from app.services.notification_service import NotificationService
# Import unified authentication
from shared.auth.decorators import (
get_current_user_dep,
get_current_tenant_id_dep,
require_role
)
router = APIRouter(prefix="/notifications", tags=["notifications"])
logger = structlog.get_logger()
@router.post("/send", response_model=NotificationResponse)
async def send_notification(
notification: NotificationCreate,
tenant_id: str = Depends(get_current_tenant_id_dep),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
):
"""Send notification to users"""
try:
logger.info("Sending notification",
tenant_id=tenant_id,
sender_id=current_user["user_id"],
type=notification.type)
notification_service = NotificationService()
# Ensure notification is scoped to tenant
notification.tenant_id = tenant_id
notification.sender_id = current_user["user_id"]
# Check permissions
if notification.broadcast and current_user.get("role") not in ["admin", "manager"]:
raise HTTPException(
status_code=403,
detail="Only admins and managers can send broadcast notifications"
)
result = await notification_service.send_notification(notification)
return result
except HTTPException:
raise
except Exception as e:
logger.error("Failed to send notification", error=str(e))
raise HTTPException(status_code=500, detail=str(e))
@router.get("/preferences", response_model=NotificationPreferences)
async def get_notification_preferences(
tenant_id: str = Depends(get_current_tenant_id_dep),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
):
"""Get user's notification preferences"""
try:
notification_service = NotificationService()
preferences = await notification_service.get_user_preferences(
user_id=current_user["user_id"],
tenant_id=tenant_id
)
return preferences
except Exception as e:
logger.error("Failed to get preferences", error=str(e))
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -14,24 +14,26 @@ from app.schemas.tenants import (
TenantUpdate, TenantMemberResponse TenantUpdate, TenantMemberResponse
) )
from app.services.tenant_service import TenantService from app.services.tenant_service import TenantService
from shared.auth.decorators import require_authentication, get_current_user, get_current_tenant_id # Import unified authentication
from shared.auth.decorators import (
get_current_user_dep,
get_current_tenant_id_dep,
require_role
)
logger = structlog.get_logger() logger = structlog.get_logger()
router = APIRouter() router = APIRouter()
@router.post("/tenants/register", response_model=TenantResponse) @router.post("/tenants/register", response_model=TenantResponse)
@require_authentication
async def register_bakery( async def register_bakery(
bakery_data: BakeryRegistration, bakery_data: BakeryRegistration,
request: Request, current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db) db: AsyncSession = Depends(get_db)
): ):
"""Register a new bakery/tenant"""
user = get_current_user(request)
try: try:
result = await TenantService.create_bakery(bakery_data, user["user_id"], db) result = await TenantService.create_bakery(bakery_data, current_user["user_id"], db)
logger.info(f"Bakery registered: {bakery_data.name} by {user['email']}") logger.info(f"Bakery registered: {bakery_data.name} by {current_user['email']}")
return result return result
except Exception as e: except Exception as e:
@@ -64,11 +66,9 @@ async def verify_tenant_access(
@require_authentication @require_authentication
async def get_user_tenants( async def get_user_tenants(
user_id: str, user_id: str,
request: Request, current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db) db: AsyncSession = Depends(get_db)
): ):
"""Get all tenants accessible by user"""
current_user = get_current_user(request)
# Users can only see their own tenants # Users can only see their own tenants
if current_user["user_id"] != user_id: if current_user["user_id"] != user_id:
@@ -92,14 +92,12 @@ async def get_user_tenants(
@require_authentication @require_authentication
async def get_tenant( async def get_tenant(
tenant_id: str, tenant_id: str,
request: Request, current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db) db: AsyncSession = Depends(get_db)
): ):
"""Get tenant details"""
user = get_current_user(request)
# Verify user has access to tenant # Verify user has access to tenant
access = await TenantService.verify_user_access(user["user_id"], tenant_id, db) access = await TenantService.verify_user_access(current_user["user_id"], tenant_id, db)
if not access.has_access: if not access.has_access:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
@@ -120,14 +118,12 @@ async def get_tenant(
async def update_tenant( async def update_tenant(
tenant_id: str, tenant_id: str,
update_data: TenantUpdate, update_data: TenantUpdate,
request: Request, current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db) db: AsyncSession = Depends(get_db)
): ):
"""Update tenant information"""
user = get_current_user(request)
try: try:
result = await TenantService.update_tenant(tenant_id, update_data, user["user_id"], db) result = await TenantService.update_tenant(tenant_id, update_data, current_user["user_id"], db)
return result return result
except HTTPException: except HTTPException:
@@ -145,11 +141,9 @@ async def add_team_member(
tenant_id: str, tenant_id: str,
user_id: str, user_id: str,
role: str, role: str,
request: Request, current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db) db: AsyncSession = Depends(get_db)
): ):
"""Add a team member to tenant"""
current_user = get_current_user(request)
try: try:
result = await TenantService.add_team_member( result = await TenantService.add_team_member(