summaryrefslogtreecommitdiff
path: root/packages/core/src
diff options
context:
space:
mode:
authorYongsheng Xu <[email protected]>2025-07-18 09:55:26 +0800
committerGitHub <[email protected]>2025-07-18 01:55:26 +0000
commit91c69731c7be59adbea017d3c8f30281989b6f0a (patch)
tree1d2bdb71b942c2eabebd663123044ddc1d13deb7 /packages/core/src
parent2f5eecfc49eb4ccb3b5568dd37f2bc2b938a9438 (diff)
feat(auth): Enhance OAuth callback for robust Docker support (#3532)
Co-authored-by: Scott Densmore <[email protected]>
Diffstat (limited to 'packages/core/src')
-rw-r--r--packages/core/src/code_assist/oauth2.test.ts2
-rw-r--r--packages/core/src/code_assist/oauth2.ts42
2 files changed, 40 insertions, 4 deletions
diff --git a/packages/core/src/code_assist/oauth2.test.ts b/packages/core/src/code_assist/oauth2.test.ts
index 7fa98e17..9d8fb892 100644
--- a/packages/core/src/code_assist/oauth2.test.ts
+++ b/packages/core/src/code_assist/oauth2.test.ts
@@ -105,7 +105,7 @@ describe('oauth2', () => {
let capturedPort = 0;
const mockHttpServer = {
- listen: vi.fn((port: number, callback?: () => void) => {
+ listen: vi.fn((port: number, _host: string, callback?: () => void) => {
capturedPort = port;
if (callback) {
callback();
diff --git a/packages/core/src/code_assist/oauth2.ts b/packages/core/src/code_assist/oauth2.ts
index 3c3f7055..ac45177c 100644
--- a/packages/core/src/code_assist/oauth2.ts
+++ b/packages/core/src/code_assist/oauth2.ts
@@ -139,13 +139,33 @@ export async function getOauthClient(
} else {
const webLogin = await authWithWeb(client);
- // This does basically nothing, as it isn't show to the user.
console.log(
`\n\nCode Assist login required.\n` +
`Attempting to open authentication page in your browser.\n` +
`Otherwise navigate to:\n\n${webLogin.authUrl}\n\n`,
);
- await open(webLogin.authUrl);
+ try {
+ // Attempt to open the authentication URL in the default browser.
+ // We do not use the `wait` option here because the main script's execution
+ // is already paused by `loginCompletePromise`, which awaits the server callback.
+ const childProcess = await open(webLogin.authUrl);
+
+ // IMPORTANT: Attach an error handler to the returned child process.
+ // Without this, if `open` fails to spawn a process (e.g., `xdg-open` is not found
+ // in a minimal Docker container), it will emit an unhandled 'error' event,
+ // causing the entire Node.js process to crash.
+ childProcess.on('error', (_) => {
+ console.error(
+ 'Failed to open browser automatically. Please open the URL manually:',
+ );
+ console.error(webLogin.authUrl);
+ });
+ } catch (err) {
+ console.error(
+ 'An unexpected error occurred while trying to open the browser:',
+ err,
+ );
+ }
console.log('Waiting for authentication...');
await webLogin.loginCompletePromise;
@@ -202,6 +222,12 @@ async function authWithUserCode(client: OAuth2Client): Promise<boolean> {
async function authWithWeb(client: OAuth2Client): Promise<OauthWebLogin> {
const port = await getAvailablePort();
+ // The hostname used for the HTTP server binding (e.g., '0.0.0.0' in Docker).
+ const host = process.env.OAUTH_CALLBACK_HOST || 'localhost';
+ // The `redirectUri` sent to Google's authorization server MUST use a loopback IP literal
+ // (i.e., 'localhost' or '127.0.0.1'). This is a strict security policy for credentials of
+ // type 'Desktop app' or 'Web application' (when using loopback flow) to mitigate
+ // authorization code interception attacks.
const redirectUri = `http://localhost:${port}/oauth2callback`;
const state = crypto.randomBytes(32).toString('hex');
const authUrl = client.generateAuthUrl({
@@ -259,7 +285,7 @@ async function authWithWeb(client: OAuth2Client): Promise<OauthWebLogin> {
server.close();
}
});
- server.listen(port);
+ server.listen(port, host);
});
return {
@@ -272,6 +298,16 @@ export function getAvailablePort(): Promise<number> {
return new Promise((resolve, reject) => {
let port = 0;
try {
+ const portStr = process.env.OAUTH_CALLBACK_PORT;
+ if (portStr) {
+ port = parseInt(portStr, 10);
+ if (isNaN(port) || port <= 0 || port > 65535) {
+ return reject(
+ new Error(`Invalid value for OAUTH_CALLBACK_PORT: "${portStr}"`),
+ );
+ }
+ return resolve(port);
+ }
const server = net.createServer();
server.listen(0, () => {
const address = server.address()! as net.AddressInfo;