feat(auth): enable Basic Auth as default authentication method

- 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 <noreply@anthropic.com>
This commit is contained in:
k3nny 2025-10-05 02:11:41 +02:00
parent f12d89f3da
commit e6ee91babf

View File

@ -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/<int:id>")
@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()