Skip to content

Commit c8d4359

Browse files
committed
revert "replace checkAuthToken with getUserInfoFromApiKey"
1 parent 334e92b commit c8d4359

File tree

6 files changed

+158
-79
lines changed

6 files changed

+158
-79
lines changed

backend/src/api/usage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { getOrganizationUsageResponse } from '@codebuff/billing'
22
import { INVALID_AUTH_TOKEN_MESSAGE } from '@codebuff/common/old-constants'
33
import { z } from 'zod/v4'
44

5-
import { BACKEND_AGENT_RUNTIME_IMPL } from '../impl/agent-runtime'
65
import { checkAuth } from '../util/check-auth'
76
import { logger } from '../util/logger'
87
import { getUserInfoFromApiKey } from '../websockets/auth'
@@ -32,9 +31,10 @@ async function usageHandler(
3231
const clientSessionId = `api-${fingerprintId}-${Date.now()}`
3332

3433
const authResult = await checkAuth({
35-
...BACKEND_AGENT_RUNTIME_IMPL,
34+
fingerprintId,
3635
authToken,
3736
clientSessionId,
37+
logger,
3838
})
3939
if (authResult) {
4040
const errorMessage =

backend/src/util/check-auth.ts

Lines changed: 66 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
1+
import db from '@codebuff/common/db'
2+
import * as schema from '@codebuff/common/db/schema'
13
import { utils } from '@codebuff/internal'
4+
import { eq } from 'drizzle-orm'
25

36
import { extractAuthTokenFromHeader } from './auth-helpers'
4-
import { getUserInfoFromApiKey } from '../websockets/auth'
57

68
import type { ServerAction } from '@codebuff/common/actions'
7-
import type { GetUserInfoFromApiKeyFn } from '@codebuff/common/types/contracts/database'
89
import type { Logger } from '@codebuff/common/types/contracts/logger'
910
import type { Request, Response, NextFunction } from 'express'
1011

1112
export const checkAuth = async (params: {
13+
fingerprintId?: string
1214
authToken?: string
1315
clientSessionId: string
14-
getUserInfoFromApiKey: GetUserInfoFromApiKeyFn
1516
logger: Logger
1617
}): Promise<void | ServerAction> => {
17-
const { authToken, clientSessionId, getUserInfoFromApiKey, logger } = params
18-
18+
const { fingerprintId, authToken, clientSessionId, logger } = params
1919
// Use shared auth check functionality
20-
const authResult = authToken
21-
? await getUserInfoFromApiKey({
22-
apiKey: authToken,
23-
fields: ['id'],
24-
})
25-
: null
20+
const authResult = await utils.checkAuthToken({
21+
fingerprintId,
22+
authToken,
23+
})
2624

27-
if (!authResult) {
28-
const errorMessage = 'Authentication failed'
25+
if (!authResult.success) {
26+
const errorMessage = authResult.error?.message || 'Authentication failed'
2927
logger.error({ clientSessionId, error: errorMessage }, errorMessage)
3028
return {
3129
type: 'action-error',
@@ -45,44 +43,65 @@ export const checkAuth = async (params: {
4543
}
4644

4745
// Express middleware for checking admin access
48-
export const checkAdmin =
49-
(logger: Logger) =>
50-
async (req: Request, res: Response, next: NextFunction) => {
51-
// Extract auth token from x-codebuff-api-key header
52-
const authToken = extractAuthTokenFromHeader(req)
53-
if (!authToken) {
54-
return res
55-
.status(401)
56-
.json({ error: 'Missing x-codebuff-api-key header' })
57-
}
46+
export const checkAdmin = (logger: Logger) => async (
47+
req: Request,
48+
res: Response,
49+
next: NextFunction,
50+
) => {
51+
// Extract auth token from x-codebuff-api-key header
52+
const authToken = extractAuthTokenFromHeader(req)
53+
if (!authToken) {
54+
return res.status(401).json({ error: 'Missing x-codebuff-api-key header' })
55+
}
5856

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

62-
// Check authentication
63-
const user = await getUserInfoFromApiKey({
64-
apiKey: authToken,
65-
fields: ['id', 'email'],
66-
})
60+
// Check authentication
61+
const authResult = await checkAuth({
62+
authToken,
63+
clientSessionId,
64+
logger,
65+
})
6766

68-
if (!user) {
69-
return res.status(401).json({ error: 'Invalid session' })
70-
}
67+
if (authResult) {
68+
// checkAuth returns an error action if auth fails
69+
const errorMessage =
70+
authResult.type === 'action-error'
71+
? authResult.message
72+
: 'Authentication failed'
73+
return res.status(401).json({ error: errorMessage })
74+
}
7175

72-
// Check if user has admin access using shared utility
73-
const adminUser = await utils.checkUserIsCodebuffAdmin(user.id)
74-
if (!adminUser) {
75-
logger.warn(
76-
{ userId: user.id, email: user.email, clientSessionId },
77-
'Unauthorized access attempt to admin endpoint',
78-
)
79-
return res.status(403).json({ error: 'Forbidden' })
80-
}
76+
// Get the user ID associated with this session token
77+
const user = await db
78+
.select({
79+
id: schema.user.id,
80+
email: schema.user.email,
81+
})
82+
.from(schema.user)
83+
.innerJoin(schema.session, eq(schema.user.id, schema.session.userId))
84+
.where(eq(schema.session.sessionToken, authToken))
85+
.then((users) => users[0])
8186

82-
// Store user info in request for handlers to use if needed
83-
// req.user = adminUser // TODO: ensure type check passes
87+
if (!user) {
88+
return res.status(401).json({ error: 'Invalid session' })
89+
}
8490

85-
// Auth passed and user is admin, proceed to next middleware
86-
next()
87-
return
91+
// Check if user has admin access using shared utility
92+
const adminUser = await utils.checkUserIsCodebuffAdmin(user.id)
93+
if (!adminUser) {
94+
logger.warn(
95+
{ userId: user.id, email: user.email, clientSessionId },
96+
'Unauthorized access attempt to admin endpoint',
97+
)
98+
return res.status(403).json({ error: 'Forbidden' })
8899
}
100+
101+
// Store user info in request for handlers to use if needed
102+
// req.user = adminUser // TODO: ensure type check passes
103+
104+
// Auth passed and user is admin, proceed to next middleware
105+
next()
106+
return
107+
}

backend/src/websockets/middleware.ts

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -60,27 +60,25 @@ export class WebSocketMiddleware {
6060
}
6161

6262
use<T extends ClientAction['type']>(
63-
callback: (
64-
params: {
65-
action: ClientAction<T>
66-
clientSessionId: string
67-
ws: WebSocket
68-
userInfo: { id: string } | null
69-
} & AgentRuntimeDeps,
70-
) => Promise<void | ServerAction>,
63+
callback: (params: {
64+
action: ClientAction<T>
65+
clientSessionId: string
66+
ws: WebSocket
67+
userInfo: { id: string } | null
68+
logger: Logger
69+
}) => Promise<void | ServerAction>,
7170
) {
7271
this.middlewares.push(callback as MiddlewareCallback)
7372
}
7473

75-
async execute(
76-
params: {
77-
action: ClientAction
78-
clientSessionId: string
79-
ws: WebSocket
80-
silent?: boolean
81-
getUserInfoFromApiKey: GetUserInfoFromApiKeyFn
82-
} & AgentRuntimeDeps,
83-
): Promise<boolean> {
74+
async execute(params: {
75+
action: ClientAction
76+
clientSessionId: string
77+
ws: WebSocket
78+
silent?: boolean
79+
getUserInfoFromApiKey: GetUserInfoFromApiKeyFn
80+
logger: Logger
81+
}): Promise<boolean> {
8482
const { action, clientSessionId, ws, silent, logger } = params
8583

8684
const userInfo =
@@ -175,13 +173,14 @@ export class WebSocketMiddleware {
175173

176174
export const protec = new WebSocketMiddleware(BACKEND_AGENT_RUNTIME_IMPL)
177175

178-
protec.use(async (params) => {
179-
const { action } = params
180-
return checkAuth({
181-
...params,
176+
protec.use(async ({ action, clientSessionId, logger }) =>
177+
checkAuth({
178+
fingerprintId: 'fingerprintId' in action ? action.fingerprintId : undefined,
182179
authToken: 'authToken' in action ? action.authToken : undefined,
183-
})
184-
})
180+
clientSessionId,
181+
logger,
182+
}),
183+
)
185184

186185
// Organization repository coverage detection middleware
187186
protec.use(async ({ action, userInfo, logger }) => {

packages/internal/src/knowledge.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,9 @@ All environment variables are defined and validated in `env.ts`:
2525
- **Purpose**: Transactional emails (invitations, basic messages)
2626
- **Functions**: `sendOrganizationInvitationEmail`, `sendBasicEmail`, `sendSignupEventToLoops`
2727
- **Environment**: Requires `LOOPS_API_KEY`
28+
29+
### Auth Utilities
30+
31+
- **Purpose**: Admin user verification and session validation
32+
- **Functions**: `checkAuthToken`, `checkSessionIsAdmin`, `isCodebuffAdmin`
33+
- **Usage**: Used by admin routes and protected endpoints

packages/internal/src/utils/auth.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import db from '@codebuff/common/db'
22
import * as schema from '@codebuff/common/db/schema'
33
import { eq } from 'drizzle-orm'
44

5+
import { INVALID_AUTH_TOKEN_MESSAGE } from '@codebuff/common/old-constants'
6+
57
// List of admin user emails - single source of truth
68
const CODEBUFF_ADMIN_USER_EMAILS = [
79
'venkateshrameshkumar+1@gmail.com',
@@ -36,6 +38,62 @@ export interface AuthResult {
3638
}
3739
}
3840

41+
/**
42+
* Check authentication based on auth token and fingerprint ID
43+
* Returns structured result instead of throwing or logging directly
44+
*/
45+
export async function checkAuthToken({
46+
fingerprintId,
47+
authToken,
48+
}: {
49+
fingerprintId?: string
50+
authToken?: string
51+
}): Promise<AuthResult> {
52+
if (!authToken) {
53+
if (!fingerprintId) {
54+
return {
55+
success: false,
56+
error: {
57+
type: 'missing-credentials',
58+
message: 'Auth token and fingerprint ID are missing',
59+
},
60+
}
61+
}
62+
return { success: true }
63+
}
64+
65+
const user = await db
66+
.select({
67+
id: schema.user.id,
68+
email: schema.user.email,
69+
discord_id: schema.user.discord_id,
70+
})
71+
.from(schema.user)
72+
.innerJoin(schema.session, eq(schema.user.id, schema.session.userId))
73+
.where(eq(schema.session.sessionToken, authToken))
74+
.then((users) => {
75+
if (users.length === 1) {
76+
return users[0]
77+
}
78+
return undefined
79+
})
80+
81+
if (!user) {
82+
return {
83+
success: false,
84+
error: {
85+
type: 'invalid-token',
86+
message: INVALID_AUTH_TOKEN_MESSAGE,
87+
},
88+
}
89+
}
90+
91+
return {
92+
success: true,
93+
user,
94+
}
95+
}
96+
3997
/**
4098
* Check if the current user session corresponds to a Codebuff admin
4199
* Returns the admin user if authorized, null if not

web/src/app/api/agents/publish/route.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as schema from '@codebuff/common/db/schema'
33
import { validateAgentsWithSpawnableAgents } from '@codebuff/common/templates/agent-validation'
44
import { publishAgentsRequestSchema } from '@codebuff/common/types/api/agents/publish'
55
import {
6+
checkAuthToken,
67
determineNextVersion,
78
stringifyVersion,
89
versionExists,
@@ -21,7 +22,6 @@ import { authOptions } from '../../auth/[...nextauth]/auth-options'
2122
import type { Version } from '@codebuff/internal'
2223
import type { NextRequest } from 'next/server'
2324

24-
import { getUserInfoFromApiKey } from '@/db/user'
2525
import { logger } from '@/util/logger'
2626

2727
async function getPublishedAgentIds(publisherId: string) {
@@ -96,12 +96,9 @@ export async function POST(request: NextRequest) {
9696
if (session?.user?.id) {
9797
userId = session.user.id
9898
} else if (authToken) {
99-
const user = await getUserInfoFromApiKey({
100-
apiKey: authToken,
101-
fields: ['id'],
102-
})
103-
if (user) {
104-
userId = user.id
99+
const authResult = await checkAuthToken({ authToken })
100+
if (authResult.success && authResult.user) {
101+
userId = authResult.user.id
105102
}
106103
}
107104

0 commit comments

Comments
 (0)