Coverage for config/settings/base.py: 100%

50 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-04 14:06 +0000

1"""Base settings shared by every environment (local, production).""" 

2 

3import os 

4from pathlib import Path 

5 

6from dotenv import load_dotenv 

7 

8# backend/ (config/settings/base.py -> up 3) 

9BASE_DIR = Path(__file__).resolve().parent.parent.parent 

10 

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") 

15 

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] 

19 

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 

49 

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] 

60 

61ROOT_URLCONF = "config.urls" 

62WSGI_APPLICATION = "config.wsgi.application" 

63ASGI_APPLICATION = "config.asgi.application" 

64 

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] 

79 

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" 

84 

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} 

97 

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] 

104 

105LANGUAGE_CODE = "en-us" 

106TIME_ZONE = "UTC" 

107USE_I18N = True 

108USE_TZ = True 

109 

110STATIC_URL = "static/" 

111STATIC_ROOT = BASE_DIR / "staticfiles" 

112 

113DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 

114 

115EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" 

116 

117# --- Auth ---------------------------------------------------------------------- 

118# Token verifier: "dev" (Bearer "staff:<email>" / "borrower:<email>") or "cognito". 

119AUTH_VERIFIER = os.environ.get("AUTH_VERIFIER", "dev") 

120 

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", "") 

130 

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")) 

134 

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("/") 

140 

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", "") 

150 

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") 

154 

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", "") 

163 

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) 

168 

169# --- CORS (origins set per environment) ---------------------------------------- 

170CORS_ALLOWED_ORIGINS: list[str] = []