Files
server-core/app/park/auth.py
T
Pine 81f4dfd0ad feat(park): 园区多租户 — SQLite(tenants表+companies+kb) + 每租户引擎 + 认证 + 作用域隔离
- tenants.py:park_tenants/park_companies/park_kb_docs 三表(serverdata/park/park.db),
  每园区独立 name/intro(两段式)/auth(账密 salted hash)/data(大屏锚点)/agent/kb;
  ensure_default_tenant 自愈建默认园区+灌 39 家企业。
- sim_engine 重构为 SimEngine(data)——每租户独立实例,get_engine(tid)/refresh_engine(tid)。
- auth.py:/park/auth/login(账密→长效 tenant JWT)、/park/auth/me、require_tenant(401)。
- mqtt:register/publish_command/publish_tick 按 tenant_id 作用域(拓扑为 command/tid/clientId)。
- routers:/park/tenants CRUD + 全部数据/指令端点租户作用域(authorization token 或 ?tenant_id)。
- app.py lifespan init_db + 逐租户 tick/publish;tools._query_companies 读租户库。
- .gitignore 增 serverdata/park/。TestClient:建租户/登录/快照隔离/企业CRUD/指令鉴权(无token401) 全过。
2026-08-24 18:06:51 +08:00

44 lines
1.4 KiB
Python

# -*- coding: utf-8 -*-
"""园区租户认证 —— 大屏登录(账号密码 → 长效 tenant token,一次登录持久保持)。"""
from __future__ import annotations
import datetime
import jwt
from fastapi import Header, HTTPException
from .config import settings
_SECRET = settings.ADMIN_SECRET
_ALG = "HS256"
_TTL_DAYS = 365
def create_token(tenant_id: str) -> str:
now = datetime.datetime.now(datetime.timezone.utc)
payload = {
"typ": "park",
"tid": tenant_id,
"iat": now,
"exp": now + datetime.timedelta(days=_TTL_DAYS),
}
return jwt.encode(payload, _SECRET, algorithm=_ALG)
def parse_token(token: str) -> str:
"""返回 tenant_id;非法/过期抛 401。"""
try:
payload = jwt.decode(token, _SECRET, algorithms=[_ALG])
except jwt.PyJWTError as e: # noqa: BLE001
raise HTTPException(status_code=401, detail=f"park token 无效: {e}") from e
if payload.get("typ") != "park" or not payload.get("tid"):
raise HTTPException(status_code=401, detail="park token 无效")
return payload["tid"]
def require_tenant(authorization: str | None = Header(default=None, description="Bearer <park-token>")) -> str:
"""大屏接口鉴权:从 Authorization 解析 tenant_id。"""
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="缺少 park token")
return parse_token(authorization.split(" ", 1)[1])