tfrs-auth 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ *.whl
9
+
10
+ # uv / venv
11
+ .venv/
12
+ venv/
13
+ .uv/
14
+
15
+ # 测试 / 覆盖
16
+ .pytest_cache/
17
+ .ruff_cache/
18
+ .coverage
19
+ coverage.xml
20
+ htmlcov/
21
+
22
+ # 环境与密钥(E2E 凭证绝不入库)
23
+ .env
24
+ .env.*
25
+ !.env.example
26
+
27
+ # IDE / OS
28
+ .idea/
29
+ .vscode/
30
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TuringFocus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.4
2
+ Name: tfrs-auth
3
+ Version: 0.1.0
4
+ Summary: TFRS 跨语言共享凭证工具包(Python):PAT/client_credentials → 短 JWT 换发 + 缓存/刷新 + 纯 RS256 验签
5
+ Project-URL: Homepage, https://cnb.cool/turingfocus/foundation/tfrs-foundation-py
6
+ Project-URL: Repository, https://cnb.cool/turingfocus/foundation/tfrs-foundation-py
7
+ Project-URL: Issues, https://cnb.cool/turingfocus/foundation/tfrs-foundation-py/-/issues
8
+ Author: TuringFocus
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: jwt,oauth,rfc8693,tfrs,token-exchange
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Security
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: pyjwt[crypto]>=2.8
24
+ Provides-Extra: httpx
25
+ Requires-Dist: httpx>=0.27; extra == 'httpx'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # tfrs-auth (Python)
29
+
30
+ TFRS AccessToken 客户端工具包:把「PAT / `client_credentials` → 短 RS256 JWT 换发
31
+ (RFC 8693)+ 缓存/刷新」与「Claims/scope 契约 + 纯验签」抽成可复用包,避免每个
32
+ MCP 工具 / SDK 重复实现 token 获取与生命周期管理。
33
+
34
+ > SoT = TFRSManager Contract Registry。本包 `contract.py` 为镜像,**变更先改 Manager**。
35
+ > 强制执行层(验签中间件/Socket.IO/ESO/默认拒绝 CI)**不在本包**,留 TFRobotServer。
36
+
37
+ ## 安装
38
+
39
+ ```bash
40
+ # 从 PyPI(推荐)
41
+ uv add tfrs-auth # 核心:契约 + 纯验签
42
+ uv add "tfrs-auth[httpx]" # 含客户端 token source / JWKS live 拉取
43
+ # 或:pip install "tfrs-auth[httpx]"
44
+
45
+ # 或从 git + tag(未发布版本)
46
+ uv add "tfrs-auth[httpx] @ git+https://cnb.cool/turingfocus/foundation/tfrs-foundation-py@<tag>#subdirectory=packages/tfrs-auth"
47
+ ```
48
+
49
+ - 核心依赖:`pyjwt[crypto]`(契约 + 纯验签)。
50
+ - 可选 extra `httpx`:客户端 token source / JWKS live 拉取。
51
+ - 许可证:MIT。
52
+
53
+ ## 用法
54
+
55
+ ### 客户端 token source(`client_credentials`,S5 A2A 主叫端)
56
+
57
+ ```python
58
+ from tfrs_auth import AsyncCachingTokenSource, ClientCredentials
59
+
60
+ cred = ClientCredentials.for_robot(
61
+ client_id=machine_client_id, # 机器人自身 Account.ID
62
+ client_secret=machine_client_secret, # tfp_ 明文(machineClientSecret)
63
+ callee_robot_id=callee_id, # → aud=robot:<calleeId>
64
+ scope=["a2a:invoke"],
65
+ )
66
+ async with AsyncCachingTokenSource(
67
+ cred, token_url="https://<user-host>/api/v1/oauth/token"
68
+ ) as source:
69
+ token = await source.token() # 缓存 + 临期自动刷新 + 退避
70
+ headers = {"Authorization": f"Bearer {token.access_token}"}
71
+ ```
72
+
73
+ 同步场景用 `CachingTokenSource`(API 对称)。
74
+
75
+ ### 纯验签(资源服务器侧,零网络)
76
+
77
+ ```python
78
+ from tfrs_auth import JwtVerifier
79
+
80
+ verifier = JwtVerifier.from_jwks(jwks_dict, issuer="https://<user-host>", audience="robot:42")
81
+ claims = verifier.verify(access_token) # 失败抛 TokenVerificationError
82
+ assert claims.has_scope("a2a:invoke")
83
+ ```
84
+
85
+ live 拉取 JWKS:`JwtVerifier.from_url("https://<user-host>/.well-known/jwks.json", ...)`。
86
+
87
+ > ⚠️ **资源服务器务必传 `issuer` 与 `audience`**:二者仅在提供时才校验。不传 `audience`
88
+ > 会放行签给**任意**机器人的 token,丧失 `aud=robot:<id>` 受众隔离(`issuer` 之于颁发者
89
+ > 隔离同理)。`from_url` 的未知-kid 刷新已内置限速(`min_refresh_interval`,默认 10s),
90
+ > 避免被构造的随机-kid token 放大成对 JWKS 端点的 DoS。
91
+ >
92
+ > 异常约定:验签失败抛 `TokenVerificationError`;`from_url` 模式下 JWKS 拉取的网络失败抛
93
+ > `TransportError`。消费方应 `except (TokenVerificationError, TransportError)` 同时兜住。
94
+
95
+ ## 模块
96
+
97
+ | 模块 | 内容 |
98
+ |------|------|
99
+ | `contract` | `Scope`(8 active + 2 reserved) · `Claims` · `GrantType`/`SubjectTokenType` URN · `OAuthErrorCode` · JWKS/kid 形状 |
100
+ | `errors` | 异常树(`InvalidClientError`(401) / `InvalidTargetError` / `TemporarilyUnavailableError`(503) …)+ `from_oauth_error` |
101
+ | `client` | `Token` · `TokenSource`/`AsyncTokenSource` · `CachingTokenSource` / `AsyncCachingTokenSource`(single-flight + 退避)|
102
+ | `credentials` | `ClientCredentials`(✅)· `PatCredential` / `UserJwtCredential`(骨架)|
103
+ | `verify` | `JwtVerifier`(RS256 + JWKS)|
104
+ | `transport` | `BearerAuth`(httpx auth,骨架)|
105
+
106
+ ## 边界与状态
107
+
108
+ - **已实现**:`contract` / `errors` / `client`(sync+async caching) / `credentials.ClientCredentials` / `verify`。
109
+ - **骨架(首批 CNB Issue)**:`credentials.PatCredential` / `credentials.UserJwtCredential` / `transport.BearerAuth`。
@@ -0,0 +1,82 @@
1
+ # tfrs-auth (Python)
2
+
3
+ TFRS AccessToken 客户端工具包:把「PAT / `client_credentials` → 短 RS256 JWT 换发
4
+ (RFC 8693)+ 缓存/刷新」与「Claims/scope 契约 + 纯验签」抽成可复用包,避免每个
5
+ MCP 工具 / SDK 重复实现 token 获取与生命周期管理。
6
+
7
+ > SoT = TFRSManager Contract Registry。本包 `contract.py` 为镜像,**变更先改 Manager**。
8
+ > 强制执行层(验签中间件/Socket.IO/ESO/默认拒绝 CI)**不在本包**,留 TFRobotServer。
9
+
10
+ ## 安装
11
+
12
+ ```bash
13
+ # 从 PyPI(推荐)
14
+ uv add tfrs-auth # 核心:契约 + 纯验签
15
+ uv add "tfrs-auth[httpx]" # 含客户端 token source / JWKS live 拉取
16
+ # 或:pip install "tfrs-auth[httpx]"
17
+
18
+ # 或从 git + tag(未发布版本)
19
+ uv add "tfrs-auth[httpx] @ git+https://cnb.cool/turingfocus/foundation/tfrs-foundation-py@<tag>#subdirectory=packages/tfrs-auth"
20
+ ```
21
+
22
+ - 核心依赖:`pyjwt[crypto]`(契约 + 纯验签)。
23
+ - 可选 extra `httpx`:客户端 token source / JWKS live 拉取。
24
+ - 许可证:MIT。
25
+
26
+ ## 用法
27
+
28
+ ### 客户端 token source(`client_credentials`,S5 A2A 主叫端)
29
+
30
+ ```python
31
+ from tfrs_auth import AsyncCachingTokenSource, ClientCredentials
32
+
33
+ cred = ClientCredentials.for_robot(
34
+ client_id=machine_client_id, # 机器人自身 Account.ID
35
+ client_secret=machine_client_secret, # tfp_ 明文(machineClientSecret)
36
+ callee_robot_id=callee_id, # → aud=robot:<calleeId>
37
+ scope=["a2a:invoke"],
38
+ )
39
+ async with AsyncCachingTokenSource(
40
+ cred, token_url="https://<user-host>/api/v1/oauth/token"
41
+ ) as source:
42
+ token = await source.token() # 缓存 + 临期自动刷新 + 退避
43
+ headers = {"Authorization": f"Bearer {token.access_token}"}
44
+ ```
45
+
46
+ 同步场景用 `CachingTokenSource`(API 对称)。
47
+
48
+ ### 纯验签(资源服务器侧,零网络)
49
+
50
+ ```python
51
+ from tfrs_auth import JwtVerifier
52
+
53
+ verifier = JwtVerifier.from_jwks(jwks_dict, issuer="https://<user-host>", audience="robot:42")
54
+ claims = verifier.verify(access_token) # 失败抛 TokenVerificationError
55
+ assert claims.has_scope("a2a:invoke")
56
+ ```
57
+
58
+ live 拉取 JWKS:`JwtVerifier.from_url("https://<user-host>/.well-known/jwks.json", ...)`。
59
+
60
+ > ⚠️ **资源服务器务必传 `issuer` 与 `audience`**:二者仅在提供时才校验。不传 `audience`
61
+ > 会放行签给**任意**机器人的 token,丧失 `aud=robot:<id>` 受众隔离(`issuer` 之于颁发者
62
+ > 隔离同理)。`from_url` 的未知-kid 刷新已内置限速(`min_refresh_interval`,默认 10s),
63
+ > 避免被构造的随机-kid token 放大成对 JWKS 端点的 DoS。
64
+ >
65
+ > 异常约定:验签失败抛 `TokenVerificationError`;`from_url` 模式下 JWKS 拉取的网络失败抛
66
+ > `TransportError`。消费方应 `except (TokenVerificationError, TransportError)` 同时兜住。
67
+
68
+ ## 模块
69
+
70
+ | 模块 | 内容 |
71
+ |------|------|
72
+ | `contract` | `Scope`(8 active + 2 reserved) · `Claims` · `GrantType`/`SubjectTokenType` URN · `OAuthErrorCode` · JWKS/kid 形状 |
73
+ | `errors` | 异常树(`InvalidClientError`(401) / `InvalidTargetError` / `TemporarilyUnavailableError`(503) …)+ `from_oauth_error` |
74
+ | `client` | `Token` · `TokenSource`/`AsyncTokenSource` · `CachingTokenSource` / `AsyncCachingTokenSource`(single-flight + 退避)|
75
+ | `credentials` | `ClientCredentials`(✅)· `PatCredential` / `UserJwtCredential`(骨架)|
76
+ | `verify` | `JwtVerifier`(RS256 + JWKS)|
77
+ | `transport` | `BearerAuth`(httpx auth,骨架)|
78
+
79
+ ## 边界与状态
80
+
81
+ - **已实现**:`contract` / `errors` / `client`(sync+async caching) / `credentials.ClientCredentials` / `verify`。
82
+ - **骨架(首批 CNB Issue)**:`credentials.PatCredential` / `credentials.UserJwtCredential` / `transport.BearerAuth`。
@@ -0,0 +1,43 @@
1
+ [project]
2
+ name = "tfrs-auth"
3
+ version = "0.1.0"
4
+ description = "TFRS 跨语言共享凭证工具包(Python):PAT/client_credentials → 短 JWT 换发 + 缓存/刷新 + 纯 RS256 验签"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [{ name = "TuringFocus" }]
10
+ keywords = ["oauth", "rfc8693", "token-exchange", "jwt", "tfrs"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Intended Audience :: Developers",
14
+ "Operating System :: OS Independent",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Topic :: Security",
20
+ "Topic :: Software Development :: Libraries :: Python Modules",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = [
24
+ # 纯验签(verify.from_jwks)+ 契约只需 pyjwt[crypto];httpx 为可选 extra。
25
+ "pyjwt[crypto]>=2.8",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ # 客户端 token source / JWKS live 拉取需要 httpx。
30
+ httpx = ["httpx>=0.27"]
31
+
32
+ [project.urls]
33
+ Homepage = "https://cnb.cool/turingfocus/foundation/tfrs-foundation-py"
34
+ Repository = "https://cnb.cool/turingfocus/foundation/tfrs-foundation-py"
35
+ Issues = "https://cnb.cool/turingfocus/foundation/tfrs-foundation-py/-/issues"
36
+
37
+ [build-system]
38
+ # hatchling >= 1.27 才支持 PEP 639 的 SPDX license 表达式(license = "MIT")。
39
+ requires = ["hatchling>=1.27"]
40
+ build-backend = "hatchling.build"
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages = ["src/tfrs_auth"]
@@ -0,0 +1,116 @@
1
+ """tfrs-auth —— TFRS 跨语言共享凭证工具包(Python)。
2
+
3
+ 三层(PRD §13.2,**强制执行层不进包**,留 TFRobotServer):
4
+
5
+ 1. **契约层** :mod:`tfrs_auth.contract` —— Claims / scope 词表 / 错误码 / grant·
6
+ token-type URN / JWKS·kid 形状(M0 SoT 镜像)。
7
+ 2. **客户端 token source** :mod:`tfrs_auth.client` + :mod:`tfrs_auth.credentials`
8
+ —— PAT / User JWT / client_credentials 换发 + 缓存 / single-flight 刷新 / 退避。
9
+ 3. **纯验签** :mod:`tfrs_auth.verify` —— RS256 + JWKS,零 Web 框架。
10
+
11
+ 快速上手(client_credentials,S5 A2A 主叫端)::
12
+
13
+ from tfrs_auth import AsyncCachingTokenSource, ClientCredentials, JwtVerifier
14
+
15
+ cred = ClientCredentials.for_robot(
16
+ client_id=machine_client_id,
17
+ client_secret=machine_client_secret,
18
+ callee_robot_id=callee_id,
19
+ scope=["a2a:invoke"],
20
+ )
21
+ source = AsyncCachingTokenSource(cred, token_url="https://<host>/api/v1/oauth/token")
22
+ token = await source.token() # 缓存 + 临期自动刷新
23
+ # 注入:Authorization: Bearer {token.access_token}
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from . import contract, credentials, verify
29
+ from .client import (
30
+ AsyncCachingTokenSource,
31
+ AsyncTokenSource,
32
+ CachingTokenSource,
33
+ Credential,
34
+ Token,
35
+ TokenSource,
36
+ )
37
+ from .contract import (
38
+ ACTIVE_SCOPES,
39
+ CONTRACT_VERSION,
40
+ DEFAULT_KID,
41
+ RESERVED_SCOPES,
42
+ SIGNING_ALG,
43
+ Claims,
44
+ GrantType,
45
+ OAuthErrorCode,
46
+ Scope,
47
+ SubjectTokenType,
48
+ is_active_scope,
49
+ robot_audience,
50
+ scopes_from_str,
51
+ scopes_to_str,
52
+ )
53
+ from .credentials import ClientCredentials, PatCredential, UserJwtCredential
54
+ from .errors import (
55
+ InvalidClientError,
56
+ InvalidGrantError,
57
+ InvalidRequestError,
58
+ InvalidScopeError,
59
+ InvalidTargetError,
60
+ PaymentRequiredError,
61
+ TemporarilyUnavailableError,
62
+ TfrsAuthError,
63
+ TokenExchangeError,
64
+ TokenVerificationError,
65
+ TransportError,
66
+ )
67
+ from .verify import JwtVerifier
68
+
69
+ __version__ = "0.1.0"
70
+
71
+ __all__ = [
72
+ "__version__",
73
+ # 子模块
74
+ "contract",
75
+ "credentials",
76
+ "verify",
77
+ # 契约
78
+ "Claims",
79
+ "Scope",
80
+ "GrantType",
81
+ "SubjectTokenType",
82
+ "OAuthErrorCode",
83
+ "ACTIVE_SCOPES",
84
+ "RESERVED_SCOPES",
85
+ "CONTRACT_VERSION",
86
+ "DEFAULT_KID",
87
+ "SIGNING_ALG",
88
+ "is_active_scope",
89
+ "scopes_to_str",
90
+ "scopes_from_str",
91
+ "robot_audience",
92
+ # 客户端
93
+ "Token",
94
+ "TokenSource",
95
+ "AsyncTokenSource",
96
+ "Credential",
97
+ "CachingTokenSource",
98
+ "AsyncCachingTokenSource",
99
+ "ClientCredentials",
100
+ "PatCredential",
101
+ "UserJwtCredential",
102
+ # 验签
103
+ "JwtVerifier",
104
+ # 错误
105
+ "TfrsAuthError",
106
+ "TokenExchangeError",
107
+ "TokenVerificationError",
108
+ "TransportError",
109
+ "InvalidClientError",
110
+ "InvalidGrantError",
111
+ "InvalidRequestError",
112
+ "InvalidTargetError",
113
+ "InvalidScopeError",
114
+ "TemporarilyUnavailableError",
115
+ "PaymentRequiredError",
116
+ ]