From e6ee91babf50a6de83b9685f4c983f833c3cdbdb Mon Sep 17 00:00:00 2001 From: k3nny Date: Sun, 5 Oct 2025 02:11:41 +0200 Subject: [PATCH] feat(auth): enable Basic Auth as default authentication method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Initialize authentication system on Flask app startup - Default to Basic Auth if no [authentication] section in config - Support TISBACKUP_AUTH_USERNAME and TISBACKUP_AUTH_PASSWORD env vars - Generate secure random password if not configured with warning - Protect all Flask routes with @auth.require_auth decorator - Fallback to 'none' auth provider on initialization errors Routes protected: - / (backup_all) - /config_number/ (set_config_number) - /all_json (backup_all_json) - /json (backup_json) - /status.json (export_backup_status) - /backups.json (last_backup_json) - /last_backups (last_backup) - /export_backup (export_backup) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- tisbackup_gui.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tisbackup_gui.py b/tisbackup_gui.py index ac6dba0..136a5c0 100755 --- a/tisbackup_gui.py +++ b/tisbackup_gui.py @@ -40,6 +40,7 @@ from iniparse import ConfigParser, RawConfigParser from config import huey from libtisbackup.common import * +from libtisbackup.auth import get_auth_provider from tasks import get_task, run_export_backup, set_task from tisbackup import tis_backup @@ -79,6 +80,69 @@ if not SECRET_KEY: app.secret_key = SECRET_KEY app.config["PROPAGATE_EXCEPTIONS"] = True +# Initialize authentication +auth_config = {} +try: + # Read authentication config from tisbackup_gui.ini + cp_gui = ConfigParser() + cp_gui.read("/etc/tis/tisbackup_gui.ini") + + if cp_gui.has_section("authentication"): + auth_type = cp_gui.get("authentication", "type", fallback="basic") + + # Load auth provider config + for key, value in cp_gui.items("authentication"): + if key != "type": + auth_config[key] = value + else: + # Default to Basic Auth if no config section + auth_type = "basic" + + # Get credentials from environment or use defaults + default_username = os.environ.get("TISBACKUP_AUTH_USERNAME", "admin") + default_password = os.environ.get("TISBACKUP_AUTH_PASSWORD") + + if not default_password: + # Generate random password if not set + import secrets + default_password = secrets.token_urlsafe(16) + logging.warning( + f"TISBACKUP_AUTH_PASSWORD not set. Generated temporary password for user '{default_username}': {default_password}" + ) + logging.warning( + "Set TISBACKUP_AUTH_USERNAME and TISBACKUP_AUTH_PASSWORD environment variables, " + "or add [authentication] section to tisbackup_gui.ini for production use." + ) + + auth_config = { + "username": default_username, + "password": default_password, + "use_bcrypt": False, # Plain text for auto-generated password + "realm": "TISBackup" + } + +except Exception as e: + # Fallback to basic auth on error + logging.error(f"Error loading authentication config: {e}. Using default Basic Auth.") + auth_type = "basic" + auth_config = { + "username": os.environ.get("TISBACKUP_AUTH_USERNAME", "admin"), + "password": os.environ.get("TISBACKUP_AUTH_PASSWORD", "changeme"), + "use_bcrypt": False, + "realm": "TISBackup" + } + +# Initialize auth provider +try: + auth = get_auth_provider(auth_type, auth_config) + auth.init_app(app) + logging.info(f"Authentication initialized: {auth_type}") +except Exception as e: + logging.error(f"Failed to initialize authentication: {e}") + # Fallback to no auth + auth = get_auth_provider("none", {}) + logging.warning("Authentication disabled due to initialization error") + tasks_db = os.path.join(tisbackup_root_dir, "tasks.sqlite") @@ -263,6 +327,7 @@ def read_config(): @app.route("/") +@auth.require_auth def backup_all(): backup_dict = read_config() return render_template("backups.html", backup_list=backup_dict) @@ -270,6 +335,7 @@ def backup_all(): @app.route("/config_number/") @app.route("/config_number/") +@auth.require_auth def set_config_number(id=None): if id is not None and len(CONFIG) > id: global config_number @@ -279,6 +345,7 @@ def set_config_number(id=None): @app.route("/all_json") +@auth.require_auth def backup_all_json(): backup_dict = read_all_configs(BASE_DIR) return json.dumps( @@ -295,6 +362,7 @@ def backup_all_json(): @app.route("/json") +@auth.require_auth def backup_json(): backup_dict = read_config() return json.dumps( @@ -483,6 +551,7 @@ def check_mount_disk(partition_name, refresh): @app.route("/status.json") +@auth.require_auth def export_backup_status(): exports = dbstat.query('select * from stats where TYPE="EXPORT" and backup_start>="%s"' % mindate) error = "" @@ -503,18 +572,21 @@ def runnings_backups(): @app.route("/backups.json") +@auth.require_auth def last_backup_json(): exports = dbstat.query('select * from stats where TYPE="BACKUP" ORDER BY backup_start DESC ') return Response(response=json.dumps(exports), status=200, mimetype="application/json") @app.route("/last_backups") +@auth.require_auth def last_backup(): exports = dbstat.query('select * from stats where TYPE="BACKUP" ORDER BY backup_start DESC LIMIT 20 ') return render_template("last_backups.html", backups=exports) @app.route("/export_backup") +@auth.require_auth def export_backup(): raise_error("", "") backup_dict = read_config()