@eduwork/dsh-oidc 0.2.3 → 0.3.0-dev.1

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.
Files changed (84) hide show
  1. package/CHANGELOG.en.md +6 -38
  2. package/CHANGELOG.md +7 -39
  3. package/CODE_OF_CONDUCT.en.md +1 -1
  4. package/CODE_OF_CONDUCT.md +1 -1
  5. package/CONTRIBUTING.en.md +1 -1
  6. package/CONTRIBUTING.md +1 -1
  7. package/README.md +21 -130
  8. package/README_EN.md +36 -0
  9. package/SECURITY.en.md +1 -1
  10. package/SECURITY.md +1 -1
  11. package/SUPPORT.en.md +1 -1
  12. package/SUPPORT.md +1 -1
  13. package/docs/EDUWORK-MIGRATION.en.md +3 -31
  14. package/docs/EDUWORK-MIGRATION.md +3 -31
  15. package/docs/account-extensions.en.md +6 -6
  16. package/docs/account-extensions.md +3 -3
  17. package/docs/architecture.en.md +12 -117
  18. package/docs/architecture.md +12 -118
  19. package/docs/compatibility.en.md +6 -62
  20. package/docs/compatibility.md +6 -62
  21. package/docs/decisions/0001-one-plugin-composition-boundary.en.md +2 -2
  22. package/docs/decisions/0001-one-plugin-composition-boundary.md +2 -2
  23. package/docs/desktop-host.en.md +54 -55
  24. package/docs/desktop-host.md +63 -64
  25. package/docs/development.en.md +44 -44
  26. package/docs/development.md +6 -6
  27. package/docs/dsh-integration.en.md +13 -48
  28. package/docs/dsh-integration.md +14 -49
  29. package/docs/ecnu-reference.en.md +4 -61
  30. package/docs/ecnu-reference.md +4 -61
  31. package/docs/enterprise-profile.en.md +10 -31
  32. package/docs/enterprise-profile.md +11 -32
  33. package/docs/gateway-auth/README.md +168 -0
  34. package/docs/gateway-auth/README_EN.md +70 -0
  35. package/docs/gateway-auth/experimental-oidc-llm.en.md +53 -0
  36. package/docs/gateway-auth/experimental-oidc-llm.md +53 -0
  37. package/docs/gateway-auth/litellm-setup.en.md +114 -0
  38. package/docs/gateway-auth/litellm-setup.md +114 -0
  39. package/docs/gateway-auth/oidc-llm-draft.en.md +141 -0
  40. package/docs/gateway-auth/oidc-llm-draft.md +141 -0
  41. package/docs/getting-started.en.md +17 -122
  42. package/docs/getting-started.md +16 -225
  43. package/docs/key-binding-protocol.en.md +10 -220
  44. package/docs/key-binding-protocol.md +10 -220
  45. package/docs/oidc-interoperability.en.md +1 -1
  46. package/docs/oidc-interoperability.md +1 -1
  47. package/docs/open-integration.en.md +11 -0
  48. package/docs/open-integration.md +11 -0
  49. package/docs/public-resource-protocol.en.md +8 -44
  50. package/docs/public-resource-protocol.md +8 -43
  51. package/docs/release-checklist.en.md +7 -61
  52. package/docs/release-checklist.md +7 -61
  53. package/docs/security-model.en.md +10 -132
  54. package/docs/security-model.md +10 -132
  55. package/docs/server-integration-contract.en.md +12 -811
  56. package/docs/server-integration-contract.md +12 -823
  57. package/examples/ecnu.enterprise-profile.example.json +14 -19
  58. package/examples/enterprise-profile.example.json +73 -78
  59. package/examples/identity-only.example.json +1 -1
  60. package/examples/litellm.enterprise-profile.example.json +9 -0
  61. package/examples/oidc-llm.enterprise-profile.example.json +12 -0
  62. package/examples/resources-discovery.example.json +6 -9
  63. package/lib/callback-page.js +5 -5
  64. package/lib/client.js +31 -48
  65. package/lib/desktop-oidc.js +7 -11
  66. package/lib/gateway-backend.js +443 -0
  67. package/lib/gateway-protocol.js +6 -0
  68. package/lib/index.js +14 -15
  69. package/lib/litellm-protocol.js +117 -0
  70. package/lib/model-resource-transport.js +19 -0
  71. package/lib/oidc-llm-protocol.js +88 -0
  72. package/lib/oidc.js +37 -323
  73. package/lib/profile.js +34 -40
  74. package/lib/provider/authorization-scope.js +46 -0
  75. package/lib/provider/index.js +11 -2
  76. package/lib/provider/transforms.js +0 -1
  77. package/lib/typert-schemas.js +1 -1
  78. package/lib/typert.host.js +38 -38
  79. package/lib/typert.remote-client.js +37 -37
  80. package/package.json +7 -6
  81. package/protocol/openapi.yaml +33 -240
  82. package/protocol/resources.openapi.yaml +8 -9
  83. package/schema/enterprise-profile.v1alpha1.schema.json +595 -193
  84. package/README.en.md +0 -186
@@ -1,66 +1,9 @@
1
- # 华东师范大学参考组合
1
+ # 机构接入参考
2
2
 
3
3
  **简体中文** | [English](ecnu-reference.en.md)
4
4
 
5
- 本仓库仅以华东师范大学 / ChatECNU 作为通用标准的一项具体参考实现。公开示例全部使用占位符,不包含生产 endpoint、OIDC Client ID、API Key、Token、个人数据或 Logo 资产。
5
+ 公共包不包含真实机构地址、客户端注册信息、账号或配额数据。部署方可使用[OIDC Token 示例](../examples/oidc-llm.enterprise-profile.example.json)或[LiteLLM 示例](../examples/litellm.enterprise-profile.example.json)。
6
6
 
7
- 参考文件为 [`examples/ecnu.enterprise-profile.example.json`](../examples/ecnu.enterprise-profile.example.json)。
7
+ 本人配额沿用机构服务端既有规则。可选机构扩展通过共享 Host 的 modelResourceFetch 发送当前 Access Token,并复用自己的解析器;公共包不定义配额对象或 RPC。
8
8
 
9
- ## 预期组合
10
-
11
- EduWork@ECNU 使用公共核心加机构扩展的组合:
12
-
13
- ```text
14
- DeepSeek Harness
15
- + dsh-oidc
16
- - 标准 OIDC 身份
17
- - worker-user-center-v1 Key Binding
18
- - ECNU Enterprise Profile
19
- - 声明式 ChatECNU Provider/模型
20
- - 有边界的品牌/账号 UI 和共用企业模型设置 UI
21
- + ECNU 产品插件
22
- - 面向纯文本 ecnu-max 的图像理解回退
23
- - 企业搜索通路和 DSH 官方结果渲染
24
- - 产品预设、Skill 和资源
25
- - 仅旧版需要的 native 账号桥(新桌面使用公共 desktop 后端)
26
- - 配额或机构特有业务 UI
27
- + 桌面外壳、打包与更新器
28
- ```
29
-
30
- 新桌面组合使用公共 `desktop` 后端,通过宿主浏览器服务完成临时 loopback 登录;本机功能验证用 `web` 后端。旧 Wails 发行版的 `native`/`enterpriseAccounts` 桥属于迁移兼容路径,不是新机构接入要求。企业配置由可信文件管理,界面负责展示和登录。配额、心跳、校内搜索由明确安装的机构插件提供;公版不请求这些服务。所有模式共用 `EDUWORK_API_KEY` 规则和完整部署绑定校验,不改变现有 OIDC/Key Binding wire。
31
-
32
- ## 身份预期
33
-
34
- OIDC Provider 应返回标准 UserInfo:
35
-
36
- ```json
37
- {
38
- "sub": "opaque-stable-subject",
39
- "name": "Display Name"
40
- }
41
- ```
42
-
43
- 如果没有 `name`,UI 会降级显示 `sub`。插件不会调用 ECNU 私有人员信息接口,也不会映射 `data.attributes.XM` 等路径。这是有意设计:机构 OIDC 服务应提供标准 claim。
44
-
45
- ## 模型示例
46
-
47
- 占位示例展示了两类常见模型事实:
48
-
49
- - `ecnu-max`:纯文本、大上下文、支持选择 reasoning effort;
50
- - `ecnu-plus`:原生文本/图片输入,支持 thinking,但不支持选择 reasoning-effort 参数。
51
-
52
- 对 `ecnu-plus` 设置 `supportsReasoningEffort: false`,会移除不支持的 effort 参数但保留 thinking。对 `ecnu-max`,独立的 ECNU 图像理解插件可注册 `enterpriseTransforms`,在不修改 Profile、不替换 Provider 路由的情况下增加聚合图像能力。
53
-
54
- ## 任何公开发布之前
55
-
56
- ECNU 维护者必须另行批准:
57
-
58
- - GitHub 组织和仓库可见性;
59
- - 版权主体表述;
60
- - 校名、ChatECNU 名称和品牌值的使用;
61
- - 公开支持和安全联系地址;
62
- - npm 包名发布;
63
- - 清除全部内部 endpoint、标识符、日志、归档和凭据;
64
- - 生产部署的隐私与安全审查。
65
-
66
- 存在这份参考文档不代表已获准发布任何机构基础设施细节或品牌资产。
9
+ 旧 Key Binding 配置迁移见[迁移说明](key-binding-protocol.md)。真实环境的联调日志和配置不进入公开仓库。
@@ -1,12 +1,12 @@
1
1
  # Enterprise Profile specification (`dsh-oidc/v1alpha1`)
2
2
 
3
- > Candidate extension: omit both keyBinding/provider for identity-only OIDC; brand is optional. Resource mode accepts keyBinding.type=eduwork-resources-v1 (default worker-user-center-v1). provider.modelSource=discovery may omit models; normal profile mode requires models. See [Public resource protocol](public-resource-protocol.en.md).
3
+ > Current source accepts token gateway profiles or identity-only OIDC. Key Binding profiles are removed; see [migration](key-binding-protocol.en.md).
4
4
 
5
5
  [简体中文](enterprise-profile.md) | **English**
6
6
 
7
7
  ## Status and conformance
8
8
 
9
- This document specifies the data contract consumed by `dsh-oidc` `0.2.x`. The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHOULD**, **SHOULD NOT**, and **MAY** are interpreted as described by RFC 2119 and RFC 8174.
9
+ This document specifies the current unpublished source branch contract. See the migration link above for previously released configurations. The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHOULD**, **SHOULD NOT**, and **MAY** are interpreted as described by RFC 2119 and RFC 8174.
10
10
 
11
11
  The canonical machine-readable schema is [`schema/enterprise-profile.v1alpha1.schema.json`](../schema/enterprise-profile.v1alpha1.schema.json). Runtime validation is intentionally stricter in several security-sensitive URL cases. A conforming profile MUST pass both JSON Schema validation and `normalizeEnterpriseProfile()`.
12
12
 
@@ -20,7 +20,6 @@ Profiles are trusted deployment configuration, not user input. Nevertheless, the
20
20
  - development HTTP needs an explicit loopback allowance or exact development-origin allowlist;
21
21
  - URL credentials, fragments, and endpoint-base query strings are rejected;
22
22
  - only the built-in `openai-compatible` adapter can be selected;
23
- - Key Binding accepts only its protocol base and an optional local DSH credential reference; network paths and fields remain fixed;
24
23
  - logo data URLs accept PNG/WebP only, not SVG.
25
24
 
26
25
  A remote administration system MAY distribute profile JSON only if the host authenticates the source, verifies integrity, and stages changes through review. Downloaded JSON does not become safe merely because it contains no JavaScript.
@@ -33,12 +32,11 @@ A remote administration system MAY distribute profile JSON only if the host auth
33
32
  | `id` | yes | Profile ID matching `^[a-z][a-z0-9-]{0,63}$`. |
34
33
  | `displayName` | yes | Human-readable integration name. |
35
34
  | `organization` | no | Organization label; defaults to `displayName`. |
36
- | `nativeInstitutionID` | no | Identifier passed only to a native backend; defaults to `id`. |
37
35
  | `allowInsecureDevelopment` | no | Enables loopback HTTP for local development. Network HTTP additionally requires `insecureDevelopmentOrigin`. |
38
- | `insecureDevelopmentOrigin` | no | Exact non-TLS development origin. It is accepted only together with `allowInsecureDevelopment: true`, and every HTTP OIDC/key-binding/provider endpoint must use this exact origin. Never ship it in a production profile. |
36
+ | `insecureDevelopmentOrigin` | no | Exact non-TLS development origin. It is accepted only together with `allowInsecureDevelopment: true`, and every HTTP OIDC/gateway endpoint must use this exact origin. Never ship it in a production profile. |
39
37
  | `brand` | no | Bounded presentational values. |
40
- | `oidc` | yes | OIDC public-client facts. |
41
- | `keyBinding` | resource mode | Key Binding base URL and optional local DSH credential reference. |
38
+ | `oidc` | identity-only | OIDC public-client facts; cannot include auth or provider. |
39
+ | `auth` | models | Full discovery URL; see the gateway guide for explicit protocol fields. |
42
40
  | `provider` | resource mode | One local OpenAI-compatible Provider route and model list. |
43
41
 
44
42
  ## Branding
@@ -72,31 +70,12 @@ Profiles MUST NOT include logos or names without permission from the rights hold
72
70
  - `clientId` identifies a public client. No client secret belongs in a profile or this plugin.
73
71
  - `scopes` MUST contain `openid` and `profile`, contain no whitespace within an item, and contain no duplicates.
74
72
  - `offline_access` SHOULD be requested when the Provider issues refresh tokens and policy allows it.
75
- - Key Binding authorization scopes are deployment-specific but SHOULD use the names in the reference example.
76
73
 
77
74
  The Web redirect URI is fixed to `http://127.0.0.1:<DSH-port>/oauth/callback`. The host and path are not configurable; the port follows the DSH WebServer's actual listening port.
78
75
 
79
- ## Key Binding object
76
+ ## Gateway auth
80
77
 
81
- ```json
82
- {
83
- "baseURL": "https://ai.example.edu/api/worker/v1",
84
- "credentialRef": "EDUWORK_API_KEY"
85
- }
86
- ```
87
-
88
- | Field | Required | Meaning |
89
- | --- | --- | --- |
90
- | `baseURL` | yes | Base URL of the `worker-user-center-v1` Key Binding interface group. |
91
- | `credentialRef` | no | Local DSH Credential Provider name under which the bound model API key is stored. It must match `^[A-Za-z_][A-Za-z0-9_]*$` and is limited to 128 characters. |
92
-
93
- The Provider ID sent on the wire is always `provider.id`. An omitted `credentialRef` now defaults to `EDUWORK_API_KEY`. Legacy automatic references derived from this Profile’s `id` or `provider.id` (uppercase, non-alphanumerics replaced with `_`, followed by `_API_KEY`) normalize to this common name. Other explicit references remain unchanged.
94
-
95
- `credentialRef` names a local secret-store entry; it is not a key and is never sent over the network. Normal deployments use `EDUWORK_API_KEY`; explicit independent overrides such as `MY_TEST_MODEL_KEY` remain supported. With a shared reference, only a verified login whose bound credential fingerprint matches the current key may read it. Rebinding leaves other profiles disconnected from that key, and their logout cannot delete a key written by another login. Independently configured personal model credentials are unaffected.
96
-
97
- An upgrade imports an old key only when issuer, client ID, management URL, runtime URL and Provider ID all match and only the automatic reference changed. It neither overwrites nor adopts an occupied target. Unproven ownership retains identity but requires reconnecting model resources. Pre-fingerprint sessions are accepted only with an identical complete binding and unambiguous reference ownership.
98
-
99
- Endpoint paths, request/response fields, and Provider ID semantics are protocol facts and MUST NOT be customized by profiles. If a native host returns `runtimeCredentialRef`, it MUST equal the normalized profile value or the plugin fails closed.
78
+ Use [LiteLLM](gateway-auth/README_EN.md) or [experimental oidc-llm](gateway-auth/experimental-oidc-llm.en.md). auth.discoveryUrl is explicit; OIDC mode requires the experimental flag, clientId and identityMode. No model-key fallback is supported.
100
79
 
101
80
  ## Provider object
102
81
 
@@ -107,7 +86,7 @@ The Provider object is data interpreted by a local audited adapter.
107
86
  | `id` | yes | DSH route ID, independent of the shared default credential reference. Must be unique across loaded profiles. |
108
87
  | `displayName` | no | User-facing Provider name. |
109
88
  | `adapter` | yes | Exact string `openai-compatible`. |
110
- | `baseURL` | yes | HTTPS OpenAI-compatible API base. |
89
+ | `baseURL` | not configurable | Derived only from validated gateway discovery. |
111
90
  | `reasoning` | no | Default DSH/pi-ai reasoning level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`; default `high`. |
112
91
  | `defaultContextWindow` | no | Positive safe integer, default 262144. |
113
92
  | `defaultMaxTokens` | no | Positive safe integer, default 32768. |
@@ -117,8 +96,8 @@ The Provider object is data interpreted by a local audited adapter.
117
96
  | `streamIdleTimeoutMs` | no | Positive idle timeout, default 300000. |
118
97
  | `retryPolicy` | no | DSH provider-owned retry policy; default normal/2 retries. |
119
98
  | `compat` | no | Bounded pi-ai OpenAI compatibility facts. |
120
- | `modelSource` | no | `profile` (default) or `discovery`; discovery requests `/models` with the managed key. |
121
- | `models` | profile mode | 1–128 unique entries; discovery may omit them, and configured entries supply reviewed capabilities. |
99
+ | `modelSource` | no | Only discovery, the default; fetches /models with the current Access Token. |
100
+ | `models` | no | Reviewed model capabilities; cannot expose models absent from the authorized catalog. |
122
101
 
123
102
  `retryPolicy.mode` is `normal` or `always`. `always` can retry indefinitely until success, cancellation, or disposal and SHOULD NOT be enabled without an explicit product decision. The policy is validated again by DSH.
124
103
 
@@ -1,12 +1,12 @@
1
1
  # Enterprise Profile 规范(`dsh-oidc/v1alpha1`)
2
2
 
3
- > 当前候选扩展:纯身份模式同时省略 keyBinding/provider;brand 可省略。资源模式支持 keyBinding.type=eduwork-resources-v1,默认兼容 worker-user-center-v1。provider.modelSource=discovery 时 models 可省略;默认 profile 模式仍要求模型列表。见 [公共资源协议](public-resource-protocol.md)。
3
+ > 当前源码接受 Token 网关或纯身份 OIDC 配置。Key Binding 配置已移除,见[迁移说明](key-binding-protocol.md)。
4
4
 
5
5
  **简体中文** | [English](enterprise-profile.en.md)
6
6
 
7
7
  ## 状态与符合性
8
8
 
9
- 本文规定 `dsh-oidc` `0.2.x` 使用的数据契约。关键词 **必须(MUST)**、**不得(MUST NOT)**、**必需(REQUIRED)**、**应该(SHOULD)**、**不应该(SHOULD NOT)** 和 **可以(MAY)** 按 RFC 2119 与 RFC 8174 解释。
9
+ 本文规定当前未发布源码分支使用的数据契约,旧已发布版本的配置迁移见上文链接。关键词 **必须(MUST)**、**不得(MUST NOT)**、**必需(REQUIRED)**、**应该(SHOULD)**、**不应该(SHOULD NOT)** 和 **可以(MAY)** 按 RFC 2119 与 RFC 8174 解释。
10
10
 
11
11
  权威机器可读 Schema 为 [`schema/enterprise-profile.v1alpha1.schema.json`](../schema/enterprise-profile.v1alpha1.schema.json)。运行时对若干安全敏感 URL 的校验有意比 JSON Schema 更严格。符合规范的 Profile 必须同时通过 JSON Schema 和 `normalizeEnterpriseProfile()` 校验。
12
12
 
@@ -17,10 +17,9 @@ Profile 是受信任的部署配置,不是用户输入。即便如此,解析
17
17
  - 根对象和所有涉及可执行行为的嵌套对象都会拒绝未知字段;
18
18
  - 序列化后的 Profile 总大小不超过 512 KiB;
19
19
  - 生产 URL 必须使用 HTTPS;
20
- - 只有显式 loopback 主机可以在开发环境使用 HTTP;
20
+ - 开发环境使用 HTTP 必须显式允许 loopback,或绑定精确的开发 origin;
21
21
  - 拒绝 URL 凭据、fragment 和 endpoint-base query string;
22
22
  - 只能选择内置的 `openai-compatible` adapter;
23
- - Key Binding 只接受协议基址和可选的本地 DSH 凭据引用,不能改写网络路径或字段;
24
23
  - logo data URL 只接受 PNG/WebP,不接受 SVG。
25
24
 
26
25
  远程管理系统只有在宿主能够认证来源、校验完整性并通过审查流程暂存变更时,才可以分发 Profile JSON。下载的 JSON 不会仅仅因为不包含 JavaScript 就自动变得安全。
@@ -33,12 +32,11 @@ Profile 是受信任的部署配置,不是用户输入。即便如此,解析
33
32
  | `id` | 是 | 匹配 `^[a-z][a-z0-9-]{0,63}$` 的 Profile ID。 |
34
33
  | `displayName` | 是 | 面向用户的集成名称。 |
35
34
  | `organization` | 否 | 机构名称;默认使用 `displayName`。 |
36
- | `nativeInstitutionID` | 否 | 仅传给 native 后端的标识符;默认使用 `id`。 |
37
35
  | `allowInsecureDevelopment` | 否 | 为本地开发启用 loopback HTTP。网络 HTTP 还必须配置 `insecureDevelopmentOrigin`。 |
38
- | `insecureDevelopmentOrigin` | 否 | 精确的非 TLS 开发 origin。只有与 `allowInsecureDevelopment: true` 一起使用时才有效,且所有 HTTP OIDC/Key Binding/Provider endpoint 必须使用该 origin。不得放入生产 Profile。 |
36
+ | `insecureDevelopmentOrigin` | 否 | 精确的非 TLS 开发 origin。只有与 `allowInsecureDevelopment: true` 一起使用时才有效,且所有 HTTP OIDC/网关 endpoint 必须使用该 origin。不得放入生产 Profile。 |
39
37
  | `brand` | 否 | 有边界的展示配置。 |
40
- | `oidc` | 是 | OIDC public client 配置事实。 |
41
- | `keyBinding` | 资源模式 | Key Binding 基础 URL,以及可选的本地 DSH 凭据引用。 |
38
+ | `oidc` | 纯身份模式 | OIDC public client 信息,不得同时配置 auth 或 provider。 |
39
+ | `auth` | 模型模式 | 完整发现地址,协议字段见网关指南。 |
42
40
  | `provider` | 资源模式 | 一个本地 OpenAI-compatible Provider 路由和模型列表。 |
43
41
 
44
42
  ## 品牌替换
@@ -72,31 +70,12 @@ Profile 是受信任的部署配置,不是用户输入。即便如此,解析
72
70
  - `clientId` 标识 public client。Profile 和本插件都不应包含 client secret。
73
71
  - `scopes` 必须包含 `openid` 和 `profile`;每项内部不得包含空白,且不得重复。
74
72
  - 当 Provider 会签发 refresh token 且策略允许时,应该请求 `offline_access`。
75
- - Key Binding 授权 scope 由部署决定,但建议使用参考示例中的名称。
76
73
 
77
74
  Web 重定向地址固定为 `http://127.0.0.1:<DSH端口>/oauth/callback`。host 和 path 不可配置;端口取 DSH WebServer 的实际监听端口。
78
75
 
79
- ## Key Binding 对象
76
+ ## 网关 auth
80
77
 
81
- ```json
82
- {
83
- "baseURL": "https://ai.example.edu/api/worker/v1",
84
- "credentialRef": "EDUWORK_API_KEY"
85
- }
86
- ```
87
-
88
- | 字段 | 必需 | 含义 |
89
- | --- | --- | --- |
90
- | `baseURL` | 是 | `worker-user-center-v1` Key Binding 接口组的基址。 |
91
- | `credentialRef` | 否 | 保存绑定后模型 API Key 的本地 DSH Credential Provider 名称。必须匹配 `^[A-Za-z_][A-Za-z0-9_]*$`,最长 128 字符。 |
92
-
93
- 请求中的 Provider ID 固定取 `provider.id`。`credentialRef` 省略时统一为 `EDUWORK_API_KEY`。旧配置中按本 Profile 的 `id` 或 `provider.id` 大写、非字母数字替换为 `_` 后追加 `_API_KEY` 的引用,按兼容别名规范化到同一默认值,不再按企业生成名称。其他显式引用原样保留。
94
-
95
- `credentialRef` 只是本地秘密存储的引用名,不是 API Key,不发送给 OIDC、Key Binding 或模型服务。常规部署使用 `EDUWORK_API_KEY`;需要额外独立管理的显式引用仍可使用例如 `MY_TEST_MODEL_KEY`。默认槽位记录当前绑定的企业 Key;多个 Profile 共用时,仅凭据指纹与当前 Key 匹配的已验证登录可以读取它。重新绑定后,其他 Profile 不会借用该 Key,登出也不会删除其他登录写入的新 Key。个人模型使用的独立引用不受影响。
96
-
97
- 升级只在同一 issuer、client ID、资源管理基址、模型服务基址、Provider ID 且仅旧自动引用改名时迁移原 Key。新槽位已有值时既不覆盖也不自动接管;无法证明归属时保留身份并重新连接模型资源。旧会话缺少指纹时,仅完整绑定相同且引用归属无歧义才兼容。
98
-
99
- Endpoint 路径、请求/响应字段和 Provider ID 语义属于协议事实,不得由 Profile 自定义。Native 宿主若返回 `runtimeCredentialRef`,其值必须与本字段规范化后的结果一致,否则插件失败关闭。
78
+ 选择 [LiteLLM](gateway-auth/README.md) 或 [oidc-llm 实验](gateway-auth/experimental-oidc-llm.md)。auth.discoveryUrl 必须完整;实验 OIDC 另外填写显式开关、clientId 和 identityMode。不支持回退到模型 Key 流程。
100
79
 
101
80
  ## Provider 对象
102
81
 
@@ -107,7 +86,7 @@ Provider 对象是由本地、经过审查的 adapter 解释的数据。
107
86
  | `id` | 是 | DSH 路由 ID;在所有已加载 Profile 中必须唯一。 |
108
87
  | `displayName` | 否 | 面向用户的 Provider 名称。 |
109
88
  | `adapter` | 是 | 精确字符串 `openai-compatible`。 |
110
- | `baseURL` | 是 | HTTPS OpenAI-compatible API 基址。 |
89
+ | `baseURL` | 不得配置 | 只能来自已校验的网关发现。 |
111
90
  | `reasoning` | 否 | 默认 DSH/pi-ai reasoning level,取值为 `off`、`minimal`、`low`、`medium`、`high`、`xhigh`、`max`,默认为 `high`。 |
112
91
  | `defaultContextWindow` | 否 | 正安全整数,默认 262144。 |
113
92
  | `defaultMaxTokens` | 否 | 正安全整数,默认 32768。 |
@@ -117,8 +96,8 @@ Provider 对象是由本地、经过审查的 adapter 解释的数据。
117
96
  | `streamIdleTimeoutMs` | 否 | 正数空闲超时,默认 300000。 |
118
97
  | `retryPolicy` | 否 | 由 DSH Provider 管理的重试策略;默认 normal/重试 2 次。 |
119
98
  | `compat` | 否 | 有边界的 pi-ai OpenAI 兼容事实。 |
120
- | `modelSource` | 否 | `profile`(默认)或 `discovery`;后者通过受管 Key 请求 `/models`。 |
121
- | `models` | `profile` 模式 | 1–128 个唯一模型条目;`discovery` 可省略,已配置条目作为能力声明。 |
99
+ | `modelSource` | 否 | 仅支持 discovery,默认值;使用当前 Access Token 读取 /models。 |
100
+ | `models` | 否 | 已审查的模型能力元数据,不得扩大获授权目录。 |
122
101
 
123
102
  `retryPolicy.mode` 可以是 `normal` 或 `always`。`always` 可能一直重试,直到成功、取消或销毁;没有明确产品决策时不应启用。该策略还会由 DSH 再次校验。
124
103
 
@@ -0,0 +1,168 @@
1
+ # 网关认证接入
2
+
3
+ **简体中文** | [English](README_EN.md)
4
+
5
+ 本分支新增 LiteLLM native OAuth 支持,以及默认关闭的 [oidc-llm 实验适配器](experimental-oidc-llm.md),尚未发布 npm 或桌面版本。仅身份 oidc 配置保留;旧 Key Binding 模型流程已移除,迁移见[说明](../key-binding-protocol.md)。新的 **oidc-llm** 仍是[协议草案](oidc-llm-draft.md),实验实现不代表协议已定稿。
6
+
7
+ ## 选择接入方式
8
+
9
+ 配置已有 LiteLLM 网关请从 [LiteLLM 接入指南](litellm-setup.md)开始;本页描述底层协议和宿主集成要求。
10
+
11
+ | 服务端 | 配置 | 模型凭据 | 客户端注册 |
12
+ | --- | --- | --- | --- |
13
+ | 纯身份 OIDC | 仅 oidc,不配置 provider | 无机构模型凭据 | 预注册 public client |
14
+ | LiteLLM 1.101.0 native contract 1 | `auth.discoveryUrl` | 登录返回的 Access Token | 每次登录动态注册实际本机回调 |
15
+ | oidc-llm 0.1 实验 | `auth` + 显式实验开关与身份模式 | Access Token | 已实现预注册 public client |
16
+
17
+ LiteLLM 不需要实现 EduWork 的 Key Binding。普通 OIDC 的 Access Token 也不会因为配置了模型 URL 就获得推理权限;须配置明确的 Token 网关契约。配额和团队管理不纳入本次统一协议;LiteLLM 自己的团队选择留在网关网页,客户端仅保存不透明的授权上下文用于防止刷新串号。
18
+
19
+ ## LiteLLM 配置
20
+
21
+ 在含本功能的构建中,把下列对象放入 `config/eduwork.jsonc` 的 `organizations` 数组;直接使用插件时放入 `profiles`。完整示例见 [Enterprise Profile](../../examples/litellm.enterprise-profile.example.json)。
22
+
23
+ ```json
24
+ {
25
+ "schemaVersion": "dsh-oidc/v1alpha1",
26
+ "id": "example-gateway",
27
+ "displayName": "示例模型网关",
28
+ "auth": {
29
+ "discoveryUrl": "https://gateway.example.org/.well-known/litellm-cli-auth",
30
+ "expectedIssuer": "https://gateway.example.org"
31
+ }
32
+ }
33
+ ```
34
+
35
+ `discoveryUrl` 是完整地址,不由客户端猜路径。`expectedIssuer` 可省略,但部署方建议填写;LiteLLM 适配器始终要求 issuer、resource、认证端点与发现地址同源,且 resource 与 issuer 完整字符串一致。生产使用 HTTPS;实验只允许显式设置 `allowInsecureDevelopment: true` 后的 loopback HTTP。不允许通过 `insecureDevelopmentOrigin` 放宽此新协议到任意明文地址。
36
+
37
+ `auth` 与 `oidc`、`keyBinding` 互斥;LiteLLM 配置不填写固定 `clientId`、secret、scope 或 `provider.baseURL`。模型 API 基址由已校验的 issuer 追加 `/v1`,保留部署路径前缀。可选 `provider` 支持唯一的 `id`、显示名、上下文/输出限制和经管理员核实的 `models` 能力;只能使用 `modelSource: "discovery"`。模型能否出现由当前账户的 `/v1/models` 决定,配置里的模型信息只是能力补充,不会绕过授权。
38
+
39
+ 普通 `/models` 只有 ID 时默认视为文本模型、不推测思考或图像能力。可用模型列表为空时保持空列表。读取失败显示目录暂不可用;不会沿用另一个账户的模型列表。
40
+
41
+ 插件使用 `backend: "desktop"` 或本机 `backend: "web"`;旧 `native` 桥已移除。示例插件配置:
42
+
43
+ ```json
44
+ {
45
+ "backend": "desktop",
46
+ "profilePathEnv": "EDUWORK_OIDC_PROFILE"
47
+ }
48
+ ```
49
+
50
+ 环境变量指向上述 Profile JSON 文件。桌面宿主须按[宿主接入指南](../desktop-host.md)提供 `desktopServices.openExternal` 和 Credential Provider;网页宿主保持既有本机单用户边界。
51
+
52
+ ## LiteLLM native contract 1 接口
53
+
54
+ 以下描述对应 **LiteLLM v1.101.0**,不是我们要求 LiteLLM 实现的新协议。端点名称来自发现响应,表中的路径为该版本默认部署示例。
55
+
56
+ ### 1. 发现
57
+
58
+ 无凭据 `GET /.well-known/litellm-cli-auth`,成功 200:
59
+
60
+ ```json
61
+ {
62
+ "contract_version": 1,
63
+ "issuer": "https://gateway.example.org",
64
+ "authorization_endpoint": "https://gateway.example.org/authorize",
65
+ "token_endpoint": "https://gateway.example.org/token",
66
+ "registration_endpoint": "https://gateway.example.org/register",
67
+ "revocation_endpoint": "https://gateway.example.org/revoke",
68
+ "resource": "https://gateway.example.org",
69
+ "response_types_supported": ["code"],
70
+ "grant_types_supported": ["authorization_code", "refresh_token"],
71
+ "code_challenge_methods_supported": ["S256"],
72
+ "token_endpoint_auth_methods_supported": ["none"],
73
+ "revocation_endpoint_auth_methods_supported": ["none"]
74
+ }
75
+ ```
76
+
77
+ 客户端核对所有必需能力和 URL,未知版本、缺失能力、混入 `oidc_llm` 标志均报错。不能仅根据域名、一个 `contract_version` 字段或没有 ID Token 来猜协议。配置/端点绑定变化后,旧 Token 不发送到新服务。
78
+
79
+ ### 2. 动态注册
80
+
81
+ 先绑定 `127.0.0.1` 临时端口,再向 registration endpoint 发 JSON POST:
82
+
83
+ ```json
84
+ {
85
+ "client_name": "EduWork",
86
+ "redirect_uris": ["http://127.0.0.1:53187/oauth/callback"],
87
+ "token_endpoint_auth_method": "none",
88
+ "grant_types": ["authorization_code", "refresh_token"],
89
+ "response_types": ["code"]
90
+ }
91
+ ```
92
+
93
+ 成功 201,返回 `client_id`、`client_id_issued_at`、`redirect_uris`、`token_endpoint_auth_method`、`grant_types` 和 `response_types`。回调必须与注册请求一致。该版本允许 1–3 个回调,每个至多 256 字符;EduWork 只注册本次实际回调。`client_id` 是网关签发的 opaque 注册标识,不是用户 ID;不能用任意字符串代替。刷新复用原 client ID,不重新注册。
94
+
95
+ ### 3. 浏览器授权
96
+
97
+ GET authorization endpoint,参数:
98
+
99
+ | 参数 | 客户端值 |
100
+ | --- | --- |
101
+ | `response_type` | `code` |
102
+ | `client_id` | 注册返回值 |
103
+ | `redirect_uri` | 与注册精确一致 |
104
+ | `state` | 每次随机,回调必须匹配,只消费一次 |
105
+ | `code_challenge` / `code_challenge_method` | PKCE SHA-256 / `S256` |
106
+ | `resource` | 发现响应原值 |
107
+
108
+ 必须发送 `resource`,否则该版本可能进入 MCP 授权路径。Native 流程不提供 OIDC 的 nonce/ID Token 校验,也未按应用 scope 实现权限隔离,客户端不发送虚构的 `openid` 或自定义模型 scope。
109
+
110
+ 用户在网关登录、选择网关支持的授权上下文并同意后,浏览器返回 `code` 与 `state`;拒绝返回 `error=access_denied` 与 `state`。无效 client/回调可能直接收到 JSON 错误。授权页面及其内部 `/authorize/complete` 由 LiteLLM 管理,不属于 EduWork Host 调用的认证 API。
111
+
112
+ ### 4. 换码与刷新
113
+
114
+ 向 token endpoint 发 `application/x-www-form-urlencoded` POST:
115
+
116
+ | 操作 | 表单字段 |
117
+ | --- | --- |
118
+ | 换码 | `grant_type=authorization_code`、`client_id`、`code`、`redirect_uri`、`code_verifier`、`resource` |
119
+ | 刷新 | `grant_type=refresh_token`、`client_id`、`refresh_token`、`resource` |
120
+
121
+ 成功 200,两种操作均返回:
122
+
123
+ ```json
124
+ {
125
+ "access_token": "OPAQUE_ACCESS_TOKEN",
126
+ "token_type": "Bearer",
127
+ "expires_in": 3600,
128
+ "refresh_token": "OPAQUE_REFRESH_TOKEN",
129
+ "user_id": "example-user",
130
+ "team_id": null
131
+ }
132
+ ```
133
+
134
+ Access Token 是不透明凭据,不能自行解析为 JWT。`user_id` 是非空字符串,`team_id` 是字符串或 null,二者在刷新后必须保持一致;不一致要求重新登录。`expires_in` 以响应为准:上例 1 小时仅为示例,该版本默认 24 小时且可配置。Refresh Token 每次轮换、单次使用,其 14 天期限随新签发重新计算,不是整个登录的绝对寿命。
135
+
136
+ Host 在临近到期时刷新,同一授权的并发请求共用一次刷新,将新 Token 对整体保存后再使用。失败的目录读取、403、429、5xx 不会一概清除登录;明确 `invalid_grant`/`invalid_client` 或刷新身份改变才要求重新登录。重定向不会携带 code/token 自动跟随。已经开始的生成和 SSE 不因认证失败而由本层自动重放。
137
+
138
+ 模型请求在准备时绑定当前授权。退出或更换账户/授权上下文后,已准备但尚未发送的请求不能改用新账户凭据;正在接收的请求会取消,迟到的正文与流式内容不再交给调用方。相同授权内的 Token 刷新不会中断生成。这是客户端的本地隔离,不代表网关已立即撤销 Access Token,也不保证上游推理或计费同步停止。
139
+
140
+ ### 5. 用户资料与模型
141
+
142
+ | 请求 | 鉴权与处理 |
143
+ | --- | --- |
144
+ | `GET /user/info` | Bearer Access Token;返回 `user_id`、`user_info` 等。只接收匹配主体的 `user_info.user_alias` 作为可选显示名;失败降级为 Token 响应的 user ID |
145
+ | `GET /v1/models` | Bearer Access Token;OpenAI 风格 `data` 数组,每项 `id`。用于当前授权的模型目录 |
146
+ | `POST /v1/chat/completions` | 相同 Bearer;普通 JSON 或 `stream=true` 的 SSE(含 `[DONE]`),复用 DSH 模型适配器 |
147
+
148
+ `/user/info` 是 LiteLLM adapter 的固定映射,native discovery 没有 `userinfo_endpoint`。其完整响应可能包含 Key、团队、预算等管理信息,不能直接传给 UI、日志或诊断包。模型 Token 不写成长期 `EDUWORK_API_KEY` 副本;主会话与子代理在实际请求时共用 Host 的有效凭据解析入口。
149
+
150
+ ### 6. 撤销与错误
151
+
152
+ POST revocation endpoint,表单 `token=<refresh_token>&client_id=<registered_client>`。该版本以 200 `{}` 返回撤销结果;有效 client 下未知、过期、重复的 Token 可得到相同成功形状。非法 client 返回 401;协调服务或其他服务器异常可能返回 503/500。
153
+
154
+ **只撤销提交的 Refresh Token,不能宣称立即撤销全部 Access Token 或整个刷新家族。** Access Token 仍按网关有效期处理。客户端退出先清除本地授权及模型目录,再尝试远端撤销;远端失败记录不含凭据的警告,不阻止本地退出。当前客户端没有跨重启的待撤销重试队列。
155
+
156
+ 常见 OAuth 错误为 400 的 `invalid_request`、`invalid_grant`、`invalid_target`、`unsupported_grant_type`;参数结构错误也可能是 FastAPI 的 422。403 表示请求无权限;429/5xx 为独立的资源/服务错误,不能解释成必定需要重新登录。用户 Token 的实际权限由该版网关决定,不宣称已限制为“仅模型推理”。
157
+
158
+ ## 现有 OIDC 与后续迁移
159
+
160
+ 纯身份 OIDC 与 oidc-llm 的 OIDC 模式共用 Discovery issuer、PKCE S256、state、nonce、RS256 ID Token 和 UserInfo sub 一致性校验。Key Binding 与旧凭据引用迁移已移除,模型授权改用网关 Token。相关接口见[服务端契约](../server-integration-contract.md)、[Profile 规范](../enterprise-profile.md)。没有为兼容 LiteLLM 放松这些检查。
161
+
162
+ 新 oidc-llm 的 UserInfo 从发现的 `userinfo_endpoint` 获取,复用标准主体和资料字段。实验配置及实际限制见[实验接入](experimental-oidc-llm.md),尚待讨论的 scope、生命周期和撤销保证见[草案](oidc-llm-draft.md)。真实服务完成验收前,不迁移已有机构配置。
163
+
164
+ ## 开发验证
165
+
166
+ 包内执行 `npm ci`、`npm run check`。合成测试覆盖纯身份 OIDC、安全发现、真实 loopback、刷新竞争、账户切换、目录隔离和错误行为;真实 LiteLLM 联调需单独运行本地网关并完成浏览器登录、普通/SSE、刷新、重启和退出验收。HTTP 同意页驱动、模拟到期或 mock 模型不等价于完整桌面、自然到期或真实推理验收。测试账号和 Token 不进入公开仓库。
167
+
168
+ 官方依据:[v1.101.0](https://github.com/BerriAI/litellm/releases/tag/v1.101.0)、[native flow](https://github.com/BerriAI/litellm/blob/v1.101.0/litellm/proxy/_experimental/mcp_server/gateway_dcr_flow.py)、[HTTP routes](https://github.com/BerriAI/litellm/blob/v1.101.0/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py)、[proxy credentials](https://github.com/BerriAI/litellm/blob/v1.101.0/litellm/proxy/_experimental/mcp_server/proxy_api_credentials.py)。
@@ -0,0 +1,70 @@
1
+ # Gateway authentication
2
+
3
+ [简体中文](README.md) | **English**
4
+
5
+ This branch adds LiteLLM native OAuth and an [experimental oidc-llm adapter](experimental-oidc-llm.en.md), disabled by default. Neither is included in a published npm package or desktop Release yet. Identity-only oidc profiles remain supported. Key Binding model flows are removed; see [migration](../key-binding-protocol.en.md). **oidc-llm** remains a [draft](oidc-llm-draft.en.md); the experiment does not finalize the protocol.
6
+
7
+ ## Choose a connection
8
+
9
+ Start with the [LiteLLM setup guide](litellm-setup.en.md) to configure an existing gateway. This page describes the underlying protocol and Host integration requirements.
10
+
11
+ | Server | Configuration | Model credential | Client registration |
12
+ | --- | --- | --- | --- |
13
+ | Identity-only OIDC | oidc without provider | No organization model credential | Pre-registered public client |
14
+ | LiteLLM 1.101.0 native contract 1 | `auth.discoveryUrl` | Login Access Token | Dynamically register the actual callback on each login |
15
+ | Experimental oidc-llm 0.1 | `auth` with explicit opt-in and identity mode | Access Token | Static public client implemented |
16
+
17
+ Quota standardization and team management are outside this integration. LiteLLM handles its own team choice in the gateway page; the adapter retains its opaque authorization context solely to detect an unexpected identity change during refresh. An ordinary OIDC Access Token does not automatically authorize model inference.
18
+
19
+ ## Configuration
20
+
21
+ In a build containing this feature, add this object to `organizations` in `config/eduwork.jsonc`, or `profiles` when using the plugin directly:
22
+
23
+ ```json
24
+ {
25
+ "schemaVersion": "dsh-oidc/v1alpha1",
26
+ "id": "example-gateway",
27
+ "displayName": "Example model gateway",
28
+ "auth": {
29
+ "discoveryUrl": "https://gateway.example.org/.well-known/litellm-cli-auth",
30
+ "expectedIssuer": "https://gateway.example.org"
31
+ }
32
+ }
33
+ ```
34
+
35
+ See the [complete Profile example](../../examples/litellm.enterprise-profile.example.json). `discoveryUrl` is the complete configured URL. `expectedIssuer` is optional but recommended. Native discovery requires issuer, resource and auth endpoints on the discovery origin; resource must exactly equal issuer. Production uses HTTPS. Explicit `allowInsecureDevelopment: true` permits loopback HTTP only; `insecureDevelopmentOrigin` does not relax this new protocol to arbitrary HTTP hosts.
36
+
37
+ `auth` is mutually exclusive with `oidc` and `keyBinding`. Do not supply a static `clientId`, secret, scopes or `provider.baseURL`. The adapter appends `/v1` to the validated issuer, preserving any deployment prefix. An optional `provider` may supply a distinct `id`, display name, context/output limits and reviewed per-model capabilities in `models`; only `modelSource: "discovery"` is supported. The current account's model list determines visibility. Local model metadata cannot grant access to an absent model.
38
+
39
+ Models with only an ID default to text-only without assumed reasoning support. An empty catalog stays empty; a failed fetch never adopts another account's catalog.
40
+
41
+ Use `backend: "desktop"` or local `backend: "web"`; the legacy `native` account bridge has been removed. A direct plugin configuration can use `{"backend":"desktop","profilePathEnv":"EDUWORK_OIDC_PROFILE"}` with the environment variable pointing to a Profile JSON file. The desktop Host supplies `desktopServices.openExternal` and a Credential Provider as described in the [Host guide](../desktop-host.md). Web keeps the existing single-user loopback boundary.
42
+
43
+ ## LiteLLM native contract 1
44
+
45
+ The wire behavior below is pinned to **LiteLLM v1.101.0**. Endpoints come from discovery; paths are the default deployment examples, not a new specification imposed on LiteLLM.
46
+
47
+ 1. **Discovery:** unauthenticated GET `/.well-known/litellm-cli-auth`, 200 JSON containing integer `contract_version: 1`, `issuer`, `resource`, `authorization_endpoint`, `token_endpoint`, `registration_endpoint`, `revocation_endpoint`; required capabilities are `response_types_supported: ["code"]`, `grant_types_supported` containing code and refresh, `code_challenge_methods_supported` containing `S256`, and token/revocation auth methods containing `none`. Unknown versions, missing capabilities and an `oidc_llm` marker fail explicitly. Detection never depends on a brand in the URL or a missing ID Token. A changed configuration/endpoint binding cannot receive saved tokens.
48
+ 2. **Registration:** bind an actual `http://127.0.0.1:<port>/oauth/callback`, then POST JSON to the registration endpoint with `client_name: "EduWork"`, `redirect_uris: [actualCallback]`, `token_endpoint_auth_method: "none"`, code/refresh grant types and `response_types: ["code"]`. The 201 response contains `client_id`, `client_id_issued_at`, registered redirects, auth method, grants and response types. The callback must match. This server allows 1–3 redirects of at most 256 characters; EduWork registers one. The opaque client ID is not a user ID; each login registers, while refresh reuses its original ID.
49
+ 3. **Authorize:** navigate the system browser to the advertised endpoint with `response_type=code`, registered `client_id`, exact `redirect_uri`, random one-use `state`, S256 PKCE challenge and exact discovered `resource`. Omitting resource can enter the MCP flow. Native auth does not supply OIDC ID Tokens or enforce application scopes; the client invents neither nonce/`openid` requirements nor model scopes. Gateway login/consent returns code + state, or `access_denied` + state. The gateway's internal `/authorize/complete` form is not a Host API.
50
+ 4. **Exchange/refresh:** POST form data to token endpoint. Exchange sends `grant_type=authorization_code`, client ID, code, redirect URI, verifier and resource. Refresh sends `grant_type=refresh_token`, client ID, refresh token and resource. Both return 200 with nonempty `access_token`, `refresh_token`, `token_type: "Bearer"`, positive integer `expires_in`, nonempty `user_id`, and `team_id` (string or null). Access tokens are opaque. The response lifetime is authoritative; default access lifetime in this version is 24 hours and configurable. Rotating refresh tokens are single-use, with a new 14-day expiry on each issuance. Subject and opaque team must remain unchanged during refresh.
51
+ 5. **Identity/models:** GET `/user/info`, GET `/v1/models`, POST `/v1/chat/completions` all use the Access Token as Bearer. `/user/info` is a native adapter mapping, not an advertised OIDC `userinfo_endpoint`; only matching `user_id` and optional `user_info.user_alias` are projected. Failure falls back to the token subject. Its keys, teams and budgets are never returned wholesale to the renderer. Models use OpenAI-style `data[].id`. Inference reuses the DSH adapter for JSON and SSE with `[DONE]`. No permanent `EDUWORK_API_KEY` copy is created; main and subagents resolve fresh credentials through the shared Host.
52
+ 6. **Revoke:** POST form `token=<refresh>&client_id=<registeredClient>` to the advertised revoke endpoint. 200 `{}` also covers unknown/expired/repeated tokens under a valid client. Invalid client can return 401; coordination/server failures can return 503/500. **Only the submitted refresh token is revoked, not a whole family or all access tokens.** Access tokens retain the server's expiry behavior. Local logout clears tokens and routes before attempting remote revocation; a failure emits a credential-free warning. This client does not yet persist a revocation retry queue across restarts.
53
+
54
+ See the [Chinese wire reference](README.md#litellm-native-contract-1-接口) for complete JSON examples. Common token errors include 400 `invalid_request`, `invalid_grant`, `invalid_target`, `unsupported_grant_type`; malformed input can instead use FastAPI 422. 403 permissions, 429 limits and 5xx outages do not generally erase login. Do not claim native tokens have server-enforced inference-only scopes.
55
+
56
+ The Host merges concurrent refreshes for one authorization and persists the complete rotated pair before use. A still-valid token can survive a temporary refresh outage; an expired token stops. Terminal grant errors or changed refresh identity require sign-in. Credential-bearing requests do not follow redirects. This authentication layer never automatically replays generation or a partially consumed SSE; safe reads may refresh and retry once after 401.
57
+
58
+ Model calls bind to the current authorization when prepared. After logout or account/authorization replacement, a prepared call cannot acquire the new account's credential, active requests are cancelled, and late response bytes or stream content are discarded. Refresh within the same authorization keeps generation running. This is local client isolation, not immediate server-side Access Token revocation or a guarantee that upstream inference and billing stop at the same time.
59
+
60
+ ## Existing OIDC and the future draft
61
+
62
+ Identity-only OIDC and the oidc-llm OIDC mode share issuer, PKCE, state, nonce, RS256 ID Token and matching UserInfo subject validation. Key Binding and legacy credential-name migration have been removed; model authorization uses gateway Tokens. See the [existing server contract](../server-integration-contract.md) and [Profile reference](../enterprise-profile.md). These checks were not weakened for LiteLLM.
63
+
64
+ The oidc-llm adapter discovers `userinfo_endpoint` and reuses standard subject and identity claims. See [experimental integration](experimental-oidc-llm.en.md) for configuration and actual limits. Scope, lifetimes and revocation guarantees remain under review in the [draft](oidc-llm-draft.en.md). Do not migrate an existing institution configuration before real-server acceptance.
65
+
66
+ ## Validation
67
+
68
+ Run `npm ci` and `npm run check` in the package. Synthetic tests cover identity-only OIDC, discovery validation, real loopback callbacks, refresh races, account/catalog isolation and errors. Live gateway acceptance separately covers browser authorization, ordinary/SSE inference, refresh, restart and logout. An HTTP form driver, simulated near-expiry and a mock inference backend do not prove desktop UI, natural expiry or real inference. Keep test credentials outside the public repository.
69
+
70
+ Official baseline: [release](https://github.com/BerriAI/litellm/releases/tag/v1.101.0), [native flow](https://github.com/BerriAI/litellm/blob/v1.101.0/litellm/proxy/_experimental/mcp_server/gateway_dcr_flow.py), [HTTP routes](https://github.com/BerriAI/litellm/blob/v1.101.0/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py), [proxy credentials](https://github.com/BerriAI/litellm/blob/v1.101.0/litellm/proxy/_experimental/mcp_server/proxy_api_credentials.py).
@@ -0,0 +1,53 @@
1
+ # Experimental oidc-llm integration
2
+
3
+ [简体中文](experimental-oidc-llm.md) | **English**
4
+
5
+ This branch provides an opt-in oidc-llm 0.1 adapter, disabled by default and not released. The protocol remains under review. This page describes implemented client behavior; it does not claim that the [complete draft](oidc-llm-draft.en.md) is finalized or that a server has passed acceptance.
6
+
7
+ ## Configuration
8
+
9
+ Add this object to desktop `organizations` or plugin `profiles`. The server must publish the complete `oidc_llm` extension; ordinary OIDC metadata cannot implicitly enable model access.
10
+
11
+ ```json
12
+ {
13
+ "schemaVersion": "dsh-oidc/v1alpha1",
14
+ "id": "example-models",
15
+ "displayName": "Example model service",
16
+ "auth": {
17
+ "discoveryUrl": "https://models.example.org/.well-known/openid-configuration",
18
+ "expectedIssuer": "https://models.example.org",
19
+ "experimentalOidcLlm": true,
20
+ "clientId": "replace-with-public-client-id",
21
+ "identityMode": "oidc"
22
+ }
23
+ }
24
+ ```
25
+
26
+ Select `identityMode` explicitly. Neither mode falls back to the other:
27
+
28
+ | Mode | Requested scopes | Identity validation |
29
+ | --- | --- | --- |
30
+ | `oidc` | `openid profile offline_access llm:models:read llm:invoke`, with consent | Initial RS256 ID Token required; signature, issuer, aud/azp, nonce, time, optional at_hash and UserInfo subject are verified |
31
+ | `oauth` | `llm:profile llm:models:read llm:invoke` | Reads sub from the discovered UserInfo-shaped endpoint; does not claim OIDC authentication |
32
+
33
+ Only static public-client registration is implemented; no client secret is accepted. Register the actual IPv4 loopback callback `http://127.0.0.1:<random-port>/oauth/callback`. Dynamic registration remains future work.
34
+
35
+ When discovery declares `authorization_response_iss_parameter_supported: true`, both successful and error authorization responses must include `iss` exactly matching the discovered `issuer`. A missing value produces `gateway_callback_issuer_missing`; repeated, empty or mismatched values produce `gateway_callback_issuer_invalid`. The client does not exchange the code. Ask the authentication service administrator to align discovery and callback behavior before starting a new sign-in. The desktop callback listener closes when the flow ends; refreshing an old callback URL cannot resume sign-in.
36
+
37
+ Production endpoints require HTTPS. An isolated experiment may reuse `allowInsecureDevelopment: true` and `insecureDevelopmentOrigin`, with a mandatory `expectedIssuer` pin. HTTP is restricted to loopback or that exact origin, never an entire private network. LiteLLM native retains its loopback-only exception.
38
+
39
+ ## Shared implementation and limits
40
+
41
+ Both gateway adapters reuse discovery transport, Code+PKCE, browser/callbacks, the Host credential vault, single-flight refresh, model catalogs, the DSH Provider and streaming authorization isolation. The OIDC identity mode directly reuses the existing strict verifier. Standalone identity-only `oidc` remains supported. Legacy model Key Binding has been removed; see the [migration guide](../key-binding-protocol.en.md).
42
+
43
+ Access tokens stay opaque. Discovered `api_base` and UserInfo must share the resource origin; authentication endpoints only receive their designated credentials, without redirects. Changed configuration/discovery bindings cannot reuse old authorization. Token responses require actual scope, positive integer expiry and a refresh token; grants cannot expand or omit required connection permissions. The server's expiry is honored without enforcing the unagreed 15-minute proposal.
44
+
45
+ Refresh may omit an ID Token. If present, its original issuer, subject, audience, optional auth_time and nonce are checked. Failed validation after rotation revokes the new credentials and requires sign-in instead of continuing with a possibly consumed old refresh token. Temporary Token endpoint failures preserve still-valid authorization.
46
+
47
+ Catalogs retain shared conservative capability mapping: ID-only rows support text without inferred vision or reasoning. Optional draft model capability fields are not consumed yet; use reviewed provider configuration when needed.
48
+
49
+ Institution extensions can use the existing Host-only `modelResourceFetch(profileID, relativePath)`. Legacy connections use their managed model Key; experimental connections use the current authorized Access Token. Both reuse path restrictions, GET, bounded bodies and logout isolation. The public plugin makes no implicit quota request and adds no quota RPC/fields. Extensions must be installed explicitly; the server must define and enforce their authorization.
50
+
51
+ Logout stops local requests before submitting refresh-token revocation; 200 empty and JSON responses are accepted. Remote failure only produces a sanitized warning. A persistent revocation retry queue is not implemented, so that draft proposal is not claimed. Refresh-family revocation, remaining Access Token validity, scope enforcement and model filtering still require real-server acceptance.
52
+
53
+ References: [OIDC refresh response](https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse), [authorization response issuer](https://www.rfc-editor.org/rfc/rfc9207.html).