"""Configuration for AgentForms."""
import os
import sys
from dotenv import load_dotenv
_env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "data", ".env")
load_dotenv(_env_path)
# ─── Required / optional environment variables ────────────────────────────────
# Required: app cannot start without these.
# Optional: app degrades gracefully when missing.
_REQUIRED_VARS = [
{
"name": "SECRET_KEY",
"aliases": ["AGENTFORMS_SECRET_KEY"],
"description": "Flask session secret key",
"hint": "python -c 'import secrets; print(secrets.token_hex(32))'",
},
{
"name": "ENCRYPTION_KEY",
"description": "AES-256 master encryption key (hex, 64+ chars)",
"hint": "python -c 'import secrets; print(secrets.token_hex(32))'",
},
]
_OPTIONAL_VARS = [
{
"name": "STRIPE_SECRET_KEY",
"description": "Stripe API secret key (required for billing)",
},
{
"name": "STRIPE_PUBLIC_KEY",
"description": "Stripe publishable key (required for client-side Stripe)",
},
{
"name": "STRIPE_WEBHOOK_SECRET",
"description": "Stripe webhook signing secret (required for webhook verification)",
},
{
"name": "REDIS_URL",
"description": "Redis connection URL (required for rate limiting & background jobs)",
},
]
def validate() -> None:
"""Validate required environment variables at startup.
Fails fast with a clear error message if any required env var is missing.
Logs warnings for missing optional vars.
Raises:
SystemExit: If a required env var is missing (with clear guidance).
"""
errors = []
warnings = []
for var in _REQUIRED_VARS:
value = os.environ.get(var["name"])
# Check aliases
if not value:
for alias in var.get("aliases", []):
value = os.environ.get(alias)
if value:
# Found via alias — set canonical name so downstream code works
os.environ.setdefault(var["name"], value)
break
if not value:
hint = var.get("hint", "")
hint_str = f" Generate with: {hint}" if hint else ""
errors.append(
f" ✗ {var['name']} — {var['description']}.{hint_str}"
)
for var in _OPTIONAL_VARS:
value = os.environ.get(var["name"])
if not value:
warnings.append(f" ⚠ {var['name']} — {var['description']}")
if errors:
print("=" * 65, file=sys.stderr)
print(" ❌ AgentForms startup failed — missing required config", file=sys.stderr)
print("=" * 65, file=sys.stderr)
print(file=sys.stderr)
print(" Missing required environment variables:", file=sys.stderr)
print(file=sys.stderr)
for err in errors:
print(err, file=sys.stderr)
print(file=sys.stderr)
print(" Fix this by setting the variables in your .env file", file=sys.stderr)
print(" or in your deployment environment.", file=sys.stderr)
print(file=sys.stderr)
print("=" * 65, file=sys.stderr)
sys.exit(1)
if warnings:
print("=" * 65)
print(" ⚠ AgentForms config warnings — some features will be limited")
print("=" * 65)
print()
print(" Missing optional environment variables:")
print()
for w in warnings:
print(w)
print()
print(" These are not required for startup, but features that")
print(" depend on them will be unavailable or degraded.")
print("=" * 65)
class Config:
"""Application configuration."""
# ─── Rate limiting ───────────────────────────────────────────────────────
AUTH_RATE_LIMIT = 10
API_RATE_LIMIT = 60
USER_API_RATE_LIMIT = 120
RATE_LIMIT_WINDOW = 60
# ─── Payload size limit (Phase 2) ────────────────────────────────────────
MAX_CONTENT_LENGTH = 1 * 1024 * 1024 # 1MB
# ─── Session ─────────────────────────────────────────────────────────────
PERMANENT_SESSION_LIFETIME = 14 * 24 * 3600 # 2 weeks for "remember me"
@staticmethod
def apply(app):
"""Apply configuration to a Flask app instance."""
secret_key = os.environ.get("SECRET_KEY")
if not secret_key:
raise RuntimeError(
"SECRET_KEY environment variable is required. "
"Generate one with: python -c 'import secrets; print(secrets.token_hex(32))'"
)
app.secret_key = secret_key
app.permanent_session_lifetime = Config.PERMANENT_SESSION_LIFETIME
# Global payload size limit (Phase 2)
app.config["MAX_CONTENT_LENGTH"] = Config.MAX_CONTENT_LENGTH
# Session cookie hardening
app.config["SESSION_COOKIE_SECURE"] = not app.config.get("TESTING", False)
app.config["SESSION_COOKIE_HTTPONLY"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
# Custom session cookie name (not the default "session")
app.config["SESSION_COOKIE_NAME"] = "__Host-_af_session"