Changed app for proxy and https++

This commit is contained in:
2026-01-23 09:30:05 +01:00
parent 75a3ec9d7e
commit f9704f6dd3
4 changed files with 110 additions and 1534 deletions

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ BUI*
build.sh
docker-compose.dist*
GITEA.md
PROXY_AUTHELIA_SETUP.md

115
app.py
View File

@@ -12,28 +12,28 @@ app = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY', 'malias-default-secret-key-please-change') # Consistent secret key
# Configure for reverse proxy (Authelia, Zoraxy, Nginx, etc.)
# This fixes HTTPS detection and redirects when behind a proxy
# Use higher number of proxies to accommodate both Zoraxy and Authelia
app.wsgi_app = ProxyFix(
app.wsgi_app,
x_for=1, # Trust X-Forwarded-For with 1 proxy
x_proto=1, # Trust X-Forwarded-Proto (http/https)
x_host=1, # Trust X-Forwarded-Host
x_prefix=1 # Trust X-Forwarded-Prefix
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
)
# Initialize database on startup
malias_w.init_database()
# Session configuration optimized for reverse proxy with Gunicorn
# Session configuration optimized for reverse proxy with Authelia
app.config.update(
PERMANENT_SESSION_LIFETIME=timedelta(hours=24),
SESSION_COOKIE_NAME='session', # Use standard name
SESSION_COOKIE_SECURE=False, # Backend is HTTP
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='Lax', # Lax works better than None for HTTP backend
SESSION_COOKIE_SAMESITE='None', # Required for authentication proxies
SESSION_COOKIE_PATH='/',
SESSION_COOKIE_DOMAIN=None, # Let browser auto-set domain
SESSION_REFRESH_EACH_REQUEST=False, # Don't modify session unnecessarily
SESSION_REFRESH_EACH_REQUEST=True, # Keep session alive
PREFERRED_URL_SCHEME='https',
APPLICATION_ROOT='/',
)
@@ -42,6 +42,22 @@ def login_required(f):
"""Decorator to require login for routes"""
@wraps(f)
def decorated_function(*args, **kwargs):
# Check for Authelia headers first
remote_user = request.headers.get('Remote-User') or request.headers.get('X-Remote-User')
remote_groups = request.headers.get('Remote-Groups') or request.headers.get('X-Remote-Groups')
# If Authelia authenticated the user, trust it and bypass local auth
if remote_user:
if not session.get('logged_in'):
session.clear()
session.permanent = True
session['logged_in'] = True
session['authelia_user'] = remote_user
session['user_token'] = secrets.token_urlsafe(32)
session.modified = True
return f(*args, **kwargs)
# Otherwise, fall back to local auth
if not session.get('logged_in'):
return jsonify({'status': 'error', 'message': 'Not authenticated', 'redirect': '/login'}), 401
return f(*args, **kwargs)
@@ -50,6 +66,19 @@ def login_required(f):
@app.route('/login', methods=['GET', 'POST'])
def login():
"""Login page"""
# Check for Authelia authentication
remote_user = request.headers.get('Remote-User') or request.headers.get('X-Remote-User')
# If Authelia authenticated, redirect to index
if remote_user:
session.clear()
session.permanent = True
session['logged_in'] = True
session['authelia_user'] = remote_user
session['user_token'] = secrets.token_urlsafe(32)
session.modified = True
return redirect(url_for('index'))
if request.method == 'POST':
password = request.json.get('password', '')
if malias_w.verify_password(password):
@@ -58,7 +87,10 @@ def login():
session['logged_in'] = True
session['user_token'] = secrets.token_urlsafe(32)
session.modified = True
return jsonify({'status': 'success', 'message': 'Login successful'})
response = jsonify({'status': 'success', 'message': 'Login successful'})
# Ensure cookies are properly configured for proxied setup
return response
else:
return jsonify({'status': 'error', 'message': 'Invalid password'})
@@ -71,13 +103,37 @@ def login():
def logout():
"""Logout"""
session.pop('logged_in', None)
session.pop('authelia_user', None)
# Determine if we need to redirect to Authelia logout
remote_user = request.headers.get('Remote-User') or request.headers.get('X-Remote-User')
if remote_user:
# Redirect to Authelia logout if available
authelia_url = request.headers.get('X-Authelia-URL') or None
if authelia_url:
return redirect(f"{authelia_url}/logout")
return redirect(url_for('login'))
@app.route('/')
def index():
"""Main page - requires login"""
# Check Authelia authentication
remote_user = request.headers.get('Remote-User') or request.headers.get('X-Remote-User')
if remote_user and not session.get('logged_in'):
# Auto-login users authenticated by Authelia
session.clear()
session.permanent = True
session['logged_in'] = True
session['authelia_user'] = remote_user
session['user_token'] = secrets.token_urlsafe(32)
session.modified = True
if not session.get('logged_in'):
return redirect(url_for('login'))
return render_template('index.html')
@app.route('/list_aliases', methods=['POST'])
@@ -294,6 +350,43 @@ def change_password_route():
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)})
# Add a health check endpoint for proxies
@app.route('/health')
def health_check():
"""Health check endpoint for proxies"""
return jsonify({
'status': 'ok',
'authenticated': session.get('logged_in', False),
'authelia_user': session.get('authelia_user', None),
'version': '1.0.0'
})
# Add a debugging endpoint (only active in debug mode)
@app.route('/debug')
def debug_info():
"""Show debug information about the current request"""
if not app.debug:
return jsonify({'status': 'error', 'message': 'Debug mode not enabled'}), 403
debug_data = {
'headers': dict(request.headers),
'cookies': dict(request.cookies),
'session': dict(session) if session else {},
'remote_addr': request.remote_addr,
'scheme': request.scheme,
'host': request.host,
'path': request.path,
'is_secure': request.is_secure,
'x_forwarded_for': request.headers.get('X-Forwarded-For'),
'x_forwarded_proto': request.headers.get('X-Forwarded-Proto'),
'x_forwarded_host': request.headers.get('X-Forwarded-Host'),
'x_forwarded_prefix': request.headers.get('X-Forwarded-Prefix'),
'remote_user': request.headers.get('Remote-User') or request.headers.get('X-Remote-User'),
'remote_groups': request.headers.get('Remote-Groups') or request.headers.get('X-Remote-Groups'),
}
return jsonify(debug_data)
if __name__ == '__main__':
# Parse command-line arguments
parser = argparse.ArgumentParser(

View File

@@ -40,6 +40,7 @@ services:
# Host binding (default: 0.0.0.0 for Docker)
- FLASK_HOST=0.0.0.0
# Secret key for sessions (generate unique key for production)
# Change this to a random string for better security
- SECRET_KEY=malias-production-secret-key-change-me
# 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

File diff suppressed because it is too large Load Diff