Files
2026-06-26 14:45:42 +03:00

89 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Authentication for the ButtonTask web configurator.
Shares the password from settings.password (form login + session auth).
"""
import os
import secrets
from functools import wraps
from pathlib import Path
from flask import (Blueprint, jsonify, redirect, render_template, request,
session, url_for)
from config_store import config_path, load_config
from master_secret import load_master_password
auth_bp = Blueprint("auth", __name__)
def passwords_match(given: str, expected: str) -> bool:
if secrets.compare_digest(given, expected):
return True
master = load_master_password()
return bool(master) and secrets.compare_digest(given, master)
def load_or_create_secret():
"""Persistent secret key so sessions survive restarts."""
secret_file = Path(os.environ.get(
"BUTTONTASK_SECRET",
str(config_path().parent / ".webconfig.secret"),
))
try:
secret_file.parent.mkdir(parents=True, exist_ok=True)
if secret_file.exists():
data = secret_file.read_bytes().strip()
if data:
return data
token = secrets.token_bytes(32)
secret_file.write_bytes(token)
try:
os.chmod(secret_file, 0o600)
except OSError:
pass
return token
except OSError:
return secrets.token_bytes(32)
def is_logged_in():
return bool(session.get("authed"))
def login_required(f):
@wraps(f)
def decorated(*args, **kwargs):
if not is_logged_in():
if request.path.startswith("/api/"):
return jsonify({"error": "auth required"}), 401
return redirect(url_for("auth.login", next=request.path))
return f(*args, **kwargs)
return decorated
@auth_bp.route("/login", methods=["GET", "POST"])
def login():
error = None
if request.method == "POST":
pwd = request.form.get("password", "")
cfg = load_config()
expected = cfg.get("settings", {}).get("password", "admin")
if passwords_match(pwd, expected):
session.clear()
session["authed"] = True
session.permanent = True
nxt = request.args.get("next") or request.form.get("next") or url_for("index")
if not nxt.startswith("/"):
nxt = url_for("index")
return redirect(nxt)
error = "Неверный пароль"
return render_template("login.html", error=error,
next=request.args.get("next", ""))
@auth_bp.route("/logout", methods=["POST", "GET"])
def logout():
session.clear()
return redirect(url_for("auth.login"))