ssos-user-cli 0.2.1 → 0.2.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.
- package/package.json +1 -1
- package/src/auth.mjs +92 -2
package/package.json
CHANGED
package/src/auth.mjs
CHANGED
|
@@ -55,6 +55,75 @@ function defaultKeychain() {
|
|
|
55
55
|
return import(keychainModule).then(module => module.default ?? module)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function escapeHtml(value) {
|
|
59
|
+
return String(value).replace(/[&<>"']/g, character => ({
|
|
60
|
+
'&': '&',
|
|
61
|
+
'<': '<',
|
|
62
|
+
'>': '>',
|
|
63
|
+
'"': '"',
|
|
64
|
+
"'": ''',
|
|
65
|
+
})[character])
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function renderAuthorizationCallbackPage({ status = 'success', title, message, detail } = {}) {
|
|
69
|
+
const success = status === 'success'
|
|
70
|
+
const safeTitle = escapeHtml(title ?? (success ? '授权已完成' : '授权未完成'))
|
|
71
|
+
const safeMessage = escapeHtml(message ?? (success ? '创业OS CLI 已完成安全授权。' : '本次授权没有完成。'))
|
|
72
|
+
const safeDetail = detail ? escapeHtml(detail) : ''
|
|
73
|
+
const accent = success ? '#22c55e' : '#ef4444'
|
|
74
|
+
const softAccent = success ? '#dcfce7' : '#fee2e2'
|
|
75
|
+
const icon = success ? '✓' : '!'
|
|
76
|
+
return `<!doctype html>
|
|
77
|
+
<html lang="zh-CN">
|
|
78
|
+
<head>
|
|
79
|
+
<meta charset="utf-8">
|
|
80
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
81
|
+
<meta name="color-scheme" content="light dark">
|
|
82
|
+
<title>${safeTitle} · 创业OS</title>
|
|
83
|
+
<style>
|
|
84
|
+
:root { color-scheme: light dark; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
85
|
+
* { box-sizing: border-box; }
|
|
86
|
+
body { margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 24px; color: #172033; background: linear-gradient(135deg, #f7f9fc 0%, #eef3fb 100%); }
|
|
87
|
+
.shell { width: min(100%, 520px); }
|
|
88
|
+
.brand { display: flex; align-items: center; gap: 10px; margin: 0 0 18px 4px; font-weight: 700; letter-spacing: -.02em; }
|
|
89
|
+
.brand-mark { width: 34px; height: 34px; display: grid; place-items: center; border-radius: 10px; color: white; background: #2563eb; box-shadow: 0 8px 24px rgba(37, 99, 235, .25); }
|
|
90
|
+
.brand-name { font-size: 17px; }
|
|
91
|
+
.card { overflow: hidden; border: 1px solid rgba(148, 163, 184, .28); border-radius: 24px; background: rgba(255, 255, 255, .9); box-shadow: 0 24px 70px rgba(30, 64, 175, .12); backdrop-filter: blur(16px); }
|
|
92
|
+
.topline { height: 5px; background: ${accent}; }
|
|
93
|
+
.content { padding: 42px 36px 36px; text-align: center; }
|
|
94
|
+
.icon { width: 68px; height: 68px; display: grid; place-items: center; margin: 0 auto 22px; border-radius: 22px; color: ${accent}; background: ${softAccent}; font-size: 34px; font-weight: 700; }
|
|
95
|
+
h1 { margin: 0; font-size: 26px; line-height: 1.25; letter-spacing: -.03em; }
|
|
96
|
+
.message { margin: 14px auto 0; max-width: 370px; color: #526079; font-size: 15px; line-height: 1.7; }
|
|
97
|
+
.detail { margin: 22px 0 0; padding: 13px 15px; border-radius: 14px; color: #64748b; background: #f8fafc; font-size: 13px; line-height: 1.6; word-break: break-word; }
|
|
98
|
+
.hint { margin: 26px 0 0; padding-top: 20px; border-top: 1px solid #e2e8f0; color: #7a879b; font-size: 12px; line-height: 1.6; }
|
|
99
|
+
@media (prefers-color-scheme: dark) {
|
|
100
|
+
body { color: #e5e7eb; background: linear-gradient(135deg, #101827 0%, #172554 100%); }
|
|
101
|
+
.card { border-color: rgba(148, 163, 184, .24); background: rgba(15, 23, 42, .88); box-shadow: 0 24px 70px rgba(2, 6, 23, .4); }
|
|
102
|
+
.message { color: #a6b3c7; }
|
|
103
|
+
.detail { color: #a6b3c7; background: rgba(30, 41, 59, .75); }
|
|
104
|
+
.hint { border-color: #334155; color: #94a3b8; }
|
|
105
|
+
}
|
|
106
|
+
@media (max-width: 480px) { body { padding: 16px; } .content { padding: 34px 22px 26px; } h1 { font-size: 23px; } }
|
|
107
|
+
</style>
|
|
108
|
+
</head>
|
|
109
|
+
<body>
|
|
110
|
+
<main class="shell" aria-live="polite">
|
|
111
|
+
<div class="brand"><span class="brand-mark" aria-hidden="true">OS</span><span class="brand-name">创业OS</span></div>
|
|
112
|
+
<section class="card">
|
|
113
|
+
<div class="topline" aria-hidden="true"></div>
|
|
114
|
+
<div class="content">
|
|
115
|
+
<div class="icon" aria-hidden="true">${icon}</div>
|
|
116
|
+
<h1>${safeTitle}</h1>
|
|
117
|
+
<p class="message">${safeMessage}</p>
|
|
118
|
+
${safeDetail ? `<p class="detail">${safeDetail}</p>` : ''}
|
|
119
|
+
<p class="hint">${success ? '可以返回终端继续使用。此页面可以安全关闭。' : '请返回终端重新运行 ssos login。'}</p>
|
|
120
|
+
</div>
|
|
121
|
+
</section>
|
|
122
|
+
</main>
|
|
123
|
+
</body>
|
|
124
|
+
</html>`
|
|
125
|
+
}
|
|
126
|
+
|
|
58
127
|
export function createUserCliAuth(options = {}) {
|
|
59
128
|
const env = options.env ?? process.env
|
|
60
129
|
const config = createUserCliConfig(env)
|
|
@@ -118,9 +187,30 @@ export function createUserCliAuth(options = {}) {
|
|
|
118
187
|
const error = url.searchParams.get('error')
|
|
119
188
|
const codeValue = url.searchParams.get('code')
|
|
120
189
|
if (error || !codeValue || url.searchParams.get('state') !== state) {
|
|
121
|
-
response.writeHead(400
|
|
190
|
+
response.writeHead(400, {
|
|
191
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
192
|
+
'Cache-Control': 'no-store',
|
|
193
|
+
'Content-Security-Policy': "default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; frame-ancestors 'none'",
|
|
194
|
+
})
|
|
195
|
+
response.end(renderAuthorizationCallbackPage({
|
|
196
|
+
status: 'error',
|
|
197
|
+
title: '授权未完成',
|
|
198
|
+
message: '创业OS 没有收到有效的授权回调。',
|
|
199
|
+
detail: error ?? '授权状态或一次性授权码无效',
|
|
200
|
+
}))
|
|
201
|
+
server.close(); reject(new Error(error ?? 'OAuth state or code is invalid')); return
|
|
122
202
|
}
|
|
123
|
-
response.writeHead(200
|
|
203
|
+
response.writeHead(200, {
|
|
204
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
205
|
+
'Cache-Control': 'no-store',
|
|
206
|
+
'Content-Security-Policy': "default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; frame-ancestors 'none'",
|
|
207
|
+
})
|
|
208
|
+
response.end(renderAuthorizationCallbackPage({
|
|
209
|
+
title: '授权已完成',
|
|
210
|
+
message: '创业OS CLI 已完成安全授权。',
|
|
211
|
+
detail: '工作区权限已确认,短期访问令牌只会保存在当前 CLI 进程中。',
|
|
212
|
+
}))
|
|
213
|
+
server.close(); resolve(codeValue)
|
|
124
214
|
})
|
|
125
215
|
server.listen(config.redirectPort, '127.0.0.1')
|
|
126
216
|
const timeout = setTimeout(() => { server.close(); reject(new Error('OAuth authorization timed out')) }, 5 * 60 * 1000)
|