diff --git a/app.py b/app.py index 2d8a78e..1cf151c 100644 --- a/app.py +++ b/app.py @@ -10,6 +10,7 @@ import sys import redis import json import mysql.connector +import re from lib.datetime import filter_accounts_next_30_days, filter_accounts_expired from lib.reqs import (get_urls, get_user_accounts, add_user_account, @@ -185,9 +186,11 @@ def login() -> Union[Response, str]: login_url, auth=requests.auth.HTTPBasicAuth(username, password) ) response.raise_for_status() - if response.json().get("auth") == "Success": + response_data = response.json() + if response_data.get("auth") == "Success": session["logged_in"] = True - session["username"] = username + session["username"] = response_data.get("username", username) + session["user_id"] = response_data.get("user_id") session["auth_credentials"] = encoded_credentials next_url = request.args.get("next") if next_url: @@ -298,29 +301,24 @@ def stream_names() -> Union[Response, str]: return jsonify(get_stream_names(base_url, session["auth_credentials"])) -@app.route('/config', methods=['GET', 'POST']) +@app.route('/config') def config(): """Handles access to the configuration page.""" - if request.method == 'POST': - password = request.form.get('password') - if password == app.config['CONFIG_PASSWORD']: - session['config_logged_in'] = True - return redirect(url_for('config_dashboard')) - else: - return render_template('config.html', error='Invalid password') - return render_template('config.html') + if session.get('user_id') and int(session.get('user_id')) == 1: + return redirect(url_for('config_dashboard')) + return redirect(url_for('home')) @app.route('/config/dashboard') def config_dashboard(): """Renders the configuration dashboard.""" - if not session.get('config_logged_in'): - return redirect(url_for('config')) + if not session.get('user_id') or int(session.get('user_id')) != 1: + return redirect(url_for('home')) return render_template('config_dashboard.html') @app.route('/check-expiring-accounts', methods=['POST']) def check_expiring_accounts(): """Proxies the request to check for expiring accounts to the backend.""" - if not session.get("config_logged_in"): + if not session.get('user_id') or int(session.get('user_id')) != 1: return jsonify({'error': 'Unauthorized'}), 401 backend_url = f"{app.config['BACKEND_URL']}/check-expiry" @@ -334,7 +332,7 @@ def check_expiring_accounts(): @app.route('/dns', methods=['GET', 'POST', 'DELETE']) def proxy_dns(): """Proxies DNS management requests to the backend.""" - if not session.get("config_logged_in"): + if not session.get('user_id') or int(session.get('user_id')) != 1: return jsonify({'error': 'Unauthorized'}), 401 backend_url = f"{app.config['BACKEND_URL']}/dns" @@ -362,7 +360,7 @@ def proxy_dns(): @app.route('/extra_urls', methods=['GET', 'POST', 'DELETE']) def proxy_extra_urls(): """Proxies extra URL management requests to the backend.""" - if not session.get("config_logged_in"): + if not session.get('user_id') or int(session.get('user_id')) != 1: return jsonify({'error': 'Unauthorized'}), 401 backend_url = f"{app.config['BACKEND_URL']}/extra_urls" @@ -387,6 +385,144 @@ def proxy_extra_urls(): return jsonify({"error": str(e)}), 502 +class NginxProxyManager: + def __init__(self, host, email, password): + self.host = host + self.email = email + self.password = password + self.token = None + + def login(self): + url = f"{self.host}/api/tokens" + payload = { + "identity": self.email, + "secret": self.password + } + headers = { + "Content-Type": "application/json" + } + response = requests.post(url, headers=headers, data=json.dumps(payload)) + if response.status_code == 200: + self.token = response.json()["token"] + print("Login successful.") + else: + print(f"Failed to login: {response.text}") + exit(1) + + def get_proxy_host(self, host_id): + if not self.token: + self.login() + + url = f"{self.host}/api/nginx/proxy-hosts/{host_id}" + headers = { + "Authorization": f"Bearer {self.token}" + } + response = requests.get(url, headers=headers) + if response.status_code == 200: + return response.json() + else: + print(f"Failed to get proxy host {host_id}: {response.text}") + return None + + def update_proxy_host_config(self, host_id, config): + if not self.token: + self.login() + + url = f"{self.host}/api/nginx/proxy-hosts/{host_id}" + + original_host_data = self.get_proxy_host(host_id) + if not original_host_data: + return + + # Construct a new payload with only the allowed fields for an update + update_payload = { + "domain_names": original_host_data.get("domain_names", []), + "forward_scheme": original_host_data.get("forward_scheme", "http"), + "forward_host": original_host_data.get("forward_host"), + "forward_port": original_host_data.get("forward_port"), + "access_list_id": original_host_data.get("access_list_id", 0), + "certificate_id": original_host_data.get("certificate_id", 0), + "ssl_forced": original_host_data.get("ssl_forced", False), + "hsts_enabled": original_host_data.get("hsts_enabled", False), + "hsts_subdomains": original_host_data.get("hsts_subdomains", False), + "http2_support": original_host_data.get("http2_support", False), + "block_exploits": original_host_data.get("block_exploits", False), + "caching_enabled": original_host_data.get("caching_enabled", False), + "allow_websocket_upgrade": original_host_data.get("allow_websocket_upgrade", False), + "advanced_config": config, # The updated advanced config + "meta": original_host_data.get("meta", {}), + "locations": original_host_data.get("locations", []), + } + + headers = { + "Authorization": f"Bearer {self.token}", + "Content-Type": "application/json" + } + + response = requests.put(url, headers=headers, data=json.dumps(update_payload)) + if response.status_code == 200: + print(f"Successfully updated proxy host {host_id}") + else: + print(f"Failed to update proxy host {host_id}: {response.text}") + + + +def update_config_with_streams(config, streams): + # Get all stream names from the database + db_stream_names = {stream['streamName'] for stream in streams} + + # Find all location blocks in the config + location_blocks = re.findall(r'location ~ \^/(\w+)\(\.\*\)\$ \{[^}]+\}', config) + + # Remove location blocks that are not in the database + for stream_name in location_blocks: + if stream_name not in db_stream_names: + print(f"Removing location block for stream: {stream_name}") + pattern = re.compile(f'location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{[^}}]+}}\\s*', re.DOTALL) + config = pattern.sub('', config) + + # Update existing stream URLs + for stream in streams: + stream_name = stream['streamName'] + stream_url = stream['streamURL'] + if stream_url: # Ensure there is a URL to update to + # Use a more specific regex to avoid replacing parts of other URLs + pattern = re.compile(f'(location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{\\s*return 302 )([^;]+)(;\\s*}})') + config = pattern.sub(f'\\1{stream_url}/$1$is_args$args\\3', config) + + return config + +@app.route('/update_host_9_config', methods=['POST']) +def update_host_9_config(): + if not session.get('user_id') or int(session.get('user_id')) != 1: + return jsonify({'error': 'Unauthorized'}), 401 + + npm = NginxProxyManager(app.config['NPM_HOST'], app.config['NPM_EMAIL'], app.config['NPM_PASSWORD']) + npm.login() + host = npm.get_proxy_host(9) + if host: + current_config = host.get('advanced_config', '') + + # Fetch streams from the backend API + backend_url = f"{app.config['BACKEND_URL']}/get_all_stream_urls" + credentials = base64.b64decode(session["auth_credentials"]).decode() + username, password = credentials.split(":", 1) + auth = requests.auth.HTTPBasicAuth(username, password) + + try: + response = requests.get(backend_url, auth=auth) + response.raise_for_status() + streams = response.json() + except requests.exceptions.RequestException as e: + return jsonify({"error": f"Failed to fetch streams from backend: {e}"}), 502 + + if streams: + new_config = update_config_with_streams(current_config, streams) + npm.update_proxy_host_config(9, new_config) + return jsonify({'message': 'Config updated successfully'}), 200 + return jsonify({'error': 'Failed to update config'}), 500 + + if __name__ == "__main__": app.run( debug=app.config["DEBUG"], diff --git a/templates/base.html b/templates/base.html index ba00197..54b90b4 100644 --- a/templates/base.html +++ b/templates/base.html @@ -46,7 +46,7 @@ diff --git a/templates/config.html b/templates/config.html deleted file mode 100644 index d98eb41..0000000 --- a/templates/config.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Config Access{% endblock %} - -{% block content %} -
-
-
-
-

Enter Password

-
-
- {% if error %} -
{{ error }}
- {% endif %} -
-
- - -
- -
-
-
-
-
-{% endblock %} \ No newline at end of file diff --git a/templates/config_dashboard.html b/templates/config_dashboard.html index 646ccec..f66ad6e 100644 --- a/templates/config_dashboard.html +++ b/templates/config_dashboard.html @@ -16,6 +16,7 @@ + @@ -280,6 +281,21 @@ alert('An error occurred while triggering the expiring accounts check.'); }); }); + + document.getElementById('update-host-9-btn').addEventListener('click', function() { + fetch('{{ url_for("update_host_9_config") }}', { + method: 'POST' + }).then(response => { + if (response.ok) { + alert('Host 9 config updated successfully!'); + } else { + alert('Failed to update Host 9 config.'); + } + }).catch(err => { + console.error('Error updating Host 9 config:', err); + alert('An error occurred while updating the Host 9 config.'); + }); + }); }); {% endblock %} \ No newline at end of file diff --git a/test_npm_update.py b/test_npm_update.py new file mode 100644 index 0000000..68dd7bf --- /dev/null +++ b/test_npm_update.py @@ -0,0 +1,164 @@ +import requests +import json +import mysql.connector +import re +import os +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + +# Assuming config.py is in the same directory or accessible via PYTHONPATH +from config import DevelopmentConfig as app_config + +class NginxProxyManager: + def __init__(self, host, email, password): + self.host = host + self.email = email + self.password = password + self.token = None + + def login(self): + url = f"{self.host}/api/tokens" + payload = { + "identity": self.email, + "secret": self.password + } + headers = { + "Content-Type": "application/json" + } + response = requests.post(url, headers=headers, data=json.dumps(payload)) + if response.status_code == 200: + self.token = response.json()["token"] + print("Login successful.") + else: + print(f"Failed to login: {response.text}") + exit(1) + + def get_proxy_host(self, host_id): + if not self.token: + self.login() + + url = f"{self.host}/api/nginx/proxy-hosts/{host_id}" + headers = { + "Authorization": f"Bearer {self.token}" + } + response = requests.get(url, headers=headers) + if response.status_code == 200: + return response.json() + else: + print(f"Failed to get proxy host {host_id}: {response.text}") + return None + + def update_proxy_host_config(self, host_id, config): + if not self.token: + self.login() + + url = f"{self.host}/api/nginx/proxy-hosts/{host_id}" + + original_host_data = self.get_proxy_host(host_id) + if not original_host_data: + return + + # Construct a new payload with only the allowed fields for an update + update_payload = { + "domain_names": original_host_data.get("domain_names", []), + "forward_scheme": original_host_data.get("forward_scheme", "http"), + "forward_host": original_host_data.get("forward_host"), + "forward_port": original_host_data.get("forward_port"), + "access_list_id": original_host_data.get("access_list_id", 0), + "certificate_id": original_host_data.get("certificate_id", 0), + "ssl_forced": original_host_data.get("ssl_forced", False), + "hsts_enabled": original_host_data.get("hsts_enabled", False), + "hsts_subdomains": original_host_data.get("hsts_subdomains", False), + "http2_support": original_host_data.get("http2_support", False), + "block_exploits": original_host_data.get("block_exploits", False), + "caching_enabled": original_host_data.get("caching_enabled", False), + "allow_websocket_upgrade": original_host_data.get("allow_websocket_upgrade", False), + "advanced_config": config, # The updated advanced config + "meta": original_host_data.get("meta", {}), + "locations": original_host_data.get("locations", []), + } + + headers = { + "Authorization": f"Bearer {self.token}", + "Content-Type": "application/json" + } + + response = requests.put(url, headers=headers, data=json.dumps(update_payload)) + if response.status_code == 200: + print(f"Successfully updated proxy host {host_id}") + else: + print(f"Failed to update proxy host {host_id}: {response.text}") + + +def get_streams_from_db(db_host, db_user, db_pass, db_name, db_port): + try: + conn = mysql.connector.connect( + host=db_host, + user=db_user, + password=db_pass, + database=db_name, + port=db_port + ) + cursor = conn.cursor(dictionary=True) + cursor.execute(""" + SELECT DISTINCT + SUBSTRING_INDEX(stream, ' ', 1) AS streamName, + streamURL + FROM userAccounts + """) + streams = cursor.fetchall() + cursor.close() + conn.close() + return streams + except mysql.connector.Error as err: + print(f"Error connecting to database: {err}") + return [] + +def update_config_with_streams(config, streams): + # Get all stream names from the database + db_stream_names = {stream['streamName'] for stream in streams} + + # Find all location blocks in the config + location_blocks = re.findall(r'location ~ \^/(\w+)\(\.\*\)\$ \{[^}]+\}', config) + + # Remove location blocks that are not in the database + for stream_name in location_blocks: + if stream_name not in db_stream_names: + print(f"Removing location block for stream: {stream_name}") + pattern = re.compile(f'location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{[^}}]+}}\\s*', re.DOTALL) + config = pattern.sub('', config) + + # Update existing stream URLs + for stream in streams: + stream_name = stream['streamName'] + stream_url = stream['streamURL'] + if stream_url: # Ensure there is a URL to update to + # Use a more specific regex to avoid replacing parts of other URLs + pattern = re.compile(f'(location ~ \\^/{re.escape(stream_name)}\\(\\.\\*\\)\\$ {{\\s*return 302 )([^;]+)(;\\s*}})') + config = pattern.sub(f'\\1{stream_url}/$1$is_args$args\\3', config) + + return config + +def main(): + npm = NginxProxyManager(app_config.NPM_HOST, app_config.NPM_EMAIL, app_config.NPM_PASSWORD) + npm.login() + host = npm.get_proxy_host(9) + if host: + current_config = host.get('advanced_config', '') + print("Current Config:") + print(current_config) + + streams = get_streams_from_db(app_config.DBHOST, app_config.DBUSER, app_config.DBPASS, app_config.DATABASE, app_config.DBPORT) + if streams: + new_config = update_config_with_streams(current_config, streams) + print("\nNew Config:") + print(new_config) + + # Uncomment the following line to apply the changes + npm.update_proxy_host_config(9, new_config) + print("\nTo apply the changes, uncomment the last line in the main function.") + +if __name__ == "__main__": + main() \ No newline at end of file