opencode-qwen-oauth 1.0.1 → 2.0.1
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/README.md +59 -1
- package/dist/browser.d.ts +10 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +87 -0
- package/dist/browser.js.map +1 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +25 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +10 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +37 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +79 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +105 -205
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +15 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +79 -0
- package/dist/logger.js.map +1 -0
- package/dist/mutex.d.ts +30 -0
- package/dist/mutex.d.ts.map +1 -0
- package/dist/mutex.js +90 -0
- package/dist/mutex.js.map +1 -0
- package/dist/oauth.d.ts +33 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +262 -0
- package/dist/oauth.js.map +1 -0
- package/dist/pkce.d.ts +8 -0
- package/dist/pkce.d.ts.map +1 -0
- package/dist/pkce.js +17 -0
- package/dist/pkce.js.map +1 -0
- package/dist/retry.d.ts +18 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +74 -0
- package/dist/retry.js.map +1 -0
- package/dist/validation.d.ts +39 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +125 -0
- package/dist/validation.js.map +1 -0
- package/package.json +5 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAoCH,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,yBAAyB,EAAE,MAAM,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAiBD,wBAAsB,eAAe,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAwEpE;AAED,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,MAAM,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAwJD;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CA+DD"}
|
package/dist/oauth.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Device Flow implementation for Qwen
|
|
3
|
+
*/
|
|
4
|
+
import { QWEN_OAUTH_BASE_URL, QWEN_DEVICE_CODE_ENDPOINT, QWEN_TOKEN_ENDPOINT, QWEN_CLIENT_ID, QWEN_SCOPES, } from "./constants.js";
|
|
5
|
+
import { createPkcePair } from "./pkce.js";
|
|
6
|
+
import { debugLog, warnLog } from "./logger.js";
|
|
7
|
+
import { fetchWithRetry } from "./retry.js";
|
|
8
|
+
import { DeviceFlowError, NetworkError, } from "./errors.js";
|
|
9
|
+
import { validateDeviceCode, validateUserCode, validateToken, validateExpiresIn, validateInterval, validateQwenUrl, validateOAuthError, } from "./validation.js";
|
|
10
|
+
import { getConfig } from "./config.js";
|
|
11
|
+
import { Mutex } from "./mutex.js";
|
|
12
|
+
// Global mutex for token refresh to prevent race conditions
|
|
13
|
+
const tokenRefreshMutex = new Mutex();
|
|
14
|
+
// Track active polling operations
|
|
15
|
+
const activePollingOperations = new Set();
|
|
16
|
+
export async function authorizeDevice() {
|
|
17
|
+
const config = getConfig();
|
|
18
|
+
const { verifier, challenge } = createPkcePair();
|
|
19
|
+
const params = new URLSearchParams({
|
|
20
|
+
client_id: QWEN_CLIENT_ID,
|
|
21
|
+
scope: QWEN_SCOPES.join(" "),
|
|
22
|
+
code_challenge: challenge,
|
|
23
|
+
code_challenge_method: "S256",
|
|
24
|
+
});
|
|
25
|
+
try {
|
|
26
|
+
const url = `${QWEN_OAUTH_BASE_URL}${QWEN_DEVICE_CODE_ENDPOINT}`;
|
|
27
|
+
debugLog("Requesting device authorization", { url });
|
|
28
|
+
const response = await fetchWithRetry(url, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
31
|
+
body: params.toString(),
|
|
32
|
+
}, {
|
|
33
|
+
maxRetries: config.maxRetries,
|
|
34
|
+
baseDelay: config.baseRetryDelay,
|
|
35
|
+
maxDelay: config.maxRetryDelay,
|
|
36
|
+
timeout: config.timeout,
|
|
37
|
+
});
|
|
38
|
+
const data = (await response.json());
|
|
39
|
+
// Validate response data
|
|
40
|
+
validateDeviceCode(data.device_code);
|
|
41
|
+
validateUserCode(data.user_code);
|
|
42
|
+
validateExpiresIn(data.expires_in);
|
|
43
|
+
const interval = data.interval || 5;
|
|
44
|
+
validateInterval(interval);
|
|
45
|
+
// Validate URLs
|
|
46
|
+
if (!validateQwenUrl(data.verification_uri)) {
|
|
47
|
+
throw new DeviceFlowError("Invalid verification URI received from server");
|
|
48
|
+
}
|
|
49
|
+
debugLog("Device authorization successful", {
|
|
50
|
+
user_code: data.user_code,
|
|
51
|
+
expires_in: data.expires_in,
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
device_code: data.device_code,
|
|
55
|
+
user_code: data.user_code,
|
|
56
|
+
verification_uri: data.verification_uri,
|
|
57
|
+
verification_uri_complete: data.verification_uri_complete,
|
|
58
|
+
expires_in: data.expires_in,
|
|
59
|
+
interval,
|
|
60
|
+
verifier,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
debugLog("Device authorization failed", { error: String(error) });
|
|
65
|
+
if (error instanceof DeviceFlowError || error instanceof NetworkError) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
throw new DeviceFlowError(`Failed to start device flow: ${error instanceof Error ? error.message : String(error)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function pollForToken(deviceCode, codeVerifier, intervalSeconds, expiresIn) {
|
|
72
|
+
// Validate inputs
|
|
73
|
+
try {
|
|
74
|
+
validateDeviceCode(deviceCode);
|
|
75
|
+
validateInterval(intervalSeconds);
|
|
76
|
+
validateExpiresIn(expiresIn);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
debugLog("Invalid polling parameters", { error: String(error) });
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: error instanceof Error ? error.message : "Invalid parameters",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Prevent multiple concurrent polling for same device code
|
|
86
|
+
if (activePollingOperations.has(deviceCode)) {
|
|
87
|
+
warnLog("Polling already in progress for this device code", { deviceCode });
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
error: "Authorization already in progress",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Register this polling operation
|
|
94
|
+
activePollingOperations.add(deviceCode);
|
|
95
|
+
const timeoutMs = expiresIn * 1000;
|
|
96
|
+
const startTime = Date.now();
|
|
97
|
+
let currentInterval = intervalSeconds * 1000;
|
|
98
|
+
let pollAttempts = 0;
|
|
99
|
+
debugLog("Starting token polling", { timeoutMs, interval: currentInterval });
|
|
100
|
+
// Ensure cleanup on exit
|
|
101
|
+
const cleanup = () => {
|
|
102
|
+
activePollingOperations.delete(deviceCode);
|
|
103
|
+
};
|
|
104
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
105
|
+
await new Promise((resolve) => setTimeout(resolve, currentInterval));
|
|
106
|
+
pollAttempts++;
|
|
107
|
+
const params = new URLSearchParams({
|
|
108
|
+
client_id: QWEN_CLIENT_ID,
|
|
109
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
110
|
+
device_code: deviceCode,
|
|
111
|
+
code_verifier: codeVerifier,
|
|
112
|
+
});
|
|
113
|
+
debugLog(`Polling attempt ${pollAttempts}...`);
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(`${QWEN_OAUTH_BASE_URL}${QWEN_TOKEN_ENDPOINT}`, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
118
|
+
body: params.toString(),
|
|
119
|
+
});
|
|
120
|
+
if (response.ok) {
|
|
121
|
+
const data = (await response.json());
|
|
122
|
+
// Validate token response
|
|
123
|
+
try {
|
|
124
|
+
validateToken(data.access_token);
|
|
125
|
+
validateToken(data.refresh_token);
|
|
126
|
+
validateExpiresIn(data.expires_in);
|
|
127
|
+
}
|
|
128
|
+
catch (validationError) {
|
|
129
|
+
debugLog("Invalid token response", {
|
|
130
|
+
error: String(validationError),
|
|
131
|
+
});
|
|
132
|
+
cleanup();
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
error: "Received invalid token from server",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
debugLog("Token received successfully", { pollAttempts });
|
|
139
|
+
cleanup();
|
|
140
|
+
return {
|
|
141
|
+
success: true,
|
|
142
|
+
access_token: data.access_token,
|
|
143
|
+
refresh_token: data.refresh_token,
|
|
144
|
+
expires_in: data.expires_in,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// Handle error responses
|
|
148
|
+
const errorData = await response.json().catch(() => ({}));
|
|
149
|
+
const oauthError = validateOAuthError(errorData);
|
|
150
|
+
if (oauthError.error === "authorization_pending") {
|
|
151
|
+
debugLog("Authorization pending, retrying...");
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (oauthError.error === "slow_down") {
|
|
155
|
+
currentInterval += 5000;
|
|
156
|
+
debugLog("Server requested slow down", {
|
|
157
|
+
newInterval: currentInterval,
|
|
158
|
+
});
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (oauthError.error === "expired_token") {
|
|
162
|
+
debugLog("Device code expired");
|
|
163
|
+
cleanup();
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
error: "Device code expired. Please try again.",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (oauthError.error === "access_denied") {
|
|
170
|
+
debugLog("User denied authorization");
|
|
171
|
+
cleanup();
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
error: "Authorization was denied",
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
debugLog(`Token polling failed: ${oauthError.error}`, {
|
|
178
|
+
description: oauthError.error_description,
|
|
179
|
+
});
|
|
180
|
+
cleanup();
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
error: oauthError.error_description || oauthError.error,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
debugLog("Network error during polling", { error: String(error) });
|
|
188
|
+
// Continue polling on network errors
|
|
189
|
+
if (Date.now() - startTime < timeoutMs) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
cleanup();
|
|
193
|
+
return {
|
|
194
|
+
success: false,
|
|
195
|
+
error: "Network error occurred during authentication",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
debugLog("Polling timeout exceeded", { attempts: pollAttempts });
|
|
200
|
+
cleanup();
|
|
201
|
+
return { success: false, error: "Polling timeout - device code expired" };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Refresh an expired access token using a refresh token
|
|
205
|
+
* Uses mutex to prevent race conditions when multiple requests
|
|
206
|
+
* try to refresh the token simultaneously
|
|
207
|
+
*/
|
|
208
|
+
export async function refreshAccessToken(refreshToken) {
|
|
209
|
+
// Use mutex to ensure only one refresh happens at a time
|
|
210
|
+
return tokenRefreshMutex.runExclusive(async () => {
|
|
211
|
+
try {
|
|
212
|
+
validateToken(refreshToken);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
return {
|
|
216
|
+
success: false,
|
|
217
|
+
error: "Invalid refresh token",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
// Check if a refresh is already in progress
|
|
221
|
+
if (tokenRefreshMutex.isLocked()) {
|
|
222
|
+
debugLog("Token refresh already in progress, waiting...");
|
|
223
|
+
}
|
|
224
|
+
const config = getConfig();
|
|
225
|
+
try {
|
|
226
|
+
const params = new URLSearchParams({
|
|
227
|
+
client_id: QWEN_CLIENT_ID,
|
|
228
|
+
grant_type: "refresh_token",
|
|
229
|
+
refresh_token: refreshToken,
|
|
230
|
+
});
|
|
231
|
+
debugLog("Refreshing access token");
|
|
232
|
+
const response = await fetchWithRetry(`${QWEN_OAUTH_BASE_URL}${QWEN_TOKEN_ENDPOINT}`, {
|
|
233
|
+
method: "POST",
|
|
234
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
235
|
+
body: params.toString(),
|
|
236
|
+
}, {
|
|
237
|
+
maxRetries: config.maxRetries,
|
|
238
|
+
timeout: config.timeout,
|
|
239
|
+
});
|
|
240
|
+
const data = (await response.json());
|
|
241
|
+
// Validate new tokens
|
|
242
|
+
validateToken(data.access_token);
|
|
243
|
+
validateToken(data.refresh_token);
|
|
244
|
+
validateExpiresIn(data.expires_in);
|
|
245
|
+
debugLog("Token refresh successful");
|
|
246
|
+
return {
|
|
247
|
+
success: true,
|
|
248
|
+
access_token: data.access_token,
|
|
249
|
+
refresh_token: data.refresh_token,
|
|
250
|
+
expires_in: data.expires_in,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
debugLog("Token refresh failed", { error: String(error) });
|
|
255
|
+
return {
|
|
256
|
+
success: false,
|
|
257
|
+
error: error instanceof Error ? error.message : "Token refresh failed",
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"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"}
|
package/dist/pkce.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../src/pkce.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,wBAAgB,cAAc,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAMxE"}
|
package/dist/pkce.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE (Proof Key for Code Exchange) utilities
|
|
3
|
+
*/
|
|
4
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
5
|
+
function base64UrlEncode(buffer) {
|
|
6
|
+
return buffer
|
|
7
|
+
.toString("base64")
|
|
8
|
+
.replace(/\+/g, "-")
|
|
9
|
+
.replace(/\//g, "_")
|
|
10
|
+
.replace(/=+$/, "");
|
|
11
|
+
}
|
|
12
|
+
export function createPkcePair() {
|
|
13
|
+
const verifier = base64UrlEncode(randomBytes(32));
|
|
14
|
+
const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
|
|
15
|
+
return { verifier, challenge };
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=pkce.js.map
|
package/dist/pkce.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../src/pkce.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD,SAAS,eAAe,CAAC,MAAc;IACrC,OAAO,MAAM;SACV,QAAQ,CAAC,QAAQ,CAAC;SAClB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,QAAQ,GAAG,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,eAAe,CAC/B,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAC/C,CAAC;IACF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC"}
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry logic with exponential backoff for network requests
|
|
3
|
+
*/
|
|
4
|
+
export interface RetryOptions {
|
|
5
|
+
maxRetries?: number;
|
|
6
|
+
baseDelay?: number;
|
|
7
|
+
maxDelay?: number;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Retry a function with exponential backoff
|
|
12
|
+
*/
|
|
13
|
+
export declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
14
|
+
/**
|
|
15
|
+
* Retry fetch with exponential backoff
|
|
16
|
+
*/
|
|
17
|
+
export declare function fetchWithRetry(url: string, init?: RequestInit, options?: RetryOptions): Promise<Response>;
|
|
18
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AASD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,CAAC,CAAC,CAqDZ;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,WAAW,EAClB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,QAAQ,CAAC,CAwBnB"}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry logic with exponential backoff for network requests
|
|
3
|
+
*/
|
|
4
|
+
import { NetworkError, isRecoverableError } from "./errors.js";
|
|
5
|
+
import { debugLog } from "./logger.js";
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
maxRetries: 3,
|
|
8
|
+
baseDelay: 1000,
|
|
9
|
+
maxDelay: 30000,
|
|
10
|
+
timeout: 30000,
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Retry a function with exponential backoff
|
|
14
|
+
*/
|
|
15
|
+
export async function retryWithBackoff(fn, options = {}) {
|
|
16
|
+
const opts = { ...defaultOptions, ...options };
|
|
17
|
+
let lastError;
|
|
18
|
+
for (let attempt = 0; attempt < opts.maxRetries; attempt++) {
|
|
19
|
+
try {
|
|
20
|
+
debugLog(`Attempt ${attempt + 1}/${opts.maxRetries}`);
|
|
21
|
+
// Add timeout wrapper
|
|
22
|
+
const result = await Promise.race([
|
|
23
|
+
fn(),
|
|
24
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Request timeout")), opts.timeout)),
|
|
25
|
+
]);
|
|
26
|
+
debugLog(`Attempt ${attempt + 1} succeeded`);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
31
|
+
// Don't retry if error is not recoverable
|
|
32
|
+
if (!isRecoverableError(error)) {
|
|
33
|
+
debugLog(`Non-recoverable error, not retrying: ${lastError.message}`);
|
|
34
|
+
throw lastError;
|
|
35
|
+
}
|
|
36
|
+
// Don't retry on last attempt
|
|
37
|
+
if (attempt === opts.maxRetries - 1) {
|
|
38
|
+
debugLog(`Max retries (${opts.maxRetries}) reached`);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
// Calculate delay with exponential backoff and jitter
|
|
42
|
+
const exponentialDelay = opts.baseDelay * Math.pow(2, attempt);
|
|
43
|
+
const jitter = Math.random() * 0.1 * exponentialDelay; // 10% jitter
|
|
44
|
+
const delay = Math.min(exponentialDelay + jitter, opts.maxDelay);
|
|
45
|
+
debugLog(`Retrying in ${Math.round(delay)}ms...`, {
|
|
46
|
+
attempt: attempt + 1,
|
|
47
|
+
error: lastError.message,
|
|
48
|
+
});
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw new NetworkError(`Failed after ${opts.maxRetries} retries: ${lastError?.message}`);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Retry fetch with exponential backoff
|
|
56
|
+
*/
|
|
57
|
+
export async function fetchWithRetry(url, init, options = {}) {
|
|
58
|
+
return retryWithBackoff(async () => {
|
|
59
|
+
const response = await fetch(url, init);
|
|
60
|
+
// Throw on HTTP errors for retry logic
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
// Check for rate limiting
|
|
63
|
+
if (response.status === 429) {
|
|
64
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
65
|
+
const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 60000;
|
|
66
|
+
throw new NetworkError(`Rate limited (429)`, `Rate limit exceeded. Please wait ${Math.ceil(waitTime / 1000)} seconds.`);
|
|
67
|
+
}
|
|
68
|
+
// Other HTTP errors
|
|
69
|
+
throw new NetworkError(`HTTP ${response.status}: ${response.statusText}`);
|
|
70
|
+
}
|
|
71
|
+
return response;
|
|
72
|
+
}, options);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation and security utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Validate URL is HTTPS and from qwen.ai domain
|
|
6
|
+
*/
|
|
7
|
+
export declare function validateQwenUrl(url: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Validate device code format
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateDeviceCode(code: string): void;
|
|
12
|
+
/**
|
|
13
|
+
* Validate user code format
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateUserCode(code: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Validate access token format
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateToken(token: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Validate expires_in value
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateExpiresIn(expiresIn: number): void;
|
|
24
|
+
/**
|
|
25
|
+
* Validate interval for polling
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateInterval(interval: number): void;
|
|
28
|
+
/**
|
|
29
|
+
* Sanitize log data by removing sensitive fields
|
|
30
|
+
*/
|
|
31
|
+
export declare function sanitizeLogData(data: any): any;
|
|
32
|
+
/**
|
|
33
|
+
* Validate OAuth error response
|
|
34
|
+
*/
|
|
35
|
+
export declare function validateOAuthError(error: any): {
|
|
36
|
+
error: string;
|
|
37
|
+
error_description?: string;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAYpD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOrD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAQnD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAOjD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAQzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQvD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,CA8B9C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,GAAG,GAAG;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAWA"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation and security utilities
|
|
3
|
+
*/
|
|
4
|
+
import { ValidationError } from "./errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Validate URL is HTTPS and from qwen.ai domain
|
|
7
|
+
*/
|
|
8
|
+
export function validateQwenUrl(url) {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = new URL(url);
|
|
11
|
+
return (parsed.protocol === "https:" &&
|
|
12
|
+
(parsed.hostname === "chat.qwen.ai" ||
|
|
13
|
+
parsed.hostname === "portal.qwen.ai" ||
|
|
14
|
+
parsed.hostname.endsWith(".qwen.ai")));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Validate device code format
|
|
22
|
+
*/
|
|
23
|
+
export function validateDeviceCode(code) {
|
|
24
|
+
if (!code || typeof code !== "string") {
|
|
25
|
+
throw new ValidationError("Device code must be a non-empty string", "device_code");
|
|
26
|
+
}
|
|
27
|
+
if (code.length < 10 || code.length > 100) {
|
|
28
|
+
throw new ValidationError("Device code has invalid length", "device_code");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Validate user code format
|
|
33
|
+
*/
|
|
34
|
+
export function validateUserCode(code) {
|
|
35
|
+
if (!code || typeof code !== "string") {
|
|
36
|
+
throw new ValidationError("User code must be a non-empty string", "user_code");
|
|
37
|
+
}
|
|
38
|
+
// User codes are typically short alphanumeric codes
|
|
39
|
+
if (!/^[A-Z0-9-]{4,12}$/i.test(code)) {
|
|
40
|
+
throw new ValidationError("User code has invalid format", "user_code");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validate access token format
|
|
45
|
+
*/
|
|
46
|
+
export function validateToken(token) {
|
|
47
|
+
if (!token || typeof token !== "string") {
|
|
48
|
+
throw new ValidationError("Token must be a non-empty string", "token");
|
|
49
|
+
}
|
|
50
|
+
if (token.length < 20) {
|
|
51
|
+
throw new ValidationError("Token is too short", "token");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate expires_in value
|
|
56
|
+
*/
|
|
57
|
+
export function validateExpiresIn(expiresIn) {
|
|
58
|
+
if (!Number.isInteger(expiresIn) || expiresIn <= 0) {
|
|
59
|
+
throw new ValidationError("expires_in must be a positive integer", "expires_in");
|
|
60
|
+
}
|
|
61
|
+
// Sanity check: shouldn't be more than 1 year
|
|
62
|
+
if (expiresIn > 365 * 24 * 60 * 60) {
|
|
63
|
+
throw new ValidationError("expires_in value is unreasonably large", "expires_in");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Validate interval for polling
|
|
68
|
+
*/
|
|
69
|
+
export function validateInterval(interval) {
|
|
70
|
+
if (!Number.isInteger(interval) || interval <= 0) {
|
|
71
|
+
throw new ValidationError("Interval must be a positive integer", "interval");
|
|
72
|
+
}
|
|
73
|
+
// Minimum 1 second, maximum 60 seconds
|
|
74
|
+
if (interval < 1 || interval > 60) {
|
|
75
|
+
throw new ValidationError("Interval must be between 1 and 60 seconds", "interval");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Sanitize log data by removing sensitive fields
|
|
80
|
+
*/
|
|
81
|
+
export function sanitizeLogData(data) {
|
|
82
|
+
if (!data || typeof data !== "object") {
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
const sensitiveFields = [
|
|
86
|
+
"access_token",
|
|
87
|
+
"refresh_token",
|
|
88
|
+
"api_key",
|
|
89
|
+
"apiKey",
|
|
90
|
+
"secret",
|
|
91
|
+
"password",
|
|
92
|
+
"verifier",
|
|
93
|
+
"device_code",
|
|
94
|
+
];
|
|
95
|
+
const sanitized = Array.isArray(data) ? [] : {};
|
|
96
|
+
for (const [key, value] of Object.entries(data)) {
|
|
97
|
+
const lowerKey = key.toLowerCase();
|
|
98
|
+
if (sensitiveFields.some((field) => lowerKey.includes(field))) {
|
|
99
|
+
sanitized[key] = "[REDACTED]";
|
|
100
|
+
}
|
|
101
|
+
else if (value && typeof value === "object") {
|
|
102
|
+
sanitized[key] = sanitizeLogData(value);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
sanitized[key] = value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return sanitized;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Validate OAuth error response
|
|
112
|
+
*/
|
|
113
|
+
export function validateOAuthError(error) {
|
|
114
|
+
if (!error || typeof error !== "object") {
|
|
115
|
+
throw new ValidationError("Invalid OAuth error response");
|
|
116
|
+
}
|
|
117
|
+
if (!error.error || typeof error.error !== "string") {
|
|
118
|
+
throw new ValidationError("OAuth error must have an error field");
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
error: error.error,
|
|
122
|
+
error_description: error.error_description,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-qwen-oauth",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Qwen OAuth authentication plugin for OpenCode - authenticate with Qwen.ai using OAuth device flow",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"opencode-qwen-oauth": "
|
|
9
|
+
"opencode-qwen-oauth": "bin/install.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"build": "tsc -p tsconfig.json",
|
|
19
19
|
"dev": "tsc -p tsconfig.json --watch",
|
|
20
20
|
"prepublishOnly": "npm run build",
|
|
21
|
-
"test": "
|
|
21
|
+
"test": "bun test",
|
|
22
|
+
"test:watch": "bun test --watch"
|
|
22
23
|
},
|
|
23
24
|
"keywords": [
|
|
24
25
|
"opencode",
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
"license": "MIT",
|
|
34
35
|
"repository": {
|
|
35
36
|
"type": "git",
|
|
36
|
-
"url": "https://github.com/yourusername/opencode-qwen-oauth.git"
|
|
37
|
+
"url": "git+https://github.com/yourusername/opencode-qwen-oauth.git"
|
|
37
38
|
},
|
|
38
39
|
"bugs": {
|
|
39
40
|
"url": "https://github.com/yourusername/opencode-qwen-oauth/issues"
|