opencode-codebuddy-external-auth 1.0.2 → 1.0.4
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/dist/plugin.js +55 -19
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -20,9 +20,6 @@ const CONFIG = {
|
|
|
20
20
|
function sleep(ms) {
|
|
21
21
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
22
|
}
|
|
23
|
-
function generateState() {
|
|
24
|
-
return crypto.randomUUID();
|
|
25
|
-
}
|
|
26
23
|
/**
|
|
27
24
|
* Creates an authenticated fetch function with CodeBuddy headers
|
|
28
25
|
*/
|
|
@@ -46,18 +43,50 @@ function createAuthenticatedFetch(accessToken, userId) {
|
|
|
46
43
|
// OAuth Flow Implementation (IOA Login)
|
|
47
44
|
// ============================================================================
|
|
48
45
|
/**
|
|
49
|
-
*
|
|
46
|
+
* Request auth state from server - the state is generated by server, not client!
|
|
47
|
+
*
|
|
48
|
+
* 关键发现:
|
|
49
|
+
* 1. 使用 POST 方法,不是 GET
|
|
50
|
+
* 2. 路径是 /v2/plugin/auth/state,不是 /plugin/auth/state
|
|
51
|
+
* 3. 需要设置 X-No-Authorization 等 Headers
|
|
50
52
|
*/
|
|
51
|
-
function
|
|
53
|
+
async function requestAuthState() {
|
|
52
54
|
const params = new URLSearchParams({
|
|
53
55
|
platform: CONFIG.platform,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
ioa: "1",
|
|
57
|
+
});
|
|
58
|
+
const response = await fetch(`${CONFIG.serverUrl}/v2/plugin/auth/state?${params.toString()}`, {
|
|
59
|
+
method: "POST", // 关键:使用 POST 方法
|
|
60
|
+
headers: {
|
|
61
|
+
"Accept": "application/json",
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
// 这些 Headers 告诉服务端跳过认证检查
|
|
64
|
+
"X-No-Authorization": "true",
|
|
65
|
+
"X-No-User-Id": "true",
|
|
66
|
+
"X-No-Enterprise-Id": "true",
|
|
67
|
+
"X-No-Department-Info": "true",
|
|
68
|
+
},
|
|
56
69
|
});
|
|
57
|
-
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
const text = await response.text();
|
|
72
|
+
throw new Error(`Auth state request failed: ${response.status} - ${text}`);
|
|
73
|
+
}
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
if (data.code !== 0 || !data.data?.state) {
|
|
76
|
+
throw new Error(`Invalid auth state response: ${JSON.stringify(data)}`);
|
|
77
|
+
}
|
|
78
|
+
// 使用服务端返回的 authUrl,或构造默认 URL
|
|
79
|
+
const loginUrl = data.data.authUrl ||
|
|
80
|
+
`${CONFIG.serverUrl}/login?platform=${CONFIG.platform}&state=${data.data.state}&ioa=1`;
|
|
81
|
+
return {
|
|
82
|
+
state: data.data.state,
|
|
83
|
+
url: loginUrl,
|
|
84
|
+
};
|
|
58
85
|
}
|
|
59
86
|
/**
|
|
60
87
|
* Poll for token after user completes browser authentication
|
|
88
|
+
*
|
|
89
|
+
* 注意:token 端点也需要使用 /v2 前缀
|
|
61
90
|
*/
|
|
62
91
|
async function pollForToken(state, expiresAt, signal) {
|
|
63
92
|
const pollInterval = 3000; // 3 seconds
|
|
@@ -67,17 +96,20 @@ async function pollForToken(state, expiresAt, signal) {
|
|
|
67
96
|
}
|
|
68
97
|
await sleep(pollInterval);
|
|
69
98
|
try {
|
|
70
|
-
const response = await fetch(`${CONFIG.serverUrl}/plugin/auth/token?state=${state}`, {
|
|
99
|
+
const response = await fetch(`${CONFIG.serverUrl}/v2/plugin/auth/token?state=${state}`, {
|
|
71
100
|
method: "GET",
|
|
72
101
|
headers: {
|
|
73
|
-
"Content-Type": "application/json",
|
|
74
102
|
"Accept": "application/json",
|
|
103
|
+
"X-No-Authorization": "true",
|
|
104
|
+
"X-No-User-Id": "true",
|
|
105
|
+
"X-No-Enterprise-Id": "true",
|
|
106
|
+
"X-No-Department-Info": "true",
|
|
75
107
|
},
|
|
76
108
|
signal,
|
|
77
109
|
});
|
|
78
110
|
if (response.ok) {
|
|
79
111
|
const data = await response.json();
|
|
80
|
-
if (data.data?.accessToken) {
|
|
112
|
+
if (data.code === 0 && data.data?.accessToken) {
|
|
81
113
|
return data.data;
|
|
82
114
|
}
|
|
83
115
|
}
|
|
@@ -97,11 +129,12 @@ async function pollForToken(state, expiresAt, signal) {
|
|
|
97
129
|
*/
|
|
98
130
|
async function refreshAccessToken(refreshToken) {
|
|
99
131
|
try {
|
|
100
|
-
const response = await fetch(`${CONFIG.serverUrl}/plugin/auth/token/refresh`, {
|
|
132
|
+
const response = await fetch(`${CONFIG.serverUrl}/v2/plugin/auth/token/refresh`, {
|
|
101
133
|
method: "POST",
|
|
102
134
|
headers: {
|
|
103
135
|
"Content-Type": "application/json",
|
|
104
|
-
|
|
136
|
+
"Accept": "application/json",
|
|
137
|
+
"Authorization": `Bearer ${refreshToken}`,
|
|
105
138
|
},
|
|
106
139
|
});
|
|
107
140
|
if (!response.ok) {
|
|
@@ -109,6 +142,10 @@ async function refreshAccessToken(refreshToken) {
|
|
|
109
142
|
return null;
|
|
110
143
|
}
|
|
111
144
|
const data = await response.json();
|
|
145
|
+
if (data.code !== 0) {
|
|
146
|
+
console.warn(`[codebuddy-external] Token refresh error: ${data.msg}`);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
112
149
|
return data.data || null;
|
|
113
150
|
}
|
|
114
151
|
catch (error) {
|
|
@@ -155,17 +192,16 @@ const CodeBuddyExternalAuthPlugin = async (_input) => {
|
|
|
155
192
|
label: "IOA 登录 (浏览器)",
|
|
156
193
|
type: "oauth",
|
|
157
194
|
async authorize() {
|
|
158
|
-
//
|
|
159
|
-
const
|
|
160
|
-
const loginUrl = buildLoginUrl(state);
|
|
195
|
+
// 从服务端获取 state(服务端生成,非客户端)
|
|
196
|
+
const authState = await requestAuthState();
|
|
161
197
|
const expiresAt = Date.now() + 10 * 60 * 1000; // 10 minutes
|
|
162
198
|
return {
|
|
163
|
-
url:
|
|
199
|
+
url: authState.url,
|
|
164
200
|
instructions: `请在浏览器中完成 IOA 登录`,
|
|
165
201
|
method: "auto",
|
|
166
202
|
async callback() {
|
|
167
|
-
//
|
|
168
|
-
const tokenData = await pollForToken(state, expiresAt);
|
|
203
|
+
// 使用服务端返回的 state 轮询 token
|
|
204
|
+
const tokenData = await pollForToken(authState.state, expiresAt);
|
|
169
205
|
if (!tokenData) {
|
|
170
206
|
return { type: "failed" };
|
|
171
207
|
}
|