mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
feat(vscode-ide-companion): add auth token validation to IDE server (#8491)
This commit is contained in:
@@ -12,6 +12,10 @@ import * as path from 'node:path';
|
||||
import { IDEServer } from './ide-server.js';
|
||||
import type { DiffManager } from './diff-manager.js';
|
||||
|
||||
vi.mock('node:crypto', () => ({
|
||||
randomUUID: vi.fn(() => 'test-auth-token'),
|
||||
}));
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
diffManager: {
|
||||
onDidChange: vi.fn(() => ({ dispose: vi.fn() })),
|
||||
@@ -21,6 +25,7 @@ const mocks = vi.hoisted(() => ({
|
||||
vi.mock('node:fs/promises', () => ({
|
||||
writeFile: vi.fn(() => Promise.resolve(undefined)),
|
||||
unlink: vi.fn(() => Promise.resolve(undefined)),
|
||||
chmod: vi.fn(() => Promise.resolve(undefined)),
|
||||
}));
|
||||
|
||||
vi.mock('node:os', async (importOriginal) => {
|
||||
@@ -134,6 +139,7 @@ describe('IDEServer', () => {
|
||||
port: parseInt(port, 10),
|
||||
workspacePath: expectedWorkspacePaths,
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
@@ -143,6 +149,8 @@ describe('IDEServer', () => {
|
||||
expectedPpidPortFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
});
|
||||
|
||||
it('should set a single folder path', async () => {
|
||||
@@ -169,6 +177,7 @@ describe('IDEServer', () => {
|
||||
port: parseInt(port, 10),
|
||||
workspacePath: '/foo/bar',
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
@@ -178,6 +187,8 @@ describe('IDEServer', () => {
|
||||
expectedPpidPortFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
});
|
||||
|
||||
it('should set an empty string if no folders are open', async () => {
|
||||
@@ -204,6 +215,7 @@ describe('IDEServer', () => {
|
||||
port: parseInt(port, 10),
|
||||
workspacePath: '',
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
@@ -213,6 +225,8 @@ describe('IDEServer', () => {
|
||||
expectedPpidPortFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
});
|
||||
|
||||
it('should update the path when workspace folders change', async () => {
|
||||
@@ -253,6 +267,7 @@ describe('IDEServer', () => {
|
||||
port: parseInt(port, 10),
|
||||
workspacePath: expectedWorkspacePaths,
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
@@ -262,6 +277,8 @@ describe('IDEServer', () => {
|
||||
expectedPpidPortFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
|
||||
// Simulate removing a folder
|
||||
vscodeMock.workspace.workspaceFolders = [{ uri: { fsPath: '/baz/qux' } }];
|
||||
@@ -275,6 +292,7 @@ describe('IDEServer', () => {
|
||||
port: parseInt(port, 10),
|
||||
workspacePath: '/baz/qux',
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
@@ -284,6 +302,8 @@ describe('IDEServer', () => {
|
||||
expectedPpidPortFile,
|
||||
expectedContent2,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
});
|
||||
|
||||
it('should clear env vars and delete port file on stop', async () => {
|
||||
@@ -335,6 +355,7 @@ describe('IDEServer', () => {
|
||||
port: parseInt(port, 10),
|
||||
workspacePath: expectedWorkspacePaths,
|
||||
ppid: process.ppid,
|
||||
authToken: 'test-auth-token',
|
||||
});
|
||||
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||
expectedPortFile,
|
||||
@@ -344,6 +365,94 @@ describe('IDEServer', () => {
|
||||
expectedPpidPortFile,
|
||||
expectedContent,
|
||||
);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600);
|
||||
expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600);
|
||||
},
|
||||
);
|
||||
|
||||
describe('auth token', () => {
|
||||
let port: number;
|
||||
|
||||
beforeEach(async () => {
|
||||
await ideServer.start(mockContext);
|
||||
port = (ideServer as unknown as { port: number }).port;
|
||||
});
|
||||
|
||||
it('should allow request without auth token for backwards compatibility', async () => {
|
||||
const response = await fetch(`http://localhost:${port}/mcp`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: 'initialize',
|
||||
params: {},
|
||||
id: 1,
|
||||
}),
|
||||
});
|
||||
expect(response.status).not.toBe(401);
|
||||
});
|
||||
|
||||
it('should allow request with valid auth token', async () => {
|
||||
const response = await fetch(`http://localhost:${port}/mcp`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer test-auth-token`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: 'initialize',
|
||||
params: {},
|
||||
id: 1,
|
||||
}),
|
||||
});
|
||||
expect(response.status).not.toBe(401);
|
||||
});
|
||||
|
||||
it('should reject request with invalid auth token', async () => {
|
||||
const response = await fetch(`http://localhost:${port}/mcp`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer invalid-token',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: 'initialize',
|
||||
params: {},
|
||||
id: 1,
|
||||
}),
|
||||
});
|
||||
expect(response.status).toBe(401);
|
||||
const body = await response.text();
|
||||
expect(body).toBe('Unauthorized');
|
||||
});
|
||||
|
||||
it('should reject request with malformed auth token', async () => {
|
||||
const malformedHeaders = [
|
||||
'Bearer',
|
||||
'invalid-token',
|
||||
'Bearer token extra',
|
||||
];
|
||||
|
||||
for (const header of malformedHeaders) {
|
||||
const response = await fetch(`http://localhost:${port}/mcp`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: header,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: 'initialize',
|
||||
params: {},
|
||||
id: 1,
|
||||
}),
|
||||
});
|
||||
expect(response.status, `Failed for header: ${header}`).toBe(401);
|
||||
const body = await response.text();
|
||||
expect(body, `Failed for header: ${header}`).toBe('Unauthorized');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user