Metadata-Version: 2.4
Name: tfrs-auth
Version: 0.1.1
Summary: TFRS 跨语言共享凭证工具包（Python）：PAT/client_credentials → 短 JWT 换发 + 缓存/刷新 + 纯 RS256 验签
Project-URL: Homepage, https://cnb.cool/turingfocus/foundation/tfrs-foundation-py
Project-URL: Repository, https://cnb.cool/turingfocus/foundation/tfrs-foundation-py
Project-URL: Issues, https://cnb.cool/turingfocus/foundation/tfrs-foundation-py/-/issues
Author: TuringFocus
License-Expression: MIT
License-File: LICENSE
Keywords: jwt,oauth,rfc8693,tfrs,token-exchange
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: pyjwt[crypto]>=2.8
Provides-Extra: httpx
Requires-Dist: httpx>=0.27; extra == 'httpx'
Description-Content-Type: text/markdown

# tfrs-auth (Python)

TFRS AccessToken 客户端工具包：把「PAT / `client_credentials` → 短 RS256 JWT 换发
（RFC 8693）+ 缓存/刷新」与「Claims/scope 契约 + 纯验签」抽成可复用包，避免每个
MCP 工具 / SDK 重复实现 token 获取与生命周期管理。

> SoT = TFRSManager Contract Registry。本包 `contract.py` 为镜像，**变更先改 Manager**。
> 强制执行层（验签中间件/Socket.IO/ESO/默认拒绝 CI）**不在本包**，留 TFRobotServer。

## 安装

```bash
# 从 PyPI（推荐）
uv add tfrs-auth                  # 核心：契约 + 纯验签
uv add "tfrs-auth[httpx]"         # 含客户端 token source / JWKS live 拉取
# 或：pip install "tfrs-auth[httpx]"

# 或从 git + tag（未发布版本）
uv add "tfrs-auth[httpx] @ git+https://cnb.cool/turingfocus/foundation/tfrs-foundation-py@<tag>#subdirectory=packages/tfrs-auth"
```

- 核心依赖：`pyjwt[crypto]`（契约 + 纯验签）。
- 可选 extra `httpx`：客户端 token source / JWKS live 拉取。
- 许可证：MIT。

## 用法

### 客户端 token source（`client_credentials`，S5 A2A 主叫端）

```python
from tfrs_auth import AsyncCachingTokenSource, ClientCredentials

cred = ClientCredentials.for_robot(
    client_id=machine_client_id,          # 机器人自身 Account.ID
    client_secret=machine_client_secret,  # tfp_ 明文（machineClientSecret）
    callee_robot_id=callee_id,            # → aud=robot:<calleeId>
    scope=["a2a:invoke"],
)
async with AsyncCachingTokenSource(
    cred, token_url="https://<user-host>/api/v1/oauth/token"
) as source:
    token = await source.token()          # 缓存 + 临期自动刷新 + 退避
    headers = {"Authorization": f"Bearer {token.access_token}"}
```

同步场景用 `CachingTokenSource`（API 对称）。

### 纯验签（资源服务器侧，零网络）

```python
from tfrs_auth import JwtVerifier

verifier = JwtVerifier.from_jwks(jwks_dict, issuer="https://<user-host>", audience="robot:42")
claims = verifier.verify(access_token)    # 失败抛 TokenVerificationError
assert claims.has_scope("a2a:invoke")
```

live 拉取 JWKS：`JwtVerifier.from_url("https://<user-host>/.well-known/jwks.json", ...)`。

> ⚠️ **资源服务器务必传 `issuer` 与 `audience`**：二者仅在提供时才校验。不传 `audience`
> 会放行签给**任意**机器人的 token，丧失 `aud=robot:<id>` 受众隔离（`issuer` 之于颁发者
> 隔离同理）。`from_url` 的未知-kid 刷新已内置限速（`min_refresh_interval`，默认 10s），
> 避免被构造的随机-kid token 放大成对 JWKS 端点的 DoS。
>
> 异常约定：验签失败抛 `TokenVerificationError`；`from_url` 模式下 JWKS 拉取的网络失败抛
> `TransportError`。消费方应 `except (TokenVerificationError, TransportError)` 同时兜住。

## 模块

| 模块 | 内容 |
|------|------|
| `contract` | `Scope`(8 active + 2 reserved) · `Claims` · `GrantType`/`SubjectTokenType` URN · `OAuthErrorCode` · JWKS/kid 形状 |
| `errors` | 异常树（`InvalidClientError`(401) / `InvalidTargetError` / `TemporarilyUnavailableError`(503) …）+ `from_oauth_error` |
| `client` | `Token` · `TokenSource`/`AsyncTokenSource` · `CachingTokenSource` / `AsyncCachingTokenSource`（single-flight + 退避）|
| `credentials` | `ClientCredentials`（✅）· `PatCredential` / `UserJwtCredential`（骨架）|
| `verify` | `JwtVerifier`（RS256 + JWKS）|
| `transport` | `BearerAuth`（httpx auth，骨架）|

## 边界与状态

- **已实现**：`contract` / `errors` / `client`(sync+async caching) / `credentials.ClientCredentials` / `verify`。
- **骨架（首批 CNB Issue）**：`credentials.PatCredential` / `credentials.UserJwtCredential` / `transport.BearerAuth`。
