81f4dfd0ad
- 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) 全过。
44 lines
1.4 KiB
Python
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])
|