@eduwork/dsh-oidc 0.1.0 → 0.2.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 (58) hide show
  1. package/CHANGELOG.en.md +40 -1
  2. package/CHANGELOG.md +40 -1
  3. package/README.en.md +23 -21
  4. package/README.md +17 -15
  5. package/THIRD_PARTY_NOTICES.en.md +7 -5
  6. package/THIRD_PARTY_NOTICES.md +7 -5
  7. package/cordis.patch.yml +2 -1
  8. package/docs/EDUWORK-MIGRATION.en.md +14 -2
  9. package/docs/EDUWORK-MIGRATION.md +14 -2
  10. package/docs/account-extensions.en.md +40 -0
  11. package/docs/account-extensions.md +42 -0
  12. package/docs/architecture.en.md +9 -9
  13. package/docs/architecture.md +11 -10
  14. package/docs/compatibility.en.md +11 -2
  15. package/docs/compatibility.md +11 -2
  16. package/docs/desktop-host.en.md +69 -0
  17. package/docs/desktop-host.md +79 -0
  18. package/docs/development.en.md +42 -0
  19. package/docs/development.md +42 -0
  20. package/docs/dsh-integration.en.md +23 -7
  21. package/docs/dsh-integration.md +23 -7
  22. package/docs/ecnu-reference.en.md +3 -3
  23. package/docs/ecnu-reference.md +3 -3
  24. package/docs/enterprise-profile.en.md +19 -11
  25. package/docs/enterprise-profile.md +18 -10
  26. package/docs/getting-started.en.md +12 -12
  27. package/docs/getting-started.md +16 -14
  28. package/docs/key-binding-protocol.en.md +2 -2
  29. package/docs/key-binding-protocol.md +2 -2
  30. package/docs/oidc-interoperability.en.md +3 -3
  31. package/docs/oidc-interoperability.md +2 -2
  32. package/docs/public-resource-protocol.en.md +51 -0
  33. package/docs/public-resource-protocol.md +50 -0
  34. package/docs/release-checklist.en.md +4 -2
  35. package/docs/release-checklist.md +4 -2
  36. package/docs/server-integration-contract.en.md +124 -8
  37. package/docs/server-integration-contract.md +136 -8
  38. package/examples/ecnu.enterprise-profile.example.json +2 -2
  39. package/examples/enterprise-profile.example.json +78 -77
  40. package/examples/identity-only.example.json +10 -0
  41. package/examples/resources-discovery.example.json +20 -0
  42. package/lib/callback-page.js +42 -0
  43. package/lib/client.js +935 -295
  44. package/lib/desktop-oidc.js +205 -0
  45. package/lib/index.js +148 -18
  46. package/lib/oidc.js +314 -58
  47. package/lib/profile.js +53 -24
  48. package/lib/provider/core.js +76 -15
  49. package/lib/provider/index.js +33 -20
  50. package/lib/provider/transform-adapter.js +31 -10
  51. package/lib/resources.js +23 -0
  52. package/lib/typert-schemas.js +34 -3
  53. package/lib/typert.host.js +42 -36
  54. package/lib/typert.remote-client.js +41 -35
  55. package/package.json +100 -29
  56. package/protocol/openapi.yaml +2 -2
  57. package/protocol/resources.openapi.yaml +51 -0
  58. package/schema/enterprise-profile.v1alpha1.schema.json +193 -188
@@ -0,0 +1,51 @@
1
+ # EduWork public identity and resource protocol v1
2
+
3
+ [简体中文](public-resource-protocol.md) | **English**
4
+
5
+ The public `@eduwork/dsh-oidc` package owns identity login, managed model keys and model configuration. Quota and heartbeat are institution-only extensions; the public package neither requests nor renders quota. Remote speech, institution activity heartbeats and institution-specific tools belong in separate plugins. Each deployment is a local single-user client; the browser connects to the local Runtime.
6
+
7
+ ## Modes and configuration
8
+
9
+ Identity-only profiles omit both `keyBinding` and `provider`; successful OIDC login completes identity integration. Users configure personal API keys through ordinary DSH model settings. Organization logout leaves those personal credentials alone. See the [identity-only example](../examples/identity-only.example.json).
10
+
11
+ Full integration includes both fields. `keyBinding.type` defaults to `worker-user-center-v1`, while `eduwork-resources-v1` explicitly names the public resource contract. Both use the existing deployed wire paths; current institutions do not need new endpoint URLs. See the [discovery example](../examples/resources-discovery.example.json).
12
+
13
+ Both modes permit other models and personal API keys. Optional `brand` only controls presentation; an empty brand leaves the host product name unchanged.
14
+
15
+ ## Endpoints and credentials
16
+
17
+ | Capability | Endpoint | Authentication |
18
+ | --- | --- | --- |
19
+ | Identity | OIDC Discovery, Authorization Code + PKCE S256, Token, JWKS, UserInfo | OIDC |
20
+ | Credential state | `GET {keyBinding.baseURL}/bootstrap` | OIDC access token |
21
+ | Provision, resolve, renew | `POST {keyBinding.baseURL}/runtime-credential/{provision,resolve,renew}` | OIDC access token |
22
+ | Models | `GET {provider.baseURL}/models` | Managed model API key |
23
+ | Inference | `POST {provider.baseURL}/chat/completions` | Managed model API key |
24
+
25
+ The [Key Binding OpenAPI](../protocol/openapi.yaml) retains existing lifecycle fields and idempotency requirements. The [resource OpenAPI](../protocol/resources.openapi.yaml) defines model discovery. Optional bootstrap `protocol_version` accepts `worker.user-center.v1`, `worker-user-center/v1` or `eduwork-resources/v1`; unknown versions fail closed. Bootstrap never replaces verified OIDC identity, trusted endpoint configuration or executable components. Resource HTTP requests do not follow redirects.
26
+
27
+ ## Model discovery
28
+
29
+ `provider.modelSource` defaults to `profile`, preserving reviewed distribution metadata. With `discovery`, `/models` is read after managed key resolution; initial `models` may be omitted and no fake route is registered before discovery.
30
+
31
+ The response is `{"data":[{"id":"model-a"}]}` with up to 128 unique model IDs. IDs already declared in the trusted Profile retain their capabilities. New IDs are conservatively text-only with reasoning disabled; model names are not capability evidence. Arbitrary URLs, scripts and adapter configuration in remote responses are ignored.
32
+
33
+ Empty or invalid discovery preserves the last usable catalog and returns `models_unavailable`. Successful discovery replaces the provider snapshot for new calls; prepared calls retain their earlier snapshot. After restart the first signed-in status read rediscovers models. Personal provider routes are unaffected.
34
+
35
+ ## Host integration
36
+
37
+ The plugin is `@eduwork/dsh-oidc`, with bundle layer ID `enterprise-oidc`. Use `backend: web` and `uiMode: standard` for the public interface; `external` keeps public RPC while allowing a product-owned UI. `allowEmptyProfiles: true` supports a public client using only personal keys.
38
+
39
+ `EDUWORK_OIDC_PROFILE` names a single profile, an array, or a `{ "profiles": [...] }` file. When absent, `DSH_OIDC_ENTERPRISE_PROFILE` remains a compatible fallback. Explicit `profilePathEnv` names remain supported.
40
+
41
+ Existing `oidcAccounts.configuration/status/begin/reconcile/logout/management` RPCs remain available. New `resources(profileID)` returns `{profileID, modelSource, models, issues}` without secrets. `reconcile` automatically refreshes model metadata. The legacy native backend does not call its former quota method; new `backend: desktop` shares the Web OIDC implementation but uses a temporary loopback callback without requiring a persistent WebServer.
42
+
43
+ Institution plugins may call Host-only `ctx.oidcAccounts.authorizedFetch(profileID, endpoint, init)` to reuse the current OIDC access token, refresh and one 401 retry. It returns a regular `Response`, has no Remote marker, and never exposes tokens to the browser. Allowed origins default to the configured issuer and Key Binding origins. Additional exact HTTPS origins require trusted host `authorizedOrigins` configuration and cannot be set by browser RPC. Concurrent requests share one rotating refresh operation. This is an authenticated transport seam, not a heartbeat or speech business protocol.
44
+
45
+ Host event `oidc/accounts-changed` carries `{profileID, state}`. Both `authenticated` and `connected` mean identity is signed in; identity-only `connected` does not require a model key. `signed_out` means signed out. Authorized requests without a usable identity throw `oidc_login_required`; forbidden origins throw `oidc_authorized_origin_denied`. A second HTTP 401 remains a Response for the caller to handle. Explicit sign-out should not automatically produce an expiry warning.
46
+
47
+ Changed issuer, client ID or resource endpoints prevent stale managed keys from reaching a different deployment. Existing Web identity records can retain identity while reacquiring the managed model key. Personal API keys remain unaffected by organization logout or resource-binding changes.
48
+
49
+
50
+
51
+ Institution account extensions use the [Host transport and account-menu slot contract](account-extensions.en.md). A configured capability string does not activate an extension; install and configure the institution plugin explicitly.
@@ -0,0 +1,50 @@
1
+ # EduWork 公共身份与资源协议 v1
2
+
3
+ **简体中文** | [English](public-resource-protocol.en.md)
4
+
5
+ 本规范属于通用 `@eduwork/dsh-oidc`。身份登录、自动获得模型 Key 和自动配置模型由本包实现;配额由独立机构扩展实现,公版既不请求也不展示配额;远端语音、机构活跃心跳和校内服务由独立插件实现。所有客户端均为单机,Web 只是本机 Runtime 的界面。
6
+
7
+ ## 接入模式
8
+
9
+ - 纯身份接入:只配置 `oidc`,省略 `keyBinding` 与 `provider`。登录成功即完成身份接入。用户继续在 DSH 原生模型设置添加自己的 API Key,退出机构身份不会删除个人凭据。见 [示例](../examples/identity-only.example.json)。
10
+ - 完整资源接入:同时配置 `keyBinding` 与 `provider`。`keyBinding.type` 默认为 `worker-user-center-v1`;也可显式声明 `eduwork-resources-v1`。两者复用同一组已部署的固定路径,不要求现有机构改造端点。见 [自动目录示例](../examples/resources-discovery.example.json)。
11
+
12
+ 这两个模式都允许其他模型与个人 API Key,不能以组织登录覆盖原生模型配置。可选 `brand` 只负责展示,空品牌保持宿主产品名称。
13
+
14
+ ## 路径与认证
15
+
16
+ | 能力 | 路径 | 凭据 |
17
+ | --- | --- | --- |
18
+ | 身份 | OIDC Discovery、Authorization Code + PKCE S256、Token、JWKS、UserInfo | OIDC 标准 |
19
+ | 凭据状态 | `GET {keyBinding.baseURL}/bootstrap` | OIDC Access Token |
20
+ | 申请、读取、续期 Key | `POST {keyBinding.baseURL}/runtime-credential/{provision,resolve,renew}` | OIDC Access Token |
21
+ | 模型目录 | `GET {provider.baseURL}/models` | 获得的模型 API Key |
22
+ | 模型调用 | `POST {provider.baseURL}/chat/completions` | 获得的模型 API Key |
23
+
24
+ 管理面既有字段、幂等语义见 [Key Binding OpenAPI](../protocol/openapi.yaml)。完整扩展见 [资源 OpenAPI](../protocol/resources.openapi.yaml)。Bootstrap 的可选 `protocol_version` 接受 `worker.user-center.v1`、`worker-user-center/v1` 或 `eduwork-resources/v1`;未知版本拒绝。主体身份只来自经验证的 OIDC,Bootstrap 不覆盖用户身份、发行配置、执行模块或服务地址。资源请求不跟随重定向。
25
+
26
+ ## 模型目录
27
+
28
+ `provider.modelSource` 默认 `profile`,使用发行方已审查的模型事实,保证现有部署不受发现接口变化影响。设为 `discovery` 时登录/凭据关联后自动读取 `/models`;`models` 可省略,初始化时等待目录,不注册伪造的模型。
29
+
30
+ `/models` 返回 `{"data":[{"id":"model-a"}]}`,最多 128 个不同 ID。目录内已在 Profile 声明的 ID 沿用其 reasoning、模态和上下文等事实;其他新 ID 仅声明文本与不支持推理档位。不能从模型名猜能力,也不采用服务响应中的执行配置或任意 URL。目录为空、结构错误或暂不可用时保留上一份已可用目录,并单独返回 `models_unavailable`。
31
+
32
+ 成功更新目录会替换同一 Provider 的运行模型快照,新调用使用新目录,已经准备的调用继续使用原快照。重启后首次读取已登录状态会重新发现。个人提供方不受影响。
33
+
34
+ ## 宿主 RPC 与装配
35
+
36
+ 插件名为 `@eduwork/dsh-oidc`,Bundle 层 ID 为 `enterprise-oidc`。`backend: web`、`uiMode: standard` 启用公共界面;`external` 允许产品自绘入口但保留公共 RPC。`allowEmptyProfiles: true` 允许公版先只用个人 Key。
37
+
38
+ 环境变量 `EDUWORK_OIDC_PROFILE` 指向一个 Profile、Profile 数组或 `{ "profiles": [...] }` 文件。未设置时兼容 `DSH_OIDC_ENTERPRISE_PROFILE`;显式 `profilePathEnv` 仍遵循宿主指定名称。
39
+
40
+ `oidcAccounts.configuration/status/begin/reconcile/logout/management` 保持原契约;新增 `resources(profileID)` 返回 `{profileID, modelSource, models, issues}`,不含秘密。`reconcile` 自动同步模型元数据。旧 `native` backend 不调用原配额桥接口,不新增桌面框架依赖;新桌面 `backend: desktop` 与 Web 共享 OIDC 实现,使用临时 loopback 回调而不依赖常驻 WebServer。
41
+
42
+ 专属插件可在 Host 调用 `ctx.oidcAccounts.authorizedFetch(profileID, endpoint, init)`,复用有效 OIDC Access Token、刷新及一次 401 重试,得到普通 `Response`。此方法没有 Remote 标记,不向浏览器暴露 Token;默认只允许该 Profile 的 issuer 和 Key Binding 精确 origin。额外 HTTPS origin 必须通过受信宿主插件配置 `authorizedOrigins` 声明,浏览器不能改变。并发请求共享一次 Refresh Token 轮换,避免心跳与模型配置同时刷新造成失效。该入口提供认证运输能力,不定义心跳或语音业务。
43
+
44
+ Host 事件 `oidc/accounts-changed` 返回 `{profileID, state}`。`authenticated` 与 `connected` 都表示身份已登录,纯身份的 `connected` 不要求模型 Key;`signed_out` 表示已退出。未登录的授权请求抛出 `oidc_login_required`,越界 origin 为 `oidc_authorized_origin_denied`;第二次 HTTP 401 仍返回 Response,由业务方处理。主动退出不应自动弹出过期提示。
45
+
46
+ Issuer、Client ID 或资源端点变化会阻止旧管理凭据流向新环境;旧 Web 登录记录仍可复用身份,但需要重新关联模型 Key。个人 API Key 不受机构注销与资源绑定变化影响。
47
+
48
+
49
+
50
+ 机构账户扩展使用 [Host 授权传输与账户菜单插槽](account-extensions.md)。声明 capability 不会激活扩展;发行方必须显式安装和配置机构插件。
@@ -4,6 +4,8 @@
4
4
 
5
5
  Record the maintainer, date, command-output links, and exceptions in the release pull request or release record. An applicable but incomplete security, compatibility, or supply-chain item blocks public release; the current maintainer records why an item is not applicable. During the initial single-maintainer phase, do not invent a second human approval: independent technical review, CI/focused regression evidence, and an explicit maintainer decision form the release basis.
6
6
 
7
+ This independent stable version is `0.2.0`; the manifest is updated while dev.7 runtime sources and historical frozen artifacts remain unchanged. The repository locks registry-published DSH RC.1 dependencies. npm authorization does not authorize a GitHub push or Release; record actual publication outcomes separately. Local Windows checks must not be reported as completed Linux/Node matrix or institution deployment acceptance.
8
+
7
9
  ## Authority and project metadata
8
10
 
9
11
  - [ ] Repository owner confirmed public release and the MIT license.
@@ -24,7 +26,7 @@ Record the maintainer, date, command-output links, and exceptions in the release
24
26
  ## Security and privacy
25
27
 
26
28
  - [ ] Threat model reviewed for actual topology.
27
- - [ ] Single-user versus multi-user Credential Provider boundary verified.
29
+ - [ ] Single-user Credential Provider, Profile ownership and shared-key-slot boundaries verified.
28
30
  - [ ] OIDC success and negative conformance suite passed against staging.
29
31
  - [ ] Cross-user/tenant Key Binding authorization tests passed.
30
32
  - [ ] API-key expiry, rotation, suspension, and remote revocation behavior tested.
@@ -45,7 +47,7 @@ Record the maintainer, date, command-output links, and exceptions in the release
45
47
  ## Product acceptance
46
48
 
47
49
  - [ ] Plain Web DSH: login, callback, display name/sub fallback, provision, model call, refresh, check, logout.
48
- - [ ] Native desktop: current ChatECNU Work experience unchanged.
50
+ - [ ] Desktop backend: shared Wails/Electron flow, cancellation and model defaults verified; validate any maintained legacy native bridge separately.
49
51
  - [ ] Native multimodal and text-only + image-transform routes tested.
50
52
  - [ ] Unsupported reasoning-effort model still runs with thinking enabled.
51
53
  - [ ] Failure UX tested: Discovery, token, UserInfo, bootstrap, policy denial, model key, expiry, network timeout.
@@ -4,6 +4,8 @@
4
4
 
5
5
  在发布 Pull Request 或发行记录中记录维护者、日期、命令输出链接和例外说明。适用但未完成的安全、兼容或供应链项目会阻止公开发布;不适用项由当前维护者记录原因。项目处于初期单一维护者阶段时,不虚构第二位人工审批,以独立技术复核、CI/专项回归证据和维护者明确决定作为发布依据。
6
6
 
7
+ 本次独立稳定版本为 `0.2.0`;manifest 已更新,dev.7 的运行源码与历史冻结产物不变。仓库锁使用 npm 可安装的 DSH RC.1 基线。npm 发布授权不等于 GitHub 推送或 Release 授权,按实际执行结果记录。本机 Windows 检查不能冒充 Linux/Node 矩阵或机构真实部署验收。
8
+
7
9
  ## 授权与项目元数据
8
10
 
9
11
  - [ ] 仓库所有者确认公开发布和 MIT 许可证。
@@ -24,7 +26,7 @@
24
26
  ## 安全与隐私
25
27
 
26
28
  - [ ] 已按实际部署拓扑审查威胁模型。
27
- - [ ] 单用户/多用户 Credential Provider 边界已验证。
29
+ - [ ] 单机 Credential Provider、Profile 归属与共享 Key 槽隔离边界已验证。
28
30
  - [ ] 已在预发布环境通过 OIDC 成功和反向符合性测试。
29
31
  - [ ] 已通过跨用户/跨 tenant Key Binding 授权测试。
30
32
  - [ ] 已测试 API Key 过期、轮换、停用和远程撤销行为。
@@ -45,7 +47,7 @@
45
47
  ## 产品验收
46
48
 
47
49
  - [ ] 纯 Web DSH:登录、callback、显示 name/sub 降级、provision、模型调用、刷新、检查连接和退出。
48
- - [ ] Native 桌面:现有 ChatECNU Work 体验无回退。
50
+ - [ ] 桌面 backend:Wails/Electron 共用流程、取消和默认模型正确;仍需维护的旧 native 桥单独验证。
49
51
  - [ ] 原生多模态和纯文本 + image-transform 路由均已测试。
50
52
  - [ ] 不支持 reasoning effort 的模型仍能启用 thinking 并正常运行。
51
53
  - [ ] 已测试失败 UX:Discovery、token、UserInfo、bootstrap、策略拒绝、模型 Key、过期和网络超时。
@@ -1,9 +1,27 @@
1
- # dsh-oidc server API specification
1
+ # EduWork identity and resource integration (RFC EW-IDENTITY-1)
2
+
3
+ > Scope update in .20260910.6: quota and activity heartbeat are institution-only extensions. The public package never requests /quota and its resources RPC contains only model metadata. No capability string or configuration flag enables quota in the public package. ECNU retains its existing server wire in a separate package.
4
+
5
+ > Current candidate supports two modes: identity-only OIDC can omit all resource interfaces and use personal API keys. The complete organization-resource mode below adds managed credentials and models; see [Public resource protocol](public-resource-protocol.en.md). The mandatory requirements below apply to complete integration, not identity-only login.
2
6
 
3
7
  [简体中文](server-integration-contract.md) | **English**
4
8
 
5
9
  This document is written for identity-platform, model-platform, and integration-test teams. It defines, in API-reference form, the server capabilities required by `dsh-oidc`.
6
10
 
11
+ Status: implemented client wire specification, not an IETF standard. Source code and HTTP contract tests determine current behavior. Section 10 provides curl examples; section 11 contains **unimplemented optional proposals**. Every client is a local, single-user Runtime; this is not a shared Web-login architecture.
12
+
13
+ | Capability | Required interfaces | Optionality and implemented behavior |
14
+ | --- | --- | --- |
15
+ | Identity | Discovery, Authorization, Token, JWKS, UserInfo | No resource API needed; personal model keys remain available |
16
+ | Managed models | Identity, Bootstrap, Key lifecycle, Chat Completions | Enabled by keyBinding/provider together; provisioning requires user consent |
17
+ | Model discovery | GET /models | Required only for modelSource=discovery |
18
+ | Token refresh/revocation | Refresh grant / Discovery revocation_endpoint | Optional; missing refresh means signing in again after expiry |
19
+ | Remote Profile discovery, remote model-key logout | Section 11 proposals | Not automatically requested by the public client |
20
+
21
+ Optional interfaces still require valid fields when implemented. Quota belongs to the ECNU institution extension and is not a public integration requirement. An identity-only integration must not be represented as complete model integration. Trusted local configuration owns branding, issuer, service origins and model policy. Bootstrap does not overwrite configuration or load executable extensions. No school-specific service is required.
22
+
23
+ Servers must implement the complete fields and constraints in this document and OpenAPI. Client tolerance for some legacy missing values does not waive server security requirements.
24
+
7
25
  “MUST”, “MUST NOT”, “SHOULD”, and “MAY” are normative. The OIDC interfaces follow OpenID Connect and OAuth 2.0; Key Binding is the fixed protocol defined by this project; the model gateway uses an OpenAI Chat Completions-compatible interface.
8
26
 
9
27
  ## 1. Integration scope
@@ -63,19 +81,19 @@ The identity platform MUST register a Public Client without a Client Secret and
63
81
  - the `openid` and `profile` scopes;
64
82
  - `offline_access` and the corresponding authorization policy when Refresh Tokens are required.
65
83
 
66
- The redirect URI MUST be registered exactly as:
84
+ Desktop callbacks use an available random loopback port by default:
67
85
 
68
86
  ```text
69
- http://127.0.0.1:3080/oauth/callback
87
+ http://127.0.0.1:<actual-port>/oauth/callback
70
88
  ```
71
89
 
72
90
  Constraints:
73
91
 
74
92
  - the host is fixed to `127.0.0.1`, not `localhost`, a LAN address, or a public hostname;
75
93
  - the path is fixed to `/oauth/callback`;
76
- - `3080` is the recommended port; if DSH WebServer uses another port, the registered URI MUST use that actual port;
94
+ - Desktop uses an available loopback port chosen for each attempt (RFC 8252). The authorization server MUST support this native-client registration pattern; `3080` below is only an example. `desktop.callbackPort` optionally fixes the port and fails clearly on conflicts. Web validation uses its actual WebServer port;
77
95
  - this is the browser's return address to the user's local DSH process, not an institutional server address;
78
- - the current implementation is a local single-user Web composition and does not support a shared public-Web callback.
96
+ - the current implementation is a local single-user client and does not support a shared public-Web callback.
79
97
 
80
98
  ## 3. API summary
81
99
 
@@ -92,6 +110,9 @@ Constraints:
92
110
  | KEY-03 | `POST` | `{keyBinding.baseURL}/runtime-credential/resolve` | OIDC Bearer Access Token | Yes |
93
111
  | KEY-04 | `POST` | `{keyBinding.baseURL}/runtime-credential/renew` | OIDC Bearer Access Token | Yes |
94
112
  | MODEL-01 | `POST` | `{provider.baseURL}/chat/completions` | Runtime API Key | Yes |
113
+ | MODEL-02 | `GET` | `{provider.baseURL}/models` | Runtime API Key | If `modelSource=discovery` |
114
+
115
+ The loopback callback and local `oidcAccounts.*` RPC methods belong to the client, not the enterprise server.
95
116
 
96
117
  ## 4. OIDC interface group
97
118
 
@@ -427,7 +448,7 @@ Cache-Control: no-store
427
448
 
428
449
  | Field | Required | Description |
429
450
  | --- | --- | --- |
430
- | `protocol_version` | No | When present, MUST be `worker-user-center/v1` |
451
+ | `protocol_version` | Optional for legacy; new servers SHOULD provide it | Accepted aliases with identical wire: `worker.user-center.v1`, `worker-user-center/v1`, `eduwork-resources/v1`; unknown versions are rejected |
431
452
  | `provider.id` | Yes | Exact match with Profile `provider.id` |
432
453
  | `capabilities` | Yes | String array with at most 256 entries |
433
454
  | `runtime_credential.status` | Yes | Current credential state |
@@ -634,7 +655,7 @@ data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"index":0,
634
655
  data: [DONE]
635
656
  ```
636
657
 
637
- For every runtime API key, the server MUST enforce subject binding, Provider scope, credential state, expiry, revocation, and quota. A revoked or rotated old key MUST NOT retain indefinite access.
658
+ For every runtime API key, the server MUST enforce subject binding, Provider scope, credential state, expiry, revocation, and quota policy when enabled. This server-side policy does not require public quota UI or a `/quota` endpoint. A revoked or rotated old key MUST NOT retain indefinite access.
638
659
 
639
660
  #### Error response
640
661
 
@@ -652,7 +673,17 @@ Errors SHOULD interoperate with OpenAI-compatible clients, for example:
652
673
 
653
674
  The gateway SHOULD distinguish at least: `400` invalid request, `401` invalid credential, `403` access denied, `404` unknown model, `429` rate/quota limit, and `5xx` server failure.
654
675
 
655
- The trusted Enterprise Profile declares the model catalog. A gateway does not need to implement `/models` to conform to this specification.
676
+ Static `modelSource=profile` deployments may omit `/models`; `modelSource=discovery` deployments implement MODEL-02. A missing discovery catalog must not be replaced with invented model IDs.
677
+
678
+ ### MODEL-02: Model catalog
679
+
680
+ `GET {provider.baseURL}/models`, authenticated with `Authorization: Bearer <runtime-api-key>`, returns HTTP 200 JSON:
681
+
682
+ ```json
683
+ {"data":[{"id":"example-model","object":"model","owned_by":"example-ai"}]}
684
+ ```
685
+
686
+ `data` contains at most 128 distinct IDs, each 1–256 characters, available to that key. The client consumes IDs only. Profile-reviewed models retain their declared context, input modalities and reasoning effort levels; new IDs default to text input with no reasoning_effort. Additional OpenAI-style fields are ignored. Empty/invalid/unavailable discovery retains the last valid catalog and reports `models_unavailable`. Remote capabilities, arbitrary URLs and executable configuration are not accepted. Accurate advanced capabilities currently belong in the trusted Profile.
656
687
 
657
688
  ## 7. Cross-interface invariants
658
689
 
@@ -704,3 +735,88 @@ A complete integration MUST pass at least these tests:
704
735
  - every failure response has a stable, diagnosable, non-secret error structure.
705
736
 
706
737
  See [Enterprise Profile](enterprise-profile.en.md) for configuration fields, [OIDC interoperability](oidc-interoperability.en.md) for additional OIDC details, and [Key Binding protocol](key-binding-protocol.en.md) for lifecycle and security semantics.
738
+
739
+ ## 10. Refresh, logout and HTTP request examples
740
+
741
+ The client refreshes access tokens within 90 seconds of expiry and coalesces concurrent refreshes. A management `401` refreshes and retries once with the same body and idempotency key. `invalid_grant` clears local identity and only a model key still owned by it; transient failures preserve valid credentials. `status` projects state; `reconcile` checks the server.
742
+
743
+ Normal OIDC/management/resource timeouts are 20 seconds, revocation 10 seconds. Management/resource requests reject redirects. There is no scheduled `Retry-After` retry for management operations; a later user action creates a new idempotency key. Provision must also prevent duplicate active credentials for the same subject/provider.
744
+
745
+ Logout cancels pending desktop login and clears the profile's identity and model snapshot. It deletes a local managed key only while its saved fingerprint still owns that value, preserving another login's key and personal keys. Revocation is best effort when advertised; browser-wide SSO logout and remote model-key revocation are not implemented. The server remains responsible for denying expired, revoked or disabled credentials.
746
+
747
+ The following POSIX shell examples contain placeholders; Windows can use WSL. Requests originate in the user's local Runtime, so browser CORS is not needed. Do not put real secrets into shared logs or tickets. `ACCESS_TOKEN` and `RUNTIME_KEY` have different purposes. All OIDC endpoints must come from verified Discovery.
748
+
749
+ ```bash
750
+ ISSUER='https://id.example.edu/oidc'
751
+ MANAGEMENT='https://ai.example.edu/api/worker/v1'
752
+ MODEL_BASE='https://ai.example.edu/open/api/v1'
753
+ CLIENT_ID='eduwork-public-client'
754
+ REDIRECT_URI='http://127.0.0.1:3080/oauth/callback'
755
+ curl --fail-with-body --max-time 20 "$ISSUER/.well-known/openid-configuration"
756
+ # Copy these URLs from verified Discovery; their paths are not fixed.
757
+ AUTHORIZATION_ENDPOINT='https://id.example.edu/oidc/authorize'
758
+ TOKEN_ENDPOINT='https://id.example.edu/oidc/token'
759
+ JWKS_URI='https://id.example.edu/oidc/jwks'
760
+ USERINFO_ENDPOINT='https://id.example.edu/oidc/userinfo'
761
+ REVOCATION_ENDPOINT='https://id.example.edu/oidc/revoke'
762
+ curl --fail-with-body --max-time 20 "$JWKS_URI"
763
+ ```
764
+
765
+ Authorization must complete in the system browser. The GET below illustrates the request, not a way to bypass institutional login. Generate state, nonce and the verifier with a secure random source; the S256 challenge is BASE64URL(SHA256(verifier)). The original client owns and validates the callback and ID Token.
766
+
767
+ ```bash
768
+ curl --get --max-time 20 "$AUTHORIZATION_ENDPOINT" \
769
+ --data-urlencode 'response_type=code' --data-urlencode "client_id=$CLIENT_ID" \
770
+ --data-urlencode "redirect_uri=$REDIRECT_URI" --data-urlencode 'scope=openid profile offline_access' \
771
+ --data-urlencode "state=$STATE" --data-urlencode "nonce=$NONCE" \
772
+ --data-urlencode "code_challenge=$PKCE_CHALLENGE" --data-urlencode 'code_challenge_method=S256'
773
+ curl --fail-with-body --max-time 20 "$TOKEN_ENDPOINT" \
774
+ --data-urlencode 'grant_type=authorization_code' --data-urlencode "client_id=$CLIENT_ID" \
775
+ --data-urlencode "code=$CODE" --data-urlencode "redirect_uri=$REDIRECT_URI" --data-urlencode "code_verifier=$PKCE_VERIFIER"
776
+ curl --fail-with-body --max-time 20 -H "Authorization: Bearer $ACCESS_TOKEN" "$USERINFO_ENDPOINT"
777
+ curl --fail-with-body --max-time 20 -H "Authorization: Bearer $ACCESS_TOKEN" "$MANAGEMENT/bootstrap"
778
+ ```
779
+
780
+ Perform only the lifecycle operation selected by Bootstrap. Provision needs user consent. Reuse one idempotency key for retries of a logical write, with a separate key for Renew. `api_key_id` is the string identifier returned by the server. Supply the actual resource scopes described in section 5.1 when the deployment requires them.
781
+
782
+ ```bash
783
+ curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/provision" \
784
+ -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
785
+ -H "Idempotency-Key: $IDEMPOTENCY_KEY" --data '{"provider_id":"example-ai"}'
786
+ curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/resolve" \
787
+ -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
788
+ --data '{"provider_id":"example-ai","api_key_id":"key-123"}'
789
+ curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/renew" \
790
+ -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
791
+ -H "Idempotency-Key: $RENEW_IDEMPOTENCY_KEY" --data '{"provider_id":"example-ai","api_key_id":"key-123"}'
792
+ curl --fail-with-body --max-time 20 -H "Authorization: Bearer $RUNTIME_KEY" "$MODEL_BASE/models"
793
+ curl --no-buffer --fail-with-body "$MODEL_BASE/chat/completions" \
794
+ -H "Authorization: Bearer $RUNTIME_KEY" -H 'Content-Type: application/json' \
795
+ --data '{"model":"example-model","messages":[{"role":"user","content":"Reply with OK"}],"stream":true}'
796
+ curl --fail-with-body --max-time 20 "$TOKEN_ENDPOINT" \
797
+ --data-urlencode 'grant_type=refresh_token' --data-urlencode "client_id=$CLIENT_ID" --data-urlencode "refresh_token=$REFRESH_TOKEN"
798
+ curl --fail-with-body --max-time 10 "$REVOCATION_ENDPOINT" \
799
+ --data-urlencode "client_id=$CLIENT_ID" --data-urlencode "token=$REFRESH_TOKEN"
800
+ ```
801
+
802
+ Skip revocation when Discovery omits the endpoint. The OIDC paths shown are examples, while management suffixes, `/models` and `/chat/completions` are fixed. A code from another browser attempt cannot be exchanged without that attempt's verifier. Validate every ID Token requirement in section 4; base64 decoding alone proves nothing.
803
+
804
+ ## 11. Optional extension proposals — not enabled or called by this release
805
+
806
+ ### 11.1 Remote profile discovery
807
+
808
+ A future `GET /.well-known/eduwork-configuration` could declare `protocol_version: "eduwork-discovery/v1alpha1"`, an OIDC issuer and resource capabilities. The current client reads its trusted local configuration and does not request this endpoint. Any later import must require explicit trust before adding origins, issuer or permissions; a discovery response must never install code, plugins or arbitrary commands. The existing OIDC Discovery endpoint remains implemented and required for identity; it is a different mechanism.
809
+
810
+ ### 11.2 Institution activity heartbeat
811
+
812
+ Heartbeat is outside this public protocol. Institution plugins own its private wire and explicit enablement; it is never a login, model, or token-refresh requirement. The public package does not schedule telemetry.
813
+
814
+ ### 11.3 Other separate future capabilities
815
+
816
+ RP-initiated/global SSO logout, server model-key revocation, dynamic OIDC client registration and remotely advertised advanced model capabilities are not implemented by this release. Omission of these capabilities must not break basic identity or manually configured personal model access.
817
+
818
+ ## 12. Versioning and primary references
819
+
820
+ Profile schema (`v1alpha1`), resource wire (`v1`), npm package version and DSH runtime version are independent. The accepted Bootstrap aliases `worker.user-center.v1`, `worker-user-center/v1`, and `eduwork-resources/v1` describe the same deployed wire. Missing version is accepted for existing v1 servers; unknown explicit versions fail with a resource-connection error and do not invalidate an already verified identity. New servers SHOULD return an explicit supported version. Additive optional fields may be ignored; removing/renaming fields or changing credentials/scopes requires a new major wire version and reviewed client support. Institution quotas are not part of the public schema.
821
+
822
+ Primary standards: [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html), [OAuth for Native Apps / RFC 8252](https://www.rfc-editor.org/rfc/rfc8252), [OAuth Security Best Current Practice / RFC 9700](https://www.rfc-editor.org/rfc/rfc9700). This resource protocol complements OIDC; it does not redefine identity validation or infer a model credential from an ID Token.
@@ -1,9 +1,27 @@
1
- # dsh-oidc 服务端接口规范
1
+ # EduWork 身份与资源接入规范(RFC EW-IDENTITY-1)
2
+
3
+ > .20260910.6 范围调整:配额与活跃心跳属于机构专属扩展。公版不请求 /quota,resources RPC 只返回模型元数据;任何 capabilities 字符串或公版配置开关都不会启用配额。ECNU 在独立机构包中沿用原配额服务协议。
4
+
5
+ > 当前候选支持两种模式:纯身份 OIDC 可省略全部资源接口,用户自配个人 Key;下文完整机构资源模式提供托管凭据与模型,见 [公共资源协议](public-resource-protocol.md)。下文的完整交付要求不适用于只做身份接入的场景。
2
6
 
3
7
  **简体中文** | [English](server-integration-contract.en.md)
4
8
 
5
9
  本文面向身份平台、模型平台和联调测试人员,以接口说明的形式定义接入 `dsh-oidc` 时服务端必须提供的能力。
6
10
 
11
+ 状态:现有客户端 wire 规范;不是 IETF 标准。本文以 `@eduwork/dsh-oidc` 源码及 HTTP 契约测试为准。第 10 节列出需填入本次授权结果的 curl 请求示例;第 11 节是**尚未实现的可选扩展提案**。所有客户端都是用户本机的单用户 Runtime,不设计共享 Web 服务端登录会话。
12
+
13
+ | 能力档 | 最小交付 | 可选性与当前行为 |
14
+ | --- | --- | --- |
15
+ | 身份接入(已实现) | Discovery、Authorization、Token、JWKS、UserInfo | 不需要模型接口;用户可用个人 Key |
16
+ | 托管模型(已实现) | 身份接入、Bootstrap、Key 生命周期、Chat Completions | 由 `keyBinding` 与 `provider` 一起启用;创建需用户确认 |
17
+ | 模型发现(已实现) | `GET /models` | `modelSource=discovery` 时需要;静态 Profile 可省略 |
18
+ | Token 刷新/撤销(已实现) | Token refresh grant / Discovery `revocation_endpoint` | Refresh Token、撤销端点可选;缺少刷新能力时到期重新登录 |
19
+ | 远程机构 Profile 发现、远端模型 Key 注销(提案) | 见第 11 节 | 当前公共客户端不会因文档或 capabilities 字符串自动调用 |
20
+
21
+ 实现中的“可选”表示该能力缺失不会让其他能力失效,不意味着服务端可以返回错误字段。独立接口能力应独立验收,不能将只通过 OIDC 登录描述成已完成模型接入。Profile 的机构名称、品牌、issuer、端点和模型策略来自用户或发行方的可信配置文件;Bootstrap 不能覆盖这些配置。此规范不要求任何学校专有服务。
22
+
23
+ 服务端应按本文与 OpenAPI 完整输出字段和约束;客户端为兼容既有部署接受部分旧版缺省值,不代表服务端可省略安全校验。
24
+
7
25
  关键词“必须”“不得”“应该”“可以”具有规范性含义。OIDC 接口遵循 OpenID Connect 与 OAuth 2.0 标准;Key Binding 是本项目定义的固定协议;模型网关使用 OpenAI Chat Completions 兼容接口。
8
26
 
9
27
  ## 1. 接入范围
@@ -63,17 +81,19 @@ Enterprise Profile 分别声明三个入口:
63
81
  - `openid`、`profile` scope;
64
82
  - 如需 Refresh Token,再启用 `offline_access` 及相应授权策略。
65
83
 
66
- 回调地址必须精确登记为:
84
+ 桌面版默认使用随机可用端口,回调地址形式为:
67
85
 
68
86
  ```text
69
- http://127.0.0.1:3080/oauth/callback
87
+ http://127.0.0.1:<本次实际端口>/oauth/callback
70
88
  ```
71
89
 
72
90
  约束如下:
73
91
 
74
92
  - host 固定为 `127.0.0.1`,不得改成 `localhost`、局域网 IP 或公网域名;
75
93
  - path 固定为 `/oauth/callback`;
76
- - `3080` 是推荐端口;如果 DSH WebServer 使用其他端口,登记地址必须同步使用实际端口;
94
+ - 原生桌面 Public Client 应按 RFC 8252 接受 loopback 动态端口;本客户端监听 IPv4 `127.0.0.1`;
95
+ - 只支持精确 URI 注册的 IdP 可由发行方固定 `desktop.callbackPort`,端口冲突会明确失败;下文 `3080` 仅作为示例;
96
+ - 本机 Web 验证组合使用其 WebServer 端口,仍需登记对应 URI;
77
97
  - 这是浏览器返回用户本机 DSH 的地址,不是机构服务端地址;
78
98
  - 当前实现是本机单用户 Web 组合,不支持把回调部署成共享公网 Web 会话。
79
99
 
@@ -92,6 +112,9 @@ http://127.0.0.1:3080/oauth/callback
92
112
  | KEY-03 | `POST` | `{keyBinding.baseURL}/runtime-credential/resolve` | OIDC Bearer Access Token | 是 |
93
113
  | KEY-04 | `POST` | `{keyBinding.baseURL}/runtime-credential/renew` | OIDC Bearer Access Token | 是 |
94
114
  | MODEL-01 | `POST` | `{provider.baseURL}/chat/completions` | 运行时 API Key | 是 |
115
+ | MODEL-02 | `GET` | `{provider.baseURL}/models` | 运行时 API Key | 自动模型目录时必须 |
116
+
117
+ 以上路径均是远端服务路径。`/oauth/callback` 是客户端本机地址,`oidcAccounts.*` 是本机 Host RPC,不应作为服务端 REST API 实现。
95
118
 
96
119
  ## 4. OIDC 接口组
97
120
 
@@ -427,7 +450,7 @@ Cache-Control: no-store
427
450
 
428
451
  | 字段 | 必需 | 说明 |
429
452
  | --- | --- | --- |
430
- | `protocol_version` | 否 | 提供时必须为 `worker-user-center/v1` |
453
+ | `protocol_version` | 否(新服务应提供) | 接受 `worker.user-center.v1`、`worker-user-center/v1`、`eduwork-resources/v1`;三个名称采用本文相同 wire;未知版本拒绝 |
431
454
  | `provider.id` | 是 | 必须与 Profile `provider.id` 完全一致 |
432
455
  | `capabilities` | 是 | 字符串数组,最多 256 项 |
433
456
  | `runtime_credential.status` | 是 | 当前凭据状态 |
@@ -634,7 +657,7 @@ data: {"id":"chatcmpl-1","object":"chat.completion.chunk","choices":[{"index":0,
634
657
  data: [DONE]
635
658
  ```
636
659
 
637
- 服务端必须按运行 API Key 检查 subject 绑定、Provider scope、凭据状态、过期时间、撤销状态和配额。已经撤销或轮换的旧 Key 不得无限期继续访问。
660
+ 服务端必须按运行 API Key 检查 subject 绑定、Provider scope、凭据状态、过期时间、撤销状态和配额策略(如部署启用);该服务端策略不要求提供公版配额 UI 或 `/quota` 接口。已经撤销或轮换的旧 Key 不得无限期继续访问。
638
661
 
639
662
  #### 错误响应
640
663
 
@@ -652,7 +675,26 @@ data: [DONE]
652
675
 
653
676
  至少应正确区分:`400` 请求错误、`401` 凭据无效、`403` 无权访问、`404` 模型不存在、`429` 限流或配额不足、`5xx` 服务端错误。
654
677
 
655
- 模型目录由受信任的 Enterprise Profile 声明;模型网关不需要实现 `/models` 才能符合本规范。
678
+ 模型目录可以由可信 Enterprise Profile 声明,也可以通过以下接口读取。静态 `modelSource=profile` 不要求 `/models`;自动发现不能以硬编码的虚假模型作为备用。
679
+
680
+ ### MODEL-02:模型目录
681
+
682
+ ```http
683
+ GET /open/api/v1/models HTTP/1.1
684
+ Host: ai.example.edu
685
+ Authorization: Bearer <runtime-api-key>
686
+ Accept: application/json
687
+ ```
688
+
689
+ ```http
690
+ HTTP/1.1 200 OK
691
+ Content-Type: application/json
692
+ Cache-Control: no-store
693
+
694
+ {"data":[{"id":"example-model","object":"model","owned_by":"example-ai"}]}
695
+ ```
696
+
697
+ `data` 是最多 128 个不同模型的数组;每项 `id` 为 1–256 字符字符串,服务器必须返回当前 Key 可用的公开模型 ID。当前客户端只消费 ID:已在 Profile 声明的模型沿用其上下文、输入模态和 reasoning 档位;新 ID 默认文本输入、不声明 reasoning_effort。OpenAI 常见 `object`、`owned_by` 字段可以返回,但不能借此下发执行代码、任意 endpoint 或未经审核的能力。空/无效目录或查询失败保留上一份有效目录,记录 `models_unavailable`。如需准确多模态/上下文/推理能力,当前版本在可信 Profile 中配置;远程能力描述是后续协议演进,不应冒充已支持。
656
698
 
657
699
  ## 7. 三组接口的一致性要求
658
700
 
@@ -684,7 +726,13 @@ OIDC -> 127.0.0.1:<port>/oauth/callback
684
726
  客户端 -> MODEL-01 Chat Completions
685
727
  ```
686
728
 
687
- 客户端在 Access Token 即将过期时使用 OIDC-03 的 Refresh Token grant。退出时清除本地 OIDC 会话和运行凭据,并在可用时调用 OIDC-06。
729
+ 客户端在 Access Token 即将过期时使用 OIDC-03 的 Refresh Token grant。退出时清除本地 OIDC 会话及仍归该会话所有的运行凭据,并在可用时调用 OIDC-06。
730
+
731
+ 当前实现细节:管理请求在 Access Token 距到期不足 90 秒时尝试刷新,同一 Profile 并发刷新合并;管理面收到 401 时可刷新后重试**一次**,保留同一次请求的 body 与 Idempotency-Key。没有 Refresh Token、或刷新返回 `invalid_grant` 时清除该机构本地身份及仍归其所有的 Key,要求重新登录;429/5xx/断网不会清除有效身份。`status()` 是安全状态投影,不代表每次都会刷新 Token;“刷新账户”通过 `reconcile()` 进行有效性检查。
732
+
733
+ 通用 Host 授权请求使用已批准的 origin,管理及模型资源请求不跟随重定向。单次普通 OIDC/管理/资源请求超时 20 秒,注销撤销请求 10 秒。当前客户端不会自动按 Retry-After 排队重试 Bootstrap、Key 操作或模型目录;429/503 应给出该 Header,供操作者重试和后续调度器使用。客户端只在本次请求的 401 重试中复用幂等 Key;用户重新点击会生成新 Key,服务端还必须对同 subject/provider 的 Provision 保证不会无限创建重复凭据。
734
+
735
+ “退出登录”是该应用的本地注销:清除该机构 OIDC session 和托管模型 Key 快照;只有指纹仍匹配的本地 Key 才会被删除,其他会话已写入公共槽的 Key 和个人 API Key 都保留。发现撤销端点时尽力撤销 Refresh Token(没有则 Access Token),失败不阻止本地注销。它不等同于注销 IdP 浏览器 SSO、注销其他设备,也不保证远程模型 Key 已撤销;这些边界必须向服务器实现人员说明。服务器应在用户停用、授权撤销、到期时自行阻止旧 Key。
688
736
 
689
737
  ## 9. 联调验收清单
690
738
 
@@ -704,3 +752,83 @@ OIDC -> 127.0.0.1:<port>/oauth/callback
704
752
  - 所有失败响应均有稳定、可诊断且不泄密的错误结构。
705
753
 
706
754
  Enterprise Profile 的完整字段见 [Enterprise Profile 规范](enterprise-profile.md),OIDC 细节见 [OIDC 互操作规范](oidc-interoperability.md),Key Binding 的生命周期与安全语义见 [Key Binding 协议](key-binding-protocol.md)。
755
+
756
+ ## 10. curl 联调示例(现有接口)
757
+
758
+ 以下为 POSIX shell 示例,Windows 可使用 WSL。域名、client ID 和变量均是占位值;不要将真实 Token 写入工单或提交脚本。请求在运行应用的主机发起,不需要浏览器跨域 CORS 授权。`ACCESS_TOKEN` 与 `RUNTIME_KEY` 必须属于不同用途的凭据。
759
+
760
+ ```bash
761
+ ISSUER='https://id.example.edu/oidc'
762
+ MANAGEMENT='https://ai.example.edu/api/worker/v1'
763
+ MODEL_BASE='https://ai.example.edu/open/api/v1'
764
+ CLIENT_ID='eduwork-public-client'
765
+ REDIRECT_URI='http://127.0.0.1:3080/oauth/callback'
766
+ curl --fail-with-body --max-time 20 "$ISSUER/.well-known/openid-configuration"
767
+ # Copy these URLs from verified Discovery; their paths are not fixed.
768
+ AUTHORIZATION_ENDPOINT='https://id.example.edu/oidc/authorize'
769
+ TOKEN_ENDPOINT='https://id.example.edu/oidc/token'
770
+ JWKS_URI='https://id.example.edu/oidc/jwks'
771
+ USERINFO_ENDPOINT='https://id.example.edu/oidc/userinfo'
772
+ REVOCATION_ENDPOINT='https://id.example.edu/oidc/revoke'
773
+ curl --fail-with-body --max-time 20 "$JWKS_URI"
774
+ ```
775
+
776
+ Authorization URL 必须在系统浏览器中打开,不能用 curl 绕过机构登录。下方 Authorization GET 是联调请求示例,需要在浏览器完成机构交互,不是绕过登录的完整脚本;`PKCE_CHALLENGE` 必须是 `BASE64URL(SHA256(PKCE_VERIFIER))`,不是任意占位字符串。`STATE/NONCE/PKCE_VERIFIER` 由客户端使用安全随机源生成,并与本次短时监听绑定。
777
+
778
+ ```bash
779
+ curl --get --max-time 20 "$AUTHORIZATION_ENDPOINT" \
780
+ --data-urlencode 'response_type=code' --data-urlencode "client_id=$CLIENT_ID" \
781
+ --data-urlencode "redirect_uri=$REDIRECT_URI" --data-urlencode 'scope=openid profile offline_access' \
782
+ --data-urlencode "state=$STATE" --data-urlencode "nonce=$NONCE" \
783
+ --data-urlencode "code_challenge=$PKCE_CHALLENGE" --data-urlencode 'code_challenge_method=S256'
784
+ curl --fail-with-body --max-time 20 "$TOKEN_ENDPOINT" \
785
+ --data-urlencode 'grant_type=authorization_code' --data-urlencode "client_id=$CLIENT_ID" \
786
+ --data-urlencode "code=$CODE" --data-urlencode "redirect_uri=$REDIRECT_URI" --data-urlencode "code_verifier=$PKCE_VERIFIER"
787
+ curl --fail-with-body --max-time 20 -H "Authorization: Bearer $ACCESS_TOKEN" "$USERINFO_ENDPOINT"
788
+ curl --fail-with-body --max-time 20 -H "Authorization: Bearer $ACCESS_TOKEN" "$MANAGEMENT/bootstrap"
789
+ ```
790
+
791
+ 只有用户已同意创建时执行 Provision。重试同一逻辑写操作时重用相同的 `IDEMPOTENCY_KEY`;Renew 的示例 Key 与 Provision 应分别生成。
792
+
793
+ ```bash
794
+ curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/provision" \
795
+ -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
796
+ -H "Idempotency-Key: $IDEMPOTENCY_KEY" --data '{"provider_id":"example-ai"}'
797
+ curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/resolve" \
798
+ -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
799
+ --data '{"provider_id":"example-ai","api_key_id":"key-123"}'
800
+ curl --fail-with-body --max-time 20 "$MANAGEMENT/runtime-credential/renew" \
801
+ -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
802
+ -H "Idempotency-Key: $RENEW_IDEMPOTENCY_KEY" --data '{"provider_id":"example-ai","api_key_id":"key-123"}'
803
+ curl --fail-with-body --max-time 20 -H "Authorization: Bearer $RUNTIME_KEY" "$MODEL_BASE/models"
804
+ curl --no-buffer --fail-with-body "$MODEL_BASE/chat/completions" \
805
+ -H "Authorization: Bearer $RUNTIME_KEY" -H 'Content-Type: application/json' \
806
+ --data '{"model":"example-model","messages":[{"role":"user","content":"Reply with OK"}],"stream":true}'
807
+ curl --fail-with-body --max-time 20 "$TOKEN_ENDPOINT" \
808
+ --data-urlencode 'grant_type=refresh_token' --data-urlencode "client_id=$CLIENT_ID" --data-urlencode "refresh_token=$REFRESH_TOKEN"
809
+ curl --fail-with-body --max-time 10 "$REVOCATION_ENDPOINT" \
810
+ --data-urlencode "client_id=$CLIENT_ID" --data-urlencode "token=$REFRESH_TOKEN"
811
+ ```
812
+
813
+ 所有 OIDC endpoint URL 均取自已验证的 Discovery,示例路径不是强制约定;未声明撤销端点时跳过最后一步。Scope 可按第 5.1 节增补服务端已部署的授权名称。这里只展示请求格式;Bootstrap 决定执行 Provision、Resolve、Renew 中哪一个,不应依次创建、读取和续期。Authorization Code 交换由真正发起授权的客户端执行,不能从他人浏览器复制 code 到另一个缺少原 verifier 的进程。ID Token 必须进行第 4 节全部校验,不能仅 base64 解码后相信其中的 subject。
814
+
815
+ ## 11. 可选扩展提案(未实现、不会自动启用)
816
+
817
+ ### 11.1 机构资源发现
818
+
819
+ 当前公版只读取本机可信 JSONC/Profile;**不会请求** `/.well-known/eduwork-configuration`。后续可约定此 HTTPS 入口返回纯数据 `protocol_version: eduwork-discovery/v1alpha1`,包含机构展示、OIDC Public Client、资源基址和能力名称。必须由用户/发行方明确导入,核对 issuer/provider/origin 后转换成本机配置;不得自动更换凭据目标,不得下发插件、脚本、CSS、可执行命令或任意模型 adapter。具体字段、签名/信任根、版本协商另行评审,不能把该提案当作本轮服务器必做接口。
820
+
821
+ ### 11.2 机构活跃心跳
822
+
823
+ 心跳不属于公共协议。机构插件自行管理其私有 wire 和显式启用配置,不能作为登录、模型或 Token 刷新的前置条件。公版不调度遥测请求。
824
+
825
+ ### 11.3 远端注销与模型能力扩展
826
+
827
+ RP-Initiated Logout、远端模型 Key 撤销、动态客户端注册、跨设备会话管理与远程模型能力声明不在当前客户端 wire 内。不要自行创建同名“已支持”端点,或要求现有用户必须实现它们。未来加入时需要独立 capability/version、授权范围、兼容测试与数据保留规则。
828
+
829
+ ## 12. 规范依据与版本规则
830
+
831
+ - [OIDC Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) 定义身份、Token 与 UserInfo;本实现的 RS256/UserInfo 要求是互操作子集,不能推断支持所有 OIDC 选项。
832
+ - [RFC 8252](https://www.rfc-editor.org/rfc/rfc8252) 定义原生应用外部浏览器与 loopback 回调;[RFC 9700](https://www.rfc-editor.org/rfc/rfc9700) 提供 OAuth 安全实践。
833
+ - `dsh-oidc/v1alpha1` 是本机 Profile schema;`worker.user-center.v1` / `worker-user-center/v1` / `eduwork-resources/v1` 是相同资源 wire 的兼容标识;npm 插件版本和 DSH 宿主版本各自独立。心跳草案名称不是对已发布资源 wire 的自动升级。
834
+ - 新增可选字段应保持旧客户端可忽略;不得重解释现有字段或把数量 null 改成 0。破坏性变更须新版本标识及迁移方案,未知 `protocol_version` 的拒绝不能伪装为身份登录失败。
@@ -4,7 +4,7 @@
4
4
  "displayName": "华东师范大学 ChatECNU",
5
5
  "organization": "华东师范大学",
6
6
  "brand": {
7
- "productName": "ChatECNU Work",
7
+ "productName": "EduWork@ECNU",
8
8
  "organizationName": "华东师范大学",
9
9
  "mark": "华",
10
10
  "primaryColor": "#9f2636",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "keyBinding": {
29
29
  "baseURL": "https://worker.example.edu/api/worker/v1",
30
- "credentialRef": "CHATECNU_API_KEY"
30
+ "credentialRef": "EDUWORK_API_KEY"
31
31
  },
32
32
  "provider": {
33
33
  "id": "chatecnu",