from datetime import timedelta from pathlib import Path from typing import cast BASE_DIR = Path(__file__).resolve().parent.parent.parent SECRET_KEY = "change-me-in-production" DEBUG = False ALLOWED_HOSTS = [] INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "rest_framework", "django_filters", "corsheaders", "django_celery_results", "django_celery_beat", "rest_framework_simplejwt.token_blacklist", "infrasynth.tenancy", "infrasynth.audit", "infrasynth.security", "infrasynth.files", "infrasynth.notifications", "infrasynth.webhooks", "infrasynth.workflows", "infrasynth.scheduler", "infrasynth.features", "infrasynth.billing", ] MIDDLEWARE = [ "infrasynth.api.middleware.RequestIdMiddleware", "django.middleware.security.SecurityMiddleware", "corsheaders.middleware.CorsMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "infrasynth.security.auth.middleware.JWTAuthenticationMiddleware", "infrasynth.tenancy.middleware.TenantMiddleware", "infrasynth.security.two_factor.middleware.TwoFactorMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "infrasynth.audit.middleware.AuditAPIMiddleware", "infrasynth.api.middleware.RateLimitHeadersMiddleware", ] ROOT_URLCONF = "config.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ], }, }, ] WSGI_APPLICATION = "config.wsgi.application" LANGUAGE_CODE = "es" TIME_ZONE = "America/Bogota" USE_TZ = True STATIC_URL = "static/" MEDIA_URL = "media/" MEDIA_ROOT = BASE_DIR / "media" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" MIGRATION_MODULES = { "tenancy": "infrasynth.tenancy.migrations", "infrasynth_audit": "infrasynth.audit.migrations", "infrasynth_security": "infrasynth.security.migrations", "infrasynth_files": "infrasynth.files.migrations", "infrasynth_notifications": "infrasynth.notifications.migrations", "infrasynth_webhooks": "infrasynth.webhooks.migrations", "infrasynth_workflows": "infrasynth.workflows.migrations", "infrasynth_scheduler": "infrasynth.scheduler.migrations", "infrasynth_features": "infrasynth.features.migrations", "infrasynth_billing": "infrasynth.billing.migrations", } DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": "infrasynth", "USER": "infrasynth", "PASSWORD": "infrasynth", "HOST": "localhost", "PORT": "5432", }, } REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "infrasynth.security.auth.cookies.CookieJWTAuthentication", "infrasynth.security.auth.api_keys.APIKeyAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", "infrasynth.gates.GatePermission", ], "DEFAULT_RENDERER_CLASSES": [ "infrasynth.api.renderers.EnvelopeJSONRenderer", ], "EXCEPTION_HANDLER": "infrasynth.api.exceptions.envelope_exception_handler", "DEFAULT_PAGINATION_CLASS": "infrasynth.api.pagination.CursorPagination", "DEFAULT_THROTTLE_CLASSES": ["infrasynth.api.throttling.TenantRateThrottle"], "PAGE_SIZE": 25, "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"], "DEFAULT_THROTTLE_RATES": {"tenant": "1000/hour"}, "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", } SPECTACULAR_SETTINGS = { "TITLE": "InfraSynth Base API", "VERSION": "1.0.0", "SERVE_INCLUDE_SCHEMA": False, "COMPONENT_SPLIT_REQUEST": True, } CELERY_BROKER_URL = "redis://localhost:6379/0" CELERY_RESULT_BACKEND = "redis://localhost:6379/1" CELERY_RESULT_EXTENDED = True CELERY_TASK_SOFT_TIME_LIMIT = 300 CELERY_TASK_TIME_LIMIT = 600 CELERY_WORKER_PREFETCH_MULTIPLIER = 1 CELERY_ACCEPT_CONTENT = ["json"] CELERY_TASK_SERIALIZER = "json" CELERY_RESULT_SERIALIZER = "json" CACHES = { "default": { "BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": "redis://localhost:6379/1", }, } INFRASYNTH_AUDIT = { "EXCLUDED_MODELS": [ "sessions.Session", "admin.LogEntry", "contenttypes.ContentType", "migrations.Migration", "infrasynth_audit.ModelChangeLog", "infrasynth_audit.APIInteractionLog", "infrasynth_audit.SecurityEvent", "infrasynth_features.FeatureFlag", "infrasynth_features.FeatureFlagOverride", ], "EXCLUDED_FIELDS": ["password", "token", "secret", "credit_card"], "SENSITIVE_KEYS": ["password", "token", "secret", "authorization", "api_key"], "MAX_BODY_SIZE_BYTES": 5000, "STORE_IN_DB": True, "RETENTION_DAYS": 365, "ENABLE_API_LOGGING": True, "ENABLE_MODEL_CHANGE_TRACKING": True, "ENABLE_SECURITY_EVENTS": True, } INFRASYNTH_SECURITY = { "ACCESS_TOKEN_LIFETIME_MINUTES": 30, "REFRESH_TOKEN_LIFETIME_DAYS": 7, "ROTATE_REFRESH_TOKENS": True, "BLACKLIST_AFTER_ROTATION": True, "ACCESS_COOKIE_NAME": "access_token", "REFRESH_COOKIE_NAME": "refresh_token", "COOKIE_SECURE": True, "COOKIE_HTTPONLY": True, "COOKIE_SAMESITE": "Lax", "PRE_AUTH_COOKIE_NAME": "pre_auth_token", "CRYPTO_KEY": None, "AUTH_BACKEND_CLASS": "infrasynth.security.auth.backends.EmailOrUsernameBackend", "LOGIN_RATE_LIMIT": "10/m", "IP_BLACKLIST_THRESHOLD": 100, "IP_BLACKLIST_WINDOW_MINUTES": 15, "TWO_FACTOR_ISSUER_NAME": "InfraSynth", "TWO_FACTOR_RECOVERY_CODES_COUNT": 8, "TWO_FACTOR_TOTP_VALIDITY_WINDOW": 1, "TWO_FACTOR_SERVICE": "infrasynth.security.two_factor.services.TOTPService", "TWO_FACTOR_RECOVERY_SERVICE": "infrasynth.security.two_factor.services.RecoveryCodeService", "PRE_AUTH_TOKEN_LIFETIME_MINUTES": 5, "ALTCHA_DIFFICULTY": 10000, "ALTCHA_CHALLENGE_EXPIRY_SECONDS": 300, "ALTCHA_PROTECT_LOGIN": False, "ALTCHA_HEADER": "X-Altcha", "API_KEY_PREFIX_LENGTH": 8, "API_KEY_HASH_ALGORITHM": "pbkdf2_sha256", "API_KEY_DEFAULT_EXPIRY_DAYS": 365, "PASSWORD_MIN_LENGTH": 8, "PASSWORD_REQUIRE_UPPERCASE": True, "PASSWORD_REQUIRE_DIGIT": True, "PASSWORD_REQUIRE_SPECIAL_CHAR": True, "ENABLE_WORKSPACE_SWITCHING": True, } AUTH_PASSWORD_VALIDATORS = [ {"NAME": "infrasynth.security.password_validation.PasswordPolicyValidator"}, ] AUTHENTICATION_BACKENDS = [ INFRASYNTH_SECURITY["AUTH_BACKEND_CLASS"], ] # JWT lifetimes/rotation are derived from the INFRASYNTH_SECURITY block so there # is a single source of truth. SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(minutes=cast(int, INFRASYNTH_SECURITY["ACCESS_TOKEN_LIFETIME_MINUTES"])), "REFRESH_TOKEN_LIFETIME": timedelta(days=cast(int, INFRASYNTH_SECURITY["REFRESH_TOKEN_LIFETIME_DAYS"])), "ROTATE_REFRESH_TOKENS": cast(bool, INFRASYNTH_SECURITY["ROTATE_REFRESH_TOKENS"]), "BLACKLIST_AFTER_ROTATION": cast(bool, INFRASYNTH_SECURITY["BLACKLIST_AFTER_ROTATION"]), } INFRASYNTH_FILES = { "DEFAULT_STORAGE_BACKEND": "local", "STORAGE_BACKENDS": { "S3": { "ACCESS_KEY": None, "SECRET_KEY": None, "BUCKET_NAME": None, "REGION": "us-east-1", "ENDPOINT_URL": None, }, "cloudinary": { "CLOUD_NAME": None, "API_KEY": None, "API_SECRET": None, }, "gcs": { "PROJECT_ID": None, "BUCKET_NAME": None, "CREDENTIALS_PATH": None, }, "local": {}, }, "SIGNED_URL_EXPIRY_SECONDS": 3600, "MAX_UPLOAD_SIZE_MB": 100, "ENABLE_PROCESSING_PIPELINES": True, "PROCESSING_BACKEND": "celery", "PIPELINE_EXECUTOR": "infrasynth.files.processing.PipelineExecutor", "ENABLE_X_SENDFILE": False, "VIRUS_SCANNER": "noop", "CLAMAV_SOCKET": "/var/run/clamav/clamd.ctl", "REQUIRE_VIRUS_SCAN": False, } INFRASYNTH_NOTIFICATIONS = { "DEFAULT_FROM_EMAIL": "noreply@example.com", "CHANNELS": { "email": { "primary": "infrasynth.notifications.channels.email_smtp.SMTPChannel", "fallback": "infrasynth.notifications.channels.email_sendgrid.SendGridChannel", }, "sms": { "primary": "infrasynth.notifications.channels.sms_twilio.TwilioSMSChannel", }, }, "DISPATCH_BACKEND": "celery", "MAX_RETRIES": 3, "RETRY_DELAY_SECONDS": [60, 300, 900], "RATE_LIMIT_PER_CHANNEL": { "email": "50/m", "sms": "10/m", }, "STORE_DISPATCH_LOGS": True, "DISPATCH_LOG_RETENTION_DAYS": 90, "RETRY_SCAN_BATCH_SIZE": 100, } INFRASYNTH_WEBHOOKS = { "DEFAULT_TIMEOUT_SECONDS": 10, "MAX_RETRIES": 5, "RETRY_BACKOFF": "exponential", "RETRY_INITIAL_DELAY_SECONDS": 60, "SIGNATURE_ALGORITHM": "sha256", "SIGNATURE_HEADER": "X-Webhook-Signature", "DELIVERY_BACKEND": "celery", "INBOUND_PROCESSING_BACKEND": "sync", "INBOUND_SIGNATURE_TOLERANCE_SECONDS": 300, "MAX_PAYLOAD_SIZE_BYTES": 1048576, } INFRASYNTH_WORKFLOWS = { "MAX_INSTANCES_PER_WORKFLOW": 10000, "DEFAULT_APPROVAL_STRATEGY": "ALL", "AUTO_CLONE_ASSIGNEES_ON_REENTRY": True, "ALLOW_SELF_ASSIGNMENT": False, "ROUTE_MAX_DEPTH": 50, } INFRASYNTH_SCHEDULER = { "BACKEND": "celery", "CELERY_BROKER_URL": "redis://localhost:6379/0", "CELERY_RESULT_BACKEND": "redis://localhost:6379/1", "CELERY_TASK_SOFT_TIME_LIMIT": 300, "CELERY_TASK_TIME_LIMIT": 600, "CELERY_WORKER_PREFETCH_MULTIPLIER": 1, "DEFAULT_QUEUE": "default", "MAX_EXECUTION_HISTORY_PER_TASK": 1000, "AUTO_DISCOVER_TASKS": True, } INFRASYNTH_FEATURES = { "CACHE_BACKEND": "default", "CACHE_TTL_SECONDS": 60, "CACHE_KEY_PREFIX": "features", "ROLLOUT_HASH_ALGORITHM": "md5", "AUTO_REGISTER_FROM_SETTINGS": True, "FLAGS": {}, "EXPOSE_PERMISSIONS_IN_ACTIVE_ENDPOINT": True, "EXPOSE_ROLES_IN_ACTIVE_ENDPOINT": True, } # Deployment environment used by feature-flag ``environments`` targeting. ENVIRONMENT = "development" INFRASYNTH_TENANCY = { "ENABLED": True, "TENANT_MODEL": "infrasynth.tenancy.Tenant", "MEMBERSHIP_MODEL": "infrasynth.tenancy.TenantMembership", "TENANT_CLAIM": "tenant", "REQUIRE_TENANT_BY_DEFAULT": True, "TENANT_ALLOWLIST_PATHS": [ "/api/v1/auth/login/", "/api/v1/auth/refresh/", "/api/v1/auth/select-workspace/", "/api/v1/auth/altcha/", "/api/v1/auth/2fa/", "/api/v1/billing/webhook/", "/api/v1/schema/", "/api/v1/tenancy/invitations/accept/", "/healthz", "/readyz", ], "ENABLE_WORKSPACE_SWITCHING": True, "DEFAULT_LOCALE": "es", "DEFAULT_TIMEZONE": "UTC", } INFRASYNTH_BILLING = { "INVOICE_NUMBER_PREFIX": "INV-", "INVOICE_PDF_BUILDER": "infrasynth.billing.invoice_generator._build_invoice_pdf", "GRACE_PERIOD_DAYS": 5, "MAX_RETRY_FAILED_PAYMENTS": 3, "DEFAULT_CURRENCY": "USD", "TAX_PERCENTAGE": 0, "TAX_NAME": "", "INVOICE_GENERATION_DAYS_BEFORE_RENEWAL": 3, "WEBHOOK_TOLERANCE_SECONDS": 300, "SYNC_SUBSCRIPTIONS_EVERY_HOURS": 24, "ENTITLEMENT_CACHE_TTL_SECONDS": 60, } # Periodic work. Every task binds the tenant(s) it touches explicitly. CELERY_BEAT_SCHEDULE = { "notifications-retry-pending": { "task": "infrasynth.notifications.retry_pending_dispatches", "schedule": 60.0, }, "notifications-purge-old": { "task": "infrasynth.notifications.purge_old_dispatches", "schedule": 86400.0, }, "billing-sync-subscriptions": { "task": "infrasynth.billing.sync_subscriptions", "schedule": cast(int, INFRASYNTH_BILLING["SYNC_SUBSCRIPTIONS_EVERY_HOURS"]) * 3600, }, "billing-advance-lifecycle": { "task": "infrasynth.billing.advance_entitlement_lifecycle", "schedule": 3600.0, }, "billing-expire-entitlements": { "task": "infrasynth.billing.expire_entitlements", "schedule": 3600.0, }, "billing-generate-renewal-invoices": { "task": "infrasynth.billing.generate_renewal_invoices", "schedule": 86400.0, }, "audit-purge-expired": { "task": "infrasynth.audit.purge_expired_logs", "schedule": 86400.0, }, }