# -*- coding: utf-8 -*- """JWT 访问令牌:签发与验签。 - 无状态验签(HS256),claims 携带角色/组织/数据范围/权限。 - 撤销由 ``sessions`` 表(按 jti)+ ``users.token_version`` 兜底, ``get_current_user`` 每请求一次 DB 读校验。 """ from __future__ import annotations import uuid from datetime import datetime, timedelta, timezone import jwt as pyjwt from . import config JTI_TTL_SECONDS = config.JWT_ACCESS_EXPIRY_SECONDS def _now_ts() -> int: return int(datetime.now(timezone.utc).timestamp()) def create_access_token( user: dict, *, permissions: list[str], scope_region_ids: list[str], scope_level: str | None, expiry_seconds: int, identity_id: str | None = None, ) -> tuple[str, str, str]: """签发 JWT,返回 (token, jti, expires_at_iso)。 ``user`` 为来自 UserRepository 的用户 dict(含 id/username/role/ sub_role/org_id/region_id/token_version)。``identity_id`` 非空时表示 该令牌绑定到指定端口身份(get_current_user 据此解析当前身份)。 """ jti = str(uuid.uuid4()) now = _now_ts() exp_ts = now + (expiry_seconds if expiry_seconds else config.JWT_ACCESS_EXPIRY_SECONDS) payload = { "sub": user["id"], "username": user.get("username", ""), "role": user.get("role", "opc_member"), "sub_role": user.get("sub_role"), "org_id": user.get("org_id"), "region_id": user.get("region_id"), "identity_id": identity_id, "scope_level": scope_level, "scope_region_ids": scope_region_ids, "perms": permissions, "ver": user.get("token_version", 0), "jti": jti, "iat": now, "exp": exp_ts, } token = pyjwt.encode(payload, config.JWT_SECRET, algorithm=config.JWT_ALGORITHM) expires_at = datetime.fromtimestamp(exp_ts, tz=timezone.utc).isoformat() return token, jti, expires_at def decode_access_token(token: str) -> dict | None: """验签并返回 claims;无效/过期返回 None。""" try: payload = pyjwt.decode( token, config.JWT_SECRET, algorithms=[config.JWT_ALGORITHM], ) except pyjwt.PyJWTError: return None return payload