@eduwork/dsh-oidc 0.1.0-alpha.11
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.
- package/CHANGELOG.en.md +120 -0
- package/CHANGELOG.md +120 -0
- package/LICENSE +21 -0
- package/README.en.md +184 -0
- package/README.md +143 -0
- package/THIRD_PARTY_NOTICES.en.md +51 -0
- package/THIRD_PARTY_NOTICES.md +53 -0
- package/cordis.patch.yml +8 -0
- package/docs/EDUWORK-MIGRATION.en.md +23 -0
- package/docs/EDUWORK-MIGRATION.md +23 -0
- package/docs/architecture.en.md +125 -0
- package/docs/architecture.md +125 -0
- package/docs/compatibility.en.md +61 -0
- package/docs/compatibility.md +61 -0
- package/docs/decisions/0001-one-plugin-composition-boundary.en.md +34 -0
- package/docs/decisions/0001-one-plugin-composition-boundary.md +34 -0
- package/docs/dsh-integration.en.md +171 -0
- package/docs/dsh-integration.md +171 -0
- package/docs/ecnu-reference.en.md +66 -0
- package/docs/ecnu-reference.md +66 -0
- package/docs/enterprise-profile.en.md +159 -0
- package/docs/enterprise-profile.md +159 -0
- package/docs/getting-started.en.md +137 -0
- package/docs/getting-started.md +239 -0
- package/docs/key-binding-protocol.en.md +227 -0
- package/docs/key-binding-protocol.md +227 -0
- package/docs/oidc-interoperability.en.md +100 -0
- package/docs/oidc-interoperability.md +100 -0
- package/docs/release-checklist.en.md +60 -0
- package/docs/release-checklist.md +60 -0
- package/docs/security-model.en.md +138 -0
- package/docs/security-model.md +138 -0
- package/docs/server-integration-contract.en.md +706 -0
- package/docs/server-integration-contract.md +706 -0
- package/examples/ecnu.enterprise-profile.example.json +81 -0
- package/examples/enterprise-profile.example.json +77 -0
- package/lib/client.js +5855 -0
- package/lib/index.js +59 -0
- package/lib/oidc.js +649 -0
- package/lib/profile.js +371 -0
- package/lib/provider/core.js +138 -0
- package/lib/provider/index.js +156 -0
- package/lib/provider/transform-adapter.js +65 -0
- package/lib/provider/transforms.js +59 -0
- package/lib/typert-schemas.js +84 -0
- package/lib/typert.host.js +38 -0
- package/lib/typert.remote-client.js +37 -0
- package/package.json +120 -0
- package/protocol/openapi.yaml +257 -0
- package/schema/enterprise-profile.v1alpha1.schema.json +174 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Worker User Center Key Binding Protocol v1
|
|
2
|
+
|
|
3
|
+
[简体中文](key-binding-protocol.md) | **English**
|
|
4
|
+
|
|
5
|
+
## 1. Status and terminology
|
|
6
|
+
|
|
7
|
+
This document defines `worker-user-center-v1`, the enterprise management protocol used by `dsh-oidc` to bind an OIDC-authenticated subject to a model runtime API key. It is not part of OIDC.
|
|
8
|
+
|
|
9
|
+
It is a required part of the [server API specification](server-integration-contract.en.md). An implementation must also provide the OIDC Provider and model gateway required by that specification; implementing only the API below is not a complete `dsh-oidc` server.
|
|
10
|
+
|
|
11
|
+
The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **NOT RECOMMENDED**, **MAY**, and **OPTIONAL** are interpreted as described by RFC 2119 and RFC 8174.
|
|
12
|
+
|
|
13
|
+
[`protocol/openapi.yaml`](../protocol/openapi.yaml) is the machine-readable companion contract. If prose and OpenAPI differ, this document controls security and lifecycle semantics; OpenAPI controls field spelling and basic shapes. Such a difference is a specification defect and must be reported.
|
|
14
|
+
|
|
15
|
+
## 2. Design objectives
|
|
16
|
+
|
|
17
|
+
The protocol separates three credentials:
|
|
18
|
+
|
|
19
|
+
- the user's organization password, seen only by the OIDC Provider;
|
|
20
|
+
- an OIDC access token, used only against the UserInfo and Key Binding services;
|
|
21
|
+
- a model runtime API key, used only by the model Provider route.
|
|
22
|
+
|
|
23
|
+
The API key is a replaceable, revocable runtime credential—not proof of the user's identity. The Key Binding server is the authorization authority for provisioning and resolving it.
|
|
24
|
+
|
|
25
|
+
## 3. Base URL and fixed resources
|
|
26
|
+
|
|
27
|
+
An Enterprise Profile supplies one HTTPS `keyBinding.baseURL`. It MUST NOT contain userinfo, query, or fragment and SHOULD contain a versioned path such as `/api/worker/v1`.
|
|
28
|
+
|
|
29
|
+
The following paths are fixed relative to that base URL:
|
|
30
|
+
|
|
31
|
+
| Method | Path | Purpose |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| GET | `/bootstrap` | Read Provider, capabilities, entitlement, and credential state. |
|
|
34
|
+
| POST | `/runtime-credential/provision` | Create/bind a credential when state is `missing`. |
|
|
35
|
+
| POST | `/runtime-credential/resolve` | Return the active credential. |
|
|
36
|
+
| POST | `/runtime-credential/renew` | Rotate an `expiring` or `expired` credential. |
|
|
37
|
+
|
|
38
|
+
Profiles and users MUST NOT override paths, HTTP methods, field mappings, or credential-reference names.
|
|
39
|
+
|
|
40
|
+
## 4. Authentication and authorization
|
|
41
|
+
|
|
42
|
+
Every request MUST use TLS except explicit loopback development. The client sends:
|
|
43
|
+
|
|
44
|
+
```http
|
|
45
|
+
Authorization: Bearer <OIDC access token>
|
|
46
|
+
Accept: application/json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The server MUST validate at least:
|
|
50
|
+
|
|
51
|
+
- token signature or introspection result;
|
|
52
|
+
- issuer;
|
|
53
|
+
- audience intended for the Key Binding service;
|
|
54
|
+
- expiry/not-before;
|
|
55
|
+
- authenticated subject;
|
|
56
|
+
- required operation scope;
|
|
57
|
+
- organization membership, account state, and model entitlement.
|
|
58
|
+
|
|
59
|
+
Recommended scopes are:
|
|
60
|
+
|
|
61
|
+
| Operation | Scope |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| bootstrap | `worker.bootstrap.read` |
|
|
64
|
+
| provision | `worker.credential.provision` |
|
|
65
|
+
| resolve | `worker.credential.read` |
|
|
66
|
+
| renew | `worker.credential.renew` |
|
|
67
|
+
|
|
68
|
+
A deployment MAY use different scope names, but they remain OIDC authorization policy and are listed in the Enterprise Profile `oidc.scopes`; wire operations do not change.
|
|
69
|
+
|
|
70
|
+
The server MUST derive the bound subject from the validated access token. It MUST NOT accept a caller-supplied subject ID in a request body.
|
|
71
|
+
|
|
72
|
+
## 5. Bootstrap
|
|
73
|
+
|
|
74
|
+
Request:
|
|
75
|
+
|
|
76
|
+
```http
|
|
77
|
+
GET /api/worker/v1/bootstrap HTTP/1.1
|
|
78
|
+
Authorization: Bearer …
|
|
79
|
+
Accept: application/json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Minimum successful response:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"provider": { "id": "example-ai" },
|
|
87
|
+
"capabilities": ["worker.credential.provision"],
|
|
88
|
+
"runtime_credential": {
|
|
89
|
+
"status": "missing",
|
|
90
|
+
"provisioning": { "allowed": true }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The returned `provider.id` is REQUIRED and MUST exactly equal the Enterprise Profile `provider.id`. A mismatch is a binding failure.
|
|
96
|
+
|
|
97
|
+
Valid `runtime_credential.status` values are:
|
|
98
|
+
|
|
99
|
+
- `missing`: no credential is bound;
|
|
100
|
+
- `active`: an active credential can be resolved;
|
|
101
|
+
- `expiring`: rotate if `api_key_id` exists, otherwise resolve according to server policy;
|
|
102
|
+
- `expired`: rotate if `api_key_id` exists, otherwise resolve according to server policy;
|
|
103
|
+
- `suspended`: entitlement/account disabled; local credential must not be treated as usable;
|
|
104
|
+
- `unavailable`: service cannot currently provide a credential.
|
|
105
|
+
|
|
106
|
+
`subject` MAY carry informational management-plane display data. The client MUST NOT use it to replace OIDC `sub` or `name`.
|
|
107
|
+
|
|
108
|
+
`protocol_version`, if present, MUST be `worker-user-center/v1`. It is optional during v1 adoption; a future major protocol can require explicit negotiation.
|
|
109
|
+
|
|
110
|
+
## 6. Provision
|
|
111
|
+
|
|
112
|
+
The user MUST complete OIDC authentication before provisioning. `dsh-oidc` additionally requires an explicit UI action (`allowProvision: true`) so a callback alone does not silently create a model credential.
|
|
113
|
+
|
|
114
|
+
```http
|
|
115
|
+
POST /api/worker/v1/runtime-credential/provision HTTP/1.1
|
|
116
|
+
Authorization: Bearer …
|
|
117
|
+
Content-Type: application/json
|
|
118
|
+
Idempotency-Key: <UUID>
|
|
119
|
+
|
|
120
|
+
{ "provider_id": "example-ai" }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The server MUST:
|
|
124
|
+
|
|
125
|
+
- verify `provider_id` is authorized for the subject;
|
|
126
|
+
- enforce organization provisioning policy;
|
|
127
|
+
- make the operation idempotent for the same authenticated subject, Provider, and Idempotency-Key;
|
|
128
|
+
- return the same logical result for a replay during the server's idempotency retention window;
|
|
129
|
+
- reject reuse of a key with a different payload as `409 Conflict`;
|
|
130
|
+
- never log the returned API key.
|
|
131
|
+
|
|
132
|
+
## 7. Resolve
|
|
133
|
+
|
|
134
|
+
```http
|
|
135
|
+
POST /api/worker/v1/runtime-credential/resolve HTTP/1.1
|
|
136
|
+
Authorization: Bearer …
|
|
137
|
+
Content-Type: application/json
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
"provider_id": "example-ai",
|
|
141
|
+
"api_key_id": "optional-known-id"
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Resolve MUST NOT create a new entitlement. It returns the active credential already bound to the subject or a `404`/policy error. A server MAY rotate opaque secret material while preserving the same logical `api_key_id`, but it must not change Provider ownership.
|
|
146
|
+
|
|
147
|
+
## 8. Renew
|
|
148
|
+
|
|
149
|
+
```http
|
|
150
|
+
POST /api/worker/v1/runtime-credential/renew HTTP/1.1
|
|
151
|
+
Authorization: Bearer …
|
|
152
|
+
Content-Type: application/json
|
|
153
|
+
Idempotency-Key: <UUID>
|
|
154
|
+
|
|
155
|
+
{
|
|
156
|
+
"provider_id": "example-ai",
|
|
157
|
+
"api_key_id": "key-123"
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Renew requires `api_key_id`, is idempotent, and rotates or replaces an expiring/expired credential. The server SHOULD invalidate superseded secret material as soon as operationally safe and MUST document any overlap window.
|
|
162
|
+
|
|
163
|
+
## 9. Credential response
|
|
164
|
+
|
|
165
|
+
All three successful POST operations return:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"provider_id": "example-ai",
|
|
170
|
+
"api_key": "secret-runtime-value",
|
|
171
|
+
"api_key_id": "key-123",
|
|
172
|
+
"status": "active",
|
|
173
|
+
"expires_at": "2026-09-01T00:00:00Z"
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`provider_id`, `api_key`, and `status=active` are REQUIRED. The client refuses an absent/mismatched Provider or empty key.
|
|
178
|
+
|
|
179
|
+
Credential responses MUST include `Cache-Control: no-store` and SHOULD include `Pragma: no-cache`. Servers, proxies, APM agents, and WAFs MUST redact Authorization headers and `api_key` response fields. The secret MUST NOT be placed in URLs, error details, analytics events, or audit logs.
|
|
180
|
+
|
|
181
|
+
## 10. Errors
|
|
182
|
+
|
|
183
|
+
Errors SHOULD use `application/problem+json` following RFC 9457 with a stable extension field `code`. At minimum:
|
|
184
|
+
|
|
185
|
+
- `400`: malformed request/Provider ID;
|
|
186
|
+
- `401`: missing, invalid, expired, or wrong-audience token;
|
|
187
|
+
- `403`: scope, membership, entitlement, suspension, or provisioning policy denial;
|
|
188
|
+
- `404`: no resolvable/renewable credential;
|
|
189
|
+
- `409`: idempotency or lifecycle conflict;
|
|
190
|
+
- `429`: rate limit, preferably with `Retry-After`;
|
|
191
|
+
- `5xx`: server failure without secret material.
|
|
192
|
+
|
|
193
|
+
Clients MUST NOT retry provisioning/renewal without preserving the same Idempotency-Key for the same logical operation. `dsh-oidc` currently performs one call; DSH model request retries are separate and never repeat Key Binding operations.
|
|
194
|
+
|
|
195
|
+
## 11. Lifecycle and revocation
|
|
196
|
+
|
|
197
|
+
The Key Binding service SHOULD revoke model credentials when:
|
|
198
|
+
|
|
199
|
+
- organization membership or entitlement ends;
|
|
200
|
+
- the OIDC subject is disabled;
|
|
201
|
+
- a credential is rotated;
|
|
202
|
+
- a security administrator revokes it;
|
|
203
|
+
- its maximum lifetime expires.
|
|
204
|
+
|
|
205
|
+
Local logout removes the cached model key but does not by itself prove remote revocation. Deployments requiring immediate remote revocation SHOULD add an authenticated management operation in a future protocol version rather than overloading OIDC token revocation.
|
|
206
|
+
|
|
207
|
+
The model gateway MUST independently enforce API key status, Provider scope, expiration, and quota. Possession of an old key must not bypass the current enterprise policy indefinitely.
|
|
208
|
+
|
|
209
|
+
## 12. Privacy and audit
|
|
210
|
+
|
|
211
|
+
The server SHOULD minimize identity data in bootstrap. Audit records SHOULD contain event time, opaque subject reference, Provider ID, operation, outcome, policy reason, request correlation ID, and `api_key_id`—never the API key or OIDC token.
|
|
212
|
+
|
|
213
|
+
Retention, access control, breach response, and cross-border processing are deployment responsibilities. The protocol does not grant permission to collect additional personal data.
|
|
214
|
+
|
|
215
|
+
## 13. Conformance checklist
|
|
216
|
+
|
|
217
|
+
A server is conformant when it:
|
|
218
|
+
|
|
219
|
+
- implements all four fixed resources;
|
|
220
|
+
- authenticates requests with the OIDC access token and never request-body subject data;
|
|
221
|
+
- returns exact Provider binding;
|
|
222
|
+
- enforces required scopes and organization entitlement;
|
|
223
|
+
- implements provision/renew idempotency;
|
|
224
|
+
- returns no-store credential responses;
|
|
225
|
+
- never logs secrets;
|
|
226
|
+
- exposes stable lifecycle states and problem codes;
|
|
227
|
+
- passes the repository's OpenAPI contract tests plus organization-specific authorization tests.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Worker User Center Key Binding Protocol v1
|
|
2
|
+
|
|
3
|
+
**简体中文** | [English](key-binding-protocol.en.md)
|
|
4
|
+
|
|
5
|
+
## 1. 状态与术语
|
|
6
|
+
|
|
7
|
+
本文定义 `worker-user-center-v1`:`dsh-oidc` 用它把经 OIDC 认证的 subject 与模型运行时 API Key 绑定。这是企业管理协议,不属于 OIDC 标准。
|
|
8
|
+
|
|
9
|
+
它是[服务端接口规范](server-integration-contract.md)的必需组成部分。实现方必须同时提供该规范要求的 OIDC Provider 与模型网关;只实现以下 API 不构成完整的 `dsh-oidc` 服务端。
|
|
10
|
+
|
|
11
|
+
关键词 **必须(MUST)**、**不得(MUST NOT)**、**必需(REQUIRED)**、**应该(SHOULD)**、**不应该(SHOULD NOT)**、**建议(RECOMMENDED)**、**不建议(NOT RECOMMENDED)**、**可以(MAY)** 和 **可选(OPTIONAL)** 按 RFC 2119 与 RFC 8174 解释。
|
|
12
|
+
|
|
13
|
+
[`protocol/openapi.yaml`](../protocol/openapi.yaml) 是配套的机器可读契约。若正文与 OpenAPI 不一致,本文决定安全和生命周期语义,OpenAPI 决定字段拼写和基本结构。任何差异都属于规范缺陷,必须报告。
|
|
14
|
+
|
|
15
|
+
## 2. 设计目标
|
|
16
|
+
|
|
17
|
+
协议区分三类凭据:
|
|
18
|
+
|
|
19
|
+
- 机构密码:只由 OIDC Provider 看见;
|
|
20
|
+
- OIDC access token:只用于 UserInfo 和 Key Binding 服务;
|
|
21
|
+
- 模型运行时 API Key:只用于模型 Provider 路由。
|
|
22
|
+
|
|
23
|
+
API Key 是可替换、可撤销的运行凭据,不是用户身份证明。Key Binding 服务端是决定其创建和解析权限的授权权威。
|
|
24
|
+
|
|
25
|
+
## 3. Base URL 与固定资源
|
|
26
|
+
|
|
27
|
+
Enterprise Profile 提供一个 HTTPS `keyBinding.baseURL`。其中不得包含 userinfo、query 或 fragment,并且应该包含类似 `/api/worker/v1` 的版本路径。
|
|
28
|
+
|
|
29
|
+
相对于该 base URL,以下路径固定不变:
|
|
30
|
+
|
|
31
|
+
| 方法 | 路径 | 用途 |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| GET | `/bootstrap` | 读取 Provider、能力、授权资格和凭据状态。 |
|
|
34
|
+
| POST | `/runtime-credential/provision` | 状态为 `missing` 时创建或绑定凭据。 |
|
|
35
|
+
| POST | `/runtime-credential/resolve` | 返回活动凭据。 |
|
|
36
|
+
| POST | `/runtime-credential/renew` | 轮换 `expiring` 或 `expired` 凭据。 |
|
|
37
|
+
|
|
38
|
+
Profile 和用户不得覆盖路径、HTTP 方法、字段映射或凭据引用名。
|
|
39
|
+
|
|
40
|
+
## 4. 认证与授权
|
|
41
|
+
|
|
42
|
+
除显式 loopback 开发环境外,每个请求都必须使用 TLS。客户端发送:
|
|
43
|
+
|
|
44
|
+
```http
|
|
45
|
+
Authorization: Bearer <OIDC access token>
|
|
46
|
+
Accept: application/json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
服务端至少必须验证:
|
|
50
|
+
|
|
51
|
+
- token 签名或 introspection 结果;
|
|
52
|
+
- issuer;
|
|
53
|
+
- 面向 Key Binding 服务的 audience;
|
|
54
|
+
- 过期时间和 not-before;
|
|
55
|
+
- 已认证 subject;
|
|
56
|
+
- 操作所需 scope;
|
|
57
|
+
- 机构成员资格、账号状态和模型授权。
|
|
58
|
+
|
|
59
|
+
建议 scope:
|
|
60
|
+
|
|
61
|
+
| 操作 | Scope |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| bootstrap | `worker.bootstrap.read` |
|
|
64
|
+
| provision | `worker.credential.provision` |
|
|
65
|
+
| resolve | `worker.credential.read` |
|
|
66
|
+
| renew | `worker.credential.renew` |
|
|
67
|
+
|
|
68
|
+
部署可以使用不同 scope 名称,但这些仍属于 OIDC 授权策略,并列在 Enterprise Profile `oidc.scopes` 中;网络操作本身不变。
|
|
69
|
+
|
|
70
|
+
服务端必须从已验证 access token 派生绑定 subject,不得接受请求 body 中由调用方提供的 subject ID。
|
|
71
|
+
|
|
72
|
+
## 5. Bootstrap
|
|
73
|
+
|
|
74
|
+
请求:
|
|
75
|
+
|
|
76
|
+
```http
|
|
77
|
+
GET /api/worker/v1/bootstrap HTTP/1.1
|
|
78
|
+
Authorization: Bearer …
|
|
79
|
+
Accept: application/json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
最小成功响应:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"provider": { "id": "example-ai" },
|
|
87
|
+
"capabilities": ["worker.credential.provision"],
|
|
88
|
+
"runtime_credential": {
|
|
89
|
+
"status": "missing",
|
|
90
|
+
"provisioning": { "allowed": true }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
返回的 `provider.id` 是必需字段,必须与 Enterprise Profile `provider.id` 完全相等。不一致即视为绑定失败。
|
|
96
|
+
|
|
97
|
+
有效的 `runtime_credential.status`:
|
|
98
|
+
|
|
99
|
+
- `missing`:尚未绑定凭据;
|
|
100
|
+
- `active`:可以解析活动凭据;
|
|
101
|
+
- `expiring`:存在 `api_key_id` 时轮换,否则按服务端策略解析;
|
|
102
|
+
- `expired`:存在 `api_key_id` 时轮换,否则按服务端策略解析;
|
|
103
|
+
- `suspended`:授权或账号已停用;本地凭据不得继续视为可用;
|
|
104
|
+
- `unavailable`:服务当前无法提供凭据。
|
|
105
|
+
|
|
106
|
+
`subject` 可以携带仅供管理面显示的信息。客户端不得用它替换 OIDC `sub` 或 `name`。
|
|
107
|
+
|
|
108
|
+
若返回 `protocol_version`,其值必须是 `worker-user-center/v1`。该字段在 v1 采用阶段可选;未来 major 协议可以要求显式协商。
|
|
109
|
+
|
|
110
|
+
## 6. Provision
|
|
111
|
+
|
|
112
|
+
用户必须先完成 OIDC 认证。`dsh-oidc` 还要求显式 UI 操作(`allowProvision: true`),因此仅完成 callback 不会静默创建模型凭据。
|
|
113
|
+
|
|
114
|
+
```http
|
|
115
|
+
POST /api/worker/v1/runtime-credential/provision HTTP/1.1
|
|
116
|
+
Authorization: Bearer …
|
|
117
|
+
Content-Type: application/json
|
|
118
|
+
Idempotency-Key: <UUID>
|
|
119
|
+
|
|
120
|
+
{ "provider_id": "example-ai" }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
服务端必须:
|
|
124
|
+
|
|
125
|
+
- 验证该 subject 获准使用 `provider_id`;
|
|
126
|
+
- 执行机构凭据创建策略;
|
|
127
|
+
- 对相同已认证 subject、Provider 和 Idempotency-Key 保证幂等;
|
|
128
|
+
- 在服务端幂等保留窗口内,对重放请求返回相同逻辑结果;
|
|
129
|
+
- 同一 key 与不同 payload 重用时返回 `409 Conflict`;
|
|
130
|
+
- 绝不记录返回的 API Key。
|
|
131
|
+
|
|
132
|
+
## 7. Resolve
|
|
133
|
+
|
|
134
|
+
```http
|
|
135
|
+
POST /api/worker/v1/runtime-credential/resolve HTTP/1.1
|
|
136
|
+
Authorization: Bearer …
|
|
137
|
+
Content-Type: application/json
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
"provider_id": "example-ai",
|
|
141
|
+
"api_key_id": "optional-known-id"
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Resolve 不得创建新的授权资格。它返回已绑定到 subject 的活动凭据,或者返回 `404`/策略错误。服务端可以在保持同一逻辑 `api_key_id` 的情况下轮换不透明秘密材料,但不得改变 Provider 所有权。
|
|
146
|
+
|
|
147
|
+
## 8. Renew
|
|
148
|
+
|
|
149
|
+
```http
|
|
150
|
+
POST /api/worker/v1/runtime-credential/renew HTTP/1.1
|
|
151
|
+
Authorization: Bearer …
|
|
152
|
+
Content-Type: application/json
|
|
153
|
+
Idempotency-Key: <UUID>
|
|
154
|
+
|
|
155
|
+
{
|
|
156
|
+
"provider_id": "example-ai",
|
|
157
|
+
"api_key_id": "key-123"
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Renew 要求提供 `api_key_id`,必须幂等,并轮换或替换即将过期/已过期凭据。服务端应该在操作安全允许后尽快使旧秘密材料失效,并且必须记录任何重叠有效窗口。
|
|
162
|
+
|
|
163
|
+
## 9. 凭据响应
|
|
164
|
+
|
|
165
|
+
三个 POST 操作成功时都返回:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"provider_id": "example-ai",
|
|
170
|
+
"api_key": "secret-runtime-value",
|
|
171
|
+
"api_key_id": "key-123",
|
|
172
|
+
"status": "active",
|
|
173
|
+
"expires_at": "2026-09-01T00:00:00Z"
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`provider_id`、`api_key` 和 `status=active` 是必需字段。Provider 缺失、不匹配或 key 为空时,客户端会拒绝响应。
|
|
178
|
+
|
|
179
|
+
凭据响应必须包含 `Cache-Control: no-store`,并且应该包含 `Pragma: no-cache`。服务端、代理、APM agent 和 WAF 必须脱敏 Authorization header 与 `api_key` 响应字段。不得把秘密写入 URL、错误详情、分析事件或审计日志。
|
|
180
|
+
|
|
181
|
+
## 10. 错误
|
|
182
|
+
|
|
183
|
+
错误应该遵循 RFC 9457,使用 `application/problem+json`,并提供稳定扩展字段 `code`。至少包含:
|
|
184
|
+
|
|
185
|
+
- `400`:请求或 Provider ID 格式错误;
|
|
186
|
+
- `401`:token 缺失、无效、过期或 audience 错误;
|
|
187
|
+
- `403`:scope、成员资格、授权、停用或创建策略拒绝;
|
|
188
|
+
- `404`:没有可解析或可更新的凭据;
|
|
189
|
+
- `409`:幂等或生命周期冲突;
|
|
190
|
+
- `429`:限流,最好包含 `Retry-After`;
|
|
191
|
+
- `5xx`:不包含秘密材料的服务端失败。
|
|
192
|
+
|
|
193
|
+
同一逻辑操作重试 provision/renew 时,客户端必须保留相同 Idempotency-Key。`dsh-oidc` 当前只发起一次调用;DSH 模型请求重试属于另一条链路,绝不会重复 Key Binding 操作。
|
|
194
|
+
|
|
195
|
+
## 11. 生命周期与撤销
|
|
196
|
+
|
|
197
|
+
以下情况发生时,Key Binding 服务应该撤销模型凭据:
|
|
198
|
+
|
|
199
|
+
- 机构成员资格或授权终止;
|
|
200
|
+
- OIDC subject 被停用;
|
|
201
|
+
- 凭据被轮换;
|
|
202
|
+
- 安全管理员撤销凭据;
|
|
203
|
+
- 达到最大生命周期。
|
|
204
|
+
|
|
205
|
+
本地退出会删除缓存模型 Key,但不能单独证明远程凭据已撤销。要求即时远程撤销的部署,应该在未来协议版本增加经过认证的管理操作,而不是复用 OIDC token revocation。
|
|
206
|
+
|
|
207
|
+
模型网关必须独立执行 API Key 状态、Provider scope、过期和配额策略。持有旧 Key 不得无限期绕过当前企业策略。
|
|
208
|
+
|
|
209
|
+
## 12. 隐私与审计
|
|
210
|
+
|
|
211
|
+
服务端应该尽量减少 bootstrap 返回的身份数据。审计记录应该包含事件时间、不透明 subject 引用、Provider ID、操作、结果、策略原因、请求关联 ID 和 `api_key_id`,绝不能包含 API Key 或 OIDC token。
|
|
212
|
+
|
|
213
|
+
数据保留、访问控制、泄露响应和跨境处理由部署方负责。该协议不授予收集更多个人数据的权限。
|
|
214
|
+
|
|
215
|
+
## 13. 符合性检查
|
|
216
|
+
|
|
217
|
+
服务端满足以下条件时符合本规范:
|
|
218
|
+
|
|
219
|
+
- 实现全部四个固定资源;
|
|
220
|
+
- 使用 OIDC access token 认证请求,绝不使用请求 body 中的 subject 数据;
|
|
221
|
+
- 返回完全匹配的 Provider 绑定;
|
|
222
|
+
- 执行所需 scope 和机构授权;
|
|
223
|
+
- 实现 provision/renew 幂等;
|
|
224
|
+
- 凭据响应使用 no-store;
|
|
225
|
+
- 不记录秘密;
|
|
226
|
+
- 提供稳定生命周期状态和问题代码;
|
|
227
|
+
- 通过仓库 OpenAPI 契约测试和机构特有授权测试。
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# OIDC interoperability profile
|
|
2
|
+
|
|
3
|
+
[简体中文](oidc-interoperability.md) | **English**
|
|
4
|
+
|
|
5
|
+
OIDC is one part of the [server API specification](server-integration-contract.en.md). A conforming integration also requires the same accountable service owner to jointly deliver fixed Key Binding and the model gateway; implementing this document alone is not complete.
|
|
6
|
+
|
|
7
|
+
## Standards
|
|
8
|
+
|
|
9
|
+
The Web backend is a public OpenID Connect Relying Party based on:
|
|
10
|
+
|
|
11
|
+
- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
|
|
12
|
+
- [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html)
|
|
13
|
+
- [OAuth 2.0 Authorization Framework (RFC 6749)](https://www.rfc-editor.org/rfc/rfc6749)
|
|
14
|
+
- [PKCE (RFC 7636)](https://www.rfc-editor.org/rfc/rfc7636)
|
|
15
|
+
- [OAuth 2.0 for Native Apps (RFC 8252)](https://www.rfc-editor.org/rfc/rfc8252)
|
|
16
|
+
- [OAuth 2.0 Token Revocation (RFC 7009)](https://www.rfc-editor.org/rfc/rfc7009)
|
|
17
|
+
- [OAuth 2.0 Authorization Server Issuer Identification (RFC 9207)](https://www.rfc-editor.org/rfc/rfc9207)
|
|
18
|
+
|
|
19
|
+
This document states the narrower interoperability choices made by `dsh-oidc`.
|
|
20
|
+
|
|
21
|
+
## Required Provider behavior
|
|
22
|
+
|
|
23
|
+
The Provider MUST:
|
|
24
|
+
|
|
25
|
+
1. publish Discovery at `{issuer-without-trailing-slash}/.well-known/openid-configuration`;
|
|
26
|
+
2. return an `issuer` exactly equal to the configured Issuer Identifier;
|
|
27
|
+
3. publish `authorization_endpoint`, `token_endpoint`, `jwks_uri`, and `userinfo_endpoint`;
|
|
28
|
+
4. advertise `S256` in `code_challenge_methods_supported`;
|
|
29
|
+
5. issue RS256 ID Tokens with a non-empty `kid` and a unique matching RSA signing JWK;
|
|
30
|
+
6. support Authorization Code flow for a public client without a client secret;
|
|
31
|
+
7. return Bearer access tokens and an ID Token from the token endpoint;
|
|
32
|
+
8. return JSON UserInfo containing non-empty `sub` equal to the ID Token `sub`.
|
|
33
|
+
|
|
34
|
+
Discovery endpoints MAY use HTTPS origins different from the issuer, as the standard allows. Network HTTP is rejected. Loopback HTTP is accepted only when the profile explicitly enables development mode and both issuer/endpoint hosts are loopback.
|
|
35
|
+
|
|
36
|
+
The current implementation supports RS256 only. Providers using ES256, PS256, encrypted ID Tokens, signed UserInfo JWTs, PAR, JAR, DPoP, or mTLS are not yet interoperable.
|
|
37
|
+
|
|
38
|
+
## Authorization request
|
|
39
|
+
|
|
40
|
+
The plugin generates cryptographically random:
|
|
41
|
+
|
|
42
|
+
- 256-bit `state`;
|
|
43
|
+
- 256-bit `nonce`;
|
|
44
|
+
- 384-bit PKCE verifier and S256 challenge.
|
|
45
|
+
|
|
46
|
+
Pending flows expire after ten minutes and are limited to 32 per plugin process. Existing query parameters on a discovered authorization endpoint are preserved; protocol parameters are set by the plugin.
|
|
47
|
+
|
|
48
|
+
The redirect URI is always `http://127.0.0.1:<DSH-port>/oauth/callback`. Host and path are fixed, the port follows the DSH WebServer's actual listening port, and no request or proxy header is used to infer it.
|
|
49
|
+
|
|
50
|
+
## Callback validation
|
|
51
|
+
|
|
52
|
+
The callback:
|
|
53
|
+
|
|
54
|
+
- accepts only the fixed path;
|
|
55
|
+
- requires exactly one non-empty `state` and `code` for a successful response;
|
|
56
|
+
- rejects duplicate `state`, `code`, `error`, or `iss` parameters;
|
|
57
|
+
- consumes a pending state once, even on failure;
|
|
58
|
+
- checks RFC 9207 `iss` when present;
|
|
59
|
+
- exchanges the code with the original PKCE verifier.
|
|
60
|
+
|
|
61
|
+
The post-callback `returnPath` must be a same-origin absolute path, preventing an open redirect.
|
|
62
|
+
|
|
63
|
+
## ID Token validation
|
|
64
|
+
|
|
65
|
+
The implementation verifies:
|
|
66
|
+
|
|
67
|
+
- three-part JWS structure;
|
|
68
|
+
- protected `alg=RS256` and non-empty `kid`;
|
|
69
|
+
- exactly one RSA JWK matching `kid`, optional `use=sig`, and optional `alg=RS256`;
|
|
70
|
+
- signature;
|
|
71
|
+
- exact `iss`;
|
|
72
|
+
- `aud` containing the client ID;
|
|
73
|
+
- `azp` equal to the client ID when `aud` contains multiple values;
|
|
74
|
+
- non-empty `sub`;
|
|
75
|
+
- exact nonce;
|
|
76
|
+
- finite required `iat` and `exp`, optional `nbf`, with 60 seconds of clock skew;
|
|
77
|
+
- `at_hash` when the claim is present.
|
|
78
|
+
|
|
79
|
+
TLS trust and DNS resolution remain responsibilities of the Node.js host and operating system.
|
|
80
|
+
|
|
81
|
+
## UserInfo and display identity
|
|
82
|
+
|
|
83
|
+
`userinfo_endpoint` is required by this interoperability profile even though its advertisement is not mandatory in every generic OIDC deployment.
|
|
84
|
+
|
|
85
|
+
The response MUST contain `sub` and it MUST match the verified ID Token `sub`. The account display label is:
|
|
86
|
+
|
|
87
|
+
1. trimmed standard UserInfo `name`, if non-empty;
|
|
88
|
+
2. otherwise trimmed UserInfo `sub`.
|
|
89
|
+
|
|
90
|
+
The plugin intentionally has no JSON path mapping or private profile endpoint. Organizations SHOULD fix their OIDC UserInfo response to supply the standard `name` claim when a human-readable label is desired.
|
|
91
|
+
|
|
92
|
+
`affiliation` is currently accepted as an optional display extension; it does not affect authorization. A future stable profile may replace it with a namespaced claim or remove it.
|
|
93
|
+
|
|
94
|
+
## Refresh and logout
|
|
95
|
+
|
|
96
|
+
If the access token is within 90 seconds of expiry and a refresh token exists, the plugin refreshes it. On `invalid_grant`, the stored OIDC session and local model credential are removed and login is required. A rotated refresh token replaces the previous one. An expired session without a refresh token is handled the same way when an active session is next required.
|
|
97
|
+
|
|
98
|
+
Logout attempts RFC 7009 revocation when Discovery publishes `revocation_endpoint`, then removes both the OIDC session record and the derived model credential locally. Remote revocation failure is logged without blocking local cleanup.
|
|
99
|
+
|
|
100
|
+
The plugin does not implement RP-Initiated Logout or front/back-channel logout in this version.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# OIDC 互操作规范
|
|
2
|
+
|
|
3
|
+
**简体中文** | [English](oidc-interoperability.en.md)
|
|
4
|
+
|
|
5
|
+
OIDC 是[服务端接口规范](server-integration-contract.md)的一部分。合格的机构接入还必须由同一服务所有者联合提供固定 Key Binding 和模型网关能力;只实现本文件并不构成完整接入。
|
|
6
|
+
|
|
7
|
+
## 标准依据
|
|
8
|
+
|
|
9
|
+
Web 后端是基于以下标准的 public OpenID Connect Relying Party:
|
|
10
|
+
|
|
11
|
+
- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
|
|
12
|
+
- [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html)
|
|
13
|
+
- [OAuth 2.0 Authorization Framework(RFC 6749)](https://www.rfc-editor.org/rfc/rfc6749)
|
|
14
|
+
- [PKCE(RFC 7636)](https://www.rfc-editor.org/rfc/rfc7636)
|
|
15
|
+
- [OAuth 2.0 for Native Apps(RFC 8252)](https://www.rfc-editor.org/rfc/rfc8252)
|
|
16
|
+
- [OAuth 2.0 Token Revocation(RFC 7009)](https://www.rfc-editor.org/rfc/rfc7009)
|
|
17
|
+
- [OAuth 2.0 Authorization Server Issuer Identification(RFC 9207)](https://www.rfc-editor.org/rfc/rfc9207)
|
|
18
|
+
|
|
19
|
+
本文说明 `dsh-oidc` 在这些标准范围内采用的更严格互操作选择。
|
|
20
|
+
|
|
21
|
+
## Provider 必需行为
|
|
22
|
+
|
|
23
|
+
Provider 必须:
|
|
24
|
+
|
|
25
|
+
1. 在 `{去掉末尾斜杠的 issuer}/.well-known/openid-configuration` 发布 Discovery;
|
|
26
|
+
2. 返回与配置的 Issuer Identifier 完全一致的 `issuer`;
|
|
27
|
+
3. 发布 `authorization_endpoint`、`token_endpoint`、`jwks_uri` 和 `userinfo_endpoint`;
|
|
28
|
+
4. 在 `code_challenge_methods_supported` 中声明 `S256`;
|
|
29
|
+
5. 签发带非空 `kid` 的 RS256 ID Token,并提供唯一匹配的 RSA 签名 JWK;
|
|
30
|
+
6. 支持无 client secret 的 public client Authorization Code flow;
|
|
31
|
+
7. 从 token endpoint 返回 Bearer access token 和 ID Token;
|
|
32
|
+
8. 返回 JSON UserInfo,其中非空 `sub` 必须与 ID Token `sub` 相同。
|
|
33
|
+
|
|
34
|
+
标准允许 Discovery endpoint 使用与 issuer 不同的 HTTPS origin。插件拒绝网络 HTTP;只有 Profile 显式启用开发模式,且 issuer/endpoint 主机都是 loopback 时才接受 HTTP。
|
|
35
|
+
|
|
36
|
+
当前实现只支持 RS256。使用 ES256、PS256、加密 ID Token、签名 UserInfo JWT、PAR、JAR、DPoP 或 mTLS 的 Provider 暂不兼容。
|
|
37
|
+
|
|
38
|
+
## Authorization 请求
|
|
39
|
+
|
|
40
|
+
插件生成密码学安全随机值:
|
|
41
|
+
|
|
42
|
+
- 256-bit `state`;
|
|
43
|
+
- 256-bit `nonce`;
|
|
44
|
+
- 384-bit PKCE verifier 和 S256 challenge。
|
|
45
|
+
|
|
46
|
+
待完成流程十分钟后过期,每个插件进程最多保存 32 个。Discovery 返回的 authorization endpoint 上已有 query 参数会被保留,协议参数则由插件设置。
|
|
47
|
+
|
|
48
|
+
Redirect URI 始终为 `http://127.0.0.1:<DSH端口>/oauth/callback`。host 和 path 固定,端口取 DSH WebServer 的实际监听端口;不从请求头或代理配置推断。
|
|
49
|
+
|
|
50
|
+
## Callback 校验
|
|
51
|
+
|
|
52
|
+
Callback:
|
|
53
|
+
|
|
54
|
+
- 只接受固定路径;
|
|
55
|
+
- 成功响应必须恰好包含一个非空 `state` 和一个非空 `code`;
|
|
56
|
+
- 拒绝重复的 `state`、`code`、`error` 或 `iss` 参数;
|
|
57
|
+
- 即使失败,也只允许一个待处理 state 被消费一次;
|
|
58
|
+
- 如果存在 RFC 9207 `iss`,则对其进行校验;
|
|
59
|
+
- 使用原始 PKCE verifier 交换 code。
|
|
60
|
+
|
|
61
|
+
Callback 后的 `returnPath` 必须是同源绝对路径,以防止开放重定向。
|
|
62
|
+
|
|
63
|
+
## ID Token 校验
|
|
64
|
+
|
|
65
|
+
实现会校验:
|
|
66
|
+
|
|
67
|
+
- 三段式 JWS 结构;
|
|
68
|
+
- protected header 中 `alg=RS256` 且 `kid` 非空;
|
|
69
|
+
- 恰好一个 RSA JWK 与 `kid` 匹配,可选 `use=sig` 和 `alg=RS256`;
|
|
70
|
+
- 签名;
|
|
71
|
+
- `iss` 完全一致;
|
|
72
|
+
- `aud` 包含 client ID;
|
|
73
|
+
- 当 `aud` 有多个值时,`azp` 等于 client ID;
|
|
74
|
+
- 非空 `sub`;
|
|
75
|
+
- nonce 完全一致;
|
|
76
|
+
- 必需且有限的 `iat`、`exp`,以及可选 `nbf`,允许 60 秒时钟偏差;
|
|
77
|
+
- 存在 `at_hash` 时校验它。
|
|
78
|
+
|
|
79
|
+
TLS 信任和 DNS 解析仍由 Node.js 宿主和操作系统负责。
|
|
80
|
+
|
|
81
|
+
## UserInfo 与显示身份
|
|
82
|
+
|
|
83
|
+
虽然并非所有通用 OIDC 部署都强制发布 `userinfo_endpoint`,本互操作规范要求它存在。
|
|
84
|
+
|
|
85
|
+
响应必须包含 `sub`,且必须与已验证 ID Token 的 `sub` 匹配。账号显示名称按以下顺序确定:
|
|
86
|
+
|
|
87
|
+
1. 非空且去除首尾空白的标准 UserInfo `name`;
|
|
88
|
+
2. 否则使用去除首尾空白的 UserInfo `sub`。
|
|
89
|
+
|
|
90
|
+
插件有意不提供 JSON path 映射或私有人员信息 endpoint。希望显示人类可读姓名的机构,应该修正 OIDC UserInfo 响应并提供标准 `name` claim。
|
|
91
|
+
|
|
92
|
+
当前接受 `affiliation` 作为可选显示扩展,但它不影响授权。未来稳定规范可能将其替换为带命名空间的 claim,或直接移除。
|
|
93
|
+
|
|
94
|
+
## 刷新与退出
|
|
95
|
+
|
|
96
|
+
当 access token 距离过期不足 90 秒且存在 refresh token 时,插件会刷新会话。若收到 `invalid_grant`,会删除已存 OIDC 会话和本地模型凭据,并要求重新登录。轮换后的 refresh token 会替换旧值。没有 refresh token 的过期会话在下次需要活动会话时按同样方式处理。
|
|
97
|
+
|
|
98
|
+
退出登录时,如果 Discovery 发布 `revocation_endpoint`,插件会尝试按 RFC 7009 撤销 token;随后在本地删除 OIDC 会话记录和派生模型凭据。远程撤销失败会被记录,但不阻止本地清理。
|
|
99
|
+
|
|
100
|
+
当前版本不实现 RP-Initiated Logout、front-channel logout 或 back-channel logout。
|