fix(auth): cancel OAuth callback server when flow is aborted
Add AbortSignal support to startCallbackServer and loginAnthropicOAuth so that pressing Ctrl+C during the browser OAuth flow immediately closes the HTTP server and 5-minute timer instead of leaving the process hung. Wire up an AbortController in the TUI browser OAuth path so the cancel callback aborts the signal on Ctrl+C. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+20
-4
@@ -35,9 +35,9 @@ export interface CallbackServer {
|
||||
* Start a one-shot HTTP server on a random port bound to 127.0.0.1.
|
||||
* Returns { port, waitForCode } immediately.
|
||||
* waitForCode resolves when the browser hits /callback?code=...&state=...
|
||||
* or rejects if timeoutMs elapses first.
|
||||
* or rejects if timeoutMs elapses first, or if signal is aborted.
|
||||
*/
|
||||
export function startCallbackServer(timeoutMs: number): Promise<CallbackServer> {
|
||||
export function startCallbackServer(timeoutMs: number, signal?: AbortSignal): Promise<CallbackServer> {
|
||||
return new Promise((resolveServer, rejectServer) => {
|
||||
let resolveCb!: (v: { code: string; state: string }) => void;
|
||||
let rejectCb!: (e: Error) => void;
|
||||
@@ -79,6 +79,21 @@ export function startCallbackServer(timeoutMs: number): Promise<CallbackServer>
|
||||
rejectCb(new Error('Anthropic OAuth timed out — browser flow was not completed.'));
|
||||
}, timeoutMs);
|
||||
|
||||
// Handle cancellation via AbortSignal
|
||||
if (signal) {
|
||||
if (signal.aborted) {
|
||||
clearTimeout(timer);
|
||||
try { server.close(); } catch { /* already closed */ }
|
||||
rejectServer(new Error('Anthropic OAuth was cancelled.'));
|
||||
return;
|
||||
}
|
||||
signal.addEventListener('abort', () => {
|
||||
clearTimeout(timer);
|
||||
try { server.close(); } catch { /* already closed */ }
|
||||
rejectCb(new Error('Anthropic OAuth was cancelled.'));
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const port = (server.address() as AddressInfo).port;
|
||||
resolveServer({ port, waitForCode });
|
||||
@@ -143,13 +158,14 @@ export async function exchangeCodeForToken(
|
||||
*/
|
||||
export async function loginAnthropicOAuth(
|
||||
onOpen: (url: string) => void,
|
||||
_startServer: typeof startCallbackServer = startCallbackServer,
|
||||
signal?: AbortSignal,
|
||||
_startServer: (timeoutMs: number, signal?: AbortSignal) => Promise<CallbackServer> = startCallbackServer,
|
||||
): Promise<string> {
|
||||
const codeVerifier = generateCodeVerifier();
|
||||
const codeChallenge = generateCodeChallenge(codeVerifier);
|
||||
const state = randomBytes(16).toString('hex');
|
||||
|
||||
const { port, waitForCode } = await _startServer(OAUTH_TIMEOUT_MS);
|
||||
const { port, waitForCode } = await _startServer(OAUTH_TIMEOUT_MS, signal);
|
||||
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
||||
|
||||
const authUrl = new URL(ANTHROPIC_AUTH_URL);
|
||||
|
||||
Reference in New Issue
Block a user