Add production config: WhiteNoise, CORS, security headers, custom Swagger UI

This commit is contained in:
authentik Default Admin 2026-03-06 23:01:09 +00:00
parent d449b14d6a
commit a61c7f3572
6 changed files with 101 additions and 4 deletions

View file

@ -2,3 +2,4 @@ DEBUG=1
SECRET_KEY=change-me-in-production
DATABASE_URL=postgres://locflow:locflow@db:5432/locflow
ALLOWED_HOSTS=localhost,127.0.0.1
UMAMI_WEBSITE_ID=

52
locflow/settings.py Normal file → Executable file
View file

@ -36,6 +36,7 @@ INSTALLED_APPS = [
'rest_framework',
'rest_framework_simplejwt',
'rest_framework_simplejwt.token_blacklist',
'corsheaders',
'django_filters',
'drf_spectacular',
# Local apps
@ -46,7 +47,9 @@ INSTALLED_APPS = [
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@ -60,7 +63,7 @@ ROOT_URLCONF = 'locflow.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@ -76,8 +79,9 @@ TEMPLATES = [
WSGI_APPLICATION = 'locflow.wsgi.application'
# Database
# Parse DATABASE_URL: postgres://user:pass@host:port/dbname
# Supports DATABASE_URL format or individual POSTGRES_* env vars
DATABASE_URL = os.getenv('DATABASE_URL', '')
POSTGRES_HOST = os.getenv('POSTGRES_HOST', '')
if DATABASE_URL:
_db_url = urlparse(DATABASE_URL)
@ -91,6 +95,17 @@ if DATABASE_URL:
'PORT': str(_db_url.port or 5432),
}
}
elif POSTGRES_HOST:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.getenv('POSTGRES_DB', 'locflow'),
'USER': os.getenv('POSTGRES_USER', 'locflow'),
'PASSWORD': os.getenv('POSTGRES_PASSWORD', ''),
'HOST': POSTGRES_HOST,
'PORT': os.getenv('POSTGRES_PORT', '5432'),
}
}
else:
DATABASES = {
'default': {
@ -162,10 +177,43 @@ SPECTACULAR_SETTINGS = {
},
},
},
'SWAGGER_UI_SETTINGS': {
'deepLinking': True,
'persistAuthorization': True,
},
}
# CORS
CORS_ALLOWED_ORIGINS = os.getenv(
'CORS_ALLOWED_ORIGINS', 'http://localhost:3000'
).split(',')
CORS_ALLOW_CREDENTIALS = True
# Translation Memory
TRANSLATION_MEMORY = {
'MIN_SIMILARITY': 0.7,
'MAX_RESULTS': 10,
}
# WhiteNoise static files compression
STORAGES = {
'staticfiles': {
'BACKEND': 'whitenoise.storage.CompressedManifestStaticFilesStorage',
},
}
# Production security settings (when behind Traefik reverse proxy)
if not DEBUG:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
CSRF_TRUSTED_ORIGINS = [
'https://locflow.richardnixon.dev',
]
# Umami Analytics
UMAMI_WEBSITE_ID = os.getenv('UMAMI_WEBSITE_ID', '')

6
locflow/urls.py Normal file → Executable file
View file

@ -2,7 +2,9 @@
from django.contrib import admin
from django.urls import path, include
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from drf_spectacular.views import SpectacularAPIView
from locflow.views import swagger_ui
urlpatterns = [
path('admin/', admin.site.urls),
@ -11,5 +13,5 @@ urlpatterns = [
path('api/v1/', include('apps.resources.urls')),
path('api/v1/', include('apps.translations.urls')),
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/docs/', swagger_ui, name='swagger-ui'),
]

10
locflow/views.py Executable file
View file

@ -0,0 +1,10 @@
from django.conf import settings
from django.shortcuts import render
from django.urls import reverse
def swagger_ui(request):
return render(request, 'swagger_ui.html', {
'schema_url': reverse('schema'),
'umami_website_id': getattr(settings, 'UMAMI_WEBSITE_ID', ''),
})

View file

@ -2,11 +2,14 @@ django>=5.1,<5.2
djangorestframework>=3.15,<4.0
psycopg2-binary>=2.9
gunicorn>=22.0
whitenoise>=6.5
polib>=1.2
django-cors-headers>=4.3
django-filter>=24.0
python-dotenv>=1.0
drf-spectacular>=0.27
djangorestframework-simplejwt>=5.3,<6.0
requests>=2.31
pytest>=8.0
pytest-django>=4.8
factory-boy>=3.3

33
templates/swagger_ui.html Normal file
View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<title>LocFlow API</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" >
{% if umami_website_id %}
<script defer src="https://analytics.richardnixon.dev/script.js" data-website-id="{{ umami_website_id }}"></script>
{% endif %}
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"> </script>
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
const ui = SwaggerUIBundle({
url: "{{ schema_url }}",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
layout: "StandaloneLayout",
deepLinking: true,
persistAuthorization: true,
})
window.ui = ui
}
</script>
</body>
</html>