@eduwork/dsh-oidc 0.2.4 → 0.3.0-dev.2

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 (72) hide show
  1. package/CHANGELOG.en.md +6 -0
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +20 -132
  4. package/README_EN.md +20 -177
  5. package/docs/EDUWORK-MIGRATION.en.md +3 -25
  6. package/docs/EDUWORK-MIGRATION.md +3 -25
  7. package/docs/account-extensions.en.md +6 -6
  8. package/docs/account-extensions.md +3 -3
  9. package/docs/architecture.en.md +12 -117
  10. package/docs/architecture.md +12 -118
  11. package/docs/compatibility.en.md +6 -40
  12. package/docs/compatibility.md +6 -40
  13. package/docs/decisions/0001-one-plugin-composition-boundary.en.md +2 -2
  14. package/docs/decisions/0001-one-plugin-composition-boundary.md +2 -2
  15. package/docs/desktop-host.en.md +4 -5
  16. package/docs/desktop-host.md +8 -9
  17. package/docs/development.en.md +2 -2
  18. package/docs/development.md +2 -2
  19. package/docs/dsh-integration.en.md +11 -46
  20. package/docs/dsh-integration.md +12 -47
  21. package/docs/ecnu-reference.en.md +4 -61
  22. package/docs/ecnu-reference.md +4 -61
  23. package/docs/enterprise-profile.en.md +11 -32
  24. package/docs/enterprise-profile.md +12 -33
  25. package/docs/gateway-auth/README.md +168 -0
  26. package/docs/gateway-auth/README_EN.md +70 -0
  27. package/docs/gateway-auth/experimental-oidc-llm.en.md +53 -0
  28. package/docs/gateway-auth/experimental-oidc-llm.md +53 -0
  29. package/docs/gateway-auth/litellm-setup.en.md +114 -0
  30. package/docs/gateway-auth/litellm-setup.md +114 -0
  31. package/docs/gateway-auth/oidc-llm-draft.en.md +141 -0
  32. package/docs/gateway-auth/oidc-llm-draft.md +141 -0
  33. package/docs/getting-started.en.md +17 -122
  34. package/docs/getting-started.md +16 -225
  35. package/docs/key-binding-protocol.en.md +10 -220
  36. package/docs/key-binding-protocol.md +10 -220
  37. package/docs/oidc-interoperability.en.md +1 -1
  38. package/docs/oidc-interoperability.md +1 -1
  39. package/docs/open-integration.en.md +5 -67
  40. package/docs/open-integration.md +5 -67
  41. package/docs/public-resource-protocol.en.md +8 -44
  42. package/docs/public-resource-protocol.md +8 -43
  43. package/docs/security-model.en.md +10 -132
  44. package/docs/security-model.md +10 -132
  45. package/docs/server-integration-contract.en.md +12 -813
  46. package/docs/server-integration-contract.md +12 -825
  47. package/examples/ecnu.enterprise-profile.example.json +16 -20
  48. package/examples/enterprise-profile.example.json +16 -20
  49. package/examples/identity-only.example.json +7 -2
  50. package/examples/litellm.enterprise-profile.example.json +10 -0
  51. package/examples/oidc-llm.enterprise-profile.example.json +13 -0
  52. package/examples/resources-discovery.example.json +7 -9
  53. package/lib/callback-page.js +5 -5
  54. package/lib/client.js +31 -48
  55. package/lib/desktop-oidc.js +7 -11
  56. package/lib/gateway-backend.js +443 -0
  57. package/lib/gateway-protocol.js +6 -0
  58. package/lib/index.js +14 -15
  59. package/lib/litellm-protocol.js +117 -0
  60. package/lib/model-resource-transport.js +19 -0
  61. package/lib/oidc-llm-protocol.js +87 -0
  62. package/lib/oidc.js +40 -338
  63. package/lib/profile.js +42 -74
  64. package/lib/provider/authorization-scope.js +46 -0
  65. package/lib/provider/core.js +5 -16
  66. package/lib/provider/index.js +11 -2
  67. package/lib/transport.js +5 -0
  68. package/lib/typert-schemas.js +1 -1
  69. package/package.json +2 -2
  70. package/protocol/openapi.yaml +33 -240
  71. package/protocol/resources.openapi.yaml +8 -9
  72. package/schema/enterprise-profile.v1alpha1.schema.json +508 -107
@@ -12,28 +12,17 @@
12
12
  "loginDescription": "登录后由学校企业服务为当前用户绑定 ChatECNU 运行凭据;学校密码不会进入 DSH。",
13
13
  "supportURL": "https://support.example.edu/replace-me"
14
14
  },
15
- "oidc": {
16
- "issuer": "https://oidc.example.edu/replace-me",
15
+ "auth": {
16
+ "discoveryUrl": "https://models.example.org/.well-known/openid-configuration",
17
+ "expectedIssuer": "https://models.example.org",
18
+ "experimentalOidcLlm": true,
17
19
  "clientId": "replace-with-public-client-id",
18
- "scopes": [
19
- "openid",
20
- "profile",
21
- "offline_access",
22
- "worker.bootstrap.read",
23
- "worker.credential.provision",
24
- "worker.credential.read",
25
- "worker.credential.renew"
26
- ]
27
- },
28
- "keyBinding": {
29
- "baseURL": "https://worker.example.edu/api/worker/v1",
30
- "credentialRef": "EDUWORK_API_KEY"
20
+ "identityMode": "oidc"
31
21
  },
32
22
  "provider": {
33
23
  "id": "chatecnu",
34
24
  "displayName": "ChatECNU",
35
25
  "adapter": "openai-compatible",
36
- "baseURL": "https://models.example.edu/open/api/v1",
37
26
  "reasoning": "high",
38
27
  "defaultContextWindow": 1000000,
39
28
  "defaultMaxTokens": 393216,
@@ -59,7 +48,9 @@
59
48
  "name": "ecnu-max",
60
49
  "contextWindow": 1000000,
61
50
  "maxTokens": 393216,
62
- "input": ["text"],
51
+ "input": [
52
+ "text"
53
+ ],
63
54
  "reasoningEfforts": {
64
55
  "max": "max",
65
56
  "high": "high",
@@ -71,11 +62,16 @@
71
62
  "name": "ecnu-plus",
72
63
  "contextWindow": 262144,
73
64
  "maxTokens": 65536,
74
- "input": ["text", "image"],
65
+ "input": [
66
+ "text",
67
+ "image"
68
+ ],
75
69
  "compat": {
76
70
  "supportsReasoningEffort": false
77
71
  }
78
72
  }
79
- ]
80
- }
73
+ ],
74
+ "modelSource": "discovery"
75
+ },
76
+ "allowInsecureDevelopment": false
81
77
  }
@@ -12,28 +12,17 @@
12
12
  "loginDescription": "Authenticate with the university identity provider and bind a model runtime credential. Your password never enters DSH.",
13
13
  "supportURL": "https://support.example.edu/ai"
14
14
  },
15
- "oidc": {
16
- "issuer": "https://id.example.edu/oidc",
15
+ "auth": {
16
+ "discoveryUrl": "https://models.example.org/.well-known/openid-configuration",
17
+ "expectedIssuer": "https://models.example.org",
18
+ "experimentalOidcLlm": true,
17
19
  "clientId": "replace-with-public-client-id",
18
- "scopes": [
19
- "openid",
20
- "profile",
21
- "offline_access",
22
- "worker.bootstrap.read",
23
- "worker.credential.provision",
24
- "worker.credential.read",
25
- "worker.credential.renew"
26
- ]
27
- },
28
- "keyBinding": {
29
- "baseURL": "https://ai.example.edu/api/worker/v1",
30
- "credentialRef": "EDUWORK_API_KEY"
20
+ "identityMode": "oidc"
31
21
  },
32
22
  "provider": {
33
23
  "id": "example-ai",
34
24
  "displayName": "Example AI",
35
25
  "adapter": "openai-compatible",
36
- "baseURL": "https://ai.example.edu/open/api/v1",
37
26
  "reasoning": "high",
38
27
  "defaultContextWindow": 262144,
39
28
  "defaultMaxTokens": 32768,
@@ -57,7 +46,9 @@
57
46
  {
58
47
  "id": "example-max",
59
48
  "name": "Example Max",
60
- "input": ["text"],
49
+ "input": [
50
+ "text"
51
+ ],
61
52
  "reasoningEfforts": {
62
53
  "max": "max",
63
54
  "high": "high",
@@ -68,11 +59,16 @@
68
59
  {
69
60
  "id": "example-plus",
70
61
  "name": "Example Plus",
71
- "input": ["text", "image"],
62
+ "input": [
63
+ "text",
64
+ "image"
65
+ ],
72
66
  "compat": {
73
67
  "supportsReasoningEffort": false
74
68
  }
75
69
  }
76
- ]
77
- }
70
+ ],
71
+ "modelSource": "discovery"
72
+ },
73
+ "allowInsecureDevelopment": false
78
74
  }
@@ -5,6 +5,11 @@
5
5
  "oidc": {
6
6
  "issuer": "https://identity.example.edu",
7
7
  "clientId": "replace-with-public-client-id",
8
- "scopes": ["openid", "profile", "offline_access"]
9
- }
8
+ "scopes": [
9
+ "openid",
10
+ "profile",
11
+ "offline_access"
12
+ ]
13
+ },
14
+ "allowInsecureDevelopment": false
10
15
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "schemaVersion": "dsh-oidc/v1alpha1",
3
+ "id": "example-gateway",
4
+ "displayName": "示例模型网关",
5
+ "auth": {
6
+ "discoveryUrl": "https://gateway.example.org/.well-known/litellm-cli-auth",
7
+ "expectedIssuer": "https://gateway.example.org"
8
+ },
9
+ "allowInsecureDevelopment": false
10
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "schemaVersion": "dsh-oidc/v1alpha1",
3
+ "id": "example-models",
4
+ "displayName": "示例模型服务(实验)",
5
+ "auth": {
6
+ "discoveryUrl": "https://models.example.org/.well-known/openid-configuration",
7
+ "expectedIssuer": "https://models.example.org",
8
+ "experimentalOidcLlm": true,
9
+ "clientId": "replace-with-public-client-id",
10
+ "identityMode": "oidc"
11
+ },
12
+ "allowInsecureDevelopment": false
13
+ }
@@ -2,19 +2,17 @@
2
2
  "schemaVersion": "dsh-oidc/v1alpha1",
3
3
  "id": "example-resources",
4
4
  "displayName": "Example Organization",
5
- "oidc": {
6
- "issuer": "https://identity.example.edu",
5
+ "auth": {
6
+ "discoveryUrl": "https://models.example.org/.well-known/openid-configuration",
7
+ "expectedIssuer": "https://models.example.org",
8
+ "experimentalOidcLlm": true,
7
9
  "clientId": "replace-with-public-client-id",
8
- "scopes": ["openid", "profile", "offline_access"]
9
- },
10
- "keyBinding": {
11
- "type": "eduwork-resources-v1",
12
- "baseURL": "https://ai.example.edu/api/worker/v1"
10
+ "identityMode": "oidc"
13
11
  },
14
12
  "provider": {
15
13
  "id": "example-ai",
16
14
  "adapter": "openai-compatible",
17
- "baseURL": "https://ai.example.edu/open/api/v1",
18
15
  "modelSource": "discovery"
19
- }
16
+ },
17
+ "allowInsecureDevelopment": false
20
18
  }
@@ -15,13 +15,13 @@ export function callbackPage(profile = {}, outcome = 'failed', language = 'en')
15
15
  const zh = language === 'zh-CN'
16
16
  const brand = profile.brand ?? {}, product = brand.productName || profile.displayName || 'EduWork'
17
17
  const organization = brand.organizationName || profile.organization || ''
18
- const success = outcome === 'completed' || outcome === 'credential-required'
18
+ const success = outcome === 'completed'
19
19
  const title = success ? (zh ? '身份认证已完成' : 'You’re signed in')
20
20
  : outcome === 'expired' ? (zh ? '此次登录已过期' : 'This sign-in has expired') : (zh ? '此次登录未完成' : 'Sign-in did not complete')
21
- const description = outcome === 'credential-required'
22
- ? (zh ? '请返回应用,继续完成模型服务连接。' : 'Return to the app to finish connecting your model service.')
23
- : success ? (zh ? '请返回应用继续工作。' : 'Return to the app to continue your work.')
24
- : (zh ? '请返回应用重新发起登录。' : 'Return to the app and start sign-in again.')
21
+ const description = success ? (zh ? '请返回应用继续工作。' : 'Return to the app to continue your work.')
22
+ : outcome === 'issuer-invalid'
23
+ ? (zh ? '认证服务返回的信息与登录配置不一致。请联系管理员检查后重试。' : 'The authentication response does not match the sign-in configuration. Contact your administrator before trying again.')
24
+ : (zh ? '请返回应用重新发起登录。' : 'Return to the app and start sign-in again.')
25
25
  const accent = /^#[a-f0-9]{6}$/i.test(brand.primaryColor ?? '') ? brand.primaryColor : '#4f5fd7'
26
26
  let logo = '', imageSource = "'none'"
27
27
  if (/^data:image\/(png|webp);base64,[A-Za-z0-9+/]+=*$/.test(brand.logoURL ?? '')) { logo = brand.logoURL; imageSource = 'data:' }
package/lib/client.js CHANGED
@@ -4247,7 +4247,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4247
4247
  const pkg$1 = "@eduwork/dsh-oidc";
4248
4248
  const profileIdSchema = string().min(1).max(64);
4249
4249
  const loginIdSchema = string().regex(/^[A-Za-z0-9_-]{32}$/);
4250
- const reconcileOptionsSchema = object({ allowProvision: boolean().optional() }).strict();
4250
+ const reconcileOptionsSchema = object({}).strict();
4251
4251
  const baseURLSchema = string().min(1).max(2048);
4252
4252
  const modelCatalogModeSchema = _enum(["discovery", "manual"]);
4253
4253
  const configurationTargetSchema = _enum(["config", "examples"]);
@@ -4648,7 +4648,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4648
4648
  return next.status;
4649
4649
  }
4650
4650
  if (next.state === "cancelled") return service.status(profileID);
4651
- if (next.state === "failed") throw Object.assign(/* @__PURE__ */ new Error("Organization sign-in did not complete. Please try again."), { code: next.errorCode });
4651
+ if (next.state === "failed") {
4652
+ const message = ["gateway_callback_issuer_missing", "gateway_callback_issuer_invalid"].includes(next.errorCode) ? "The authentication response does not match the sign-in configuration. Contact your administrator before trying again." : "Organization sign-in did not complete. Please try again.";
4653
+ throw Object.assign(new Error(message), { code: next.errorCode });
4654
+ }
4652
4655
  if (next.state === "expired") break;
4653
4656
  await wait(interval, signal);
4654
4657
  }
@@ -4709,7 +4712,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4709
4712
  configure: "配置",
4710
4713
  enable: "启用并重载",
4711
4714
  login: "登录",
4712
- provision: "确认创建并连接",
4713
4715
  refresh: "刷新状态",
4714
4716
  processing: "处理中…",
4715
4717
  profileManaged: "Enterprise Profile 管理",
@@ -4789,7 +4791,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4789
4791
  configure: "Configure",
4790
4792
  enable: "Enable and reload",
4791
4793
  login: "Sign in",
4792
- provision: "Confirm and connect",
4793
4794
  refresh: "Refresh",
4794
4795
  processing: "Working…",
4795
4796
  profileManaged: "Enterprise Profile managed",
@@ -5005,7 +5006,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5005
5006
  };
5006
5007
  const login = useSignIn(service);
5007
5008
  const signIn = (profileID) => run(() => login.begin(profileID));
5008
- const connect = (profileID, status) => status?.state === "authenticated" || status?.state === "provision_required" ? run(() => service.reconcile(profileID, { allowProvision: true })) : signIn(profileID);
5009
+ const connect = (profileID) => signIn(profileID);
5009
5010
  if (!management) return h$2("p", { style: {
5010
5011
  margin: "18px 0",
5011
5012
  color: textSecondary,
@@ -5163,12 +5164,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5163
5164
  type: "button",
5164
5165
  disabled: busy,
5165
5166
  style: primaryStyle,
5166
- onClick: () => connect(profile.id, status)
5167
- }, busy ? copy.processing : status?.state === "authenticated" || status?.state === "provision_required" ? copy.provision : copy.login), profile.enabled && !restartRequired && connected && h$2("button", {
5167
+ onClick: () => connect(profile.id)
5168
+ }, busy ? copy.processing : copy.login), profile.enabled && !restartRequired && connected && h$2("button", {
5168
5169
  type: "button",
5169
5170
  disabled: busy,
5170
5171
  style: buttonStyle,
5171
- onClick: () => run(() => service.reconcile(profile.id, { allowProvision: false }))
5172
+ onClick: () => run(() => service.reconcile(profile.id, {}))
5172
5173
  }, copy.refresh))));
5173
5174
  })) : h$2("div", { style: {
5174
5175
  padding: 18,
@@ -5703,6 +5704,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5703
5704
  const activate = async (value) => {
5704
5705
  if (value?.state === "connected" && value.credentialReady) await connected(value.profileID);
5705
5706
  };
5707
+ const reconcile = async (id, select) => {
5708
+ const generation = mutate(id), result = await base.reconcile(id, {});
5709
+ if (epoch(id) === generation && !disposed) {
5710
+ publish(result);
5711
+ if (select) await activate(result);
5712
+ }
5713
+ return result;
5714
+ };
5706
5715
  return {
5707
5716
  service: {
5708
5717
  ...base,
@@ -5737,14 +5746,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5737
5746
  }
5738
5747
  return next;
5739
5748
  },
5740
- async reconcile(id, options) {
5741
- const generation = mutate(id), result = await base.reconcile(id, options);
5742
- if (epoch(id) === generation && !disposed) {
5743
- publish(result);
5744
- if (options?.allowProvision === true) await activate(result);
5745
- }
5746
- return result;
5747
- },
5749
+ reconcile: (id) => reconcile(id, false),
5750
+ useModels: (id) => reconcile(id, true),
5748
5751
  async logout(id) {
5749
5752
  const generation = mutate(id), result = await base.logout(id);
5750
5753
  if (epoch(id) === generation) publish(result);
@@ -5787,7 +5790,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5787
5790
  check: "刷新账户",
5788
5791
  logout: "退出登录",
5789
5792
  login: "登录账户",
5790
- provision: "确认创建模型凭据",
5791
5793
  waiting: "请在浏览器中完成登录…",
5792
5794
  cancel: "取消登录",
5793
5795
  processing: "正在处理…"
@@ -5796,7 +5798,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5796
5798
  check: "Refresh account",
5797
5799
  logout: "Sign out",
5798
5800
  login: "Sign in",
5799
- provision: "Provision model credential",
5800
5801
  waiting: "Complete sign-in in your browser…",
5801
5802
  cancel: "Cancel sign-in",
5802
5803
  processing: "Working…"
@@ -5897,7 +5898,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5897
5898
  style: action,
5898
5899
  onClick
5899
5900
  }, label);
5900
- const refreshAccount = () => service.reconcile(profile.id, { allowProvision: false });
5901
+ const refreshAccount = () => service.reconcile(profile.id, {});
5901
5902
  const defaultContent = signedIn && button(busy ? words.processing : words.check, () => void run(refreshAccount));
5902
5903
  const content = open && h$1("div", {
5903
5904
  ref: panel,
@@ -5938,7 +5939,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
5938
5939
  }, { fallback: defaultContent }) : defaultContent, h$1("div", { style: {
5939
5940
  display: "grid",
5940
5941
  gap: 6
5941
- } }, status?.state === "authenticated" && profile.provider && button(words.provision, () => void run(() => service.reconcile(profile.id, { allowProvision: true }))), !signedIn && button(busy ? words.processing : words.login, () => void run(() => login.begin(profile.id))), signedIn && button(words.logout, () => void run(() => service.logout(profile.id), true))), login.pending && h$1("p", { role: "status" }, words.waiting, " ", h$1("button", {
5942
+ } }, !signedIn && button(busy ? words.processing : words.login, () => void run(() => login.begin(profile.id))), signedIn && button(words.logout, () => void run(() => service.logout(profile.id), true))), login.pending && h$1("p", { role: "status" }, words.waiting, " ", h$1("button", {
5942
5943
  type: "button",
5943
5944
  role: "menuitem",
5944
5945
  style: action,
@@ -6005,13 +6006,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6005
6006
  identityConnected: "组织身份已登录",
6006
6007
  unavailable: "企业模型服务暂时不可用",
6007
6008
  connected: "已连接",
6008
- authenticated: "身份认证完成,等待创建模型凭据",
6009
6009
  disconnected: "尚未连接",
6010
- description: "使用组织统一身份认证连接企业模型。密码不会进入 DSH;模型请求只使用绑定后的运行凭据。",
6010
+ description: "使用组织统一身份认证连接企业模型。密码不会进入 DSH;模型请求使用登录授权。",
6011
6011
  connecting: "正在连接…",
6012
6012
  login: "使用企业账号登录",
6013
- provisioning: "正在创建…",
6014
- provision: "确认创建模型凭据",
6015
6013
  checking: "正在检查…",
6016
6014
  check: "检查连接",
6017
6015
  logout: "退出登录",
@@ -6021,7 +6019,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6021
6019
  other: "使用其他模型",
6022
6020
  enabled: "完成后将启用",
6023
6021
  footerConnected: "企业模型已连接",
6024
- footerAuthenticated: "身份认证完成,等待创建模型凭据",
6025
6022
  footerSetup: "点击设置完成登录"
6026
6023
  } : {
6027
6024
  waiting: "Complete sign-in in your browser…",
@@ -6030,13 +6027,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6030
6027
  identityConnected: "Organization identity connected",
6031
6028
  unavailable: "Enterprise model service is temporarily unavailable",
6032
6029
  connected: "Connected",
6033
- authenticated: "Identity verified; model credential is not provisioned",
6034
6030
  disconnected: "Not connected",
6035
- description: "Connect with your organization account. Your password never enters DSH; model requests use only the bound runtime credential.",
6031
+ description: "Connect with your organization account. Your password never enters DSH; model requests use your sign-in authorization.",
6036
6032
  connecting: "Connecting…",
6037
6033
  login: "Sign in with organization",
6038
- provisioning: "Provisioning…",
6039
- provision: "Provision model credential",
6040
6034
  checking: "Checking…",
6041
6035
  check: "Check connection",
6042
6036
  logout: "Sign out",
@@ -6046,7 +6040,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6046
6040
  other: "Use another model",
6047
6041
  enabled: "This enables",
6048
6042
  footerConnected: "Enterprise models connected",
6049
- footerAuthenticated: "Identity verified; provision a model credential",
6050
6043
  footerSetup: "Open settings to connect"
6051
6044
  };
6052
6045
  async function unwrap(operation) {
@@ -6176,7 +6169,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6176
6169
  };
6177
6170
  const login = useSignIn(service);
6178
6171
  const begin = () => account.run("login", () => login.begin(profile.id));
6179
- const stateLabel = account.status?.state === "connected" ? messages.connected : account.status?.state === "authenticated" ? messages.authenticated : messages.disconnected;
6172
+ const stateLabel = account.status?.state === "connected" ? messages.connected : messages.disconnected;
6180
6173
  const userName = accountUserName(account.status);
6181
6174
  return h("section", { style: {
6182
6175
  padding: "16px 0",
@@ -6216,21 +6209,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6216
6209
  display: "flex",
6217
6210
  gap: 8,
6218
6211
  flexWrap: "wrap"
6219
- } }, account.status?.state !== "connected" && account.status?.state !== "authenticated" && h("button", {
6212
+ } }, account.status?.state !== "connected" && h("button", {
6220
6213
  type: "button",
6221
6214
  disabled: Boolean(account.busy),
6222
6215
  style: primary,
6223
6216
  onClick: begin
6224
- }, account.busy ? messages.connecting : messages.login), profile.provider && !account.status?.credentialReady && account.status?.state === "authenticated" && h("button", {
6225
- type: "button",
6226
- disabled: Boolean(account.busy),
6227
- style: primary,
6228
- onClick: () => account.run("provision", () => service.reconcile(profile.id, { allowProvision: true }))
6229
- }, account.busy ? messages.provisioning : messages.provision), account.status?.state === "connected" && h("button", {
6217
+ }, account.busy ? messages.connecting : messages.login), account.status?.state === "connected" && h("button", {
6230
6218
  type: "button",
6231
6219
  disabled: Boolean(account.busy),
6232
6220
  style: button,
6233
- onClick: () => account.run("reconcile", () => service.reconcile(profile.id, { allowProvision: false }))
6221
+ onClick: () => account.run("reconcile", () => service.reconcile(profile.id, {}))
6234
6222
  }, account.busy ? messages.checking : messages.check), account.status?.state !== "signed_out" && h("button", {
6235
6223
  type: "button",
6236
6224
  disabled: Boolean(account.busy),
@@ -6332,17 +6320,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6332
6320
  display: "flex",
6333
6321
  gap: 9,
6334
6322
  flexWrap: "wrap"
6335
- } }, account.status?.state !== "authenticated" && h("button", {
6336
- type: "button",
6337
- disabled: Boolean(account.busy),
6338
- style: primary,
6339
- onClick: account.status?.state === "connected" ? () => account.run("provision", () => service.reconcile(profile.id, { allowProvision: true })) : begin
6340
- }, account.busy ? messages.connecting : account.status?.state === "connected" ? isChinese ? "使用企业模型" : "Use organization model" : messages.login), account.status?.state === "authenticated" && h("button", {
6323
+ } }, h("button", {
6341
6324
  type: "button",
6342
6325
  disabled: Boolean(account.busy),
6343
6326
  style: primary,
6344
- onClick: () => account.run("provision", () => service.reconcile(profile.id, { allowProvision: true }))
6345
- }, account.busy ? messages.provisioning : messages.provision), h("button", {
6327
+ onClick: account.status?.state === "connected" ? () => account.run("select", () => service.useModels(profile.id)) : begin
6328
+ }, account.busy ? messages.connecting : account.status?.state === "connected" ? isChinese ? "使用企业模型" : "Use organization model" : messages.login), h("button", {
6346
6329
  type: "button",
6347
6330
  disabled: Boolean(account.busy),
6348
6331
  style: button,
@@ -6369,7 +6352,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6369
6352
  const profile = configuration.profiles[0];
6370
6353
  const status = useAccountStatus(service, profile.id);
6371
6354
  const userName = accountUserName(status);
6372
- const statusLabel = status?.state === "connected" ? profile.provider ? messages.footerConnected : messages.identityConnected : status?.state === "authenticated" ? messages.footerAuthenticated : messages.footerSetup;
6355
+ const statusLabel = status?.state === "connected" ? profile.provider ? messages.footerConnected : messages.identityConnected : messages.footerSetup;
6373
6356
  return h(AccountMenu, {
6374
6357
  service,
6375
6358
  profile,
@@ -6,7 +6,7 @@ import { callbackLanguage, callbackPage } from './callback-page.js'
6
6
  const MAX_FLOWS = 32
7
7
  const MAX_HISTORY = 128
8
8
  const error = (code, message) => Object.assign(new Error(message), { code })
9
- const safeCodes = new Set(['oidc_callback_invalid', 'oidc_authorization_rejected', 'oidc_token_invalid', 'oidc_id_token_invalid', 'oidc_userinfo_invalid'])
9
+ const safeCodes = new Set(['oidc_callback_invalid', 'oidc_authorization_rejected', 'oidc_token_invalid', 'oidc_id_token_invalid', 'oidc_userinfo_invalid', 'gateway_token_invalid', 'gateway_scope_changed', 'gateway_identity_changed', 'gateway_callback_issuer_missing', 'gateway_callback_issuer_invalid'])
10
10
 
11
11
  function reply(response, status, profile, outcome, language) {
12
12
  const page = callbackPage(profile, outcome, language)
@@ -60,7 +60,8 @@ export class DesktopOidcBackend extends WebOidcBackend {
60
60
  throw error('oidc_host_unavailable', 'OIDC host has stopped')
61
61
  }
62
62
  attempt.origin = `http://127.0.0.1:${server.address().port}`
63
- const authorization = this.createAuthorization(profile, discovery, `${attempt.origin}${OIDC_CALLBACK_PATH}`)
63
+ const authorization = await this.createAuthorization(profile, discovery, `${attempt.origin}${OIDC_CALLBACK_PATH}`)
64
+ if (this.disposed || attempt.state !== 'pending') throw error('oidc_login_cancelled', 'Sign-in was cancelled')
64
65
  Object.assign(attempt, { oauthState: authorization.state, flow: authorization.flow })
65
66
  attempt.timer = setTimeout(() => { void this.cancelLogin(loginID, 'expired').catch(() => {}) }, this.ttl)
66
67
  attempt.timer.unref()
@@ -137,8 +138,9 @@ export class DesktopOidcBackend extends WebOidcBackend {
137
138
  this.ctx.logger.warn('Desktop OIDC host credentials are unavailable')
138
139
  })
139
140
  await attempt.processing
141
+ const failedOutcome = ['gateway_callback_issuer_missing', 'gateway_callback_issuer_invalid'].includes(attempt.errorCode) ? 'issuer-invalid' : 'failed'
140
142
  if (!response.destroyed) reply(response, attempt.state === 'completed' ? 200 : 400, profile,
141
- attempt.state === 'completed' ? (attempt.status?.state === 'authenticated' ? 'credential-required' : 'completed') : 'failed', language)
143
+ attempt.state === 'completed' ? 'completed' : failedOutcome, language)
142
144
  await this.closeCallback(attempt)
143
145
  }
144
146
 
@@ -149,7 +151,7 @@ export class DesktopOidcBackend extends WebOidcBackend {
149
151
  try {
150
152
  const session = await this.exchangeAuthorization(requested, attempt.flow)
151
153
  if (attempt.state !== 'pending') return
152
- const refs = [sessionRef(profile), ...(profile.keyBinding ? [profile.keyBinding.credentialRef] : [])]
154
+ const refs = [sessionRef(profile)]
153
155
  for (const ref of refs) previous.set(ref, await this.ctx.credentials.resolve(ref))
154
156
  if (attempt.state !== 'pending') return
155
157
  wrote = true
@@ -157,7 +159,7 @@ export class DesktopOidcBackend extends WebOidcBackend {
157
159
  if (attempt.state !== 'pending') return
158
160
  // A resource outage must not turn a successful identity login into a failed login.
159
161
  let status
160
- try { status = await this.reconcile(profile.id, { allowProvision: false }) }
162
+ try { status = await this.reconcile(profile.id) }
161
163
  catch { status = await this.status(profile.id) }
162
164
  if (attempt.state !== 'pending') return
163
165
  attempt.status = status
@@ -173,13 +175,7 @@ export class DesktopOidcBackend extends WebOidcBackend {
173
175
  // Cancellation/disposal while the host vault writes must not resurrect a login.
174
176
  if (wrote && attempt.state !== 'completed') {
175
177
  await this.writeCredentials(profile, attempt.flow.epoch, async () => {
176
- const currentRecord = await this.ctx.credentials.resolve(sessionRef(profile))
177
- let currentSession
178
- try { currentSession = JSON.parse(currentRecord?.value) } catch { /* No proven runtime-key ownership. */ }
179
- const ownsCurrentKey = await this.boundCredential(profile, currentSession)
180
178
  for (const [ref, record] of previous) {
181
- // A different profile may have rebound the shared key during login.
182
- if (ref !== sessionRef(profile) && !ownsCurrentKey) continue
183
179
  if (record?.value !== undefined) await this.ctx.credentials.set(ref, record.value)
184
180
  else await this.ctx.credentials.unset(ref)
185
181
  }