Skip to content

Commit 78bbe2d

Browse files
Fix critical security vulnerability: use server-side auth instead of client-provided sessionID
1 parent ed495f5 commit 78bbe2d

5 files changed

Lines changed: 50 additions & 61 deletions

File tree

app/api/files/content/route.ts

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,30 @@
11
import { NextRequest, NextResponse } from 'next/server'
2-
import { createClient } from '@supabase/supabase-js'
2+
import { createServerClient } from '@/lib/supabase-server'
33

44
export const dynamic = 'force-dynamic'
55

66
export async function GET(request: NextRequest) {
77
try {
8-
const sessionID = request.nextUrl.searchParams.get('sessionID')
98
const path = request.nextUrl.searchParams.get('path')
109

11-
if (!sessionID || !path) {
12-
return NextResponse.json({ error: 'Session ID and path are required' }, { status: 400 })
10+
if (!path) {
11+
return NextResponse.json({ error: 'Path is required' }, { status: 400 })
1312
}
1413

15-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
16-
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY
14+
const supabase = createServerClient()
1715

18-
if (!supabaseUrl || !supabaseKey) {
19-
return NextResponse.json({ error: 'Supabase configuration missing' }, { status: 500 })
20-
}
16+
// Get authenticated user from session
17+
const { data: { user }, error: authError } = await supabase.auth.getUser()
2118

22-
const supabase = createClient(supabaseUrl, supabaseKey)
19+
if (authError || !user) {
20+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
21+
}
2322

2423
// Fetch the file content
2524
const { data: file, error } = await supabase
2625
.from('workspace_files')
2726
.select('content, path, name, is_directory')
28-
.eq('user_id', sessionID)
27+
.eq('user_id', user.id)
2928
.eq('path', path)
3029
.single()
3130

@@ -52,20 +51,20 @@ export async function GET(request: NextRequest) {
5251
export async function POST(request: NextRequest) {
5352
try {
5453
const body = await request.json()
55-
const { sessionID, path, content } = body
54+
const { path, content } = body
5655

57-
if (!sessionID || !path || content === undefined) {
58-
return NextResponse.json({ error: 'Session ID, path, and content are required' }, { status: 400 })
56+
if (!path || content === undefined) {
57+
return NextResponse.json({ error: 'Path and content are required' }, { status: 400 })
5958
}
6059

61-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
62-
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY
60+
const supabase = createServerClient()
6361

64-
if (!supabaseUrl || !supabaseKey) {
65-
return NextResponse.json({ error: 'Supabase configuration missing' }, { status: 500 })
66-
}
62+
// Get authenticated user from session
63+
const { data: { user }, error: authError } = await supabase.auth.getUser()
6764

68-
const supabase = createClient(supabaseUrl, supabaseKey)
65+
if (authError || !user) {
66+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
67+
}
6968

7069
// Calculate content size
7170
const sizeBytes = Buffer.byteLength(content, 'utf8')
@@ -78,7 +77,7 @@ export async function POST(request: NextRequest) {
7877
size_bytes: sizeBytes,
7978
updated_at: new Date().toISOString()
8079
})
81-
.eq('user_id', sessionID)
80+
.eq('user_id', user.id)
8281
.eq('path', path)
8382
.select()
8483
.single()

app/api/files/route.ts

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NextRequest, NextResponse } from 'next/server'
2-
import { createClient } from '@supabase/supabase-js'
2+
import { createServerClient } from '@/lib/supabase-server'
33

44
export const dynamic = 'force-dynamic'
55

@@ -38,26 +38,20 @@ function buildFileTree(files: any[]): any[] {
3838

3939
export async function GET(request: NextRequest) {
4040
try {
41-
const sessionID = request.nextUrl.searchParams.get('sessionID')
41+
const supabase = createServerClient()
4242

43-
if (!sessionID) {
44-
return NextResponse.json({ error: 'Session ID is required' }, { status: 400 })
45-
}
46-
47-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
48-
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY
43+
// Get authenticated user from session
44+
const { data: { user }, error: authError } = await supabase.auth.getUser()
4945

50-
if (!supabaseUrl || !supabaseKey) {
51-
return NextResponse.json({ error: 'Supabase configuration missing' }, { status: 500 })
46+
if (authError || !user) {
47+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
5248
}
5349

54-
const supabase = createClient(supabaseUrl, supabaseKey)
55-
5650
// Fetch all workspace files for the user
5751
const { data: files, error } = await supabase
5852
.from('workspace_files')
5953
.select('*')
60-
.eq('user_id', sessionID)
54+
.eq('user_id', user.id)
6155
.order('path', { ascending: true })
6256

6357
if (error) {
@@ -78,20 +72,20 @@ export async function GET(request: NextRequest) {
7872
export async function POST(request: NextRequest) {
7973
try {
8074
const body = await request.json()
81-
const { sessionID, path, isDirectory, content = '' } = body
75+
const { path, isDirectory, content = '' } = body
8276

83-
if (!sessionID || !path) {
84-
return NextResponse.json({ error: 'Session ID and path are required' }, { status: 400 })
77+
if (!path) {
78+
return NextResponse.json({ error: 'Path is required' }, { status: 400 })
8579
}
8680

87-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
88-
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY
81+
const supabase = createServerClient()
8982

90-
if (!supabaseUrl || !supabaseKey) {
91-
return NextResponse.json({ error: 'Supabase configuration missing' }, { status: 500 })
92-
}
83+
// Get authenticated user from session
84+
const { data: { user }, error: authError } = await supabase.auth.getUser()
9385

94-
const supabase = createClient(supabaseUrl, supabaseKey)
86+
if (authError || !user) {
87+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
88+
}
9589

9690
// Extract file name and parent path
9791
const pathParts = path.split('/')
@@ -105,7 +99,7 @@ export async function POST(request: NextRequest) {
10599
const { data: file, error } = await supabase
106100
.from('workspace_files')
107101
.insert({
108-
user_id: sessionID,
102+
user_id: user.id,
109103
path,
110104
name,
111105
content,
@@ -131,26 +125,26 @@ export async function POST(request: NextRequest) {
131125
export async function DELETE(request: NextRequest) {
132126
try {
133127
const body = await request.json()
134-
const { sessionID, path } = body
128+
const { path } = body
135129

136-
if (!sessionID || !path) {
137-
return NextResponse.json({ error: 'Session ID and path are required' }, { status: 400 })
130+
if (!path) {
131+
return NextResponse.json({ error: 'Path is required' }, { status: 400 })
138132
}
139133

140-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
141-
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY
134+
const supabase = createServerClient()
142135

143-
if (!supabaseUrl || !supabaseKey) {
144-
return NextResponse.json({ error: 'Supabase configuration missing' }, { status: 500 })
145-
}
136+
// Get authenticated user from session
137+
const { data: { user }, error: authError } = await supabase.auth.getUser()
146138

147-
const supabase = createClient(supabaseUrl, supabaseKey)
139+
if (authError || !user) {
140+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
141+
}
148142

149143
// Delete the file and all its children (for directories)
150144
const { error } = await supabase
151145
.from('workspace_files')
152146
.delete()
153-
.eq('user_id', sessionID)
147+
.eq('user_id', user.id)
154148
.or(`path.eq.${path},parent_path.eq.${path}`)
155149

156150
if (error) {

app/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,6 @@ export default function Home() {
457457
'Content-Type': 'application/json',
458458
},
459459
body: JSON.stringify({
460-
sessionID: session.user.id,
461460
path,
462461
content
463462
}),

components/github-import.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ export function GitHubImport({ onImport, onClose }: GitHubImportProps) {
176176
'Content-Type': 'application/json',
177177
},
178178
body: JSON.stringify({
179-
sessionID: session.user.id,
180179
path: `${repo.name}/${file.path}`,
181180
isDirectory: false,
182181
content: file.content,

components/ide.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function IDE({ sandboxId }: IDEProps = {}) {
4242
} else if (session) {
4343
// Fetch files from Supabase
4444
try {
45-
const response = await fetch(`/api/files?sessionID=${session.user.id}`)
45+
const response = await fetch('/api/files')
4646
if (response.ok) {
4747
const data = await response.json()
4848
setFiles(data)
@@ -79,7 +79,7 @@ export function IDE({ sandboxId }: IDEProps = {}) {
7979
setSelectedFile({ path, content })
8080
} else if (session) {
8181
// Load file from Supabase
82-
const response = await fetch(`/api/files/content?sessionID=${session.user.id}&path=${path}`)
82+
const response = await fetch(`/api/files/content?path=${encodeURIComponent(path)}`)
8383
const { content } = await response.json()
8484
setSelectedFile({ path, content })
8585
}
@@ -102,7 +102,7 @@ export function IDE({ sandboxId }: IDEProps = {}) {
102102
headers: {
103103
'Content-Type': 'application/json',
104104
},
105-
body: JSON.stringify({ sessionID: session.user.id, path, content }),
105+
body: JSON.stringify({ path, content }),
106106
})
107107
}
108108
}
@@ -122,7 +122,6 @@ export function IDE({ sandboxId }: IDEProps = {}) {
122122
'Content-Type': 'application/json',
123123
},
124124
body: JSON.stringify({
125-
sessionID: session.user.id,
126125
path,
127126
isDirectory,
128127
content: isDirectory ? '' : '// New file\n'
@@ -151,7 +150,6 @@ export function IDE({ sandboxId }: IDEProps = {}) {
151150
'Content-Type': 'application/json',
152151
},
153152
body: JSON.stringify({
154-
sessionID: session.user.id,
155153
path,
156154
}),
157155
})

0 commit comments

Comments
 (0)