@levr-one/auth 1.0.0
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 +37 -0
- package/dist/config-watcher.d.ts +65 -0
- package/dist/config-watcher.d.ts.map +1 -0
- package/dist/config-watcher.js +140 -0
- package/dist/config-watcher.js.map +1 -0
- package/dist/config-watcher.test.d.ts +2 -0
- package/dist/config-watcher.test.d.ts.map +1 -0
- package/dist/config-watcher.test.js +116 -0
- package/dist/config-watcher.test.js.map +1 -0
- package/dist/config.d.ts +58 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +234 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +221 -0
- package/dist/config.test.js.map +1 -0
- package/dist/file-lock.d.ts +41 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +110 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth-client.d.ts +156 -0
- package/dist/oauth-client.d.ts.map +1 -0
- package/dist/oauth-client.js +572 -0
- package/dist/oauth-client.js.map +1 -0
- package/dist/oauth-client.test.d.ts +2 -0
- package/dist/oauth-client.test.d.ts.map +1 -0
- package/dist/oauth-client.test.js +290 -0
- package/dist/oauth-client.test.js.map +1 -0
- package/dist/test-utils.d.ts +10 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +45 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/token-store.d.ts +78 -0
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +299 -0
- package/dist/token-store.js.map +1 -0
- package/dist/token-store.test.d.ts +2 -0
- package/dist/token-store.test.d.ts.map +1 -0
- package/dist/token-store.test.js +239 -0
- package/dist/token-store.test.js.map +1 -0
- package/dist/token-watcher.d.ts +82 -0
- package/dist/token-watcher.d.ts.map +1 -0
- package/dist/token-watcher.js +185 -0
- package/dist/token-watcher.js.map +1 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/workspace-store.d.ts +60 -0
- package/dist/workspace-store.d.ts.map +1 -0
- package/dist/workspace-store.js +147 -0
- package/dist/workspace-store.js.map +1 -0
- package/dist/workspace-store.test.d.ts +2 -0
- package/dist/workspace-store.test.d.ts.map +1 -0
- package/dist/workspace-store.test.js +169 -0
- package/dist/workspace-store.test.js.map +1 -0
- package/dist/workspaces.d.ts +60 -0
- package/dist/workspaces.d.ts.map +1 -0
- package/dist/workspaces.js +80 -0
- package/dist/workspaces.js.map +1 -0
- package/dist/workspaces.test.d.ts +2 -0
- package/dist/workspaces.test.d.ts.map +1 -0
- package/dist/workspaces.test.js +111 -0
- package/dist/workspaces.test.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 client for TQ MCP servers
|
|
3
|
+
* Implements authorization code flow with PKCE (RFC 7636)
|
|
4
|
+
*/
|
|
5
|
+
import * as crypto from 'crypto';
|
|
6
|
+
import * as http from 'http';
|
|
7
|
+
import { URL } from 'url';
|
|
8
|
+
import { loadTokens, saveTokens, clearTokens, restoreTokensFromBackup, } from './token-store.js';
|
|
9
|
+
import { acquireFileLock, getLockPath } from './file-lock.js';
|
|
10
|
+
import { saveWorkspace, clearWorkspace } from './workspace-store.js';
|
|
11
|
+
import { loadConfig } from './config.js';
|
|
12
|
+
/**
|
|
13
|
+
* OAuth 2.0 client with PKCE support
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Number of consecutive 400/401 refresh failures before clearing tokens.
|
|
17
|
+
* A single transient backend error (restart, DB hiccup) returns 400 but the
|
|
18
|
+
* refresh token may still be valid. Only clear after repeated failures.
|
|
19
|
+
*/
|
|
20
|
+
const CLEAR_TOKENS_AFTER_FAILURES = 3;
|
|
21
|
+
export class OAuthClient {
|
|
22
|
+
config;
|
|
23
|
+
accessToken = null;
|
|
24
|
+
refreshToken = null;
|
|
25
|
+
expiresAt = 0;
|
|
26
|
+
consecutiveRefreshFailures = 0;
|
|
27
|
+
/**
|
|
28
|
+
* Set when refresh is definitively rejected (400/401).
|
|
29
|
+
* Prevents further network refresh attempts until re-authorization
|
|
30
|
+
* or fresh tokens are loaded via setTokensFromStorage/authorize.
|
|
31
|
+
*/
|
|
32
|
+
refreshRejected = false;
|
|
33
|
+
/**
|
|
34
|
+
* Deduplicates concurrent refresh calls. When multiple callers invoke
|
|
35
|
+
* refresh() simultaneously (e.g., parallel MCP tool requests), they
|
|
36
|
+
* all await the same in-flight promise instead of each firing a
|
|
37
|
+
* separate token rotation request — which would cause the second
|
|
38
|
+
* request to fail with invalid_grant (token already revoked by first).
|
|
39
|
+
*/
|
|
40
|
+
refreshPromise = null;
|
|
41
|
+
constructor(config) {
|
|
42
|
+
// Resolve defaults from unified config (staging default)
|
|
43
|
+
const tqConfig = loadConfig();
|
|
44
|
+
const defaults = {
|
|
45
|
+
authServerUrl: tqConfig.apiUrl,
|
|
46
|
+
redirectPort: 6274,
|
|
47
|
+
scopes: ['read:own', 'write:own'],
|
|
48
|
+
};
|
|
49
|
+
this.config = {
|
|
50
|
+
...defaults,
|
|
51
|
+
...config,
|
|
52
|
+
};
|
|
53
|
+
// Try to load stored tokens on initialization
|
|
54
|
+
this.loadStoredTokens();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Load tokens from persistent storage
|
|
58
|
+
*/
|
|
59
|
+
loadStoredTokens() {
|
|
60
|
+
const stored = loadTokens();
|
|
61
|
+
if (stored) {
|
|
62
|
+
this.accessToken = stored.accessToken;
|
|
63
|
+
this.refreshToken = stored.refreshToken;
|
|
64
|
+
this.expiresAt = stored.expiresAt;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Set tokens from external storage (used during initialization)
|
|
69
|
+
*/
|
|
70
|
+
setTokensFromStorage(tokens) {
|
|
71
|
+
this.accessToken = tokens.accessToken;
|
|
72
|
+
this.refreshToken = tokens.refreshToken;
|
|
73
|
+
this.expiresAt = tokens.expiresAt;
|
|
74
|
+
// Fresh tokens from external source clear the rejection flag
|
|
75
|
+
this.refreshRejected = false;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Generate PKCE code verifier and challenge
|
|
79
|
+
* @see https://datatracker.ietf.org/doc/html/rfc7636
|
|
80
|
+
*/
|
|
81
|
+
generatePKCE() {
|
|
82
|
+
// Code verifier: 43-128 character random string
|
|
83
|
+
const verifier = crypto.randomBytes(32).toString('base64url');
|
|
84
|
+
// Code challenge: base64url(SHA256(verifier))
|
|
85
|
+
const challenge = crypto
|
|
86
|
+
.createHash('sha256')
|
|
87
|
+
.update(verifier)
|
|
88
|
+
.digest('base64url');
|
|
89
|
+
return { verifier, challenge };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Start local HTTP server to receive OAuth callback
|
|
93
|
+
*/
|
|
94
|
+
async waitForCallback(port, expectedState) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const server = http.createServer((req, res) => {
|
|
97
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
98
|
+
const code = url.searchParams.get('code');
|
|
99
|
+
const error = url.searchParams.get('error');
|
|
100
|
+
const state = url.searchParams.get('state');
|
|
101
|
+
// Validate state parameter to prevent CSRF
|
|
102
|
+
if (state !== expectedState) {
|
|
103
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
104
|
+
res.end(`
|
|
105
|
+
<html>
|
|
106
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
107
|
+
<h1>Authorization Failed</h1>
|
|
108
|
+
<p>Invalid state parameter. This may be a security issue.</p>
|
|
109
|
+
</body>
|
|
110
|
+
</html>
|
|
111
|
+
`);
|
|
112
|
+
server.close();
|
|
113
|
+
reject(new Error('Invalid state parameter'));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (error) {
|
|
117
|
+
const errorDescription = url.searchParams.get('error_description');
|
|
118
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
119
|
+
res.end(`
|
|
120
|
+
<html>
|
|
121
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
122
|
+
<h1>Authorization Failed</h1>
|
|
123
|
+
<p>${errorDescription || error}</p>
|
|
124
|
+
</body>
|
|
125
|
+
</html>
|
|
126
|
+
`);
|
|
127
|
+
server.close();
|
|
128
|
+
reject(new Error(errorDescription || error));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (code) {
|
|
132
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
133
|
+
res.end(`
|
|
134
|
+
<html>
|
|
135
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
136
|
+
<h1>Authorization Successful!</h1>
|
|
137
|
+
<p>You can close this window and return to your terminal.</p>
|
|
138
|
+
<script>setTimeout(() => window.close(), 2000);</script>
|
|
139
|
+
</body>
|
|
140
|
+
</html>
|
|
141
|
+
`);
|
|
142
|
+
server.close();
|
|
143
|
+
resolve(code);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Unknown request
|
|
147
|
+
res.writeHead(404);
|
|
148
|
+
res.end('Not found');
|
|
149
|
+
});
|
|
150
|
+
server.listen(port, () => {
|
|
151
|
+
console.log(`[tq-oauth] OAuth callback server listening on port ${port}`);
|
|
152
|
+
});
|
|
153
|
+
// Timeout after 2 minutes
|
|
154
|
+
const timeout = setTimeout(() => {
|
|
155
|
+
server.close();
|
|
156
|
+
reject(new Error('Authorization timeout - no response received'));
|
|
157
|
+
}, 120000);
|
|
158
|
+
server.on('close', () => {
|
|
159
|
+
clearTimeout(timeout);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Perform full OAuth authorization flow
|
|
165
|
+
* Opens browser for user to authorize, waits for callback
|
|
166
|
+
*/
|
|
167
|
+
async authorize() {
|
|
168
|
+
const { verifier, challenge } = this.generatePKCE();
|
|
169
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
170
|
+
const redirectUri = `http://localhost:${this.config.redirectPort}/oauth/callback`;
|
|
171
|
+
// Build authorization URL (uses versioned API route)
|
|
172
|
+
const authUrl = new URL('/v1/oauth/authorize', this.config.authServerUrl);
|
|
173
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
174
|
+
authUrl.searchParams.set('client_id', this.config.clientId);
|
|
175
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
176
|
+
authUrl.searchParams.set('scope', this.config.scopes.join(' '));
|
|
177
|
+
authUrl.searchParams.set('state', state);
|
|
178
|
+
authUrl.searchParams.set('code_challenge', challenge);
|
|
179
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
180
|
+
// Open browser for user authorization
|
|
181
|
+
console.log('[tq-oauth] Opening browser for authorization...');
|
|
182
|
+
console.log(`[tq-oauth] If browser doesn't open, visit: ${authUrl.toString()}`);
|
|
183
|
+
// Dynamic import of 'open' package (ESM)
|
|
184
|
+
// unref() the child process so it doesn't keep the event loop alive
|
|
185
|
+
const open = (await import('open')).default;
|
|
186
|
+
const cp = await open(authUrl.toString());
|
|
187
|
+
cp.unref();
|
|
188
|
+
// Wait for callback with authorization code
|
|
189
|
+
const code = await this.waitForCallback(this.config.redirectPort, state);
|
|
190
|
+
// Exchange code for tokens
|
|
191
|
+
await this.exchangeCode(code, verifier, redirectUri);
|
|
192
|
+
// Fresh authorization clears any prior rejection
|
|
193
|
+
this.refreshRejected = false;
|
|
194
|
+
console.log('[tq-oauth] Authorization successful!');
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Perform OAuth 2.0 Device Authorization Grant (RFC 8628).
|
|
198
|
+
* Works in headless/remote terminals — no browser redirect needed.
|
|
199
|
+
* Displays a URL and code for the user to authorize on any device.
|
|
200
|
+
*/
|
|
201
|
+
async authorizeDevice() {
|
|
202
|
+
// 1. Request device code
|
|
203
|
+
const deviceUrl = new URL('/v1/oauth/device/authorize', this.config.authServerUrl);
|
|
204
|
+
const deviceRes = await fetch(deviceUrl.toString(), {
|
|
205
|
+
method: 'POST',
|
|
206
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
207
|
+
body: new URLSearchParams({
|
|
208
|
+
client_id: this.config.clientId,
|
|
209
|
+
scope: this.config.scopes.join(' '),
|
|
210
|
+
}),
|
|
211
|
+
});
|
|
212
|
+
if (!deviceRes.ok) {
|
|
213
|
+
const body = await deviceRes.text();
|
|
214
|
+
throw new Error(`Device authorization request failed (${deviceRes.status}): ${body}`);
|
|
215
|
+
}
|
|
216
|
+
const device = (await deviceRes.json());
|
|
217
|
+
// 2. Display code and URL (stderr to keep stdout clean for piped output)
|
|
218
|
+
console.error(`\nVisit: ${device.verification_uri}`);
|
|
219
|
+
console.error(`Enter code: ${device.user_code}\n`);
|
|
220
|
+
// 3. Poll for token
|
|
221
|
+
let interval = device.interval;
|
|
222
|
+
const deadline = Date.now() + device.expires_in * 1000;
|
|
223
|
+
const tokenUrl = new URL('/v1/oauth/token', this.config.authServerUrl);
|
|
224
|
+
while (Date.now() < deadline) {
|
|
225
|
+
await new Promise((r) => setTimeout(r, interval * 1000));
|
|
226
|
+
const res = await fetch(tokenUrl.toString(), {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
229
|
+
body: new URLSearchParams({
|
|
230
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
231
|
+
device_code: device.device_code,
|
|
232
|
+
client_id: this.config.clientId,
|
|
233
|
+
}),
|
|
234
|
+
});
|
|
235
|
+
if (res.ok) {
|
|
236
|
+
const tokens = (await res.json());
|
|
237
|
+
this.setTokens(tokens);
|
|
238
|
+
this.refreshRejected = false;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const bodyText = await res.text();
|
|
242
|
+
let error;
|
|
243
|
+
try {
|
|
244
|
+
error = JSON.parse(bodyText).error;
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
throw new Error(`Device flow polling failed (${res.status}): ${bodyText}`);
|
|
248
|
+
}
|
|
249
|
+
switch (error) {
|
|
250
|
+
case 'authorization_pending':
|
|
251
|
+
continue;
|
|
252
|
+
case 'slow_down':
|
|
253
|
+
interval += 5;
|
|
254
|
+
continue;
|
|
255
|
+
case 'expired_token':
|
|
256
|
+
throw new Error('Device code expired. Please try again.');
|
|
257
|
+
case 'access_denied':
|
|
258
|
+
throw new Error('Authorization denied by user.');
|
|
259
|
+
default:
|
|
260
|
+
throw new Error(`Device flow error: ${error}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
throw new Error('Device code expired. Please try again.');
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Exchange authorization code for access and refresh tokens
|
|
267
|
+
*/
|
|
268
|
+
async exchangeCode(code, codeVerifier, redirectUri) {
|
|
269
|
+
const tokenUrl = new URL('/v1/oauth/token', this.config.authServerUrl);
|
|
270
|
+
const body = new URLSearchParams({
|
|
271
|
+
grant_type: 'authorization_code',
|
|
272
|
+
code,
|
|
273
|
+
redirect_uri: redirectUri,
|
|
274
|
+
client_id: this.config.clientId,
|
|
275
|
+
code_verifier: codeVerifier,
|
|
276
|
+
});
|
|
277
|
+
const response = await fetch(tokenUrl.toString(), {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
280
|
+
body: body.toString(),
|
|
281
|
+
});
|
|
282
|
+
if (!response.ok) {
|
|
283
|
+
const errorBody = await response.text();
|
|
284
|
+
throw new Error(`Token exchange failed: ${response.status} ${response.statusText} - ${errorBody}`);
|
|
285
|
+
}
|
|
286
|
+
const tokens = (await response.json());
|
|
287
|
+
this.setTokens(tokens);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Refresh access token using refresh token.
|
|
291
|
+
*
|
|
292
|
+
* Two layers of coordination:
|
|
293
|
+
* 1. In-process: `refreshPromise` dedups concurrent callers within
|
|
294
|
+
* this OAuthClient instance.
|
|
295
|
+
* 2. Cross-process: a file lock keyed on apiBaseUrl serializes
|
|
296
|
+
* refreshes across MCP server processes. On lock acquire, we
|
|
297
|
+
* re-read the on-disk token — if another process already
|
|
298
|
+
* refreshed while we waited, adopt their tokens and skip the
|
|
299
|
+
* network call. This avoids the "N processes all POST with the
|
|
300
|
+
* same refresh_token, all but one get invalid_grant" race that
|
|
301
|
+
* would otherwise exhaust consecutiveRefreshFailures and trigger
|
|
302
|
+
* clearTokens().
|
|
303
|
+
*/
|
|
304
|
+
async refresh() {
|
|
305
|
+
// If a refresh is already in flight in THIS process, piggyback on it
|
|
306
|
+
if (this.refreshPromise) {
|
|
307
|
+
return this.refreshPromise;
|
|
308
|
+
}
|
|
309
|
+
// Pre-flight checks (synchronous — safe before dedup)
|
|
310
|
+
if (!this.refreshToken) {
|
|
311
|
+
// Last resort: try restoring from backup before giving up
|
|
312
|
+
if (restoreTokensFromBackup()) {
|
|
313
|
+
this.loadStoredTokens();
|
|
314
|
+
if (this.refreshToken) {
|
|
315
|
+
console.error('[tq-oauth] Recovered refresh token from backup, retrying...');
|
|
316
|
+
return this.refresh();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
throw new Error('No refresh token available - authorization required');
|
|
320
|
+
}
|
|
321
|
+
if (this.refreshRejected) {
|
|
322
|
+
throw new Error('Refresh token was rejected - re-authorization required');
|
|
323
|
+
}
|
|
324
|
+
// Create the deduplication promise — wraps lock acquire + conditional
|
|
325
|
+
// refresh + release in a single await-able unit.
|
|
326
|
+
this.refreshPromise = this.refreshWithLock().finally(() => {
|
|
327
|
+
this.refreshPromise = null;
|
|
328
|
+
});
|
|
329
|
+
return this.refreshPromise;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Acquire the cross-process refresh lock, re-check disk state, and
|
|
333
|
+
* either adopt freshly-rotated tokens from disk or call the refresh
|
|
334
|
+
* endpoint. Always releases the lock.
|
|
335
|
+
*/
|
|
336
|
+
async refreshWithLock() {
|
|
337
|
+
const lockPath = getLockPath(this.config.authServerUrl);
|
|
338
|
+
const tokenBeforeLock = this.accessToken;
|
|
339
|
+
const lock = await acquireFileLock(lockPath).catch((err) => {
|
|
340
|
+
// Lock acquisition failed outright — fall back to unlocked refresh.
|
|
341
|
+
// Better to race than to block indefinitely if the lock subsystem
|
|
342
|
+
// itself is broken (e.g. disk full, permissions, etc.).
|
|
343
|
+
console.error('[tq-oauth] File lock acquire failed; proceeding without lock:', err instanceof Error ? err.message : String(err));
|
|
344
|
+
return null;
|
|
345
|
+
});
|
|
346
|
+
if (lock?.brokeStaleLock) {
|
|
347
|
+
console.error('[tq-oauth] Broke stale refresh lock (previous holder likely crashed)');
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
// Check if another process refreshed while we waited for the lock
|
|
351
|
+
const onDisk = loadTokens();
|
|
352
|
+
if (onDisk &&
|
|
353
|
+
onDisk.accessToken &&
|
|
354
|
+
onDisk.accessToken !== tokenBeforeLock &&
|
|
355
|
+
onDisk.expiresAt > Date.now()) {
|
|
356
|
+
// Another process won the race — adopt their tokens instead of
|
|
357
|
+
// rotating ours (which would now 400 with invalid_grant since
|
|
358
|
+
// our refresh_token was already consumed by the winner).
|
|
359
|
+
this.accessToken = onDisk.accessToken;
|
|
360
|
+
this.refreshToken = onDisk.refreshToken;
|
|
361
|
+
this.expiresAt = onDisk.expiresAt;
|
|
362
|
+
this.refreshRejected = false;
|
|
363
|
+
this.consecutiveRefreshFailures = 0;
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
// Nobody else refreshed — do it ourselves
|
|
367
|
+
await this.refreshInternal();
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
lock?.release();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Internal refresh implementation — called only once per dedup window.
|
|
375
|
+
* All concurrent callers share the same promise via refresh().
|
|
376
|
+
*/
|
|
377
|
+
async refreshInternal() {
|
|
378
|
+
const tokenUrl = new URL('/v1/oauth/token', this.config.authServerUrl);
|
|
379
|
+
const body = new URLSearchParams({
|
|
380
|
+
grant_type: 'refresh_token',
|
|
381
|
+
refresh_token: this.refreshToken,
|
|
382
|
+
client_id: this.config.clientId,
|
|
383
|
+
});
|
|
384
|
+
let response;
|
|
385
|
+
try {
|
|
386
|
+
response = await fetch(tokenUrl.toString(), {
|
|
387
|
+
method: 'POST',
|
|
388
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
389
|
+
body: body.toString(),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
catch (fetchError) {
|
|
393
|
+
// Network error — backend is unreachable. NEVER clear tokens for this.
|
|
394
|
+
throw new Error(`Token refresh failed (network error: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}) - will retry`);
|
|
395
|
+
}
|
|
396
|
+
if (!response.ok) {
|
|
397
|
+
if (response.status === 400 || response.status === 401) {
|
|
398
|
+
this.consecutiveRefreshFailures++;
|
|
399
|
+
console.error(`[tq-oauth] Refresh rejected (${response.status}), ` +
|
|
400
|
+
`failure ${this.consecutiveRefreshFailures}/${CLEAR_TOKENS_AFTER_FAILURES}`);
|
|
401
|
+
// Only clear tokens after CLEAR_TOKENS_AFTER_FAILURES consecutive
|
|
402
|
+
// 400/401 responses. A single failure could be a transient backend
|
|
403
|
+
// issue (restart, DB connection drop) where the refresh token is
|
|
404
|
+
// still valid.
|
|
405
|
+
if (this.consecutiveRefreshFailures >= CLEAR_TOKENS_AFTER_FAILURES) {
|
|
406
|
+
console.error('[tq-oauth] Clearing tokens after ' +
|
|
407
|
+
`${this.consecutiveRefreshFailures} consecutive failures`);
|
|
408
|
+
this.accessToken = null;
|
|
409
|
+
this.refreshToken = null;
|
|
410
|
+
this.expiresAt = 0;
|
|
411
|
+
this.refreshRejected = true;
|
|
412
|
+
clearTokens();
|
|
413
|
+
this.consecutiveRefreshFailures = 0;
|
|
414
|
+
throw new Error('Refresh token rejected by server after multiple attempts - re-authorization required');
|
|
415
|
+
}
|
|
416
|
+
// Not enough consecutive failures yet — clear in-memory access token
|
|
417
|
+
// but keep the file on disk so next retry uses the same refresh token.
|
|
418
|
+
this.accessToken = null;
|
|
419
|
+
this.expiresAt = 0;
|
|
420
|
+
throw new Error(`Token refresh failed (${response.status}) - will retry ` +
|
|
421
|
+
`(${CLEAR_TOKENS_AFTER_FAILURES - this.consecutiveRefreshFailures} attempts remaining)`);
|
|
422
|
+
}
|
|
423
|
+
// 5xx or other non-auth errors — transient, don't count as rejections
|
|
424
|
+
throw new Error(`Token refresh failed (${response.status}) - will retry`);
|
|
425
|
+
}
|
|
426
|
+
// Success — reset failure counter and rejected flag
|
|
427
|
+
this.consecutiveRefreshFailures = 0;
|
|
428
|
+
this.refreshRejected = false;
|
|
429
|
+
const tokens = (await response.json());
|
|
430
|
+
this.setTokens(tokens);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Store tokens in memory and persist to disk.
|
|
434
|
+
*
|
|
435
|
+
* Non-refresh grants (authorization_code, device_code, password) carry
|
|
436
|
+
* `tokens.user.workspace_id` — the workspace the user just logged into.
|
|
437
|
+
* Refresh grants omit it. When present we persist it to ~/.tq/workspace.json
|
|
438
|
+
* so MCP clients pick up the selected workspace without a separate round-trip.
|
|
439
|
+
*/
|
|
440
|
+
setTokens(tokens) {
|
|
441
|
+
this.accessToken = tokens.access_token;
|
|
442
|
+
this.refreshToken = tokens.refresh_token;
|
|
443
|
+
// Expire 1 minute early to avoid edge cases
|
|
444
|
+
this.expiresAt = Date.now() + tokens.expires_in * 1000 - 60000;
|
|
445
|
+
// Persist to disk (keyed by apiBaseUrl in the token map)
|
|
446
|
+
saveTokens({
|
|
447
|
+
accessToken: this.accessToken,
|
|
448
|
+
refreshToken: this.refreshToken,
|
|
449
|
+
expiresAt: this.expiresAt,
|
|
450
|
+
apiBaseUrl: this.config.authServerUrl,
|
|
451
|
+
});
|
|
452
|
+
if (tokens.user?.workspace_id) {
|
|
453
|
+
try {
|
|
454
|
+
saveWorkspace(tokens.user.workspace_id);
|
|
455
|
+
}
|
|
456
|
+
catch (err) {
|
|
457
|
+
console.error('[tq-oauth] Failed to persist workspace:', err);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Get a valid access token, refreshing if needed
|
|
463
|
+
* Note: Does NOT auto-authorize - throws if no tokens available
|
|
464
|
+
*/
|
|
465
|
+
async getAccessToken() {
|
|
466
|
+
// No access token but refresh token available — try refresh
|
|
467
|
+
if (!this.accessToken && this.refreshToken) {
|
|
468
|
+
await this.refresh();
|
|
469
|
+
return this.accessToken;
|
|
470
|
+
}
|
|
471
|
+
// No token at all - need to authorize
|
|
472
|
+
if (!this.accessToken) {
|
|
473
|
+
throw new Error('No access token available - authorization required');
|
|
474
|
+
}
|
|
475
|
+
// Token expired - try to refresh
|
|
476
|
+
if (Date.now() >= this.expiresAt) {
|
|
477
|
+
await this.refresh();
|
|
478
|
+
}
|
|
479
|
+
return this.accessToken;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Get access token without auto-refresh (for checking current state)
|
|
483
|
+
*/
|
|
484
|
+
getAccessTokenSync() {
|
|
485
|
+
return this.accessToken;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Check if client has valid tokens
|
|
489
|
+
*/
|
|
490
|
+
isAuthorized() {
|
|
491
|
+
return this.accessToken !== null && Date.now() < this.expiresAt;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Check if tokens exist (may be expired)
|
|
495
|
+
*/
|
|
496
|
+
hasTokens() {
|
|
497
|
+
return this.accessToken !== null;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Attempt a proactive (background) refresh without accumulating toward
|
|
501
|
+
* the nuclear clearTokens() threshold. If refresh fails, the failure
|
|
502
|
+
* counter is reset so background timer ticks can never delete the
|
|
503
|
+
* token file. Only critical-path refreshes (from getAccessToken /
|
|
504
|
+
* ensureAuth) should count toward the threshold.
|
|
505
|
+
*
|
|
506
|
+
* @returns true if refresh succeeded, false otherwise
|
|
507
|
+
*/
|
|
508
|
+
async refreshProactive() {
|
|
509
|
+
try {
|
|
510
|
+
await this.refresh();
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
// Reset counter — proactive failures must never accumulate toward
|
|
515
|
+
// the CLEAR_TOKENS_AFTER_FAILURES threshold that deletes the file.
|
|
516
|
+
this.consecutiveRefreshFailures = 0;
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Clear all tokens (logout). Also clears the persisted workspace selection
|
|
522
|
+
* so the next login starts from a clean state on shared machines.
|
|
523
|
+
*/
|
|
524
|
+
clearTokens() {
|
|
525
|
+
this.accessToken = null;
|
|
526
|
+
this.refreshToken = null;
|
|
527
|
+
this.expiresAt = 0;
|
|
528
|
+
clearTokens();
|
|
529
|
+
clearWorkspace();
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Get token expiration time
|
|
533
|
+
*/
|
|
534
|
+
getExpiresAt() {
|
|
535
|
+
return this.expiresAt;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Get current refresh token (for storage)
|
|
539
|
+
*/
|
|
540
|
+
getRefreshToken() {
|
|
541
|
+
return this.refreshToken;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Get time until expiration in milliseconds
|
|
545
|
+
*/
|
|
546
|
+
getTimeUntilExpiry() {
|
|
547
|
+
return this.expiresAt - Date.now();
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Check if token is expiring soon (within given minutes)
|
|
551
|
+
*/
|
|
552
|
+
isExpiringSoon(withinMinutes = 5) {
|
|
553
|
+
const threshold = withinMinutes * 60 * 1000;
|
|
554
|
+
const timeUntilExpiry = this.getTimeUntilExpiry();
|
|
555
|
+
return timeUntilExpiry > 0 && timeUntilExpiry < threshold;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Check if refresh was definitively rejected (token revoked/invalid).
|
|
559
|
+
* When true, no further refresh attempts will be made until
|
|
560
|
+
* re-authorization or fresh tokens are loaded.
|
|
561
|
+
*/
|
|
562
|
+
isRefreshRejected() {
|
|
563
|
+
return this.refreshRejected;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Reload tokens from storage (useful after external refresh)
|
|
567
|
+
*/
|
|
568
|
+
reloadTokens() {
|
|
569
|
+
this.loadStoredTokens();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
//# sourceMappingURL=oauth-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-client.js","sourceRoot":"","sources":["../src/oauth-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-client.test.d.ts","sourceRoot":"","sources":["../src/oauth-client.test.ts"],"names":[],"mappings":""}
|