diff --git a/.mofin_token b/.mofin_token new file mode 100644 index 00000000..92957cf2 --- /dev/null +++ b/.mofin_token @@ -0,0 +1 @@ +nyPj-13XGqau4VdBkr2ksCycGpAtnVlNbbRaMrufas8 \ No newline at end of file diff --git a/server.py b/server.py index 8226015b..278d6acb 100644 --- a/server.py +++ b/server.py @@ -173,7 +173,21 @@ from mo_data import read_portfolio, read_decisions, read_watchlist from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_watchlist_stock, write_holding_strategy app = Flask(__name__, static_folder="static", static_url_path="") -app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # 禁静态缓存:前端迭代频繁,防浏览器旧版残留 +app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 + +# ── 访问鉴权:?token= 或 X-Auth-Token 头(2026-08-28 防公网裸访问)── +MOFIN_TOKEN = os.environ.get("MOFIN_TOKEN", "nyPj-13XGqau4VdBkr2ksCycGpAtnVlNbbRaMrufas8") + +@app.before_request +def _require_token(): + # 静态资源放行(JS/CSS/HTML外壳,不携带数据) + if request.path.startswith("/static") or request.path in ("/favicon.ico",): + return None + # token 校验:URL 参数 ?token=xxx 或请求头 X-Auth-Token + t = (request.args.get("token") or "").strip() or (request.headers.get("X-Auth-Token") or "").strip() + if t != MOFIN_TOKEN: + return jsonify({"ok": False, "error": "unauthorized: missing or invalid token"}), 401 + return None # 禁静态缓存:前端迭代频繁,防浏览器旧版残留 DATA_DIR = Path(__file__).parent / "data" UPLOAD_DIR = Path(__file__).parent / "uploads" diff --git a/static/index.html b/static/index.html index 116f60c6..10645fb6 100644 --- a/static/index.html +++ b/static/index.html @@ -318,10 +318,15 @@ function renderTab(name) { else if (name === 'docs') renderDocs(); } + +// ── 面板 token:从 URL ?token=xxx 提取(2026-08-28 鉴权)── +const MOFIN_TOKEN = new URLSearchParams(location.search).get('token') || ''; + // ── Data Fetching ── async function fetchJSON(url) { try { - const r = await fetch(url); + const sep = url.includes('?') ? '&' : '?'; + const r = await fetch(url + sep + 'token=' + encodeURIComponent(MOFIN_TOKEN)); return await r.json(); } catch(e) { return {}; } }