Files
bakery-ia/services/auth/app/core/auth.py
2026-01-16 15:19:34 +01:00

132 lines
4.0 KiB
Python

"""
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 structlog
from app.core.config import settings
from app.core.database import get_db
from app.models.users import User
logger = structlog.get_logger()
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}")
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