Skip to content

Commit 12511ca

Browse files
feat(auth): unify CLI ↔ backend API key auth; stabilize agent validation tests
Use x-codebuff-api-key across CLI and backend; add auth header helpers; update agent validation API to read API key; adjust mocks to include headers; standardize org coverage endpoint auth. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
1 parent 4d4ff84 commit 12511ca

File tree

8 files changed

+99
-77
lines changed

8 files changed

+99
-77
lines changed

backend/src/api/__tests__/validate-agent-name.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type {
1919
} from 'express'
2020

2121
function createMockReq(query: Record<string, any>): Partial<ExpressRequest> {
22-
return { query } as any
22+
return { query, headers: {} } as any
2323
}
2424

2525
function createMockRes() {

backend/src/api/agents.ts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
import { AGENT_PERSONAS } from '@codebuff/common/constants/agents'
12
import { z } from 'zod/v4'
3+
4+
import { getAgentTemplate } from '../templates/agent-registry'
5+
import { extractAuthTokenFromHeader } from '../util/auth-helpers'
6+
import { logger } from '../util/logger'
7+
28
import type {
39
Request as ExpressRequest,
410
Response as ExpressResponse,
511
NextFunction,
612
} from 'express'
7-
import { logger } from '../util/logger'
8-
import { AGENT_PERSONAS } from '@codebuff/common/constants/agents'
9-
import { getAgentTemplate } from '../templates/agent-registry'
1013

1114
// Add short-lived cache for positive validations
1215
const AGENT_VALIDATION_CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes
@@ -30,21 +33,19 @@ export async function validateAgentNameHandler(
3033
next: NextFunction,
3134
): Promise<void | ExpressResponse> {
3235
try {
33-
// Log authentication headers if present (for debugging)
34-
const hasAuthHeader = !!req.headers.authorization
35-
const hasApiKey = !!req.headers['x-api-key']
36-
37-
if (hasAuthHeader || hasApiKey) {
38-
logger.info(
39-
{
40-
hasAuthHeader,
41-
hasApiKey,
36+
// Check for x-codebuff-api-key header for authentication
37+
const apiKey = extractAuthTokenFromHeader(req)
38+
39+
if (apiKey) {
40+
logger.debug(
41+
{
42+
hasApiKey: true,
4243
agentId: req.query.agentId,
4344
},
44-
'Agent validation request with authentication',
45+
'Agent validation request with API key authentication',
4546
)
4647
}
47-
48+
4849
// Parse from query instead (GET)
4950
const { agentId } = validateAgentRequestSchema.parse({
5051
agentId: String((req.query as any)?.agentId ?? ''),
@@ -60,7 +61,11 @@ export async function validateAgentNameHandler(
6061

6162
// Check built-in agents first
6263
if (AGENT_PERSONAS[agentId as keyof typeof AGENT_PERSONAS]) {
63-
const result = { valid: true as const, source: 'builtin', normalizedId: agentId }
64+
const result = {
65+
valid: true as const,
66+
source: 'builtin',
67+
normalizedId: agentId,
68+
}
6469
agentValidationCache.set(agentId, {
6570
result,
6671
expiresAt: Date.now() + AGENT_VALIDATION_CACHE_TTL_MS,
@@ -90,7 +95,11 @@ export async function validateAgentNameHandler(
9095
'Error validating agent name',
9196
)
9297
if (error instanceof z.ZodError) {
93-
return res.status(400).json({ valid: false, message: 'Invalid request', issues: error.issues })
98+
return res.status(400).json({
99+
valid: false,
100+
message: 'Invalid request',
101+
issues: error.issues,
102+
})
94103
}
95104
next(error)
96105
return

backend/src/api/org.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { findOrganizationForRepository } from '@codebuff/billing'
22
import { z } from 'zod/v4'
33

44
import { logger } from '../util/logger'
5+
import { extractAuthTokenFromHeader } from '../util/auth-helpers'
56
import { getUserIdFromAuthToken } from '../websockets/websocket-action'
67

78
import type {
@@ -26,15 +27,13 @@ async function isRepoCoveredHandler(
2627
req.body,
2728
)
2829

29-
// Get user ID from Authorization header
30-
const authHeader = req.headers.authorization
31-
if (!authHeader || !authHeader.startsWith('Bearer ')) {
30+
// Get user ID from x-codebuff-api-key header
31+
const authToken = extractAuthTokenFromHeader(req)
32+
if (!authToken) {
3233
return res
3334
.status(401)
34-
.json({ error: 'Missing or invalid authorization header' })
35+
.json({ error: 'Missing x-codebuff-api-key header' })
3536
}
36-
37-
const authToken = authHeader.substring(7) // Remove 'Bearer ' prefix
3837
const userId = await getUserIdFromAuthToken(authToken)
3938

4039
if (!userId) {

backend/src/util/auth-helpers.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { Request } from 'express'
2+
3+
/**
4+
* Extract auth token from x-codebuff-api-key header
5+
*/
6+
export function extractAuthTokenFromHeader(req: Request): string | undefined {
7+
const token = req.headers['x-codebuff-api-key'] as string | undefined
8+
// Trim any whitespace that might be present
9+
return token?.trim()
10+
}

backend/src/util/check-auth.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { utils } from '@codebuff/internal'
44
import { eq } from 'drizzle-orm'
55

66
import { logger } from './logger'
7+
import { extractAuthTokenFromHeader } from './auth-helpers'
78

89
import type { ServerAction } from '@codebuff/common/actions'
910
import type { Request, Response, NextFunction } from 'express'
@@ -49,14 +50,11 @@ export const checkAdmin = async (
4950
res: Response,
5051
next: NextFunction,
5152
) => {
52-
// Extract auth token from Authorization header
53-
const authHeader = req.headers.authorization
54-
if (!authHeader?.startsWith('Bearer ')) {
55-
return res
56-
.status(401)
57-
.json({ error: 'Missing or invalid Authorization header' })
53+
// Extract auth token from x-codebuff-api-key header
54+
const authToken = extractAuthTokenFromHeader(req)
55+
if (!authToken) {
56+
return res.status(401).json({ error: 'Missing x-codebuff-api-key header' })
5857
}
59-
const authToken = authHeader.substring(7) // Remove 'Bearer ' prefix
6058

6159
// Generate a client session ID for this request
6260
const clientSessionId = `admin-relabel-${Date.now()}`

npm-app/src/client.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import {
8282
} from './subagent-storage'
8383
import { handleToolCall } from './tool-handlers'
8484
import { identifyUser, trackEvent } from './utils/analytics'
85+
import { addAuthHeader } from './utils/auth-headers'
8586
import { getRepoMetrics, gitCommandIsAvailable } from './utils/git'
8687
import { logger, loggerContext } from './utils/logger'
8788
import { Spinner } from './utils/spinner'
@@ -1630,10 +1631,10 @@ Go to https://www.codebuff.com/config for more information.`) +
16301631
// Call backend API to check if repo is covered by organization
16311632
const response = await fetch(`${backendUrl}/api/orgs/is-repo-covered`, {
16321633
method: 'POST',
1633-
headers: {
1634-
'Content-Type': 'application/json',
1635-
Authorization: `Bearer ${this.user.authToken}`,
1636-
},
1634+
headers: addAuthHeader(
1635+
{ 'Content-Type': 'application/json' },
1636+
this.user.authToken,
1637+
),
16371638
body: JSON.stringify({
16381639
owner: owner.toLowerCase(),
16391640
repo: repo.toLowerCase(),

npm-app/src/index.ts

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ import { rageDetectors } from './rage-detectors'
2424
import { logAndHandleStartup } from './startup-process-handler'
2525
import { recreateShell } from './terminal/run-command'
2626
import { validateAgentDefinitionsIfAuthenticated } from './utils/agent-validation'
27-
import { getUserCredentials } from './credentials'
28-
import { API_KEY_ENV_VAR } from '@codebuff/common/constants'
27+
import { createAuthHeaders } from './utils/auth-headers'
2928
import { initAnalytics, trackEvent } from './utils/analytics'
3029
import { logger } from './utils/logger'
3130
import { Spinner } from './utils/spinner'
@@ -36,22 +35,6 @@ export async function validateAgent(
3635
agent: string,
3736
localAgents?: Record<string, any>,
3837
): Promise<void> {
39-
// Check what credentials are available at this point
40-
const userCredentials = getUserCredentials()
41-
const apiKeyEnvVar = process.env[API_KEY_ENV_VAR]
42-
43-
logger.info(
44-
{
45-
agent,
46-
hasUserCredentials: !!userCredentials,
47-
hasApiKeyEnvVar: !!apiKeyEnvVar,
48-
userId: userCredentials?.id,
49-
userEmail: userCredentials?.email,
50-
hasAuthToken: !!userCredentials?.authToken,
51-
},
52-
'[startup] validateAgent: checking available credentials',
53-
)
54-
5538
const agents = localAgents ?? {}
5639

5740
// if local agents are loaded, they're already validated
@@ -64,31 +47,10 @@ export async function validateAgent(
6447
Spinner.get().start('Checking agent...')
6548
try {
6649
const url = `${backendUrl}/api/agents/validate-name?agentId=${encodeURIComponent(agent)}`
67-
68-
// Add auth headers if available
69-
const headers: Record<string, string> = {
70-
'Content-Type': 'application/json',
71-
}
72-
73-
if (userCredentials?.authToken) {
74-
headers.Authorization = `Bearer ${userCredentials.authToken}`
75-
logger.debug(
76-
{ hasAuthHeader: true },
77-
'[startup] Adding Authorization header to agent validation request',
78-
)
79-
} else if (apiKeyEnvVar) {
80-
headers['X-API-Key'] = apiKeyEnvVar
81-
logger.debug(
82-
{ hasApiKey: true },
83-
'[startup] Adding API key header to agent validation request',
84-
)
85-
} else {
86-
logger.warn(
87-
{},
88-
'[startup] No authentication credentials available for agent validation',
89-
)
90-
}
91-
50+
51+
// Use helper to create headers with x-codebuff-api-key
52+
const headers = createAuthHeaders()
53+
9254
const resp = await fetch(url, {
9355
method: 'GET',
9456
headers,

npm-app/src/utils/auth-headers.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { getUserCredentials } from '../credentials'
2+
import { API_KEY_ENV_VAR } from '@codebuff/common/constants'
3+
4+
/**
5+
* Get the auth token from user credentials or environment variable
6+
*/
7+
export function getAuthToken(): string | undefined {
8+
const userCredentials = getUserCredentials()
9+
return userCredentials?.authToken || process.env[API_KEY_ENV_VAR]
10+
}
11+
12+
/**
13+
* Create headers with x-codebuff-api-key for API requests
14+
*/
15+
export function createAuthHeaders(contentType = 'application/json'): Record<string, string> {
16+
const headers: Record<string, string> = {
17+
'Content-Type': contentType,
18+
}
19+
20+
const authToken = getAuthToken()
21+
if (authToken) {
22+
headers['x-codebuff-api-key'] = authToken
23+
}
24+
25+
return headers
26+
}
27+
28+
/**
29+
* Add x-codebuff-api-key to existing headers
30+
*/
31+
export function addAuthHeader(
32+
headers: Record<string, string>,
33+
authToken?: string,
34+
): Record<string, string> {
35+
const token = authToken || getAuthToken()
36+
if (token) {
37+
return {
38+
...headers,
39+
'x-codebuff-api-key': token,
40+
}
41+
}
42+
return headers
43+
}

0 commit comments

Comments
 (0)