@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.
Files changed (50) hide show
  1. package/CHANGELOG.en.md +120 -0
  2. package/CHANGELOG.md +120 -0
  3. package/LICENSE +21 -0
  4. package/README.en.md +184 -0
  5. package/README.md +143 -0
  6. package/THIRD_PARTY_NOTICES.en.md +51 -0
  7. package/THIRD_PARTY_NOTICES.md +53 -0
  8. package/cordis.patch.yml +8 -0
  9. package/docs/EDUWORK-MIGRATION.en.md +23 -0
  10. package/docs/EDUWORK-MIGRATION.md +23 -0
  11. package/docs/architecture.en.md +125 -0
  12. package/docs/architecture.md +125 -0
  13. package/docs/compatibility.en.md +61 -0
  14. package/docs/compatibility.md +61 -0
  15. package/docs/decisions/0001-one-plugin-composition-boundary.en.md +34 -0
  16. package/docs/decisions/0001-one-plugin-composition-boundary.md +34 -0
  17. package/docs/dsh-integration.en.md +171 -0
  18. package/docs/dsh-integration.md +171 -0
  19. package/docs/ecnu-reference.en.md +66 -0
  20. package/docs/ecnu-reference.md +66 -0
  21. package/docs/enterprise-profile.en.md +159 -0
  22. package/docs/enterprise-profile.md +159 -0
  23. package/docs/getting-started.en.md +137 -0
  24. package/docs/getting-started.md +239 -0
  25. package/docs/key-binding-protocol.en.md +227 -0
  26. package/docs/key-binding-protocol.md +227 -0
  27. package/docs/oidc-interoperability.en.md +100 -0
  28. package/docs/oidc-interoperability.md +100 -0
  29. package/docs/release-checklist.en.md +60 -0
  30. package/docs/release-checklist.md +60 -0
  31. package/docs/security-model.en.md +138 -0
  32. package/docs/security-model.md +138 -0
  33. package/docs/server-integration-contract.en.md +706 -0
  34. package/docs/server-integration-contract.md +706 -0
  35. package/examples/ecnu.enterprise-profile.example.json +81 -0
  36. package/examples/enterprise-profile.example.json +77 -0
  37. package/lib/client.js +5855 -0
  38. package/lib/index.js +59 -0
  39. package/lib/oidc.js +649 -0
  40. package/lib/profile.js +371 -0
  41. package/lib/provider/core.js +138 -0
  42. package/lib/provider/index.js +156 -0
  43. package/lib/provider/transform-adapter.js +65 -0
  44. package/lib/provider/transforms.js +59 -0
  45. package/lib/typert-schemas.js +84 -0
  46. package/lib/typert.host.js +38 -0
  47. package/lib/typert.remote-client.js +37 -0
  48. package/package.json +120 -0
  49. package/protocol/openapi.yaml +257 -0
  50. package/schema/enterprise-profile.v1alpha1.schema.json +174 -0
@@ -0,0 +1,159 @@
1
+ # Enterprise Profile 规范(`dsh-oidc/v1alpha1`)
2
+
3
+ **简体中文** | [English](enterprise-profile.en.md)
4
+
5
+ ## 状态与符合性
6
+
7
+ 本文规定 `dsh-oidc` `0.1.x` 使用的数据契约。关键词 **必须(MUST)**、**不得(MUST NOT)**、**必需(REQUIRED)**、**应该(SHOULD)**、**不应该(SHOULD NOT)** 和 **可以(MAY)** 按 RFC 2119 与 RFC 8174 解释。
8
+
9
+ 权威机器可读 Schema 为 [`schema/enterprise-profile.v1alpha1.schema.json`](../schema/enterprise-profile.v1alpha1.schema.json)。运行时对若干安全敏感 URL 的校验有意比 JSON Schema 更严格。符合规范的 Profile 必须同时通过 JSON Schema 和 `normalizeEnterpriseProfile()` 校验。
10
+
11
+ ## 信任模型
12
+
13
+ Profile 是受信任的部署配置,不是用户输入。即便如此,解析器仍以失败关闭方式工作:
14
+
15
+ - 根对象和所有涉及可执行行为的嵌套对象都会拒绝未知字段;
16
+ - 序列化后的 Profile 总大小不超过 512 KiB;
17
+ - 生产 URL 必须使用 HTTPS;
18
+ - 只有显式 loopback 主机可以在开发环境使用 HTTP;
19
+ - 拒绝 URL 凭据、fragment 和 endpoint-base query string;
20
+ - 只能选择内置的 `openai-compatible` adapter;
21
+ - Key Binding 只接受协议基址和可选的本地 DSH 凭据引用,不能改写网络路径或字段;
22
+ - logo data URL 只接受 PNG/WebP,不接受 SVG。
23
+
24
+ 远程管理系统只有在宿主能够认证来源、校验完整性并通过审查流程暂存变更时,才可以分发 Profile JSON。下载的 JSON 不会仅仅因为不包含 JavaScript 就自动变得安全。
25
+
26
+ ## 根对象
27
+
28
+ | 字段 | 必需 | 含义 |
29
+ | --- | --- | --- |
30
+ | `schemaVersion` | 是 | 精确字符串 `dsh-oidc/v1alpha1`。 |
31
+ | `id` | 是 | 匹配 `^[a-z][a-z0-9-]{0,63}$` 的 Profile ID。 |
32
+ | `displayName` | 是 | 面向用户的集成名称。 |
33
+ | `organization` | 否 | 机构名称;默认使用 `displayName`。 |
34
+ | `nativeInstitutionID` | 否 | 仅传给 native 后端的标识符;默认使用 `id`。 |
35
+ | `allowInsecureDevelopment` | 否 | 为本地开发启用 loopback HTTP。网络 HTTP 还必须配置 `insecureDevelopmentOrigin`。 |
36
+ | `insecureDevelopmentOrigin` | 否 | 精确的非 TLS 开发 origin。只有与 `allowInsecureDevelopment: true` 一起使用时才有效,且所有 HTTP OIDC/Key Binding/Provider endpoint 必须使用该 origin。不得放入生产 Profile。 |
37
+ | `brand` | 是 | 有边界的展示配置。 |
38
+ | `oidc` | 是 | OIDC public client 配置事实。 |
39
+ | `keyBinding` | 是 | Key Binding 基础 URL,以及可选的本地 DSH 凭据引用。 |
40
+ | `provider` | 是 | 一个本地 OpenAI-compatible Provider 路由和模型列表。 |
41
+
42
+ ## 品牌替换
43
+
44
+ 品牌配置只修改经过批准的展示表面,不改变认证或可执行行为。
45
+
46
+ | 字段 | 限制/行为 |
47
+ | --- | --- |
48
+ | `productName` | 最多 80 字符;用于页面标题和侧栏名称。 |
49
+ | `organizationName` | 最多 120 字符。 |
50
+ | `mark` | 未提供 Logo 时使用的 1–4 个字符。 |
51
+ | `logoURL` | HTTPS URL 或 base64 PNG/WebP;文本最多 128 KiB。远程图片使用 `referrerPolicy=no-referrer`。 |
52
+ | `primaryColor` | 六位十六进制颜色;只覆盖一组受限 DSH 主题 token。 |
53
+ | `loginTitle` | 最多 120 字符。 |
54
+ | `loginDescription` | 最多 500 字符。 |
55
+ | `supportURL` | 使用 `noopener noreferrer` 打开的绝对 HTTPS URL。 |
56
+
57
+ 没有权利人的许可,Profile 不得包含其 Logo 或名称。担心远程图片跟踪的部署方应该打包 base64 PNG/WebP,或在受控 origin 上托管资产并配置严格 CSP。
58
+
59
+ ## OIDC 对象
60
+
61
+ ```json
62
+ {
63
+ "issuer": "https://id.example.edu/oidc",
64
+ "clientId": "dsh-web-public-client",
65
+ "scopes": ["openid", "profile", "offline_access"]
66
+ }
67
+ ```
68
+
69
+ - `issuer` 是 OIDC Issuer Identifier。支持路径,不允许 query 和 fragment;必须与 Discovery 元数据逐字符一致。
70
+ - `clientId` 标识 public client。Profile 和本插件都不应包含 client secret。
71
+ - `scopes` 必须包含 `openid` 和 `profile`;每项内部不得包含空白,且不得重复。
72
+ - 当 Provider 会签发 refresh token 且策略允许时,应该请求 `offline_access`。
73
+ - Key Binding 授权 scope 由部署决定,但建议使用参考示例中的名称。
74
+
75
+ Web 重定向地址固定为 `http://127.0.0.1:<DSH端口>/oauth/callback`。host 和 path 不可配置;端口取 DSH WebServer 的实际监听端口。
76
+
77
+ ## Key Binding 对象
78
+
79
+ ```json
80
+ {
81
+ "baseURL": "https://ai.example.edu/api/worker/v1",
82
+ "credentialRef": "EXAMPLE_AI_TEST_API_KEY"
83
+ }
84
+ ```
85
+
86
+ | 字段 | 必需 | 含义 |
87
+ | --- | --- | --- |
88
+ | `baseURL` | 是 | `worker-user-center-v1` Key Binding 接口组的基址。 |
89
+ | `credentialRef` | 否 | 保存绑定后模型 API Key 的本地 DSH Credential Provider 名称。必须匹配 `^[A-Za-z_][A-Za-z0-9_]*$`,最长 128 字符。 |
90
+
91
+ 协议类型固定为 `worker-user-center-v1`,请求中的 Provider ID 固定取 `provider.id`。如果省略 `credentialRef`,解析器保持向后兼容:将 Provider ID 转为大写、把非字母数字替换为 `_`,再追加 `_API_KEY`。例如 `example-ai` 默认对应 `EXAMPLE_AI_API_KEY`。
92
+
93
+ `credentialRef` 只是本地秘密存储的引用名,不是 API Key,不发送给 OIDC、Key Binding 或模型服务。普通单环境部署应该省略它并使用默认值;同一 Provider ID 在生产和测试部署间复用时,必须为不同环境配置不同引用,例如 `CHATECNU_API_KEY` 与 `CHATECNU_QUOTA_TEST_API_KEY`,避免一个环境读取另一环境的 Key。
94
+
95
+ Endpoint 路径、请求/响应字段和 Provider ID 语义属于协议事实,不得由 Profile 自定义。Native 宿主若返回 `runtimeCredentialRef`,其值必须与本字段规范化后的结果一致,否则插件失败关闭。
96
+
97
+ ## Provider 对象
98
+
99
+ Provider 对象是由本地、经过审查的 adapter 解释的数据。
100
+
101
+ | 字段 | 必需 | 含义 |
102
+ | --- | --- | --- |
103
+ | `id` | 是 | DSH 路由 ID,也是默认凭据引用的来源;在所有已加载 Profile 中必须唯一。 |
104
+ | `displayName` | 否 | 面向用户的 Provider 名称。 |
105
+ | `adapter` | 是 | 精确字符串 `openai-compatible`。 |
106
+ | `baseURL` | 是 | HTTPS OpenAI-compatible API 基址。 |
107
+ | `reasoning` | 否 | 默认 DSH/pi-ai reasoning level,默认为 `high`。 |
108
+ | `defaultContextWindow` | 否 | 正安全整数,默认 262144。 |
109
+ | `defaultMaxTokens` | 否 | 正安全整数,默认 32768。 |
110
+ | `maxRequestImageBytes` | 否 | 单次原生图片请求的累计大小预算。 |
111
+ | `requestImagePixelBudget` | 否 | 图片归一化像素预算。 |
112
+ | `requestImageMaxBytes` | 否 | 单张归一化图片大小预算。 |
113
+ | `streamIdleTimeoutMs` | 否 | 正数空闲超时,默认 300000。 |
114
+ | `retryPolicy` | 否 | 由 DSH Provider 管理的重试策略;默认 normal/重试 2 次。 |
115
+ | `compat` | 否 | 有边界的 pi-ai OpenAI 兼容事实。 |
116
+ | `models` | 是 | 1–128 个唯一模型条目。 |
117
+
118
+ `retryPolicy.mode` 可以是 `normal` 或 `always`。`always` 可能一直重试,直到成功、取消或销毁;没有明确产品决策时不应启用。该策略还会由 DSH 再次校验。
119
+
120
+ `compat` 只接受 JSON Schema 中列出的字段。部署方必须准确描述 Provider 事实;兼容性覆盖可能改变请求语义,但不能执行代码。
121
+
122
+ ## 模型条目
123
+
124
+ 每个模型包含:
125
+
126
+ - 必需的 `id`;
127
+ - 可选显示名称 `name`;
128
+ - `input` 可包含 `text`、`image` 或两者,默认 `text`;
129
+ - 可选的正数 `contextWindow` 和 `maxTokens`;
130
+ - 可选 `reasoning` 布尔值;
131
+ - 可选 `reasoningEfforts` 对象或 `false`;
132
+ - 可选的受限 `compat` 覆盖。
133
+
134
+ 若模型支持 thinking,但不支持选择 reasoning effort,请设置:
135
+
136
+ ```json
137
+ {
138
+ "compat": { "supportsReasoningEffort": false }
139
+ }
140
+ ```
141
+
142
+ Adapter 会保留 Profile 的 thinking 行为,并在校验和传输前移除不支持的用户所选 effort。不会因为 Provider 不支持 effort 参数而拒绝整个请求。
143
+
144
+ ## 禁止写入的秘密
145
+
146
+ Profile 不得包含:
147
+
148
+ - OIDC client secret;
149
+ - access、refresh 或 ID token;
150
+ - 模型 API Key;
151
+ - 私有签名密钥;
152
+ - session cookie;
153
+ - 个人身份数据。
154
+
155
+ OIDC 客户端是 public client。运行时秘密在登录后创建,并且只由当前 DSH Credential Provider 保存。
156
+
157
+ ## 版本管理
158
+
159
+ 未知 `schemaVersion` 会被拒绝。Alpha 阶段字段可能在 `v1alphaN` 之间变化。稳定 `v1` 的 minor 版本只增加可选字段;不兼容变更使用新 Schema 版本。详见[兼容性与发布策略](compatibility.md)。
@@ -0,0 +1,137 @@
1
+ # Getting started: connect enterprise OIDC and models to DSH
2
+
3
+ [简体中文](getting-started.md) | **English**
4
+
5
+ This guide is for operators integrating `dsh-oidc` into their own DeepSeek Harness deployment. The result is a closed loop: users sign in through the organization's OIDC Provider, explicitly bind a revocable model runtime credential, inspect the enterprise Provider and model catalog, and call those models from ordinary DSH Web.
6
+
7
+ `dsh-oidc` is Web-first and does not require Wails or ChatECNU Work. ECNU is only a reference deployment.
8
+
9
+ ## 1. Prerequisites
10
+
11
+ | Component | Minimum requirement |
12
+ | --- | --- |
13
+ | DSH Host | DeepSeek Harness `0.1.2-alpha.2` on Node.js 22+. |
14
+ | Institutional enterprise-model integration service | One accountable owner jointly delivers the OIDC Provider, fixed Key Binding API, and OpenAI-compatible model gateway; all three are required. |
15
+ | Enterprise Profile | Trusted local JSON containing public configuration only. |
16
+ | Credential Provider | The current Web backend supports trusted single-user local storage only. |
17
+
18
+ Have the identity and model-platform teams jointly review the [server API specification](server-integration-contract.en.md). OIDC alone establishes identity; without Key Binding and the model gateway it does not safely complete model authorization. Do not place a shared model API key in an Enterprise Profile.
19
+
20
+ ## 2. Register the OIDC Public Client
21
+
22
+ Register a public client without a client secret. Its redirect URI must exactly equal:
23
+
24
+ ```text
25
+ http://127.0.0.1:3080/oauth/callback
26
+ ```
27
+
28
+ `127.0.0.1` and `/oauth/callback` are fixed. `3080` is the recommended DSH port; if the DSH WebServer port changes, the registered URI must use the same port. Do not register `localhost`, a LAN address, or a public address. Discovery must publish `authorization_endpoint`, `token_endpoint`, `jwks_uri`, and `userinfo_endpoint`. UserInfo must contain a non-empty `sub` matching the ID Token; provide the standard `name` claim for a human-readable label. The current release accepts RS256 ID Tokens only.
29
+
30
+ See the complete [OIDC interoperability profile](oidc-interoperability.en.md).
31
+
32
+ ## 3. Implement the complete institutional service
33
+
34
+ One institutional delivery must provide the OIDC interface group above, the Key Binding interface group below, and an OpenAI-compatible model gateway that accepts the bound credentials. Internal systems may host different interfaces, but versioning, security, and operations have one accountable integration boundary. See the [server API specification](server-integration-contract.en.md).
35
+
36
+ The configured `keyBinding.baseURL` has four fixed operations:
37
+
38
+ ```text
39
+ GET /bootstrap
40
+ POST /runtime-credential/provision
41
+ POST /runtime-credential/resolve
42
+ POST /runtime-credential/renew
43
+ ```
44
+
45
+ The Profile may additionally use optional `keyBinding.credentialRef` to isolate entries in the local DSH Credential Provider. It is not sent over the network and does not alter these operations. Omit it for an ordinary single-environment deployment; use distinct references when production and test reuse the same Provider ID.
46
+
47
+ Every request carries the OIDC Access Token as a Bearer credential. The service must validate issuer, audience, expiry, scopes, subject, and organization authorization. It must not trust `provider_id` by itself. Credential responses are `no-store`, and `api_key` must never enter logs, metrics labels, or error details.
48
+
49
+ Use the normative [Key Binding protocol](key-binding-protocol.en.md) and machine-readable [`protocol/openapi.yaml`](../protocol/openapi.yaml). OIDC and the model gateway use their established interfaces, so this project does not duplicate them in another OpenAPI document.
50
+
51
+ ## 4. Create an Enterprise Profile
52
+
53
+ Copy [`examples/enterprise-profile.example.json`](../examples/enterprise-profile.example.json) and replace the organization, bounded brand values, OIDC public-client facts, Key Binding base URL, OpenAI-compatible Provider base URL, and model catalog. Configure optional `keyBinding.credentialRef` only when local keys must be isolated across environments.
54
+
55
+ The Profile is trusted deployment data, not a secret store. It must not contain client secrets, tokens, API keys, signing keys, cookies, or personal records. See the full [Enterprise Profile specification](enterprise-profile.en.md).
56
+
57
+ ## 5. Install into ordinary DSH Web
58
+
59
+ The package is itself an installable DSH Bundle. Team deployments should install the reviewed, exact version from npm:
60
+
61
+ ```bash
62
+ dsh plugin --profile web add @eduwork/dsh-oidc@0.1.0-alpha.11
63
+ ```
64
+
65
+ For auditing, development, or testing unpublished changes, install from a local checkout instead:
66
+
67
+ ```bash
68
+ git clone https://github.com/freedomkk-qfeng/dsh-oidc.git
69
+ cd dsh-oidc
70
+ npm ci
71
+ npm run check
72
+ dsh plugin --profile web add .
73
+ ```
74
+
75
+ From the parent directory, the equivalent explicit relative-path command is:
76
+
77
+ ```bash
78
+ dsh plugin --profile web add ./dsh-oidc
79
+ ```
80
+
81
+ DSH links the checkout into `$DSH_HOME/profiles/web`; it does not scan or copy the invoking directory. Keep the checkout in place and pin a reviewed commit for source-based deployments. To intentionally follow alpha updates, use:
82
+
83
+ ```bash
84
+ dsh plugin --profile web add @eduwork/dsh-oidc@alpha
85
+ ```
86
+
87
+ Set the trusted local Profile path and start DSH on the fixed loopback host:
88
+
89
+ ```bash
90
+ export DSH_OIDC_ENTERPRISE_PROFILE=/etc/dsh/enterprise-profile.json
91
+ dsh --profile web --host 127.0.0.1 --port 3080 --no-open
92
+ ```
93
+
94
+ The plugin constructs `http://127.0.0.1:<port>/oauth/callback` from the DSH WebServer port. It does not accept `publicBaseURL` and does not infer an address from proxy headers. If the startup port changes, update the OIDC Public Client registration to the same port.
95
+
96
+ The package's `cordis.patch.yml` mounts one `enterprise-oidc` plugin instance. It does not traverse the current workspace or replace DSH Web, sessions, Workspace, LLM services, the Credential Provider, or other official capabilities. The invoking directory matters only when resolving a relative package path such as `.` or `./dsh-oidc`.
97
+
98
+ ## 6. Advanced product composition
99
+
100
+ A product-owned Bundle may mount the plugin explicitly instead of using the default package patch:
101
+
102
+ ```yaml
103
+ - insert:
104
+ - id: enterprise-oidc
105
+ name: '@eduwork/dsh-oidc'
106
+ config:
107
+ profilePathEnv: DSH_OIDC_ENTERPRISE_PROFILE
108
+ web:
109
+ returnPath: /
110
+ ```
111
+
112
+ Desktop products may use `backend: native`, point `profilePathEnv` at the same standard Enterprise Profile used by Web, and supply the capability-detected `enterpriseAccounts` service. Product-owned institution, tenant, or account catalogs MUST be converted to an Enterprise Profile by the product assembly layer before `dsh-oidc` reads them. Wails, Electron, Tauri, and other hosts can implement that boundary; the plugin depends on neither a desktop framework nor a product catalog format. See [DSH integration](dsh-integration.en.md).
113
+
114
+ ## 7. Acceptance checklist
115
+
116
+ Before production, verify at least:
117
+
118
+ 1. `dsh --profile web --dump-config` contains exactly one `enterprise-oidc` instance;
119
+ 2. login and the exact `/oauth/callback` succeed while invalid state and duplicate callback parameters fail;
120
+ 3. the UI displays standard UserInfo `name`, falling back to stable `sub`;
121
+ 4. provisioning requires explicit user confirmation;
122
+ 5. a provisioned model completes a streaming conversation;
123
+ 6. no API key appears in the Profile, browser configuration, logs, or exported sessions;
124
+ 7. refresh, expiry, revocation, authorization denial, rate limiting, and backend outage behavior are understandable;
125
+ 8. logout clears local OIDC and model credentials and attempts standard token revocation;
126
+ 9. the DSH WebServer listens only on `127.0.0.1` and is not exposed as a shared site through a proxy or port mapping;
127
+ 10. OIDC, all four Key Binding operations, and the model gateway jointly pass the [server API specification](server-integration-contract.en.md).
128
+
129
+ The current Web backend does not support shared Web. A shared scenario needs a new host backend with per-user isolation; do not simply relax the loopback restriction. See the [security model](security-model.en.md) and [public release checklist](release-checklist.en.md).
130
+
131
+ ## 8. Troubleshooting
132
+
133
+ - **Callback fails:** compare the registered URI with `http://127.0.0.1:<DSH-port>/oauth/callback` character for character. Confirm the DSH host is `127.0.0.1` and its actual port matches the registration; do not use `localhost`, a LAN address, or a public address.
134
+ - **A subject string is shown instead of a name:** return a non-empty standard `name` from UserInfo under the `profile` scope; private JSON-path mappings are intentionally unsupported.
135
+ - **Login works but models do not:** inspect `/bootstrap`, explicit provisioning state, exact `provider_id` equality, and whether the model gateway accepts the bound key.
136
+ - **Thinking works but selectable effort does not:** set model `compat.supportsReasoningEffort` to `false`; thinking remains enabled while the unsupported effort parameter is removed.
137
+ - **Why the enterprise controls are inside Models:** DSH `0.1.2-alpha.2` exposes the official `settings.models.footer` extension slot. `dsh-oidc` mounts its management UI there and leaves the upstream Models page enabled; no product-private Models-page fork is required.
@@ -0,0 +1,239 @@
1
+ # 接入指南:把企业 OIDC 与模型服务接入 DSH
2
+
3
+ **简体中文** | [English](getting-started.en.md)
4
+
5
+ 这份指南面向准备在自己机构部署 `dsh-oidc` 的管理员和开发者。完成后,用户可以在普通 DSH Web 中:
6
+
7
+ 1. 使用机构 OIDC 登录;
8
+ 2. 明确确认创建或绑定模型运行凭据;
9
+ 3. 在“企业服务”设置页查看账号、Provider 和模型;
10
+ 4. 直接选择企业模型开始对话;
11
+ 5. 刷新登录、检查连接或退出登录,而模型 API Key 不进入浏览器配置。
12
+
13
+ `dsh-oidc` 不要求 Wails,也不要求使用 ChatECNU Work。华东师范大学只是参考实现。
14
+
15
+ ## 1. 先确认你需要准备什么
16
+
17
+ | 组件 | 谁提供 | 最低要求 |
18
+ | --- | --- | --- |
19
+ | DSH Host | 部署方 | DeepSeek Harness `0.1.2-alpha.2`,Node.js 22+。 |
20
+ | 机构企业模型集成服务 | 同一服务所有者 | 共同提供 OIDC Provider、固定 Key Binding 和 OpenAI-compatible 模型网关;三者全部必需。 |
21
+ | Enterprise Profile | 部署方 | 一份受信任的本地 JSON,只存公开配置,不存密钥或个人信息。 |
22
+ | Credential Provider | DSH Host | 当前 Web backend 仅支持可信单用户本机存储。 |
23
+
24
+ 先把[服务端接口规范](server-integration-contract.md)交给身份平台与模型平台共同确认。只有 OIDC 时,插件只能证明“用户是谁”;没有 Key Binding 和模型网关,就无法安全完成“这个用户可以调用哪些企业模型”的闭环。不要把全局模型 API Key 写进 Enterprise Profile。
25
+
26
+ ## 2. 注册 OIDC Public Client
27
+
28
+ 在机构身份平台登记一个不带 Client Secret 的 Public Client。回调地址必须逐字符匹配:
29
+
30
+ ```text
31
+ http://127.0.0.1:3080/oauth/callback
32
+ ```
33
+
34
+ `127.0.0.1` 和 `/oauth/callback` 固定不可配置。`3080` 是推荐的 DSH 端口;若修改 DSH WebServer 端口,OIDC 注册值必须使用相同端口。不要登记 `localhost`、局域网地址或公网地址。
35
+
36
+ OIDC Provider 必须通过 Discovery 发布 `authorization_endpoint`、`token_endpoint`、`jwks_uri` 和 `userinfo_endpoint`,支持 PKCE `S256`,并返回:
37
+
38
+ - ID Token:至少包含正确的 `iss`、`aud`、`sub`、`iat`、`exp` 和 `nonce`;当前版本只支持带 `kid` 的 RS256;
39
+ - UserInfo:必须包含与 ID Token 相同的非空 `sub`;建议提供标准 `name` 作为用户显示名;
40
+ - Access Token:可以是 JWT 或 opaque token,但 Key Binding 服务必须能验证 issuer、audience、有效期、scope 和 subject。
41
+
42
+ 插件只读取标准 `userinfo.name`,缺失时显示 `userinfo.sub`。它不会读取私有人员接口,也不支持 JSON Path 姓名映射。完整兼容要求见 [OIDC 兼容规范](oidc-interoperability.md)。
43
+
44
+ ## 3. 实现完整机构服务端
45
+
46
+ 同一机构服务交付必须完整提供上一节的 OIDC、下面的 Key Binding,以及接受绑定凭据的 OpenAI-compatible 模型网关。它们可以由内部不同系统承载,但必须有统一的版本、安全和运维责任。接口定义见[服务端接口规范](server-integration-contract.md)。
47
+
48
+ Key Binding 不是 OIDC 标准的一部分,而是 `dsh-oidc` 定义的企业模型凭据协议。Enterprise Profile 用 `baseURL` 指定接口组基址,插件固定调用:
49
+
50
+ ```text
51
+ GET {baseURL}/bootstrap
52
+ POST {baseURL}/runtime-credential/provision
53
+ POST {baseURL}/runtime-credential/resolve
54
+ POST {baseURL}/runtime-credential/renew
55
+ ```
56
+
57
+ Profile 还可以用可选的 `keyBinding.credentialRef` 隔离本地 DSH Credential Provider 中的秘密条目。它不参与网络请求,也不改变下列接口;普通单环境部署省略即可。生产/测试复用相同 Provider ID 时,应使用不同的引用名。
58
+
59
+ 每个请求都携带 OIDC Access Token:
60
+
61
+ ```http
62
+ Authorization: Bearer <oidc-access-token>
63
+ ```
64
+
65
+ 服务端必须验证 Access Token 和用户授权,不得只相信请求里的 `provider_id`。`provision` 与 `renew` 还必须支持 `Idempotency-Key`。返回的 `api_key` 只能通过 TLS 响应一次性交给插件,必须带 `Cache-Control: no-store`,不得进入日志、监控标签或错误详情。
66
+
67
+ 规范文本见 [Key Binding 协议](key-binding-protocol.md),可直接交给后端研发的机器可读契约是 [`protocol/openapi.yaml`](../protocol/openapi.yaml)。OIDC 和模型网关分别遵循其既有标准,因此本项目不复制另一份 OpenAPI。
68
+
69
+ ## 4. 创建 Enterprise Profile
70
+
71
+ 复制 [`examples/enterprise-profile.example.json`](../examples/enterprise-profile.example.json),至少替换:
72
+
73
+ - `id`、`displayName`、`organization`;
74
+ - `brand` 中有权使用的名称、颜色和支持地址;
75
+ - `oidc.issuer`、Public `clientId` 与 scopes;
76
+ - `keyBinding.baseURL`;
77
+ - 跨环境需要隔离本地 Key 时,可选配置 `keyBinding.credentialRef`;
78
+ - `provider.id`、OpenAI-compatible `baseURL` 和模型列表。
79
+
80
+ 典型结构如下:
81
+
82
+ ```json
83
+ {
84
+ "schemaVersion": "dsh-oidc/v1alpha1",
85
+ "id": "example-university",
86
+ "displayName": "Example University AI",
87
+ "organization": "Example University",
88
+ "brand": {
89
+ "productName": "Example AI Work",
90
+ "organizationName": "Example University",
91
+ "mark": "E",
92
+ "primaryColor": "#5157af",
93
+ "loginTitle": "使用机构账号登录",
94
+ "loginDescription": "登录后绑定企业模型运行凭据。",
95
+ "supportURL": "https://support.example.edu/ai"
96
+ },
97
+ "oidc": {
98
+ "issuer": "https://id.example.edu/oidc",
99
+ "clientId": "dsh-web-public-client",
100
+ "scopes": ["openid", "profile", "offline_access"]
101
+ },
102
+ "keyBinding": {
103
+ "baseURL": "https://ai.example.edu/api/worker/v1"
104
+ },
105
+ "provider": {
106
+ "id": "example-ai",
107
+ "displayName": "Example AI",
108
+ "adapter": "openai-compatible",
109
+ "baseURL": "https://ai.example.edu/open/api/v1",
110
+ "models": [
111
+ { "id": "example-max", "name": "Example Max", "input": ["text"] },
112
+ { "id": "example-plus", "name": "Example Plus", "input": ["text", "image"] }
113
+ ]
114
+ }
115
+ }
116
+ ```
117
+
118
+ Profile 是部署配置,不是用户输入。它可以声明模型事实和有限品牌 token,但不能加载脚本、CSS、工具、Skill 或远程 Provider adapter。字段全集和安全限制见 [Enterprise Profile 规范](enterprise-profile.md)。
119
+
120
+ ## 5. 安装到普通 DSH Web
121
+
122
+ `dsh-oidc` 本身是可直接安装的 DSH Bundle。团队部署应从 npm 安装经过复核的精确版本:
123
+
124
+ ```bash
125
+ dsh plugin --profile web add @eduwork/dsh-oidc@0.1.0-alpha.11
126
+ ```
127
+
128
+ 需要审计、开发或测试尚未发布的改动时,再从本地 checkout 安装:
129
+
130
+ ```bash
131
+ git clone https://github.com/freedomkk-qfeng/dsh-oidc.git
132
+ cd dsh-oidc
133
+ npm ci
134
+ npm run check
135
+ dsh plugin --profile web add .
136
+ ```
137
+
138
+ 也可以在项目父目录中显式传入相对路径:
139
+
140
+ ```bash
141
+ dsh plugin --profile web add ./dsh-oidc
142
+ ```
143
+
144
+ DSH 会把本地 checkout 链接到 `$DSH_HOME/profiles/web`,不会扫描或复制当前目录;安装完成后不要移动或删除 checkout。源码部署应固定经过审核的 commit。若明确希望持续跟随 alpha 更新,也可以使用:
145
+
146
+ ```bash
147
+ dsh plugin --profile web add @eduwork/dsh-oidc@alpha
148
+ ```
149
+
150
+ 设置 Enterprise Profile 路径,并让 DSH 监听固定 loopback host:
151
+
152
+ ```bash
153
+ export DSH_OIDC_ENTERPRISE_PROFILE=/etc/dsh/enterprise-profile.json
154
+ dsh --profile web --host 127.0.0.1 --port 3080 --no-open
155
+ ```
156
+
157
+ PowerShell 示例:
158
+
159
+ ```powershell
160
+ $env:DSH_OIDC_ENTERPRISE_PROFILE = 'C:\dsh-config\enterprise-profile.json'
161
+ dsh --profile web --host 127.0.0.1 --port 3080 --no-open
162
+ ```
163
+
164
+ 插件会从 DSH WebServer 的端口构造 `http://127.0.0.1:<port>/oauth/callback`,不接受 `publicBaseURL`,也不会从代理 header 推断地址。若启动参数改变端口,必须同步修改 OIDC Public Client 的登记值。
165
+
166
+ 安装命令会把 `dsh-oidc` 作为 Bundle 加到 Profile,并由包内 `cordis.patch.yml` 挂载插件。它不会遍历当前工作目录,也不会替换 DSH 的 Web、会话、Workspace、LLM、Credential Provider 或其他官方能力;当前目录只用于解析 `.`、`./dsh-oidc` 等相对安装路径。
167
+
168
+ ## 6. 高级组合:自定义 Bundle 或桌面宿主
169
+
170
+ 如果产品已有自己的 Bundle,可以不使用包内默认 patch,而是在产品 patch 中显式插入:
171
+
172
+ ```yaml
173
+ - insert:
174
+ - id: enterprise-oidc
175
+ name: '@eduwork/dsh-oidc'
176
+ config:
177
+ profilePathEnv: DSH_OIDC_ENTERPRISE_PROFILE
178
+ web:
179
+ returnPath: /
180
+ ```
181
+
182
+ 桌面宿主可以切换到 native backend:
183
+
184
+ ```yaml
185
+ - insert:
186
+ - id: enterprise-oidc
187
+ name: '@eduwork/dsh-oidc'
188
+ config:
189
+ backend: native
190
+ uiMode: models-only
191
+ profilePathEnv: PRODUCT_ENTERPRISE_PROFILE
192
+ ```
193
+
194
+ native backend 必须提供 `enterpriseAccounts` 服务,并与 Web 一样传入标准 Enterprise Profile。产品自有目录、租户或账号配置必须先由产品装配层转换为该 Profile,不能交给 `dsh-oidc` 解释。宿主可以由 Wails、Electron、Tauri 或其他技术实现;插件本身不依赖任何桌面框架或产品目录格式。接口和能力探测见 [DSH 集成说明](dsh-integration.md)。
195
+
196
+ ## 7. 上线前验收
197
+
198
+ 至少完成以下真实链路:
199
+
200
+ 1. `dsh --profile web --dump-config` 中只有一个 `enterprise-oidc` 实例;
201
+ 2. 登录跳转和 `/oauth/callback` 成功,错误 state、重复参数和过期 code 会失败;
202
+ 3. 设置页显示标准 UserInfo `name`,没有 `name` 时显示稳定 `sub`;
203
+ 4. 未创建运行凭据时先显示用户确认,不自动静默 provision;
204
+ 5. provision/resolve 后企业模型可完成流式对话;
205
+ 6. API Key 不出现在 Profile、浏览器配置、日志和导出的会话中;
206
+ 7. refresh、过期、撤销、权限不足、限流和 Key Binding 不可用均有可理解的失败结果;
207
+ 8. logout 清理本地 OIDC 会话和模型凭据,并尽力调用标准 revocation endpoint;
208
+ 9. DSH WebServer 只监听 `127.0.0.1`,没有通过反向代理或端口映射暴露为共享站点;
209
+ 10. OIDC、全部四个 Key Binding 操作和模型网关按[服务端接口规范](server-integration-contract.md)联合通过验收。
210
+
211
+ 当前 Web backend 不支持共享 Web。需要共享场景时必须由具备每用户隔离的新宿主 backend 承担,不能直接放宽 loopback 约束。详见 [安全模型](security-model.md) 和 [公开发布检查表](release-checklist.md)。
212
+
213
+ ## 8. 常见问题
214
+
215
+ ### 回调失败或跳回后仍未登录
216
+
217
+ 核对 OIDC 注册值是否与 `http://127.0.0.1:<DSH端口>/oauth/callback` 完全一致。确认 DSH 实际监听 host 为 `127.0.0.1`,登记端口与启动端口相同;不要使用 `localhost`、局域网地址或公网地址。
218
+
219
+ ### 页面显示一串 subject,而不是姓名
220
+
221
+ 这是标准降级行为。让 OIDC UserInfo 在 `profile` scope 下返回非空标准 `name`,并保证 `sub` 与 ID Token 完全一致。
222
+
223
+ ### 登录成功但模型不可用
224
+
225
+ 检查 Key Binding 的 `/bootstrap` 状态、用户是否确认 provision、返回的 `provider_id` 是否等于 Enterprise Profile 的 `provider.id`,以及模型网关是否接受绑定后的 API Key。
226
+
227
+ ### 模型支持 thinking,但不支持 reasoning effort
228
+
229
+ 在模型 `compat` 中设置:
230
+
231
+ ```json
232
+ { "supportsReasoningEffort": false }
233
+ ```
234
+
235
+ 插件会保留 thinking 行为并移除不受支持的 effort 参数,不会因此拒绝调用。
236
+
237
+ ### 为什么企业服务是单独的设置页
238
+
239
+ 当前 DSH `0.1.2-alpha.2` 已提供正式的 `settings.models.footer` 插槽。插件直接把企业模型管理能力挂载到官方“模型”页,并保持官方模型页启用,不再依赖产品私有的模型页 fork。