From 603e37b588cee3323ebf5162c1f84de63b3bb97a Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Sun, 15 Jun 2025 19:19:03 +0545 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(config):=20configure=20REST?= =?UTF-8?q?=20Framework=20settings=20and=20add=20JWT,=20Swagger,=20and=20R?= =?UTF-8?q?edoc=20views=20to=20urls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/settings/base.py | 30 +++++--- config/settings/rest_framework.py | 120 ++++++++++++++++++++++++++++++ config/urls.py | 41 +++++++++- pyproject.toml | 1 + requirements.txt | 1 + uv.lock | 25 +++++++ 6 files changed, 208 insertions(+), 10 deletions(-) create mode 100644 config/settings/rest_framework.py diff --git a/config/settings/base.py b/config/settings/base.py index af6b9c4..2c490ca 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -2,6 +2,7 @@ import os from decouple import config from pathlib import Path from .jazzmin import JAZZMIN_SETTINGS +from .rest_framework import REST_FRAMEWORK, SIMPLE_JWT, SPECTACULAR_SETTINGS BASE_DIR = Path(__file__).resolve().parent.parent.parent @@ -22,8 +23,10 @@ INSTALLED_APPS = [ "django.contrib.staticfiles", # Third-party "rest_framework", - 'django_filters', - 'corsheaders', + "rest_framework_simplejwt", + "django_filters", + "corsheaders", + "drf_spectacular", # Local "apps.accounts", "apps.events", @@ -40,7 +43,6 @@ INSTALLED_APPS = [ AUTH_USER_MODEL = "accounts.User" - MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", @@ -52,7 +54,6 @@ MIDDLEWARE = [ "django.middleware.clickjacking.XFrameOptionsMiddleware", ] - ROOT_URLCONF = "config.urls" TEMPLATES = [ @@ -97,22 +98,21 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] - - +# Internationalization LANGUAGE_CODE = 'en-us' TIME_ZONE = 'Asia/Kathmandu' USE_I18N = True USE_TZ = True - - +# Static files (CSS, JavaScript, Images) STATIC_URL = "/static/" STATICFILES_DIRS = [BASE_DIR / "static"] MEDIA_URL = "/media/" MEDIA_ROOT = BASE_DIR / "media" -REST_FRAMEWORK = {"DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema"} +# REST Framework configuration is imported from rest_framework.py +# Default primary key field type DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # CORS Configuration @@ -144,3 +144,15 @@ CORS_ALLOW_HEADERS = [ "x-csrftoken", "x-requested-with", ] + +# Security Settings +if not DEBUG: + SECURE_SSL_REDIRECT = True + SESSION_COOKIE_SECURE = True + CSRF_COOKIE_SECURE = True + SECURE_BROWSER_XSS_FILTER = True + SECURE_CONTENT_TYPE_NOSNIFF = True + X_FRAME_OPTIONS = 'DENY' + SECURE_HSTS_SECONDS = 31536000 # 1 year + SECURE_HSTS_INCLUDE_SUBDOMAINS = True + SECURE_HSTS_PRELOAD = True diff --git a/config/settings/rest_framework.py b/config/settings/rest_framework.py new file mode 100644 index 0000000..daa3c09 --- /dev/null +++ b/config/settings/rest_framework.py @@ -0,0 +1,120 @@ +""" +REST Framework configuration settings +""" + +from datetime import timedelta + +REST_FRAMEWORK = { + # Use Django's standard `django.contrib.auth` permissions, + # or allow read-only access for unauthenticated users. + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + ], + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework_simplejwt.authentication.JWTAuthentication', + 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.BasicAuthentication', + ], + # Pagination settings + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 10, + # Filtering + 'DEFAULT_FILTER_BACKENDS': [ + 'django_filters.rest_framework.DjangoFilterBackend', + 'rest_framework.filters.SearchFilter', + 'rest_framework.filters.OrderingFilter', + ], + # Throttling + 'DEFAULT_THROTTLE_CLASSES': [ + 'rest_framework.throttling.AnonRateThrottle', + 'rest_framework.throttling.UserRateThrottle' + ], + 'DEFAULT_THROTTLE_RATES': { + 'anon': '100/day', + 'user': '1000/day' + }, + # Versioning + 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning', + 'DEFAULT_VERSION': 'v1', + 'ALLOWED_VERSIONS': ['v1'], + 'VERSION_PARAM': 'version', + # Schema + 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', + # Renderer/Parser settings + 'DEFAULT_RENDERER_CLASSES': [ + 'rest_framework.renderers.JSONRenderer', + 'rest_framework.renderers.BrowsableAPIRenderer', + ], + 'DEFAULT_PARSER_CLASSES': [ + 'rest_framework.parsers.JSONParser', + 'rest_framework.parsers.FormParser', + 'rest_framework.parsers.MultiPartParser' + ], + # Exception handling + 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', + # Content negotiation + 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation', + # Metadata + 'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata', + # Caching + 'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 15, # 15 minutes + # Test settings + 'TEST_REQUEST_DEFAULT_FORMAT': 'json', + 'TEST_REQUEST_RENDERER_CLASSES': [ + 'rest_framework.renderers.JSONRenderer', + ], +} + +# JWT Settings +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), + 'ROTATE_REFRESH_TOKENS': False, + 'BLACKLIST_AFTER_ROTATION': True, + 'UPDATE_LAST_LOGIN': False, + + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': None, # Will use Django's SECRET_KEY + 'VERIFYING_KEY': None, + 'AUDIENCE': None, + 'ISSUER': None, + + 'AUTH_HEADER_TYPES': ('Bearer',), + 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', + + 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), + 'TOKEN_TYPE_CLAIM': 'token_type', + + 'JTI_CLAIM': 'jti', + + 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', + 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), + 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), +} + +# Spectacular API settings +SPECTACULAR_SETTINGS = { + 'TITLE': 'AlumniVerse API', + 'DESCRIPTION': 'API documentation for AlumniVerse platform', + 'VERSION': '1.0.0', + 'SERVE_INCLUDE_SCHEMA': False, + 'COMPONENT_SPLIT_REQUEST': True, + 'SWAGGER_UI_SETTINGS': { + 'deepLinking': True, + 'persistAuthorization': True, + 'displayOperationId': True, + }, + 'SWAGGER_UI_DIST': 'SIDECAR', + 'SWAGGER_UI_FAVICON_HREF': 'SIDECAR', + 'REDOC_DIST': 'SIDECAR', + 'PREPROCESSING_HOOKS': [], + 'SERVE_PERMISSIONS': ['rest_framework.permissions.IsAuthenticated'], + 'SERVE_AUTHENTICATION': ['rest_framework.authentication.SessionAuthentication'], + 'SWAGGER_UI_SETTINGS': { + 'deepLinking': True, + 'persistAuthorization': True, + 'displayOperationId': True, + }, +} \ No newline at end of file diff --git a/config/urls.py b/config/urls.py index 35a0802..91da3b4 100644 --- a/config/urls.py +++ b/config/urls.py @@ -15,8 +15,47 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static +from rest_framework_simplejwt.views import ( + TokenObtainPairView, + TokenRefreshView, + TokenVerifyView, +) +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularRedocView, + SpectacularSwaggerView, +) urlpatterns = [ path('admin/', admin.site.urls), + + # API Authentication + path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'), + + # API Documentation + path('api/schema/', SpectacularAPIView.as_view(), name='schema'), + path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), + path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), + + # API Apps + path('api/accounts/', include('apps.accounts.urls')), + path('api/events/', include('apps.events.urls')), + path('api/news/', include('apps.news.urls')), + path('api/jobs/', include('apps.jobs.urls')), + path('api/forum/', include('apps.forum.urls')), + path('api/mentorship/', include('apps.mentorship.urls')), + path('api/achievements/', include('apps.achievements.urls')), + path('api/groups/', include('apps.groups.urls')), + path('api/messaging/', include('apps.messaging.urls')), + path('api/notifications/', include('apps.notifications.urls')), + path('api/dashboard/', include('apps.dashboard.urls')), ] + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/pyproject.toml b/pyproject.toml index 874043e..af2880d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ dependencies = [ "django-filter>=25.1", "django-jazzmin>=3.0.1", "djangorestframework>=3.16.0", + "djangorestframework-simplejwt>=5.5.0", "drf-spectacular>=0.28.0", "pillow>=11.2.1", "python-decouple>=3.8", diff --git a/requirements.txt b/requirements.txt index a951573..16dde1d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ django-cors-headers==4.7.0 django-filter==25.1 django-jazzmin==3.0.1 djangorestframework==3.16.0 +djangorestframework-simplejwt==5.3.1 drf-spectacular==0.28.0 inflection==0.5.1 jsonschema==4.24.0 diff --git a/uv.lock b/uv.lock index 6cbb0e1..780fa98 100644 --- a/uv.lock +++ b/uv.lock @@ -12,6 +12,7 @@ dependencies = [ { name = "django-filter" }, { name = "django-jazzmin" }, { name = "djangorestframework" }, + { name = "djangorestframework-simplejwt" }, { name = "drf-spectacular" }, { name = "pillow" }, { name = "python-decouple" }, @@ -24,6 +25,7 @@ requires-dist = [ { name = "django-filter", specifier = ">=25.1" }, { name = "django-jazzmin", specifier = ">=3.0.1" }, { name = "djangorestframework", specifier = ">=3.16.0" }, + { name = "djangorestframework-simplejwt", specifier = ">=5.5.0" }, { name = "drf-spectacular", specifier = ">=0.28.0" }, { name = "pillow", specifier = ">=11.2.1" }, { name = "python-decouple", specifier = ">=3.8" }, @@ -110,6 +112,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/eb/3e/2448e93f4f87fc9a9f35e73e3c05669e0edd0c2526834686e949bb1fd303/djangorestframework-3.16.0-py3-none-any.whl", hash = "sha256:bea7e9f6b96a8584c5224bfb2e4348dfb3f8b5e34edbecb98da258e892089361", size = 1067305, upload-time = "2025-03-28T14:18:39.489Z" }, ] +[[package]] +name = "djangorestframework-simplejwt" +version = "5.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "djangorestframework" }, + { name = "pyjwt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/1e/0d4439d0fa1d93599fbcfc56efdc02cbf012e3a4b4ef90c835e0a51017d4/djangorestframework_simplejwt-5.5.0.tar.gz", hash = "sha256:474a1b737067e6462b3609627a392d13a4da8a08b1f0574104ac6d7b1406f90e", size = 97946, upload-time = "2025-02-26T19:36:01.717Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/b4/d1c1750aa7c8cc07e4974275f96b9b9b3a38e95ff734e14b4e97790c8974/djangorestframework_simplejwt-5.5.0-py3-none-any.whl", hash = "sha256:4ef6b38af20cdde4a4a51d1fd8e063cbbabb7b45f149cc885d38d905c5a62edb", size = 103480, upload-time = "2025-02-26T19:36:29.04Z" }, +] + [[package]] name = "drf-spectacular" version = "0.28.0" @@ -204,6 +220,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" }, ] +[[package]] +name = "pyjwt" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825, upload-time = "2024-08-01T15:01:08.445Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344, upload-time = "2024-08-01T15:01:06.481Z" }, +] + [[package]] name = "python-decouple" version = "3.8"