mirror of
https://github.com/johndoe6345789/snippet-pastebin.git
synced 2026-04-24 13:34:55 +00:00
Generated by Spark: Should handle caprover / cloudflare cors - Check frontend and backend config. example setup: https://frontend.example.com https://backend.example.com
This commit is contained in:
18
.env.example
18
.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
|
||||
|
||||
@@ -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)
|
||||
|
||||
383
CORS-CONFIG-SUMMARY.md
Normal file
383
CORS-CONFIG-SUMMARY.md
Normal file
@@ -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)!
|
||||
409
CORS-GUIDE.md
Normal file
409
CORS-GUIDE.md
Normal file
@@ -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)
|
||||
190
DEPLOYMENT-CHECKLIST.md
Normal file
190
DEPLOYMENT-CHECKLIST.md
Normal file
@@ -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/)
|
||||
380
DEPLOYMENT.md
Normal file
380
DEPLOYMENT.md
Normal file
@@ -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
|
||||
329
ENV-CONFIG.md
Normal file
329
ENV-CONFIG.md
Normal file
@@ -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)
|
||||
10
README.md
10
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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
182
backend/app.py
182
backend/app.py
@@ -1,41 +1,53 @@
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
from datetime
|
||||
|
||||
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()
|
||||
|
||||
i
|
||||
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,
|
||||
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:
|
||||
|
||||
tags TEXT,
|
||||
category TEXT DEFAULT 'general',
|
||||
componentName TEXT,
|
||||
previewParams TEXT,
|
||||
createdAt TEXT NOT NULL,
|
||||
updatedAt TEXT NOT NULL
|
||||
)
|
||||
''')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -45,9 +57,9 @@ def health():
|
||||
|
||||
@app.route('/api/snippets', methods=['GET'])
|
||||
def get_snippets():
|
||||
|
||||
try:
|
||||
conn = get_db()
|
||||
preview_params_json =
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT * FROM snippets ORDER BY updatedAt DESC')
|
||||
rows = cursor.fetchall()
|
||||
conn.close()
|
||||
@@ -66,74 +78,72 @@ def get_snippets():
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/snippets/<snippet_id>', 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/<snippet_id>', methods=['DELETE
|
||||
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', {}))
|
||||
|
||||
return jsonify
|
||||
return jsonify({'success': True})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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/<snippet_id>', 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 = ?
|
||||
@@ -178,4 +188,4 @@ def delete_snippet(snippet_id):
|
||||
|
||||
if __name__ == '__main__':
|
||||
init_db()
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
app.run(host='0.0.0.0', port=5000, debug=False)
|
||||
|
||||
4
backend/captain-definition
Normal file
4
backend/captain-definition
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"dockerfilePath": "./Dockerfile"
|
||||
}
|
||||
4
captain-definition
Normal file
4
captain-definition
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"dockerfilePath": "./Dockerfile"
|
||||
}
|
||||
35
docker-compose.production.yml
Normal file
35
docker-compose.production.yml
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
10
nginx.conf
10
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;
|
||||
}
|
||||
}
|
||||
|
||||
138
test-cors.sh
Normal file
138
test-cors.sh
Normal file
@@ -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 "======================================"
|
||||
Reference in New Issue
Block a user