Changed app for proxy and https++
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ BUI*
|
|||||||
build.sh
|
build.sh
|
||||||
docker-compose.dist*
|
docker-compose.dist*
|
||||||
GITEA.md
|
GITEA.md
|
||||||
|
PROXY_AUTHELIA_SETUP.md
|
||||||
115
app.py
115
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
|
app.secret_key = os.getenv('SECRET_KEY', 'malias-default-secret-key-please-change') # Consistent secret key
|
||||||
|
|
||||||
# Configure for reverse proxy (Authelia, Zoraxy, Nginx, etc.)
|
# 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 = ProxyFix(
|
||||||
app.wsgi_app,
|
app.wsgi_app,
|
||||||
x_for=1, # Trust X-Forwarded-For with 1 proxy
|
x_for=2, # Trust X-Forwarded-For with 2 proxies (Zoraxy + Authelia)
|
||||||
x_proto=1, # Trust X-Forwarded-Proto (http/https)
|
x_proto=2, # Trust X-Forwarded-Proto (http/https)
|
||||||
x_host=1, # Trust X-Forwarded-Host
|
x_host=2, # Trust X-Forwarded-Host
|
||||||
x_prefix=1 # Trust X-Forwarded-Prefix
|
x_prefix=2 # Trust X-Forwarded-Prefix
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize database on startup
|
# Initialize database on startup
|
||||||
malias_w.init_database()
|
malias_w.init_database()
|
||||||
|
|
||||||
# Session configuration optimized for reverse proxy with Gunicorn
|
# Session configuration optimized for reverse proxy with Authelia
|
||||||
app.config.update(
|
app.config.update(
|
||||||
PERMANENT_SESSION_LIFETIME=timedelta(hours=24),
|
PERMANENT_SESSION_LIFETIME=timedelta(hours=24),
|
||||||
SESSION_COOKIE_NAME='session', # Use standard name
|
SESSION_COOKIE_NAME='malias_session', # Unique name to avoid conflicts with Authelia
|
||||||
SESSION_COOKIE_SECURE=False, # Backend is HTTP
|
SESSION_COOKIE_SECURE=True, # Always use secure cookies with Authelia
|
||||||
SESSION_COOKIE_HTTPONLY=True,
|
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_PATH='/',
|
||||||
SESSION_COOKIE_DOMAIN=None, # Let browser auto-set domain
|
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',
|
PREFERRED_URL_SCHEME='https',
|
||||||
APPLICATION_ROOT='/',
|
APPLICATION_ROOT='/',
|
||||||
)
|
)
|
||||||
@@ -42,6 +42,22 @@ def login_required(f):
|
|||||||
"""Decorator to require login for routes"""
|
"""Decorator to require login for routes"""
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
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'):
|
if not session.get('logged_in'):
|
||||||
return jsonify({'status': 'error', 'message': 'Not authenticated', 'redirect': '/login'}), 401
|
return jsonify({'status': 'error', 'message': 'Not authenticated', 'redirect': '/login'}), 401
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
@@ -50,6 +66,19 @@ def login_required(f):
|
|||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
"""Login page"""
|
"""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':
|
if request.method == 'POST':
|
||||||
password = request.json.get('password', '')
|
password = request.json.get('password', '')
|
||||||
if malias_w.verify_password(password):
|
if malias_w.verify_password(password):
|
||||||
@@ -58,7 +87,10 @@ def login():
|
|||||||
session['logged_in'] = True
|
session['logged_in'] = True
|
||||||
session['user_token'] = secrets.token_urlsafe(32)
|
session['user_token'] = secrets.token_urlsafe(32)
|
||||||
session.modified = True
|
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:
|
else:
|
||||||
return jsonify({'status': 'error', 'message': 'Invalid password'})
|
return jsonify({'status': 'error', 'message': 'Invalid password'})
|
||||||
|
|
||||||
@@ -71,13 +103,37 @@ def login():
|
|||||||
def logout():
|
def logout():
|
||||||
"""Logout"""
|
"""Logout"""
|
||||||
session.pop('logged_in', None)
|
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'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
"""Main page - requires login"""
|
"""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'):
|
if not session.get('logged_in'):
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
|
||||||
@app.route('/list_aliases', methods=['POST'])
|
@app.route('/list_aliases', methods=['POST'])
|
||||||
@@ -294,6 +350,43 @@ def change_password_route():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'status': 'error', 'message': str(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__':
|
if __name__ == '__main__':
|
||||||
# Parse command-line arguments
|
# Parse command-line arguments
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ services:
|
|||||||
# Host binding (default: 0.0.0.0 for Docker)
|
# Host binding (default: 0.0.0.0 for Docker)
|
||||||
- FLASK_HOST=0.0.0.0
|
- FLASK_HOST=0.0.0.0
|
||||||
|
|
||||||
# Secret key for sessions (generate unique key for production)
|
# Secret key for sessions - CRITICAL for multi-worker Gunicorn!
|
||||||
# Change this to a random string for better security
|
# All workers must use the SAME secret key to decode session cookies
|
||||||
- SECRET_KEY=malias-production-secret-key-change-me
|
# 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