mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
fix(security) - Use emitFeedback (#11961)
Co-authored-by: gemini-cli-robot <gemini-cli-robot@google.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
import type { KeychainTokenStorage } from './keychain-token-storage.js';
|
import type { KeychainTokenStorage } from './keychain-token-storage.js';
|
||||||
import type { OAuthCredentials } from './types.js';
|
import type { OAuthCredentials } from './types.js';
|
||||||
|
import { coreEvents } from '../../utils/events.js';
|
||||||
|
|
||||||
// Hoist the mock to be available in the vi.mock factory
|
// Hoist the mock to be available in the vi.mock factory
|
||||||
const mockKeytar = vi.hoisted(() => ({
|
const mockKeytar = vi.hoisted(() => ({
|
||||||
@@ -30,6 +31,12 @@ vi.mock('node:crypto', () => ({
|
|||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../utils/events.js', () => ({
|
||||||
|
coreEvents: {
|
||||||
|
emitFeedback: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
describe('KeychainTokenStorage', () => {
|
describe('KeychainTokenStorage', () => {
|
||||||
let storage: KeychainTokenStorage;
|
let storage: KeychainTokenStorage;
|
||||||
|
|
||||||
@@ -82,7 +89,8 @@ describe('KeychainTokenStorage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if keytar fails to set password', async () => {
|
it('should return false if keytar fails to set password', async () => {
|
||||||
mockKeytar.setPassword.mockRejectedValue(new Error('write error'));
|
const error = new Error('write error');
|
||||||
|
mockKeytar.setPassword.mockRejectedValue(error);
|
||||||
const isAvailable = await storage.checkKeychainAvailability();
|
const isAvailable = await storage.checkKeychainAvailability();
|
||||||
expect(isAvailable).toBe(false);
|
expect(isAvailable).toBe(false);
|
||||||
});
|
});
|
||||||
@@ -265,14 +273,20 @@ describe('KeychainTokenStorage', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return an empty array on error', async () => {
|
it('should return an empty array on error', async () => {
|
||||||
mockKeytar.findCredentials.mockRejectedValue(new Error('find error'));
|
const error = new Error('find error');
|
||||||
|
mockKeytar.findCredentials.mockRejectedValue(error);
|
||||||
const result = await storage.listServers();
|
const result = await storage.listServers();
|
||||||
expect(result).toEqual([]);
|
expect(result).toEqual([]);
|
||||||
|
expect(coreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||||
|
'error',
|
||||||
|
'Failed to list servers from keychain',
|
||||||
|
error,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getAllCredentials', () => {
|
describe('getAllCredentials', () => {
|
||||||
it('should return a map of all valid credentials', async () => {
|
it('should return a map of all valid credentials and emit feedback for invalid ones', async () => {
|
||||||
const creds2 = {
|
const creds2 = {
|
||||||
...validCredentials,
|
...validCredentials,
|
||||||
serverName: 'server2',
|
serverName: 'server2',
|
||||||
@@ -310,6 +324,30 @@ describe('KeychainTokenStorage', () => {
|
|||||||
expect(result.has('expired-server')).toBe(false);
|
expect(result.has('expired-server')).toBe(false);
|
||||||
expect(result.has('bad-server')).toBe(false);
|
expect(result.has('bad-server')).toBe(false);
|
||||||
expect(result.has('invalid-server')).toBe(false);
|
expect(result.has('invalid-server')).toBe(false);
|
||||||
|
|
||||||
|
expect(coreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||||
|
'error',
|
||||||
|
'Failed to parse credentials for bad-server',
|
||||||
|
expect.any(SyntaxError),
|
||||||
|
);
|
||||||
|
expect(coreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||||
|
'error',
|
||||||
|
'Failed to parse credentials for invalid-server',
|
||||||
|
expect.any(Error),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit feedback and return empty map if findCredentials fails', async () => {
|
||||||
|
const error = new Error('find all error');
|
||||||
|
mockKeytar.findCredentials.mockRejectedValue(error);
|
||||||
|
|
||||||
|
const result = await storage.getAllCredentials();
|
||||||
|
expect(result.size).toBe(0);
|
||||||
|
expect(coreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||||
|
'error',
|
||||||
|
'Failed to get all credentials from keychain',
|
||||||
|
error,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import * as crypto from 'node:crypto';
|
import * as crypto from 'node:crypto';
|
||||||
import { BaseTokenStorage } from './base-token-storage.js';
|
import { BaseTokenStorage } from './base-token-storage.js';
|
||||||
import type { OAuthCredentials } from './types.js';
|
import type { OAuthCredentials } from './types.js';
|
||||||
|
import { coreEvents } from '../../utils/events.js';
|
||||||
|
|
||||||
interface Keytar {
|
interface Keytar {
|
||||||
getPassword(service: string, account: string): Promise<string | null>;
|
getPassword(service: string, account: string): Promise<string | null>;
|
||||||
@@ -42,7 +43,7 @@ export class KeychainTokenStorage extends BaseTokenStorage {
|
|||||||
const module = await import(moduleName);
|
const module = await import(moduleName);
|
||||||
this.keytarModule = module.default || module;
|
this.keytarModule = module.default || module;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
coreEvents.emitFeedback('error', "Failed to load 'keytar' module", error);
|
||||||
}
|
}
|
||||||
return this.keytarModule;
|
return this.keytarModule;
|
||||||
}
|
}
|
||||||
@@ -139,7 +140,11 @@ export class KeychainTokenStorage extends BaseTokenStorage {
|
|||||||
.filter((cred) => !cred.account.startsWith(KEYCHAIN_TEST_PREFIX))
|
.filter((cred) => !cred.account.startsWith(KEYCHAIN_TEST_PREFIX))
|
||||||
.map((cred: { account: string }) => cred.account);
|
.map((cred: { account: string }) => cred.account);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to list servers from keychain:', error);
|
coreEvents.emitFeedback(
|
||||||
|
'error',
|
||||||
|
'Failed to list servers from keychain',
|
||||||
|
error,
|
||||||
|
);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,14 +172,19 @@ export class KeychainTokenStorage extends BaseTokenStorage {
|
|||||||
result.set(cred.account, data);
|
result.set(cred.account, data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
coreEvents.emitFeedback(
|
||||||
`Failed to parse credentials for ${cred.account}:`,
|
'error',
|
||||||
|
`Failed to parse credentials for ${cred.account}`,
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to get all credentials from keychain:', error);
|
coreEvents.emitFeedback(
|
||||||
|
'error',
|
||||||
|
'Failed to get all credentials from keychain',
|
||||||
|
error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
Reference in New Issue
Block a user