intent/config/settings/prod.py
Alec Perkins 99f810d043 Harden auth, invites and deployment after security review
- Signed-out visitors get the same login redirect for missing and
  private projects, so the redirect no longer reveals private ones.
- Production refuses the console email backend; sign-in codes must
  never reach the logs.
- Database cache shared by all workers for allauth rate limits; invite
  sending throttled per sender and per recipient.
- Email invite links are claimed on first use and the token rotated,
  so a token copied from request logs is worthless.
- allauth password URLs removed; adding an email requires a fresh code
  and notifies existing addresses.
- Markdown drops remote images until uploads exist.
- Project and org slugs lowercased; only superusers can grant staff
  status or permissions in the admin.
- Container runs as a non-root user; CI actions pinned to commits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-27 08:42:19 -04:00

37 lines
1.3 KiB
Python

import os
import sentry_sdk
from django.core.exceptions import ImproperlyConfigured
from .base import * # noqa: F403
if not SECRET_KEY: # noqa: F405
raise ImproperlyConfigured("DJANGO_SECRET_KEY must be set")
# The console backend would print sign-in codes and invite links into the logs.
if EMAIL_BACKEND == "django.core.mail.backends.console.EmailBackend": # noqa: F405
raise ImproperlyConfigured("EMAIL_BACKEND must be set to a real email backend in production")
ALLOWED_HOSTS = [h for h in os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",") if h]
CSRF_TRUSTED_ORIGINS = [f"https://{h}" for h in ALLOWED_HOSTS]
# Fly terminates TLS and forwards the original scheme.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Short until the domain is settled; raise to a year after launch.
SECURE_HSTS_SECONDS = 60 * 60 * 24
MIDDLEWARE = [*MIDDLEWARE] # noqa: F405
MIDDLEWARE.insert(
MIDDLEWARE.index("django.middleware.security.SecurityMiddleware") + 1,
"whitenoise.middleware.WhiteNoiseMiddleware",
)
STORAGES = {
**STORAGES, # noqa: F405
"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}
if dsn := os.environ.get("SENTRY_DSN"):
sentry_sdk.init(dsn=dsn, send_default_pii=False, traces_sample_rate=0)