Fixed login with only ip:port access

This commit is contained in:
2026-01-23 13:32:19 +01:00
parent 3b78959901
commit 877ecf32b4
5 changed files with 214 additions and 89 deletions

View File

@@ -29,6 +29,7 @@ EXPOSE 5172
ENV PYTHONUNBUFFERED=1
ENV FLASK_DEBUG=False
ENV FLASK_ENV=production
ENV ENABLE_PROXY=false
# Run the application via entrypoint
ENTRYPOINT ["./docker-entrypoint.sh"]

View File

@@ -2,7 +2,114 @@
This guide helps you configure reverse proxies (Nginx, Traefik, Zoraxy, Authelia, Caddy, etc.) to work with Mailcow Alias Manager.
## ✅ Built-in Proxy Support
---
## 🔧 ENABLE_PROXY Configuration
The application supports **two access modes** controlled by the `ENABLE_PROXY` environment variable in `docker-compose.yml`:
### **Mode 1: Direct Access (ENABLE_PROXY=false)** - DEFAULT
Use this when accessing the application directly via IP:port **without a reverse proxy**.
**docker-compose.yml:**
```yaml
environment:
- ENABLE_PROXY=false # Default setting
```
**Access:** `http://192.168.1.100:5172` (replace with your server IP)
**Features:**
- ✅ Works over HTTP (no HTTPS required)
- ✅ Standard cookie behavior (SameSite=Lax)
- ✅ No proxy configuration needed
- ✅ Simple login flow
- ✅ Perfect for internal/LAN access
**When to use:**
- Accessing from internal network only
- No reverse proxy in place
- Testing or development
- Simple single-server setup
---
### **Mode 2: Proxy Access (ENABLE_PROXY=true)**
Use this when running **behind a reverse proxy** (Authelia, Zoraxy, Nginx, Traefik, Caddy).
**docker-compose.yml:**
```yaml
environment:
- ENABLE_PROXY=true
```
**Access:** `https://alias.yourdomain.com` (through your reverse proxy)
**Features:**
- ✅ ProxyFix middleware handles X-Forwarded-* headers
- ✅ HTTPS redirect support
- ✅ Secure cookies (HTTPS only)
- ✅ Works with authentication proxies (Authelia)
- ✅ Multi-proxy chain support
**When to use:**
- Accessing from internet via domain name
- Behind Nginx, Traefik, Caddy, HAProxy
- Behind authentication proxy (Authelia, Authentik)
- SSL/TLS termination at proxy
- Production deployments with HTTPS
---
### **Switching Between Modes**
To switch from one mode to another:
1. **Edit `docker-compose.yml`**
```yaml
# Change this line:
- ENABLE_PROXY=false # or true
```
2. **Restart the container**
```bash
docker compose down
docker compose up -d
```
3. **Verify mode in logs**
```bash
docker compose logs mailcow-alias-manager | grep "ACCESS MODE"
```
You should see either:
- `ACCESS MODE: Direct IP:Port (ENABLE_PROXY=false)`
- `ACCESS MODE: Reverse Proxy (ENABLE_PROXY=true)`
4. **Clear browser cookies** (IMPORTANT!)
- Press F12 → Application → Cookies
- Delete all cookies for your domain
- Close and reopen browser
5. **Login again**
---
### **Quick Reference Table**
| Access Method | ENABLE_PROXY | Access URL | Cookie Mode |
|--------------|--------------|------------|-------------|
| Direct IP:port | `false` (default) | `http://192.168.1.100:5172` | HTTP, SameSite=Lax |
| Nginx/Traefik | `true` | `https://alias.example.com` | HTTPS, SameSite=None |
| Authelia + Zoraxy | `true` | `https://alias.example.com` | HTTPS, SameSite=None |
| Caddy | `true` | `https://alias.example.com` | HTTPS, SameSite=None |
| Local dev (`python3 app.py`) | N/A (not set) | `http://localhost:5172` | HTTP, SameSite=Lax |
---
## ✅ Built-in Proxy Support (When ENABLE_PROXY=true)
The application includes **ProxyFix middleware** that automatically handles:
- ✅ HTTPS detection via `X-Forwarded-Proto`
@@ -10,7 +117,7 @@ The application includes **ProxyFix middleware** that automatically handles:
- ✅ Client IP forwarding via `X-Forwarded-For`
- ✅ Path prefix support via `X-Forwarded-Prefix`
**No configuration changes needed in most cases!**
**Works with 2 proxies in chain** (e.g., Zoraxy → Authelia → App)
---

View File

@@ -68,7 +68,30 @@ The application will be available at: `http://localhost:5172`
## Configuration
### 1. Configure Mailcow Connection
### 1. Access Mode Configuration
The application supports two access modes:
**Direct Access (Default)** - Access via IP:port
- Set `ENABLE_PROXY=false` in `docker-compose.yml` (default)
- Access at: `http://your-server-ip:5172`
- Works over HTTP (no HTTPS required)
- Perfect for internal/LAN access
**Proxy Access** - Access via reverse proxy (Authelia, Nginx, Traefik, etc.)
- Set `ENABLE_PROXY=true` in `docker-compose.yml`
- Access at: `https://alias.yourdomain.com` (through your proxy)
- Requires HTTPS
- See [PROXY_SETUP.md](PROXY_SETUP.md) for detailed configuration
**To switch modes:**
1. Edit `docker-compose.yml` and change `ENABLE_PROXY` value
2. Restart: `docker compose down && docker compose up -d`
3. Clear browser cookies and login again
---
### 2. Configure Mailcow Connection
You can configure your Mailcow server connection either:

145
app.py
View File

@@ -21,71 +21,78 @@ logger = logging.getLogger('mailcow-alias-manager')
app = Flask(__name__)
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
# Increasing the number of proxies to handle Zoraxy->Authelia->App chain
app.wsgi_app = ProxyFix(
app.wsgi_app,
x_for=2, # Trust X-Forwarded-For with 2 proxies (Zoraxy + Authelia)
x_proto=2, # Trust X-Forwarded-Proto (http/https)
x_host=2, # Trust X-Forwarded-Host
x_prefix=2 # Trust X-Forwarded-Prefix
)
# Configure proxy mode based on environment variable
# ENABLE_PROXY=true: Behind reverse proxy (Authelia/Zoraxy/Nginx/etc.)
# ENABLE_PROXY=false: Direct IP:port access (default)
ENABLE_PROXY = os.getenv('ENABLE_PROXY', 'false').lower() in ('true', '1', 'yes')
if ENABLE_PROXY:
# Mode: Behind reverse proxy (Authelia/Zoraxy/Nginx/etc.)
logger.info("=" * 60)
logger.info(" ACCESS MODE: Reverse Proxy (ENABLE_PROXY=true)")
logger.info("=" * 60)
logger.info(" Configuration:")
logger.info(" - ProxyFix middleware: ACTIVE")
logger.info(" - Trusted proxies: 2 (e.g., Zoraxy + Authelia)")
logger.info(" - Cookie security: HTTPS only (Secure=True)")
logger.info(" - SameSite policy: None (cross-origin auth)")
logger.info(" - URL scheme: https://")
logger.info("=" * 60)
app.wsgi_app = ProxyFix(
app.wsgi_app,
x_for=2, # Trust X-Forwarded-For with 2 proxies
x_proto=2, # Trust X-Forwarded-Proto (http/https)
x_host=2, # Trust X-Forwarded-Host
x_prefix=2 # Trust X-Forwarded-Prefix
)
cookie_secure = True
cookie_samesite = 'None'
preferred_scheme = 'https'
else:
# Mode: Direct IP:port access (no proxy)
logger.info("=" * 60)
logger.info(" ACCESS MODE: Direct IP:Port (ENABLE_PROXY=false)")
logger.info("=" * 60)
logger.info(" Configuration:")
logger.info(" - ProxyFix middleware: DISABLED")
logger.info(" - Cookie security: HTTP allowed (Secure=False)")
logger.info(" - SameSite policy: Lax (standard mode)")
logger.info(" - URL scheme: http://")
logger.info(" - Access via: http://your-ip:5172")
logger.info("=" * 60)
# Don't apply ProxyFix for direct access
cookie_secure = False
cookie_samesite = 'Lax'
preferred_scheme = 'http'
# Initialize database on startup
malias_w.init_database()
# Session configuration optimized for reverse proxy with Authelia
# Session configuration - dynamic based on proxy mode
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_NAME='malias_session',
SESSION_COOKIE_SECURE=cookie_secure,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='None', # Required for authentication proxies
SESSION_COOKIE_SAMESITE=cookie_samesite,
SESSION_COOKIE_PATH='/',
SESSION_COOKIE_DOMAIN=None, # Let browser auto-set domain
SESSION_REFRESH_EACH_REQUEST=True, # Keep session alive
PREFERRED_URL_SCHEME='https'
SESSION_COOKIE_DOMAIN=None,
SESSION_REFRESH_EACH_REQUEST=True,
PREFERRED_URL_SCHEME=preferred_scheme
)
# 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)
if ENABLE_PROXY:
# Set CORS headers for proxy mode (needed for Authelia/Zoraxy)
response.headers['Access-Control-Allow-Origin'] = request.headers.get('Origin', '*')
response.headers['Access-Control-Allow-Credentials'] = 'true'
# 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
# Cache control (applies to both modes)
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
@@ -213,21 +220,6 @@ def login():
# Return JSON response
response = 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:
logger.warning("Login failed: Invalid password")
@@ -307,24 +299,7 @@ def index():
session['user_token'] = secrets.token_urlsafe(32)
session['auth_method'] = 'authelia'
session.modified = True
# Set cookie manually with correct parameters
response = make_response(render_template('index.html'))
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 on index: {app.config['SESSION_COOKIE_NAME']}")
return response
return render_template('index.html')
# Check if logged in
if not session.get('logged_in'):
@@ -608,7 +583,7 @@ def debug_info():
'app_config': {
'DEBUG': app.debug,
'PREFERRED_URL_SCHEME': app.config['PREFERRED_URL_SCHEME'],
'ZORAXY_COOKIE_FIX': ZORAXY_COOKIE_FIX
'ENABLE_PROXY': ENABLE_PROXY
}
}

View File

@@ -43,4 +43,23 @@ services:
# Secret key for sessions - CRITICAL for multi-worker Gunicorn!
# All workers must use the SAME secret key to decode session cookies
# Generate with: python3 -c "import secrets; print(secrets.token_hex(32))"
- SECRET_KEY=dca6920115b8bbc13c346c75d668a49849590c77d9baaf903296582f24ff816a
- SECRET_KEY=dca6920115b8bbc13c346c75d668a49849590c77d9baaf903296582f24ff816a
# Reverse Proxy Mode
# ------------------
# Set to 'true' when accessing through reverse proxy (Authelia, Zoraxy, Nginx, etc.)
# Set to 'false' for direct IP:port access (e.g., http://192.168.1.100:5172)
#
# ENABLE_PROXY=true (Proxy Mode):
# - ProxyFix middleware enabled (handles X-Forwarded-* headers)
# - Secure cookies required (HTTPS only)
# - SameSite=None (allows cross-origin authentication)
# - Access via: https://alias.yourdomain.com
#
# ENABLE_PROXY=false (Direct Mode) - DEFAULT:
# - ProxyFix middleware disabled
# - Standard cookies (HTTP allowed)
# - SameSite=Lax (standard browser behavior)
# - Access via: http://your-server-ip:5172
#
- ENABLE_PROXY=false