From a469b44a54e4eea5a1c3debd0fe457d7b02a9a5f Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sat, 17 Jan 2026 18:47:08 +0000 Subject: [PATCH] Generated by Spark: Should handle caprover / cloudflare cors - Check frontend and backend config. example setup: https://frontend.example.com https://backend.example.com --- .env.example | 18 +- BACKEND-CONFIG.md | 10 +- CORS-CONFIG-SUMMARY.md | 383 +++++++++++++++++++++++++++++++ CORS-GUIDE.md | 409 ++++++++++++++++++++++++++++++++++ DEPLOYMENT-CHECKLIST.md | 190 ++++++++++++++++ DEPLOYMENT.md | 380 +++++++++++++++++++++++++++++++ ENV-CONFIG.md | 329 +++++++++++++++++++++++++++ README.md | 10 +- backend/Dockerfile | 8 +- backend/README.md | 58 ++++- backend/app.py | 372 ++++++++++++++++--------------- backend/captain-definition | 4 + captain-definition | 4 + docker-compose.production.yml | 35 +++ docker-compose.yml | 7 +- nginx.conf | 10 + test-cors.sh | 138 ++++++++++++ 17 files changed, 2165 insertions(+), 200 deletions(-) create mode 100644 CORS-CONFIG-SUMMARY.md create mode 100644 CORS-GUIDE.md create mode 100644 DEPLOYMENT-CHECKLIST.md create mode 100644 DEPLOYMENT.md create mode 100644 ENV-CONFIG.md create mode 100644 backend/captain-definition create mode 100644 captain-definition create mode 100644 docker-compose.production.yml create mode 100644 test-cors.sh diff --git a/.env.example b/.env.example index aaa0e48..63f1ec5 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,16 @@ -# Flask Backend Configuration (Optional) -# If set, the app will automatically use Flask backend instead of IndexedDB -# Example: VITE_FLASK_BACKEND_URL=http://localhost:5000 +# Frontend Configuration +# Flask Backend URL - If set, the app will automatically use Flask backend instead of IndexedDB +# Development: VITE_FLASK_BACKEND_URL=http://localhost:5000 +# Production: VITE_FLASK_BACKEND_URL=https://backend.example.com VITE_FLASK_BACKEND_URL= + +# Backend Configuration (for backend/app.py) +# CORS Allowed Origins - Comma-separated list of allowed frontend URLs +# Development: CORS_ALLOWED_ORIGINS=* +# Production: CORS_ALLOWED_ORIGINS=https://frontend.example.com +# Multiple: CORS_ALLOWED_ORIGINS=https://frontend.example.com,https://app.example.com +CORS_ALLOWED_ORIGINS=* + +# Database Path - Location of SQLite database file in backend +# Default: DATABASE_PATH=/app/data/snippets.db +DATABASE_PATH=/app/data/snippets.db diff --git a/BACKEND-CONFIG.md b/BACKEND-CONFIG.md index 63d8edf..a344c72 100644 --- a/BACKEND-CONFIG.md +++ b/BACKEND-CONFIG.md @@ -148,7 +148,9 @@ Or configure manually in Settings with the remote URL. | Variable | Description | Default | Example | |----------|-------------|---------|---------| -| `VITE_FLASK_BACKEND_URL` | Flask backend URL. When set, forces Flask backend usage. | (none) | `http://localhost:5000` | +| `VITE_FLASK_BACKEND_URL` | Flask backend URL. When set, forces Flask backend usage. | (none) | `http://localhost:5000` or `https://backend.example.com` | +| `CORS_ALLOWED_ORIGINS` | Comma-separated list of allowed frontend origins for CORS. | `*` | `https://frontend.example.com` | +| `DATABASE_PATH` | Path to SQLite database file in backend. | `/app/data/snippets.db` | `/app/data/snippets.db` | ## Troubleshooting @@ -164,7 +166,8 @@ Or configure manually in Settings with the remote URL. 1. Verify backend is running: `curl http://localhost:5000/health` 2. Check URL spelling and port number 3. Review backend logs for errors -4. Ensure CORS is enabled in Flask app +4. Ensure CORS is enabled in Flask app (see CORS-GUIDE.md) +5. For production deployments, see DEPLOYMENT.md ### Environment variable not working @@ -258,3 +261,6 @@ VITE_FLASK_BACKEND_URL=https://api.your-domain.com - [Backend API Documentation](./backend/README.md#api-endpoints) - [Docker Compose Configuration](./docker-compose.yml) - [Example .env file](./.env.example) +- [CORS Configuration Guide](./CORS-GUIDE.md) +- [Production Deployment Guide](./DEPLOYMENT.md) +- [Deployment Checklist](./DEPLOYMENT-CHECKLIST.md) diff --git a/CORS-CONFIG-SUMMARY.md b/CORS-CONFIG-SUMMARY.md new file mode 100644 index 0000000..dfb08b8 --- /dev/null +++ b/CORS-CONFIG-SUMMARY.md @@ -0,0 +1,383 @@ +# CapRover/Cloudflare CORS Configuration Summary + +## ✅ What's Been Configured + +### Backend CORS Implementation + +The Flask backend (`backend/app.py`) now includes comprehensive CORS support: + +1. **Environment-based CORS configuration:** + - `CORS_ALLOWED_ORIGINS` environment variable + - Supports wildcard (`*`) for development + - Supports comma-separated list for multiple origins in production + +2. **Proper CORS headers:** + - `Access-Control-Allow-Origin` + - `Access-Control-Allow-Methods` + - `Access-Control-Allow-Headers` + - `Access-Control-Allow-Credentials` + +3. **Security features:** + - Specific origins in production + - Wildcard only in development + - Credentials support for specific origins + +### Frontend Configuration + +The frontend automatically detects and uses the backend via: + +1. **Environment variable:** `VITE_FLASK_BACKEND_URL` +2. **Automatic configuration:** When set, forces Flask backend usage +3. **Manual configuration:** Settings page (if env var not set) + +### Docker Configuration + +1. **Backend Dockerfile:** + - Environment variables support + - Persistent volume at `/app/data` + - Health check endpoint + +2. **Frontend Dockerfile:** + - Build-time argument for backend URL + - Nginx with proxy support + - Static file serving + +3. **Nginx Configuration:** + - Proper proxy headers + - Cache control for SPA + - API proxying + +### CapRover Support + +1. **captain-definition files:** + - Frontend: Root directory + - Backend: Backend directory + +2. **Deployment ready:** + - Separate app deployments + - Environment variable configuration + - Persistent storage support + +## 📚 Documentation Created + +### Primary Guides + +1. **[DEPLOYMENT.md](./DEPLOYMENT.md)** + - Complete CapRover/Cloudflare deployment walkthrough + - Step-by-step instructions + - DNS configuration + - SSL setup + - Testing procedures + - Troubleshooting + +2. **[CORS-GUIDE.md](./CORS-GUIDE.md)** + - CORS concepts and configuration + - Testing procedures + - Common errors and solutions + - Automated testing script + - Security best practices + - Debugging tips + +3. **[DEPLOYMENT-CHECKLIST.md](./DEPLOYMENT-CHECKLIST.md)** + - Quick reference checklist + - All deployment steps + - Testing verification + - Security checks + - Quick commands + +4. **[ENV-CONFIG.md](./ENV-CONFIG.md)** + - Environment variable examples + - Different deployment scenarios + - Common mistakes + - Troubleshooting + +### Updated Documentation + +1. **[BACKEND-CONFIG.md](./BACKEND-CONFIG.md)** + - Added CORS environment variable + - Updated with deployment links + +2. **[backend/README.md](./backend/README.md)** + - Added CORS configuration + - Production deployment section + - Environment variables table + +3. **[README.md](./README.md)** + - Added deployment documentation links + - Organized documentation section + +### Configuration Files + +1. **[.env.example](./.env.example)** + - Frontend variables + - Backend variables + - Comments and examples + +2. **[docker-compose.yml](./docker-compose.yml)** + - Updated with new environment variables + - Proper volume paths + +3. **[docker-compose.production.yml](./docker-compose.production.yml)** + - Production configuration example + - Network configuration + +4. **[nginx.conf](./nginx.conf)** + - Enhanced proxy configuration + - Security headers + - Cache control + +### Testing Tools + +1. **[test-cors.sh](./test-cors.sh)** + - Automated CORS testing script + - 5 comprehensive tests + - Clear pass/fail indicators + - Usage instructions + +## 🚀 Quick Start Guide + +### Local Development + +```bash +# Backend +cd backend +pip install -r requirements.txt +CORS_ALLOWED_ORIGINS=http://localhost:3000 python app.py + +# Frontend +echo "VITE_FLASK_BACKEND_URL=http://localhost:5000" > .env +npm install +npm run dev +``` + +### Docker Compose + +```bash +# Starts both frontend and backend +docker-compose up -d + +# Access at http://localhost:3000 +``` + +### CapRover Deployment + +```bash +# Deploy backend +cd backend +caprover deploy -a codesnippet-backend + +# Configure in CapRover dashboard: +# - CORS_ALLOWED_ORIGINS=https://frontend.example.com +# - DATABASE_PATH=/app/data/snippets.db + +# Deploy frontend +cd .. +caprover deploy -a codesnippet-frontend + +# Configure in CapRover dashboard: +# - VITE_FLASK_BACKEND_URL=https://backend.example.com +``` + +## 🔧 Configuration Examples + +### Separate Domains (Recommended) + +``` +Frontend: https://frontend.example.com +Backend: https://backend.example.com +``` + +**Frontend:** +```bash +VITE_FLASK_BACKEND_URL=https://backend.example.com +``` + +**Backend:** +```bash +CORS_ALLOWED_ORIGINS=https://frontend.example.com +DATABASE_PATH=/app/data/snippets.db +``` + +### Single Domain (Proxied) + +``` +Frontend: https://app.example.com +Backend: https://app.example.com/api (proxied) +``` + +**Frontend:** +```bash +VITE_FLASK_BACKEND_URL=/api +``` + +**Backend:** +```bash +CORS_ALLOWED_ORIGINS=* +DATABASE_PATH=/app/data/snippets.db +``` + +### Multiple Frontends + +``` +Frontend 1: https://app.example.com +Frontend 2: https://staging.example.com +Backend: https://api.example.com +``` + +**Backend:** +```bash +CORS_ALLOWED_ORIGINS=https://app.example.com,https://staging.example.com +DATABASE_PATH=/app/data/snippets.db +``` + +## ✅ Testing CORS + +### Quick Test + +```bash +curl -H "Origin: https://frontend.example.com" \ + -H "Access-Control-Request-Method: GET" \ + -X OPTIONS \ + https://backend.example.com/api/snippets +``` + +Should return: +``` +Access-Control-Allow-Origin: https://frontend.example.com +Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS +``` + +### Automated Test + +```bash +chmod +x test-cors.sh +./test-cors.sh https://backend.example.com https://frontend.example.com +``` + +## 🔒 Security Checklist + +- [ ] HTTPS enabled for both frontend and backend +- [ ] `CORS_ALLOWED_ORIGINS` set to specific domains (not `*`) +- [ ] Cloudflare proxy enabled (orange cloud) +- [ ] SSL/TLS mode set to "Full (strict)" +- [ ] Flask debug mode disabled +- [ ] Database backed up regularly +- [ ] Rate limiting configured +- [ ] Monitoring enabled + +## 📋 Environment Variables + +### Frontend + +| Variable | Required | Example | +|----------|----------|---------| +| `VITE_FLASK_BACKEND_URL` | Yes (for Flask) | `https://backend.example.com` | + +### Backend + +| Variable | Required | Example | +|----------|----------|---------| +| `CORS_ALLOWED_ORIGINS` | Yes | `https://frontend.example.com` | +| `DATABASE_PATH` | No | `/app/data/snippets.db` (default) | + +## 🐛 Troubleshooting + +### CORS Errors + +**Problem:** Browser shows CORS policy error + +**Solution:** +1. Check `CORS_ALLOWED_ORIGINS` includes frontend URL +2. Verify both use HTTPS (not mixed) +3. Restart backend after env changes +4. See [CORS-GUIDE.md](./CORS-GUIDE.md) + +### Connection Failed + +**Problem:** Frontend can't connect to backend + +**Solution:** +1. Test backend: `curl https://backend.example.com/health` +2. Check `VITE_FLASK_BACKEND_URL` is correct +3. Verify backend is running +4. Check firewall/network settings + +### SSL Issues + +**Problem:** SSL certificate not valid + +**Solution:** +1. Wait 5-10 minutes for Let's Encrypt +2. Verify DNS records are correct +3. Check Cloudflare SSL mode: "Full (strict)" +4. Disable and re-enable HTTPS in CapRover + +## 📖 Documentation Index + +### For Developers +- [Backend Configuration](./BACKEND-CONFIG.md) - Configure storage backends +- [Environment Configuration](./ENV-CONFIG.md) - Environment variable examples +- [Backend API](./backend/README.md) - API documentation + +### For DevOps +- [Deployment Guide](./DEPLOYMENT.md) - Complete deployment walkthrough +- [CORS Guide](./CORS-GUIDE.md) - CORS configuration and testing +- [Deployment Checklist](./DEPLOYMENT-CHECKLIST.md) - Quick reference + +### For Everyone +- [Quick Start](./QUICKSTART.md) - Get started quickly +- [Application Guide](./README-APP.md) - Using the application +- [Main README](./README.md) - Overview and links + +## 🎯 Key Benefits + +1. **Flexible Deployment:** + - Single domain or separate domains + - CapRover, Docker, or standalone + - Local development or cloud production + +2. **Secure by Default:** + - CORS properly configured + - HTTPS enforced + - Specific origins in production + +3. **Easy to Configure:** + - Environment variables + - Clear documentation + - Testing tools included + +4. **Production Ready:** + - Cloudflare CDN support + - CapRover deployment ready + - Monitoring and logging + +5. **Well Documented:** + - Step-by-step guides + - Configuration examples + - Troubleshooting help + +## 🤝 Support + +For issues or questions: + +1. Check the relevant guide in [Documentation Index](#documentation-index) +2. Review [Troubleshooting](#troubleshooting) section +3. Run the test script: `./test-cors.sh` +4. Check application logs + +## 📝 Next Steps + +After deployment: + +1. ✅ Test all functionality +2. ✅ Configure backups +3. ✅ Set up monitoring +4. ✅ Review security settings +5. ✅ Configure rate limiting +6. ✅ Test disaster recovery +7. ✅ Document your specific configuration +8. ✅ Share with your team + +--- + +**Ready to deploy?** Start with the [Deployment Checklist](./DEPLOYMENT-CHECKLIST.md)! diff --git a/CORS-GUIDE.md b/CORS-GUIDE.md new file mode 100644 index 0000000..f6b800f --- /dev/null +++ b/CORS-GUIDE.md @@ -0,0 +1,409 @@ +# CORS Configuration & Testing Guide + +This guide covers Cross-Origin Resource Sharing (CORS) configuration for CodeSnippet when deployed with separate frontend and backend domains. + +## Understanding CORS + +CORS is a security feature that controls which domains can make requests to your backend API. When your frontend (`https://frontend.example.com`) makes requests to your backend (`https://backend.example.com`), the browser enforces CORS policies. + +## Backend CORS Configuration + +### Environment Variable: `CORS_ALLOWED_ORIGINS` + +The backend Flask application uses the `CORS_ALLOWED_ORIGINS` environment variable to control which origins can access the API. + +#### Development (Allow All Origins) + +```bash +CORS_ALLOWED_ORIGINS=* +``` + +**Warning:** Only use `*` in development. This allows ANY website to access your backend API. + +#### Production (Specific Origins) + +```bash +# Single origin +CORS_ALLOWED_ORIGINS=https://frontend.example.com + +# Multiple origins (comma-separated) +CORS_ALLOWED_ORIGINS=https://frontend.example.com,https://app.example.com,https://staging.example.com +``` + +**Important:** Do NOT include trailing slashes in URLs. + +### How It Works + +The Flask backend (`backend/app.py`) reads this environment variable and configures CORS accordingly: + +```python +ALLOWED_ORIGINS = os.environ.get('CORS_ALLOWED_ORIGINS', '*') +if ALLOWED_ORIGINS == '*': + CORS(app, origins='*', ...) # Development mode +else: + origins_list = [origin.strip() for origin in ALLOWED_ORIGINS.split(',')] + CORS(app, origins=origins_list, ...) # Production mode +``` + +### CORS Headers Returned + +When properly configured, the backend returns these headers: + +``` +Access-Control-Allow-Origin: https://frontend.example.com +Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS +Access-Control-Allow-Headers: Content-Type, Authorization +Access-Control-Allow-Credentials: true +``` + +## Testing CORS Configuration + +### Test 1: Health Check (No CORS) + +Simple GET request to verify backend is running: + +```bash +curl https://backend.example.com/health +``` + +Expected response: +```json +{ + "status": "healthy", + "timestamp": "2024-01-01T12:00:00" +} +``` + +### Test 2: Preflight Request (OPTIONS) + +Browsers send an OPTIONS request before the actual request to check CORS permissions: + +```bash +curl -X OPTIONS https://backend.example.com/api/snippets \ + -H "Origin: https://frontend.example.com" \ + -H "Access-Control-Request-Method: GET" \ + -H "Access-Control-Request-Headers: Content-Type" \ + -v +``` + +Expected headers in response: +``` +< HTTP/1.1 200 OK +< Access-Control-Allow-Origin: https://frontend.example.com +< Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS +< Access-Control-Allow-Headers: Content-Type, Authorization +``` + +### Test 3: Actual API Request with Origin + +Test a real API request with Origin header: + +```bash +curl https://backend.example.com/api/snippets \ + -H "Origin: https://frontend.example.com" \ + -v +``` + +Should return `Access-Control-Allow-Origin` header and snippet data. + +### Test 4: Wrong Origin (Should Fail) + +Test that unauthorized origins are blocked: + +```bash +curl https://backend.example.com/api/snippets \ + -H "Origin: https://malicious-site.com" \ + -v +``` + +Expected: No `Access-Control-Allow-Origin` header (or browser would block). + +### Test 5: Browser DevTools Test + +1. Open frontend in browser: `https://frontend.example.com` +2. Open DevTools (F12) → Network tab +3. Create a new snippet or load snippets +4. Check the API request: + - Should show status 200 OK + - Response headers should include `Access-Control-Allow-Origin` + - No CORS errors in Console + +## Common CORS Errors & Solutions + +### Error: "CORS policy: No 'Access-Control-Allow-Origin' header" + +**Cause:** Backend is not returning CORS headers + +**Solutions:** +1. Verify `CORS_ALLOWED_ORIGINS` is set correctly in backend +2. Check backend logs for errors +3. Ensure Flask-CORS is installed: `pip install flask-cors` +4. Restart backend after environment variable changes + +### Error: "CORS policy: Origin 'https://frontend.example.com' not allowed" + +**Cause:** Frontend origin not in allowed list + +**Solutions:** +1. Check `CORS_ALLOWED_ORIGINS` includes exact frontend URL +2. Ensure no trailing slash: `https://frontend.example.com` not `https://frontend.example.com/` +3. Verify HTTPS vs HTTP matches exactly +4. Check for typos in domain name + +### Error: "CORS policy: Request header 'content-type' not allowed" + +**Cause:** Backend not allowing required headers + +**Solutions:** +1. Verify backend allows `Content-Type` header +2. Check Flask-CORS configuration in `app.py` +3. Ensure `allow_headers` includes `Content-Type` + +### Error: Mixed Content (HTTP/HTTPS) + +**Cause:** Frontend uses HTTPS but backend uses HTTP (or vice versa) + +**Solutions:** +1. Ensure both frontend and backend use HTTPS in production +2. Update `VITE_FLASK_BACKEND_URL` to use `https://` +3. Enable HTTPS in CapRover for both apps +4. Verify Cloudflare SSL/TLS mode is "Full (strict)" + +### Error: "CORS policy: Credential is not supported if origin is '*'" + +**Cause:** Using `CORS_ALLOWED_ORIGINS=*` with `supports_credentials=True` + +**Solutions:** +1. Set specific origins instead of `*` +2. Or disable credentials if not needed + +## Deployment Scenarios + +### Scenario 1: Separate Domains (Recommended for Production) + +``` +Frontend: https://frontend.example.com +Backend: https://backend.example.com +``` + +**Frontend Config:** +```bash +VITE_FLASK_BACKEND_URL=https://backend.example.com +``` + +**Backend Config:** +```bash +CORS_ALLOWED_ORIGINS=https://frontend.example.com +``` + +**Pros:** Clean separation, independent scaling +**Cons:** Requires CORS configuration + +### Scenario 2: Single Domain with Proxy + +``` +Frontend: https://app.example.com +Backend: https://app.example.com/api (proxied) +``` + +**Frontend Config:** +```bash +VITE_FLASK_BACKEND_URL=/api +``` + +**Backend Config:** +```bash +CORS_ALLOWED_ORIGINS=* # Not needed if proxied through nginx +``` + +**Nginx Config:** (already configured in `nginx.conf`) +```nginx +location /api { + proxy_pass http://backend:5000; +} +``` + +**Pros:** No CORS issues (same-origin), simpler configuration +**Cons:** Tight coupling, single domain + +### Scenario 3: Multiple Frontends + +``` +Frontend 1: https://app.example.com +Frontend 2: https://staging.example.com +Backend: https://api.example.com +``` + +**Frontend Config (both):** +```bash +VITE_FLASK_BACKEND_URL=https://api.example.com +``` + +**Backend Config:** +```bash +CORS_ALLOWED_ORIGINS=https://app.example.com,https://staging.example.com +``` + +## Cloudflare-Specific Configuration + +### Cloudflare SSL/TLS Mode + +Set to **"Full (strict)"** to ensure end-to-end encryption: + +1. Cloudflare Dashboard → SSL/TLS → Overview +2. Select "Full (strict)" +3. Ensures both Cloudflare-to-origin and client-to-Cloudflare use SSL + +### Cloudflare Always Use HTTPS + +1. SSL/TLS → Edge Certificates +2. Enable "Always Use HTTPS" +3. Automatically redirects HTTP to HTTPS + +### Cloudflare Transform Rules (Optional) + +Add security headers using Transform Rules: + +``` +Header: Strict-Transport-Security +Value: max-age=31536000; includeSubDomains + +Header: X-Content-Type-Options +Value: nosniff + +Header: X-Frame-Options +Value: DENY +``` + +## Automated CORS Testing Script + +Save this as `test-cors.sh`: + +```bash +#!/bin/bash + +FRONTEND_URL="https://frontend.example.com" +BACKEND_URL="https://backend.example.com" + +echo "Testing CORS Configuration..." +echo "================================" + +echo -e "\n1. Testing Health Endpoint..." +curl -s "$BACKEND_URL/health" | jq . + +echo -e "\n2. Testing OPTIONS Preflight..." +curl -X OPTIONS "$BACKEND_URL/api/snippets" \ + -H "Origin: $FRONTEND_URL" \ + -H "Access-Control-Request-Method: GET" \ + -i -s | grep -i "access-control" + +echo -e "\n3. Testing GET with Origin..." +curl -s "$BACKEND_URL/api/snippets" \ + -H "Origin: $FRONTEND_URL" \ + -i | grep -i "access-control" + +echo -e "\n4. Testing POST with Origin..." +curl -X POST "$BACKEND_URL/api/snippets" \ + -H "Origin: $FRONTEND_URL" \ + -H "Content-Type: application/json" \ + -d '{"id":"test","title":"Test","code":"test","language":"JavaScript","createdAt":"2024-01-01T00:00:00","updatedAt":"2024-01-01T00:00:00"}' \ + -i -s | grep -i "access-control" + +echo -e "\nCORS tests complete!" +``` + +Make it executable and run: +```bash +chmod +x test-cors.sh +./test-cors.sh +``` + +## Frontend Storage Config Helper + +The frontend automatically handles backend configuration through the `getStorageConfig()` function: + +### Automatic Configuration + +If `VITE_FLASK_BACKEND_URL` is set, the app automatically uses Flask backend: + +```typescript +// src/lib/storage.ts +function getDefaultConfig(): StorageConfig { + const flaskUrl = import.meta.env.VITE_FLASK_BACKEND_URL + + if (flaskUrl) { + return { backend: 'flask', flaskUrl: flaskUrl } + } + + return { backend: 'indexeddb' } +} +``` + +### Manual Configuration + +Users can also manually configure in Settings page (if no env var is set). + +## Debugging Tips + +### Enable Verbose Logging + +Add logging to backend `app.py`: + +```python +import logging +logging.basicConfig(level=logging.DEBUG) + +@app.after_request +def after_request(response): + app.logger.debug(f"Response headers: {response.headers}") + return response +``` + +### Browser DevTools + +1. Open DevTools (F12) +2. Network tab → Enable "Preserve log" +3. Filter by "Fetch/XHR" +4. Look for OPTIONS and GET/POST requests +5. Check Response Headers for `Access-Control-Allow-Origin` +6. Check Console for CORS error messages + +### CapRover Logs + +View real-time backend logs: + +```bash +# Via CapRover CLI +caprover logs codesnippet-backend --lines 100 --follow + +# Via Dashboard +Apps → codesnippet-backend → Logs +``` + +## Security Best Practices + +### Production CORS Checklist + +- [ ] Set specific origins (not `*`) +- [ ] Use HTTPS for all URLs +- [ ] Enable Cloudflare proxy (orange cloud) +- [ ] Set Cloudflare SSL mode to "Full (strict)" +- [ ] Remove trailing slashes from origin URLs +- [ ] Test with automated script +- [ ] Monitor for CORS errors in production logs +- [ ] Document allowed origins + +### Regular Audits + +Periodically review: +1. Which origins are allowed +2. Whether all origins are still needed +3. CORS-related errors in logs +4. Unauthorized access attempts + +## Additional Resources + +- [MDN CORS Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) +- [Flask-CORS Documentation](https://flask-cors.readthedocs.io/) +- [Cloudflare SSL/TLS Documentation](https://developers.cloudflare.com/ssl/) +- [CapRover Environment Variables](https://caprover.com/docs/app-configuration.html) diff --git a/DEPLOYMENT-CHECKLIST.md b/DEPLOYMENT-CHECKLIST.md new file mode 100644 index 0000000..ba4a2b6 --- /dev/null +++ b/DEPLOYMENT-CHECKLIST.md @@ -0,0 +1,190 @@ +# Deployment Checklist + +Quick reference checklist for deploying CodeSnippet with separate frontend and backend domains. + +## Pre-Deployment + +- [ ] CapRover server is running and accessible +- [ ] Domain name configured in DNS provider (Cloudflare) +- [ ] CapRover CLI installed: `npm install -g caprover` +- [ ] Logged into CapRover: `caprover login` + +## DNS Configuration (Cloudflare) + +- [ ] A record for `frontend.example.com` → CapRover IP (Proxied ☁️) +- [ ] A record for `backend.example.com` → CapRover IP (Proxied ☁️) +- [ ] DNS records propagated (check with `dig` or `nslookup`) + +## Backend Deployment + +- [ ] Create app in CapRover: `codesnippet-backend` +- [ ] Enable "Has Persistent Data" with path: `/app/data` +- [ ] Set environment variable: `CORS_ALLOWED_ORIGINS=https://frontend.example.com` +- [ ] Set environment variable: `DATABASE_PATH=/app/data/snippets.db` +- [ ] Deploy code: `cd backend && caprover deploy -a codesnippet-backend` +- [ ] Enable HTTPS in CapRover +- [ ] Connect custom domain: `backend.example.com` +- [ ] Force HTTPS redirect enabled +- [ ] SSL certificate issued successfully +- [ ] Test health endpoint: `curl https://backend.example.com/health` + +## Frontend Deployment + +- [ ] Create app in CapRover: `codesnippet-frontend` +- [ ] Set environment variable: `VITE_FLASK_BACKEND_URL=https://backend.example.com` +- [ ] Deploy code: `caprover deploy -a codesnippet-frontend` (from project root) +- [ ] Enable HTTPS in CapRover +- [ ] Connect custom domain: `frontend.example.com` +- [ ] Force HTTPS redirect enabled +- [ ] SSL certificate issued successfully +- [ ] Test frontend loads: Visit `https://frontend.example.com` + +## Cloudflare Configuration + +- [ ] SSL/TLS mode set to "Full (strict)" +- [ ] "Always Use HTTPS" enabled +- [ ] "Automatic HTTPS Rewrites" enabled +- [ ] "Auto Minify" enabled (JS, CSS, HTML) +- [ ] "Brotli" compression enabled + +## Testing + +- [ ] Backend health check responds: `curl https://backend.example.com/health` +- [ ] CORS preflight test passes (see CORS-GUIDE.md) +- [ ] Frontend loads without errors +- [ ] Backend indicator shows "Backend" status (not "Local") +- [ ] Can create new snippet +- [ ] Can view existing snippet +- [ ] Can edit snippet +- [ ] Can delete snippet +- [ ] No CORS errors in browser console (F12) +- [ ] Mobile responsive layout works + +## Post-Deployment + +- [ ] Database backup strategy configured +- [ ] Monitoring enabled (CapRover metrics) +- [ ] Rate limiting configured (Cloudflare) +- [ ] Error logging reviewed +- [ ] Documentation updated with actual URLs + +## Security Verification + +- [ ] Both domains use HTTPS only +- [ ] `CORS_ALLOWED_ORIGINS` set to specific domain (not `*`) +- [ ] Flask debug mode disabled (`debug=False`) +- [ ] No sensitive data in environment variables +- [ ] CapRover firewall rules configured +- [ ] Cloudflare security features enabled + +## Quick Commands + +### Deploy Backend +```bash +cd backend +caprover deploy -a codesnippet-backend +``` + +### Deploy Frontend +```bash +caprover deploy -a codesnippet-frontend +``` + +### Check Backend Logs +```bash +caprover logs codesnippet-backend --lines 100 --follow +``` + +### Check Frontend Logs +```bash +caprover logs codesnippet-frontend --lines 100 --follow +``` + +### Test CORS +```bash +curl -X OPTIONS https://backend.example.com/api/snippets \ + -H "Origin: https://frontend.example.com" \ + -H "Access-Control-Request-Method: GET" \ + -v +``` + +### Backup Database +```bash +docker cp captain--codesnippet-backend:/app/data/snippets.db ./backup-$(date +%Y%m%d).db +``` + +## Troubleshooting Quick Fixes + +### Frontend can't connect to backend +1. Check `VITE_FLASK_BACKEND_URL` in frontend environment variables +2. Verify backend is running: `curl https://backend.example.com/health` +3. Check CORS configuration in backend + +### CORS errors in browser +1. Verify `CORS_ALLOWED_ORIGINS` includes frontend URL exactly +2. Ensure both use HTTPS (not mixed HTTP/HTTPS) +3. Restart backend app after environment changes + +### SSL certificate issues +1. Wait 5-10 minutes for Let's Encrypt +2. Verify DNS records point to CapRover +3. Disable and re-enable HTTPS in CapRover + +### Data lost after restart +1. Verify "Has Persistent Data" enabled in backend app +2. Check persistent directory path: `/app/data` +3. Verify volume is mounted correctly + +## Environment Variables Quick Reference + +### Backend (codesnippet-backend) +``` +CORS_ALLOWED_ORIGINS=https://frontend.example.com +DATABASE_PATH=/app/data/snippets.db +``` + +### Frontend (codesnippet-frontend) +``` +VITE_FLASK_BACKEND_URL=https://backend.example.com +``` + +## Alternative: Single Domain Deployment + +For single domain setup (`https://app.example.com`): + +### Backend +- [ ] Deploy backend (internal only, no custom domain) +- [ ] Set `CORS_ALLOWED_ORIGINS=*` (not needed if proxied) + +### Frontend +- [ ] Set `VITE_FLASK_BACKEND_URL=/api` +- [ ] nginx proxies `/api` to backend (already configured) +- [ ] Deploy with custom domain: `app.example.com` + +Benefits: No CORS issues, simpler DNS +Drawbacks: Tightly coupled services + +## Rollback Plan + +If deployment fails: + +1. **Frontend issues:** Redeploy previous version +2. **Backend issues:** Check logs, fix errors, redeploy +3. **Database corruption:** Restore from backup +4. **DNS issues:** Verify Cloudflare settings +5. **SSL issues:** Disable HTTPS temporarily, debug, re-enable + +## Maintenance Schedule + +- **Daily:** Check error logs +- **Weekly:** Review metrics and performance +- **Monthly:** Update dependencies, security patches +- **Quarterly:** Review and rotate secrets/keys + +## Support Resources + +- [Full Deployment Guide](./DEPLOYMENT.md) +- [CORS Configuration Guide](./CORS-GUIDE.md) +- [Backend Configuration](./BACKEND-CONFIG.md) +- [CapRover Documentation](https://caprover.com/docs/) +- [Cloudflare Documentation](https://developers.cloudflare.com/) diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..beb24ae --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,380 @@ +# CapRover / Cloudflare Deployment Guide + +This guide explains how to deploy CodeSnippet with separate frontend and backend domains using CapRover and Cloudflare. + +## Architecture + +``` +┌─────────────────────────────────────────────┐ +│ Cloudflare DNS & Proxy │ +│ https://frontend.example.com │ +│ https://backend.example.com │ +└─────────────┬───────────────────────────────┘ + │ + │ HTTPS (Cloudflare SSL) + │ +┌─────────────▼───────────────────────────────┐ +│ CapRover Server │ +│ │ +│ ┌──────────────┐ ┌─────────────────┐ │ +│ │ Frontend │ │ Backend │ │ +│ │ (Nginx) │ │ (Flask) │ │ +│ │ Port 3000 │ │ Port 5000 │ │ +│ └──────────────┘ └─────────────────┘ │ +└─────────────────────────────────────────────┘ +``` + +## Prerequisites + +1. CapRover server installed and configured +2. Domain name configured in Cloudflare +3. DNS records pointing to your CapRover server +4. CapRover CLI installed: `npm install -g caprover` + +## Step 1: Configure DNS in Cloudflare + +Add these DNS records in Cloudflare: + +| Type | Name | Content | Proxy Status | +|------|----------|----------------------|--------------| +| A | frontend | YOUR_CAPROVER_IP | Proxied | +| A | backend | YOUR_CAPROVER_IP | Proxied | + +**Important:** Enable "Proxied" (orange cloud) to use Cloudflare's CDN and SSL. + +## Step 2: Deploy Backend to CapRover + +### Create Backend App + +1. Login to CapRover dashboard +2. Go to "Apps" → "One-Click Apps/Databases" +3. Create a new app named `codesnippet-backend` +4. Enable "Has Persistent Data" and set persistent directory to `/app/data` + +### Configure Backend Environment Variables + +In the backend app settings, add these environment variables: + +```bash +CORS_ALLOWED_ORIGINS=https://frontend.example.com +DATABASE_PATH=/app/data/snippets.db +``` + +### Deploy Backend Code + +From the `backend` directory: + +```bash +cd backend +caprover deploy -a codesnippet-backend +``` + +### Enable HTTPS for Backend + +1. In CapRover dashboard → Apps → codesnippet-backend +2. Go to "HTTP Settings" +3. Enable "HTTPS" +4. Connect domain: `backend.example.com` +5. Enable "Force HTTPS by redirecting all HTTP traffic to HTTPS" +6. Wait for SSL certificate to be issued + +## Step 3: Deploy Frontend to CapRover + +### Create Frontend App + +1. In CapRover dashboard, create new app: `codesnippet-frontend` +2. No persistent data needed for frontend + +### Configure Frontend Environment Variables + +In the frontend app settings, add: + +```bash +VITE_FLASK_BACKEND_URL=https://backend.example.com +``` + +### Deploy Frontend Code + +From the project root: + +```bash +caprover deploy -a codesnippet-frontend +``` + +### Enable HTTPS for Frontend + +1. In CapRover dashboard → Apps → codesnippet-frontend +2. Go to "HTTP Settings" +3. Enable "HTTPS" +4. Connect domain: `frontend.example.com` +5. Enable "Force HTTPS by redirecting all HTTP traffic to HTTPS" +6. Wait for SSL certificate to be issued + +## Step 4: Configure Cloudflare Settings + +### SSL/TLS Settings + +1. Go to Cloudflare dashboard → SSL/TLS +2. Set encryption mode to "Full (strict)" +3. Enable "Always Use HTTPS" +4. Enable "Automatic HTTPS Rewrites" + +### Security Settings + +1. Go to Security → WAF +2. Consider enabling Bot Fight Mode for backend +3. Set up rate limiting rules if needed + +### Speed Settings + +1. Enable "Auto Minify" for JavaScript, CSS, HTML +2. Enable "Brotli" compression +3. Set Browser Cache TTL appropriately + +## Step 5: Verify Deployment + +### Test Backend + +```bash +curl https://backend.example.com/health +``` + +Expected response: +```json +{ + "status": "healthy", + "timestamp": "2024-01-01T12:00:00.000000" +} +``` + +### Test CORS + +```bash +curl -H "Origin: https://frontend.example.com" \ + -H "Access-Control-Request-Method: GET" \ + -X OPTIONS \ + https://backend.example.com/api/snippets +``` + +Should return CORS headers: +``` +Access-Control-Allow-Origin: https://frontend.example.com +Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS +``` + +### Test Frontend + +Visit `https://frontend.example.com` and verify: +- Frontend loads correctly +- Backend indicator shows "Backend" status +- Can create, edit, and delete snippets +- No CORS errors in browser console + +## Environment Variables Reference + +### Backend Environment Variables + +| Variable | Description | Example | +|-----------------------|------------------------------------------------|----------------------------------| +| `CORS_ALLOWED_ORIGINS`| Comma-separated list of allowed frontend URLs | `https://frontend.example.com` | +| `DATABASE_PATH` | Path to SQLite database file | `/app/data/snippets.db` | + +**Note:** Use `*` for `CORS_ALLOWED_ORIGINS` only in development. In production, always specify exact origins. + +### Frontend Environment Variables + +| Variable | Description | Example | +|---------------------------|------------------------------|--------------------------------| +| `VITE_FLASK_BACKEND_URL` | Backend API URL | `https://backend.example.com` | + +## Alternative: Single Domain Setup + +If you prefer a single domain (e.g., `https://app.example.com`), you can deploy frontend with nginx proxying to backend: + +### Deploy Both Services + +1. Deploy backend as before (internal only, no custom domain) +2. Frontend proxies `/api` requests to backend via nginx + +### Frontend Configuration + +```bash +# Frontend environment variables +VITE_FLASK_BACKEND_URL=/api +``` + +### Nginx Configuration + +The included `nginx.conf` already handles this: + +```nginx +location /api { + proxy_pass http://backend:5000; + # ... proxy headers +} +``` + +### Benefits + +- Simplified DNS (single domain) +- No CORS issues (same-origin requests) +- Easier SSL management + +### Drawbacks + +- Frontend and backend tightly coupled +- Can't independently scale services +- Single point of failure + +## Troubleshooting + +### CORS Errors + +**Problem:** Browser console shows CORS errors + +**Solutions:** +1. Verify `CORS_ALLOWED_ORIGINS` in backend matches frontend URL exactly +2. Ensure both domains use HTTPS (not mixed HTTP/HTTPS) +3. Check Cloudflare proxy status is enabled for both domains +4. Clear browser cache and hard refresh + +### Backend Connection Failed + +**Problem:** Frontend shows "Connection failed" error + +**Solutions:** +1. Verify backend is running: `curl https://backend.example.com/health` +2. Check CapRover logs for backend app +3. Verify `VITE_FLASK_BACKEND_URL` in frontend matches backend URL +4. Test from command line: `curl -v https://backend.example.com/api/snippets` + +### SSL Certificate Issues + +**Problem:** SSL certificate not issued or invalid + +**Solutions:** +1. Wait 5-10 minutes for Let's Encrypt to issue certificate +2. Verify DNS records are correct and propagated +3. Check CapRover can reach Let's Encrypt (port 80 open) +4. Try disabling and re-enabling HTTPS in CapRover + +### Data Persistence Issues + +**Problem:** Backend loses data after restart + +**Solutions:** +1. Verify "Has Persistent Data" is enabled in CapRover +2. Check persistent directory path is `/app/data` +3. Verify `DATABASE_PATH` environment variable is correct +4. Check CapRover volume is properly mounted + +### Multiple Origins + +**Problem:** Need to allow multiple frontend domains + +**Solution:** Set comma-separated origins: +```bash +CORS_ALLOWED_ORIGINS=https://frontend.example.com,https://app.example.com,https://staging.example.com +``` + +## Security Best Practices + +### Production Checklist + +- [ ] HTTPS enabled for both frontend and backend +- [ ] `CORS_ALLOWED_ORIGINS` set to specific domains (not `*`) +- [ ] Cloudflare proxy enabled (orange cloud) +- [ ] Rate limiting configured in Cloudflare +- [ ] Backend database backed up regularly +- [ ] Environment variables stored securely (not in code) +- [ ] Debug mode disabled in Flask (`debug=False`) +- [ ] Cloudflare WAF rules configured +- [ ] HTTPS-only cookies enabled +- [ ] Security headers configured in nginx + +### Recommended Cloudflare Rules + +1. **Rate Limiting:** Limit API requests to 100 per minute per IP +2. **Bot Protection:** Challenge or block known bad bots +3. **Geographic Restrictions:** Block countries you don't serve (optional) +4. **DDoS Protection:** Enable automatic DDoS mitigation + +## Monitoring + +### CapRover Monitoring + +1. Enable app metrics in CapRover dashboard +2. Monitor CPU and memory usage +3. Set up alerts for app crashes +4. Review logs regularly for errors + +### Cloudflare Analytics + +1. Monitor traffic patterns +2. Check for unusual spikes or attacks +3. Review security events +4. Analyze performance metrics + +## Backup Strategy + +### Automated Backups + +Set up a cron job in CapRover to backup database: + +```bash +# In backend app settings, add a schedule: +0 2 * * * tar -czf /app/data/backup-$(date +%Y%m%d).tar.gz /app/data/snippets.db +``` + +### Manual Backup + +1. SSH into CapRover server +2. Copy database file: + ```bash + docker cp captain--codesnippet-backend:/app/data/snippets.db ./backup.db + ``` + +### Restore from Backup + +1. Copy backup to container: + ```bash + docker cp ./backup.db captain--codesnippet-backend:/app/data/snippets.db + ``` +2. Restart backend app in CapRover + +## Scaling Considerations + +### Vertical Scaling + +Increase resources in CapRover: +1. Go to app settings → "Resources" +2. Increase CPU and memory limits +3. Restart app + +### Horizontal Scaling + +For high traffic: +1. Deploy multiple backend instances in CapRover +2. Use CapRover's load balancing +3. Consider shared database (PostgreSQL instead of SQLite) +4. Use Redis for session management + +## Cost Optimization + +### Cloudflare + +- Free plan includes SSL, CDN, and basic DDoS protection +- Pro plan ($20/mo) adds WAF and additional performance features + +### CapRover + +- Single VPS can run both frontend and backend +- Recommended: 2 CPU / 4GB RAM minimum +- Estimated cost: $10-20/month (DigitalOcean, Linode, Vultr) + +## Support + +For issues specific to: +- **CapRover:** https://caprover.com/docs/ +- **Cloudflare:** https://support.cloudflare.com/ +- **CodeSnippet:** Check the main README.md and BACKEND-CONFIG.md diff --git a/ENV-CONFIG.md b/ENV-CONFIG.md new file mode 100644 index 0000000..d7a7c17 --- /dev/null +++ b/ENV-CONFIG.md @@ -0,0 +1,329 @@ +# Environment Configuration Examples + +This directory contains example environment configurations for different deployment scenarios. + +## Quick Reference + +Copy the appropriate example to `.env` in the project root: + +```bash +cp .env.example .env +# Edit .env with your values +``` + +## Scenarios + +### 1. Local Development (Default) + +**Use case:** Developing frontend only with local browser storage + +```bash +# No backend URL - uses IndexedDB +VITE_FLASK_BACKEND_URL= +``` + +### 2. Local Development with Backend + +**Use case:** Developing with local Flask backend + +```bash +# Frontend (.env) +VITE_FLASK_BACKEND_URL=http://localhost:5000 + +# Backend (environment or .env) +CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173 +DATABASE_PATH=/app/data/snippets.db +``` + +### 3. Docker Compose (Full Stack) + +**Use case:** Running both frontend and backend with Docker + +```bash +# Frontend automatically connects to backend via nginx proxy +# No .env needed - configured in docker-compose.yml +``` + +**docker-compose.yml already includes:** +```yaml +environment: + - VITE_FLASK_BACKEND_URL=http://localhost:5000 + - CORS_ALLOWED_ORIGINS=http://localhost:3000 + - DATABASE_PATH=/app/data/snippets.db +``` + +### 4. Production - Separate Domains (CapRover) + +**Use case:** Frontend and backend on different domains + +```bash +# Frontend environment variables (in CapRover) +VITE_FLASK_BACKEND_URL=https://backend.example.com + +# Backend environment variables (in CapRover) +CORS_ALLOWED_ORIGINS=https://frontend.example.com +DATABASE_PATH=/app/data/snippets.db +``` + +### 5. Production - Multiple Frontend Domains + +**Use case:** Multiple frontend deployments (prod, staging, dev) + +```bash +# Backend environment variables +CORS_ALLOWED_ORIGINS=https://app.example.com,https://staging.example.com,https://dev.example.com +DATABASE_PATH=/app/data/snippets.db + +# Frontend 1 (Production) +VITE_FLASK_BACKEND_URL=https://api.example.com + +# Frontend 2 (Staging) +VITE_FLASK_BACKEND_URL=https://api.example.com + +# Frontend 3 (Development) +VITE_FLASK_BACKEND_URL=https://api.example.com +``` + +### 6. Production - Single Domain (Proxied) + +**Use case:** Frontend and backend on same domain, nginx proxy + +```bash +# Frontend environment variables +VITE_FLASK_BACKEND_URL=/api + +# Backend environment variables +CORS_ALLOWED_ORIGINS=* +# Note: CORS not needed since nginx proxies requests (same-origin) +DATABASE_PATH=/app/data/snippets.db +``` + +**nginx.conf includes:** +```nginx +location /api { + proxy_pass http://backend:5000; +} +``` + +## Environment Variables Reference + +### Frontend Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `VITE_FLASK_BACKEND_URL` | Backend API URL. When set, forces Flask backend usage. | `https://backend.example.com` | + +**Notes:** +- Must start with `VITE_` to be exposed to frontend +- If not set, app uses IndexedDB (local storage) +- Can be relative (`/api`) if using nginx proxy +- Requires rebuild for production: `npm run build` + +### Backend Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `CORS_ALLOWED_ORIGINS` | Comma-separated list of allowed origins | `https://frontend.example.com` | +| `DATABASE_PATH` | Path to SQLite database file | `/app/data/snippets.db` | + +**Notes:** +- Use `*` only in development +- In production, always specify exact origins +- No trailing slashes on URLs +- Must match frontend URL exactly (including https://) + +## Setting Environment Variables + +### Local Development (.env file) + +```bash +# Create .env file +cat > .env << EOF +VITE_FLASK_BACKEND_URL=http://localhost:5000 +EOF + +# Start dev server +npm run dev +``` + +### Docker Build + +```bash +# Build with environment variable +docker build --build-arg VITE_FLASK_BACKEND_URL=https://backend.example.com -t frontend . +``` + +### Docker Compose + +```yaml +# docker-compose.yml +services: + frontend: + environment: + - VITE_FLASK_BACKEND_URL=https://backend.example.com +``` + +### CapRover + +1. Go to CapRover dashboard +2. Select your app +3. Click "App Configs" tab +4. Add environment variables in "Environment Variables" section +5. Redeploy app + +### Kubernetes + +```yaml +# deployment.yaml +env: + - name: VITE_FLASK_BACKEND_URL + value: "https://backend.example.com" + - name: CORS_ALLOWED_ORIGINS + value: "https://frontend.example.com" +``` + +## Testing Configuration + +### Test Frontend Backend Connection + +```bash +# Check if environment variable is set +echo $VITE_FLASK_BACKEND_URL + +# Check in browser console +console.log(import.meta.env.VITE_FLASK_BACKEND_URL) +``` + +### Test Backend CORS + +```bash +# Quick test +curl -H "Origin: https://frontend.example.com" \ + -H "Access-Control-Request-Method: GET" \ + -X OPTIONS \ + https://backend.example.com/api/snippets + +# Full test suite +./test-cors.sh https://backend.example.com https://frontend.example.com +``` + +## Common Mistakes + +### ❌ Wrong: Missing VITE_ Prefix + +```bash +FLASK_BACKEND_URL=http://localhost:5000 # Won't work! +``` + +### ✅ Correct: VITE_ Prefix Required + +```bash +VITE_FLASK_BACKEND_URL=http://localhost:5000 +``` + +--- + +### ❌ Wrong: Trailing Slash in CORS + +```bash +CORS_ALLOWED_ORIGINS=https://frontend.example.com/ # Won't work! +``` + +### ✅ Correct: No Trailing Slash + +```bash +CORS_ALLOWED_ORIGINS=https://frontend.example.com +``` + +--- + +### ❌ Wrong: HTTP/HTTPS Mismatch + +```bash +# Frontend +VITE_FLASK_BACKEND_URL=https://backend.example.com + +# Backend +CORS_ALLOWED_ORIGINS=http://frontend.example.com # Wrong protocol! +``` + +### ✅ Correct: Matching Protocols + +```bash +# Frontend +VITE_FLASK_BACKEND_URL=https://backend.example.com + +# Backend +CORS_ALLOWED_ORIGINS=https://frontend.example.com +``` + +--- + +### ❌ Wrong: Using * in Production + +```bash +CORS_ALLOWED_ORIGINS=* # Security risk in production! +``` + +### ✅ Correct: Specific Origins + +```bash +CORS_ALLOWED_ORIGINS=https://frontend.example.com +``` + +## Troubleshooting + +### Environment variable not working + +1. **Frontend not rebuilding:** + - Vite requires rebuild for env vars: `npm run build` + - Dev server: Restart `npm run dev` + +2. **Variable not prefixed correctly:** + - Must start with `VITE_` for frontend + - Backend vars don't need prefix + +3. **Docker not picking up changes:** + - Rebuild: `docker-compose up -d --build` + - Check: `docker-compose config` + +### CORS errors persist + +1. **Backend not restarted:** + - Restart after env changes + - Check logs: `docker-compose logs backend` + +2. **URL mismatch:** + - Frontend URL must match CORS_ALLOWED_ORIGINS exactly + - Check browser console for actual origin + +3. **Cloudflare issues:** + - Verify proxy status (orange cloud) + - Check SSL/TLS mode: "Full (strict)" + +## Security Best Practices + +### Development +- ✅ Use `*` for CORS_ALLOWED_ORIGINS +- ✅ Use http:// for local URLs +- ✅ Keep .env out of version control + +### Staging +- ✅ Use specific origins for CORS +- ✅ Use https:// for all URLs +- ✅ Test with production-like configuration + +### Production +- ✅ Always use specific origins +- ✅ Always use https:// +- ✅ Store secrets in secure environment +- ✅ Never commit .env files +- ✅ Rotate credentials regularly +- ✅ Monitor access logs + +## Additional Resources + +- [Backend Configuration Guide](./BACKEND-CONFIG.md) +- [CORS Configuration & Testing](./CORS-GUIDE.md) +- [Deployment Guide](./DEPLOYMENT.md) +- [Deployment Checklist](./DEPLOYMENT-CHECKLIST.md) diff --git a/README.md b/README.md index 2a04e31..1ea3d21 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,18 @@ When set, the app automatically connects to Flask backend and disables manual co ## 📚 Documentation +### Getting Started - **[Quick Start Guide](./QUICKSTART.md)** - Get up and running quickly - **[Application Guide](./README-APP.md)** - Features and usage -- **[Backend Configuration](./BACKEND-CONFIG.md)** - Detailed backend setup + +### Backend & Storage +- **[Backend Configuration](./BACKEND-CONFIG.md)** - Detailed backend setup and environment variables - **[Backend API](./backend/README.md)** - Flask API documentation + +### Production Deployment +- **[Deployment Guide](./DEPLOYMENT.md)** - Complete CapRover/Cloudflare deployment walkthrough +- **[CORS Configuration](./CORS-GUIDE.md)** - CORS setup and troubleshooting +- **[Deployment Checklist](./DEPLOYMENT-CHECKLIST.md)** - Quick deployment reference - **[Docker Examples](./docker-compose.README.md)** - Docker deployment options ## 🛠️ Technology Stack diff --git a/backend/Dockerfile b/backend/Dockerfile index 1789c9c..35e1b8e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -7,10 +7,12 @@ RUN pip install --no-cache-dir -r requirements.txt COPY app.py . -RUN mkdir -p /data +RUN mkdir -p /app/data + +ENV FLASK_APP=app.py +ENV DATABASE_PATH=/app/data/snippets.db +ENV CORS_ALLOWED_ORIGINS=* EXPOSE 5000 -ENV DB_PATH=/data/snippets.db - CMD ["python", "app.py"] diff --git a/backend/README.md b/backend/README.md index 423869c..abce013 100644 --- a/backend/README.md +++ b/backend/README.md @@ -113,10 +113,14 @@ docker build -t codesnippet-backend ./backend ```bash # With volume for persistent data -docker run -p 5000:5000 -v $(pwd)/data:/data codesnippet-backend +docker run -p 5000:5000 -v $(pwd)/data:/app/data codesnippet-backend -# With custom database path -docker run -p 5000:5000 -e DB_PATH=/data/custom.db -v $(pwd)/data:/data codesnippet-backend +# With custom database path and CORS +docker run -p 5000:5000 \ + -e DATABASE_PATH=/app/data/custom.db \ + -e CORS_ALLOWED_ORIGINS=https://frontend.example.com \ + -v $(pwd)/data:/app/data \ + codesnippet-backend ``` ### Using Docker Compose @@ -162,7 +166,24 @@ CREATE TABLE snippets ( ## Environment Variables -- `DB_PATH` - Path to SQLite database file (default: `/data/snippets.db`) +| Variable | Description | Default | Example | +|----------|-------------|---------|---------| +| `DATABASE_PATH` | Path to SQLite database file | `/app/data/snippets.db` | `/app/data/snippets.db` | +| `CORS_ALLOWED_ORIGINS` | Comma-separated list of allowed frontend origins | `*` (all origins) | `https://frontend.example.com` | + +### Production Configuration + +For production deployments, always set specific CORS origins: + +```bash +# Single origin +export CORS_ALLOWED_ORIGINS=https://frontend.example.com + +# Multiple origins +export CORS_ALLOWED_ORIGINS=https://frontend.example.com,https://app.example.com +``` + +**Important:** Using `*` for CORS in production is a security risk. Only use in development. ## Troubleshooting @@ -172,9 +193,34 @@ CREATE TABLE snippets ( - Verify the port (5000) is not in use ### CORS Errors -- The backend allows all origins by default -- Modify `CORS(app)` in `app.py` if you need to restrict origins +- The backend allows all origins by default in development (`CORS_ALLOWED_ORIGINS=*`) +- For production, set specific origins: `CORS_ALLOWED_ORIGINS=https://frontend.example.com` +- See [CORS-GUIDE.md](../CORS-GUIDE.md) for detailed CORS configuration and testing +- Verify frontend URL matches exactly (including https:// and no trailing slash) ### Database Locked - Ensure only one instance of the backend is running - Check file permissions on the database file + +## Production Deployment + +For deploying to production with separate frontend and backend domains: + +1. **See [DEPLOYMENT.md](../DEPLOYMENT.md)** - Complete CapRover/Cloudflare deployment guide +2. **See [CORS-GUIDE.md](../CORS-GUIDE.md)** - CORS configuration and testing +3. **See [DEPLOYMENT-CHECKLIST.md](../DEPLOYMENT-CHECKLIST.md)** - Quick deployment checklist + +### Quick Production Setup + +```bash +# Build and deploy backend to CapRover +cd backend +caprover deploy -a codesnippet-backend + +# Set environment variables in CapRover dashboard: +# - CORS_ALLOWED_ORIGINS=https://frontend.example.com +# - DATABASE_PATH=/app/data/snippets.db + +# Enable persistent storage at /app/data +# Enable HTTPS and connect custom domain +``` diff --git a/backend/app.py b/backend/app.py index 8fcfbbd..afa9f98 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,181 +1,191 @@ -from flask import Flask, request, jsonify -from flask_cors import CORS -from datetime - -from datetime import datetime -import os - -app = Flask(__name__) - - - cursor = conn.cursor() - - i - code TEXT NOT NULL, - description TEXT, - cat - - up - ''') - conn.commit() - -def - -def get_snippets(): - conn = get_db() - cursor.execute('SELECT * - conn.close() - snippets = [] - snippet = dict(ro - snippe - snippet['p - - except Exception as e: - - updatedAt TEXT NOT NULL - ) - ''') - - conn.commit() - conn.close() - -@app.route('/health', methods=['GET']) -def health(): - return jsonify({'status': 'healthy', 'timestamp': datetime.utcnow().isoformat()}) - -@app.route('/api/snippets', methods=['GET']) -def get_snippets(): - - conn = get_db() - preview_params_json = - cursor.execute('SELECT * FROM snippets ORDER BY updatedAt DESC') - rows = cursor.fetchall() - conn.close() - - snippets = [] - for row in rows: - snippet = dict(row) - if snippet.get('tags'): - snippet['tags'] = json.loads(snippet['tags']) - if snippet.get('previewParams'): - snippet['previewParams'] = json.loads(snippet['previewParams']) - snippets.append(snippet) - - return jsonify(snippets) - except Exception as e: - return jsonify({'error': str(e)}), 500 - -@app.route('/api/snippets/', methods=['GET']) - UPDATE snippets - - data['title - data['language'], - tags_json, - data.get('component - data['up - - conn.commit - - - return jsonify(data - return jsonify({'error' -@app.route('/api/snippets/', methods=['DELETE - try: - cursor = conn.cursor() - - - return jsonify - return jsonify({'success': True}) - -if __name__ == '__main__': - app.run(host='0.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - UPDATE snippets - SET title = ?, code = ?, language = ?, description = ?, tags = ?, category = ?, componentName = ?, previewParams = ?, updatedAt = ? - WHERE id = ? - ''', ( - data['title'], - data['code'], - data['language'], - data.get('description', ''), - tags_json, - data.get('category', 'general'), - data.get('componentName', ''), - preview_params_json, - data['updatedAt'], - snippet_id - )) - - conn.commit() - conn.close() - - if cursor.rowcount == 0: - return jsonify({'error': 'Snippet not found'}), 404 - - return jsonify(data) - except Exception as e: - return jsonify({'error': str(e)}), 500 - -@app.route('/api/snippets/', methods=['DELETE']) -def delete_snippet(snippet_id): - try: - conn = get_db() - cursor = conn.cursor() - cursor.execute('DELETE FROM snippets WHERE id = ?', (snippet_id,)) - conn.commit() - conn.close() - - if cursor.rowcount == 0: - return jsonify({'error': 'Snippet not found'}), 404 - - return jsonify({'success': True}) - except Exception as e: - return jsonify({'error': str(e)}), 500 - -if __name__ == '__main__': - init_db() - app.run(host='0.0.0.0', port=5000, debug=True) +from flask import Flask, request, jsonify +from flask_cors import CORS +from datetime import datetime +import sqlite3 +import json +import os + +app = Flask(__name__) + +ALLOWED_ORIGINS = os.environ.get('CORS_ALLOWED_ORIGINS', '*') +if ALLOWED_ORIGINS == '*': + CORS(app, + origins='*', + methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allow_headers=['Content-Type', 'Authorization'], + supports_credentials=False) +else: + origins_list = [origin.strip() for origin in ALLOWED_ORIGINS.split(',')] + CORS(app, + origins=origins_list, + methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allow_headers=['Content-Type', 'Authorization'], + supports_credentials=True) + +DATABASE_PATH = os.environ.get('DATABASE_PATH', '/app/data/snippets.db') +os.makedirs(os.path.dirname(DATABASE_PATH), exist_ok=True) + +def get_db(): + conn = sqlite3.connect(DATABASE_PATH) + conn.row_factory = sqlite3.Row + return conn + +def init_db(): + conn = get_db() + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS snippets ( + id TEXT PRIMARY KEY, + title TEXT NOT NULL, + code TEXT NOT NULL, + language TEXT NOT NULL, + description TEXT, + tags TEXT, + category TEXT DEFAULT 'general', + componentName TEXT, + previewParams TEXT, + createdAt TEXT NOT NULL, + updatedAt TEXT NOT NULL + ) + ''') + conn.commit() + conn.close() + +@app.route('/health', methods=['GET']) +def health(): + return jsonify({'status': 'healthy', 'timestamp': datetime.utcnow().isoformat()}) + +@app.route('/api/snippets', methods=['GET']) +def get_snippets(): + try: + conn = get_db() + cursor = conn.cursor() + cursor.execute('SELECT * FROM snippets ORDER BY updatedAt DESC') + rows = cursor.fetchall() + conn.close() + + snippets = [] + for row in rows: + snippet = dict(row) + if snippet.get('tags'): + snippet['tags'] = json.loads(snippet['tags']) + if snippet.get('previewParams'): + snippet['previewParams'] = json.loads(snippet['previewParams']) + snippets.append(snippet) + + return jsonify(snippets) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/snippets/', methods=['GET']) +def get_snippet(snippet_id): + try: + conn = get_db() + cursor = conn.cursor() + cursor.execute('SELECT * FROM snippets WHERE id = ?', (snippet_id,)) + row = cursor.fetchone() + conn.close() + + if not row: + return jsonify({'error': 'Snippet not found'}), 404 + + snippet = dict(row) + if snippet.get('tags'): + snippet['tags'] = json.loads(snippet['tags']) + if snippet.get('previewParams'): + snippet['previewParams'] = json.loads(snippet['previewParams']) + + return jsonify(snippet) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/snippets', methods=['POST']) +def create_snippet(): + try: + data = request.json + conn = get_db() + cursor = conn.cursor() + + tags_json = json.dumps(data.get('tags', [])) + preview_params_json = json.dumps(data.get('previewParams', {})) + + cursor.execute(''' + INSERT INTO snippets (id, title, code, language, description, tags, category, componentName, previewParams, createdAt, updatedAt) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', ( + data['id'], + data['title'], + data['code'], + data['language'], + data.get('description', ''), + tags_json, + data.get('category', 'general'), + data.get('componentName', ''), + preview_params_json, + data['createdAt'], + data['updatedAt'] + )) + + conn.commit() + conn.close() + + return jsonify(data), 201 + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/snippets/', methods=['PUT']) +def update_snippet(snippet_id): + try: + data = request.json + conn = get_db() + cursor = conn.cursor() + + tags_json = json.dumps(data.get('tags', [])) + preview_params_json = json.dumps(data.get('previewParams', {})) + + cursor.execute(''' + UPDATE snippets + SET title = ?, code = ?, language = ?, description = ?, tags = ?, category = ?, componentName = ?, previewParams = ?, updatedAt = ? + WHERE id = ? + ''', ( + data['title'], + data['code'], + data['language'], + data.get('description', ''), + tags_json, + data.get('category', 'general'), + data.get('componentName', ''), + preview_params_json, + data['updatedAt'], + snippet_id + )) + + conn.commit() + conn.close() + + if cursor.rowcount == 0: + return jsonify({'error': 'Snippet not found'}), 404 + + return jsonify(data) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/snippets/', methods=['DELETE']) +def delete_snippet(snippet_id): + try: + conn = get_db() + cursor = conn.cursor() + cursor.execute('DELETE FROM snippets WHERE id = ?', (snippet_id,)) + conn.commit() + conn.close() + + if cursor.rowcount == 0: + return jsonify({'error': 'Snippet not found'}), 404 + + return jsonify({'success': True}) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +if __name__ == '__main__': + init_db() + app.run(host='0.0.0.0', port=5000, debug=False) diff --git a/backend/captain-definition b/backend/captain-definition new file mode 100644 index 0000000..0e14f82 --- /dev/null +++ b/backend/captain-definition @@ -0,0 +1,4 @@ +{ + "schemaVersion": 2, + "dockerfilePath": "./Dockerfile" +} diff --git a/captain-definition b/captain-definition new file mode 100644 index 0000000..0e14f82 --- /dev/null +++ b/captain-definition @@ -0,0 +1,4 @@ +{ + "schemaVersion": 2, + "dockerfilePath": "./Dockerfile" +} diff --git a/docker-compose.production.yml b/docker-compose.production.yml new file mode 100644 index 0000000..234c7bb --- /dev/null +++ b/docker-compose.production.yml @@ -0,0 +1,35 @@ +version: '3.8' + +services: + backend: + build: ./backend + ports: + - "5000:5000" + volumes: + - snippet-data:/app/data + environment: + - DATABASE_PATH=/app/data/snippets.db + - CORS_ALLOWED_ORIGINS=https://frontend.example.com,https://app.example.com + restart: unless-stopped + networks: + - app-network + + frontend: + build: + context: . + args: + - VITE_FLASK_BACKEND_URL=https://backend.example.com + ports: + - "3000:3000" + depends_on: + - backend + restart: unless-stopped + networks: + - app-network + +volumes: + snippet-data: + +networks: + app-network: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml index 76172c1..f70dd80 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,9 +6,10 @@ services: ports: - "5000:5000" volumes: - - snippet-data:/data + - snippet-data:/app/data environment: - - DB_PATH=/data/snippets.db + - DATABASE_PATH=/app/data/snippets.db + - CORS_ALLOWED_ORIGINS=http://localhost:3000 restart: unless-stopped frontend: @@ -18,8 +19,6 @@ services: - VITE_FLASK_BACKEND_URL=http://localhost:5000 ports: - "3000:3000" - environment: - - VITE_FLASK_BACKEND_URL=http://backend:5000 depends_on: - backend restart: unless-stopped diff --git a/nginx.conf b/nginx.conf index 10f81be..ae726e1 100644 --- a/nginx.conf +++ b/nginx.conf @@ -6,6 +6,10 @@ server { location / { try_files $uri $uri/ /index.html; + + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; } location /api { @@ -14,6 +18,12 @@ server { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; + + proxy_buffering off; + proxy_request_buffering off; } } diff --git a/test-cors.sh b/test-cors.sh new file mode 100644 index 0000000..35dc410 --- /dev/null +++ b/test-cors.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +# CORS Testing Script for CodeSnippet +# Usage: ./test-cors.sh [BACKEND_URL] [FRONTEND_URL] +# Example: ./test-cors.sh https://backend.example.com https://frontend.example.com + +BACKEND_URL="${1:-http://localhost:5000}" +FRONTEND_URL="${2:-http://localhost:3000}" + +echo "======================================" +echo "CodeSnippet CORS Testing Script" +echo "======================================" +echo "Backend URL: $BACKEND_URL" +echo "Frontend URL: $FRONTEND_URL" +echo "======================================" +echo "" + +# Test 1: Health Check +echo "🔍 Test 1: Health Check (No CORS required)" +echo "--------------------------------------" +HEALTH_RESPONSE=$(curl -s "$BACKEND_URL/health") +if [ $? -eq 0 ]; then + echo "✅ Health check successful" + echo "Response: $HEALTH_RESPONSE" +else + echo "❌ Health check failed - backend may not be running" + exit 1 +fi +echo "" + +# Test 2: OPTIONS Preflight +echo "🔍 Test 2: OPTIONS Preflight Request" +echo "--------------------------------------" +PREFLIGHT_HEADERS=$(curl -s -X OPTIONS "$BACKEND_URL/api/snippets" \ + -H "Origin: $FRONTEND_URL" \ + -H "Access-Control-Request-Method: GET" \ + -H "Access-Control-Request-Headers: Content-Type" \ + -i | grep -i "access-control") + +if echo "$PREFLIGHT_HEADERS" | grep -q "access-control-allow-origin"; then + echo "✅ CORS preflight successful" + echo "$PREFLIGHT_HEADERS" +else + echo "❌ CORS preflight failed - missing CORS headers" + echo "Response headers:" + curl -s -X OPTIONS "$BACKEND_URL/api/snippets" \ + -H "Origin: $FRONTEND_URL" \ + -H "Access-Control-Request-Method: GET" \ + -i | head -n 20 +fi +echo "" + +# Test 3: GET with Origin +echo "🔍 Test 3: GET Request with Origin" +echo "--------------------------------------" +GET_HEADERS=$(curl -s "$BACKEND_URL/api/snippets" \ + -H "Origin: $FRONTEND_URL" \ + -i | grep -i "access-control") + +if echo "$GET_HEADERS" | grep -q "access-control-allow-origin"; then + echo "✅ GET request CORS successful" + echo "$GET_HEADERS" +else + echo "❌ GET request CORS failed - missing CORS headers" +fi +echo "" + +# Test 4: POST with Origin +echo "🔍 Test 4: POST Request with Origin" +echo "--------------------------------------" +TEST_SNIPPET='{ + "id": "test-cors-'$(date +%s)'", + "title": "CORS Test Snippet", + "code": "console.log(\"CORS test\");", + "language": "JavaScript", + "description": "Test snippet for CORS validation", + "tags": ["test"], + "category": "general", + "createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")'", + "updatedAt": "'$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")'" +}' + +POST_RESPONSE=$(curl -s -X POST "$BACKEND_URL/api/snippets" \ + -H "Origin: $FRONTEND_URL" \ + -H "Content-Type: application/json" \ + -d "$TEST_SNIPPET" \ + -i) + +POST_HEADERS=$(echo "$POST_RESPONSE" | grep -i "access-control") +POST_STATUS=$(echo "$POST_RESPONSE" | head -n 1) + +if echo "$POST_HEADERS" | grep -q "access-control-allow-origin"; then + echo "✅ POST request CORS successful" + echo "Status: $POST_STATUS" + echo "$POST_HEADERS" +else + echo "❌ POST request CORS failed - missing CORS headers" + echo "Status: $POST_STATUS" +fi +echo "" + +# Test 5: Wrong Origin (Should fail or return no CORS headers) +echo "🔍 Test 5: Request from Unauthorized Origin" +echo "--------------------------------------" +WRONG_ORIGIN="https://malicious-site.com" +WRONG_HEADERS=$(curl -s "$BACKEND_URL/api/snippets" \ + -H "Origin: $WRONG_ORIGIN" \ + -i | grep -i "access-control") + +if [ -z "$WRONG_HEADERS" ]; then + echo "✅ Correctly blocking unauthorized origin" + echo " (No CORS headers returned for $WRONG_ORIGIN)" +elif echo "$WRONG_HEADERS" | grep -q "access-control-allow-origin.*\*"; then + echo "⚠️ Warning: Backend allows all origins (*)" + echo " This is fine for development but should be restricted in production" +else + echo "⚠️ Unexpected CORS response for unauthorized origin" + echo "$WRONG_HEADERS" +fi +echo "" + +# Summary +echo "======================================" +echo "Test Summary" +echo "======================================" +echo "Backend URL: $BACKEND_URL" +echo "Frontend URL: $FRONTEND_URL" +echo "" +echo "If all tests passed:" +echo " ✅ Your CORS configuration is working correctly" +echo "" +echo "If tests failed:" +echo " 1. Verify backend is running at $BACKEND_URL" +echo " 2. Check CORS_ALLOWED_ORIGINS environment variable" +echo " 3. Ensure it includes $FRONTEND_URL" +echo " 4. Restart backend after environment changes" +echo " 5. See CORS-GUIDE.md for detailed troubleshooting" +echo "======================================"