Coverage for config/settings/base.py: 100%
50 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-04 17:24 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-04 17:24 +0000
1"""Base settings shared by every environment (local, production)."""
3import os
4from pathlib import Path
6from dotenv import load_dotenv
8# backend/ (config/settings/base.py -> up 3)
9BASE_DIR = Path(__file__).resolve().parent.parent.parent
11# Load local environment overrides from backend/.env if present (git-ignored).
12# Lets the committed defaults target docker while a dev machine points at local
13# Postgres/Redis without editing tracked files.
14load_dotenv(BASE_DIR / ".env")
16SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "django-insecure-dev-only-change-me")
17DEBUG = False
18ALLOWED_HOSTS = [h for h in os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",") if h]
20# Application definition
21DJANGO_APPS = [
22 "django.contrib.admin",
23 "django.contrib.auth",
24 "django.contrib.contenttypes",
25 "django.contrib.sessions",
26 "django.contrib.messages",
27 "django.contrib.staticfiles",
28]
29THIRD_PARTY_APPS = [
30 "corsheaders",
31]
32# Domain apps live under backend/apps/ and are referenced by dotted path.
33LOCAL_APPS = [
34 "apps.accounts",
35 "apps.core_los",
36 "apps.application_1003",
37 "apps.pricing_ppe",
38 "apps.fees_trid",
39 "apps.underwriting",
40 "apps.workflow",
41 "apps.communications",
42 "apps.client_needs",
43 "apps.integrations",
44 "apps.mismo",
45 "apps.disclosures",
46 "apps.borrower_portal",
47]
48INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
50MIDDLEWARE = [
51 "corsheaders.middleware.CorsMiddleware",
52 "django.middleware.security.SecurityMiddleware",
53 "django.contrib.sessions.middleware.SessionMiddleware",
54 "django.middleware.common.CommonMiddleware",
55 "django.middleware.csrf.CsrfViewMiddleware",
56 "django.contrib.auth.middleware.AuthenticationMiddleware",
57 "django.contrib.messages.middleware.MessageMiddleware",
58 "django.middleware.clickjacking.XFrameOptionsMiddleware",
59]
61ROOT_URLCONF = "config.urls"
62WSGI_APPLICATION = "config.wsgi.application"
63ASGI_APPLICATION = "config.asgi.application"
65TEMPLATES = [
66 {
67 "BACKEND": "django.template.backends.django.DjangoTemplates",
68 "DIRS": [],
69 "APP_DIRS": True,
70 "OPTIONS": {
71 "context_processors": [
72 "django.template.context_processors.request",
73 "django.contrib.auth.context_processors.auth",
74 "django.contrib.messages.context_processors.messages",
75 ],
76 },
77 },
78]
80# --- Custom user model ---------------------------------------------------------
81# MUST be set before the very first migration (design D2). Changing it after
82# migrations exist requires dropping the database or rewriting migrations.
83AUTH_USER_MODEL = "accounts.User"
85# --- Database (AWS RDS PostgreSQL in prod; docker-compose Postgres locally) -----
86DATABASES = {
87 "default": {
88 "ENGINE": "django.db.backends.postgresql",
89 "NAME": os.environ.get("POSTGRES_DB", "lumen_lending"),
90 "USER": os.environ.get("POSTGRES_USER", "lumen"),
91 "PASSWORD": os.environ.get("POSTGRES_PASSWORD", "lumen_secret_password"),
92 "HOST": os.environ.get("POSTGRES_HOST", "localhost"),
93 "PORT": os.environ.get("POSTGRES_PORT", "5432"),
94 "CONN_MAX_AGE": int(os.environ.get("DB_CONN_MAX_AGE", "0")),
95 }
96}
98AUTH_PASSWORD_VALIDATORS = [
99 {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
100 {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
101 {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
102 {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
103]
105LANGUAGE_CODE = "en-us"
106TIME_ZONE = "UTC"
107USE_I18N = True
108USE_TZ = True
110STATIC_URL = "static/"
111STATIC_ROOT = BASE_DIR / "staticfiles"
113DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
115EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
117# --- Auth ----------------------------------------------------------------------
118# Token verifier: "dev" (Bearer "staff:<email>" / "borrower:<email>") or "cognito".
119AUTH_VERIFIER = os.environ.get("AUTH_VERIFIER", "dev")
121# AWS Cognito (used when AUTH_VERIFIER=cognito). The pool is the SHARED Tireless pool; Lumen
122# verifies tokens bearing its OWN app-client audience (own-audience SSO). Issuer + audience are
123# enforced in production (DEBUG=False); the verifier fails closed if they are unset there.
124COGNITO_AWS_REGION = os.environ.get("COGNITO_AWS_REGION", "us-east-1")
125COGNITO_USER_POOL_ID = os.environ.get("COGNITO_USER_POOL_ID", "") # shared pool id
126COGNITO_APP_CLIENT_ID = os.environ.get("COGNITO_APP_CLIENT_ID", "") # Lumen web app client
127COGNITO_ISSUER = os.environ.get("COGNITO_ISSUER", "")
128# M2M (Cognito client-credentials): the resource-server scope required on service tokens.
129COGNITO_M2M_SCOPE = os.environ.get("COGNITO_M2M_SCOPE", "")
131# Lumen-native login (used when AUTH_VERIFIER=local). HS256-signed with SECRET_KEY —
132# the frontend's own login, pending Cognito SSO (add-staff-auth-login design.md).
133AUTH_LOCAL_TOKEN_TTL_HOURS = int(os.environ.get("AUTH_LOCAL_TOKEN_TTL_HOURS", "24"))
135# Borrower portal (Borrower POS) base URL — reset links, invitations and upload links point
136# here (add-borrower-portal-api design.md D10).
137BORROWER_POS_URL = os.environ.get("BORROWER_POS_URL", "http://localhost:3001").rstrip("/")
138# Staff LOS base URL — staff invite and password-reset emails link here (add-sendgrid-email).
139STAFF_LOS_URL = os.environ.get("STAFF_LOS_URL", "http://localhost:3000").rstrip("/")
141# --- Email (Twilio SendGrid; add-sendgrid-email) -----------------------------------
142# With no API key, email is "simulated": recorded as a Communication but not sent (local dev,
143# tests). SENDGRID_FROM_EMAIL must be a verified sender in the SendGrid account.
144SENDGRID_API_KEY = os.environ.get("SENDGRID_API_KEY", "")
145SENDGRID_FROM_EMAIL = os.environ.get("SENDGRID_FROM_EMAIL", "")
146SENDGRID_FROM_NAME = os.environ.get("SENDGRID_FROM_NAME", "Lumen Lending")
147# Signed Event Webhook verification key (SendGrid → Mail Settings → Event Webhook). Unset = the
148# delivery-tracking webhook is disabled.
149SENDGRID_WEBHOOK_PUBLIC_KEY = os.environ.get("SENDGRID_WEBHOOK_PUBLIC_KEY", "")
151# Tireless AI Platform Integration
152TIRELESS_WEB_BASE_URL = os.environ.get("TIRELESS_WEB_BASE_URL", "https://app.tireless-ai.com")
153TIRELESS_API_BASE_URL = os.environ.get("TIRELESS_API_BASE_URL", "https://api.tireless-ai.com")
155# --- Integrations (pluggable adapters; dev stubs locally) ----------------------
156# Platform-default providers (the dev fallback); per-org/per-user creds override via the
157# credential resolver (apps.integrations). See add-integrations-config.
158CREDIT_PROVIDER = os.environ.get("CREDIT_PROVIDER", "dev") # or "meridianlink"
159AUS_ENGINE = os.environ.get("AUS_ENGINE", "dev") # or "agency" (Fannie DU / Freddie LPA)
160# Fernet key for integration secret-at-rest. Empty in dev → an ephemeral per-process key is
161# generated (secrets don't persist across restarts). Production MUST set this (rotate to KMS).
162INTEGRATIONS_FERNET_KEY = os.environ.get("INTEGRATIONS_FERNET_KEY", "")
164# --- Redis / Celery (configured now, exercised in later sprints) ----------------
165REDIS_URL = os.environ.get("REDIS_URL", "redis://localhost:6379/0")
166CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", REDIS_URL)
167CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", REDIS_URL)
169# --- CORS (origins set per environment) ----------------------------------------
170CORS_ALLOWED_ORIGINS: list[str] = []