Skip to content

Commit e33c122

Browse files
committed
fix(cli): revalidate auth when token changes
- Key auth validation query by sha256(apiKey) to avoid stale cache and avoid putting the raw token in the cache key - Resolve CODEBUFF_API_KEY via CiEnv and make auth token resolution injectable
1 parent b217090 commit e33c122

File tree

5 files changed

+63
-35
lines changed

5 files changed

+63
-35
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { describe, expect, test } from 'bun:test'
2+
3+
import { authQueryKeys } from '../use-auth-query'
4+
5+
describe('authQueryKeys.validation', () => {
6+
test('changes when api key changes', () => {
7+
const firstKey = authQueryKeys.validation('token-1')
8+
const secondKey = authQueryKeys.validation('token-2')
9+
10+
expect(firstKey).not.toEqual(secondKey)
11+
})
12+
13+
test('does not include the raw api key', () => {
14+
const token = 'secret-token-123'
15+
const key = authQueryKeys.validation(token)
16+
const [, , apiKeyHash] = key
17+
18+
expect(key).not.toContain(token)
19+
expect(apiKeyHash).toMatch(/^[0-9a-f]{64}$/)
20+
})
21+
})
22+

cli/src/hooks/use-auth-query.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { API_KEY_ENV_VAR } from '@codebuff/common/old-constants'
1+
import { createHash } from 'crypto'
2+
3+
import { getCiEnv } from '@codebuff/common/env-ci'
24
import {
35
AuthenticationError,
46
ErrorCodes,
@@ -22,12 +24,16 @@ import { logger as defaultLogger, loggerContext } from '../utils/logger'
2224
import type { GetUserInfoFromApiKeyFn } from '@codebuff/common/types/contracts/database'
2325
import type { Logger } from '@codebuff/common/types/contracts/logger'
2426

27+
const getApiKeyHash = (apiKey: string): string => {
28+
return createHash('sha256').update(apiKey).digest('hex')
29+
}
30+
2531
// Query keys for type-safe cache management
2632
export const authQueryKeys = {
2733
all: ['auth'] as const,
2834
user: () => [...authQueryKeys.all, 'user'] as const,
2935
validation: (apiKey: string) =>
30-
[...authQueryKeys.all, 'validation', apiKey] as const,
36+
[...authQueryKeys.all, 'validation', getApiKeyHash(apiKey)] as const,
3137
}
3238

3339
interface ValidateAuthParams {
@@ -122,8 +128,7 @@ export function useAuthQuery(deps: UseAuthQueryDeps = {}) {
122128
} = deps
123129

124130
const userCredentials = getUserCredentials()
125-
const apiKey =
126-
userCredentials?.authToken || process.env[API_KEY_ENV_VAR] || ''
131+
const apiKey = userCredentials?.authToken || getCiEnv().CODEBUFF_API_KEY || ''
127132

128133
return useQuery({
129134
queryKey: authQueryKeys.validation(apiKey),

cli/src/hooks/use-auth-state.ts

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ import { loggerContext } from '../utils/logger'
1010
import type { MultilineInputHandle } from '../components/multiline-input'
1111
import type { User } from '../utils/auth'
1212

13+
const setAuthLoggerContext = (params: { userId: string; email: string }) => {
14+
loggerContext.userId = params.userId
15+
loggerContext.userEmail = params.email
16+
identifyUser(params.userId, { email: params.email })
17+
}
18+
19+
const clearAuthLoggerContext = () => {
20+
delete loggerContext.userId
21+
delete loggerContext.userEmail
22+
}
23+
1324
interface UseAuthStateOptions {
1425
requireAuth: boolean | null
1526
inputRef: React.MutableRefObject<MultilineInputHandle | null>
@@ -27,8 +38,7 @@ export const useAuthState = ({
2738
const logoutMutation = useLogoutMutation()
2839
const { resetLoginState } = useLoginStore()
2940

30-
const initialAuthState =
31-
requireAuth === false ? true : requireAuth === true ? false : null
41+
const initialAuthState = requireAuth === null ? null : !requireAuth
3242
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(
3343
initialAuthState,
3444
)
@@ -55,23 +65,15 @@ export const useAuthState = ({
5565
authToken: userCredentials?.authToken || '',
5666
}
5767
setUser(userData)
58-
59-
// Set logger context for analytics
60-
loggerContext.userId = authQuery.data.id
61-
loggerContext.userEmail = authQuery.data.email
62-
63-
// Identify user with PostHog
64-
identifyUser(authQuery.data.id, {
65-
email: authQuery.data.email,
68+
setAuthLoggerContext({
69+
userId: authQuery.data.id,
70+
email: authQuery.data.email || '',
6671
})
6772
}
6873
} else if (authQuery.isError) {
6974
setIsAuthenticated(false)
7075
setUser(null)
71-
72-
// Clear logger context on auth error
73-
delete loggerContext.userId
74-
delete loggerContext.userEmail
76+
clearAuthLoggerContext()
7577
}
7678
}, [authQuery.isSuccess, authQuery.isError, authQuery.data, user])
7779

@@ -85,14 +87,10 @@ export const useAuthState = ({
8587
setInputFocused(true)
8688
setUser(loggedInUser)
8789
setIsAuthenticated(true)
88-
89-
// Set logger context for analytics
90+
9091
if (loggedInUser.id && loggedInUser.email) {
91-
loggerContext.userId = loggedInUser.id
92-
loggerContext.userEmail = loggedInUser.email
93-
94-
// Identify user with PostHog
95-
identifyUser(loggedInUser.id, {
92+
setAuthLoggerContext({
93+
userId: loggedInUser.id,
9694
email: loggedInUser.email,
9795
})
9896
}

cli/src/index.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import { promises as fs } from 'fs'
44
import { createRequire } from 'module'
55

6-
import { API_KEY_ENV_VAR } from '@codebuff/common/old-constants'
76
import { getProjectFileTree } from '@codebuff/common/project-file-tree'
87
import { createCliRenderer } from '@opentui/core'
98
import { createRoot } from '@opentui/react'
@@ -21,7 +20,8 @@ import { handlePublish } from './commands/publish'
2120
import { initializeApp } from './init/init-app'
2221
import { getProjectRoot } from './project-files'
2322
import { initAnalytics } from './utils/analytics'
24-
import { getUserCredentials } from './utils/auth'
23+
import { getAuthTokenDetails } from './utils/auth'
24+
import { getCliEnv } from './utils/env'
2525
import { initializeAgentRegistry } from './utils/local-agent-registry'
2626
import { clearLogFile, logger } from './utils/logger'
2727
import { detectTerminalTheme } from './utils/terminal-color-detection'
@@ -33,8 +33,9 @@ import type { AgentMode } from './utils/constants'
3333
const require = createRequire(import.meta.url)
3434

3535
function loadPackageVersion(): string {
36-
if (process.env.CODEBUFF_CLI_VERSION) {
37-
return process.env.CODEBUFF_CLI_VERSION
36+
const env = getCliEnv()
37+
if (env.CODEBUFF_CLI_VERSION) {
38+
return env.CODEBUFF_CLI_VERSION
3839
}
3940

4041
try {
@@ -215,9 +216,7 @@ async function main(): Promise<void> {
215216
const [fileTree, setFileTree] = React.useState<FileTreeNode[]>([])
216217

217218
React.useEffect(() => {
218-
const userCredentials = getUserCredentials()
219-
const apiKey =
220-
userCredentials?.authToken || process.env[API_KEY_ENV_VAR] || ''
219+
const apiKey = getAuthTokenDetails().token ?? ''
221220

222221
if (!apiKey) {
223222
setRequireAuth(true)

cli/src/utils/auth.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import fs from 'fs'
22
import os from 'os'
33
import path from 'path'
44

5+
import { getCiEnv } from '@codebuff/common/env-ci'
56
import { env } from '@codebuff/common/env'
6-
import { API_KEY_ENV_VAR } from '@codebuff/common/old-constants'
77
import { z } from 'zod'
88

9+
import type { CiEnv } from '@codebuff/common/types/contracts/env'
10+
911
import { getApiClient, setApiClientAuthToken } from './codebuff-api'
1012
import { logger } from './logger'
1113

@@ -107,13 +109,15 @@ export interface AuthTokenDetails {
107109
/**
108110
* Resolve the auth token and track where it came from.
109111
*/
110-
export const getAuthTokenDetails = (): AuthTokenDetails => {
112+
export const getAuthTokenDetails = (
113+
ciEnv: CiEnv = getCiEnv(),
114+
): AuthTokenDetails => {
111115
const userCredentials = getUserCredentials()
112116
if (userCredentials?.authToken) {
113117
return { token: userCredentials.authToken, source: 'credentials' }
114118
}
115119

116-
const envToken = process.env[API_KEY_ENV_VAR]
120+
const envToken = ciEnv.CODEBUFF_API_KEY
117121
if (envToken) {
118122
return { token: envToken, source: 'environment' }
119123
}

0 commit comments

Comments
 (0)