From d8d829ab71d6f547c89e81130d8b7334a33c7157 Mon Sep 17 00:00:00 2001 From: DaanSelen Date: Mon, 16 Mar 2026 16:52:47 +0100 Subject: [PATCH] chore: remnant changes --- src/config.ini.bak | 63 ++++++++++++++ src/modules/config/config_utils.py | 104 +++++++++++++++++++++++ src/modules/database/database_utils.py | 45 ++++++++++ src/modules/routes/routes_utils.py | 27 ++++++ src/modules/routes/routes_welcome.py | 78 +++++++++++++++++ src/modules/wireguard/wireguard_utils.py | 2 + src/static/build-app.sh | 12 +++ 7 files changed, 331 insertions(+) create mode 100644 src/config.ini.bak create mode 100644 src/modules/config/config_utils.py create mode 100644 src/modules/database/database_utils.py create mode 100644 src/modules/routes/routes_utils.py create mode 100644 src/modules/routes/routes_welcome.py create mode 100644 src/modules/wireguard/wireguard_utils.py create mode 100755 src/static/build-app.sh diff --git a/src/config.ini.bak b/src/config.ini.bak new file mode 100644 index 0000000..f44b45b --- /dev/null +++ b/src/config.ini.bak @@ -0,0 +1,63 @@ +[peers] +remote_endpoint = 89.20.90.254 +peer_global_dns = 9.9.9.9 +peer_endpoint_allowed_ip = 0.0.0.0/0 +peer_display_mode = grid +peer_mtu = 1420 +peer_keep_alive = 21 + +[server] +hostname = 0.0.0.0 +port = 10086 +debug_enabled = True +wg_conf_path = /etc/wireguard +awg_conf_path = /etc/amnezia/amneziawg +app_prefix = +authentication_required = True +version = v5.0.0 +wgdashboard_refresh_interval = 60000 +wgdashboard_peer_list_display = grid +wgdashboard_sort = status +wgdashboard_theme = dark +wgdashboard_apikey = true +wgdashboard_language = en-US +log_level = DEBUG + +[account] +username = admin +password = $2b$12$1VN62Q7CS/BJcAahHAWsA.3CD6zPqWTmE/HN/AJqwP0zds2l25Fqe +enable_totp = false +totp_verified = false +totp_key = ZSI2Z4QHSGVAK6TFVMZXORFDWKHDN4TQ + +[other] +welcome_session = false + +[database] +type = sqlite +host = +port = +username = +password = + +[email] +server = +port = +encryption = +username = +email_password = +authentication_required = true +send_from = +email_template = + +[oidc] +admin_enable = false +client_enable = false + +[clients] +enable = true +sign_up = true + +[wireguardconfiguration] +autostart = +peer_tracking = false \ No newline at end of file diff --git a/src/modules/config/config_utils.py b/src/modules/config/config_utils.py new file mode 100644 index 0000000..ddc57e7 --- /dev/null +++ b/src/modules/config/config_utils.py @@ -0,0 +1,104 @@ +#!/bin/env python3 + +import logging as log + +import configparser as cp +import os + +class config_utils(): + @staticmethod + def search_known_paths() -> tuple[bool, str]: + ''' + Look at predefined paths on the filesystem for a config file + ''' + + possible_config_locations = [ + "./config.ini", + f"{os.getenv('HOME')}/.config/wgdashboard/config.ini", + "/etc/wgdashboard/config.ini", + ] + + try: + for path in possible_config_locations: + if os.path.exists(path): + return True, path + else: + continue + return False, '' + + except Exception as err: + return False, '' + + @staticmethod + def read_data(config_path) -> tuple[bool, dict]: + config = cp.ConfigParser() + + try: + config.read(config_path) + + if len(config.sections()) == 0: + return False, {} + + config_dict = {} + for section in config.sections(): + new_items = {} + + for key, value in config.items(section): + key = key.lower() + val = value.strip().lower() + + if val == 'true': + value = True + elif val == 'false': + value = False + + new_items[key] = value + + config_dict[section.lower()] = new_items + + return True, config_dict + + except cp.ParsingError as err: + return False, {} + + @staticmethod + def find_section(config_data: dict, target_section: str) -> bool: + if target_section not in config_data: + log.error("target section not in the config") + return False + + return True + + @staticmethod + def find_key(config_data: dict, target_section: str, target_key: str) -> bool: + if target_key not in config_data[target_section]: + log.error("target key not in the config") + return False + + return True + + @staticmethod + def write_key(config_data: dict, target_section: str, target_key: str, new_value: str) -> bool: + config_data[target_section][target_key] = new_value + + config = cp.ConfigParser() + + for section, values in config_data.items(): + config[section] = {} + for key, value in values.items(): + config[section][key] = str(value) + + try: + ok, candidate_path = config_utils.search_known_paths() + if not ok: + log.error("failed to retrieve a valid path for the config") + return False + + with open(candidate_path, "w") as f: + config.write(f) + + return True + + except Exception as err: + log.info("exception occured",err) + return False \ No newline at end of file diff --git a/src/modules/database/database_utils.py b/src/modules/database/database_utils.py new file mode 100644 index 0000000..8847494 --- /dev/null +++ b/src/modules/database/database_utils.py @@ -0,0 +1,45 @@ +#!/bin/env python3 + +import logging as log + +import os +from urllib.parse import quote_plus + +from modules.utilities.utilities import utilities as util + +class database_utils(): + ''' + This class functions as a collection of function to prepare a connection to a database. + ''' + + @staticmethod + def generate_connection_string(database_config: dict) -> tuple[bool, str]: + if not 'type' in database_config: + return False, '' + + username = quote_plus(database_config.get('username', '')) + password = quote_plus(database_config.get('password', '')) + + match database_config['type']: + case 'sqlite': + local_database_path = os.path.abspath("./database") + + exists = util.ensure_directory(local_database_path) + if exists: + connection_string = f'sqlite:///{local_database_path}/wgdashboard.db' + else: + return False, '' + + case 'postgresql' | 'postgres': + connection_string = f'postgresql+psycopg://{username}:{password}@{database_config.get('host', 'localhost')}:{database_config.get('port', '5432')}' + + case 'mariadb': + connection_string = f'mariadb+mariadbconnector://{username}:{password}@{database_config.get('host', 'localhost')}:{database_config.get('port', '3306')}' + + case 'mysql': + connection_string = f'mysql+pymysql://{username}:{password}@{database_config.get('host', 'localhost')}:{database_config.get('port', '3306')}' + + case _: + return False, '' + + return True, connection_string \ No newline at end of file diff --git a/src/modules/routes/routes_utils.py b/src/modules/routes/routes_utils.py new file mode 100644 index 0000000..791aa26 --- /dev/null +++ b/src/modules/routes/routes_utils.py @@ -0,0 +1,27 @@ +#!/bin/env python3 + +import flask + +from ..database.functions import functions + +class routes_utils(): + @staticmethod + def is_valid_api_key(api_key: str) -> bool: + valid_keys, _ = functions.retrieve_api_keys(flask.current_app.db_session) + valid_key_strings = [] + + for key in valid_keys: + valid_key_strings.append(key["key"]) + + return api_key in valid_key_strings + + @staticmethod + def is_path_allowed(path: str, white_list: list[str], session_data: dict) -> bool: + # Allow if session has admin role + if session_data.get("role") == "admin" and "username" in session_data: + return True + # Check white list + for p in white_list: + if p in path: + return True + return False \ No newline at end of file diff --git a/src/modules/routes/routes_welcome.py b/src/modules/routes/routes_welcome.py new file mode 100644 index 0000000..9db7cd5 --- /dev/null +++ b/src/modules/routes/routes_welcome.py @@ -0,0 +1,78 @@ +import logging as log + +import bcrypt +import flask +import hashlib +import json +import pyotp +import os +import werkzeug +from datetime import datetime + +from .response import make_resp_obj +from .routes_utils import routes_utils +from .locale import localeman + +from ..database.functions import functions +from ..config.config import config + +routes_welcome = flask.Blueprint("routes_welcome", __name__) + +@routes_welcome.route('/api/Welcome_Finish', methods=["POST"]) +def api_welcome_finish(): + ok, config_other = config.filter(flask.current_app.wgd_config, 'OTHER') + if not ok: + log.error("failed to filter the config in-memory") + return make_resp_obj(False, 'Internal error', {}, 500) + + req_data = flask.request.get_json() + if not req_data: + return make_resp_obj(False, "Invalid request body", {}, 400) + + if len(req_data["username"]) == 0: + return make_resp_obj(False, "Username cannot be empty", {}, 400) + + if len(req_data["newPassword"]) < 7: + return make_resp_obj(False, "Password must be at least 8 characters", {}, 400) + + if not config.update('ACCOUNT', 'username', req_data["username"]): + log.error("failed to update the key in the configuration file") + return make_resp_obj(False, 'Internal error', {}, 500) + + hashed_password = bcrypt.hashpw(req_data["newPassword"].encode('utf-8'), bcrypt.gensalt()) + + if not config.update('ACCOUNT', 'password', hashed_password.decode('utf-8')): + log.error("failed to update the key in the configuration file") + return make_resp_obj(False, 'Internal error', {}, 500) + + if not config.update('OTHER', 'welcome_session', False): + log.error("failed to update the key in the configuration file") + return make_resp_obj(False, 'Internal error', {}, 500) + + # Very important to also refresh the config in-memory + ok, flask.current_app.wgd_config = config.read() + if not ok: + log.error("failed to refresh the in-memory configuration") + return make_resp_obj(False, 'Internal error', {}, 500) + + return make_resp_obj() + +@routes_welcome.route('/api/Welcome_GetTotpLink') +def api_welcome_get_totp(): + ok, config_account = config.filter(flask.current_app.wgd_config, 'ACCOUNT') + if not ok: + log.error("failed to filter the config in-memory") + return make_resp_obj(False, 'Internal error', {}, 500) + + if "totp_verified" not in config_account or not config_account["totp_verified"]: + totp_key = pyotp.random_base32() + + log.debug(totp_key) + ok = config.update('ACCOUNT', 'totp_key', totp_key) + if not ok: + log.error("failed to update the key in the configuration file") + return make_resp_obj(False, 'Internal error', {}, 500) + + return make_resp_obj(True, '', pyotp.totp.TOTP(totp_key).provisioning_uri(issuer_name="WGDashboard Admin")) + + return make_resp_obj(False, 'Internal error', {}, 500) \ No newline at end of file diff --git a/src/modules/wireguard/wireguard_utils.py b/src/modules/wireguard/wireguard_utils.py new file mode 100644 index 0000000..92bc4e7 --- /dev/null +++ b/src/modules/wireguard/wireguard_utils.py @@ -0,0 +1,2 @@ +#!/bin/env python3 + diff --git a/src/static/build-app.sh b/src/static/build-app.sh new file mode 100755 index 0000000..14f8208 --- /dev/null +++ b/src/static/build-app.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +echo "Dropping current files..." + +rm ./dist -rf + +echo "Compiling the new!" + +cd ./admin && npm run build && cd .. +cd ./client && npm run build && cd .. + +echo "Done!"