- **Authentication & Lifespan Updates:** Add @asynccontextmanager for application lifecycle management, including startup/shutdown handling and daily session cleanup scheduling. Reduce token expiration from 24 hours to 15 minutes for enhanced security. Streamline superuser field validation via schema, removing redundant defensive checks.

This commit is contained in:
2025-11-02 12:38:09 +01:00
parent 6e95469d99
commit 76d36e1b12
4 changed files with 50 additions and 631 deletions

View File

@@ -143,17 +143,8 @@ async def update_current_user(
"""
Update current user's profile.
Users cannot elevate their own permissions (is_superuser).
Users cannot elevate their own permissions (protected by UserUpdate schema validator).
"""
# Prevent users from making themselves superuser
# NOTE: Pydantic validator will reject is_superuser != None, but this provides defense in depth
if getattr(user_update, 'is_superuser', None) is not None:
logger.warning(f"User {current_user.id} attempted to modify is_superuser field")
raise AuthorizationError(
message="Cannot modify superuser status",
error_code=ErrorCode.INSUFFICIENT_PERMISSIONS
)
try:
updated_user = await user_crud.update(
db,
@@ -243,7 +234,7 @@ async def update_user(
Update user by ID.
Users can update their own profile. Superusers can update any profile.
Regular users cannot modify is_superuser field.
Superuser field modification is prevented by UserUpdate schema validator.
"""
# Check permissions
is_own_profile = str(user_id) == str(current_user.id)
@@ -265,15 +256,6 @@ async def update_user(
error_code=ErrorCode.USER_NOT_FOUND
)
# Prevent non-superusers from modifying superuser status
# NOTE: Pydantic validator will reject is_superuser != None, but this provides defense in depth
if getattr(user_update, 'is_superuser', None) is not None and not current_user.is_superuser:
logger.warning(f"User {current_user.id} attempted to modify is_superuser field")
raise AuthorizationError(
message="Cannot modify superuser status",
error_code=ErrorCode.INSUFFICIENT_PERMISSIONS
)
try:
updated_user = await user_crud.update(db, db_obj=user, obj_in=user_update)
logger.info(f"User {user_id} updated by {current_user.id}")

View File

@@ -1,4 +1,6 @@
import logging
import os
from contextlib import asynccontextmanager
from datetime import datetime
from typing import Dict, Any
@@ -29,11 +31,54 @@ logger = logging.getLogger(__name__)
# Initialize rate limiter
limiter = Limiter(key_func=get_remote_address)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
Application lifespan context manager.
Handles startup and shutdown events for the application.
Sets up background jobs and scheduled tasks on startup,
cleans up resources on shutdown.
"""
# Startup
logger.info("Application starting up...")
# Skip scheduler in test environment
if os.getenv("IS_TEST", "False") != "True":
from app.services.session_cleanup import cleanup_expired_sessions
# Schedule session cleanup job
# Runs daily at 2:00 AM server time
scheduler.add_job(
cleanup_expired_sessions,
'cron',
hour=2,
minute=0,
id='cleanup_expired_sessions',
replace_existing=True
)
scheduler.start()
logger.info("Scheduled jobs started: session cleanup (daily at 2 AM)")
else:
logger.info("Test environment detected - skipping scheduler")
yield
# Shutdown
logger.info("Application shutting down...")
if os.getenv("IS_TEST", "False") != "True":
scheduler.shutdown()
logger.info("Scheduled jobs stopped")
logger.info(f"Starting app!!!")
app = FastAPI(
title=settings.PROJECT_NAME,
version=settings.VERSION,
openapi_url=f"{settings.API_V1_STR}/openapi.json"
openapi_url=f"{settings.API_V1_STR}/openapi.json",
lifespan=lifespan
)
# Add rate limiter state to app

View File

@@ -14,17 +14,13 @@ from app.core.auth import (
TokenExpiredError,
TokenInvalidError
)
from app.core.exceptions import AuthenticationError
from app.models.user import User
from app.schemas.users import Token, UserCreate, UserResponse
logger = logging.getLogger(__name__)
class AuthenticationError(Exception):
"""Exception raised for authentication errors"""
pass
class AuthService:
"""Service for handling authentication operations"""
@@ -144,7 +140,7 @@ class AuthService:
access_token=access_token,
refresh_token=refresh_token,
user=user_response,
expires_in=86400 # 24 hours in seconds (matching ACCESS_TOKEN_EXPIRE_MINUTES)
expires_in=900 # 15 minutes in seconds (matching ACCESS_TOKEN_EXPIRE_MINUTES)
)
@staticmethod