feat(gui): custom login page, session-based auth

Replace nginx auth_basic + HTTP Basic Auth with a styled Flask login form.
- Session-based authentication (cookie, session.permanent)
- Custom login page with logo, error state, clean form design
- CSRF check skipped for /login route
- Logout button in sidebar footer
- nginx auth_basic removed; ADMIN_PASSWORD restored in .env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 10:42:44 +03:00
parent 75b47e2404
commit 2391007a81
4 changed files with 258 additions and 13 deletions
+31 -13
View File
@@ -250,14 +250,6 @@ def to_png_b64(text):
return base64.b64encode(buf.getvalue()).decode("ascii")
def _unauthorized():
return Response(
"Auth required",
401,
{"WWW-Authenticate": 'Basic realm="WG Admin"'},
)
def _get_csrf_token():
if "csrf_token" not in session:
session["csrf_token"] = secrets.token_hex(32)
@@ -266,18 +258,19 @@ def _get_csrf_token():
app.jinja_env.globals["csrf_token"] = _get_csrf_token
PUBLIC_PATHS = {"/login", "/static/"}
@app.before_request
def _auth():
if request.path.startswith("/static/"):
return None
if request.path == "/login":
return None
if not ADMIN_PASSWORD:
return None
auth = request.authorization
if not auth:
return _unauthorized()
if auth.username != ADMIN_USER or auth.password != ADMIN_PASSWORD:
return _unauthorized()
if not session.get("logged_in"):
return redirect(url_for("login", next=request.path))
return None
@@ -285,12 +278,37 @@ def _auth():
def _csrf_check():
if request.method != "POST":
return None
if request.path == "/login":
return None
token = request.form.get("csrf_token")
if not token or token != session.get("csrf_token"):
return Response("CSRF token invalid", 403)
return None
@app.route("/login", methods=["GET", "POST"])
def login():
if session.get("logged_in"):
return redirect(url_for("index"))
error = None
if request.method == "POST":
user = request.form.get("username", "").strip()
pwd = request.form.get("password", "")
if user == ADMIN_USER and pwd == ADMIN_PASSWORD:
session.clear()
session["logged_in"] = True
session.permanent = True
return redirect(request.args.get("next") or url_for("index"))
error = "Неверный логин или пароль"
return render_template("login.html", error=error)
@app.route("/logout")
def logout():
session.clear()
return redirect(url_for("login"))
@app.route("/")
def index():
meta = load_meta()