mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
fix(core): resolve PKCE length issue and stabilize OAuth redirect port (#16815)
This commit is contained in:
@@ -1006,6 +1006,154 @@ describe('MCPOAuthProvider', () => {
|
||||
|
||||
global.setTimeout = originalSetTimeout;
|
||||
});
|
||||
|
||||
it('should use port from redirectUri if provided', async () => {
|
||||
const configWithPort: MCPOAuthConfig = {
|
||||
...mockConfig,
|
||||
redirectUri: 'http://localhost:12345/oauth/callback',
|
||||
};
|
||||
|
||||
let callbackHandler: unknown;
|
||||
vi.mocked(http.createServer).mockImplementation((handler) => {
|
||||
callbackHandler = handler;
|
||||
return mockHttpServer as unknown as http.Server;
|
||||
});
|
||||
|
||||
mockHttpServer.listen.mockImplementation((port, callback) => {
|
||||
callback?.();
|
||||
setTimeout(() => {
|
||||
const mockReq = {
|
||||
url: '/oauth/callback?code=auth_code_123&state=bW9ja19zdGF0ZV8xNl9ieXRlcw',
|
||||
};
|
||||
const mockRes = {
|
||||
writeHead: vi.fn(),
|
||||
end: vi.fn(),
|
||||
};
|
||||
(callbackHandler as (req: unknown, res: unknown) => void)(
|
||||
mockReq,
|
||||
mockRes,
|
||||
);
|
||||
}, 10);
|
||||
});
|
||||
mockHttpServer.address.mockReturnValue({
|
||||
port: 12345,
|
||||
address: '127.0.0.1',
|
||||
family: 'IPv4',
|
||||
});
|
||||
|
||||
mockFetch.mockResolvedValueOnce(
|
||||
createMockResponse({
|
||||
ok: true,
|
||||
contentType: 'application/json',
|
||||
text: JSON.stringify(mockTokenResponse),
|
||||
json: mockTokenResponse,
|
||||
}),
|
||||
);
|
||||
|
||||
const authProvider = new MCPOAuthProvider();
|
||||
await authProvider.authenticate('test-server', configWithPort);
|
||||
|
||||
expect(mockHttpServer.listen).toHaveBeenCalledWith(
|
||||
12345,
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('should ignore invalid ports in redirectUri', async () => {
|
||||
const configWithInvalidPort: MCPOAuthConfig = {
|
||||
...mockConfig,
|
||||
redirectUri: 'http://localhost:invalid/oauth/callback',
|
||||
};
|
||||
|
||||
let callbackHandler: unknown;
|
||||
vi.mocked(http.createServer).mockImplementation((handler) => {
|
||||
callbackHandler = handler;
|
||||
return mockHttpServer as unknown as http.Server;
|
||||
});
|
||||
|
||||
mockHttpServer.listen.mockImplementation((port, callback) => {
|
||||
callback?.();
|
||||
setTimeout(() => {
|
||||
const mockReq = {
|
||||
url: '/oauth/callback?code=auth_code_123&state=bW9ja19zdGF0ZV8xNl9ieXRlcw',
|
||||
};
|
||||
const mockRes = {
|
||||
writeHead: vi.fn(),
|
||||
end: vi.fn(),
|
||||
};
|
||||
(callbackHandler as (req: unknown, res: unknown) => void)(
|
||||
mockReq,
|
||||
mockRes,
|
||||
);
|
||||
}, 10);
|
||||
});
|
||||
|
||||
mockFetch.mockResolvedValueOnce(
|
||||
createMockResponse({
|
||||
ok: true,
|
||||
contentType: 'application/json',
|
||||
text: JSON.stringify(mockTokenResponse),
|
||||
json: mockTokenResponse,
|
||||
}),
|
||||
);
|
||||
|
||||
const authProvider = new MCPOAuthProvider();
|
||||
await authProvider.authenticate('test-server', configWithInvalidPort);
|
||||
|
||||
// Should be called with 0 (OS assigned) because the port was invalid
|
||||
expect(mockHttpServer.listen).toHaveBeenCalledWith(
|
||||
0,
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not default to privileged ports when redirectUri has no port', async () => {
|
||||
const configNoPort: MCPOAuthConfig = {
|
||||
...mockConfig,
|
||||
redirectUri: 'http://localhost/oauth/callback',
|
||||
};
|
||||
|
||||
let callbackHandler: unknown;
|
||||
vi.mocked(http.createServer).mockImplementation((handler) => {
|
||||
callbackHandler = handler;
|
||||
return mockHttpServer as unknown as http.Server;
|
||||
});
|
||||
|
||||
mockHttpServer.listen.mockImplementation((port, callback) => {
|
||||
callback?.();
|
||||
setTimeout(() => {
|
||||
const mockReq = {
|
||||
url: '/oauth/callback?code=auth_code_123&state=bW9ja19zdGF0ZV8xNl9ieXRlcw',
|
||||
};
|
||||
const mockRes = {
|
||||
writeHead: vi.fn(),
|
||||
end: vi.fn(),
|
||||
};
|
||||
(callbackHandler as (req: unknown, res: unknown) => void)(
|
||||
mockReq,
|
||||
mockRes,
|
||||
);
|
||||
}, 10);
|
||||
});
|
||||
|
||||
mockFetch.mockResolvedValueOnce(
|
||||
createMockResponse({
|
||||
ok: true,
|
||||
contentType: 'application/json',
|
||||
text: JSON.stringify(mockTokenResponse),
|
||||
json: mockTokenResponse,
|
||||
}),
|
||||
);
|
||||
|
||||
const authProvider = new MCPOAuthProvider();
|
||||
await authProvider.authenticate('test-server', configNoPort);
|
||||
|
||||
// Should be called with 0 (OS assigned), not 80
|
||||
expect(mockHttpServer.listen).toHaveBeenCalledWith(
|
||||
0,
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshAccessToken', () => {
|
||||
@@ -1286,7 +1434,7 @@ describe('MCPOAuthProvider', () => {
|
||||
const authProvider = new MCPOAuthProvider();
|
||||
await authProvider.authenticate('test-server', mockConfig);
|
||||
|
||||
expect(crypto.randomBytes).toHaveBeenCalledWith(32); // code verifier
|
||||
expect(crypto.randomBytes).toHaveBeenCalledWith(64); // code verifier
|
||||
expect(crypto.randomBytes).toHaveBeenCalledWith(16); // state
|
||||
expect(crypto.createHash).toHaveBeenCalledWith('sha256');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user