Changed app for proxy and https++
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ BUI*
|
||||
build.sh
|
||||
docker-compose.dist*
|
||||
GITEA.md
|
||||
PROXY_AUTHELIA_SETUP.md
|
||||
117
app.py
117
app.py
@@ -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(
|
||||
@@ -373,4 +466,4 @@ Environment Variables (Docker):
|
||||
print('=' * 60)
|
||||
print()
|
||||
|
||||
app.run(debug=debug_mode, host=host, port=port)
|
||||
app.run(debug=debug_mode, host=host, port=port)
|
||||
@@ -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
Reference in New Issue
Block a user