π File detail
services/oauth/auth-code-listener.ts
π― Use case
This file lives under βservices/β, which covers long-lived services (LSP, MCP, OAuth, tool execution, memory, compaction, voice, settings sync, β¦). On the API surface it exposes AuthCodeListener β mainly types, interfaces, or factory objects. Dependencies touch Node HTTP, Node networking, and src. It composes internal code from constants, utils, and client (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import type { IncomingMessage, ServerResponse } from 'http' import { createServer, type Server } from 'http' import type { AddressInfo } from 'net' import { logEvent } from 'src/services/analytics/index.js' import { getOauthConfig } from '../../constants/oauth.js'
π€ Exports (heuristic)
AuthCodeListener
π External import roots
Package roots from from "β¦" (relative paths omitted).
httpnetsrc
π₯οΈ Source preview
import type { IncomingMessage, ServerResponse } from 'http'
import { createServer, type Server } from 'http'
import type { AddressInfo } from 'net'
import { logEvent } from 'src/services/analytics/index.js'
import { getOauthConfig } from '../../constants/oauth.js'
import { logError } from '../../utils/log.js'
import { shouldUseClaudeAIAuth } from './client.js'
/**
* Temporary localhost HTTP server that listens for OAuth authorization code redirects.
*
* When the user authorizes in their browser, the OAuth provider redirects to:
* http://localhost:[port]/callback?code=AUTH_CODE&state=STATE
*
* This server captures that redirect and extracts the auth code.
* Note: This is NOT an OAuth server - it's just a redirect capture mechanism.
*/
export class AuthCodeListener {
private localServer: Server
private port: number = 0
private promiseResolver: ((authorizationCode: string) => void) | null = null
private promiseRejecter: ((error: Error) => void) | null = null
private expectedState: string | null = null // State parameter for CSRF protection
private pendingResponse: ServerResponse | null = null // Response object for final redirect
private callbackPath: string // Configurable callback path
constructor(callbackPath: string = '/callback') {
this.localServer = createServer()
this.callbackPath = callbackPath
}
/**
* Starts listening on an OS-assigned port and returns the port number.
* This avoids race conditions by keeping the server open until it's used.
* @param port Optional specific port to use. If not provided, uses OS-assigned port.
*/
async start(port?: number): Promise<number> {
return new Promise((resolve, reject) => {
this.localServer.once('error', err => {
reject(
new Error(`Failed to start OAuth callback server: ${err.message}`),
)
})
// Listen on specified port or 0 to let the OS assign an available port
this.localServer.listen(port ?? 0, 'localhost', () => {
const address = this.localServer.address() as AddressInfo
this.port = address.port
resolve(this.port)
})
})
}
getPort(): number {
return this.port
}
hasPendingResponse(): boolean {
return this.pendingResponse !== null
}
async waitForAuthorization(
state: string,
onReady: () => Promise<void>,
): Promise<string> {
return new Promise<string>((resolve, reject) => {
this.promiseResolver = resolve
this.promiseRejecter = reject
this.expectedState = state
this.startLocalListener(onReady)
})
}
/**
* Completes the OAuth flow by redirecting the user's browser to a success page.
* Different success pages are shown based on the granted scopes.
* @param scopes The OAuth scopes that were granted
* @param customHandler Optional custom handler to serve response instead of redirecting
*/
handleSuccessRedirect(
scopes: string[],
customHandler?: (res: ServerResponse, scopes: string[]) => void,
): void {
if (!this.pendingResponse) return
// If custom handler provided, use it instead of default redirect
if (customHandler) {
customHandler(this.pendingResponse, scopes)
this.pendingResponse = null
logEvent('tengu_oauth_automatic_redirect', { custom_handler: true })
return
}
// Default behavior: Choose success page based on granted permissions
const successUrl = shouldUseClaudeAIAuth(scopes)
? getOauthConfig().CLAUDEAI_SUCCESS_URL
: getOauthConfig().CONSOLE_SUCCESS_URL
// Send browser to success page
this.pendingResponse.writeHead(302, { Location: successUrl })
this.pendingResponse.end()
this.pendingResponse = null
logEvent('tengu_oauth_automatic_redirect', {})
}
/**
* Handles error case by sending a redirect to the appropriate success page with an error indicator,
* ensuring the browser flow is completed properly.
*/
handleErrorRedirect(): void {
if (!this.pendingResponse) return
// TODO: swap to a different url once we have an error page
const errorUrl = getOauthConfig().CLAUDEAI_SUCCESS_URL
// Send browser to error page
this.pendingResponse.writeHead(302, { Location: errorUrl })
this.pendingResponse.end()
this.pendingResponse = null
logEvent('tengu_oauth_automatic_redirect_error', {})
}
private startLocalListener(onReady: () => Promise<void>): void {
// Server is already created and listening, just set up handlers
this.localServer.on('request', this.handleRedirect.bind(this))
this.localServer.on('error', this.handleError.bind(this))
// Server is already listening, so we can call onReady immediately
void onReady()
}
private handleRedirect(req: IncomingMessage, res: ServerResponse): void {
const parsedUrl = new URL(
req.url || '',
`http://${req.headers.host || 'localhost'}`,
)
if (parsedUrl.pathname !== this.callbackPath) {
res.writeHead(404)
res.end()
return
}
const authCode = parsedUrl.searchParams.get('code') ?? undefined
const state = parsedUrl.searchParams.get('state') ?? undefined
this.validateAndRespond(authCode, state, res)
}
private validateAndRespond(
authCode: string | undefined,
state: string | undefined,
res: ServerResponse,
): void {
if (!authCode) {
res.writeHead(400)
res.end('Authorization code not found')
this.reject(new Error('No authorization code received'))
return
}
if (state !== this.expectedState) {
res.writeHead(400)
res.end('Invalid state parameter')
this.reject(new Error('Invalid state parameter'))
return
}
// Store the response for later redirect
this.pendingResponse = res
this.resolve(authCode)
}
private handleError(err: Error): void {
logError(err)
this.close()
this.reject(err)
}
private resolve(authorizationCode: string): void {
if (this.promiseResolver) {
this.promiseResolver(authorizationCode)
this.promiseResolver = null
this.promiseRejecter = null
}
}
private reject(error: Error): void {
if (this.promiseRejecter) {
this.promiseRejecter(error)
this.promiseResolver = null
this.promiseRejecter = null
}
}
close(): void {
// If we have a pending response, send a redirect before closing
if (this.pendingResponse) {
this.handleErrorRedirect()
}
if (this.localServer) {
// Remove all listeners to prevent memory leaks
this.localServer.removeAllListeners()
this.localServer.close()
}
}
}