Add secret management guide, client-side performance monitoring, and search accessibility enhancements

- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
This commit is contained in:
pacnpal
2025-12-23 16:41:42 -05:00
parent ae31e889d7
commit edcd8f2076
155 changed files with 22046 additions and 4645 deletions

View File

@@ -0,0 +1,338 @@
# Environment Variables Reference
Complete reference for all environment variables used in ThrillWiki.
## Quick Start
1. Copy the example file: `cp .env.example .env`
2. Edit `.env` with your values
3. Run validation: `python manage.py validate_settings`
## Required Variables
These must be set in all environments.
| Variable | Type | Description |
|----------|------|-------------|
| `SECRET_KEY` | string | Django secret key (minimum 50 characters) |
| `DATABASE_URL` | url | Database connection URL |
## Production-Required Variables
These variables **must** be explicitly set for production deployments. The application will not function correctly in production without them.
> **Important:** See [`.env.example`](../../.env.example) for example values and [`PRODUCTION_CHECKLIST.md`](../PRODUCTION_CHECKLIST.md) for deployment verification steps.
| Variable | Type | Format/Example | Description |
|----------|------|----------------|-------------|
| `DEBUG` | bool | `False` | **Must be `False` in production.** Enables detailed error pages and debug toolbar when `True`. |
| `DJANGO_SETTINGS_MODULE` | string | `config.django.production` | **Must use `config.django.production` for production.** Controls which settings module Django loads. |
| `ALLOWED_HOSTS` | list | `thrillwiki.com,www.thrillwiki.com` | **Required.** Comma-separated list of valid hostnames. No default in production - requests will fail without this. |
| `CSRF_TRUSTED_ORIGINS` | list | `https://thrillwiki.com,https://www.thrillwiki.com` | **Required.** Must include `https://` prefix. CSRF validation fails without proper configuration. |
| `REDIS_URL` | url | `redis://[:password@]host:6379/0` | **Required for caching and sessions.** Production uses Redis for caching, session storage, and Celery broker. |
### Production Settings Behavior
When `DJANGO_SETTINGS_MODULE=config.django.production`:
- `DEBUG` is always `False`
- `ALLOWED_HOSTS` has **no default** - must be explicitly set
- `CSRF_TRUSTED_ORIGINS` has **no default** - must be explicitly set
- SSL redirect is enforced (`SECURE_SSL_REDIRECT=True`)
- Secure cookies are enforced (`SESSION_COOKIE_SECURE=True`, `CSRF_COOKIE_SECURE=True`)
- HSTS is enabled with 1-year max-age
- Redis cache is required (application will fail if `REDIS_URL` is not set)
### Validation
Run the following to validate your production configuration:
```bash
DJANGO_SETTINGS_MODULE=config.django.production python manage.py check --deploy
```
## Core Django Settings
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `DEBUG` | bool | `True` | Enable debug mode |
| `DJANGO_SETTINGS_MODULE` | string | `config.django.local` | Settings module to use |
| `ALLOWED_HOSTS` | list | `localhost,127.0.0.1` | Comma-separated allowed hosts |
| `CSRF_TRUSTED_ORIGINS` | list | `` | Comma-separated trusted origins |
## Database Configuration
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `DATABASE_URL` | url | (required) | Database connection URL |
| `DATABASE_CONN_MAX_AGE` | int | `600` | Connection pool timeout (seconds) |
| `DATABASE_CONNECT_TIMEOUT` | int | `10` | Connection timeout (seconds) |
| `DATABASE_STATEMENT_TIMEOUT` | int | `30000` | Query timeout (milliseconds) |
| `DATABASE_READ_REPLICA_URL` | url | `` | Optional read replica URL |
### Database URL Formats
```bash
# PostgreSQL with PostGIS
DATABASE_URL=postgis://user:password@host:5432/dbname
# PostgreSQL without PostGIS
DATABASE_URL=postgres://user:password@host:5432/dbname
# SQLite (development only)
DATABASE_URL=sqlite:///path/to/db.sqlite3
```
## GeoDjango Settings
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `GDAL_LIBRARY_PATH` | path | `/opt/homebrew/lib/libgdal.dylib` | Path to GDAL library |
| `GEOS_LIBRARY_PATH` | path | `/opt/homebrew/lib/libgeos_c.dylib` | Path to GEOS library |
### Platform-Specific Paths
```bash
# macOS (Homebrew)
GDAL_LIBRARY_PATH=/opt/homebrew/lib/libgdal.dylib
GEOS_LIBRARY_PATH=/opt/homebrew/lib/libgeos_c.dylib
# Linux (Ubuntu/Debian)
GDAL_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/libgdal.so
GEOS_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/libgeos_c.so
```
## Cache Configuration
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `REDIS_URL` | url | `redis://127.0.0.1:6379/1` | Redis connection URL |
| `REDIS_SESSIONS_URL` | url | (from REDIS_URL) | Redis URL for sessions |
| `REDIS_API_URL` | url | (from REDIS_URL) | Redis URL for API cache |
| `REDIS_MAX_CONNECTIONS` | int | `100` | Maximum Redis connections |
| `REDIS_CONNECTION_TIMEOUT` | int | `20` | Redis connection timeout |
| `REDIS_IGNORE_EXCEPTIONS` | bool | `True` | Graceful degradation |
| `CACHE_MIDDLEWARE_SECONDS` | int | `300` | Cache middleware timeout |
| `CACHE_MIDDLEWARE_KEY_PREFIX` | string | `thrillwiki` | Cache key prefix |
| `CACHE_KEY_PREFIX` | string | `thrillwiki` | Default cache key prefix |
## Email Configuration
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `EMAIL_BACKEND` | string | `django_forwardemail.backends.ForwardEmailBackend` | Email backend class |
| `SERVER_EMAIL` | email | `django_webmaster@thrillwiki.com` | Server email address |
| `DEFAULT_FROM_EMAIL` | string | `ThrillWiki <noreply@thrillwiki.com>` | Default from address |
| `EMAIL_SUBJECT_PREFIX` | string | `[ThrillWiki]` | Subject prefix |
| `EMAIL_TIMEOUT` | int | `30` | Email operation timeout |
### ForwardEmail Settings
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FORWARD_EMAIL_BASE_URL` | url | `https://api.forwardemail.net` | API base URL |
| `FORWARD_EMAIL_API_KEY` | string | `` | API key |
| `FORWARD_EMAIL_DOMAIN` | string | `` | Sending domain |
### SMTP Settings
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `EMAIL_HOST` | string | `localhost` | SMTP server |
| `EMAIL_PORT` | int | `587` | SMTP port |
| `EMAIL_USE_TLS` | bool | `True` | Use TLS |
| `EMAIL_USE_SSL` | bool | `False` | Use SSL |
| `EMAIL_HOST_USER` | string | `` | SMTP username |
| `EMAIL_HOST_PASSWORD` | string | `` | SMTP password |
## Security Settings
### SSL/HTTPS
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SECURE_SSL_REDIRECT` | bool | `False` | Redirect HTTP to HTTPS |
| `SECURE_PROXY_SSL_HEADER` | tuple | `` | Proxy SSL header |
### HSTS (HTTP Strict Transport Security)
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SECURE_HSTS_SECONDS` | int | `31536000` | HSTS max-age |
| `SECURE_HSTS_INCLUDE_SUBDOMAINS` | bool | `True` | Include subdomains |
| `SECURE_HSTS_PRELOAD` | bool | `False` | Allow preload |
| `SECURE_REDIRECT_EXEMPT` | list | `` | URLs exempt from redirect |
### Security Headers
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SECURE_BROWSER_XSS_FILTER` | bool | `True` | XSS filter header |
| `SECURE_CONTENT_TYPE_NOSNIFF` | bool | `True` | Nosniff header |
| `X_FRAME_OPTIONS` | string | `DENY` | Frame options |
| `SECURE_REFERRER_POLICY` | string | `strict-origin-when-cross-origin` | Referrer policy |
| `SECURE_CROSS_ORIGIN_OPENER_POLICY` | string | `same-origin` | COOP header |
### Cookie Security
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SESSION_COOKIE_SECURE` | bool | `False` | HTTPS-only session cookie |
| `SESSION_COOKIE_HTTPONLY` | bool | `True` | No JS access |
| `SESSION_COOKIE_SAMESITE` | string | `Lax` | SameSite attribute |
| `SESSION_COOKIE_AGE` | int | `3600` | Cookie age (seconds) |
| `CSRF_COOKIE_SECURE` | bool | `False` | HTTPS-only CSRF cookie |
| `CSRF_COOKIE_HTTPONLY` | bool | `True` | No JS access |
| `CSRF_COOKIE_SAMESITE` | string | `Lax` | SameSite attribute |
### Cloudflare Turnstile
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `TURNSTILE_SITE_KEY` | string | `` | Turnstile site key |
| `TURNSTILE_SECRET_KEY` | string | `` | Turnstile secret key |
| `TURNSTILE_VERIFY_URL` | url | `https://challenges.cloudflare.com/turnstile/v0/siteverify` | Verification URL |
## API Configuration
### CORS
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `CORS_ALLOWED_ORIGINS` | list | `` | Allowed origins |
| `CORS_ALLOW_ALL_ORIGINS` | bool | `False` | Allow all origins |
### Rate Limiting
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `API_RATE_LIMIT_PER_MINUTE` | int | `60` | Requests per minute |
| `API_RATE_LIMIT_PER_HOUR` | int | `1000` | Requests per hour |
| `API_RATE_LIMIT_ANON_PER_MINUTE` | int | `60` | Anonymous rate limit |
| `API_RATE_LIMIT_USER_PER_HOUR` | int | `1000` | Authenticated rate limit |
### Pagination
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `API_PAGE_SIZE` | int | `20` | Default page size |
| `API_MAX_PAGE_SIZE` | int | `100` | Maximum page size |
| `API_VERSION` | string | `1.0.0` | API version string |
## JWT Configuration
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `JWT_ACCESS_TOKEN_LIFETIME_MINUTES` | int | `15` | Access token lifetime |
| `JWT_REFRESH_TOKEN_LIFETIME_DAYS` | int | `7` | Refresh token lifetime |
| `JWT_ISSUER` | string | `thrillwiki` | Token issuer |
## Cloudflare Images
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `CLOUDFLARE_IMAGES_ACCOUNT_ID` | string | `` | Account ID |
| `CLOUDFLARE_IMAGES_API_TOKEN` | string | `` | API token |
| `CLOUDFLARE_IMAGES_ACCOUNT_HASH` | string | `` | Account hash |
| `CLOUDFLARE_IMAGES_WEBHOOK_SECRET` | string | `` | Webhook secret |
| `CLOUDFLARE_IMAGES_DEFAULT_VARIANT` | string | `public` | Default variant |
| `CLOUDFLARE_IMAGES_UPLOAD_TIMEOUT` | int | `300` | Upload timeout |
| `CLOUDFLARE_IMAGES_CLEANUP_HOURS` | int | `24` | Cleanup interval |
| `CLOUDFLARE_IMAGES_MAX_FILE_SIZE` | int | `10485760` | Max file size (bytes) |
| `CLOUDFLARE_IMAGES_REQUIRE_SIGNED_URLS` | bool | `False` | Require signed URLs |
## Road Trip Service
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `ROADTRIP_USER_AGENT` | string | `ThrillWiki/1.0` | OSM API user agent |
| `ROADTRIP_CACHE_TIMEOUT` | int | `86400` | Geocoding cache timeout |
| `ROADTRIP_ROUTE_CACHE_TIMEOUT` | int | `21600` | Route cache timeout |
| `ROADTRIP_MAX_REQUESTS_PER_SECOND` | int | `1` | Rate limit |
| `ROADTRIP_REQUEST_TIMEOUT` | int | `10` | Request timeout |
| `ROADTRIP_MAX_RETRIES` | int | `3` | Max retries |
| `ROADTRIP_BACKOFF_FACTOR` | int | `2` | Retry backoff |
## Logging Configuration
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `LOG_DIR` | path | `logs` | Log directory |
| `ROOT_LOG_LEVEL` | string | `INFO` | Root log level |
| `DJANGO_LOG_LEVEL` | string | `WARNING` | Django log level |
| `DB_LOG_LEVEL` | string | `WARNING` | Database log level |
| `APP_LOG_LEVEL` | string | `INFO` | Application log level |
| `PERFORMANCE_LOG_LEVEL` | string | `INFO` | Performance log level |
| `QUERY_LOG_LEVEL` | string | `WARNING` | Query log level |
| `NPLUSONE_LOG_LEVEL` | string | `WARNING` | N+1 detection level |
| `REQUEST_LOG_LEVEL` | string | `INFO` | Request log level |
| `CELERY_LOG_LEVEL` | string | `INFO` | Celery log level |
| `CONSOLE_LOG_LEVEL` | string | `INFO` | Console output level |
| `FILE_LOG_LEVEL` | string | `INFO` | File output level |
| `FILE_LOG_FORMATTER` | string | `json` | File log format |
## Monitoring
### Sentry
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SENTRY_DSN` | url | `` | Sentry DSN |
| `SENTRY_ENVIRONMENT` | string | `production` | Sentry environment |
| `SENTRY_TRACES_SAMPLE_RATE` | float | `0.1` | Trace sampling rate |
### Health Checks
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `HEALTH_CHECK_DISK_USAGE_MAX` | int | `90` | Max disk usage % |
| `HEALTH_CHECK_MEMORY_MIN` | int | `100` | Min available memory MB |
## Feature Flags
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `ENABLE_DEBUG_TOOLBAR` | bool | `True` | Enable debug toolbar |
| `ENABLE_SILK_PROFILER` | bool | `False` | Enable Silk profiler |
| `TEMPLATES_ENABLED` | bool | `True` | Enable Django templates |
| `AUTOCOMPLETE_BLOCK_UNAUTHENTICATED` | bool | `False` | Require auth for autocomplete |
## File Upload Settings
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FILE_UPLOAD_MAX_MEMORY_SIZE` | int | `2621440` | Max in-memory upload (bytes) |
| `DATA_UPLOAD_MAX_MEMORY_SIZE` | int | `10485760` | Max request size (bytes) |
| `DATA_UPLOAD_MAX_NUMBER_FIELDS` | int | `1000` | Max form fields |
## WhiteNoise Settings
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `WHITENOISE_COMPRESSION_QUALITY` | int | `90` | Compression quality |
| `WHITENOISE_MAX_AGE` | int | `31536000` | Cache max-age |
| `WHITENOISE_MANIFEST_STRICT` | bool | `False` | Strict manifest mode |
## Secret Management
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `SECRET_ROTATION_ENABLED` | bool | `False` | Enable rotation checks |
| `SECRET_KEY_VERSION` | string | `1` | Current secret version |
| `SECRET_EXPIRY_WARNING_DAYS` | int | `30` | Warning threshold |
| `PASSWORD_MIN_LENGTH` | int | `8` | Minimum password length |
## Celery Configuration
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `CELERY_TASK_ALWAYS_EAGER` | bool | `False` | Run tasks synchronously |
| `CELERY_TASK_EAGER_PROPAGATES` | bool | `False` | Propagate task errors |
## Third-Party Integrations
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `FRONTEND_DOMAIN` | url | `https://thrillwiki.com` | Frontend URL |
| `LOGIN_REDIRECT_URL` | path | `/` | Post-login redirect |
| `ACCOUNT_LOGOUT_REDIRECT_URL` | path | `/` | Post-logout redirect |
| `ACCOUNT_EMAIL_VERIFICATION` | string | `mandatory` | Email verification mode |