@eduwork/dsh-oidc 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.en.md +40 -1
- package/CHANGELOG.md +40 -1
- package/README.en.md +23 -21
- package/README.md +17 -15
- package/THIRD_PARTY_NOTICES.en.md +7 -5
- package/THIRD_PARTY_NOTICES.md +7 -5
- package/cordis.patch.yml +2 -1
- package/docs/EDUWORK-MIGRATION.en.md +14 -2
- package/docs/EDUWORK-MIGRATION.md +14 -2
- package/docs/account-extensions.en.md +40 -0
- package/docs/account-extensions.md +42 -0
- package/docs/architecture.en.md +9 -9
- package/docs/architecture.md +11 -10
- package/docs/compatibility.en.md +11 -2
- package/docs/compatibility.md +11 -2
- package/docs/desktop-host.en.md +69 -0
- package/docs/desktop-host.md +79 -0
- package/docs/development.en.md +42 -0
- package/docs/development.md +42 -0
- package/docs/dsh-integration.en.md +23 -7
- package/docs/dsh-integration.md +23 -7
- package/docs/ecnu-reference.en.md +3 -3
- package/docs/ecnu-reference.md +3 -3
- package/docs/enterprise-profile.en.md +19 -11
- package/docs/enterprise-profile.md +18 -10
- package/docs/getting-started.en.md +12 -12
- package/docs/getting-started.md +16 -14
- package/docs/key-binding-protocol.en.md +2 -2
- package/docs/key-binding-protocol.md +2 -2
- package/docs/oidc-interoperability.en.md +3 -3
- package/docs/oidc-interoperability.md +2 -2
- package/docs/public-resource-protocol.en.md +51 -0
- package/docs/public-resource-protocol.md +50 -0
- package/docs/release-checklist.en.md +4 -2
- package/docs/release-checklist.md +4 -2
- package/docs/server-integration-contract.en.md +124 -8
- package/docs/server-integration-contract.md +136 -8
- package/examples/ecnu.enterprise-profile.example.json +2 -2
- package/examples/enterprise-profile.example.json +78 -77
- package/examples/identity-only.example.json +10 -0
- package/examples/resources-discovery.example.json +20 -0
- package/lib/callback-page.js +42 -0
- package/lib/client.js +934 -294
- package/lib/desktop-oidc.js +205 -0
- package/lib/index.js +148 -18
- package/lib/oidc.js +314 -58
- package/lib/profile.js +53 -24
- package/lib/provider/core.js +76 -15
- package/lib/provider/index.js +33 -20
- package/lib/provider/transform-adapter.js +31 -10
- package/lib/resources.js +23 -0
- package/lib/typert-schemas.js +34 -3
- package/lib/typert.host.js +42 -36
- package/lib/typert.remote-client.js +41 -35
- package/package.json +100 -29
- package/protocol/openapi.yaml +2 -2
- package/protocol/resources.openapi.yaml +51 -0
- package/schema/enterprise-profile.v1alpha1.schema.json +193 -188
|
@@ -1,77 +1,78 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schemaVersion": "dsh-oidc/v1alpha1",
|
|
3
|
-
"id": "example-university",
|
|
4
|
-
"displayName": "Example University AI",
|
|
5
|
-
"organization": "Example University",
|
|
6
|
-
"brand": {
|
|
7
|
-
"productName": "Example AI Work",
|
|
8
|
-
"organizationName": "Example University",
|
|
9
|
-
"mark": "E",
|
|
10
|
-
"primaryColor": "#5157af",
|
|
11
|
-
"loginTitle": "Sign in with Example University",
|
|
12
|
-
"loginDescription": "Authenticate with the university identity provider and bind a model runtime credential. Your password never enters DSH.",
|
|
13
|
-
"supportURL": "https://support.example.edu/ai"
|
|
14
|
-
},
|
|
15
|
-
"oidc": {
|
|
16
|
-
"issuer": "https://id.example.edu/oidc",
|
|
17
|
-
"clientId": "dsh-web-public-client",
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "dsh-oidc/v1alpha1",
|
|
3
|
+
"id": "example-university",
|
|
4
|
+
"displayName": "Example University AI",
|
|
5
|
+
"organization": "Example University",
|
|
6
|
+
"brand": {
|
|
7
|
+
"productName": "Example AI Work",
|
|
8
|
+
"organizationName": "Example University",
|
|
9
|
+
"mark": "E",
|
|
10
|
+
"primaryColor": "#5157af",
|
|
11
|
+
"loginTitle": "Sign in with Example University",
|
|
12
|
+
"loginDescription": "Authenticate with the university identity provider and bind a model runtime credential. Your password never enters DSH.",
|
|
13
|
+
"supportURL": "https://support.example.edu/ai"
|
|
14
|
+
},
|
|
15
|
+
"oidc": {
|
|
16
|
+
"issuer": "https://id.example.edu/oidc",
|
|
17
|
+
"clientId": "dsh-web-public-client",
|
|
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"
|
|
31
|
+
},
|
|
32
|
+
"provider": {
|
|
33
|
+
"id": "example-ai",
|
|
34
|
+
"displayName": "Example AI",
|
|
35
|
+
"adapter": "openai-compatible",
|
|
36
|
+
"baseURL": "https://ai.example.edu/open/api/v1",
|
|
37
|
+
"reasoning": "high",
|
|
38
|
+
"defaultContextWindow": 262144,
|
|
39
|
+
"defaultMaxTokens": 32768,
|
|
40
|
+
"streamIdleTimeoutMs": 300000,
|
|
41
|
+
"retryPolicy": {
|
|
42
|
+
"mode": "normal",
|
|
43
|
+
"maxRetries": 2
|
|
44
|
+
},
|
|
45
|
+
"compat": {
|
|
46
|
+
"supportsDeveloperRole": false,
|
|
47
|
+
"supportsStore": false,
|
|
48
|
+
"supportsReasoningEffort": true,
|
|
49
|
+
"supportsUsageInStreaming": true,
|
|
50
|
+
"maxTokensField": "max_tokens",
|
|
51
|
+
"supportsStrictMode": false,
|
|
52
|
+
"supportsLongCacheRetention": false,
|
|
53
|
+
"thinkingFormat": "deepseek",
|
|
54
|
+
"requiresReasoningContentOnAssistantMessages": true
|
|
55
|
+
},
|
|
56
|
+
"models": [
|
|
57
|
+
{
|
|
58
|
+
"id": "example-max",
|
|
59
|
+
"name": "Example Max",
|
|
60
|
+
"input": ["text"],
|
|
61
|
+
"reasoningEfforts": {
|
|
62
|
+
"max": "max",
|
|
63
|
+
"high": "high",
|
|
64
|
+
"low": "low"
|
|
65
|
+
},
|
|
66
|
+
"defaultReasoningEffort": "high"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "example-plus",
|
|
70
|
+
"name": "Example Plus",
|
|
71
|
+
"input": ["text", "image"],
|
|
72
|
+
"compat": {
|
|
73
|
+
"supportsReasoningEffort": false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "dsh-oidc/v1alpha1",
|
|
3
|
+
"id": "example-identity",
|
|
4
|
+
"displayName": "Example Organization",
|
|
5
|
+
"oidc": {
|
|
6
|
+
"issuer": "https://identity.example.edu",
|
|
7
|
+
"clientId": "example-public-client",
|
|
8
|
+
"scopes": ["openid", "profile", "offline_access"]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "dsh-oidc/v1alpha1",
|
|
3
|
+
"id": "example-resources",
|
|
4
|
+
"displayName": "Example Organization",
|
|
5
|
+
"oidc": {
|
|
6
|
+
"issuer": "https://identity.example.edu",
|
|
7
|
+
"clientId": "example-public-client",
|
|
8
|
+
"scopes": ["openid", "profile", "offline_access"]
|
|
9
|
+
},
|
|
10
|
+
"keyBinding": {
|
|
11
|
+
"type": "eduwork-resources-v1",
|
|
12
|
+
"baseURL": "https://ai.example.edu/api/worker/v1"
|
|
13
|
+
},
|
|
14
|
+
"provider": {
|
|
15
|
+
"id": "example-ai",
|
|
16
|
+
"adapter": "openai-compatible",
|
|
17
|
+
"baseURL": "https://ai.example.edu/open/api/v1",
|
|
18
|
+
"modelSource": "discovery"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
const escape = value => String(value ?? '').replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[char])
|
|
4
|
+
|
|
5
|
+
export function callbackLanguage(header = '') {
|
|
6
|
+
const languages = String(header).split(',').map((entry, order) => {
|
|
7
|
+
const [name, quality] = entry.trim().split(';q=')
|
|
8
|
+
return { name: name.toLowerCase(), quality: quality === undefined ? 1 : Number(quality), order }
|
|
9
|
+
}).filter(value => value.quality > 0).sort((a, b) => b.quality - a.quality || a.order - b.order)
|
|
10
|
+
return languages.find(value => /^(zh|en)(-|$)/.test(value.name))?.name.startsWith('zh') ? 'zh-CN' : 'en'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Only trusted profile presentation and a fixed outcome enter the page. */
|
|
14
|
+
export function callbackPage(profile = {}, outcome = 'failed', language = 'en') {
|
|
15
|
+
const zh = language === 'zh-CN'
|
|
16
|
+
const brand = profile.brand ?? {}, product = brand.productName || profile.displayName || 'EduWork'
|
|
17
|
+
const organization = brand.organizationName || profile.organization || ''
|
|
18
|
+
const success = outcome === 'completed' || outcome === 'credential-required'
|
|
19
|
+
const title = success ? (zh ? '身份认证已完成' : 'You’re signed in')
|
|
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.')
|
|
25
|
+
const accent = /^#[a-f0-9]{6}$/i.test(brand.primaryColor ?? '') ? brand.primaryColor : '#4f5fd7'
|
|
26
|
+
let logo = '', imageSource = "'none'"
|
|
27
|
+
if (/^data:image\/(png|webp);base64,[A-Za-z0-9+/]+=*$/.test(brand.logoURL ?? '')) { logo = brand.logoURL; imageSource = 'data:' }
|
|
28
|
+
else {
|
|
29
|
+
try { const url = new URL(brand.logoURL); if (url.protocol === 'https:' && !url.username && !url.password && !url.hash) { logo = url.toString(); imageSource = url.origin } } catch {}
|
|
30
|
+
}
|
|
31
|
+
const nonce = randomBytes(18).toString('base64')
|
|
32
|
+
const mark = logo ? `<img src="${escape(logo)}" alt="" referrerpolicy="no-referrer">` : escape(brand.mark || product.slice(0, 1))
|
|
33
|
+
return {
|
|
34
|
+
headers: {
|
|
35
|
+
'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'referrer-policy': 'no-referrer',
|
|
36
|
+
'content-security-policy': `default-src 'none'; style-src 'nonce-${nonce}'; img-src ${imageSource}; frame-ancestors 'none'; base-uri 'none'; form-action 'none'`,
|
|
37
|
+
'x-content-type-options': 'nosniff', connection: 'close',
|
|
38
|
+
},
|
|
39
|
+
html: `<!doctype html><html lang="${language === 'zh-CN' ? 'zh-CN' : 'en'}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escape(title)} · ${escape(product)}</title>
|
|
40
|
+
<style nonce="${nonce}">*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;background:#f5f6f8;color:#20232c;font-family:system-ui,-apple-system,"Segoe UI","Microsoft YaHei",sans-serif}.card{width:min(100%,480px);padding:36px;border:1px solid #e1e4eb;border-radius:20px;background:#fff;box-shadow:0 14px 48px #17264d0a}.brand{display:flex;align-items:center;gap:12px;margin-bottom:36px}.mark{width:42px;height:42px;display:grid;place-items:center;flex:none;border-radius:11px;background:${accent};color:white;font-size:22px;font-weight:700}.mark img{width:100%;height:100%;object-fit:contain;border-radius:inherit}.name{font-weight:650;font-size:16px}.organization{font-size:12px;color:#747987;margin-top:3px}.status{font-size:12px;color:${success ? '#397958' : '#a14d3b'};margin-bottom:12px}h1{margin:0 0 14px;font-size:26px;line-height:1.35;letter-spacing:-.4px}p{margin:0;color:#5c6270;font-size:15px;line-height:1.8}.hint{margin-top:28px;padding-top:20px;border-top:1px solid #edf0f4;font-size:13px;color:#818694}@media(max-width:480px){.card{padding:28px}h1{font-size:23px}}</style></head><body><main class="card"><div class="brand"><div class="mark">${mark}</div><div><div class="name">${escape(product)}</div>${organization && organization !== product ? `<div class="organization">${escape(organization)}</div>` : ''}</div></div><div class="status">${success ? (zh ? '已验证身份' : 'Identity verified') : (zh ? '需要重新登录' : 'Please try again')}</div><h1>${title}</h1><p>${description}</p><p class="hint">${zh ? '现在可以关闭此标签页。' : 'You can close this browser tab now.'}</p></main></body></html>`,
|
|
41
|
+
}
|
|
42
|
+
}
|