diff --git a/.gitignore b/.gitignore index 7f0db23..d91f288 100644 --- a/.gitignore +++ b/.gitignore @@ -174,4 +174,15 @@ htmlcov/ coverage.xml # Test output -.testmondata \ No newline at end of file +.testmondata + +# SSL Certificates (generated by mkcert) +nginx/ssl/*.pem +nginx/ssl/*.key +nginx/ssl/*.crt + +# Docker Compose overrides (do not ignore auto files, only manually generated) +# docker-compose.https.yml is now tracked + +# Environment backups +.env.backup \ No newline at end of file diff --git a/AUTOMATIC_HTTPS_SUMMARY.md b/AUTOMATIC_HTTPS_SUMMARY.md new file mode 100644 index 0000000..721e2e9 --- /dev/null +++ b/AUTOMATIC_HTTPS_SUMMARY.md @@ -0,0 +1,514 @@ +# Automatic HTTPS Implementation - Complete Summary + +## 🎯 Mission Accomplished + +HTTPS is now **fully automatic** at container startup! No manual steps required. + +--- + +## πŸš€ How to Use (The Easy Way) + +### One-Command Startup + +**Windows:** +```cmd +start-https.bat +``` + +**Linux/Mac:** +```bash +bash start-https.sh +``` + +**That's it!** Everything else happens automatically: +1. βœ… Certificates generated (if needed) +2. βœ… nginx configured +3. βœ… Security settings enabled +4. βœ… All services started with HTTPS + +--- + +## πŸ—οΈ Implementation Architecture + +### Automatic Certificate Generation + +**Two deployment modes:** + +#### Mode 1: Self-Signed Certificates (Default) +```yaml +docker-compose -f docker-compose.yml -f docker-compose.https-auto.yml up -d +``` + +**Flow:** +``` +1. certgen init container starts + β”œβ”€ Checks if nginx/ssl/cert.pem exists + β”œβ”€ If missing: generates self-signed certificate + β”‚ β”œβ”€ Uses OpenSSL + β”‚ β”œβ”€ Valid for 10 years + β”‚ └─ Includes localhost + detected IP + └─ Exits successfully + +2. nginx starts (depends on certgen completion) + β”œβ”€ Uses certificates from nginx/ssl/ + β”œβ”€ Listens on ports 80 (redirect) and 443 (HTTPS) + └─ Proxies to app:8080 + +3. app starts with secure settings + β”œβ”€ WTF_CSRF_SSL_STRICT=true + β”œβ”€ SESSION_COOKIE_SECURE=true + └─ CSRF_COOKIE_SECURE=true +``` + +#### Mode 2: mkcert Trusted Certificates +```yaml +docker-compose -f docker-compose.yml -f docker-compose.https-mkcert.yml up -d +``` + +**Flow:** +``` +1. mkcert init container starts + β”œβ”€ Has mkcert pre-installed + β”œβ”€ Checks if nginx/ssl/cert.pem exists + β”œβ”€ If missing: + β”‚ β”œβ”€ Installs local CA + β”‚ β”œβ”€ Generates trusted certificate + β”‚ β”œβ”€ Copies rootCA.pem for host installation + β”‚ └─ Valid for 10 years + └─ Exits successfully + +2. nginx starts (same as Mode 1) + +3. app starts (same as Mode 1) +``` + +--- + +## πŸ“ Files Created + +### Core Scripts +| File | Purpose | +|------|---------| +| `start-https.sh` | Automatic startup for Linux/Mac | +| `start-https.bat` | Automatic startup for Windows | +| `setup-https-mkcert.sh` | Manual mkcert setup (legacy) | +| `setup-https-mkcert.bat` | Manual mkcert setup (legacy) | + +### Docker Configurations +| File | Purpose | +|------|---------| +| `docker-compose.https-auto.yml` | Self-signed certificates (automatic) | +| `docker-compose.https-mkcert.yml` | mkcert certificates (automatic) | +| `docker/Dockerfile.mkcert` | mkcert image builder | + +### Certificate Generation +| File | Purpose | +|------|---------| +| `scripts/generate-certs.sh` | Self-signed cert generator | +| `docker/generate-mkcert-certs.sh` | mkcert cert generator | + +### Documentation +| File | Purpose | +|------|---------| +| `README_HTTPS_AUTO.md` | Automatic HTTPS guide | +| `README_HTTPS.md` | Manual HTTPS guide | +| `HTTPS_MKCERT_GUIDE.md` | Detailed mkcert documentation | + +--- + +## πŸ”§ Technical Details + +### Init Container Pattern + +Using Docker's init container pattern for certificate generation: + +```yaml +services: + certgen: + image: alpine:latest + volumes: + - ./nginx/ssl:/certs + command: sh /scripts/generate-certs.sh + restart: "no" # Runs once + + nginx: + depends_on: + certgen: + condition: service_completed_successfully # Waits for certgen + # ... rest of config +``` + +**Benefits:** +- βœ… Idempotent (safe to run multiple times) +- βœ… No certificates needed in repo +- βœ… Auto-generates on first run +- βœ… Reuses existing certificates +- βœ… No manual intervention + +### Certificate Persistence + +Certificates stored in `nginx/ssl/`: +``` +nginx/ssl/ +β”œβ”€β”€ cert.pem # Public certificate +β”œβ”€β”€ key.pem # Private key +└── rootCA.pem # CA cert (mkcert only) +``` + +**Lifecycle:** +1. First run: Generated by init container +2. Subsequent runs: Reused (init container detects and skips) +3. Persist across container restarts +4. Valid for 10 years + +### Security Configuration + +Automatically applied via docker-compose: +```yaml +app: + environment: + - WTF_CSRF_SSL_STRICT=true # Strict CSRF over HTTPS + - SESSION_COOKIE_SECURE=true # Cookies only over HTTPS + - CSRF_COOKIE_SECURE=true # CSRF cookies only over HTTPS +``` + +**Also updates `.env` file:** +```bash +# Automatically added/updated by start-https script +WTF_CSRF_SSL_STRICT=true +SESSION_COOKIE_SECURE=true +CSRF_COOKIE_SECURE=true +``` + +--- + +## πŸ” Certificate Types Comparison + +| Feature | Self-Signed | mkcert | +|---------|-------------|--------| +| **Setup** | Zero config | One-time CA install | +| **Browser Warning** | Yes (safe to bypass) | No warnings βœ… | +| **Encryption** | Full TLS 1.2/1.3 | Full TLS 1.2/1.3 | +| **Valid For** | 10 years | 10 years | +| **Trust** | Only you | All browsers (after CA install) | +| **Best For** | Quick testing | Regular development | +| **External Devices** | Need CA install + bypass | Need CA install only | + +--- + +## πŸ“Š Decision Flow + +``` +User runs: start-https.sh/bat + β”‚ + ↓ + Detect local IP + β”‚ + ↓ + Create nginx config (if missing) + β”‚ + ↓ + Update .env with HTTPS settings + β”‚ + ↓ + Ask: Which certificate type? + β”‚ + β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β” + ↓ ↓ + Self-Signed mkcert + β”‚ β”‚ + β”‚ (Check if mkcert installed) + β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β” + β”‚ ↓ ↓ + β”‚ Found Not Found + β”‚ β”‚ β”‚ + β”‚ β”‚ Fall back to + β”‚ β”‚ self-signed + ↓ ↓ ↓ + docker-compose.https-auto.yml + β”‚ + ↓ + certgen/mkcert init container + β”‚ + ↓ + Generate certificates (if missing) + β”‚ + ↓ + nginx + app start with HTTPS + β”‚ + ↓ + Access: https://localhost +``` + +--- + +## 🎯 Usage Scenarios + +### Scenario 1: First-Time Developer +```bash +# Clone repo +git clone ... +cd TimeTracker + +# Create config +cp env.example .env + +# Start with HTTPS (automatic!) +bash start-https.sh + +# Choose option 1 (self-signed) +# Access: https://localhost +# Click through browser warning once +# Done! βœ… +``` + +### Scenario 2: Regular Development +```bash +# Install mkcert once +brew install mkcert # or choco install mkcert + +# Start with HTTPS +bash start-https.sh + +# Choose option 2 (mkcert) +# Install CA: nginx/ssl/rootCA.pem (one-time) +# Restart browser +# Access: https://localhost +# No warnings ever! βœ… +``` + +### Scenario 3: Production-Like Testing +```bash +# Use automatic HTTPS with mkcert +bash start-https.sh + +# Option 2 (mkcert) +# All security settings: strict mode +# Test with real HTTPS behavior +# Perfect for pre-production testing βœ… +``` + +### Scenario 4: Network Access (LAN) +```bash +# Start with automatic HTTPS +bash start-https.sh + +# Detects IP: 192.168.1.100 +# Access from any device: https://192.168.1.100 +# With mkcert: install CA once, no warnings +# With self-signed: bypass warning once per device +# All devices can access! βœ… +``` + +--- + +## πŸ”„ Certificate Lifecycle + +### First Run +```bash +start-https.sh + ↓ +certgen checks: nginx/ssl/cert.pem missing + ↓ +Generates new certificate + ↓ +Saves to nginx/ssl/ + ↓ +nginx uses new certificate + ↓ +App starts with HTTPS βœ… +``` + +### Subsequent Runs +```bash +start-https.sh + ↓ +certgen checks: nginx/ssl/cert.pem exists + ↓ +"Certificates already exist, skipping" + ↓ +Exits immediately + ↓ +nginx uses existing certificate + ↓ +App starts with HTTPS βœ… +``` + +### Regeneration (if needed) +```bash +# Delete certificates +rm -rf nginx/ssl/* + +# Restart +bash start-https.sh + ↓ +certgen detects missing certificates + ↓ +Generates fresh certificates + ↓ +nginx uses new certificates βœ… +``` + +--- + +## πŸ› οΈ Troubleshooting + +### Issue: nginx Won't Start + +**Check init container logs:** +```bash +docker-compose logs certgen +# or +docker-compose logs mkcert +``` + +**Verify certificates exist:** +```bash +ls -la nginx/ssl/ +# Should show: cert.pem, key.pem +``` + +### Issue: Browser Shows Warning (with mkcert) + +**CA not installed:** +1. Check `nginx/ssl/rootCA.pem` exists +2. Install it (double-click on Windows/Mac) +3. Restart browser completely + +**Wrong certificates:** +```bash +# Regenerate +rm -rf nginx/ssl/* +bash start-https.sh +``` + +### Issue: Port 443 in Use + +**Find conflicting service:** +```bash +# Windows +netstat -ano | findstr :443 + +# Linux/Mac +lsof -i :443 +``` + +**Stop it or change nginx port** + +--- + +## πŸ“ˆ Benefits Achieved + +### For Users +βœ… **Zero manual configuration** +βœ… **One command to HTTPS** +βœ… **Choice of certificate types** +βœ… **Automatic security hardening** +βœ… **Works with IP addresses** +βœ… **No CSRF cookie issues** + +### For Developers +βœ… **Clean development experience** +βœ… **Production-like HTTPS testing** +βœ… **No certificate management** +βœ… **Git-friendly (certs not committed)** +βœ… **Reproducible across environments** + +### For Operations +βœ… **Idempotent deployment** +βœ… **Container-native approach** +βœ… **Minimal dependencies** +βœ… **Self-contained solution** +βœ… **Easy troubleshooting** + +--- + +## πŸ”— Related Fixes + +This implementation also solves: + +1. **CSRF Cookie Issues** - Strict HTTPS mode fixes IP access problems +2. **Security Headers** - Automatically applied +3. **Cookie Security** - Secure flags enabled +4. **Mixed Content** - All traffic over HTTPS +5. **WebSocket Support** - Upgrade headers configured + +--- + +## πŸ“š Documentation Map + +``` +AUTOMATIC_HTTPS_SUMMARY.md (this file) + β”‚ + β”œβ”€ Quick Start + β”‚ └─ README_HTTPS_AUTO.md + β”‚ + β”œβ”€ Manual Setup (Legacy) + β”‚ β”œβ”€ README_HTTPS.md + β”‚ └─ HTTPS_MKCERT_GUIDE.md + β”‚ + β”œβ”€ CSRF Issues + β”‚ β”œβ”€ CSRF_IP_ACCESS_FIX.md + β”‚ β”œβ”€ CSRF_IP_FIX_SUMMARY.md + β”‚ └─ docs/CSRF_IP_ACCESS_GUIDE.md + β”‚ + └─ Advanced + └─ docs/HTTPS_SETUP_GUIDE.md +``` + +--- + +## πŸŽ‰ Summary + +### What We Built + +**Fully Automatic HTTPS System:** +- πŸš€ One-command startup +- πŸ” Auto-generated certificates +- πŸ”§ Self-configuring nginx +- βš™οΈ Auto-hardened security settings +- πŸ”„ Persistent across restarts +- πŸ“± Works with any device +- βœ… Zero manual intervention + +### Quick Commands + +```bash +# Start everything with HTTPS (automatic!) +bash start-https.sh # Linux/Mac +start-https.bat # Windows + +# Access securely +https://localhost +https://192.168.1.100 + +# View logs +docker-compose logs -f + +# Stop +docker-compose down +``` + +### The Result + +**Before:** +- ❌ Manual certificate generation required +- ❌ Complex nginx configuration +- ❌ Manual security settings +- ❌ CSRF issues with IP addresses +- ❌ Multiple scripts to run + +**After:** +- βœ… One command: `bash start-https.sh` +- βœ… Everything automatic +- βœ… HTTPS just works +- βœ… No CSRF issues +- βœ… Production-ready security + +--- + +**Implementation Complete! 🎊** + +**Enjoy your automatic HTTPS TimeTracker! πŸ”’πŸš€** + diff --git a/CSRF_IP_ACCESS_FIX.md b/CSRF_IP_ACCESS_FIX.md new file mode 100644 index 0000000..167d646 --- /dev/null +++ b/CSRF_IP_ACCESS_FIX.md @@ -0,0 +1,216 @@ +# CSRF Cookie Fix for Remote IP Access + +## Problem Summary + +βœ… **Works:** Accessing via `http://localhost:8080` - CSRF cookies created correctly +❌ **Fails:** Accessing via `http://192.168.1.100:8080` - CSRF cookies NOT created + +## Root Cause + +The `WTF_CSRF_SSL_STRICT=true` setting (default) blocks cookie creation for HTTP connections to non-localhost addresses. This is a security feature that prevents CSRF tokens from being sent over insecure connections. + +## Quick Fix + +### Option 1: Automated Script (Recommended) + +**Linux/Mac:** +```bash +bash scripts/fix_csrf_ip_access.sh +``` + +**Windows:** +```cmd +scripts\fix_csrf_ip_access.bat +``` + +The script will: +1. Update your `.env` file with correct settings +2. Restart the application +3. Verify the configuration + +### Option 2: Manual Configuration + +Edit your `.env` file and add/update: + +```bash +WTF_CSRF_SSL_STRICT=false +SESSION_COOKIE_SECURE=false +CSRF_COOKIE_SECURE=false +``` + +Then restart: +```bash +docker-compose restart app +``` + +## What These Settings Do + +| Setting | Value | Purpose | +|---------|-------|---------| +| `WTF_CSRF_SSL_STRICT` | `false` | Allows CSRF tokens over HTTP (needed for IP access) | +| `SESSION_COOKIE_SECURE` | `false` | Allows session cookies over HTTP | +| `CSRF_COOKIE_SECURE` | `false` | Allows CSRF cookies over HTTP | + +## Verification + +### 1. Check Environment Variables +```bash +docker-compose exec app env | grep -E "(WTF_CSRF|SESSION_COOKIE|CSRF_COOKIE)" +``` + +Expected output: +``` +WTF_CSRF_SSL_STRICT=false +SESSION_COOKIE_SECURE=false +CSRF_COOKIE_SECURE=false +``` + +### 2. Test Cookie Creation + +1. Open your browser +2. Navigate to `http://YOUR_IP:8080` +3. Open DevTools (F12) +4. Go to **Application** β†’ **Cookies** +5. Verify these cookies exist: + - `session` - Your session cookie + - `XSRF-TOKEN` - The CSRF token + +### 3. Test CSRF Endpoint + +```bash +# Via localhost (should work) +curl -v http://localhost:8080/auth/csrf-token + +# Via IP (should now also work) +curl -v http://192.168.1.100:8080/auth/csrf-token +``` + +Look for `Set-Cookie` headers in both responses. + +## Security Considerations + +### ⚠️ Important Security Notes + +**These settings are suitable for:** +- βœ… Development environments +- βœ… Testing on local networks +- βœ… Private/trusted networks (VPN, home network) + +**NOT suitable for:** +- ❌ Public internet access without HTTPS +- ❌ Production environments with sensitive data +- ❌ Untrusted networks + +### Production Configuration + +For production deployments, always use HTTPS and set: + +```bash +WTF_CSRF_SSL_STRICT=true +SESSION_COOKIE_SECURE=true +CSRF_COOKIE_SECURE=true +``` + +## Alternative Solutions + +### Solution 1: Use a Domain Name + +Add to your hosts file instead of using IP: + +**Linux/Mac** (`/etc/hosts`): +``` +192.168.1.100 timetracker.local +``` + +**Windows** (`C:\Windows\System32\drivers\etc\hosts`): +``` +192.168.1.100 timetracker.local +``` + +Then access via: `http://timetracker.local:8080` + +### Solution 2: Set Up HTTPS + +For production-like testing with HTTPS: + +1. Generate self-signed certificate: +```bash +openssl req -x509 -newkey rsa:4096 -nodes \ + -keyout key.pem -out cert.pem -days 365 \ + -subj "/CN=192.168.1.100" +``` + +2. Update docker-compose to use HTTPS +3. Set all security flags to `true` + +## Troubleshooting + +### Still not working? + +1. **Verify settings are loaded:** + ```bash + docker-compose exec app env | grep WTF_CSRF_SSL_STRICT + ``` + +2. **Check logs:** + ```bash + docker-compose logs app | grep -i csrf + ``` + +3. **Try a fresh restart:** + ```bash + docker-compose down + docker-compose up -d + ``` + +4. **Clear browser cookies:** + - DevTools β†’ Application β†’ Cookies β†’ Delete all for this site + +5. **Test in incognito/private window:** + - Rules out browser extension issues + +### Different browsers behave differently? + +- Chrome/Edge: Usually most permissive +- Firefox: Stricter cookie policies +- Safari: Strictest, especially with tracking prevention + +Try disabling enhanced tracking protection or privacy features temporarily for testing. + +## Related Documentation + +- **Detailed Guide:** [docs/CSRF_IP_ACCESS_GUIDE.md](docs/CSRF_IP_ACCESS_GUIDE.md) +- **General CSRF Troubleshooting:** [CSRF_TROUBLESHOOTING.md](CSRF_TROUBLESHOOTING.md) +- **CSRF Configuration:** [docs/CSRF_CONFIGURATION.md](docs/CSRF_CONFIGURATION.md) + +## Summary + +**The Fix:** Set `WTF_CSRF_SSL_STRICT=false` for HTTP access via IP addresses. + +**Why It Works:** This allows Flask-WTF to create and validate CSRF cookies over HTTP connections to non-localhost addresses. + +**When to Use:** Development, testing, and trusted private networks only. Always use HTTPS with strict settings in production. + +--- + +**Quick Command Reference:** + +```bash +# Apply fix (automated) +bash scripts/fix_csrf_ip_access.sh + +# Verify configuration +docker-compose exec app env | grep -E "WTF_CSRF|SESSION_COOKIE|CSRF_COOKIE" + +# Restart application +docker-compose restart app + +# Check logs +docker-compose logs app | tail -50 +``` + +--- + +**Last Updated:** October 2024 +**Applies To:** TimeTracker v1.0+ + diff --git a/CSRF_IP_FIX_SUMMARY.md b/CSRF_IP_FIX_SUMMARY.md new file mode 100644 index 0000000..3b98028 --- /dev/null +++ b/CSRF_IP_FIX_SUMMARY.md @@ -0,0 +1,307 @@ +# CSRF IP Access Fix - Implementation Summary + +## Problem Solved + +**Issue:** CSRF cookie (`XSRF-TOKEN`) is created correctly when accessing via `localhost` but NOT created when accessing via remote IP address (e.g., `http://192.168.1.100:8080`). + +**Root Cause:** The `WTF_CSRF_SSL_STRICT=true` (default) setting blocks cookie creation for HTTP connections to non-localhost addresses as a security measure. + +## Solution Implemented + +### Quick Fix for Users + +We've provided an automated script that configures the application correctly for IP address access: + +**Linux/Mac:** +```bash +bash scripts/fix_csrf_ip_access.sh +``` + +**Windows:** +```cmd +scripts\fix_csrf_ip_access.bat +``` + +The script sets: +- `WTF_CSRF_SSL_STRICT=false` β€” Allows CSRF tokens over HTTP +- `SESSION_COOKIE_SECURE=false` β€” Allows session cookies over HTTP +- `CSRF_COOKIE_SECURE=false` β€” Allows CSRF cookies over HTTP + +## Files Modified + +### 1. Configuration Files + +#### `env.example` +- Added `WTF_CSRF_SSL_STRICT=false` setting with documentation +- Added detailed comments about CSRF cookie settings +- Added specific guidance for IP address access +- Updated troubleshooting tips + +#### `docker-compose.yml` +- Added `WTF_CSRF_SSL_STRICT` environment variable with default `false` +- Added `SESSION_COOKIE_SECURE` environment variable with default `false` +- Enhanced documentation about CSRF configuration +- Added reference to new troubleshooting guides + +### 2. Documentation Created + +#### `docs/CSRF_IP_ACCESS_GUIDE.md` (NEW) +Comprehensive guide covering: +- Problem description and root cause +- Quick fix instructions +- Detailed explanation of each setting +- Testing procedures +- Security considerations +- Alternative solutions (domain names, HTTPS) +- Troubleshooting steps +- Configuration examples for different environments + +#### `CSRF_IP_ACCESS_FIX.md` (NEW) +Quick reference guide with: +- Problem summary +- One-command fixes +- Verification steps +- Security notes +- Alternative solutions +- Troubleshooting checklist + +#### `CSRF_IP_FIX_SUMMARY.md` (THIS FILE) +Implementation summary documenting all changes + +### 3. Existing Documentation Updated + +#### `CSRF_TROUBLESHOOTING.md` +- Added new section #8: "Accessing via IP Address (Not Localhost)" +- Updated related documentation links +- Added reference to the new IP access guide + +#### `README.md` +- Added link to `CSRF_IP_ACCESS_FIX.md` in troubleshooting section +- Highlighted with πŸ”₯ emoji for visibility + +### 4. Automation Scripts Created + +#### `scripts/fix_csrf_ip_access.sh` (NEW) +Bash script for Linux/Mac that: +- Checks for `.env` file, creates if missing +- Shows current CSRF configuration +- Updates `.env` with correct settings +- Restarts the Docker container +- Provides verification steps + +#### `scripts/fix_csrf_ip_access.bat` (NEW) +Windows batch script that: +- Uses PowerShell for `.env` file manipulation +- Same functionality as bash version +- Windows-friendly output and instructions + +## Configuration Details + +### Development/Testing (HTTP, IP Access) + +```bash +WTF_CSRF_SSL_STRICT=false +SESSION_COOKIE_SECURE=false +CSRF_COOKIE_SECURE=false +SESSION_COOKIE_SAMESITE=Lax +CSRF_COOKIE_SAMESITE=Lax +``` + +### Production (HTTPS, Domain) + +```bash +WTF_CSRF_SSL_STRICT=true +SESSION_COOKIE_SECURE=true +CSRF_COOKIE_SECURE=true +SESSION_COOKIE_SAMESITE=Strict +CSRF_COOKIE_SAMESITE=Strict +``` + +## How It Works + +### Before the Fix + +1. User accesses `http://192.168.1.100:8080` +2. Flask-WTF checks `WTF_CSRF_SSL_STRICT` setting (default: `true`) +3. Since request is HTTP to non-localhost, Flask-WTF blocks cookie creation +4. CSRF cookie is NOT set in browser +5. Form submissions fail with "CSRF token missing or invalid" + +### After the Fix + +1. User accesses `http://192.168.1.100:8080` +2. Flask-WTF checks `WTF_CSRF_SSL_STRICT` setting (now: `false`) +3. Flask-WTF allows cookie creation over HTTP +4. CSRF cookie (`XSRF-TOKEN`) is set in browser +5. Form submissions work correctly βœ… + +## Security Considerations + +### Safe For: +- βœ… Development environments +- βœ… Testing on local networks +- βœ… Private/trusted networks (VPN, home network) +- βœ… Isolated lab environments + +### NOT Safe For: +- ❌ Public internet without HTTPS +- ❌ Production with sensitive data over HTTP +- ❌ Untrusted networks +- ❌ Public-facing applications + +### Recommendations: + +1. **Development:** Use the provided settings (`WTF_CSRF_SSL_STRICT=false`) +2. **Production:** Always use HTTPS with strict settings (`WTF_CSRF_SSL_STRICT=true`) +3. **Network Security:** If using HTTP, ensure network is trusted +4. **Migration:** When moving to production, update all security settings + +## Testing the Fix + +### 1. Apply the Fix +```bash +bash scripts/fix_csrf_ip_access.sh # or .bat on Windows +``` + +### 2. Verify Environment +```bash +docker-compose exec app env | grep -E "WTF_CSRF|SESSION_COOKIE|CSRF_COOKIE" +``` + +### 3. Check Cookies in Browser +1. Open DevTools (F12) +2. Navigate to `http://YOUR_IP:8080` +3. Go to Application β†’ Cookies +4. Verify `session` and `XSRF-TOKEN` cookies exist + +### 4. Test CSRF Endpoint +```bash +curl -v http://YOUR_IP:8080/auth/csrf-token +``` + +Look for `Set-Cookie` headers in response. + +### 5. Test Form Submission +1. Try logging in via IP address +2. Submit any form (create project, log time, etc.) +3. Should work without CSRF errors βœ… + +## Alternative Solutions + +### Option 1: Use Domain Name Instead of IP +``` +# Add to hosts file +192.168.1.100 timetracker.local + +# Access via domain +http://timetracker.local:8080 +``` + +### Option 2: Enable HTTPS with Self-Signed Certificate +```bash +# Generate certificate +openssl req -x509 -newkey rsa:4096 -nodes \ + -keyout key.pem -out cert.pem -days 365 \ + -subj "/CN=192.168.1.100" + +# Configure nginx/docker for HTTPS +# Set all security flags to true +``` + +### Option 3: Disable CSRF (Development Only) +```bash +WTF_CSRF_ENABLED=false # ⚠️ ONLY for isolated development! +``` + +## Rollback Instructions + +If you need to revert to strict security settings: + +1. Edit `.env`: +```bash +WTF_CSRF_SSL_STRICT=true +SESSION_COOKIE_SECURE=true +CSRF_COOKIE_SECURE=true +``` + +2. Restart: +```bash +docker-compose restart app +``` + +Note: This will prevent IP access over HTTP again. + +## User Experience Impact + +### Before Fix: +1. ❌ Users accessing via IP see CSRF errors on all forms +2. ❌ Login fails with "CSRF token missing or invalid" +3. ❌ No clear error message about IP/localhost difference +4. ❌ Users confused why localhost works but IP doesn't + +### After Fix: +1. βœ… Automated script makes fix one-click easy +2. βœ… Clear documentation explains the issue +3. βœ… Multiple solutions provided (scripts, manual, alternatives) +4. βœ… Users understand security implications +5. βœ… Separate configs for dev/prod clearly documented + +## Integration with Existing Documentation + +This fix integrates with: +- `CSRF_TROUBLESHOOTING.md` β€” Main troubleshooting guide +- `docs/CSRF_CONFIGURATION.md` β€” Detailed CSRF configuration +- `docs/DOCKER_PUBLIC_SETUP.md` β€” Docker setup guide +- `env.example` β€” Configuration template + +## Future Improvements + +Potential enhancements: +1. Auto-detection of access method (localhost vs IP) +2. Configuration wizard for first-time setup +3. Web UI warning when accessing via IP with strict settings +4. Automated HTTPS setup script with Let's Encrypt +5. Environment-specific docker-compose files (dev/prod) + +## Summary + +This fix provides: +- βœ… **Immediate solution** via automated scripts +- βœ… **Clear documentation** explaining the problem and fix +- βœ… **Security guidance** for different environments +- βœ… **Multiple approaches** (automated, manual, alternatives) +- βœ… **Comprehensive testing** procedures +- βœ… **User-friendly** implementation + +**Result:** Users can now access the application via IP address without CSRF cookie issues, while understanding the security implications and having clear paths for both development and production configurations. + +--- + +## Files Changed Summary + +### New Files Created (7): +1. `docs/CSRF_IP_ACCESS_GUIDE.md` β€” Comprehensive guide +2. `CSRF_IP_ACCESS_FIX.md` β€” Quick reference +3. `scripts/fix_csrf_ip_access.sh` β€” Linux/Mac automation +4. `scripts/fix_csrf_ip_access.bat` β€” Windows automation +5. `CSRF_IP_FIX_SUMMARY.md` β€” This summary + +### Files Modified (4): +1. `env.example` β€” Added CSRF IP settings +2. `docker-compose.yml` β€” Added environment variables +3. `CSRF_TROUBLESHOOTING.md` β€” Added IP access section +4. `README.md` β€” Added link to fix guide + +### Total Impact: +- **11 files** changed/created +- **5 documentation** files +- **2 automation** scripts +- **4 configuration** files + +--- + +**Implementation Date:** October 2024 +**Tested On:** TimeTracker v1.0+ +**Issue:** CSRF cookies not created when accessing via IP address +**Status:** βœ… Resolved + diff --git a/CSRF_TROUBLESHOOTING.md b/CSRF_TROUBLESHOOTING.md index 9f46711..d59d3ca 100644 --- a/CSRF_TROUBLESHOOTING.md +++ b/CSRF_TROUBLESHOOTING.md @@ -161,7 +161,34 @@ docker-compose restart app --- -#### βœ… 8. Development/Testing: Just Disable CSRF +#### βœ… 8. Accessing via IP Address (Not Localhost) +**Symptom:** Works on localhost but CSRF cookie not created when accessing via IP (e.g., 192.168.1.100) + +**Cause:** `WTF_CSRF_SSL_STRICT=true` blocks cookies on HTTP connections to non-localhost addresses + +**Check:** +```bash +docker-compose exec app env | grep WTF_CSRF_SSL_STRICT +``` + +**Solution:** +```bash +# In .env file +WTF_CSRF_SSL_STRICT=false +SESSION_COOKIE_SECURE=false +CSRF_COOKIE_SECURE=false + +# Restart +docker-compose restart app +``` + +**Note:** Only use these settings for development/testing on trusted networks. Production should use HTTPS with strict settings. + +**See:** `docs/CSRF_IP_ACCESS_GUIDE.md` for detailed explanation + +--- + +#### βœ… 9. Development/Testing: Just Disable CSRF **⚠️ WARNING: Only for local development/testing!** ```bash @@ -290,6 +317,7 @@ docker-compose up -d ## πŸ”— Related Documentation - [Detailed CSRF Configuration Guide](docs/CSRF_CONFIGURATION.md) +- [CSRF IP Access Guide](docs/CSRF_IP_ACCESS_GUIDE.md) - **For localhost vs IP address issues** - [Original CSRF Implementation](CSRF_TOKEN_FIX_SUMMARY.md) - [Docker Setup Guide](docs/DOCKER_PUBLIC_SETUP.md) - [Troubleshooting Guide](docs/DOCKER_STARTUP_TROUBLESHOOTING.md) diff --git a/HTTPS_MKCERT_GUIDE.md b/HTTPS_MKCERT_GUIDE.md new file mode 100644 index 0000000..686c596 --- /dev/null +++ b/HTTPS_MKCERT_GUIDE.md @@ -0,0 +1,483 @@ +# HTTPS Setup with mkcert - Complete Guide + +## 🎯 What is mkcert? + +mkcert is a simple tool for making locally-trusted development certificates. It requires **no configuration** and creates certificates that work with: +- βœ… localhost +- βœ… IP addresses (192.168.1.100) +- βœ… Custom domains (timetracker.local) +- βœ… **NO browser warnings!** + +Perfect for development and local network deployment. + +--- + +## πŸ“¦ Installation + +### Windows + +**Option 1: Chocolatey** +```powershell +choco install mkcert +``` + +**Option 2: Scoop** +```powershell +scoop bucket add extras +scoop install mkcert +``` + +### macOS + +```bash +brew install mkcert +``` + +### Linux + +**Ubuntu/Debian:** +```bash +sudo apt install libnss3-tools +curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" +chmod +x mkcert-v*-linux-amd64 +sudo mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert +``` + +**Arch Linux:** +```bash +sudo pacman -S mkcert +``` + +--- + +## πŸš€ Quick Setup + +### Automated Setup (Recommended) + +**Windows:** +```cmd +setup-https-mkcert.bat +``` + +**Linux/Mac:** +```bash +bash setup-https-mkcert.sh +``` + +This will: +1. Install local Certificate Authority (CA) +2. Generate certificates for localhost + your IP +3. Create nginx configuration +4. Create docker-compose.https.yml +5. Update .env with secure settings + +### Start with HTTPS + +```bash +docker-compose -f docker-compose.yml -f docker-compose.https.yml up -d +``` + +### Access Your Application + +``` +https://localhost +https://192.168.1.100 (your IP) +https://timetracker.local +``` + +**No certificate warnings! πŸŽ‰** + +--- + +## πŸ”§ Manual Setup + +If you prefer to do it manually: + +### Step 1: Install CA + +```bash +mkcert -install +``` + +This installs a local Certificate Authority on your system that browsers will trust. + +### Step 2: Generate Certificates + +```bash +# Create directories +mkdir -p nginx/ssl + +# Generate certs (replace 192.168.1.100 with your IP) +mkcert -key-file nginx/ssl/key.pem \ + -cert-file nginx/ssl/cert.pem \ + localhost 127.0.0.1 ::1 192.168.1.100 *.local +``` + +**Windows PowerShell:** +```powershell +mkdir nginx\ssl -Force +mkcert -key-file nginx\ssl\key.pem -cert-file nginx\ssl\cert.pem localhost 127.0.0.1 ::1 192.168.1.100 *.local +``` + +### Step 3: Create nginx Configuration + +Create `nginx/conf.d/https.conf`: + +```nginx +server { + listen 80; + server_name _; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + server_name _; + + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "DENY" always; + add_header X-Content-Type-Options "nosniff" always; + + location / { + proxy_pass http://app:8080; + 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_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} +``` + +### Step 4: Create docker-compose.https.yml + +```yaml +services: + nginx: + image: nginx:alpine + container_name: timetracker-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/conf.d:/etc/nginx/conf.d:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + depends_on: + - app + restart: unless-stopped + + app: + ports: [] # nginx handles ports + environment: + - WTF_CSRF_SSL_STRICT=true + - SESSION_COOKIE_SECURE=true + - CSRF_COOKIE_SECURE=true +``` + +### Step 5: Update .env + +```bash +WTF_CSRF_SSL_STRICT=true +SESSION_COOKIE_SECURE=true +CSRF_COOKIE_SECURE=true +``` + +### Step 6: Start Services + +```bash +docker-compose -f docker-compose.yml -f docker-compose.https.yml up -d +``` + +--- + +## 🌐 Access from Other Devices + +To access from phones, tablets, or other computers on your network **without certificate warnings**: + +### Step 1: Find Your CA Location + +```bash +mkcert -CAROOT +``` + +This shows the directory containing `rootCA.pem` + +**Example output:** +``` +/Users/username/Library/Application Support/mkcert +C:\Users\username\AppData\Local\mkcert +``` + +### Step 2: Copy rootCA.pem to Device + +Transfer the `rootCA.pem` file to your device via: +- USB drive +- Network share +- Email +- Cloud storage + +### Step 3: Install CA on Device + +**iOS/iPadOS:** +1. Open `rootCA.pem` file +2. Install profile +3. Settings β†’ General β†’ About β†’ Certificate Trust Settings +4. Enable the certificate + +**Android:** +1. Settings β†’ Security β†’ Encryption & credentials +2. Install certificate from storage +3. Select `rootCA.pem` + +**Windows:** +1. Double-click `rootCA.pem` +2. Install Certificate +3. Store: Local Machine β†’ Trusted Root Certification Authorities + +**macOS:** +1. Double-click `rootCA.pem` +2. Add to Keychain +3. Trust for SSL + +**Linux:** +```bash +sudo cp rootCA.pem /usr/local/share/ca-certificates/mkcert-rootCA.crt +sudo update-ca-certificates +``` + +### Step 4: Access from Device + +``` +https://192.168.1.100 +``` + +βœ… **No certificate warning!** + +--- + +## πŸ” Verification + +### Check Certificate in Browser + +1. Navigate to `https://localhost` +2. Click the padlock icon +3. View certificate details +4. Should show: + - βœ… Issued by: mkcert + - βœ… Valid for: localhost, 127.0.0.1, your IP + - βœ… No warnings + +### Test HTTPS Redirect + +```bash +# Should redirect to HTTPS +curl -I http://localhost + +# Should show 301 redirect +``` + +### Verify nginx + +```bash +# Check nginx is running +docker-compose ps nginx + +# Check nginx logs +docker-compose logs nginx +``` + +### Test Application + +1. Access via HTTPS +2. Open DevTools (F12) β†’ Application β†’ Cookies +3. Verify cookies have `Secure` flag +4. Test form submissions (login, create project) +5. Should work without CSRF errors + +--- + +## πŸ› οΈ Troubleshooting + +### Certificate Warning Still Appears + +**Cause:** CA not installed or browser not restarted + +**Solution:** +```bash +# Reinstall CA +mkcert -install + +# Restart browser completely (close all windows) + +# Clear browser cache and cookies +``` + +### "NET::ERR_CERT_AUTHORITY_INVALID" + +**Cause:** Browser doesn't trust the CA + +**Solution:** +1. Check CA is installed: `mkcert -CAROOT` +2. Reinstall: `mkcert -install` +3. On Linux, may need `libnss3-tools` +4. Restart browser + +### nginx Won't Start + +**Cause:** Port 80 or 443 already in use + +**Check:** +```bash +# Windows +netstat -ano | findstr :443 + +# Linux/Mac +lsof -i :443 +``` + +**Solution:** +```bash +# Stop conflicting service or change ports in docker-compose.https.yml +ports: + - "8080:80" + - "8443:443" +``` + +### Certificate Not Valid for IP Address + +**Cause:** IP not included when generating cert + +**Solution:** +```bash +# Regenerate with your IP +mkcert -key-file nginx/ssl/key.pem \ + -cert-file nginx/ssl/cert.pem \ + localhost 127.0.0.1 ::1 YOUR_IP_HERE *.local + +# Restart nginx +docker-compose restart nginx +``` + +### Other Devices Show Warning + +**Cause:** CA not installed on device + +**Solution:** +- Follow "Access from Other Devices" section above +- Install rootCA.pem on each device +- Restart browser/app on device + +--- + +## πŸ”„ Certificate Renewal + +mkcert certificates are valid for **10 years** by default. No renewal needed for development use! + +To check expiration: +```bash +openssl x509 -in nginx/ssl/cert.pem -noout -dates +``` + +To regenerate: +```bash +# Re-run setup script +bash setup-https-mkcert.sh + +# Or manually +mkcert -key-file nginx/ssl/key.pem -cert-file nginx/ssl/cert.pem localhost 127.0.0.1 ::1 YOUR_IP *.local + +# Restart nginx +docker-compose restart nginx +``` + +--- + +## πŸ” Security Notes + +### Development Use Only + +mkcert is designed for **development and testing**: +- βœ… Perfect for local development +- βœ… Great for LAN/home network +- βœ… Safe for trusted networks +- ❌ **NOT for production internet-facing apps** + +### For Production + +Use real certificates: +- Let's Encrypt (free, automated) +- Commercial CA (paid) +- Use Caddy for automatic HTTPS + +See production deployment guides for details. + +### Best Practices + +1. βœ… Keep rootCA.pem secure (anyone with it can create trusted certs) +2. βœ… Don't commit `nginx/ssl/*.pem` to git (add to .gitignore) +3. βœ… Use different certs for each project +4. βœ… Uninstall CA when not needed: `mkcert -uninstall` + +--- + +## πŸ“‹ Command Reference + +```bash +# Install CA +mkcert -install + +# Find CA location +mkcert -CAROOT + +# Generate certificate +mkcert -key-file KEY.pem -cert-file CERT.pem DOMAIN1 DOMAIN2 + +# Uninstall CA +mkcert -uninstall + +# Check certificate +openssl x509 -in CERT.pem -text -noout +``` + +--- + +## πŸŽ‰ Summary + +### What You Get + +βœ… **Local HTTPS with NO warnings** +βœ… **Works with IP addresses** +βœ… **Trusted by all browsers** +βœ… **Valid for 10 years** +βœ… **Perfect for development** + +### Quick Start + +```bash +# Install mkcert +# Windows: choco install mkcert +# Mac: brew install mkcert + +# Run setup +bash setup-https-mkcert.sh + +# Start with HTTPS +docker-compose -f docker-compose.yml -f docker-compose.https.yml up -d + +# Access +https://localhost +https://192.168.1.100 +``` + +**That's it! Enjoy secure HTTPS! πŸ”’** + diff --git a/README.md b/README.md index 0803cfa..d28de8d 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,9 @@ Comprehensive documentation is available in the [`docs/`](docs/) directory: - **[Requirements](docs/REQUIREMENTS.md)** β€” System requirements and dependencies - **[Troubleshooting](docs/DOCKER_STARTUP_TROUBLESHOOTING.md)** β€” Common issues and solutions - **[CSRF Token Issues](CSRF_TROUBLESHOOTING.md)** β€” Fix "CSRF token missing or invalid" errors +- **[CSRF IP Access Fix](CSRF_IP_ACCESS_FIX.md)** β€” πŸ”₯ Fix cookies not working when accessing via IP address +- **[HTTPS Auto-Setup](README_HTTPS_AUTO.md)** β€” πŸš€ Automatic HTTPS at startup (one command!) +- **[HTTPS Manual Setup (mkcert)](README_HTTPS.md)** β€” πŸ”’ Manual HTTPS with no certificate warnings ### Features - **[Task Management](docs/TASK_MANAGEMENT_README.md)** β€” Break projects into manageable tasks diff --git a/README_HTTPS.md b/README_HTTPS.md new file mode 100644 index 0000000..bb1c169 --- /dev/null +++ b/README_HTTPS.md @@ -0,0 +1,221 @@ +# πŸ”’ HTTPS Setup for TimeTracker + +## Quick Start with mkcert + +### 1. Install mkcert + +**Windows:** +```powershell +choco install mkcert +``` + +**macOS:** +```bash +brew install mkcert +``` + +**Linux:** +```bash +# See HTTPS_MKCERT_GUIDE.md for detailed instructions +``` + +### 2. Run Setup Script + +**Windows:** +```cmd +setup-https-mkcert.bat +``` + +**Linux/Mac:** +```bash +bash setup-https-mkcert.sh +``` + +### 3. Start with HTTPS + +```bash +docker-compose -f docker-compose.yml -f docker-compose.https.yml up -d +``` + +### 4. Access Your App + +``` +https://localhost +https://192.168.1.100 (your actual IP) +``` + +**βœ… No certificate warnings!** +**βœ… Works with IP addresses!** +**βœ… Secure HTTPS!** + +--- + +## What the Script Does + +1. βœ… Installs local Certificate Authority (trusted by your browser) +2. βœ… Generates SSL certificates for localhost + your IP +3. βœ… Creates nginx reverse proxy configuration +4. βœ… Creates docker-compose.https.yml +5. βœ… Updates .env with secure HTTPS settings: + - `WTF_CSRF_SSL_STRICT=true` + - `SESSION_COOKIE_SECURE=true` + - `CSRF_COOKIE_SECURE=true` + +--- + +## Benefits + +### Solves CSRF Cookie Issues +- βœ… CSRF cookies work correctly with IP addresses +- βœ… Strict security settings enabled +- βœ… No more "CSRF token missing or invalid" errors + +### Secure Communication +- βœ… All traffic encrypted +- βœ… Trusted certificates (no warnings) +- βœ… Modern TLS 1.2/1.3 + +### Easy Management +- βœ… One command setup +- βœ… Valid for 10 years +- βœ… No renewal needed + +--- + +## Access from Other Devices + +To access from your phone, tablet, or other computers without warnings: + +1. **Find CA location:** + ```bash + mkcert -CAROOT + ``` + +2. **Copy `rootCA.pem` to device** + +3. **Install certificate on device:** + - iOS: Settings β†’ Profile β†’ Install + - Android: Settings β†’ Security β†’ Install certificate + - See [HTTPS_MKCERT_GUIDE.md](HTTPS_MKCERT_GUIDE.md) for details + +4. **Access from device:** + ``` + https://192.168.1.100 + ``` + +--- + +## File Structure + +After running the setup: + +``` +TimeTracker/ +β”œβ”€β”€ nginx/ +β”‚ β”œβ”€β”€ conf.d/ +β”‚ β”‚ └── https.conf # nginx HTTPS config +β”‚ └── ssl/ +β”‚ β”œβ”€β”€ cert.pem # SSL certificate (gitignored) +β”‚ └── key.pem # Private key (gitignored) +β”œβ”€β”€ docker-compose.yml # Base configuration +β”œβ”€β”€ docker-compose.https.yml # HTTPS override (auto-generated) +β”œβ”€β”€ setup-https-mkcert.sh # Linux/Mac setup script +β”œβ”€β”€ setup-https-mkcert.bat # Windows setup script +└── .env # Updated with HTTPS settings +``` + +--- + +## Verification + +### Check Certificate +1. Navigate to `https://localhost` +2. Click padlock icon in browser +3. View certificate β†’ Should show "mkcert" with no warnings + +### Check Cookies +1. Open DevTools (F12) β†’ Application β†’ Cookies +2. Verify `session` and `XSRF-TOKEN` cookies have `Secure` flag + +### Test Application +1. Login +2. Create a project +3. Log time +4. Should work without any CSRF errors βœ… + +--- + +## Stopping HTTPS + +To return to HTTP: + +```bash +# Stop HTTPS setup +docker-compose -f docker-compose.yml -f docker-compose.https.yml down + +# Start normally +docker-compose up -d +``` + +--- + +## Troubleshooting + +### Certificate Warning Appears + +```bash +# Reinstall CA +mkcert -install + +# Restart browser completely +``` + +### nginx Won't Start + +```bash +# Check if port is in use +netstat -ano | findstr :443 # Windows +lsof -i :443 # Linux/Mac + +# Check logs +docker-compose logs nginx +``` + +### IP Address Not Working + +```bash +# Regenerate with correct IP +mkcert -key-file nginx/ssl/key.pem -cert-file nginx/ssl/cert.pem \ + localhost 127.0.0.1 ::1 YOUR_ACTUAL_IP *.local + +# Restart +docker-compose restart nginx +``` + +--- + +## Complete Documentation + +For detailed instructions, see: +- **[HTTPS_MKCERT_GUIDE.md](HTTPS_MKCERT_GUIDE.md)** - Complete mkcert guide +- **[CSRF_IP_ACCESS_FIX.md](CSRF_IP_ACCESS_FIX.md)** - CSRF troubleshooting + +--- + +## Summary + +**One command to HTTPS:** +```bash +bash setup-https-mkcert.sh +docker-compose -f docker-compose.yml -f docker-compose.https.yml up -d +``` + +**Result:** +βœ… Secure HTTPS +βœ… No certificate warnings +βœ… Works with IP addresses +βœ… CSRF cookies work perfectly +βœ… Production-grade security settings + +**Enjoy secure TimeTracker! πŸ”’** + diff --git a/README_HTTPS_AUTO.md b/README_HTTPS_AUTO.md new file mode 100644 index 0000000..27735b8 --- /dev/null +++ b/README_HTTPS_AUTO.md @@ -0,0 +1,388 @@ +# πŸš€ Automatic HTTPS Setup + +## One-Command HTTPS Startup + +HTTPS is now **completely automatic**! Just run one command and everything is configured: + +### Windows +```cmd +start-https.bat +``` + +### Linux/Mac +```bash +bash start-https.sh +``` + +That's it! πŸŽ‰ + +--- + +## What Happens Automatically + +When you run the startup script: + +1. βœ… **Detects your local IP** (e.g., 192.168.1.100) +2. βœ… **Creates nginx HTTPS configuration** automatically +3. βœ… **Generates SSL certificates** (init container runs once) +4. βœ… **Updates security settings** to strict mode +5. βœ… **Starts all services** with HTTPS enabled + +**No manual steps required!** + +--- + +## Two Certificate Options + +### Option 1: Self-Signed Certificates (Default) +- βœ… Works immediately, zero setup +- βœ… Fully functional HTTPS +- ⚠️ Browser shows security warning (safe to click through) + +**When to use:** Quick testing, development + +### Option 2: mkcert (Trusted Certificates) +- βœ… No browser warnings! +- βœ… Trusted by all browsers +- πŸ“‹ Requires one-time mkcert installation + +**When to use:** Cleaner experience, demos, regular development + +--- + +## Quick Start + +### First Time Setup + +**1. Create .env file:** +```bash +cp env.example .env +# Edit .env with your settings +``` + +**2. Start with HTTPS:** + +**Windows:** +```cmd +start-https.bat +``` + +**Linux/Mac:** +```bash +bash start-https.sh +``` + +**3. Choose certificate method when prompted:** +- Press `1` for self-signed (default) +- Press `2` for mkcert (if installed) + +**4. Access your app:** +``` +https://localhost +https://192.168.1.100 (your actual IP) +``` + +--- + +## Self-Signed Certificates (Option 1) + +### What You'll See +Browser will show: "Your connection is not private" + +### How to Proceed +1. Click **"Advanced"** +2. Click **"Proceed to localhost (unsafe)"** +3. That's it! You're in. + +### Why It's Safe +- Certificates are generated by YOU +- Traffic is encrypted +- Warning is just because it's self-signed +- Perfect for development + +--- + +## mkcert Certificates (Option 2) + +### Prerequisites +**Install mkcert first:** + +**Windows:** +```powershell +choco install mkcert +``` + +**macOS:** +```bash +brew install mkcert +``` + +**Linux:** +```bash +# See HTTPS_MKCERT_GUIDE.md +``` + +### One-Time CA Installation + +After first startup with mkcert: + +1. **Find the CA certificate:** + ``` + nginx/ssl/rootCA.pem + ``` + +2. **Install it:** + + **Windows:** + - Double-click `rootCA.pem` + - Install to "Trusted Root Certification Authorities" + - Restart browser + + **macOS:** + - Double-click `rootCA.pem` + - Add to Keychain + - Mark as "Always Trust" + + **Linux:** + ```bash + sudo cp nginx/ssl/rootCA.pem /usr/local/share/ca-certificates/mkcert.crt + sudo update-ca-certificates + ``` + +3. **Restart browser** + +4. **Access app** - No warnings! βœ… + +--- + +## How It Works + +### Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ start-https β”‚ ← You run this +β”‚ script β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + ↓ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Init Containerβ”‚ ← Generates certificates (runs once) +β”‚ (certgen) β”‚ β€’ Checks if certs exist +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β€’ Creates them if missing + β”‚ β€’ Self-signed or mkcert + ↓ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ nginx (HTTPS) β”‚ ← Reverse proxy with SSL +β”‚ Port 443 β”‚ β€’ Terminates SSL +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β€’ Proxies to app + β”‚ + ↓ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ TimeTracker β”‚ ← Your application +β”‚ App (HTTP) β”‚ β€’ Runs on port 8080 +β”‚ Port 8080 β”‚ β€’ Behind nginx +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Certificate Generation + +The `certgen` init container: +- Runs before nginx starts +- Checks for existing certificates in `nginx/ssl/` +- If missing, generates new ones +- Exits (runs only once) +- nginx starts after successful completion + +### Persistence + +Certificates are stored in `nginx/ssl/` on your host machine: +- Persists across container restarts +- Regenerated only if deleted +- Valid for 10 years + +--- + +## File Structure + +After automatic startup: + +``` +TimeTracker/ +β”œβ”€β”€ nginx/ +β”‚ β”œβ”€β”€ conf.d/ +β”‚ β”‚ └── https.conf # Auto-generated nginx config +β”‚ └── ssl/ +β”‚ β”œβ”€β”€ cert.pem # SSL certificate +β”‚ β”œβ”€β”€ key.pem # Private key +β”‚ └── rootCA.pem # CA cert (mkcert only) +β”œβ”€β”€ docker-compose.yml # Base config +β”œβ”€β”€ docker-compose.https-auto.yml # HTTPS with self-signed +β”œβ”€β”€ docker-compose.https-mkcert.yml # HTTPS with mkcert +β”œβ”€β”€ start-https.sh # Auto-start script (Linux/Mac) +β”œβ”€β”€ start-https.bat # Auto-start script (Windows) +└── .env # Config (auto-updated) +``` + +--- + +## Commands + +### Start with HTTPS (Automatic) +```bash +bash start-https.sh # Linux/Mac +start-https.bat # Windows +``` + +### Start with HTTPS (Manual) +```bash +# Self-signed certificates +docker-compose -f docker-compose.yml -f docker-compose.https-auto.yml up -d + +# mkcert certificates (if mkcert installed) +docker-compose -f docker-compose.yml -f docker-compose.https-mkcert.yml up -d +``` + +### View Logs +```bash +docker-compose logs -f +docker-compose logs nginx +docker-compose logs certgen +``` + +### Stop Services +```bash +docker-compose down +``` + +### Regenerate Certificates +```bash +# Delete existing certs +rm -rf nginx/ssl/* + +# Restart - new certs will be generated +bash start-https.sh +``` + +--- + +## Environment Variables + +After automatic setup, your `.env` will include: + +```bash +# HTTPS Security Settings (auto-configured) +WTF_CSRF_SSL_STRICT=true +SESSION_COOKIE_SECURE=true +CSRF_COOKIE_SECURE=true +``` + +**These settings:** +- βœ… Enable strict CSRF protection +- βœ… Require HTTPS for cookies +- βœ… Fix all CSRF cookie issues +- βœ… Provide production-grade security + +--- + +## Troubleshooting + +### nginx Won't Start + +**Check if certificates exist:** +```bash +ls -la nginx/ssl/ +``` + +**Should see:** +- `cert.pem` +- `key.pem` + +**If missing, check certgen logs:** +```bash +docker-compose logs certgen +``` + +### Browser Still Shows Warning (mkcert) + +**CA not installed or browser not restarted:** +1. Install `nginx/ssl/rootCA.pem` (see instructions above) +2. Completely close and restart browser +3. Clear browser cache + +### Different IP Address + +**Update and restart:** +```bash +export HOST_IP=192.168.1.XXX # Your actual IP +bash start-https.sh +``` + +### Port 443 Already in Use + +**Find what's using it:** +```bash +# Windows +netstat -ano | findstr :443 + +# Linux/Mac +lsof -i :443 +``` + +**Stop the conflicting service or change port in nginx config** + +--- + +## Access from Other Devices + +### Using Self-Signed Certificates +1. Access `https://192.168.1.100` from device +2. Click through security warning +3. Done! + +### Using mkcert Certificates +1. Copy `nginx/ssl/rootCA.pem` to device +2. Install as trusted CA (see device-specific instructions) +3. Access `https://192.168.1.100` +4. No warnings! βœ… + +--- + +## Production Deployment + +For production with a real domain, use Let's Encrypt: +- See `docs/HTTPS_SETUP_GUIDE.md` for Caddy (automatic) +- Or use Traefik, nginx + certbot + +--- + +## Summary + +### πŸŽ‰ What You Get + +βœ… **One-command setup** - `bash start-https.sh` +βœ… **Automatic certificate generation** +βœ… **Auto-configuration** of nginx and security settings +βœ… **Choice of certificate types** +βœ… **Persistent certificates** across restarts +βœ… **No manual steps** for basic setup + +### πŸš€ Quick Reference + +```bash +# Start everything with HTTPS +bash start-https.sh + +# Access +https://localhost +https://192.168.1.100 + +# View logs +docker-compose logs -f + +# Stop +docker-compose down +``` + +**That's it! Enjoy automatic HTTPS! πŸ”’** + diff --git a/app/__init__.py b/app/__init__.py index 2c2daa1..0222696 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -36,9 +36,9 @@ def create_app(config=None): """Application factory pattern""" app = Flask(__name__) - # Make app aware of reverse proxy (scheme/host) for correct URL generation & cookies + # Make app aware of reverse proxy (scheme/host/port) for correct URL generation & cookies # Trust a single proxy by default; adjust via env if needed - app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1) + app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1) # Configuration # Load env-specific config class @@ -272,6 +272,48 @@ def create_app(config=None): ) except Exception: pass + + # Ensure CSRF cookie is present for HTML GET responses (helps login page) + try: + # Only for safe, HTML page responses + if request.method == "GET": + content_type = response.headers.get("Content-Type", "") + if isinstance(content_type, str) and content_type.startswith("text/html"): + cookie_name = app.config.get("CSRF_COOKIE_NAME", "XSRF-TOKEN") + has_cookie = bool(request.cookies.get(cookie_name)) + if not has_cookie: + # Generate a CSRF token and set cookie using same settings as /auth/csrf-token + try: + from flask_wtf.csrf import generate_csrf + token = generate_csrf() + except Exception: + token = "" + cookie_secure = bool( + app.config.get( + "CSRF_COOKIE_SECURE", + app.config.get("SESSION_COOKIE_SECURE", False), + ) + ) + cookie_httponly = bool(app.config.get("CSRF_COOKIE_HTTPONLY", False)) + cookie_samesite = app.config.get("CSRF_COOKIE_SAMESITE", "Lax") + cookie_domain = app.config.get("CSRF_COOKIE_DOMAIN") or None + cookie_path = app.config.get("CSRF_COOKIE_PATH", "/") + try: + max_age = int(app.config.get("WTF_CSRF_TIME_LIMIT", 3600)) + except Exception: + max_age = 3600 + response.set_cookie( + cookie_name, + token or "", + max_age=max_age, + secure=cookie_secure, + httponly=cookie_httponly, + samesite=cookie_samesite, + domain=cookie_domain, + path=cookie_path, + ) + except Exception: + pass return response # CSRF error handler with HTML-friendly fallback diff --git a/app/config.py b/app/config.py index 92550e5..69e39cd 100644 --- a/app/config.py +++ b/app/config.py @@ -94,7 +94,9 @@ class Config: 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY', 'X-XSS-Protection': '1; mode=block', - 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains' + 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', + # Allow same-origin Referer on HTTPS so CSRF checks that rely on Referer can pass + 'Referrer-Policy': 'strict-origin-when-cross-origin' } # Rate limiting diff --git a/docker-compose.https-auto.yml b/docker-compose.https-auto.yml new file mode 100644 index 0000000..74bea32 --- /dev/null +++ b/docker-compose.https-auto.yml @@ -0,0 +1,35 @@ +services: + # Certificate generator - runs once to create certificates + certgen: + image: alpine:latest + container_name: timetracker-certgen + volumes: + - ./nginx/ssl:/certs + - ./scripts:/scripts:ro + command: sh /scripts/generate-certs.sh + restart: "no" + + nginx: + image: nginx:alpine + container_name: timetracker-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/conf.d:/etc/nginx/conf.d:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + depends_on: + certgen: + condition: service_completed_successfully + app: + condition: service_started + restart: unless-stopped + + app: + ports: [] # nginx handles all ports + environment: + - WTF_CSRF_SSL_STRICT=true + - SESSION_COOKIE_SECURE=true + - CSRF_COOKIE_SECURE=true + restart: unless-stopped + diff --git a/docker-compose.https-mkcert.yml b/docker-compose.https-mkcert.yml new file mode 100644 index 0000000..4db7910 --- /dev/null +++ b/docker-compose.https-mkcert.yml @@ -0,0 +1,44 @@ +services: + # mkcert certificate manager - auto-generates trusted certificates + mkcert: + build: + context: . + dockerfile: docker/Dockerfile.mkcert + container_name: timetracker-mkcert + volumes: + - ./nginx/ssl:/certs + - mkcert-ca:/root/.local/share/mkcert + environment: + - HOST_IP=${HOST_IP:-192.168.1.100} + - CERT_DOMAINS=localhost 127.0.0.1 ::1 ${HOST_IP:-192.168.1.100} *.local timetracker.local + command: /generate-mkcert-certs.sh + restart: "no" + + nginx: + image: nginx:alpine + container_name: timetracker-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/conf.d:/etc/nginx/conf.d:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + depends_on: + mkcert: + condition: service_completed_successfully + app: + condition: service_started + restart: unless-stopped + + app: + ports: [] # nginx handles all ports + environment: + - WTF_CSRF_SSL_STRICT=true + - SESSION_COOKIE_SECURE=true + - CSRF_COOKIE_SECURE=true + restart: unless-stopped + +volumes: + mkcert-ca: + driver: local + diff --git a/docker-compose.remote.yml b/docker-compose.remote.yml index e0d8c57..0b2a0fc 100644 --- a/docker-compose.remote.yml +++ b/docker-compose.remote.yml @@ -1,4 +1,33 @@ services: + # Certificate generator - runs once to create self-signed certs with SANs + certgen: + image: alpine:latest + container_name: timetracker-certgen-remote + volumes: + - ./nginx/ssl:/certs + - ./scripts:/scripts:ro + environment: + - HOST_IP=${HOST_IP:-192.168.1.100} + command: sh /scripts/generate-certs.sh + restart: "no" + + # HTTPS reverse proxy (TLS terminates here) + nginx: + image: nginx:alpine + container_name: timetracker-nginx-remote + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/conf.d:/etc/nginx/conf.d:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + depends_on: + certgen: + condition: service_completed_successfully + app: + condition: service_started + restart: unless-stopped + app: image: ghcr.io/drytrix/timetracker:latest container_name: timetracker-app-remote @@ -13,26 +42,22 @@ services: # IMPORTANT: Change SECRET_KEY in production! Used for sessions and CSRF tokens. # Generate a secure key: python -c "import secrets; print(secrets.token_hex(32))" # The app will refuse to start with the default key in production mode. - # - # TROUBLESHOOTING: If forms fail with "CSRF token missing or invalid": - # 1. Verify SECRET_KEY is set and doesn't change between restarts - # 2. Check CSRF is enabled: WTF_CSRF_ENABLED=true - # 3. Ensure cookies are enabled in your browser - # 4. If behind a reverse proxy, ensure it forwards cookies correctly - # 5. Check the token hasn't expired (increase WTF_CSRF_TIME_LIMIT if needed) - # 6. Verify all app replicas use the SAME SECRET_KEY - # For details: docs/CSRF_CONFIGURATION.md - SECRET_KEY=${SECRET_KEY:-your-secret-key-change-this} - DATABASE_URL=postgresql+psycopg2://timetracker:timetracker@db:5432/timetracker - LOG_FILE=/app/logs/timetracker.log - # CSRF Protection (enabled by default for security) + # CSRF and cookies for HTTPS deployments - WTF_CSRF_ENABLED=${WTF_CSRF_ENABLED:-true} - WTF_CSRF_TIME_LIMIT=${WTF_CSRF_TIME_LIMIT:-3600} - # Enable secure cookies for HTTPS production deployments + - WTF_CSRF_SSL_STRICT=${WTF_CSRF_SSL_STRICT:-true} - SESSION_COOKIE_SECURE=${SESSION_COOKIE_SECURE:-true} - REMEMBER_COOKIE_SECURE=${REMEMBER_COOKIE_SECURE:-true} - ports: - - "8080:8080" + - CSRF_COOKIE_SECURE=${CSRF_COOKIE_SECURE:-true} + - CSRF_COOKIE_HTTPONLY=${CSRF_COOKIE_HTTPONLY:-false} + - CSRF_COOKIE_SAMESITE=${CSRF_COOKIE_SAMESITE:-Lax} + - CSRF_COOKIE_NAME=${CSRF_COOKIE_NAME:-XSRF-TOKEN} + - PREFERRED_URL_SCHEME=${PREFERRED_URL_SCHEME:-https} + # Expose only internally; nginx publishes ports + ports: [] volumes: - app_data_remote:/data - ./logs:/app/logs @@ -70,5 +95,3 @@ volumes: driver: local db_data_remote: driver: local - - diff --git a/docker-compose.yml b/docker-compose.yml index d34f123..4e2c90e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,31 @@ services: + # Certificate generator - runs once to create self-signed certs with SANs + certgen: + image: alpine:latest + container_name: timetracker-certgen + volumes: + - ./nginx/ssl:/certs + - ./scripts:/scripts:ro + command: sh /scripts/generate-certs.sh + restart: "no" + + # HTTPS reverse proxy (TLS terminates here) + nginx: + image: nginx:alpine + container_name: timetracker-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/conf.d:/etc/nginx/conf.d:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + depends_on: + certgen: + condition: service_completed_successfully + app: + condition: service_started + restart: unless-stopped + app: build: . container_name: timetracker-app @@ -13,17 +40,36 @@ services: # IMPORTANT: Change SECRET_KEY in production! Used for sessions and CSRF tokens. # Generate a secure key: python -c "import secrets; print(secrets.token_hex(32))" # + # CSRF CONFIGURATION: + # - WTF_CSRF_SSL_STRICT: Set to 'false' for HTTP access (localhost or IP address) + # Set to 'true' only when using HTTPS in production + # - If accessing via IP address (e.g., 192.168.1.100), also set: + # SESSION_COOKIE_SECURE=false and CSRF_COOKIE_SECURE=false + # # TROUBLESHOOTING: If forms fail with "CSRF token missing or invalid": # 1. Verify SECRET_KEY is set and doesn't change between restarts # 2. Check CSRF is enabled: WTF_CSRF_ENABLED=true # 3. Ensure cookies are enabled in your browser # 4. If behind a reverse proxy, ensure it forwards cookies correctly # 5. Check the token hasn't expired (increase WTF_CSRF_TIME_LIMIT if needed) - # For details: docs/CSRF_CONFIGURATION.md + # 6. If accessing via IP (not localhost): WTF_CSRF_SSL_STRICT=false + # For details: docs/CSRF_CONFIGURATION.md and docs/CSRF_IP_ACCESS_GUIDE.md + # Disable strict Referer check by default to avoid privacy/port issues + - WTF_CSRF_SSL_STRICT=${WTF_CSRF_SSL_STRICT:-true} + - WTF_CSRF_ENABLED=${WTF_CSRF_ENABLED:-true} + - WTF_CSRF_TIME_LIMIT=${WTF_CSRF_TIME_LIMIT:-3600} + - SESSION_COOKIE_SECURE=${SESSION_COOKIE_SECURE:-true} + - SESSION_COOKIE_SAMESITE=${SESSION_COOKIE_SAMESITE:-Lax} + - REMEMBER_COOKIE_SECURE=${REMEMBER_COOKIE_SECURE:-true} + - CSRF_COOKIE_SECURE=${CSRF_COOKIE_SECURE:-true} + - CSRF_COOKIE_HTTPONLY=${CSRF_COOKIE_HTTPONLY:-false} + - CSRF_COOKIE_SAMESITE=${CSRF_COOKIE_SAMESITE:-Lax} + - CSRF_COOKIE_NAME=${CSRF_COOKIE_NAME:-XSRF-TOKEN} + - PREFERRED_URL_SCHEME=${PREFERRED_URL_SCHEME:-https} - DATABASE_URL=postgresql+psycopg2://timetracker:timetracker@db:5432/timetracker - LOG_FILE=/app/logs/timetracker.log - ports: - - "8080:8080" + # Expose only internally; nginx publishes ports + ports: [] volumes: - app_data:/data - ./logs:/app/logs diff --git a/docker/Dockerfile.mkcert b/docker/Dockerfile.mkcert new file mode 100644 index 0000000..5b91941 --- /dev/null +++ b/docker/Dockerfile.mkcert @@ -0,0 +1,17 @@ +FROM alpine:latest + +# Install mkcert +RUN apk add --no-cache \ + ca-certificates \ + curl \ + nss-tools \ + && curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" \ + && chmod +x mkcert-v*-linux-amd64 \ + && mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert + +# Create certificate generation script +COPY docker/generate-mkcert-certs.sh /generate-mkcert-certs.sh +RUN chmod +x /generate-mkcert-certs.sh + +CMD ["/generate-mkcert-certs.sh"] + diff --git a/docker/generate-mkcert-certs.sh b/docker/generate-mkcert-certs.sh new file mode 100644 index 0000000..f261ed7 --- /dev/null +++ b/docker/generate-mkcert-certs.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# Auto-generate mkcert certificates in container + +set -e + +CERT_DIR="/certs" +CERT_FILE="$CERT_DIR/cert.pem" +KEY_FILE="$CERT_DIR/key.pem" +CA_FILE="$CERT_DIR/rootCA.pem" + +echo "==========================================" +echo "mkcert Certificate Generator" +echo "==========================================" +echo "" + +# Create cert directory +mkdir -p "$CERT_DIR" + +# Check if certificates exist +if [ -f "$CERT_FILE" ] && [ -f "$KEY_FILE" ]; then + echo "βœ… Certificates already exist" + exit 0 +fi + +echo "πŸ”§ Generating mkcert certificates..." +echo "" + +# Install local CA (for container use) +mkcert -install + +# Get domains/IPs to include +DOMAINS=${CERT_DOMAINS:-"localhost 127.0.0.1 ::1"} +echo "Generating certificate for: $DOMAINS" +echo "" + +# Generate certificates +mkcert -key-file "$KEY_FILE" -cert-file "$CERT_FILE" $DOMAINS + +# Copy CA certificate for user to install on host +cp "$(mkcert -CAROOT)/rootCA.pem" "$CA_FILE" 2>/dev/null || true + +chmod 644 "$CERT_FILE" "$CA_FILE" 2>/dev/null || true +chmod 600 "$KEY_FILE" + +echo "" +echo "βœ… mkcert certificates generated!" +echo "" +echo "πŸ“‹ Next steps:" +echo " 1. The certificates are in: nginx/ssl/" +echo " 2. To avoid browser warnings, install rootCA.pem on your host:" +echo "" +echo " Windows:" +echo " - Double-click nginx/ssl/rootCA.pem" +echo " - Install to: Trusted Root Certification Authorities" +echo "" +echo " macOS:" +echo " - Double-click nginx/ssl/rootCA.pem" +echo " - Add to Keychain and mark as trusted" +echo "" +echo " Linux:" +echo " sudo cp nginx/ssl/rootCA.pem /usr/local/share/ca-certificates/mkcert.crt" +echo " sudo update-ca-certificates" +echo "" +echo " 3. Restart your browser" +echo " 4. Access: https://localhost or https://$HOST_IP" +echo "" +echo "==========================================" + diff --git a/docs/CSRF_IP_ACCESS_GUIDE.md b/docs/CSRF_IP_ACCESS_GUIDE.md new file mode 100644 index 0000000..20d1299 --- /dev/null +++ b/docs/CSRF_IP_ACCESS_GUIDE.md @@ -0,0 +1,285 @@ +# CSRF Cookie Issues with Remote IP Access + +## Problem + +When accessing the TimeTracker application: +- βœ… Works fine via `http://localhost:8080` +- ❌ CSRF cookie not created when accessing via IP address (e.g., `http://192.168.1.100:8080`) + +## Root Cause + +The issue occurs due to browser cookie security policies and Flask's CSRF protection settings: + +1. **WTF_CSRF_SSL_STRICT**: When set to `true` (default in production), Flask-WTF rejects cookies from non-HTTPS connections that it considers "insecure" +2. **SESSION_COOKIE_SECURE**: When set to `true`, cookies are only sent over HTTPS, blocking HTTP access via IP +3. **SameSite Policy**: Browsers treat localhost and IP addresses differently for cookie SameSite policies + +## Quick Fix + +### Option 1: Environment Variables (Recommended) + +Add these to your `.env` file: + +```bash +# Disable SSL strict mode for HTTP access +WTF_CSRF_SSL_STRICT=false + +# Ensure cookies work over HTTP +SESSION_COOKIE_SECURE=false +CSRF_COOKIE_SECURE=false + +# Optional: Adjust SameSite if needed +SESSION_COOKIE_SAMESITE=Lax +CSRF_COOKIE_SAMESITE=Lax +``` + +Then restart the application: + +```bash +docker-compose restart app +``` + +### Option 2: Docker Compose Override + +Create or update `docker-compose.override.yml`: + +```yaml +services: + app: + environment: + - WTF_CSRF_SSL_STRICT=false + - SESSION_COOKIE_SECURE=false + - CSRF_COOKIE_SECURE=false + - SESSION_COOKIE_SAMESITE=Lax +``` + +Restart: + +```bash +docker-compose up -d +``` + +## Detailed Explanation + +### WTF_CSRF_SSL_STRICT + +This Flask-WTF setting controls whether CSRF protection rejects requests it considers insecure: + +- **`true`** (default in production): Rejects cookies from HTTP on non-localhost addresses +- **`false`**: Allows cookies over HTTP (needed for IP access without HTTPS) + +**When to use `false`:** +- Development/testing environments +- Local network access via IP address +- When HTTPS is not configured + +**When to use `true`:** +- Production with HTTPS enabled +- Public-facing applications +- Maximum security requirements + +### Cookie Secure Flags + +**SESSION_COOKIE_SECURE** and **CSRF_COOKIE_SECURE**: +- **`true`**: Cookies only sent over HTTPS (blocks HTTP access) +- **`false`**: Cookies sent over HTTP and HTTPS + +### SameSite Policy + +Controls when browsers send cookies: + +- **`Strict`**: Cookie only sent for same-site requests (most restrictive) +- **`Lax`** (default): Cookie sent for same-site and top-level navigation +- **`None`**: Cookie sent with all requests (requires Secure flag) + +## Testing + +### 1. Check Current Settings + +```bash +docker-compose exec app env | grep -E "(CSRF|SESSION_COOKIE|WTF)" +``` + +### 2. Verify Cookie Creation + +1. Open browser DevTools (F12) +2. Go to **Application** β†’ **Cookies** +3. Navigate to your app (via IP address) +4. Look for these cookies: + - `session` - Session cookie + - `XSRF-TOKEN` - CSRF token cookie + +### 3. Test CSRF Token Endpoint + +```bash +# Via localhost (should work) +curl -v http://localhost:8080/auth/csrf-token + +# Via IP address (should also work after fix) +curl -v http://192.168.1.100:8080/auth/csrf-token +``` + +Look for `Set-Cookie` headers in the response. + +## Security Considerations + +### Development vs Production + +**Development (HTTP access via IP):** +```bash +WTF_CSRF_SSL_STRICT=false +SESSION_COOKIE_SECURE=false +CSRF_COOKIE_SECURE=false +``` + +**Production (HTTPS with domain):** +```bash +WTF_CSRF_SSL_STRICT=true +SESSION_COOKIE_SECURE=true +CSRF_COOKIE_SECURE=true +``` + +### Risk Assessment + +Setting `WTF_CSRF_SSL_STRICT=false`: +- βœ… **Safe for**: Local networks, development, testing +- ⚠️ **Risk for**: Public internet without HTTPS +- ❌ **Never**: Production with sensitive data over HTTP + +### Best Practices + +1. **Use HTTPS in Production**: Always enable HTTPS for production deployments +2. **Separate Configs**: Use different settings for dev/prod environments +3. **Network Security**: If using HTTP, ensure network is trusted (VPN, local network) +4. **Monitor Logs**: Watch for CSRF failures in application logs + +## Alternative Solutions + +### Solution 1: Use a Domain Name + +Instead of accessing via IP, use a domain name: + +```bash +# Add to /etc/hosts (Linux/Mac) or C:\Windows\System32\drivers\etc\hosts (Windows) +192.168.1.100 timetracker.local + +# Access via domain +http://timetracker.local:8080 +``` + +### Solution 2: Enable HTTPS + +Set up HTTPS with a self-signed certificate for local development: + +```bash +# Generate self-signed certificate +openssl req -x509 -newkey rsa:4096 -nodes \ + -keyout key.pem -out cert.pem -days 365 \ + -subj "/CN=192.168.1.100" + +# Update docker-compose to use HTTPS +# Then set: +WTF_CSRF_SSL_STRICT=true +SESSION_COOKIE_SECURE=true +``` + +### Solution 3: Disable CSRF (Development Only) + +⚠️ **Only for isolated development environments:** + +```bash +WTF_CSRF_ENABLED=false +``` + +**Never use this in production or with real data!** + +## Troubleshooting + +### Issue: Cookie Still Not Created + +**Check 1: Verify environment variables are loaded** +```bash +docker-compose exec app env | grep WTF_CSRF_SSL_STRICT +``` + +**Check 2: Restart the container** +```bash +docker-compose restart app +``` + +**Check 3: Check application logs** +```bash +docker-compose logs app | tail -50 +``` + +### Issue: CSRF Token Works but Form Fails + +This is different from cookie creation. Check: + +1. Token in HTML form: View page source and search for `csrf_token` +2. Token in request: Browser DevTools β†’ Network β†’ Form Data +3. Token expiration: Increase `WTF_CSRF_TIME_LIMIT` + +### Issue: Works on Chrome but not Firefox/Safari + +Different browsers have different cookie policies: + +1. Try disabling enhanced tracking protection +2. Check browser console for cookie warnings +3. Use consistent SameSite settings + +## Configuration Examples + +### Local Development (HTTP, IP Access) + +```bash +# .env +FLASK_ENV=development +WTF_CSRF_ENABLED=true +WTF_CSRF_SSL_STRICT=false +SESSION_COOKIE_SECURE=false +CSRF_COOKIE_SECURE=false +SESSION_COOKIE_SAMESITE=Lax +CSRF_COOKIE_SAMESITE=Lax +``` + +### Production (HTTPS, Domain) + +```bash +# .env +FLASK_ENV=production +WTF_CSRF_ENABLED=true +WTF_CSRF_SSL_STRICT=true +SESSION_COOKIE_SECURE=true +CSRF_COOKIE_SECURE=true +SESSION_COOKIE_SAMESITE=Strict +CSRF_COOKIE_SAMESITE=Strict +``` + +### Testing (Disable CSRF) + +```bash +# .env (isolated test environment only!) +FLASK_ENV=development +WTF_CSRF_ENABLED=false +``` + +## Related Documentation + +- [CSRF Configuration Guide](CSRF_CONFIGURATION.md) +- [CSRF Troubleshooting](../CSRF_TROUBLESHOOTING.md) +- [Docker Setup Guide](DOCKER_PUBLIC_SETUP.md) + +## Summary + +**The core issue**: `WTF_CSRF_SSL_STRICT=true` (default) blocks cookie creation for HTTP access via IP addresses. + +**The solution**: Set `WTF_CSRF_SSL_STRICT=false` when accessing via IP without HTTPS. + +**For production**: Always use HTTPS with proper domain names and keep strict settings enabled. + +--- + +**Last Updated:** October 2024 +**Applies To:** TimeTracker v1.0+ + diff --git a/env.example b/env.example index 2db294b..9f07f96 100644 --- a/env.example +++ b/env.example @@ -61,6 +61,22 @@ UPLOAD_FOLDER=/data/uploads WTF_CSRF_ENABLED=true WTF_CSRF_TIME_LIMIT=3600 +# CSRF SSL Strict Mode +# Set to false if accessing via HTTP (localhost or IP address) +# Set to true only when using HTTPS in production +WTF_CSRF_SSL_STRICT=false + +# CSRF Cookie Settings +# Only set these if you need to access the app via IP address or have cookie issues +# CSRF_COOKIE_SECURE=false # Set to false for HTTP access +# CSRF_COOKIE_SAMESITE=Lax # Options: Strict, Lax, None +# CSRF_COOKIE_DOMAIN= # Leave empty for single domain, set for subdomains + +# Session Cookie Settings for IP Address Access +# If accessing via IP address (e.g., 192.168.1.100), use these settings: +# SESSION_COOKIE_SAMESITE=Lax # Change to 'None' only if needed for cross-site +# SESSION_COOKIE_SECURE=false # Must be false for HTTP + # TROUBLESHOOTING CSRF issues ("CSRF token missing or invalid" errors): # 1. SECRET_KEY changed? All CSRF tokens become invalid when SECRET_KEY changes # 2. Cookies blocked? Check browser settings and allow cookies from your domain @@ -68,8 +84,9 @@ WTF_CSRF_TIME_LIMIT=3600 # 4. Token expired? Increase WTF_CSRF_TIME_LIMIT (in seconds) # 5. Multiple app instances? All must use the SAME SECRET_KEY # 6. Clock skew? Ensure server time is synchronized (use NTP) -# 7. Still broken? Try: docker-compose restart app -# 8. For testing only: Set WTF_CSRF_ENABLED=false (NOT for production!) +# 7. Accessing via IP? Set WTF_CSRF_SSL_STRICT=false and SESSION_COOKIE_SECURE=false +# 8. Still broken? Try: docker-compose restart app +# 9. For testing only: Set WTF_CSRF_ENABLED=false (NOT for production!) # See docs/CSRF_CONFIGURATION.md for detailed troubleshooting # Logging diff --git a/nginx/conf.d/https.conf b/nginx/conf.d/https.conf new file mode 100644 index 0000000..573c539 --- /dev/null +++ b/nginx/conf.d/https.conf @@ -0,0 +1,67 @@ +server { + listen 80; + listen [::]:80; + # Redirect all HTTP to HTTPS on the same host + return 308 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + + # Catch-all; optionally set a specific server_name + server_name _; + + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # Security headers + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "DENY" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # Proxy to application + location / { + proxy_pass http://app:8080; + # Preserve original host including port (e.g., localhost:8443) + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-Port $server_port; + 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; + + # Preserve cookies + proxy_pass_request_headers on; + proxy_cookie_path / /; + } + + # Socket.IO (WebSocket) endpoint + location /socket.io/ { + proxy_pass http://app:8080/socket.io/; + # WebSocket upgrade headers + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # Preserve original host and client details + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Host $http_host; + proxy_set_header X-Forwarded-Port $server_port; + 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; + + # Timeouts and buffering suitable for long-lived WS + proxy_read_timeout 600s; + proxy_send_timeout 600s; + proxy_buffering off; + } +} + + diff --git a/scripts/generate-certs.sh b/scripts/generate-certs.sh new file mode 100644 index 0000000..86ef489 --- /dev/null +++ b/scripts/generate-certs.sh @@ -0,0 +1,105 @@ +#!/bin/sh +# Auto-generate SSL certificates for HTTPS +# This script runs in an init container at startup + +set -e + +CERT_DIR="/certs" +CERT_FILE="$CERT_DIR/cert.pem" +KEY_FILE="$CERT_DIR/key.pem" + +echo "==========================================" +echo "SSL Certificate Generator" +echo "==========================================" +echo "" + +# Create cert directory if it doesn't exist +mkdir -p "$CERT_DIR" + +# Check if certificates already exist +if [ -f "$CERT_FILE" ] && [ -f "$KEY_FILE" ]; then + echo "βœ… Certificates already exist, skipping generation" + + # Check if they're about to expire (less than 30 days) + if command -v openssl >/dev/null 2>&1; then + EXPIRY=$(openssl x509 -enddate -noout -in "$CERT_FILE" 2>/dev/null | cut -d= -f2) + if [ -n "$EXPIRY" ]; then + echo "πŸ“… Certificate expires: $EXPIRY" + fi + fi + exit 0 +fi + +echo "πŸ”§ Generating new SSL certificates..." +echo "" + +# Install openssl if not present +if ! command -v openssl >/dev/null 2>&1; then + echo "Installing OpenSSL..." + apk add --no-cache openssl +fi + +# Detect IP address (try to get container host IP) +HOST_IP=${HOST_IP:-"192.168.1.100"} +echo "Using IP address: $HOST_IP" + +# Create OpenSSL config for SAN (Subject Alternative Names) +cat > /tmp/openssl.cnf << EOF +[req] +default_bits = 2048 +prompt = no +default_md = sha256 +x509_extensions = v3_req +distinguished_name = dn + +[dn] +C = US +ST = State +L = City +O = TimeTracker +OU = Development +CN = localhost + +[v3_req] +subjectAltName = @alt_names +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth + +[alt_names] +DNS.1 = localhost +DNS.2 = *.local +DNS.3 = timetracker.local +IP.1 = 127.0.0.1 +IP.2 = ::1 +IP.3 = ${HOST_IP} +EOF + +# Generate self-signed certificate valid for 10 years +echo "Generating certificate..." +openssl req -x509 \ + -newkey rsa:2048 \ + -nodes \ + -keyout "$KEY_FILE" \ + -out "$CERT_FILE" \ + -days 3650 \ + -config /tmp/openssl.cnf + +# Set proper permissions +chmod 644 "$CERT_FILE" +chmod 600 "$KEY_FILE" + +echo "" +echo "βœ… Certificates generated successfully!" +echo "" +echo "Certificate details:" +openssl x509 -in "$CERT_FILE" -noout -subject -dates 2>/dev/null || true +echo "" +echo "πŸ“ Note: These are self-signed certificates." +echo " Browsers will show a warning on first access." +echo " Click 'Advanced' β†’ 'Proceed' to accept." +echo "" +echo "For trusted certificates (no warnings), use mkcert:" +echo " bash setup-https-mkcert.sh" +echo "" +echo "==========================================" + diff --git a/setup-https-mkcert.bat b/setup-https-mkcert.bat new file mode 100644 index 0000000..b1056f9 --- /dev/null +++ b/setup-https-mkcert.bat @@ -0,0 +1,149 @@ +@echo off +REM Setup HTTPS for TimeTracker using mkcert +REM Works with localhost and IP addresses - NO certificate warnings! + +echo ========================================== +echo TimeTracker HTTPS Setup with mkcert +echo ========================================== +echo. + +REM Check if mkcert is installed +where mkcert >nul 2>&1 +if %errorlevel% neq 0 ( + echo [ERROR] mkcert is not installed! + echo. + echo Install mkcert: + echo Using Chocolatey: choco install mkcert + echo Using Scoop: scoop install mkcert + echo. + pause + exit /b 1 +) + +echo [OK] mkcert found +echo. + +REM Install local CA +echo Installing local Certificate Authority... +mkcert -install +echo [OK] Local CA installed +echo. + +REM Get local IP +for /f "tokens=2 delims=:" %%a in ('ipconfig ^| findstr /c:"IPv4 Address"') do ( + set LOCAL_IP=%%a + goto :found_ip +) +:found_ip +set LOCAL_IP=%LOCAL_IP: =% +if "%LOCAL_IP%"=="" set LOCAL_IP=192.168.1.100 + +echo Detected local IP: %LOCAL_IP% +echo. + +REM Create directories +if not exist nginx\ssl mkdir nginx\ssl +if not exist nginx\conf.d mkdir nginx\conf.d + +REM Generate certificates +echo Generating certificates... +mkcert -key-file nginx\ssl\key.pem -cert-file nginx\ssl\cert.pem localhost 127.0.0.1 ::1 %LOCAL_IP% *.local + +echo [OK] Certificates generated +echo. + +REM Create nginx config +( +echo server { +echo listen 80; +echo server_name _; +echo return 301 https://$host$request_uri; +echo } +echo. +echo server { +echo listen 443 ssl http2; +echo server_name _; +echo. +echo ssl_certificate /etc/nginx/ssl/cert.pem; +echo ssl_certificate_key /etc/nginx/ssl/key.pem; +echo. +echo ssl_protocols TLSv1.2 TLSv1.3; +echo ssl_ciphers HIGH:!aNULL:!MD5; +echo ssl_prefer_server_ciphers on; +echo. +echo add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; +echo add_header X-Frame-Options "DENY" always; +echo add_header X-Content-Type-Options "nosniff" always; +echo. +echo location / { +echo proxy_pass http://app:8080; +echo proxy_set_header Host $host; +echo proxy_set_header X-Real-IP $remote_addr; +echo proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +echo proxy_set_header X-Forwarded-Proto $scheme; +echo +echo proxy_http_version 1.1; +echo proxy_set_header Upgrade $http_upgrade; +echo proxy_set_header Connection "upgrade"; +echo } +echo } +) > nginx\conf.d\https.conf + +echo [OK] nginx config created +echo. + +REM Create docker-compose override +( +echo services: +echo nginx: +echo image: nginx:alpine +echo container_name: timetracker-nginx +echo ports: +echo - "80:80" +echo - "443:443" +echo volumes: +echo - ./nginx/conf.d:/etc/nginx/conf.d:ro +echo - ./nginx/ssl:/etc/nginx/ssl:ro +echo depends_on: +echo - app +echo restart: unless-stopped +echo. +echo app: +echo ports: [] +echo environment: +echo - WTF_CSRF_SSL_STRICT=true +echo - SESSION_COOKIE_SECURE=true +echo - CSRF_COOKIE_SECURE=true +) > docker-compose.https.yml + +echo [OK] docker-compose.https.yml created +echo. + +REM Update .env if exists +if exist .env ( + copy .env .env.backup >nul + powershell -Command "$content = Get-Content .env; if ($content -match '^WTF_CSRF_SSL_STRICT=') { $content = $content -replace '^WTF_CSRF_SSL_STRICT=.*', 'WTF_CSRF_SSL_STRICT=true' } else { $content += 'WTF_CSRF_SSL_STRICT=true' }; if ($content -match '^SESSION_COOKIE_SECURE=') { $content = $content -replace '^SESSION_COOKIE_SECURE=.*', 'SESSION_COOKIE_SECURE=true' } else { $content += 'SESSION_COOKIE_SECURE=true' }; if ($content -match '^CSRF_COOKIE_SECURE=') { $content = $content -replace '^CSRF_COOKIE_SECURE=.*', 'CSRF_COOKIE_SECURE=true' } else { $content += 'CSRF_COOKIE_SECURE=true' }; $content | Set-Content .env" + echo [OK] .env updated +) else ( + echo [WARNING] No .env file - create from env.example +) + +echo. +echo ========================================== +echo [OK] HTTPS Setup Complete! +echo ========================================== +echo. +echo Start with HTTPS: +echo docker-compose -f docker-compose.yml -f docker-compose.https.yml up -d +echo. +echo Access at: +echo https://localhost +echo https://%LOCAL_IP% +echo. +echo For other devices: +echo 1. Find CA: mkcert -CAROOT +echo 2. Copy rootCA.pem to device +echo 3. Import as trusted certificate +echo. +pause + diff --git a/setup-https-mkcert.sh b/setup-https-mkcert.sh new file mode 100644 index 0000000..066724e --- /dev/null +++ b/setup-https-mkcert.sh @@ -0,0 +1,152 @@ +#!/bin/bash +# Setup HTTPS for TimeTracker using mkcert +# Works with localhost and IP addresses - NO certificate warnings! + +set -e + +echo "==========================================" +echo "TimeTracker HTTPS Setup with mkcert" +echo "==========================================" +echo "" + +# Check if mkcert is installed +if ! command -v mkcert &> /dev/null; then + echo "❌ mkcert is not installed!" + echo "" + echo "Install mkcert:" + echo " macOS: brew install mkcert" + echo " Linux: https://github.com/FiloSottile/mkcert#linux" + echo " Windows: choco install mkcert OR scoop install mkcert" + echo "" + exit 1 +fi + +echo "βœ… mkcert found" +echo "" + +# Install local CA +echo "Installing local Certificate Authority..." +mkcert -install +echo "βœ… Local CA installed" +echo "" + +# Detect local IP +if [[ "$OSTYPE" == "darwin"* ]]; then + LOCAL_IP=$(ipconfig getifaddr en0 || ipconfig getifaddr en1 || echo "192.168.1.100") +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + LOCAL_IP=$(hostname -I | awk '{print $1}' || echo "192.168.1.100") +else + LOCAL_IP="192.168.1.100" +fi + +echo "Detected local IP: $LOCAL_IP" +echo "" + +# Create directories +mkdir -p nginx/ssl nginx/conf.d + +# Generate certificates +echo "Generating certificates..." +mkcert -key-file nginx/ssl/key.pem -cert-file nginx/ssl/cert.pem \ + localhost 127.0.0.1 ::1 $LOCAL_IP *.local + +echo "βœ… Certificates generated" +echo "" + +# Create nginx config +cat > nginx/conf.d/https.conf << 'NGINX_EOF' +server { + listen 80; + server_name _; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + server_name _; + + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "DENY" always; + add_header X-Content-Type-Options "nosniff" always; + + location / { + proxy_pass http://app:8080; + 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_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} +NGINX_EOF + +echo "βœ… nginx config created" +echo "" + +# Create docker-compose override +cat > docker-compose.https.yml << 'COMPOSE_EOF' +services: + nginx: + image: nginx:alpine + container_name: timetracker-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/conf.d:/etc/nginx/conf.d:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + depends_on: + - app + restart: unless-stopped + + app: + ports: [] + environment: + - WTF_CSRF_SSL_STRICT=true + - SESSION_COOKIE_SECURE=true + - CSRF_COOKIE_SECURE=true +COMPOSE_EOF + +echo "βœ… docker-compose.https.yml created" +echo "" + +# Update .env if exists +if [ -f .env ]; then + cp .env .env.backup + sed -i.bak 's/^WTF_CSRF_SSL_STRICT=.*/WTF_CSRF_SSL_STRICT=true/' .env 2>/dev/null || echo "WTF_CSRF_SSL_STRICT=true" >> .env + sed -i.bak 's/^SESSION_COOKIE_SECURE=.*/SESSION_COOKIE_SECURE=true/' .env 2>/dev/null || echo "SESSION_COOKIE_SECURE=true" >> .env + sed -i.bak 's/^CSRF_COOKIE_SECURE=.*/CSRF_COOKIE_SECURE=true/' .env 2>/dev/null || echo "CSRF_COOKIE_SECURE=true" >> .env + rm -f .env.bak + echo "βœ… .env updated" +else + echo "⚠️ No .env file - create from env.example" +fi + +echo "" +echo "==========================================" +echo "βœ… HTTPS Setup Complete!" +echo "==========================================" +echo "" +echo "Start with HTTPS:" +echo " docker-compose -f docker-compose.yml -f docker-compose.https.yml up -d" +echo "" +echo "Access at:" +echo " https://localhost" +echo " https://$LOCAL_IP" +echo "" +echo "For other devices:" +echo " 1. Find CA: mkcert -CAROOT" +echo " 2. Copy rootCA.pem to device" +echo " 3. Import as trusted certificate" +echo "" + diff --git a/start-https.bat b/start-https.bat new file mode 100644 index 0000000..1fbbbd2 --- /dev/null +++ b/start-https.bat @@ -0,0 +1,133 @@ +@echo off +REM Start TimeTracker with automatic HTTPS +REM Automatically generates certificates and starts all services + +echo ========================================== +echo TimeTracker HTTPS Startup +echo ========================================== +echo. + +REM Get local IP +for /f "tokens=2 delims=:" %%a in ('ipconfig ^| findstr /c:"IPv4 Address"') do ( + set LOCAL_IP=%%a + goto :found_ip +) +:found_ip +set LOCAL_IP=%LOCAL_IP: =% +if "%LOCAL_IP%"=="" set LOCAL_IP=192.168.1.100 + +echo [INFO] Local IP detected: %LOCAL_IP% +echo. + +REM Create nginx config if it doesn't exist +if not exist nginx\conf.d\https.conf ( + echo [INFO] Creating nginx HTTPS configuration... + if not exist nginx\conf.d mkdir nginx\conf.d + + ( + echo server { + echo listen 80; + echo server_name _; + echo return 301 https://$host$request_uri; + echo } + echo. + echo server { + echo listen 443 ssl http2; + echo server_name _; + echo. + echo ssl_certificate /etc/nginx/ssl/cert.pem; + echo ssl_certificate_key /etc/nginx/ssl/key.pem; + echo. + echo ssl_protocols TLSv1.2 TLSv1.3; + echo ssl_ciphers HIGH:!aNULL:!MD5; + echo ssl_prefer_server_ciphers on; + echo. + echo add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + echo add_header X-Frame-Options "DENY" always; + echo add_header X-Content-Type-Options "nosniff" always; + echo. + echo location / { + echo proxy_pass http://app:8080; + echo proxy_set_header Host $host; + echo proxy_set_header X-Real-IP $remote_addr; + echo proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + echo proxy_set_header X-Forwarded-Proto $scheme; + echo + echo proxy_http_version 1.1; + echo proxy_set_header Upgrade $http_upgrade; + echo proxy_set_header Connection "upgrade"; + echo } + echo } + ) > nginx\conf.d\https.conf + + echo [OK] nginx configuration created + echo. +) + +REM Update .env with HTTPS settings +if exist .env ( + echo [INFO] Updating .env with HTTPS settings... + copy .env .env.backup >nul 2>&1 + + powershell -Command "$content = Get-Content .env; if ($content -match '^WTF_CSRF_SSL_STRICT=') { $content = $content -replace '^WTF_CSRF_SSL_STRICT=.*', 'WTF_CSRF_SSL_STRICT=true' } else { $content += 'WTF_CSRF_SSL_STRICT=true' }; if ($content -match '^SESSION_COOKIE_SECURE=') { $content = $content -replace '^SESSION_COOKIE_SECURE=.*', 'SESSION_COOKIE_SECURE=true' } else { $content += 'SESSION_COOKIE_SECURE=true' }; if ($content -match '^CSRF_COOKIE_SECURE=') { $content = $content -replace '^CSRF_COOKIE_SECURE=.*', 'CSRF_COOKIE_SECURE=true' } else { $content += 'CSRF_COOKIE_SECURE=true' }; $content | Set-Content .env" + + echo [OK] .env updated + echo. +) + +REM Set environment variable for docker-compose +set HOST_IP=%LOCAL_IP% + +REM Choose certificate method +echo Select certificate method: +echo 1^) Self-signed (works immediately, shows browser warning^) +echo 2^) mkcert (trusted certificates, requires mkcert installed^) +echo. +set /p CERT_METHOD="Choice [1]: " +if "%CERT_METHOD%"=="" set CERT_METHOD=1 + +echo. + +if "%CERT_METHOD%"=="2" ( + where mkcert >nul 2>&1 + if %errorlevel% equ 0 ( + echo [INFO] Using mkcert for trusted certificates... + docker-compose -f docker-compose.yml -f docker-compose.https-mkcert.yml up -d + ) else ( + echo [WARNING] mkcert not found. Using self-signed certificates instead. + echo Install mkcert: choco install mkcert + echo. + docker-compose -f docker-compose.yml -f docker-compose.https-auto.yml up -d + ) +) else ( + echo [INFO] Using self-signed certificates... + docker-compose -f docker-compose.yml -f docker-compose.https-auto.yml up -d +) + +echo. +echo ========================================== +echo [OK] TimeTracker is starting with HTTPS! +echo ========================================== +echo. +echo Access your application at: +echo https://localhost +echo https://%LOCAL_IP% +echo. + +if "%CERT_METHOD%"=="1" ( + echo [WARNING] Browser Warning Expected: + echo Self-signed certificates will show a security warning. + echo Click 'Advanced' - 'Proceed to localhost (unsafe^)' to continue. + echo. + echo For no warnings, run: setup-https-mkcert.bat +) + +echo. +echo View logs: +echo docker-compose logs -f +echo. +echo Stop services: +echo docker-compose down +echo. +pause + diff --git a/start-https.sh b/start-https.sh new file mode 100644 index 0000000..854d5f7 --- /dev/null +++ b/start-https.sh @@ -0,0 +1,146 @@ +#!/bin/bash +# Start TimeTracker with automatic HTTPS +# Automatically generates certificates and starts all services + +set -e + +echo "==========================================" +echo "TimeTracker HTTPS Startup" +echo "==========================================" +echo "" + +# Detect local IP +if [[ "$OSTYPE" == "darwin"* ]]; then + LOCAL_IP=$(ipconfig getifaddr en0 || ipconfig getifaddr en1 || echo "192.168.1.100") +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + LOCAL_IP=$(hostname -I | awk '{print $1}' || echo "192.168.1.100") +else + LOCAL_IP="192.168.1.100" +fi + +echo "🌐 Local IP detected: $LOCAL_IP" +echo "" + +# Create nginx config if it doesn't exist +if [ ! -f nginx/conf.d/https.conf ]; then + echo "πŸ“ Creating nginx HTTPS configuration..." + mkdir -p nginx/conf.d + + cat > nginx/conf.d/https.conf << 'NGINX_EOF' +server { + listen 80; + server_name _; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + server_name _; + + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options "DENY" always; + add_header X-Content-Type-Options "nosniff" always; + + location / { + proxy_pass http://app:8080; + 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_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} +NGINX_EOF + + echo "βœ… nginx configuration created" + echo "" +fi + +# Update .env with HTTPS settings if it exists +if [ -f .env ]; then + echo "πŸ”§ Updating .env with HTTPS settings..." + + # Backup .env + cp .env .env.backup 2>/dev/null || true + + # Update settings + sed -i.bak 's/^WTF_CSRF_SSL_STRICT=.*/WTF_CSRF_SSL_STRICT=true/' .env 2>/dev/null || echo "WTF_CSRF_SSL_STRICT=true" >> .env + sed -i.bak 's/^SESSION_COOKIE_SECURE=.*/SESSION_COOKIE_SECURE=true/' .env 2>/dev/null || echo "SESSION_COOKIE_SECURE=true" >> .env + sed -i.bak 's/^CSRF_COOKIE_SECURE=.*/CSRF_COOKIE_SECURE=true/' .env 2>/dev/null || echo "CSRF_COOKIE_SECURE=true" >> .env + + # Clean up + rm -f .env.bak + + echo "βœ… .env updated" + echo "" +fi + +# Export IP for docker-compose +export HOST_IP=$LOCAL_IP + +# Choose certificate method +echo "Select certificate method:" +echo " 1) Self-signed (works immediately, shows browser warning)" +echo " 2) mkcert (trusted certificates, requires one-time CA install)" +echo "" +read -p "Choice [1]: " CERT_METHOD +CERT_METHOD=${CERT_METHOD:-1} + +echo "" + +if [ "$CERT_METHOD" = "2" ]; then + # Check if mkcert is available + if command -v mkcert >/dev/null 2>&1; then + echo "πŸ” Using mkcert for trusted certificates..." + docker-compose -f docker-compose.yml -f docker-compose.https-mkcert.yml up -d + else + echo "⚠️ mkcert not found on host. Using self-signed certificates instead." + echo " Install mkcert for trusted certificates: brew install mkcert (Mac) or choco install mkcert (Windows)" + echo "" + docker-compose -f docker-compose.yml -f docker-compose.https-auto.yml up -d + fi +else + echo "πŸ” Using self-signed certificates..." + docker-compose -f docker-compose.yml -f docker-compose.https-auto.yml up -d +fi + +echo "" +echo "==========================================" +echo "βœ… TimeTracker is starting with HTTPS!" +echo "==========================================" +echo "" +echo "Access your application at:" +echo " https://localhost" +echo " https://$LOCAL_IP" +echo "" + +if [ "$CERT_METHOD" = "1" ] || ! command -v mkcert >/dev/null 2>&1; then + echo "⚠️ Browser Warning Expected:" + echo " Self-signed certificates will show a security warning." + echo " Click 'Advanced' β†’ 'Proceed to localhost (unsafe)' to continue." + echo "" + echo " For no warnings, run: bash setup-https-mkcert.sh" +else + echo "πŸ“‹ To avoid browser warnings:" + echo " Install the CA certificate from: nginx/ssl/rootCA.pem" + echo " See instructions above or in HTTPS_MKCERT_GUIDE.md" +fi + +echo "" +echo "View logs:" +echo " docker-compose logs -f" +echo "" +echo "Stop services:" +echo " docker-compose down" +echo "" +