diff options
| author | Marat Boshernitsan <[email protected]> | 2025-08-19 13:16:06 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-19 20:16:06 +0000 |
| commit | d543c8339acd51a4cf8ade23e896249d3321fc1f (patch) | |
| tree | 46f10d08ae295f157c0da2e73e93bef562414a64 /packages/core/src/utils/user_account.ts | |
| parent | b561d3bbede8756ec7c3734ed9cb6bb5cbc9ee61 (diff) | |
fix(core): harden user account caching (#6501)
Diffstat (limited to 'packages/core/src/utils/user_account.ts')
| -rw-r--r-- | packages/core/src/utils/user_account.ts | 112 |
1 files changed, 64 insertions, 48 deletions
diff --git a/packages/core/src/utils/user_account.ts b/packages/core/src/utils/user_account.ts index 6701dfe3..18b7dcf4 100644 --- a/packages/core/src/utils/user_account.ts +++ b/packages/core/src/utils/user_account.ts @@ -5,7 +5,7 @@ */ import path from 'node:path'; -import { promises as fsp, existsSync, readFileSync } from 'node:fs'; +import { promises as fsp, readFileSync } from 'node:fs'; import * as os from 'os'; import { GEMINI_DIR, GOOGLE_ACCOUNTS_FILENAME } from './paths.js'; @@ -18,21 +18,66 @@ function getGoogleAccountsCachePath(): string { return path.join(os.homedir(), GEMINI_DIR, GOOGLE_ACCOUNTS_FILENAME); } +/** + * Parses and validates the string content of an accounts file. + * @param content The raw string content from the file. + * @returns A valid UserAccounts object. + */ +function parseAndValidateAccounts(content: string): UserAccounts { + const defaultState = { active: null, old: [] }; + if (!content.trim()) { + return defaultState; + } + + const parsed = JSON.parse(content); + + // Inlined validation logic + if (typeof parsed !== 'object' || parsed === null) { + console.log('Invalid accounts file schema, starting fresh.'); + return defaultState; + } + const { active, old } = parsed as Partial<UserAccounts>; + const isValid = + (active === undefined || active === null || typeof active === 'string') && + (old === undefined || + (Array.isArray(old) && old.every((i) => typeof i === 'string'))); + + if (!isValid) { + console.log('Invalid accounts file schema, starting fresh.'); + return defaultState; + } + + return { + active: parsed.active ?? null, + old: parsed.old ?? [], + }; +} + +function readAccountsSync(filePath: string): UserAccounts { + const defaultState = { active: null, old: [] }; + try { + const content = readFileSync(filePath, 'utf-8'); + return parseAndValidateAccounts(content); + } catch (error) { + if (error instanceof Error && 'code' in error && error.code === 'ENOENT') { + return defaultState; + } + console.log('Error during sync read of accounts, starting fresh.', error); + return defaultState; + } +} + async function readAccounts(filePath: string): Promise<UserAccounts> { + const defaultState = { active: null, old: [] }; try { const content = await fsp.readFile(filePath, 'utf-8'); - if (!content.trim()) { - return { active: null, old: [] }; - } - return JSON.parse(content) as UserAccounts; + return parseAndValidateAccounts(content); } catch (error) { if (error instanceof Error && 'code' in error && error.code === 'ENOENT') { - // File doesn't exist, which is fine. - return { active: null, old: [] }; + return defaultState; } - // File is corrupted or not valid JSON, start with a fresh object. - console.debug('Could not parse accounts file, starting fresh.', error); - return { active: null, old: [] }; + console.log('Could not parse accounts file, starting fresh.', error); + return defaultState; } } @@ -56,52 +101,23 @@ export async function cacheGoogleAccount(email: string): Promise<void> { } export function getCachedGoogleAccount(): string | null { - try { - const filePath = getGoogleAccountsCachePath(); - if (existsSync(filePath)) { - const content = readFileSync(filePath, 'utf-8').trim(); - if (!content) { - return null; - } - const accounts: UserAccounts = JSON.parse(content); - return accounts.active; - } - return null; - } catch (error) { - console.debug('Error reading cached Google Account:', error); - return null; - } + const filePath = getGoogleAccountsCachePath(); + const accounts = readAccountsSync(filePath); + return accounts.active; } export function getLifetimeGoogleAccounts(): number { - try { - const filePath = getGoogleAccountsCachePath(); - if (!existsSync(filePath)) { - return 0; - } - - const content = readFileSync(filePath, 'utf-8').trim(); - if (!content) { - return 0; - } - const accounts: UserAccounts = JSON.parse(content); - let count = accounts.old.length; - if (accounts.active) { - count++; - } - return count; - } catch (error) { - console.debug('Error reading lifetime Google Accounts:', error); - return 0; + const filePath = getGoogleAccountsCachePath(); + const accounts = readAccountsSync(filePath); + const allAccounts = new Set(accounts.old); + if (accounts.active) { + allAccounts.add(accounts.active); } + return allAccounts.size; } export async function clearCachedGoogleAccount(): Promise<void> { const filePath = getGoogleAccountsCachePath(); - if (!existsSync(filePath)) { - return; - } - const accounts = await readAccounts(filePath); if (accounts.active) { |
