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:
parent
a768d95ed3
commit
84b718e625
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user