mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-19 13:33:35 +08:00
Prevent secure-storage cache keys from overriding env-pinned OAuth files
The sync token reader memoized raw path strings and reserved '__secure-storage__' as the secure-storage discriminator. That allowed a real OPENAI_CODEX_OAUTH_FILE value with the same relative path to bypass the file branch and return secure-storage data instead. Use structured cache keys so file-backed lookups always memoize under a prefixed path while secure storage keeps a dedicated discriminator. Add a regression that seeds different secure-storage and file tokens for '__secure-storage__' and proves both sync and async getters honor the env-pinned file. Constraint: OPENAI_CODEX_OAUTH_FILE must remain authoritative even for relative paths that match internal sentinel names Rejected: Keep the raw sentinel and special-case path equality | still leaves memoization coupled to a valid user-controlled filename Directive: Preserve the file-vs-secure-storage branch split whenever sync cache keys change so env-pinned file authority stays collision-proof Confidence: high Scope-risk: narrow Tested: bun test src/services/openaiAuth/storage.test.ts --test-name-pattern "prefers env-pinned file authority when OPENAI_CODEX_OAUTH_FILE matches the secure-storage sentinel" Tested: bun test src/services/openaiAuth/storage.test.ts src/server/__tests__/haha-openai-oauth-service.test.ts Tested: bun test src/services/openaiAuth/storage.test.ts src/server/__tests__/haha-openai-oauth-service.test.ts src/server/__tests__/haha-openai-oauth-api.test.ts Tested: git diff --check
This commit is contained in:
parent
b61df181d3
commit
b10d5f7df7
@ -20,6 +20,7 @@ describe('OpenAI OAuth desktop token file storage', () => {
|
|||||||
let originalConfigDir: string | undefined
|
let originalConfigDir: string | undefined
|
||||||
let originalHome: string | undefined
|
let originalHome: string | undefined
|
||||||
let originalUserProfile: string | undefined
|
let originalUserProfile: string | undefined
|
||||||
|
let originalCwd: string
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'openai-oauth-storage-'))
|
tmpDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'openai-oauth-storage-'))
|
||||||
@ -28,6 +29,7 @@ describe('OpenAI OAuth desktop token file storage', () => {
|
|||||||
originalConfigDir = process.env.CLAUDE_CONFIG_DIR
|
originalConfigDir = process.env.CLAUDE_CONFIG_DIR
|
||||||
originalHome = process.env.HOME
|
originalHome = process.env.HOME
|
||||||
originalUserProfile = process.env.USERPROFILE
|
originalUserProfile = process.env.USERPROFILE
|
||||||
|
originalCwd = process.cwd()
|
||||||
process.env.CLAUDE_CONFIG_DIR = tmpDir
|
process.env.CLAUDE_CONFIG_DIR = tmpDir
|
||||||
process.env.HOME = tmpDir
|
process.env.HOME = tmpDir
|
||||||
process.env.USERPROFILE = tmpDir
|
process.env.USERPROFILE = tmpDir
|
||||||
@ -57,6 +59,7 @@ describe('OpenAI OAuth desktop token file storage', () => {
|
|||||||
} else {
|
} else {
|
||||||
process.env.USERPROFILE = originalUserProfile
|
process.env.USERPROFILE = originalUserProfile
|
||||||
}
|
}
|
||||||
|
process.chdir(originalCwd)
|
||||||
clearOpenAIOAuthTokenCache()
|
clearOpenAIOAuthTokenCache()
|
||||||
await fsp.rm(tmpDir, { recursive: true, force: true })
|
await fsp.rm(tmpDir, { recursive: true, force: true })
|
||||||
})
|
})
|
||||||
@ -230,6 +233,39 @@ describe('OpenAI OAuth desktop token file storage', () => {
|
|||||||
expect(getOpenAIOAuthTokens()?.accessToken).toBe('access-b')
|
expect(getOpenAIOAuthTokens()?.accessToken).toBe('access-b')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('prefers env-pinned file authority when OPENAI_CODEX_OAUTH_FILE matches the secure-storage sentinel', async () => {
|
||||||
|
const sentinelPath = '__secure-storage__'
|
||||||
|
seedSecureStorage({
|
||||||
|
accessToken: 'secure-access',
|
||||||
|
refreshToken: 'secure-refresh',
|
||||||
|
expiresAt: 4_100_000_000_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
process.chdir(tmpDir)
|
||||||
|
process.env.OPENAI_CODEX_OAUTH_FILE = sentinelPath
|
||||||
|
await fsp.writeFile(
|
||||||
|
path.join(tmpDir, sentinelPath),
|
||||||
|
JSON.stringify({
|
||||||
|
accessToken: 'file-access',
|
||||||
|
refreshToken: 'file-refresh',
|
||||||
|
expiresAt: 4_100_000_000_123,
|
||||||
|
}),
|
||||||
|
'utf-8',
|
||||||
|
)
|
||||||
|
clearOpenAIOAuthTokenCache()
|
||||||
|
|
||||||
|
expect(getOpenAIOAuthTokens()).toMatchObject({
|
||||||
|
accessToken: 'file-access',
|
||||||
|
refreshToken: 'file-refresh',
|
||||||
|
expiresAt: 4_100_000_000_123,
|
||||||
|
})
|
||||||
|
await expect(getOpenAIOAuthTokensAsync()).resolves.toMatchObject({
|
||||||
|
accessToken: 'file-access',
|
||||||
|
refreshToken: 'file-refresh',
|
||||||
|
expiresAt: 4_100_000_000_123,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('cleans up tmp file if desktop token rename fails', async () => {
|
test('cleans up tmp file if desktop token rename fails', async () => {
|
||||||
const renameSyncSpy = spyOn(fs, 'renameSync').mockImplementation(() => {
|
const renameSyncSpy = spyOn(fs, 'renameSync').mockImplementation(() => {
|
||||||
const error = new Error('rename failed') as NodeJS.ErrnoException
|
const error = new Error('rename failed') as NodeJS.ErrnoException
|
||||||
|
|||||||
@ -13,7 +13,8 @@ type SecureStorageShape = Record<string, unknown> & {
|
|||||||
openaiCodexOauth?: OpenAIOAuthTokens
|
openaiCodexOauth?: OpenAIOAuthTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
const SECURE_STORAGE_CACHE_KEY = '__secure-storage__'
|
const SECURE_STORAGE_CACHE_KEY = 'secure-storage'
|
||||||
|
const FILE_CACHE_KEY_PREFIX = 'file:'
|
||||||
|
|
||||||
function getDesktopTokenFilePath(): string | null {
|
function getDesktopTokenFilePath(): string | null {
|
||||||
const filePath = process.env[OPENAI_CODEX_OAUTH_FILE_ENV_KEY]?.trim()
|
const filePath = process.env[OPENAI_CODEX_OAUTH_FILE_ENV_KEY]?.trim()
|
||||||
@ -129,8 +130,10 @@ export function saveOpenAIOAuthTokens(tokens: OpenAIOAuthTokens): {
|
|||||||
|
|
||||||
const getOpenAIOAuthTokensCached = memoize(
|
const getOpenAIOAuthTokensCached = memoize(
|
||||||
(cacheKey: string): OpenAIOAuthTokens | null => {
|
(cacheKey: string): OpenAIOAuthTokens | null => {
|
||||||
if (cacheKey !== SECURE_STORAGE_CACHE_KEY) {
|
if (cacheKey.startsWith(FILE_CACHE_KEY_PREFIX)) {
|
||||||
return readDesktopTokenFileSync(cacheKey)
|
return readDesktopTokenFileSync(
|
||||||
|
cacheKey.slice(FILE_CACHE_KEY_PREFIX.length),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -145,8 +148,9 @@ const getOpenAIOAuthTokensCached = memoize(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export function getOpenAIOAuthTokens(): OpenAIOAuthTokens | null {
|
export function getOpenAIOAuthTokens(): OpenAIOAuthTokens | null {
|
||||||
|
const filePath = getDesktopTokenFilePath()
|
||||||
return getOpenAIOAuthTokensCached(
|
return getOpenAIOAuthTokensCached(
|
||||||
getDesktopTokenFilePath() ?? SECURE_STORAGE_CACHE_KEY,
|
filePath ? `${FILE_CACHE_KEY_PREFIX}${filePath}` : SECURE_STORAGE_CACHE_KEY,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user