"""Django settings for the SAR Link portal API.""" from pathlib import Path import environ BASE_DIR = Path(__file__).resolve().parent.parent env = environ.Env( DJANGO_DEBUG=(bool, True), ALLOWED_HOSTS=(list, ["localhost", "127.0.0.1", "backend"]), CORS_ALLOWED_ORIGINS=(list, ["http://localhost:5173"]), CSRF_TRUSTED_ORIGINS=(list, []), SECURE_SSL_REDIRECT=(bool, False), SECURE_HSTS_SECONDS=(int, 0), SECRET_KEY=(str, "insecure-dev-key-change-me"), POSTGRES_DATABASE=(str, "sarlink"), POSTGRES_USER=(str, "sarlink"), POSTGRES_PASSWORD=(str, "changeme"), POSTGRES_HOST=(str, "database"), POSTGRES_PORT=(int, 5432), SMS_API_URL=(str, "https://smsapi.sarlink.net/api/sms/send"), SMS_API_KEY=(str, ""), OTP_TTL_SECONDS=(int, 300), OTP_MAX_ATTEMPTS=(int, 5), OTP_RESEND_COOLDOWN_SECONDS=(int, 60), REGISTRATION_TICKET_TTL_SECONDS=(int, 1800), FRONTEND_URL=(str, "http://localhost:5173"), ) environ.Env.read_env(BASE_DIR / ".env") SECRET_KEY = env("SECRET_KEY") DEBUG = env("DJANGO_DEBUG") ALLOWED_HOSTS = env("ALLOWED_HOSTS") INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "whitenoise.runserver_nostatic", "django.contrib.staticfiles", # third party "rest_framework", "knox", "django_filters", "corsheaders", "procrastinate.contrib.django", # applications "core", "locations", "users", ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "corsheaders.middleware.CorsMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] ROOT_URLCONF = "apibase.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [BASE_DIR / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ], }, }, ] WSGI_APPLICATION = "apibase.wsgi.application" ASGI_APPLICATION = "apibase.asgi.application" # ----------------------------------------------------------------------------- # Database # ----------------------------------------------------------------------------- DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": env("POSTGRES_DATABASE"), "USER": env("POSTGRES_USER"), "PASSWORD": env("POSTGRES_PASSWORD"), "HOST": env("POSTGRES_HOST"), "PORT": env("POSTGRES_PORT"), } } DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # ----------------------------------------------------------------------------- # Auth # ----------------------------------------------------------------------------- AUTH_USER_MODEL = "users.User" AUTH_PASSWORD_VALIDATORS = [ {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] # Knox: the token itself is only ever returned once, at login. REST_KNOX = { "TOKEN_TTL": None, # tokens live until logout "TOKEN_LIMIT_PER_USER": None, "AUTO_REFRESH": False, } # ----------------------------------------------------------------------------- # DRF # ----------------------------------------------------------------------------- REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["knox.auth.TokenAuthentication"], "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"], "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"], "DEFAULT_PAGINATION_CLASS": "core.pagination.DefaultPagination", "PAGE_SIZE": 25, "DEFAULT_THROTTLE_CLASSES": ["rest_framework.throttling.ScopedRateThrottle"], "DEFAULT_THROTTLE_RATES": { # Unauthenticated auth endpoints, keyed per IP. "auth_start": "30/hour", "auth_login": "20/hour", "otp_request": "10/hour", "register": "10/hour", }, "EXCEPTION_HANDLER": "core.exceptions.exception_handler", } if DEBUG: INSTALLED_APPS.append("drf_spectacular") REST_FRAMEWORK["DEFAULT_SCHEMA_CLASS"] = "drf_spectacular.openapi.AutoSchema" SPECTACULAR_SETTINGS = { "TITLE": "SAR Link Portal API", "VERSION": "2.0.0", "SERVE_INCLUDE_SCHEMA": False, } # ----------------------------------------------------------------------------- # CORS / CSRF - the SPA is served from a different origin in dev # ----------------------------------------------------------------------------- CORS_ALLOWED_ORIGINS = env("CORS_ALLOWED_ORIGINS") CORS_ALLOW_CREDENTIALS = False CSRF_TRUSTED_ORIGINS = env("CSRF_TRUSTED_ORIGINS") # ----------------------------------------------------------------------------- # i18n / tz # ----------------------------------------------------------------------------- LANGUAGE_CODE = "en-us" TIME_ZONE = "Indian/Maldives" USE_I18N = True USE_TZ = True # ----------------------------------------------------------------------------- # Static / media # ----------------------------------------------------------------------------- STATIC_URL = "static/" STATIC_ROOT = BASE_DIR / "staticfiles" MEDIA_URL = "media/" MEDIA_ROOT = BASE_DIR / "media" STORAGES = { "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"}, "staticfiles": { "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage" }, } # ----------------------------------------------------------------------------- # Background tasks (procrastinate) # ----------------------------------------------------------------------------- PROCRASTINATE_APP = "apibase.tasks.app" # ----------------------------------------------------------------------------- # SMS / OTP # ----------------------------------------------------------------------------- SMS_API_URL = env("SMS_API_URL") SMS_API_KEY = env("SMS_API_KEY") OTP_TTL_SECONDS = env("OTP_TTL_SECONDS") OTP_MAX_ATTEMPTS = env("OTP_MAX_ATTEMPTS") OTP_RESEND_COOLDOWN_SECONDS = env("OTP_RESEND_COOLDOWN_SECONDS") # How long a verified number stays redeemable for the registration form. REGISTRATION_TICKET_TTL_SECONDS = env("REGISTRATION_TICKET_TTL_SECONDS") FRONTEND_URL = env("FRONTEND_URL") # ----------------------------------------------------------------------------- # Production hardening # ----------------------------------------------------------------------------- if not DEBUG: SECURE_SSL_REDIRECT = env("SECURE_SSL_REDIRECT") SECURE_HSTS_SECONDS = env("SECURE_HSTS_SECONDS") SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "simple": {"format": "{levelname} {asctime} {name} {message}", "style": "{"}, }, "handlers": { "console": {"class": "logging.StreamHandler", "formatter": "simple"}, }, "root": {"handlers": ["console"], "level": "INFO"}, }