Fixed login with only ip:port access
This commit is contained in:
40
Dockerfile
40
Dockerfile
@@ -1,34 +1,22 @@
|
|||||||
FROM python:3.11-slim
|
FROM python:3-alpine
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
COPY requirements.txt .
|
RUN pip install --no-cache-dir flask requests werkzeug
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
|
||||||
|
|
||||||
# Copy application files
|
# Copy files
|
||||||
COPY app.py .
|
COPY . /app/
|
||||||
COPY malias_wrapper.py .
|
|
||||||
COPY malias.py .
|
|
||||||
COPY reset_password.py .
|
|
||||||
COPY templates/ templates/
|
|
||||||
COPY static/ static/
|
|
||||||
|
|
||||||
# Create data directory
|
|
||||||
RUN mkdir -p /app/data
|
|
||||||
|
|
||||||
# Copy entrypoint script and make scripts executable
|
|
||||||
COPY docker-entrypoint.sh .
|
|
||||||
RUN chmod +x docker-entrypoint.sh reset_password.py
|
|
||||||
|
|
||||||
# Expose port
|
|
||||||
EXPOSE 5172
|
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV FLASK_PORT=5142 \
|
||||||
ENV FLASK_DEBUG=False
|
FLASK_HOST=0.0.0.0 \
|
||||||
ENV FLASK_ENV=production
|
FLASK_ENV=production \
|
||||||
|
FLASK_DEBUG=False \
|
||||||
|
ENABLE_PROXY=false
|
||||||
|
|
||||||
# Run the application via entrypoint
|
# Expose port
|
||||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
EXPOSE 5142
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["python", "app.py"]
|
||||||
352
PROXY_SETUP.md
352
PROXY_SETUP.md
@@ -1,322 +1,88 @@
|
|||||||
# Reverse Proxy Setup Guide
|
# Setting Up with a Reverse Proxy
|
||||||
|
|
||||||
This guide helps you configure reverse proxies (Nginx, Traefik, Zoraxy, Authelia, Caddy, etc.) to work with Mailcow Alias Manager.
|
This application supports both direct access and running behind a reverse proxy with authentication. The mode is controlled by the `ENABLE_PROXY` environment variable.
|
||||||
|
|
||||||
## ✅ Built-in Proxy Support
|
## Configuration Options
|
||||||
|
|
||||||
The application includes **ProxyFix middleware** that automatically handles:
|
### Direct Access Mode (default)
|
||||||
- ✅ HTTPS detection via `X-Forwarded-Proto`
|
|
||||||
- ✅ Host header forwarding via `X-Forwarded-Host`
|
|
||||||
- ✅ Client IP forwarding via `X-Forwarded-For`
|
|
||||||
- ✅ Path prefix support via `X-Forwarded-Prefix`
|
|
||||||
|
|
||||||
**No configuration changes needed in most cases!**
|
When `ENABLE_PROXY=false` (default), the application:
|
||||||
|
- Expects direct access via IP:port
|
||||||
|
- Uses non-secure cookies (suitable for HTTP)
|
||||||
|
- Relies only on the built-in authentication
|
||||||
|
|
||||||
---
|
Example docker-compose.yml for direct access:
|
||||||
|
```yaml
|
||||||
## Common Proxy Configurations
|
services:
|
||||||
|
mailcow-alias-manager:
|
||||||
### **Zoraxy** (Your Current Setup)
|
build: .
|
||||||
|
restart: unless-stopped
|
||||||
Zoraxy automatically sets the required headers. Just configure:
|
environment:
|
||||||
|
- FLASK_PORT=5142
|
||||||
1. **Upstream Target**: `http://localhost:5172` (or your Docker IP)
|
- ENABLE_PROXY=false
|
||||||
2. **Enable WebSocket**: Yes (optional, recommended)
|
volumes:
|
||||||
3. **Enable Proxy Headers**: Yes (should be default)
|
- ./data:/app/data
|
||||||
|
ports:
|
||||||
**Example Zoraxy Config:**
|
- "5142:5142"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "mailcow-alias-manager",
|
|
||||||
"upstream": "http://localhost:5172",
|
|
||||||
"domain": "aliases.yourdomain.com"
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
### Proxy Mode
|
||||||
|
|
||||||
### **Authelia** (Authentication Proxy)
|
When `ENABLE_PROXY=true`, the application:
|
||||||
|
- Is configured to work behind a reverse proxy
|
||||||
|
- Uses secure cookies (requires HTTPS)
|
||||||
|
- Can integrate with authentication providers like Authelia
|
||||||
|
|
||||||
If using Authelia in front of the app:
|
Example docker-compose.yml for proxy access:
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
mailcow-alias-manager:
|
||||||
|
build: .
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- FLASK_PORT=5142
|
||||||
|
- ENABLE_PROXY=true
|
||||||
|
volumes:
|
||||||
|
- ./data:/app/data
|
||||||
|
# No ports exposed - access only through proxy
|
||||||
|
networks:
|
||||||
|
- proxy-network
|
||||||
|
```
|
||||||
|
|
||||||
1. **Authelia forwards the user after login** - this should work automatically
|
## Setting Up with Nginx
|
||||||
2. **Make sure Authelia passes these headers**:
|
|
||||||
- `X-Forwarded-Proto`
|
|
||||||
- `X-Forwarded-Host`
|
|
||||||
- `X-Forwarded-For`
|
|
||||||
|
|
||||||
**Common Issue**: Authelia redirects → App login page
|
Here's a basic Nginx configuration for proxying to the application:
|
||||||
**Solution**: The app has its own authentication. You have two options:
|
|
||||||
- **Option A**: Disable app login (requires code modification)
|
|
||||||
- **Option B**: Use Authelia for network access, app login for API access
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **Nginx**
|
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
server {
|
server {
|
||||||
listen 443 ssl http2;
|
listen 443 ssl;
|
||||||
server_name aliases.yourdomain.com;
|
server_name alias.example.com;
|
||||||
|
|
||||||
|
# SSL configuration
|
||||||
ssl_certificate /path/to/cert.pem;
|
ssl_certificate /path/to/cert.pem;
|
||||||
ssl_certificate_key /path/to/key.pem;
|
ssl_certificate_key /path/to/key.pem;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://localhost:5172;
|
proxy_pass http://mailcow-alias-manager:5142;
|
||||||
|
|
||||||
# Required headers for ProxyFix
|
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
|
||||||
|
|
||||||
# Optional: WebSocket support
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## Setting Up with Zoraxy or Traefik
|
||||||
|
|
||||||
### **Traefik**
|
For Zoraxy or Traefik, make sure to:
|
||||||
|
|
||||||
**docker-compose.yml:**
|
1. Set `ENABLE_PROXY=true` in your container environment
|
||||||
```yaml
|
2. Configure the proxy to forward authentication headers if using an authentication provider
|
||||||
services:
|
3. Set up the appropriate redirect URLs
|
||||||
mailcow-alias-manager:
|
|
||||||
image: gitlab.pm/rune/malias-web:latest
|
|
||||||
container_name: mailcow-alias-manager
|
|
||||||
volumes:
|
|
||||||
- ./data:/app/data
|
|
||||||
restart: unless-stopped
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.malias.rule=Host(`aliases.yourdomain.com`)"
|
|
||||||
- "traefik.http.routers.malias.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.malias.tls=true"
|
|
||||||
- "traefik.http.routers.malias.tls.certresolver=letsencrypt"
|
|
||||||
- "traefik.http.services.malias.loadbalancer.server.port=5172"
|
|
||||||
networks:
|
|
||||||
- traefik
|
|
||||||
|
|
||||||
networks:
|
## Debugging
|
||||||
traefik:
|
|
||||||
external: true
|
|
||||||
```
|
|
||||||
|
|
||||||
Traefik automatically sets `X-Forwarded-*` headers by default.
|
When running behind a proxy, use the following endpoints for debugging:
|
||||||
|
- `/debug` - Shows detailed request information
|
||||||
---
|
- `/authelia-test` - Tests Authelia header forwarding
|
||||||
|
- `/health` - Shows basic health and authentication status
|
||||||
### **Caddy**
|
|
||||||
|
|
||||||
```caddy
|
|
||||||
aliases.yourdomain.com {
|
|
||||||
reverse_proxy localhost:5172
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Caddy automatically handles all proxy headers - no configuration needed!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Troubleshooting
|
|
||||||
|
|
||||||
### **Issue: "NetworkError when attempting to fetch resource"**
|
|
||||||
|
|
||||||
**Causes:**
|
|
||||||
1. Mixed HTTP/HTTPS content
|
|
||||||
2. CORS blocking requests
|
|
||||||
3. Session cookie not being set
|
|
||||||
4. Proxy not forwarding headers
|
|
||||||
|
|
||||||
**Solutions:**
|
|
||||||
|
|
||||||
#### **1. Check Proxy Headers**
|
|
||||||
Your proxy MUST forward these headers:
|
|
||||||
```
|
|
||||||
X-Forwarded-Proto: https
|
|
||||||
X-Forwarded-Host: aliases.yourdomain.com
|
|
||||||
X-Forwarded-For: <client-ip>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### **2. Check Browser Console**
|
|
||||||
Open browser DevTools (F12) → Console tab → Look for errors:
|
|
||||||
- **CORS errors**: Proxy misconfiguration
|
|
||||||
- **Mixed content**: HTTP resources on HTTPS page
|
|
||||||
- **401 Unauthorized**: Session/cookie issue
|
|
||||||
|
|
||||||
#### **3. Test Direct Access**
|
|
||||||
```bash
|
|
||||||
# Test without proxy
|
|
||||||
curl http://localhost:5172/
|
|
||||||
|
|
||||||
# Should return HTML (login page)
|
|
||||||
```
|
|
||||||
|
|
||||||
If this works but proxy doesn't, it's a proxy configuration issue.
|
|
||||||
|
|
||||||
#### **4. Check Session Cookies**
|
|
||||||
In browser DevTools → Application tab → Cookies:
|
|
||||||
- **Domain**: Should match your proxy domain
|
|
||||||
- **Path**: `/`
|
|
||||||
- **HttpOnly**: Should be `true`
|
|
||||||
- **Secure**: Depends on your setup
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **Issue: Login works but redirects to HTTP instead of HTTPS**
|
|
||||||
|
|
||||||
**Solution**: Already fixed in latest version via `PREFERRED_URL_SCHEME='https'` in app config.
|
|
||||||
|
|
||||||
If still happening:
|
|
||||||
1. Verify proxy sends `X-Forwarded-Proto: https`
|
|
||||||
2. Check `docker compose logs -f mailcow-alias-manager`
|
|
||||||
3. Rebuild image: `docker compose up -d --build`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **Issue: "Invalid password" but password is correct**
|
|
||||||
|
|
||||||
This is NOT a proxy issue - this is an authentication issue:
|
|
||||||
```bash
|
|
||||||
# Reset password
|
|
||||||
docker exec -it mailcow-alias-manager python3 reset_password.py "newpass"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **Issue: 502 Bad Gateway**
|
|
||||||
|
|
||||||
**Causes:**
|
|
||||||
1. App not running
|
|
||||||
2. Wrong upstream port
|
|
||||||
3. Container network issue
|
|
||||||
|
|
||||||
**Check:**
|
|
||||||
```bash
|
|
||||||
# Is container running?
|
|
||||||
docker ps | grep mailcow-alias-manager
|
|
||||||
|
|
||||||
# Check logs
|
|
||||||
docker compose logs -f mailcow-alias-manager
|
|
||||||
|
|
||||||
# Test internal connectivity
|
|
||||||
docker exec mailcow-alias-manager curl http://localhost:5172
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔒 Security Best Practices
|
|
||||||
|
|
||||||
### **1. HTTPS Only**
|
|
||||||
Always use HTTPS in production. HTTP is only for local testing.
|
|
||||||
|
|
||||||
### **2. Restrict Access**
|
|
||||||
Use firewall or proxy rules to restrict access:
|
|
||||||
```nginx
|
|
||||||
# Nginx: Restrict to internal network only
|
|
||||||
allow 192.168.1.0/24;
|
|
||||||
deny all;
|
|
||||||
```
|
|
||||||
|
|
||||||
### **3. Use Authelia/Authentik**
|
|
||||||
Add SSO layer for additional security:
|
|
||||||
- Users authenticate via Authelia
|
|
||||||
- Then app login provides API access
|
|
||||||
- Two-factor authentication recommended
|
|
||||||
|
|
||||||
### **4. Keep Session Cookies Secure**
|
|
||||||
The app sets:
|
|
||||||
- `HttpOnly=True` - Prevents JavaScript access
|
|
||||||
- `SameSite=Lax` - CSRF protection
|
|
||||||
|
|
||||||
For HTTPS-only deployments, you can enforce secure cookies:
|
|
||||||
```python
|
|
||||||
# In app.py, change:
|
|
||||||
SESSION_COOKIE_SECURE=True # Only send cookie over HTTPS
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Testing Your Setup
|
|
||||||
|
|
||||||
### **1. Basic Connectivity**
|
|
||||||
```bash
|
|
||||||
curl -v https://aliases.yourdomain.com/
|
|
||||||
# Should return 200 OK with HTML
|
|
||||||
```
|
|
||||||
|
|
||||||
### **2. Login Test**
|
|
||||||
```bash
|
|
||||||
curl -v -X POST https://aliases.yourdomain.com/login \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"password":"yourpassword"}' \
|
|
||||||
-c cookies.txt
|
|
||||||
|
|
||||||
# Check response - should be JSON with status: success
|
|
||||||
```
|
|
||||||
|
|
||||||
### **3. Session Test**
|
|
||||||
```bash
|
|
||||||
curl -v https://aliases.yourdomain.com/ \
|
|
||||||
-b cookies.txt
|
|
||||||
|
|
||||||
# Should return main page HTML (not login redirect)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🆘 Still Having Issues?
|
|
||||||
|
|
||||||
### **Enable Debug Logging**
|
|
||||||
|
|
||||||
**docker-compose.yml:**
|
|
||||||
```yaml
|
|
||||||
environment:
|
|
||||||
- FLASK_DEBUG=True # Enable debug mode
|
|
||||||
```
|
|
||||||
|
|
||||||
Restart:
|
|
||||||
```bash
|
|
||||||
docker compose down
|
|
||||||
docker compose up -d
|
|
||||||
docker compose logs -f
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Check Application Logs**
|
|
||||||
```bash
|
|
||||||
# View logs
|
|
||||||
docker compose logs -f mailcow-alias-manager
|
|
||||||
|
|
||||||
# Check for errors
|
|
||||||
docker compose logs mailcow-alias-manager | grep -i error
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Test Without Proxy**
|
|
||||||
Temporarily bypass proxy to isolate the issue:
|
|
||||||
```bash
|
|
||||||
# Direct access test
|
|
||||||
curl http://localhost:5172/
|
|
||||||
```
|
|
||||||
|
|
||||||
If this works, the issue is with proxy configuration, not the app.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Summary
|
|
||||||
|
|
||||||
**The app is already configured for reverse proxies!**
|
|
||||||
|
|
||||||
Key points:
|
|
||||||
- ✅ ProxyFix middleware is enabled
|
|
||||||
- ✅ Handles X-Forwarded-* headers automatically
|
|
||||||
- ✅ HTTPS redirect support built-in
|
|
||||||
- ✅ Session cookies work behind proxies
|
|
||||||
- ✅ No code changes needed
|
|
||||||
|
|
||||||
Just make sure your proxy forwards the standard headers and you're good to go! 🚀
|
|
||||||
314
app.py
314
app.py
@@ -18,82 +18,61 @@ logging.basicConfig(
|
|||||||
)
|
)
|
||||||
logger = logging.getLogger('mailcow-alias-manager')
|
logger = logging.getLogger('mailcow-alias-manager')
|
||||||
|
|
||||||
|
# Environment variable to control proxy mode
|
||||||
|
ENABLE_PROXY = os.getenv('ENABLE_PROXY', 'false').lower() in ('true', '1', 'yes')
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = os.getenv('SECRET_KEY', 'malias-default-secret-key-please-change') # Consistent secret key
|
app.secret_key = os.getenv('SECRET_KEY', 'malias-default-secret-key-please-change') # Consistent secret key
|
||||||
|
|
||||||
# Configure for reverse proxy - modified to handle multiple proxy layers including Authelia
|
# Configure app differently based on proxy mode
|
||||||
# Increasing the number of proxies to handle Zoraxy->Authelia->App chain
|
if ENABLE_PROXY:
|
||||||
app.wsgi_app = ProxyFix(
|
# Configure for reverse proxy - modified to handle multiple proxy layers including Authelia
|
||||||
app.wsgi_app,
|
# Increasing the number of proxies to handle Zoraxy->Authelia->App chain
|
||||||
x_for=2, # Trust X-Forwarded-For with 2 proxies (Zoraxy + Authelia)
|
app.wsgi_app = ProxyFix(
|
||||||
x_proto=2, # Trust X-Forwarded-Proto (http/https)
|
app.wsgi_app,
|
||||||
x_host=2, # Trust X-Forwarded-Host
|
x_for=2, # Trust X-Forwarded-For with 2 proxies (Zoraxy + Authelia)
|
||||||
x_prefix=2 # Trust X-Forwarded-Prefix
|
x_proto=2, # Trust X-Forwarded-Proto (http/https)
|
||||||
)
|
x_host=2, # Trust X-Forwarded-Host
|
||||||
|
x_prefix=2 # Trust X-Forwarded-Prefix
|
||||||
|
)
|
||||||
|
|
||||||
|
# Session configuration optimized for reverse proxy with Authelia
|
||||||
|
app.config.update(
|
||||||
|
PERMANENT_SESSION_LIFETIME=timedelta(hours=24),
|
||||||
|
SESSION_COOKIE_NAME='malias_session', # Unique name to avoid conflicts with Authelia
|
||||||
|
SESSION_COOKIE_SECURE=True, # Always use secure cookies with Authelia
|
||||||
|
SESSION_COOKIE_HTTPONLY=True,
|
||||||
|
SESSION_COOKIE_SAMESITE='None', # Required for authentication proxies
|
||||||
|
SESSION_COOKIE_PATH='/',
|
||||||
|
SESSION_COOKIE_DOMAIN=None, # Let browser auto-set domain
|
||||||
|
SESSION_REFRESH_EACH_REQUEST=True, # Keep session alive
|
||||||
|
PREFERRED_URL_SCHEME='https'
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Running in PROXY mode (with Authelia integration)")
|
||||||
|
else:
|
||||||
|
# Direct mode - simpler configuration for IP:port access
|
||||||
|
app.config.update(
|
||||||
|
PERMANENT_SESSION_LIFETIME=timedelta(hours=24),
|
||||||
|
SESSION_COOKIE_NAME='malias_session',
|
||||||
|
SESSION_COOKIE_SECURE=False, # Allow non-HTTPS cookies for direct IP access
|
||||||
|
SESSION_COOKIE_HTTPONLY=True,
|
||||||
|
SESSION_COOKIE_SAMESITE='Lax', # More compatible SameSite setting
|
||||||
|
SESSION_COOKIE_PATH='/',
|
||||||
|
SESSION_REFRESH_EACH_REQUEST=True
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Running in DIRECT ACCESS mode (no reverse proxy integration)")
|
||||||
|
|
||||||
# Initialize database on startup
|
# Initialize database on startup
|
||||||
malias_w.init_database()
|
malias_w.init_database()
|
||||||
|
|
||||||
# Session configuration optimized for reverse proxy with Authelia
|
|
||||||
app.config.update(
|
|
||||||
PERMANENT_SESSION_LIFETIME=timedelta(hours=24),
|
|
||||||
SESSION_COOKIE_NAME='malias_session', # Unique name to avoid conflicts with Authelia
|
|
||||||
SESSION_COOKIE_SECURE=True, # Always use secure cookies with Authelia
|
|
||||||
SESSION_COOKIE_HTTPONLY=True,
|
|
||||||
SESSION_COOKIE_SAMESITE='None', # Required for authentication proxies
|
|
||||||
SESSION_COOKIE_PATH='/',
|
|
||||||
SESSION_COOKIE_DOMAIN=None, # Let browser auto-set domain
|
|
||||||
SESSION_REFRESH_EACH_REQUEST=True, # Keep session alive
|
|
||||||
PREFERRED_URL_SCHEME='https'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set this to True to use the special cookie fix for Zoraxy
|
|
||||||
ZORAXY_COOKIE_FIX = True
|
|
||||||
|
|
||||||
def fix_cookie_for_zoraxy(response):
|
|
||||||
"""Special handler for Zoraxy cookie issues with SameSite=None"""
|
|
||||||
if not ZORAXY_COOKIE_FIX:
|
|
||||||
return response
|
|
||||||
|
|
||||||
# Get all cookies from the response
|
|
||||||
cookies = response.headers.getlist('Set-Cookie')
|
|
||||||
if not cookies:
|
|
||||||
return response
|
|
||||||
|
|
||||||
# Clear existing cookies
|
|
||||||
del response.headers['Set-Cookie']
|
|
||||||
|
|
||||||
# Fix each cookie and add it back
|
|
||||||
for cookie in cookies:
|
|
||||||
if 'malias_session' in cookie and 'SameSite' not in cookie:
|
|
||||||
# Add SameSite=None and Secure attributes
|
|
||||||
if 'HttpOnly' in cookie:
|
|
||||||
cookie = cookie.replace('HttpOnly', 'HttpOnly; SameSite=None; Secure')
|
|
||||||
else:
|
|
||||||
cookie += '; SameSite=None; Secure'
|
|
||||||
response.headers.add('Set-Cookie', cookie)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
@app.after_request
|
|
||||||
def after_request(response):
|
|
||||||
"""Process the response before it's sent"""
|
|
||||||
# Apply special cookie fix for Zoraxy
|
|
||||||
response = fix_cookie_for_zoraxy(response)
|
|
||||||
|
|
||||||
# Set CORS headers to allow Zoraxy and Authelia to work together
|
|
||||||
response.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin', '*')
|
|
||||||
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
|
||||||
|
|
||||||
# Cache control
|
|
||||||
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
|
||||||
response.headers['Pragma'] = 'no-cache'
|
|
||||||
response.headers['Expires'] = '0'
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def get_authelia_user():
|
def get_authelia_user():
|
||||||
"""Helper to get authenticated user from Authelia headers"""
|
"""Helper to get authenticated user from Authelia headers"""
|
||||||
|
# Only check for Authelia if proxy mode is enabled
|
||||||
|
if not ENABLE_PROXY:
|
||||||
|
return None
|
||||||
|
|
||||||
# Check several possible header variations
|
# Check several possible header variations
|
||||||
auth_headers = [
|
auth_headers = [
|
||||||
'Remote-User',
|
'Remote-User',
|
||||||
@@ -131,26 +110,27 @@ def login_required(f):
|
|||||||
"""Decorator to require login for routes"""
|
"""Decorator to require login for routes"""
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
# Check for Authelia authentication
|
# Check for Authelia authentication when in proxy mode
|
||||||
authelia_user = get_authelia_user()
|
if ENABLE_PROXY:
|
||||||
|
authelia_user = get_authelia_user()
|
||||||
# If Authelia authenticated the user, update local session
|
|
||||||
if authelia_user:
|
# If Authelia authenticated the user, update local session
|
||||||
# Log all headers for debugging
|
if authelia_user:
|
||||||
if app.debug:
|
# Log all headers for debugging
|
||||||
logger.info(f"Headers for authenticated request: {dict(request.headers)}")
|
if app.debug:
|
||||||
|
logger.info(f"Headers for authenticated request: {dict(request.headers)}")
|
||||||
if not session.get('logged_in') or session.get('authelia_user') != authelia_user:
|
|
||||||
logger.info(f"Auto-login via Authelia for user: {authelia_user}")
|
if not session.get('logged_in') or session.get('authelia_user') != authelia_user:
|
||||||
session.clear()
|
logger.info(f"Auto-login via Authelia for user: {authelia_user}")
|
||||||
session.permanent = True
|
session.clear()
|
||||||
session['logged_in'] = True
|
session.permanent = True
|
||||||
session['authelia_user'] = authelia_user
|
session['logged_in'] = True
|
||||||
session['user_token'] = secrets.token_urlsafe(32)
|
session['authelia_user'] = authelia_user
|
||||||
session['auth_method'] = 'authelia'
|
session['user_token'] = secrets.token_urlsafe(32)
|
||||||
session.modified = True
|
session['auth_method'] = 'authelia'
|
||||||
|
session.modified = True
|
||||||
return f(*args, **kwargs)
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
# Regular session check
|
# Regular session check
|
||||||
if not session.get('logged_in'):
|
if not session.get('logged_in'):
|
||||||
@@ -165,15 +145,17 @@ def login_required(f):
|
|||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
"""Login page"""
|
"""Login page"""
|
||||||
# First, try Authelia authentication
|
# Only check for Authelia if in proxy mode
|
||||||
authelia_user = get_authelia_user()
|
authelia_user = None
|
||||||
|
if ENABLE_PROXY:
|
||||||
|
authelia_user = get_authelia_user()
|
||||||
|
|
||||||
# Debug logging for all requests
|
# Debug logging for all requests
|
||||||
if app.debug:
|
if app.debug:
|
||||||
logger.info(f"Login route: method={request.method}, headers={dict(request.headers)}")
|
logger.info(f"Login route: method={request.method}, headers={dict(request.headers)}")
|
||||||
|
|
||||||
# If Authelia authenticated, login and redirect to index
|
# If Authelia authenticated (and we're in proxy mode), login and redirect to index
|
||||||
if authelia_user:
|
if ENABLE_PROXY and authelia_user:
|
||||||
logger.info(f"Login via Authelia for user: {authelia_user}")
|
logger.info(f"Login via Authelia for user: {authelia_user}")
|
||||||
session.clear()
|
session.clear()
|
||||||
session.permanent = True
|
session.permanent = True
|
||||||
@@ -183,19 +165,7 @@ def login():
|
|||||||
session['auth_method'] = 'authelia'
|
session['auth_method'] = 'authelia'
|
||||||
session.modified = True
|
session.modified = True
|
||||||
|
|
||||||
# Set a cookie manually to ensure it's properly formatted for Zoraxy
|
return redirect(url_for('index'))
|
||||||
response = redirect(url_for('index'))
|
|
||||||
# Set cookie parameters to work with Zoraxy/Authelia
|
|
||||||
response.set_cookie(
|
|
||||||
key=app.config['SESSION_COOKIE_NAME'],
|
|
||||||
value=secrets.token_urlsafe(32), # Generate a new token instead of using session.sid
|
|
||||||
max_age=int(app.config['PERMANENT_SESSION_LIFETIME'].total_seconds()),
|
|
||||||
path=app.config['SESSION_COOKIE_PATH'],
|
|
||||||
secure=app.config['SESSION_COOKIE_SECURE'],
|
|
||||||
httponly=app.config['SESSION_COOKIE_HTTPONLY'],
|
|
||||||
samesite='None'
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
|
|
||||||
# Handle form submission for local authentication
|
# Handle form submission for local authentication
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
@@ -212,23 +182,7 @@ def login():
|
|||||||
session.modified = True
|
session.modified = True
|
||||||
|
|
||||||
# Return JSON response
|
# Return JSON response
|
||||||
response = jsonify({'status': 'success', 'message': 'Login successful'})
|
return jsonify({'status': 'success', 'message': 'Login successful'})
|
||||||
|
|
||||||
# Manually set cookie with correct parameters for Zoraxy
|
|
||||||
if ZORAXY_COOKIE_FIX:
|
|
||||||
session_token = secrets.token_urlsafe(32) # Generate a new token
|
|
||||||
response.set_cookie(
|
|
||||||
app.config['SESSION_COOKIE_NAME'],
|
|
||||||
session_token,
|
|
||||||
max_age=int(app.config['PERMANENT_SESSION_LIFETIME'].total_seconds()),
|
|
||||||
secure=app.config['SESSION_COOKIE_SECURE'],
|
|
||||||
httponly=app.config['SESSION_COOKIE_HTTPONLY'],
|
|
||||||
samesite='None',
|
|
||||||
path=app.config['SESSION_COOKIE_PATH']
|
|
||||||
)
|
|
||||||
logger.info(f"Set fixed cookie for Zoraxy: {app.config['SESSION_COOKIE_NAME']}")
|
|
||||||
|
|
||||||
return response
|
|
||||||
else:
|
else:
|
||||||
logger.warning("Login failed: Invalid password")
|
logger.warning("Login failed: Invalid password")
|
||||||
return jsonify({'status': 'error', 'message': 'Invalid password'})
|
return jsonify({'status': 'error', 'message': 'Invalid password'})
|
||||||
@@ -258,8 +212,8 @@ def logout():
|
|||||||
# Clear local session
|
# Clear local session
|
||||||
session.clear()
|
session.clear()
|
||||||
|
|
||||||
# If user was authenticated via Authelia, try to redirect to Authelia logout
|
# If user was authenticated via Authelia (and we're in proxy mode), try to redirect to Authelia logout
|
||||||
if auth_method == 'authelia' or authelia_user:
|
if ENABLE_PROXY and (auth_method == 'authelia' or authelia_user):
|
||||||
# Look for Authelia URL in headers
|
# Look for Authelia URL in headers
|
||||||
authelia_url = request.headers.get('X-Authelia-URL')
|
authelia_url = request.headers.get('X-Authelia-URL')
|
||||||
|
|
||||||
@@ -284,47 +238,25 @@ def logout():
|
|||||||
return redirect(common_authelia_urls[0])
|
return redirect(common_authelia_urls[0])
|
||||||
|
|
||||||
# Default case: redirect to login page
|
# Default case: redirect to login page
|
||||||
response = redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
# Clear cookie by setting expired date
|
|
||||||
response.set_cookie(app.config['SESSION_COOKIE_NAME'], '', expires=0)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
"""Main page - requires login"""
|
"""Main page - requires login"""
|
||||||
# Try to auto-login with Authelia
|
# Try to auto-login with Authelia (only in proxy mode)
|
||||||
authelia_user = get_authelia_user()
|
if ENABLE_PROXY:
|
||||||
|
authelia_user = get_authelia_user()
|
||||||
if authelia_user and not session.get('logged_in'):
|
|
||||||
# Auto-login for users authenticated by Authelia
|
|
||||||
logger.info(f"Auto-login via Authelia for user: {authelia_user}")
|
|
||||||
session.clear()
|
|
||||||
session.permanent = True
|
|
||||||
session['logged_in'] = True
|
|
||||||
session['authelia_user'] = authelia_user
|
|
||||||
session['user_token'] = secrets.token_urlsafe(32)
|
|
||||||
session['auth_method'] = 'authelia'
|
|
||||||
session.modified = True
|
|
||||||
|
|
||||||
# Set cookie manually with correct parameters
|
if authelia_user and not session.get('logged_in'):
|
||||||
response = make_response(render_template('index.html'))
|
# Auto-login for users authenticated by Authelia
|
||||||
|
logger.info(f"Auto-login via Authelia for user: {authelia_user}")
|
||||||
if ZORAXY_COOKIE_FIX:
|
session.clear()
|
||||||
session_token = secrets.token_urlsafe(32) # Generate a new token
|
session.permanent = True
|
||||||
response.set_cookie(
|
session['logged_in'] = True
|
||||||
app.config['SESSION_COOKIE_NAME'],
|
session['authelia_user'] = authelia_user
|
||||||
session_token,
|
session['user_token'] = secrets.token_urlsafe(32)
|
||||||
max_age=int(app.config['PERMANENT_SESSION_LIFETIME'].total_seconds()),
|
session['auth_method'] = 'authelia'
|
||||||
secure=app.config['SESSION_COOKIE_SECURE'],
|
session.modified = True
|
||||||
httponly=app.config['SESSION_COOKIE_HTTPONLY'],
|
|
||||||
samesite='None',
|
|
||||||
path=app.config['SESSION_COOKIE_PATH']
|
|
||||||
)
|
|
||||||
logger.info(f"Set fixed cookie for Zoraxy on index: {app.config['SESSION_COOKIE_NAME']}")
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
# Check if logged in
|
# Check if logged in
|
||||||
if not session.get('logged_in'):
|
if not session.get('logged_in'):
|
||||||
@@ -569,10 +501,11 @@ def health_check():
|
|||||||
'authenticated': session.get('logged_in', False),
|
'authenticated': session.get('logged_in', False),
|
||||||
'authelia_user': session.get('authelia_user', None),
|
'authelia_user': session.get('authelia_user', None),
|
||||||
'auth_method': session.get('auth_method'),
|
'auth_method': session.get('auth_method'),
|
||||||
'version': '1.0.2',
|
'version': '1.0.3',
|
||||||
'cookies': {k: '***' for k in request.cookies.keys()}, # Only show cookie names
|
'cookies': {k: '***' for k in request.cookies.keys()}, # Only show cookie names
|
||||||
'authelia_headers_present': get_authelia_user() is not None,
|
'authelia_headers_present': get_authelia_user() is not None,
|
||||||
'zoraxy_headers_present': 'X-Forwarded-Server' in request.headers
|
'proxy_mode_enabled': ENABLE_PROXY,
|
||||||
|
'zoraxy_detected': 'X-Forwarded-Server' in request.headers
|
||||||
})
|
})
|
||||||
|
|
||||||
# Add a debugging endpoint
|
# Add a debugging endpoint
|
||||||
@@ -589,11 +522,12 @@ def debug_info():
|
|||||||
'host': request.host,
|
'host': request.host,
|
||||||
'path': request.path,
|
'path': request.path,
|
||||||
'is_secure': request.is_secure,
|
'is_secure': request.is_secure,
|
||||||
|
'proxy_mode_enabled': ENABLE_PROXY,
|
||||||
'x_forwarded_for': request.headers.get('X-Forwarded-For'),
|
'x_forwarded_for': request.headers.get('X-Forwarded-For'),
|
||||||
'x_forwarded_proto': request.headers.get('X-Forwarded-Proto'),
|
'x_forwarded_proto': request.headers.get('X-Forwarded-Proto'),
|
||||||
'x_forwarded_host': request.headers.get('X-Forwarded-Host'),
|
'x_forwarded_host': request.headers.get('X-Forwarded-Host'),
|
||||||
'x_forwarded_prefix': request.headers.get('X-Forwarded-Prefix'),
|
'x_forwarded_prefix': request.headers.get('X-Forwarded-Prefix'),
|
||||||
'remote_user': get_authelia_user(),
|
'remote_user': get_authelia_user()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Additional server information
|
# Additional server information
|
||||||
@@ -607,8 +541,8 @@ def debug_info():
|
|||||||
},
|
},
|
||||||
'app_config': {
|
'app_config': {
|
||||||
'DEBUG': app.debug,
|
'DEBUG': app.debug,
|
||||||
'PREFERRED_URL_SCHEME': app.config['PREFERRED_URL_SCHEME'],
|
'PREFERRED_URL_SCHEME': app.config.get('PREFERRED_URL_SCHEME', 'http'),
|
||||||
'ZORAXY_COOKIE_FIX': ZORAXY_COOKIE_FIX
|
'ENABLE_PROXY': ENABLE_PROXY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,50 +552,6 @@ def debug_info():
|
|||||||
response.headers['Expires'] = '0'
|
response.headers['Expires'] = '0'
|
||||||
return response
|
return response
|
||||||
|
|
||||||
# Dedicated endpoint for header diagnostics
|
|
||||||
@app.route('/headers')
|
|
||||||
def show_headers():
|
|
||||||
"""Show all request headers - useful for debugging proxies"""
|
|
||||||
# Log headers to help diagnose issues with Zoraxy/Authelia
|
|
||||||
logger.info(f"Headers endpoint: All headers received: {dict(request.headers)}")
|
|
||||||
return jsonify({
|
|
||||||
'headers': dict(request.headers),
|
|
||||||
'remote_addr': request.remote_addr,
|
|
||||||
'authelia_user': get_authelia_user()
|
|
||||||
})
|
|
||||||
|
|
||||||
# Self-test endpoint for cookies
|
|
||||||
@app.route('/cookie-test')
|
|
||||||
def cookie_test():
|
|
||||||
"""Test cookie handling"""
|
|
||||||
# Clear any existing cookie
|
|
||||||
resp = make_response(jsonify({'status': 'ok', 'message': 'Cookie set test'}))
|
|
||||||
|
|
||||||
# Set a test cookie with the same attributes as session
|
|
||||||
resp.set_cookie(
|
|
||||||
'malias_test_cookie',
|
|
||||||
'test-value',
|
|
||||||
max_age=3600,
|
|
||||||
secure=app.config['SESSION_COOKIE_SECURE'],
|
|
||||||
httponly=app.config['SESSION_COOKIE_HTTPONLY'],
|
|
||||||
samesite='None',
|
|
||||||
path=app.config['SESSION_COOKIE_PATH']
|
|
||||||
)
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
|
||||||
# Endpoint to check if test cookie is set
|
|
||||||
@app.route('/cookie-check')
|
|
||||||
def cookie_check():
|
|
||||||
"""Check if test cookie was properly set"""
|
|
||||||
test_cookie = request.cookies.get('malias_test_cookie')
|
|
||||||
return jsonify({
|
|
||||||
'test_cookie_present': test_cookie is not None,
|
|
||||||
'test_cookie_value': test_cookie if test_cookie else None,
|
|
||||||
'all_cookies': {k: '***' for k in request.cookies.keys()}
|
|
||||||
})
|
|
||||||
|
|
||||||
# New endpoint to test Zoraxy auth configuration
|
|
||||||
@app.route('/authelia-test')
|
@app.route('/authelia-test')
|
||||||
def authelia_test():
|
def authelia_test():
|
||||||
"""Test if Authelia headers are correctly passed through Zoraxy"""
|
"""Test if Authelia headers are correctly passed through Zoraxy"""
|
||||||
@@ -672,6 +562,7 @@ def authelia_test():
|
|||||||
auth_related_headers = [
|
auth_related_headers = [
|
||||||
'Remote-User', 'X-Remote-User', 'Remote-Groups', 'X-Remote-Groups',
|
'Remote-User', 'X-Remote-User', 'Remote-Groups', 'X-Remote-Groups',
|
||||||
'Remote-Name', 'X-Remote-Name', 'Remote-Email', 'X-Remote-Email',
|
'Remote-Name', 'X-Remote-Name', 'Remote-Email', 'X-Remote-Email',
|
||||||
|
'X-Authelia-Username', 'X-Authelia-Groups', 'X-Authelia-DisplayName', 'X-Authelia-Email',
|
||||||
'X-Authelia-URL', 'X-Original-URL', 'X-Forwarded-Proto'
|
'X-Authelia-URL', 'X-Original-URL', 'X-Forwarded-Proto'
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -688,6 +579,7 @@ def authelia_test():
|
|||||||
auth_cookies[cookie_name] = '***' # Hide actual value
|
auth_cookies[cookie_name] = '***' # Hide actual value
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
|
'proxy_mode_enabled': ENABLE_PROXY,
|
||||||
'request_host': request.host,
|
'request_host': request.host,
|
||||||
'authelia_user_detected': get_authelia_user() is not None,
|
'authelia_user_detected': get_authelia_user() is not None,
|
||||||
'authelia_user': get_authelia_user(),
|
'authelia_user': get_authelia_user(),
|
||||||
@@ -696,7 +588,7 @@ def authelia_test():
|
|||||||
'all_headers_count': len(all_headers),
|
'all_headers_count': len(all_headers),
|
||||||
'zoraxy_detected': any('zoraxy' in h.lower() for h in all_headers.keys()) or 'X-Forwarded-Server' in all_headers,
|
'zoraxy_detected': any('zoraxy' in h.lower() for h in all_headers.keys()) or 'X-Forwarded-Server' in all_headers,
|
||||||
'host_header': request.headers.get('Host'),
|
'host_header': request.headers.get('Host'),
|
||||||
'referer': request.headers.get('Referer'),
|
'referer': request.headers.get('Referer')
|
||||||
})
|
})
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -717,6 +609,7 @@ Environment Variables (Docker):
|
|||||||
FLASK_PORT Port to run on (default: 5172)
|
FLASK_PORT Port to run on (default: 5172)
|
||||||
FLASK_HOST Host to bind to (default: 0.0.0.0)
|
FLASK_HOST Host to bind to (default: 0.0.0.0)
|
||||||
FLASK_ENV Set to 'production' or 'development'
|
FLASK_ENV Set to 'production' or 'development'
|
||||||
|
ENABLE_PROXY Set to 'true' to enable proxy/Authelia integration (default: false)
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -772,6 +665,7 @@ Environment Variables (Docker):
|
|||||||
print(f' Host: {host}')
|
print(f' Host: {host}')
|
||||||
print(f' Port: {port}')
|
print(f' Port: {port}')
|
||||||
print(f' URL: http://{host}:{port}')
|
print(f' URL: http://{host}:{port}')
|
||||||
|
print(f' Proxy: {"ENABLED" if ENABLE_PROXY else "DISABLED"}')
|
||||||
print('=' * 60)
|
print('=' * 60)
|
||||||
if debug_mode:
|
if debug_mode:
|
||||||
print(' ⚠️ WARNING: Debug mode is enabled!')
|
print(' ⚠️ WARNING: Debug mode is enabled!')
|
||||||
@@ -781,6 +675,6 @@ Environment Variables (Docker):
|
|||||||
print('=' * 60)
|
print('=' * 60)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
logger.info(f"Starting Mailcow Alias Manager - Debug: {debug_mode}, Host: {host}, Port: {port}")
|
logger.info(f"Starting Mailcow Alias Manager - Debug: {debug_mode}, Host: {host}, Port: {port}, Proxy: {ENABLE_PROXY}")
|
||||||
|
|
||||||
app.run(debug=debug_mode, host=host, port=port)
|
app.run(debug=debug_mode, host=host, port=port)
|
||||||
@@ -1,46 +1,17 @@
|
|||||||
# Mailcow Alias Manager - Docker Compose Configuration
|
version: '3'
|
||||||
#
|
|
||||||
# Production vs Development Mode:
|
|
||||||
# --------------------------------
|
|
||||||
# FLASK_ENV=production + FLASK_DEBUG=False → Uses Gunicorn (4 workers, production-ready)
|
|
||||||
# FLASK_ENV=development or FLASK_DEBUG=True → Uses Flask dev server (auto-reload, debug info)
|
|
||||||
#
|
|
||||||
# Recommended Settings:
|
|
||||||
# - Production: FLASK_ENV=production, FLASK_DEBUG=False (default)
|
|
||||||
# - Development: FLASK_ENV=development, FLASK_DEBUG=True
|
|
||||||
#
|
|
||||||
# Reverse Proxy Support:
|
|
||||||
# ----------------------
|
|
||||||
# This application is configured to work behind reverse proxies (Nginx, Traefik, Zoraxy, Authelia, etc.)
|
|
||||||
# The ProxyFix middleware automatically handles X-Forwarded-* headers for HTTPS detection
|
|
||||||
# No additional configuration needed for most standard proxy setups
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
mailcow-alias-manager:
|
mailcow-alias-manager:
|
||||||
image: gitlab.pm/rune/malias-web:latest
|
build: .
|
||||||
container_name: mailcow-alias-manager
|
|
||||||
ports:
|
|
||||||
- "5172:5172"
|
|
||||||
volumes:
|
|
||||||
- ./data:/app/data
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- TZ=Europe/Oslo
|
- FLASK_PORT=5142
|
||||||
|
|
||||||
# Flask Configuration
|
|
||||||
# Set to 'production' for Gunicorn (recommended) or 'development' for Flask dev server
|
|
||||||
- FLASK_ENV=production
|
|
||||||
|
|
||||||
# Debug mode: False for production (recommended), True for development/troubleshooting
|
|
||||||
- FLASK_DEBUG=False
|
|
||||||
|
|
||||||
# Port configuration (default: 5172)
|
|
||||||
- FLASK_PORT=5172
|
|
||||||
|
|
||||||
# Host binding (default: 0.0.0.0 for Docker)
|
|
||||||
- FLASK_HOST=0.0.0.0
|
- FLASK_HOST=0.0.0.0
|
||||||
|
- FLASK_ENV=production
|
||||||
# Secret key for sessions - CRITICAL for multi-worker Gunicorn!
|
- FLASK_DEBUG=False
|
||||||
# All workers must use the SAME secret key to decode session cookies
|
# Set to 'true' to enable proxy/Authelia integration, 'false' for direct IP:port access
|
||||||
# Generate with: python3 -c "import secrets; print(secrets.token_hex(32))"
|
- ENABLE_PROXY=false
|
||||||
- SECRET_KEY=dca6920115b8bbc13c346c75d668a49849590c77d9baaf903296582f24ff816a
|
volumes:
|
||||||
|
- ./data:/app/data
|
||||||
|
ports:
|
||||||
|
- "5142:5142"
|
||||||
Reference in New Issue
Block a user