Skip to content

Commit 34ac8ee

Browse files
committed
Subscription endpoint: support token bearer auth
1 parent 9b50b8e commit 34ac8ee

File tree

3 files changed

+49
-14
lines changed

3 files changed

+49
-14
lines changed

web/src/app/api/referrals/route.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import { authOptions } from '../auth/[...nextauth]/auth-options'
1010

1111
import type { NextRequest } from 'next/server'
1212

13-
import { extractApiKeyFromHeader } from '@/util/auth'
13+
import {
14+
extractApiKeyFromHeader,
15+
getUserIdFromSessionToken,
16+
} from '@/util/auth'
1417

1518

1619
type Referral = Pick<typeof schema.user.$inferSelect, 'id' | 'name' | 'email'> &
@@ -169,16 +172,11 @@ export async function POST(request: NextRequest) {
169172
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
170173
}
171174

172-
const user = await db.query.session.findFirst({
173-
where: eq(schema.session.sessionToken, authToken),
174-
columns: {
175-
userId: true,
176-
},
177-
})
175+
const userId = await getUserIdFromSessionToken(authToken)
178176

179-
if (!user?.userId) {
177+
if (!userId) {
180178
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
181179
}
182180

183-
return redeemReferralCode(referralCode, user.userId)
181+
return redeemReferralCode(referralCode, userId)
184182
}

web/src/app/api/user/subscription/route.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,36 @@ import { NextResponse } from 'next/server'
1111
import { getServerSession } from 'next-auth'
1212

1313
import { authOptions } from '@/app/api/auth/[...nextauth]/auth-options'
14+
import { extractApiKeyFromHeader, getUserIdFromSessionToken } from '@/util/auth'
1415
import { logger } from '@/util/logger'
1516

1617
import type {
1718
NoSubscriptionResponse,
1819
ActiveSubscriptionResponse,
1920
} from '@codebuff/common/types/subscription'
21+
import type { NextRequest } from 'next/server'
2022

21-
export async function GET() {
22-
const session = await getServerSession(authOptions)
23-
if (!session?.user?.id) {
24-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
23+
export async function GET(req: NextRequest) {
24+
let userId: string | undefined
25+
26+
// First, try Bearer token authentication (for CLI clients)
27+
const apiKey = extractApiKeyFromHeader(req)
28+
if (apiKey) {
29+
const userIdFromToken = await getUserIdFromSessionToken(apiKey)
30+
if (userIdFromToken) {
31+
userId = userIdFromToken
32+
}
33+
}
34+
35+
// Fall back to NextAuth session authentication (for web clients)
36+
if (!userId) {
37+
const session = await getServerSession(authOptions)
38+
userId = session?.user?.id
2539
}
2640

27-
const userId = session.user.id
41+
if (!userId) {
42+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
43+
}
2844

2945
// Fetch user preference for always use a-la-carte
3046
const [subscription, userPrefs] = await Promise.all([

web/src/util/auth.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
1+
import db from '@codebuff/internal/db'
2+
import * as schema from '@codebuff/internal/db/schema'
3+
import { and, eq, gt } from 'drizzle-orm'
4+
15
import type { NextRequest } from 'next/server'
26

7+
/**
8+
* Look up user ID from a session token in the database.
9+
* Returns null if the token is invalid or expired.
10+
*/
11+
export async function getUserIdFromSessionToken(
12+
sessionToken: string,
13+
): Promise<string | null> {
14+
const session = await db.query.session.findFirst({
15+
where: and(
16+
eq(schema.session.sessionToken, sessionToken),
17+
gt(schema.session.expires, new Date()),
18+
),
19+
columns: { userId: true },
20+
})
21+
return session?.userId ?? null
22+
}
23+
324
/**
425
* Extract api key from x-codebuff-api-key header or authorization header
526
*/

0 commit comments

Comments
 (0)