fix: images, couverture, slash, agents, wizard, Stripe et admin
- Collage/accès images: ownership path userId + meta legacy, 403 non caché - Couverture note: explicite (choix/aucune), plus d’auto depuis le corps - Éditeur: suppression image TipTap, sync immédiate, toolbar réordonnée - Slash: listes par titre (plus d’index), keywords nettoyés - Agents: nextRun à la réactivation, cron sans stampede null - Wizard: titres courts + mode Expert plus robuste - Stripe checkout hosted fiable; admin métriques live; dark mode IA/settings - Indexation notes courtes + test unit aligné
This commit is contained in:
@@ -21,16 +21,30 @@ export async function GET(
|
||||
const session = await auth()
|
||||
const { path: segments } = await params
|
||||
|
||||
// Only serve from uploads/notes/ subdirectory
|
||||
if (segments[0] !== 'notes') {
|
||||
// Only serve from uploads/notes/ (flat or userId/file)
|
||||
if (!segments.length || segments[0] !== 'notes') {
|
||||
return new NextResponse('Not found', { status: 404 })
|
||||
}
|
||||
|
||||
// Reject path traversal
|
||||
if (segments.some((s) => s === '..' || s.includes('\0'))) {
|
||||
return new NextResponse('Forbidden', { status: 403 })
|
||||
}
|
||||
|
||||
const filename = segments[segments.length - 1]
|
||||
const allowed = await canAccessUploadedNoteImage(filename, session?.user?.id)
|
||||
// Never serve meta sidecars as images
|
||||
if (filename.endsWith('.meta.json')) {
|
||||
return new NextResponse('Not found', { status: 404 })
|
||||
}
|
||||
|
||||
const allowed = await canAccessUploadedNoteImage(segments, session?.user?.id)
|
||||
if (!allowed) {
|
||||
return new NextResponse(session?.user?.id ? 'Forbidden' : 'Unauthorized', {
|
||||
status: session?.user?.id ? 403 : 401,
|
||||
headers: {
|
||||
// Do not let browsers / CDNs cache a denial (would stick a broken image)
|
||||
'Cache-Control': 'no-store',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,9 +54,8 @@ export async function GET(
|
||||
return new NextResponse('Unsupported file type', { status: 400 })
|
||||
}
|
||||
|
||||
// Prevent path traversal
|
||||
const safePath = path.join(UPLOAD_DIR, ...segments)
|
||||
if (!safePath.startsWith(UPLOAD_DIR)) {
|
||||
if (!safePath.startsWith(UPLOAD_DIR + path.sep) && safePath !== UPLOAD_DIR) {
|
||||
return new NextResponse('Forbidden', { status: 403 })
|
||||
}
|
||||
|
||||
@@ -57,7 +70,8 @@ export async function GET(
|
||||
return new NextResponse(buffer, {
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
'Cache-Control': 'public, max-age=31536000, immutable',
|
||||
// private: only for authenticated session fetches; still long cache after auth ok
|
||||
'Cache-Control': 'private, max-age=31536000, immutable',
|
||||
'Content-Length': String(buffer.length),
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user