bloby-bot 0.30.0 → 0.30.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/package.json +1 -1
- package/worker/codex-auth.ts +54 -15
package/package.json
CHANGED
package/worker/codex-auth.ts
CHANGED
|
@@ -44,7 +44,7 @@ const REFRESH_LEEWAY_MS = 5 * 60 * 1000;
|
|
|
44
44
|
|
|
45
45
|
let codeVerifier: string | null = null;
|
|
46
46
|
let oauthState: string | null = null;
|
|
47
|
-
let
|
|
47
|
+
let callbackServers: http.Server[] = [];
|
|
48
48
|
|
|
49
49
|
interface AuthDotJson {
|
|
50
50
|
OPENAI_API_KEY: string | null;
|
|
@@ -236,7 +236,7 @@ export function startCodexOAuth(): Promise<{ success: boolean; authUrl?: string;
|
|
|
236
236
|
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
|
|
237
237
|
oauthState = crypto.randomUUID();
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
const handleCallback = (req: http.IncomingMessage, res: http.ServerResponse) => {
|
|
240
240
|
if (!req.url?.startsWith('/auth/callback')) {
|
|
241
241
|
res.writeHead(404);
|
|
242
242
|
res.end();
|
|
@@ -247,6 +247,7 @@ export function startCodexOAuth(): Promise<{ success: boolean; authUrl?: string;
|
|
|
247
247
|
const code = url.searchParams.get('code');
|
|
248
248
|
const returnedState = url.searchParams.get('state');
|
|
249
249
|
const error = url.searchParams.get('error');
|
|
250
|
+
log.ok(`Codex OAuth callback hit (code=${code ? 'yes' : 'no'}, state=${returnedState === oauthState ? 'ok' : 'mismatch'}, error=${error || 'none'})`);
|
|
250
251
|
|
|
251
252
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
252
253
|
res.end(`<html><body style="font-family:sans-serif;text-align:center;padding:60px;background:#0a0a0a;color:#fff">
|
|
@@ -258,11 +259,24 @@ export function startCodexOAuth(): Promise<{ success: boolean; authUrl?: string;
|
|
|
258
259
|
|
|
259
260
|
if (error || !code || returnedState !== oauthState) return;
|
|
260
261
|
exchangeCode(code);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
callbackServer.listen(OAUTH_CONFIG.PORT, '127.0.0.1', () => {
|
|
264
|
-
log.ok(`Codex OAuth callback server on port ${OAUTH_CONFIG.PORT}`);
|
|
262
|
+
};
|
|
265
263
|
|
|
264
|
+
// Bind BOTH IPv4 and IPv6 loopback. On dual-stack systems where
|
|
265
|
+
// `localhost` resolves to ::1 first, an IPv4-only bind silently fails
|
|
266
|
+
// when the browser hits the callback URL.
|
|
267
|
+
const bindHosts: Array<{ host: string; required: boolean }> = [
|
|
268
|
+
{ host: '127.0.0.1', required: true },
|
|
269
|
+
{ host: '::1', required: false }, // tolerate machines without IPv6
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
let resolved = false;
|
|
273
|
+
let pendingBinds = bindHosts.length;
|
|
274
|
+
let bindFailures = 0;
|
|
275
|
+
|
|
276
|
+
const finishWithSuccess = () => {
|
|
277
|
+
if (resolved) return;
|
|
278
|
+
resolved = true;
|
|
279
|
+
log.ok(`Codex OAuth callback servers listening on port ${OAUTH_CONFIG.PORT} (${callbackServers.length} bind${callbackServers.length === 1 ? '' : 's'})`);
|
|
266
280
|
const params = new URLSearchParams({
|
|
267
281
|
response_type: 'code',
|
|
268
282
|
client_id: OAUTH_CONFIG.CLIENT_ID,
|
|
@@ -274,16 +288,41 @@ export function startCodexOAuth(): Promise<{ success: boolean; authUrl?: string;
|
|
|
274
288
|
id_token_add_organizations: 'true',
|
|
275
289
|
codex_cli_simplified_flow: 'true',
|
|
276
290
|
});
|
|
277
|
-
|
|
278
291
|
resolve({ success: true, authUrl: `${OAUTH_CONFIG.AUTHORIZE_URL}?${params.toString()}` });
|
|
279
|
-
}
|
|
292
|
+
};
|
|
280
293
|
|
|
281
|
-
|
|
282
|
-
|
|
294
|
+
const finishWithError = (err: any) => {
|
|
295
|
+
if (resolved) return;
|
|
296
|
+
resolved = true;
|
|
297
|
+
stopCallbackServer();
|
|
298
|
+
const error = err?.code === 'EADDRINUSE'
|
|
283
299
|
? `Port ${OAUTH_CONFIG.PORT} is busy. Close other Codex instances.`
|
|
284
|
-
: err
|
|
300
|
+
: (err?.message || String(err));
|
|
285
301
|
resolve({ success: false, error });
|
|
286
|
-
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
for (const { host, required } of bindHosts) {
|
|
305
|
+
const server = http.createServer(handleCallback);
|
|
306
|
+
|
|
307
|
+
server.once('error', (err: any) => {
|
|
308
|
+
log.warn(`Codex OAuth bind ${host}:${OAUTH_CONFIG.PORT} failed — ${err.code || err.message}`);
|
|
309
|
+
if (required) {
|
|
310
|
+
finishWithError(err);
|
|
311
|
+
} else {
|
|
312
|
+
bindFailures++;
|
|
313
|
+
pendingBinds--;
|
|
314
|
+
// If only the optional bind failed but we already have at least one
|
|
315
|
+
// server up, that's still a success.
|
|
316
|
+
if (pendingBinds === 0 && callbackServers.length > 0) finishWithSuccess();
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
server.listen(OAUTH_CONFIG.PORT, host, () => {
|
|
321
|
+
callbackServers.push(server);
|
|
322
|
+
pendingBinds--;
|
|
323
|
+
if (pendingBinds === 0) finishWithSuccess();
|
|
324
|
+
});
|
|
325
|
+
}
|
|
287
326
|
});
|
|
288
327
|
}
|
|
289
328
|
|
|
@@ -356,8 +395,8 @@ export function readCodexAccessToken(): string | null {
|
|
|
356
395
|
/* ── Helpers ── */
|
|
357
396
|
|
|
358
397
|
function stopCallbackServer(): void {
|
|
359
|
-
|
|
360
|
-
try {
|
|
361
|
-
callbackServer = null;
|
|
398
|
+
for (const server of callbackServers) {
|
|
399
|
+
try { server.close(); } catch {}
|
|
362
400
|
}
|
|
401
|
+
callbackServers = [];
|
|
363
402
|
}
|