8eefc6cfd5
Users can paste the key from the seller instead of editing license.env; it persists in license_state.json. Co-authored-by: Cursor <cursoragent@cursor.com>
91 lines
3.0 KiB
Python
91 lines
3.0 KiB
Python
"""Flask 实例许可门禁与 /license 页。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from flask import Flask, jsonify, redirect, render_template_string, request
|
|
|
|
from lib.license.license_lib import (
|
|
get_device_id,
|
|
get_license_status,
|
|
is_license_valid,
|
|
redeem_code,
|
|
validate_license,
|
|
)
|
|
|
|
_TEMPLATE_PATH = Path(__file__).resolve().parent / "templates" / "license.html"
|
|
|
|
|
|
def _allowed_path(path: str) -> bool:
|
|
if path in ("/license", "/api/license/status", "/api/license/redeem", "/api/license/validate", "/health"):
|
|
return True
|
|
if path.startswith("/static/"):
|
|
return True
|
|
if path.startswith("/favicon"):
|
|
return True
|
|
return False
|
|
|
|
|
|
def install_license_gate(app: Flask) -> None:
|
|
"""注册 /license 与 before_request 门禁。三所 Flask 共用。"""
|
|
|
|
@app.get("/health")
|
|
def _license_health():
|
|
st = get_license_status(skip_remote=True)
|
|
return jsonify({"ok": True, "license_valid": bool(st.get("valid"))})
|
|
|
|
@app.get("/api/license/status")
|
|
def _license_status_api():
|
|
return jsonify(get_license_status())
|
|
|
|
@app.post("/api/license/redeem")
|
|
def _license_redeem_api():
|
|
data = request.get_json(silent=True) or {}
|
|
code = (data.get("code") or request.form.get("code") or "").strip()
|
|
ckey = (data.get("client_api_key") or request.form.get("client_api_key") or "").strip()
|
|
return jsonify(redeem_code(code, client_api_key=ckey or None))
|
|
|
|
@app.post("/api/license/validate")
|
|
def _license_validate_api():
|
|
return jsonify(validate_license(force=True))
|
|
|
|
@app.route("/license", methods=["GET", "POST"])
|
|
def _license_page():
|
|
msg = ""
|
|
err = ""
|
|
if request.method == "POST":
|
|
code = (request.form.get("code") or "").strip()
|
|
ckey = (request.form.get("client_api_key") or "").strip()
|
|
result = redeem_code(code, client_api_key=ckey or None)
|
|
if result.get("ok"):
|
|
msg = result.get("message") or "激活成功"
|
|
else:
|
|
err = result.get("message") or "激活失败"
|
|
status = get_license_status()
|
|
html = _TEMPLATE_PATH.read_text(encoding="utf-8")
|
|
return render_template_string(
|
|
html,
|
|
device_id=get_device_id(),
|
|
status=status,
|
|
message=msg,
|
|
error=err,
|
|
api_url=status.get("api_url") or "",
|
|
wechat="dekun03",
|
|
)
|
|
|
|
@app.before_request
|
|
def _license_before_request():
|
|
if os.getenv("LICENSE_DISABLED", "").strip().lower() in ("1", "true", "yes", "on"):
|
|
return None
|
|
path = request.path or "/"
|
|
if _allowed_path(path):
|
|
return None
|
|
# hub bridge 内部调用:仍要求已授权(整机许可)
|
|
if is_license_valid():
|
|
return None
|
|
if path.startswith("/api/"):
|
|
return jsonify({"ok": False, "error": "license_required", "message": "请先激活许可"}), 403
|
|
return redirect("/license")
|