Skip to content

Commit 7aa8c53

Browse files
refactor(web): route all session revocations through DELETE /api/sessions; remove legacy per-id routes
Consolidates logic to a single endpoint to reduce duplication and CSRF surface, keeping UI and backend in sync. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
1 parent ada1e73 commit 7aa8c53

File tree

4 files changed

+362
-297
lines changed

4 files changed

+362
-297
lines changed

web/src/app/api/api-keys/[id]/route.ts

Lines changed: 0 additions & 92 deletions
This file was deleted.

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

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { NextResponse, NextRequest } from 'next/server'
2+
import { getServerSession } from 'next-auth'
3+
import db from '@codebuff/common/db'
4+
import * as schema from '@codebuff/common/db/schema'
5+
import { and, eq, inArray } from 'drizzle-orm'
6+
import { sha256 } from '@/lib/crypto'
7+
import { authOptions } from '@/app/api/auth/[...nextauth]/auth-options'
8+
import { siteConfig } from '@/lib/constant'
9+
10+
function isSameOrigin(request: NextRequest) {
11+
try {
12+
const base = new URL(siteConfig.url()).origin
13+
const origin = request.headers.get('origin')
14+
const referer = request.headers.get('referer')
15+
if (origin && new URL(origin).origin === base) return true
16+
if (referer && new URL(referer).origin === base) return true
17+
} catch {}
18+
return false
19+
}
20+
21+
// DELETE /api/sessions
22+
// Body: { sessionIds?: string[]; tokenIds?: string[] }
23+
export async function DELETE(req: NextRequest) {
24+
try {
25+
if (!isSameOrigin(req)) {
26+
return new NextResponse('Forbidden', { status: 403 })
27+
}
28+
29+
const session = await getServerSession(authOptions)
30+
if (!session?.user?.id) {
31+
return new NextResponse('Unauthorized', { status: 401 })
32+
}
33+
34+
const { sessionIds, tokenIds }: { sessionIds?: string[]; tokenIds?: string[] } = await req
35+
.json()
36+
.catch(() => ({} as any))
37+
38+
if ((!sessionIds || sessionIds.length === 0) && (!tokenIds || tokenIds.length === 0)) {
39+
return NextResponse.json({ revokedSessions: 0, revokedTokens: 0 })
40+
}
41+
42+
const userId = session.user.id
43+
let revokedSessions = 0
44+
let revokedTokens = 0
45+
46+
// 1) Map provided sessionIds (raw token or sha256(token)) to actual session tokens
47+
if (sessionIds && sessionIds.length > 0) {
48+
const userSessions = await db
49+
.select({ sessionToken: schema.session.sessionToken, type: schema.session.type })
50+
.from(schema.session)
51+
.where(eq(schema.session.userId, userId))
52+
53+
const tokenSet = new Set(userSessions.map((s) => s.sessionToken))
54+
const hashToToken = new Map(
55+
userSessions.map((s) => [sha256(s.sessionToken), s.sessionToken] as const),
56+
)
57+
58+
const tokensToDelete: string[] = []
59+
for (const provided of sessionIds) {
60+
if (tokenSet.has(provided)) {
61+
tokensToDelete.push(provided)
62+
} else {
63+
const mapped = hashToToken.get(provided)
64+
if (mapped) tokensToDelete.push(mapped)
65+
}
66+
}
67+
68+
if (tokensToDelete.length > 0) {
69+
const result = await db
70+
.delete(schema.session)
71+
.where(
72+
and(
73+
eq(schema.session.userId, userId),
74+
eq(schema.session.type, 'web'), // do not delete PATs here
75+
inArray(schema.session.sessionToken, tokensToDelete),
76+
),
77+
)
78+
.returning({ sessionToken: schema.session.sessionToken })
79+
revokedSessions = result.length
80+
}
81+
}
82+
83+
// 2) Revoke API key tokens (PATs) by id (full token string)
84+
if (tokenIds && tokenIds.length > 0) {
85+
const result = await db
86+
.delete(schema.session)
87+
.where(
88+
and(
89+
eq(schema.session.userId, userId),
90+
eq(schema.session.type, 'pat'),
91+
inArray(schema.session.sessionToken, tokenIds),
92+
),
93+
)
94+
.returning({ sessionToken: schema.session.sessionToken })
95+
revokedTokens = result.length
96+
}
97+
98+
return NextResponse.json({ revokedSessions, revokedTokens })
99+
} catch (e: any) {
100+
return new NextResponse(e?.message ?? 'Internal error', { status: 500 })
101+
}
102+
}

web/src/app/api/user/sessions/[id]/route.ts

Lines changed: 0 additions & 92 deletions
This file was deleted.

0 commit comments

Comments
 (0)