Files
xiaobai-review/backend/http/hubadmin.py
T
施工员andmultica-agent 3eaa36a8d5 feat(HEL-560): 数据中枢接管数据源/模型池/会员,注册改一次性邀请码
主站
- 新增 m0006 invite_codes 迁移;注册强制邀请码(首个管理员除外),消码与建号
  同一事务,并发提交只有一个能成功
- 新增 /api/hub-admin/* 服务端点(共享 HUB_ADMIN_TOKEN,先于鉴权校验),供数据
  中枢桥接读写会话/密码/模型池/会员/邀请码,并提供供应商模型列表拉取
- 前端:注册表单加邀请码(桌面 login、index.html、移动端);「系统管理」改为
  「数据中枢」入口指向 8766,原模型池与会员管理分区移除,仅留「行情管理」;
  随之清理陈旧 CSS

数据中枢
- 取消独立账号:删除 hub_admin/hub_sessions 与登录、改密、锁定逻辑,改为校验
  主站 xiaobai_session,仅管理员可进,CSRF 由会话派生,危险操作二次确认走主站
- 控制台新增数据源凭证可编辑区(原有内容一项不删)、供应商制模型池(自动拉取
  /models,失败退回卡内手动录入)、会员管理与邀请码页
- 日夜双主题:颜色收敛为同名 token 换值,SVG 改用 inline style 以吃到变量

自测
- 主站 verify_baseline 通过(498 项);数据中枢 235 项通过
- tools/verify_datahub_console.py 端到端跑通两服务真实对话;
  tools/verify_datahub_console_ui.py 浏览器跑通门禁/凭证/模型池/会员/主题/1030 窄屏

Co-authored-by: multica-agent <github@multica.ai>
2026-09-16 11:44:09 +08:00

156 lines
5.6 KiB
Python

from __future__ import annotations
import json
from http import HTTPStatus
from backend.features.accounts.security import token_hash, verify_password
class HubAdminHttpMixin:
"""Service-to-service bridge used by the data hub console (port 8766).
Every handler here is reached only after `require_service_token`, so the
shared `HUB_ADMIN_TOKEN` is the single trust boundary and no browser
session or CSRF token is involved. The data hub still verifies the site
session of the operator through `hub_session_check` before it exposes any
of these results to a page.
"""
def _hub_body(self) -> dict:
return self.read_json_body(allow_empty=True)
def _hub_failure(self, exc: Exception) -> None:
self.send_json({"error": str(exc)}, HTTPStatus.BAD_REQUEST)
def hub_session_check(self) -> None:
try:
body = self._hub_body()
except (ValueError, json.JSONDecodeError) as exc:
self._hub_failure(exc)
return
raw_token = str(body.get("session_token") or "")
user = (
self.application_service.database.session_user(token_hash(raw_token))
if raw_token
else None
)
if not user:
self.send_json({"ok": True, "authenticated": False})
return
self.send_json(
{
"ok": True,
"authenticated": True,
"user": {
"id": int(user["id"]),
"username": str(user["username"]),
"role": str(user.get("role") or "user"),
"is_admin": str(user.get("role") or "user") == "admin",
},
}
)
def hub_session_logout(self) -> None:
try:
body = self._hub_body()
except (ValueError, json.JSONDecodeError) as exc:
self._hub_failure(exc)
return
raw_token = str(body.get("session_token") or "")
if raw_token:
self.application_service.database.delete_session(token_hash(raw_token))
self.send_json({"ok": True})
def hub_password_check(self) -> None:
try:
body = self._hub_body()
user_id = int(body.get("user_id") or 0)
except (TypeError, ValueError, json.JSONDecodeError) as exc:
self._hub_failure(exc)
return
stored = self.application_service.database.user_password(user_id)
verified = bool(
stored
and verify_password(
str(body.get("password") or ""),
str(stored.get("password_salt") or ""),
str(stored.get("password_hash") or ""),
)
)
self.send_json({"ok": True, "verified": verified})
def hub_system_status(self) -> None:
service = self.application_service
self.send_json({"ok": True, **service.system_status(), "users": service.admin_users()})
def hub_save_settings(self) -> None:
try:
result = self.application_service.save_system_settings(self._hub_body())
self.send_json({"ok": True, **result})
except (ValueError, json.JSONDecodeError) as exc:
self._hub_failure(exc)
def hub_test_model(self) -> None:
try:
body = self._hub_body()
result = self.application_service.test_system_llm_profile(
str(body.get("model_id") or ""), body.get("profile") or {}
)
self.send_json({"ok": True, "result": result})
except (ValueError, json.JSONDecodeError) as exc:
self._hub_failure(exc)
def hub_fetch_models(self) -> None:
try:
body = self._hub_body()
models = self.application_service.fetch_llm_models(
str(body.get("base_url") or ""),
str(body.get("api_key") or ""),
)
self.send_json({"ok": True, "models": models})
except (ValueError, json.JSONDecodeError) as exc:
self._hub_failure(exc)
def hub_members(self) -> None:
service = self.application_service
self.send_json(
{
"ok": True,
"users": service.admin_users(),
"membership": service.system_status()["membership"],
}
)
def hub_save_membership(self) -> None:
try:
service = self.application_service
service.update_membership(self._hub_body())
self.send_json({"ok": True, "users": service.admin_users()})
except (ValueError, json.JSONDecodeError) as exc:
self._hub_failure(exc)
def hub_invites(self) -> None:
self.send_json({"ok": True, **self.application_service.accounts.invite_overview()})
def hub_create_invites(self) -> None:
try:
body = self._hub_body()
accounts = self.application_service.accounts
codes = accounts.generate_invite_codes(
body.get("count") or 1,
str(body.get("note") or ""),
int(body.get("created_by") or 0),
)
self.send_json({"ok": True, "created": codes, **accounts.invite_overview()})
except (ValueError, json.JSONDecodeError) as exc:
self._hub_failure(exc)
def hub_revoke_invite(self) -> None:
try:
body = self._hub_body()
accounts = self.application_service.accounts
accounts.revoke_invite_code(str(body.get("code_id") or body.get("code") or ""))
self.send_json({"ok": True, **accounts.invite_overview()})
except (ValueError, json.JSONDecodeError) as exc:
self._hub_failure(exc)