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.
@@ -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,8 @@
1
+ /**
2
+ * PKCE (Proof Key for Code Exchange) utilities
3
+ */
4
+ export declare function createPkcePair(): {
5
+ verifier: string;
6
+ challenge: string;
7
+ };
8
+ //# sourceMappingURL=pkce.d.ts.map
@@ -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
@@ -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"}
@@ -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": "1.0.1",
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": "./bin/install.js"
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": "echo \"No tests specified\" && exit 0"
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"