Initial commit: SmoothSchedule multi-tenant scheduling platform
This commit includes: - Django backend with multi-tenancy (django-tenants) - React + TypeScript frontend with Vite - Platform administration API with role-based access control - Authentication system with token-based auth - Quick login dev tools for testing different user roles - CORS and CSRF configuration for local development - Docker development environment setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
302
config/settings.py
Normal file
302
config/settings.py
Normal file
@@ -0,0 +1,302 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
Reference in New Issue
Block a user