""" Authentication utilities for public web frontend. Provides authentication checking and decorators for protected routes. Uses API backend for session validation. """ from functools import wraps from flask import session, redirect, url_for, request, flash from .logging import get_logger logger = get_logger(__file__) # Track last API validation time per session to avoid excessive checks _SESSION_VALIDATION_KEY = '_api_validated_at' def get_current_user(): """ Get the currently authenticated user from session. Returns: Dictionary with user data if authenticated, None otherwise. """ # Check if we have user in Flask session if 'user' in session and session.get('user'): return session['user'] return None def require_auth_web(f): """ Decorator to require authentication for web routes. Validates the session with the API backend and redirects to login if not authenticated. Args: f: Flask route function Returns: Wrapped function that checks authentication """ @wraps(f) def decorated_function(*args, **kwargs): user = get_current_user() if user is None: logger.info("Unauthenticated access attempt", path=request.path) # Store the intended destination session['next'] = request.url return redirect(url_for('auth_views.login')) return f(*args, **kwargs) return decorated_function def clear_user_session(): """ Clear user session data. Should be called after logout. """ session.pop('user', None) session.pop('next', None) session.pop('api_session_cookie', None) session.pop(_SESSION_VALIDATION_KEY, None) logger.debug("User session cleared") def require_auth_strict(revalidate_interval: int = 300): """ Decorator to require authentication with API session validation. This decorator validates the session with the API backend periodically to ensure the session is still valid on the server side. Args: revalidate_interval: Seconds between API validation checks (default 5 minutes). Set to 0 to validate on every request. Returns: Decorator function. Usage: @app.route('/protected') @require_auth_strict() # Validates every 5 minutes def protected_route(): pass @app.route('/sensitive') @require_auth_strict(revalidate_interval=0) # Validates every request def sensitive_route(): pass """ def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): import time from .api_client import get_api_client, APIAuthenticationError, APIError user = get_current_user() if user is None: logger.info("Unauthenticated access attempt", path=request.path) session['next'] = request.url return redirect(url_for('auth_views.login')) # Check if we need to revalidate with API current_time = time.time() last_validated = session.get(_SESSION_VALIDATION_KEY, 0) if revalidate_interval == 0 or (current_time - last_validated) > revalidate_interval: try: # Validate session by hitting a lightweight endpoint api_client = get_api_client() api_client.get("/api/v1/auth/me") # Update validation timestamp session[_SESSION_VALIDATION_KEY] = current_time session.modified = True logger.debug("API session validated", user_id=user.get('id')) except APIAuthenticationError: # Session expired on server side logger.warning( "API session expired", user_id=user.get('id'), path=request.path ) clear_user_session() flash('Your session has expired. Please log in again.', 'warning') session['next'] = request.url return redirect(url_for('auth_views.login')) except APIError as e: # API error - log but allow through (fail open for availability) logger.error( "API validation error", user_id=user.get('id'), error=str(e) ) # Don't block the user, but don't update validation timestamp return f(*args, **kwargs) return decorated_function return decorator