fix: show login flashes and CSRF errors; proxy and cookie options for HTTPS deploys

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-05-12 15:53:12 +08:00
parent e3b9fca45d
commit a5e1e94fb2
4 changed files with 45 additions and 0 deletions
+7
View File
@@ -17,3 +17,10 @@
# 调试:1 开启,勿在生产长期开启 # 调试:1 开启,勿在生产长期开启
# NAV_DEBUG=0 # NAV_DEBUG=0
# 在 Nginx/Caddy 等反向代理后部署时:信任 X-Forwarded-*,以便 Cookie、CSRF 与 HTTPS 判断正确
# NAV_TRUST_PROXY=1
# 站点仅通过 HTTPS 对外时建议开启(浏览器只带 Secure Cookie
# NAV_SESSION_COOKIE_SECURE=1
# CSRF 校验仍失败时,可填前端访问的完整 Origin,多个用英文逗号分隔,例如:
# NAV_CSRF_TRUSTED_ORIGINS=https://nav.example.com
+24
View File
@@ -6,6 +6,7 @@ from typing import Optional
from flask import Flask, flash, redirect, render_template, request, url_for from flask import Flask, flash, redirect, render_template, request, url_for
from flask_login import LoginManager, current_user, login_required, login_user, logout_user from flask_login import LoginManager, current_user, login_required, login_user, logout_user
from flask_wtf.csrf import CSRFProtect from flask_wtf.csrf import CSRFProtect
from werkzeug.middleware.proxy_fix import ProxyFix
from forms import GroupForm, LoginForm, ServiceForm from forms import GroupForm, LoginForm, ServiceForm
from models import Service, ServiceGroup, User, db from models import Service, ServiceGroup, User, db
@@ -63,6 +64,16 @@ def create_app() -> Flask:
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["WTF_CSRF_TIME_LIMIT"] = None app.config["WTF_CSRF_TIME_LIMIT"] = None
if os.environ.get("NAV_SESSION_COOKIE_SECURE") == "1":
app.config["SESSION_COOKIE_SECURE"] = True
app.config["REMEMBER_COOKIE_SECURE"] = True
trusted = os.environ.get("NAV_CSRF_TRUSTED_ORIGINS", "").strip()
if trusted:
app.config["WTF_CSRF_TRUSTED_ORIGINS"] = [
o.strip() for o in trusted.split(",") if o.strip()
]
db.init_app(app) db.init_app(app)
login_manager.init_app(app) login_manager.init_app(app)
login_manager.login_view = "login" login_manager.login_view = "login"
@@ -271,6 +282,19 @@ def create_app() -> Flask:
flash("服务已删除", "success") flash("服务已删除", "success")
return redirect(url_for("admin_services")) return redirect(url_for("admin_services"))
if os.environ.get("NAV_TRUST_PROXY") == "1":
app.wsgi_app = ProxyFix(
app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1
)
if not os.environ.get("NAV_SECRET_KEY"):
print(
"[nav] 警告: 未设置 NAV_SECRET_KEY。"
"若使用 gunicorn/uwsgi 等多 worker,或未固定密钥,登录后会话会失效;"
"请在环境变量中配置随机 NAV_SECRET_KEY。",
flush=True,
)
return app return app
+9
View File
@@ -7,6 +7,15 @@
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" /> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
</head> </head>
<body> <body>
{% with msgs = get_flashed_messages(with_categories=true) %}
{% if msgs %}
<div class="flash-wrap" role="status">
{% for category, message in msgs %}
<div class="flash {{ category }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block body %}{% endblock %} {% block body %}{% endblock %}
</body> </body>
</html> </html>
+5
View File
@@ -6,6 +6,11 @@
<h1>本地导航站</h1> <h1>本地导航站</h1>
<form method="post" novalidate> <form method="post" novalidate>
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
{% if request.method == "POST" and form.csrf_token.errors %}
{% for e in form.csrf_token.errors %}
<div class="errors" role="alert">{{ e }}</div>
{% endfor %}
{% endif %}
<div class="form-row"> <div class="form-row">
{{ form.username.label }} {{ form.username.label }}
{{ form.username(class="") }} {{ form.username(class="") }}