From afab617c5c3b218590b48a3d85d7a6bb014839c9 Mon Sep 17 00:00:00 2001 From: Rune Olsen Date: Fri, 23 Jan 2026 09:48:57 +0100 Subject: [PATCH] Changed app for proxy and https++ --- app.py | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 165 insertions(+), 14 deletions(-) diff --git a/app.py b/app.py index 159fa77..6b6af9a 100644 --- a/app.py +++ b/app.py @@ -8,6 +8,7 @@ import argparse import sys import secrets import logging +import json # Configure logging logging.basicConfig( @@ -46,15 +47,48 @@ app.config.update( 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 add_security_headers(response): - """Add security headers to every response""" - # Add SameSite=None explicitly for all cookies if Auth proxy is detected - if request.headers.get('Remote-User') or request.headers.get('X-Remote-User'): - cookie_header = response.headers.get('Set-Cookie', '') - if cookie_header and 'SameSite=' not in cookie_header: - cookie_header = cookie_header.replace('HttpOnly', 'HttpOnly; SameSite=None; Secure') - response.headers['Set-Cookie'] = cookie_header +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 @@ -64,9 +98,12 @@ def get_authelia_user(): auth_headers = [ 'Remote-User', 'X-Remote-User', - 'X-Authelia-Username', + 'X-Authelia-Username', 'X-Forwarded-User', - 'REMOTE_USER' + 'REMOTE_USER', + 'Http-Remote-User', + 'Http-X-Remote-User', + 'X-Authenticated-User' ] for header in auth_headers: @@ -75,6 +112,19 @@ def get_authelia_user(): logger.info(f"Authelia user detected via {header}: {user}") return user + # Check Zoraxy forwarded headers (sometimes encoded differently) + if 'X-Forwarded-Headers' in request.headers: + try: + # Some reverse proxies encode headers as JSON + fwd_headers = json.loads(request.headers.get('X-Forwarded-Headers')) + for header in auth_headers: + if header in fwd_headers: + user = fwd_headers[header] + logger.info(f"Authelia user detected via forwarded headers - {header}: {user}") + return user + except: + pass + return None def login_required(f): @@ -105,7 +155,9 @@ def login_required(f): # Regular session check if not session.get('logged_in'): logger.warning("Access denied: User not authenticated") - return jsonify({'status': 'error', 'message': 'Not authenticated', 'redirect': '/login'}), 401 + if request.is_json: + return jsonify({'status': 'error', 'message': 'Not authenticated', 'redirect': '/login'}), 401 + return redirect(url_for('login')) return f(*args, **kwargs) return decorated_function @@ -130,7 +182,20 @@ def login(): session['user_token'] = secrets.token_urlsafe(32) session['auth_method'] = 'authelia' session.modified = True - return redirect(url_for('index')) + + # Set a cookie manually to ensure it's properly formatted for Zoraxy + response = redirect(url_for('index')) + # Set cookie parameters to work with Zoraxy/Authelia + response.set_cookie( + key=app.config['SESSION_COOKIE_NAME'], + value=request.cookies.get(app.config['SESSION_COOKIE_NAME']), + 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 if request.method == 'POST': @@ -146,7 +211,24 @@ def login(): session['auth_method'] = 'local' session.modified = True + # Return JSON response response = jsonify({'status': 'success', 'message': 'Login successful'}) + + # Manually set cookie with correct parameters for Zoraxy + if ZORAXY_COOKIE_FIX: + max_age = int(app.config['PERMANENT_SESSION_LIFETIME'].total_seconds()) + cookie_value = request.cookies.get(app.config['SESSION_COOKIE_NAME']) or session.sid + response.set_cookie( + app.config['SESSION_COOKIE_NAME'], + cookie_value, + max_age=max_age, + 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") @@ -156,6 +238,12 @@ def login(): if session.get('logged_in'): return redirect(url_for('index')) + # Check cookies and client IP for troubleshooting + if app.debug: + logger.info(f"Cookies: {request.cookies}") + logger.info(f"Client IP: {request.remote_addr}") + logger.info(f"X-Forwarded-For: {request.headers.get('X-Forwarded-For')}") + # Show login form return render_template('login.html') @@ -195,7 +283,12 @@ def logout(): return redirect(common_authelia_urls[0]) # Default case: redirect to login page - return redirect(url_for('login')) + response = redirect(url_for('login')) + + # Clear cookie by setting expired date + response.set_cookie(app.config['SESSION_COOKIE_NAME'], '', expires=0) + + return response @app.route('/') def index(): @@ -213,11 +306,34 @@ 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: + max_age = int(app.config['PERMANENT_SESSION_LIFETIME'].total_seconds()) + cookie_value = request.cookies.get(app.config['SESSION_COOKIE_NAME']) or session.sid + response.set_cookie( + app.config['SESSION_COOKIE_NAME'], + cookie_value, + max_age=max_age, + 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 # Check if logged in if not session.get('logged_in'): return redirect(url_for('login')) + # Debug logging + if app.debug and session.get('logged_in'): + logger.info(f"User authenticated with method: {session.get('auth_method', 'unknown')}") + # Show main page return render_template('index.html') @@ -453,7 +569,10 @@ def health_check(): 'authenticated': session.get('logged_in', False), 'authelia_user': session.get('authelia_user', None), 'auth_method': session.get('auth_method'), - 'version': '1.0.1' + 'version': '1.0.2', + 'cookies': {k: '***' for k in request.cookies.keys()}, # Only show cookie names + 'authelia_headers_present': get_authelia_user() is not None, + 'zoraxy_headers_present': 'X-Forwarded-Server' in request.headers }) # Add a debugging endpoint @@ -489,6 +608,7 @@ def debug_info(): 'app_config': { 'DEBUG': app.debug, 'PREFERRED_URL_SCHEME': app.config['PREFERRED_URL_SCHEME'], + 'ZORAXY_COOKIE_FIX': ZORAXY_COOKIE_FIX } } @@ -508,6 +628,37 @@ def show_headers(): '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()} + }) + if __name__ == '__main__': # Parse command-line arguments parser = argparse.ArgumentParser(