updates
This commit is contained in:
@@ -3,6 +3,7 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from jose import JWTError, jwt
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
import os
|
||||
from ...shared.config.database import get_db
|
||||
from ...shared.config.settings import settings
|
||||
@@ -15,26 +16,39 @@ def get_jwt_secret() -> str:
|
||||
Get JWT secret securely, fail if not configured.
|
||||
Never use hardcoded fallback secrets.
|
||||
"""
|
||||
default_secret = 'dev-secret-key-change-in-production-12345'
|
||||
# Remove default secret entirely - fail fast if not configured
|
||||
jwt_secret = getattr(settings, 'JWT_SECRET', None) or os.getenv('JWT_SECRET', None)
|
||||
|
||||
# Fail fast if secret is not configured or using default value
|
||||
if not jwt_secret or jwt_secret == default_secret:
|
||||
if settings.is_production:
|
||||
raise ValueError(
|
||||
'CRITICAL: JWT_SECRET is not properly configured in production. '
|
||||
'Please set JWT_SECRET environment variable to a secure random string.'
|
||||
)
|
||||
# In development, warn but allow (startup validation should catch this)
|
||||
import warnings
|
||||
warnings.warn(
|
||||
f'JWT_SECRET not configured. Using settings value but this is insecure. '
|
||||
f'Set JWT_SECRET environment variable.',
|
||||
UserWarning
|
||||
# Fail fast if secret is not configured
|
||||
if not jwt_secret:
|
||||
error_msg = (
|
||||
'CRITICAL: JWT_SECRET is not configured. '
|
||||
'Please set JWT_SECRET environment variable to a secure random string (minimum 32 characters).'
|
||||
)
|
||||
jwt_secret = getattr(settings, 'JWT_SECRET', None)
|
||||
if not jwt_secret:
|
||||
raise ValueError('JWT_SECRET must be configured')
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(error_msg)
|
||||
if settings.is_production:
|
||||
raise ValueError(error_msg)
|
||||
else:
|
||||
# In development, generate a secure secret but warn
|
||||
import secrets
|
||||
jwt_secret = secrets.token_urlsafe(64)
|
||||
logger.warning(
|
||||
f'JWT_SECRET not configured. Auto-generated secret for development. '
|
||||
f'Set JWT_SECRET environment variable for production: {jwt_secret}'
|
||||
)
|
||||
|
||||
# Validate JWT secret strength
|
||||
if len(jwt_secret) < 32:
|
||||
error_msg = 'JWT_SECRET must be at least 32 characters long for security.'
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(error_msg)
|
||||
if settings.is_production:
|
||||
raise ValueError(error_msg)
|
||||
else:
|
||||
logger.warning(error_msg)
|
||||
|
||||
return jwt_secret
|
||||
|
||||
@@ -80,6 +94,22 @@ def get_current_user(
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
|
||||
# Check if user account is active
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail='Account is disabled. Please contact support.'
|
||||
)
|
||||
|
||||
# Check if account is locked
|
||||
if user.locked_until and user.locked_until > datetime.utcnow():
|
||||
remaining_minutes = int((user.locked_until - datetime.utcnow()).total_seconds() / 60)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f'Account is temporarily locked due to multiple failed login attempts. Please try again in {remaining_minutes} minute(s).'
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
def authorize_roles(*allowed_roles: str):
|
||||
@@ -102,7 +132,8 @@ def get_current_user_optional(
|
||||
) -> Optional[User]:
|
||||
"""
|
||||
Get current user optionally from either Authorization header or httpOnly cookie.
|
||||
Returns None if no valid token is found.
|
||||
Returns None if no valid token is found, or if user is inactive/locked.
|
||||
This ensures inactive/locked users are never considered "authenticated" even for optional features.
|
||||
"""
|
||||
# Try to get token from Authorization header first
|
||||
token = None
|
||||
@@ -124,7 +155,19 @@ def get_current_user_optional(
|
||||
return None
|
||||
except (JWTError, ValueError):
|
||||
return None
|
||||
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if user is None:
|
||||
return None
|
||||
|
||||
# Check if user account is active - return None for inactive users
|
||||
if not user.is_active:
|
||||
return None
|
||||
|
||||
# Check if account is locked - return None for locked users
|
||||
if user.locked_until and user.locked_until > datetime.utcnow():
|
||||
return None
|
||||
|
||||
return user
|
||||
|
||||
def verify_token(token: str) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user