Skip to content

Commit 00c97ea

Browse files
committed
replace checkAuthToken with getUserInfoFromApiKey
1 parent c8d4359 commit 00c97ea

File tree

6 files changed

+80
-159
lines changed

6 files changed

+80
-159
lines changed

backend/src/api/usage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ 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'
56
import { checkAuth } from '../util/check-auth'
67
import { logger } from '../util/logger'
78
import { getUserInfoFromApiKey } from '../websockets/auth'
@@ -31,10 +32,9 @@ async function usageHandler(
3132
const clientSessionId = `api-${fingerprintId}-${Date.now()}`
3233

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

backend/src/util/check-auth.ts

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

63
import { extractAuthTokenFromHeader } from './auth-helpers'
4+
import { getUserInfoFromApiKey } from '../websockets/auth'
75

86
import type { ServerAction } from '@codebuff/common/actions'
7+
import type { GetUserInfoFromApiKeyFn } from '@codebuff/common/types/contracts/database'
98
import type { Logger } from '@codebuff/common/types/contracts/logger'
109
import type { Request, Response, NextFunction } from 'express'
1110

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

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

4547
// Express middleware for checking admin access
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-
}
56-
57-
// Generate a client session ID for this request
58-
const clientSessionId = `admin-relabel-${Date.now()}`
59-
60-
// Check authentication
61-
const authResult = await checkAuth({
62-
authToken,
63-
clientSessionId,
64-
logger,
65-
})
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+
}
6658

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-
}
59+
// Generate a client session ID for this request
60+
const clientSessionId = `admin-relabel-${Date.now()}`
7561

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,
62+
// Check authentication
63+
const user = await getUserInfoFromApiKey({
64+
apiKey: authToken,
65+
fields: ['id', 'email'],
8166
})
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])
8667

87-
if (!user) {
88-
return res.status(401).json({ error: 'Invalid session' })
89-
}
68+
if (!user) {
69+
return res.status(401).json({ error: 'Invalid session' })
70+
}
9071

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' })
99-
}
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+
}
10081

101-
// Store user info in request for handlers to use if needed
102-
// req.user = adminUser // TODO: ensure type check passes
82+
// Store user info in request for handlers to use if needed
83+
// req.user = adminUser // TODO: ensure type check passes
10384

104-
// Auth passed and user is admin, proceed to next middleware
105-
next()
106-
return
107-
}
85+
// Auth passed and user is admin, proceed to next middleware
86+
next()
87+
return
88+
}

backend/src/websockets/middleware.ts

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

6262
use<T extends ClientAction['type']>(
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>,
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>,
7071
) {
7172
this.middlewares.push(callback as MiddlewareCallback)
7273
}
7374

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> {
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> {
8284
const { action, clientSessionId, ws, silent, logger } = params
8385

8486
const userInfo =
@@ -91,11 +93,11 @@ export class WebSocketMiddleware {
9193

9294
for (const middleware of this.middlewares) {
9395
const actionOrContinue = await middleware({
96+
...params,
9497
action,
9598
clientSessionId,
9699
ws,
97100
userInfo,
98-
logger,
99101
})
100102
if (actionOrContinue) {
101103
logger.warn(
@@ -173,14 +175,13 @@ export class WebSocketMiddleware {
173175

174176
export const protec = new WebSocketMiddleware(BACKEND_AGENT_RUNTIME_IMPL)
175177

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

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

packages/internal/src/knowledge.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,3 @@ 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: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ 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-
75
// List of admin user emails - single source of truth
86
const CODEBUFF_ADMIN_USER_EMAILS = [
97
'venkateshrameshkumar+1@gmail.com',
@@ -38,62 +36,6 @@ export interface AuthResult {
3836
}
3937
}
4038

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-
9739
/**
9840
* Check if the current user session corresponds to a Codebuff admin
9941
* Returns the admin user if authorized, null if not

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ 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,
76
determineNextVersion,
87
stringifyVersion,
98
versionExists,
@@ -22,6 +21,7 @@ import { authOptions } from '../../auth/[...nextauth]/auth-options'
2221
import type { Version } from '@codebuff/internal'
2322
import type { NextRequest } from 'next/server'
2423

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

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

0 commit comments

Comments
 (0)