mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 07:31:09 -05:00
- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols. - Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage. - Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
285 lines
10 KiB
Python
285 lines
10 KiB
Python
"""
|
|
Django REST Framework configuration for thrillwiki project.
|
|
|
|
This module configures DRF, SimpleJWT, dj-rest-auth, CORS, and
|
|
drf-spectacular (OpenAPI documentation).
|
|
|
|
Why python-decouple?
|
|
- Already used in base.py for consistency
|
|
- Simpler API than django-environ
|
|
- Sufficient for our configuration needs
|
|
- Better separation of config from code
|
|
"""
|
|
|
|
from datetime import timedelta
|
|
from decouple import config
|
|
|
|
# =============================================================================
|
|
# Django REST Framework Settings
|
|
# =============================================================================
|
|
|
|
REST_FRAMEWORK = {
|
|
# Authentication classes (order matters - first match wins)
|
|
"DEFAULT_AUTHENTICATION_CLASSES": [
|
|
"rest_framework_simplejwt.authentication.JWTAuthentication",
|
|
"rest_framework.authentication.SessionAuthentication",
|
|
"rest_framework.authentication.TokenAuthentication", # Backward compatibility
|
|
],
|
|
# Default permissions - require authentication
|
|
"DEFAULT_PERMISSION_CLASSES": [
|
|
"rest_framework.permissions.IsAuthenticated",
|
|
],
|
|
# Pagination settings
|
|
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
|
|
"PAGE_SIZE": config("API_PAGE_SIZE", default=20, cast=int),
|
|
"MAX_PAGE_SIZE": config("API_MAX_PAGE_SIZE", default=100, cast=int),
|
|
# API versioning via Accept header
|
|
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.AcceptHeaderVersioning",
|
|
"DEFAULT_VERSION": "v1",
|
|
"ALLOWED_VERSIONS": ["v1"],
|
|
# Response rendering
|
|
"DEFAULT_RENDERER_CLASSES": [
|
|
"rest_framework.renderers.JSONRenderer",
|
|
"rest_framework.renderers.BrowsableAPIRenderer",
|
|
],
|
|
# Request parsing
|
|
"DEFAULT_PARSER_CLASSES": [
|
|
"rest_framework.parsers.JSONParser",
|
|
"rest_framework.parsers.FormParser",
|
|
"rest_framework.parsers.MultiPartParser",
|
|
],
|
|
# Custom exception handling
|
|
"EXCEPTION_HANDLER": "apps.core.api.exceptions.custom_exception_handler",
|
|
# Filter backends
|
|
"DEFAULT_FILTER_BACKENDS": [
|
|
"django_filters.rest_framework.DjangoFilterBackend",
|
|
"rest_framework.filters.SearchFilter",
|
|
"rest_framework.filters.OrderingFilter",
|
|
],
|
|
# Rate limiting
|
|
"DEFAULT_THROTTLE_CLASSES": [
|
|
"rest_framework.throttling.AnonRateThrottle",
|
|
"rest_framework.throttling.UserRateThrottle",
|
|
],
|
|
"DEFAULT_THROTTLE_RATES": {
|
|
"anon": f"{config('API_RATE_LIMIT_ANON_PER_MINUTE', default=60, cast=int)}/minute",
|
|
"user": f"{config('API_RATE_LIMIT_USER_PER_HOUR', default=1000, cast=int)}/hour",
|
|
},
|
|
# Test settings
|
|
"TEST_REQUEST_DEFAULT_FORMAT": "json",
|
|
"NON_FIELD_ERRORS_KEY": "non_field_errors",
|
|
# OpenAPI schema
|
|
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
|
}
|
|
|
|
# =============================================================================
|
|
# CORS Settings
|
|
# =============================================================================
|
|
# Cross-Origin Resource Sharing configuration for API access
|
|
|
|
# Allow credentials (cookies, authorization headers)
|
|
CORS_ALLOW_CREDENTIALS = True
|
|
|
|
# Allow all origins (not recommended for production)
|
|
CORS_ALLOW_ALL_ORIGINS = config(
|
|
"CORS_ALLOW_ALL_ORIGINS", default=False, cast=bool
|
|
)
|
|
|
|
# Specific allowed origins (comma-separated)
|
|
CORS_ALLOWED_ORIGINS = config(
|
|
"CORS_ALLOWED_ORIGINS",
|
|
default="",
|
|
cast=lambda v: [s.strip() for s in v.split(",") if s.strip()]
|
|
)
|
|
|
|
# Allowed HTTP headers for CORS requests
|
|
CORS_ALLOW_HEADERS = [
|
|
"accept",
|
|
"accept-encoding",
|
|
"authorization",
|
|
"content-type",
|
|
"dnt",
|
|
"origin",
|
|
"user-agent",
|
|
"x-csrftoken",
|
|
"x-requested-with",
|
|
"x-api-version",
|
|
]
|
|
|
|
# HTTP methods allowed for CORS requests
|
|
CORS_ALLOW_METHODS = [
|
|
"DELETE",
|
|
"GET",
|
|
"OPTIONS",
|
|
"PATCH",
|
|
"POST",
|
|
"PUT",
|
|
]
|
|
|
|
# Headers exposed to browsers (for rate limiting)
|
|
CORS_EXPOSE_HEADERS = [
|
|
"X-RateLimit-Limit",
|
|
"X-RateLimit-Remaining",
|
|
"X-RateLimit-Reset",
|
|
"X-API-Version",
|
|
]
|
|
|
|
# =============================================================================
|
|
# API Rate Limiting
|
|
# =============================================================================
|
|
|
|
API_RATE_LIMIT_PER_MINUTE = config(
|
|
"API_RATE_LIMIT_PER_MINUTE", default=60, cast=int
|
|
)
|
|
API_RATE_LIMIT_PER_HOUR = config(
|
|
"API_RATE_LIMIT_PER_HOUR", default=1000, cast=int
|
|
)
|
|
|
|
# =============================================================================
|
|
# SimpleJWT Settings
|
|
# =============================================================================
|
|
# JWT token configuration for authentication
|
|
|
|
# Import SECRET_KEY for signing tokens
|
|
# This will be set by base.py before this module is imported
|
|
def get_secret_key():
|
|
"""Get SECRET_KEY lazily to avoid circular imports."""
|
|
return config("SECRET_KEY")
|
|
|
|
SIMPLE_JWT = {
|
|
# Token lifetimes
|
|
# Short access tokens (15 min) provide better security
|
|
"ACCESS_TOKEN_LIFETIME": timedelta(
|
|
minutes=config("JWT_ACCESS_TOKEN_LIFETIME_MINUTES", default=15, cast=int)
|
|
),
|
|
"REFRESH_TOKEN_LIFETIME": timedelta(
|
|
days=config("JWT_REFRESH_TOKEN_LIFETIME_DAYS", default=7, cast=int)
|
|
),
|
|
# Token rotation and blacklisting
|
|
# Rotate refresh tokens on each use and blacklist old ones
|
|
"ROTATE_REFRESH_TOKENS": True,
|
|
"BLACKLIST_AFTER_ROTATION": True,
|
|
# Update last login on token refresh
|
|
"UPDATE_LAST_LOGIN": True,
|
|
# Cryptographic settings
|
|
"ALGORITHM": "HS256",
|
|
"SIGNING_KEY": None, # Will use Django's SECRET_KEY
|
|
"VERIFYING_KEY": None,
|
|
# Token validation
|
|
"AUDIENCE": None,
|
|
"ISSUER": config("JWT_ISSUER", default="thrillwiki"),
|
|
"JWK_URL": None,
|
|
"LEEWAY": 0, # No leeway for token expiration
|
|
# Authentication header
|
|
"AUTH_HEADER_TYPES": ("Bearer",),
|
|
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
|
|
# User identification
|
|
"USER_ID_FIELD": "id",
|
|
"USER_ID_CLAIM": "user_id",
|
|
"USER_AUTHENTICATION_RULE": (
|
|
"rest_framework_simplejwt.authentication.default_user_authentication_rule"
|
|
),
|
|
# Token classes
|
|
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
|
|
"TOKEN_TYPE_CLAIM": "token_type",
|
|
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
|
|
# JTI claim for unique token identification (enables revocation)
|
|
"JTI_CLAIM": "jti",
|
|
# Sliding token settings
|
|
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
|
|
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=15),
|
|
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1),
|
|
}
|
|
|
|
# =============================================================================
|
|
# dj-rest-auth Settings
|
|
# =============================================================================
|
|
# REST authentication endpoints configuration
|
|
|
|
# Determine if we're in debug mode for secure cookie setting
|
|
_debug = config("DEBUG", default=True, cast=bool)
|
|
|
|
REST_AUTH = {
|
|
"USE_JWT": True,
|
|
"JWT_AUTH_COOKIE": "thrillwiki-auth",
|
|
"JWT_AUTH_REFRESH_COOKIE": "thrillwiki-refresh",
|
|
# Only send cookies over HTTPS in production
|
|
"JWT_AUTH_SECURE": not _debug,
|
|
# Prevent JavaScript access to cookies
|
|
"JWT_AUTH_HTTPONLY": True,
|
|
# SameSite cookie attribute (Lax is compatible with OAuth flows)
|
|
"JWT_AUTH_SAMESITE": "Lax",
|
|
"JWT_AUTH_RETURN_EXPIRATION": True,
|
|
"JWT_TOKEN_CLAIMS_SERIALIZER": (
|
|
"rest_framework_simplejwt.serializers.TokenObtainPairSerializer"
|
|
),
|
|
}
|
|
|
|
# =============================================================================
|
|
# drf-spectacular Settings (OpenAPI Documentation)
|
|
# =============================================================================
|
|
|
|
SPECTACULAR_SETTINGS = {
|
|
"TITLE": "ThrillWiki API",
|
|
"DESCRIPTION": """Comprehensive theme park and ride information API.
|
|
|
|
## API Conventions
|
|
|
|
### Response Format
|
|
All successful responses include a `success: true` field with data nested under `data`.
|
|
All error responses include an `error` object with `code` and `message` fields.
|
|
|
|
### Pagination
|
|
List endpoints support pagination with `page` and `page_size` parameters.
|
|
Default page size is 20, maximum is 100.
|
|
|
|
### Filtering
|
|
Range filters use `{field}_min` and `{field}_max` naming convention.
|
|
Search uses the `search` parameter.
|
|
Ordering uses the `ordering` parameter (prefix with `-` for descending).
|
|
|
|
### Field Naming
|
|
All field names use snake_case convention (e.g., `image_url`, `created_at`).
|
|
""",
|
|
"VERSION": config("API_VERSION", default="1.0.0"),
|
|
"SERVE_INCLUDE_SCHEMA": False,
|
|
"COMPONENT_SPLIT_REQUEST": True,
|
|
"TAGS": [
|
|
{"name": "Parks", "description": "Theme park operations"},
|
|
{"name": "Rides", "description": "Ride information and management"},
|
|
{"name": "Park Media", "description": "Park photos and media management"},
|
|
{"name": "Ride Media", "description": "Ride photos and media management"},
|
|
{"name": "Authentication", "description": "User authentication and session management"},
|
|
{"name": "Social Authentication", "description": "Social provider login and account linking"},
|
|
{"name": "User Profile", "description": "User profile management"},
|
|
{"name": "User Settings", "description": "User preferences and settings"},
|
|
{"name": "User Notifications", "description": "User notification management"},
|
|
{"name": "User Content", "description": "User-generated content (top lists, reviews)"},
|
|
{"name": "User Management", "description": "Admin user management operations"},
|
|
{"name": "Self-Service Account Management", "description": "User account deletion and management"},
|
|
{"name": "Core", "description": "Core utility endpoints (search, suggestions)"},
|
|
{"name": "Statistics", "description": "Statistical endpoints providing aggregated data and insights"},
|
|
],
|
|
"SCHEMA_PATH_PREFIX": "/api/",
|
|
"DEFAULT_GENERATOR_CLASS": "drf_spectacular.generators.SchemaGenerator",
|
|
"DEFAULT_AUTO_SCHEMA": "drf_spectacular.openapi.AutoSchema",
|
|
"PREPROCESSING_HOOKS": [
|
|
"api.v1.schema.custom_preprocessing_hook",
|
|
],
|
|
"SERVE_PERMISSIONS": ["rest_framework.permissions.AllowAny"],
|
|
"SWAGGER_UI_SETTINGS": {
|
|
"deepLinking": True,
|
|
"persistAuthorization": True,
|
|
"displayOperationId": False,
|
|
"displayRequestDuration": True,
|
|
},
|
|
"REDOC_UI_SETTINGS": {
|
|
"hideDownloadButton": False,
|
|
"hideHostname": False,
|
|
"hideLoading": False,
|
|
"hideSchemaPattern": True,
|
|
"scrollYOffset": 0,
|
|
"theme": {"colors": {"primary": {"main": "#1976d2"}}},
|
|
},
|
|
}
|