mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 11:31:08 -05:00
Add secret management guide, client-side performance monitoring, and search accessibility enhancements
- 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.
This commit is contained in:
284
backend/config/settings/rest_framework.py
Normal file
284
backend/config/settings/rest_framework.py
Normal file
@@ -0,0 +1,284 @@
|
||||
"""
|
||||
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"}}},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user