Fix token issue
This commit is contained in:
@@ -99,6 +99,7 @@ class UserUpdate(BaseModel):
|
||||
phone: Optional[str] = None
|
||||
language: Optional[str] = Field(None, pattern="^(es|en)$")
|
||||
timezone: Optional[str] = None
|
||||
tenant_id: Optional[str] = None
|
||||
|
||||
@validator('phone')
|
||||
def validate_phone(cls, v):
|
||||
|
||||
@@ -39,6 +39,9 @@ class AuthService:
|
||||
detail="Email already registered"
|
||||
)
|
||||
|
||||
# Generate tenant_id if not provided
|
||||
tenant_id = user_data.tenant_id if hasattr(user_data, 'tenant_id') and user_data.tenant_id else str(uuid.uuid4())
|
||||
|
||||
# Hash password
|
||||
hashed_password = security_manager.hash_password(user_data.password)
|
||||
|
||||
@@ -46,6 +49,7 @@ class AuthService:
|
||||
user = User(
|
||||
email=user_data.email,
|
||||
hashed_password=hashed_password,
|
||||
tenant_id=tenant_id,
|
||||
full_name=user_data.full_name,
|
||||
phone=user_data.phone,
|
||||
language=user_data.language,
|
||||
@@ -61,6 +65,7 @@ class AuthService:
|
||||
event_data = {
|
||||
"user_id": str(user.id),
|
||||
"email": user.email,
|
||||
"tenant_id": user.tenant_id,
|
||||
"full_name": user.full_name,
|
||||
"language": user.language,
|
||||
"timestamp": datetime.now(timezone.utc).isoformat()
|
||||
|
||||
@@ -10,7 +10,7 @@ import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.auth import verify_token
|
||||
from app.core.auth import get_current_user, AuthInfo
|
||||
from app.services.sales_service import SalesService
|
||||
from app.services.data_import_service import DataImportService
|
||||
from app.services.messaging import data_publisher
|
||||
@@ -27,7 +27,7 @@ router = APIRouter()
|
||||
async def create_sales_record(
|
||||
sales_data: SalesDataCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Create a new sales record"""
|
||||
try:
|
||||
@@ -49,7 +49,7 @@ async def create_sales_record(
|
||||
async def get_sales_data(
|
||||
query: SalesDataQuery,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Get sales data by query parameters"""
|
||||
try:
|
||||
@@ -64,7 +64,7 @@ async def import_sales_data(
|
||||
file_format: str = Form(...),
|
||||
file: UploadFile = File(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Import sales data from file"""
|
||||
try:
|
||||
@@ -96,7 +96,7 @@ async def import_sales_data(
|
||||
async def import_sales_json(
|
||||
import_data: SalesDataImport,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Import sales data from JSON"""
|
||||
try:
|
||||
@@ -123,7 +123,7 @@ async def import_sales_json(
|
||||
@router.post("/import/validate")
|
||||
async def validate_import_data(
|
||||
import_data: SalesDataImport,
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Validate import data before processing"""
|
||||
try:
|
||||
@@ -138,7 +138,7 @@ async def validate_import_data(
|
||||
@router.get("/import/template/{format_type}")
|
||||
async def get_import_template(
|
||||
format_type: str,
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Get import template for specified format"""
|
||||
try:
|
||||
@@ -178,7 +178,7 @@ async def import_sales_data_advanced(
|
||||
file: UploadFile = File(...),
|
||||
validate_only: bool = Form(False),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Advanced import with validation and preview options"""
|
||||
try:
|
||||
@@ -239,7 +239,7 @@ async def get_import_history(
|
||||
limit: int = Query(10, ge=1, le=100),
|
||||
offset: int = Query(0, ge=0),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Get import history for tenant"""
|
||||
try:
|
||||
@@ -292,7 +292,7 @@ async def delete_import_batch(
|
||||
import_date: str, # Format: YYYY-MM-DD
|
||||
source: str = Query(..., description="Import source (csv, excel, json, pos)"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Delete an entire import batch"""
|
||||
try:
|
||||
@@ -354,7 +354,7 @@ async def get_sales_statistics(
|
||||
start_date: datetime = Query(None, description="Start date for statistics"),
|
||||
end_date: datetime = Query(None, description="End date for statistics"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Get sales statistics for tenant"""
|
||||
try:
|
||||
@@ -454,7 +454,7 @@ async def export_sales_data(
|
||||
end_date: datetime = Query(None, description="End date"),
|
||||
products: List[str] = Query(None, description="Filter by products"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Export sales data in specified format"""
|
||||
try:
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import List, Optional
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.auth import verify_token
|
||||
from app.core.auth import get_current_user, AuthInfo
|
||||
from app.services.traffic_service import TrafficService
|
||||
from app.services.messaging import data_publisher
|
||||
from app.schemas.external import (
|
||||
@@ -25,7 +25,7 @@ traffic_service = TrafficService()
|
||||
async def get_current_traffic(
|
||||
latitude: float = Query(..., description="Latitude"),
|
||||
longitude: float = Query(..., description="Longitude"),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Get current traffic data for location"""
|
||||
try:
|
||||
@@ -52,7 +52,7 @@ async def get_historical_traffic(
|
||||
start_date: datetime = Query(..., description="Start date"),
|
||||
end_date: datetime = Query(..., description="End date"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Get historical traffic data"""
|
||||
try:
|
||||
|
||||
@@ -9,7 +9,7 @@ from typing import List, Optional
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.auth import verify_token
|
||||
from app.core.auth import get_current_user, AuthInfo
|
||||
from app.services.weather_service import WeatherService
|
||||
from app.services.messaging import data_publisher
|
||||
from app.schemas.external import (
|
||||
@@ -26,7 +26,7 @@ weather_service = WeatherService()
|
||||
async def get_current_weather(
|
||||
latitude: float = Query(..., description="Latitude"),
|
||||
longitude: float = Query(..., description="Longitude"),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Get current weather for location"""
|
||||
try:
|
||||
@@ -43,7 +43,7 @@ async def get_weather_forecast(
|
||||
latitude: float = Query(..., description="Latitude"),
|
||||
longitude: float = Query(..., description="Longitude"),
|
||||
days: int = Query(7, description="Number of forecast days", ge=1, le=14),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Get weather forecast for location"""
|
||||
try:
|
||||
@@ -69,7 +69,7 @@ async def get_historical_weather(
|
||||
start_date: datetime = Query(..., description="Start date"),
|
||||
end_date: datetime = Query(..., description="End date"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(verify_token)
|
||||
current_user: AuthInfo = Depends(get_current_user)
|
||||
):
|
||||
"""Get historical weather data"""
|
||||
try:
|
||||
|
||||
@@ -1,35 +1,71 @@
|
||||
# ================================================================
|
||||
# services/data/app/core/auth.py
|
||||
# ================================================================
|
||||
"""Authentication utilities for data service"""
|
||||
|
||||
from fastapi import HTTPException, Depends, status
|
||||
from fastapi import HTTPException, Depends, status, Request
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
import httpx
|
||||
import structlog
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
logger = structlog.get_logger()
|
||||
security = HTTPBearer()
|
||||
security = HTTPBearer(auto_error=False) # ✅ Don't auto-error, we'll handle manually
|
||||
|
||||
async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)) -> dict:
|
||||
"""Verify JWT token with auth service"""
|
||||
class AuthInfo:
|
||||
"""Authentication information"""
|
||||
def __init__(self, user_id: str, email: str, tenant_id: str, roles: list):
|
||||
self.user_id = user_id
|
||||
self.email = email
|
||||
self.tenant_id = tenant_id
|
||||
self.roles = roles
|
||||
|
||||
async def get_current_user(
|
||||
request: Request,
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)
|
||||
) -> AuthInfo:
|
||||
"""Get current user from gateway headers or token verification"""
|
||||
|
||||
# ✅ OPTION 1: Check for gateway headers (preferred when using gateway)
|
||||
user_id = request.headers.get("X-User-ID")
|
||||
email = request.headers.get("X-User-Email")
|
||||
tenant_id = request.headers.get("X-Tenant-ID")
|
||||
roles_header = request.headers.get("X-User-Roles", "")
|
||||
|
||||
if user_id and email and tenant_id:
|
||||
# Gateway already authenticated the user
|
||||
roles = roles_header.split(",") if roles_header else ["user"]
|
||||
logger.info("Authenticated via gateway headers", user_id=user_id, email=email)
|
||||
return AuthInfo(user_id, email, tenant_id, roles)
|
||||
|
||||
# ✅ OPTION 2: Direct token verification (when not using gateway)
|
||||
if not credentials:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authentication required (no token or gateway headers)"
|
||||
)
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||
response = await client.post(
|
||||
f"{settings.AUTH_SERVICE_URL}/api/v1/auth/verify",
|
||||
headers={"Authorization": f"Bearer {credentials.credentials}"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
user_data = response.json()
|
||||
logger.info("Authenticated via direct token", user_id=user_data.get("user_id"))
|
||||
return AuthInfo(
|
||||
user_id=user_data["user_id"],
|
||||
email=user_data["email"],
|
||||
tenant_id=user_data["tenant_id"],
|
||||
roles=user_data.get("roles", ["user"])
|
||||
)
|
||||
else:
|
||||
logger.warning("Token verification failed", status_code=response.status_code)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid authentication credentials"
|
||||
)
|
||||
except httpx.RequestError:
|
||||
except httpx.RequestError as e:
|
||||
logger.error("Auth service unavailable", error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Authentication service unavailable"
|
||||
|
||||
Reference in New Issue
Block a user