Files
ping-mee-self-service-platform/portal/config/settings.py
T
ebbe.bassandClaude Sonnet 5.5 49c7fd4a0b feat: add password change
Password page with the AD policy listed first, ordered service (throttle limit below the lockout threshold, verification, local hints, directory change, ended sessions, audit and confirmation mail in one transaction), plain-language errors, German text and tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
2026-10-18 10:08:17 +02:00

133 lines
4.3 KiB
Python

"""Django settings. All deployment values come from environment variables (AD-13)."""
import os
import sys
from pathlib import Path
from .env import ConfigError, load_config
from .logging import build_logging
try:
_config = load_config(os.environ)
except ConfigError as exc:
# One clear message, no traceback, no values.
print(str(exc), file=sys.stderr)
sys.exit(1)
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = _config.secret_key
DEBUG = _config.debug
ALLOWED_HOSTS = _config.allowed_hosts
# The client-IP rule (AD-7) trusts X-Forwarded-For only from these peers.
TRUSTED_PROXIES = _config.trusted_proxies
DATA_DIR = _config.data_dir
DISPLAY_TIME_ZONE = _config.default_timezone
DIRECTORY = _config.directory
OIDC = _config.oidc
SMTP = _config.smtp
INSTALLED_APPS = [
"django.contrib.contenttypes",
"django.contrib.auth",
"django.contrib.sessions",
"django.contrib.staticfiles",
"portal.audit",
"portal.accounts",
"portal.shell",
"portal.profile",
"portal.mailer",
"portal.scheduler",
]
MIDDLEWARE = [
"portal.config.middleware.RequestIdMiddleware",
"portal.accounts.middleware.ClientIpMiddleware",
"portal.config.middleware.SecurityHeadersMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"portal.accounts.middleware.AbsoluteSessionLimitMiddleware",
"portal.accounts.middleware.CurrentUserMiddleware",
"portal.shell.middleware.PreferencesMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "portal.config.urls"
WSGI_APPLICATION = "portal.config.wsgi.application"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "portal" / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.template.context_processors.i18n",
"portal.shell.context.shell",
],
},
}
]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": DATA_DIR / "db.sqlite3",
"OPTIONS": {
"transaction_mode": "IMMEDIATE",
"timeout": 20,
"init_command": "PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;",
},
}
}
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# Time and language (AD-15): store UTC, translate every string.
USE_TZ = True
TIME_ZONE = "UTC"
USE_I18N = True
LANGUAGE_CODE = "en"
LANGUAGES = [("en", "English"), ("de", "Deutsch")]
LOCALE_PATHS = [BASE_DIR / "portal" / "locale"]
# E-mails use the portal-wide language and form of address (a per-device choice cannot reach
# server-sent mail). Becomes a runtime setting with the settings registry.
MAIL_LANGUAGE = "de"
# Static files served by whitenoise; assets are built into the image.
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_DIRS = [BASE_DIR / "portal" / "static"]
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}
# Sessions in the database; cookies secure, HttpOnly, SameSite=Lax (AD-6).
SESSION_ENGINE = "portal.accounts.session_store"
SESSION_COOKIE_AGE = 60 * 60 # idle limit: 1 hour, renewed on every request
SESSION_SAVE_EVERY_REQUEST = True
SESSION_ABSOLUTE_SECONDS = 4 * 60 * 60 # absolute limit: 4 hours
# How long roles and account state are trusted before the directory is asked again (AD-5).
ROLE_CACHE_SECONDS = 60
SESSION_COOKIE_SECURE = not DEBUG
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
CSRF_COOKIE_SECURE = not DEBUG
CSRF_TRUSTED_ORIGINS = [OIDC.public_base_url]
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = "Lax"
# TLS ends at the reverse proxy.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_REFERRER_POLICY = "same-origin"
SECURE_HSTS_SECONDS = 0 if DEBUG else 31536000
X_FRAME_OPTIONS = "DENY"
LOGGING = build_logging(_config.log_level)