"""JWT access tokens (架构文档 §9.1: Bearer JWT, 15 分钟). The revocable refresh-token half (`id_session`) is FR-ACC-05 and is deliberately NOT implemented in this scaffold — see packages/identity/README.md. """ from __future__ import annotations import time from dataclasses import dataclass from typing import Any import jwt from packages.shared.errors import AppError, ErrorCode @dataclass(frozen=True, slots=True) class AccessTokenClaims: user_id: int issued_at: int expires_at: int issuer: str def create_access_token( *, user_id: int, secret: str, algorithm: str = "HS256", ttl_seconds: int = 900, issuer: str = "meida", now: int | None = None, ) -> str: issued_at = int(now if now is not None else time.time()) payload: dict[str, Any] = { "sub": str(user_id), "iat": issued_at, "exp": issued_at + ttl_seconds, "iss": issuer, "typ": "access", } return str(jwt.encode(payload, secret, algorithm=algorithm)) def decode_access_token( token: str, *, secret: str, algorithm: str = "HS256", issuer: str = "meida", ) -> AccessTokenClaims: try: payload = jwt.decode( token, secret, algorithms=[algorithm], issuer=issuer, options={"require": ["exp", "iat", "sub"]}, ) except jwt.ExpiredSignatureError as exc: raise AppError(ErrorCode.TOKEN_EXPIRED) from exc except jwt.InvalidTokenError as exc: raise AppError(ErrorCode.TOKEN_INVALID) from exc try: user_id = int(payload["sub"]) except (KeyError, TypeError, ValueError) as exc: raise AppError(ErrorCode.TOKEN_INVALID) from exc return AccessTokenClaims( user_id=user_id, issued_at=int(payload["iat"]), expires_at=int(payload["exp"]), issuer=str(payload.get("iss", issuer)), )