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.
Files changed (2) hide show
  1. package/dist/plugin.js +55 -19
  2. 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
- * Build the login URL for IOA authentication
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 buildLoginUrl(state) {
53
+ async function requestAuthState() {
52
54
  const params = new URLSearchParams({
53
55
  platform: CONFIG.platform,
54
- state: state,
55
- ioa: "1", // IOA version flag
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
- return `${CONFIG.serverUrl}/login?${params.toString()}`;
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
- Authorization: `Bearer ${refreshToken}`,
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
- // Generate state for CSRF protection
159
- const state = generateState();
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: loginUrl,
199
+ url: authState.url,
164
200
  instructions: `请在浏览器中完成 IOA 登录`,
165
201
  method: "auto",
166
202
  async callback() {
167
- // Poll for token
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-codebuddy-external-auth",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "OpenCode plugin for CodeBuddy External (IOA) authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",