Fixed login with only ip:port access
This commit is contained in:
@@ -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"]
|
||||
|
||||
111
PROXY_SETUP.md
111
PROXY_SETUP.md
@@ -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)
|
||||
|
||||
---
|
||||
|
||||
|
||||
25
README.md
25
README.md
@@ -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:
|
||||
|
||||
|
||||
129
app.py
129
app.py
@@ -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
|
||||
# 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 (Zoraxy + Authelia)
|
||||
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)
|
||||
|
||||
# Set CORS headers to allow Zoraxy and Authelia to work together
|
||||
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'
|
||||
|
||||
# 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,3 +44,22 @@ services:
|
||||
# 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
|
||||
|
||||
# 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
|
||||
Reference in New Issue
Block a user