""" Smooth Schedule Settings - Multi-Tenancy & Security Configuration CRITICAL: This file contains essential settings for django-tenants and security middleware """ # ============================================================================= # MULTI-TENANCY CONFIGURATION (django-tenants) # ============================================================================= # Shared apps - Available to all tenants (stored in 'public' schema) SHARED_APPS = [ 'django_tenants', # Must be first 'core', # Core models (Tenant, Domain, PermissionGrant) 'users', # Custom User model - shared across all tenants # Django built-ins 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # Third-party shared apps 'rest_framework', 'rest_framework.authtoken', 'corsheaders', 'django_filters', 'hijack', # Masquerading 'hijack.contrib.admin', # Celery 'django_celery_beat', 'django_celery_results', ] # Tenant-specific apps - Each tenant gets isolated data in their own schema TENANT_APPS = [ # Core tenant functionality 'django.contrib.contenttypes', # Needed for tenant schemas # Your tenant-scoped apps (add your business logic apps here) # Examples: # 'schedule', # Resource scheduling # 'customers', # Customer management # 'payments', # Billing & payments # 'appointments', # Appointment booking # 'analytics', # Tenant-specific analytics ] # Combined installed apps (django-tenants will handle schema routing) INSTALLED_APPS = list(SHARED_APPS) + [ app for app in TENANT_APPS if app not in SHARED_APPS ] # Tenant model configuration TENANT_MODEL = "core.Tenant" TENANT_DOMAIN_MODEL = "core.Domain" # Public schema name (for shared data) PUBLIC_SCHEMA_NAME = 'public' # ============================================================================= # DATABASE CONFIGURATION # ============================================================================= DATABASES = { 'default': { 'ENGINE': 'django_tenants.postgresql_backend', # Multi-schema engine 'NAME': 'smoothschedule_db', 'USER': 'smoothschedule_user', 'PASSWORD': 'CHANGE_ME_IN_PRODUCTION', # Use env vars in production 'HOST': 'localhost', 'PORT': '5432', } } # Database routers for tenant isolation DATABASE_ROUTERS = [ 'django_tenants.routers.TenantSyncRouter', ] # ============================================================================= # MIDDLEWARE CONFIGURATION # ============================================================================= # CRITICAL: Order matters! Read comments carefully. MIDDLEWARE = [ # 1. MUST BE FIRST: Tenant resolution 'django_tenants.middleware.main.TenantMainMiddleware', # 2. Security middleware 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', # Static files # 3. Session & CSRF 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', # 4. Authentication 'django.contrib.auth.middleware.AuthenticationMiddleware', # 5. Hijack (Masquerading) - MUST come before our audit middleware 'hijack.middleware.HijackUserMiddleware', # 6. MASQUERADE AUDIT - MUST come AFTER HijackUserMiddleware # This is our custom middleware that logs masquerading activity 'core.middleware.MasqueradeAuditMiddleware', # 7. Messages & Clickjacking 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] # ============================================================================= # AUTHENTICATION & USER MODEL # ============================================================================= AUTH_USER_MODEL = 'users.User' # Custom user model with roles # Hijack (Masquerading) Configuration HIJACK_AUTHORIZATION_CHECK = 'core.permissions.can_hijack' HIJACK_DISPLAY_ADMIN_BUTTON = True # Show hijack button in admin HIJACK_USE_BOOTSTRAP = True HIJACK_ALLOW_GET_REQUESTS = False # Security: require POST for hijacking # Track when hijack sessions start (for audit duration calculation) HIJACK_INSERT_BEFORE = True # ============================================================================= # REST FRAMEWORK CONFIGURATION # ============================================================================= REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 50, 'DEFAULT_FILTER_BACKENDS': [ 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.SearchFilter', 'rest_framework.filters.OrderingFilter', ], 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', ], # Add BrowsableAPIRenderer only in development # 'rest_framework.renderers.BrowsableAPIRenderer', } # ============================================================================= # LOGGING CONFIGURATION # ============================================================================= LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '[{levelname}] {asctime} {name} {message}', 'style': '{', }, 'json': { '()': 'pythonjsonlogger.jsonlogger.JsonFormatter', 'format': '%(asctime)s %(name)s %(levelname)s %(message)s', }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'verbose', }, 'security_file': { 'class': 'logging.handlers.RotatingFileHandler', 'filename': 'logs/security.log', 'maxBytes': 1024 * 1024 * 10, # 10 MB 'backupCount': 5, 'formatter': 'json', }, 'masquerade_file': { 'class': 'logging.handlers.RotatingFileHandler', 'filename': 'logs/masquerade.log', 'maxBytes': 1024 * 1024 * 10, # 10 MB 'backupCount': 5, 'formatter': 'json', }, }, 'loggers': { # General security logger 'smoothschedule.security': { 'handlers': ['console', 'security_file'], 'level': 'INFO', 'propagate': False, }, # Masquerade-specific logger 'smoothschedule.security.masquerade': { 'handlers': ['console', 'masquerade_file'], 'level': 'INFO', 'propagate': False, }, # Django default 'django': { 'handlers': ['console'], 'level': 'INFO', }, }, } # ============================================================================= # AWS CONFIGURATION (S3, Route53) # ============================================================================= AWS_ACCESS_KEY_ID = 'YOUR_AWS_ACCESS_KEY' # Use env vars in production AWS_SECRET_ACCESS_KEY = 'YOUR_AWS_SECRET_KEY' # Use env vars in production AWS_STORAGE_BUCKET_NAME = 'smoothschedule-media' AWS_S3_REGION_NAME = 'us-east-1' AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com' # Route53 Configuration (for custom domains) AWS_ROUTE53_ENABLED = True AWS_ROUTE53_HOSTED_ZONE_ID = 'YOUR_ZONE_ID' # Main smoothschedule.com zone # Static files (CSS, JavaScript, Images) STATIC_URL = '/static/' STATIC_ROOT = 'staticfiles/' # Media files (User uploads) MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/media/' DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' # ============================================================================= # CELERY CONFIGURATION # ============================================================================= CELERY_BROKER_URL = 'redis://localhost:6379/0' CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' CELERY_ACCEPT_CONTENT = ['json'] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TIMEZONE = 'UTC' # ============================================================================= # CORS CONFIGURATION (for React frontend) # ============================================================================= CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", # React dev server "http://localhost:8000", ] CORS_ALLOW_CREDENTIALS = True # ============================================================================= # SECURITY SETTINGS # ============================================================================= # IMPORTANT: Update these for production SECRET_KEY = 'CHANGE_ME_IN_PRODUCTION' # Use env var DEBUG = True # Set to False in production ALLOWED_HOSTS = ['*'] # Restrict in production # Session security SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SECURE = False # Set True in production (HTTPS) SESSION_COOKIE_SAMESITE = 'Lax' # CSRF security CSRF_COOKIE_HTTPONLY = False # Must be False for DRF CSRF_COOKIE_SECURE = False # Set True in production (HTTPS) CSRF_COOKIE_SAMESITE = 'Lax' # Password validation AUTH_PASSWORD_VALIDATORS = [ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {'min_length': 10}}, {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'}, {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'}, ] # ============================================================================= # CODE REVIEW CHECKLIST VERIFICATION # ============================================================================= """ ✓ 1. Middleware Order: MasqueradeAuditMiddleware comes AFTER HijackUserMiddleware ✓ 2. Tenant Model: Uses django_tenants.models.TenantMixin (see core/models.py) ✓ 3. De-bloat Script: Removes templates/ folder (see setup_project.sh) Additional Security Notes: - DATABASE_ROUTERS configured for schema isolation - HIJACK_AUTHORIZATION_CHECK points to our custom permission matrix - Structured logging configured for audit trail - AWS S3 configured for file storage - CORS configured for React frontend """