Expire cached Gemini verification with its access token

Invalidate previous successful verification when an unrefreshable native OAuth access token enters its expiry window.

Checks: 194 catalog and readiness tests; desktop packaging; git diff --check.
This commit is contained in:
Ryder Sun committed 2026-11-07 20:24:44 +08:00
1 parent 8c01e2d150
commit 84a14345a5
2 files changed
+32

No files matched your search

@@ -39,6 +39,21 @@ function selectedConfigurationIdentity(agent, preferences) {
return entries
}
function geminiAccessTokenExpired(agent, preferences, now) {
const directory = preferences?.gemini?.nativeConfigDirectory || agent[NATIVE_CONFIG_IDENTITY_DIRECTORY]
if (typeof directory !== 'string' || !path.isAbsolute(directory)) return false
try {
const filename = path.join(directory, 'oauth_creds.json')
const stat = fs.statSync(filename)
if (!stat.isFile() || stat.size > 1024 * 1024) return false
const value = JSON.parse(fs.readFileSync(filename, 'utf8'))
if (typeof value?.refresh_token === 'string' && value.refresh_token.trim()) return false
return typeof value?.access_token === 'string' && Boolean(value.access_token.trim())
&& typeof value.expiry_date === 'number' && Number.isFinite(value.expiry_date)
&& value.expiry_date > 0 && value.expiry_date <= now + 5 * 60 * 1000
} catch { return false }
}
function agentVerificationIdentity(agent, preferences = {}, options = {}) {
if (!agent?.kind) return ''
let executableStat = null
@@ -65,6 +80,9 @@ function agentVerificationIdentity(agent, preferences = {}, options = {}) {
}
const configuration = selectedConfigurationIdentity(agent, preferences)
if (configuration !== null) identity.push(configuration)
if (agent.kind === 'gemini' && geminiAccessTokenExpired(agent, preferences, options.now ?? Date.now())) {
identity.push(['oauth-access-token-expired'])
}
if (Array.isArray(agent[NATIVE_AUTH_IDENTITY_FILES])) {
identity.push(agent[NATIVE_AUTH_IDENTITY_FILES].filter(filename => typeof filename === 'string' && path.isAbsolute(filename))
.map(filename => [filename, fileIdentity(filename)]))
@@ -9,6 +9,20 @@ const { agentVerificationIdentity, projectVerificationIdentity } = require('../.
const { NATIVE_CONFIG_IDENTITY_DIRECTORY, NATIVE_ENVIRONMENT_IDENTITY, NATIVE_AUTH_IDENTITY_FILES, NATIVE_PROJECT_CONFIG_PATHS, nativeEnvironmentIdentity, nativeAuthIdentityFiles, nativeProjectConfigPaths } = require('../../src/agents/native-config-directory.cjs')
const { LocalWorkspaceAgentCatalog } = catalogApi
test('Gemini verification identity changes when an unrefreshable OAuth token expires', t => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-gemini-expiry-identity-'))
t.after(() => fs.rmSync(root, { recursive: true, force: true }))
const agent = { kind: 'gemini' }
Object.defineProperty(agent, NATIVE_CONFIG_IDENTITY_DIRECTORY, { value: root })
const filename = path.join(root, 'oauth_creds.json')
fs.writeFileSync(filename, JSON.stringify({ access_token: 'synthetic', expiry_date: 1_000_000 }))
assert.notEqual(agentVerificationIdentity(agent, {}, { now: 600_000 }),
agentVerificationIdentity(agent, {}, { now: 700_000 }))
fs.writeFileSync(filename, JSON.stringify({ access_token: 'synthetic', refresh_token: 'refresh', expiry_date: 1_000_000 }))
assert.equal(agentVerificationIdentity(agent, {}, { now: 600_000 }),
agentVerificationIdentity(agent, {}, { now: 700_000 }))
})
test('Gemini trust changes invalidate default, absolute and relative configuration identities', t => {
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-gemini-trust-identity-'))
t.after(() => fs.rmSync(home, { recursive: true, force: true }))