Accept CDN URLs for question attachments
Attachments uploaded via question-upload get a CDN URL (files.second-brain.ru via S3_CDN_URL), but the validator only accepted the direct S3 endpoint prefix — so every attachment was silently dropped (message text saved, file lost). Add isAllowedPublicUrl (CDN + direct S3) in lib/s3 and use it in both question routes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import { headers } from "next/headers";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { sendQuestionFollowUpEmail, sendQuestionReplyEmail } from "@/lib/email";
|
||||
import { isAllowedPublicUrl } from "@/lib/s3";
|
||||
|
||||
interface FileAttachment {
|
||||
name: string;
|
||||
@@ -10,13 +11,6 @@ interface FileAttachment {
|
||||
size: number;
|
||||
}
|
||||
|
||||
function buildS3Prefix(): string {
|
||||
const endpoint = process.env.S3_ENDPOINT ?? "";
|
||||
const bucket = process.env.S3_BUCKET ?? "";
|
||||
// e.g. https://fsn1.your-objectstorage.com/lms-uploads/
|
||||
return `${endpoint}/${bucket}/`;
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
@@ -52,14 +46,11 @@ export async function POST(
|
||||
return NextResponse.json({ error: "text is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const s3Prefix = buildS3Prefix();
|
||||
const safeFiles = files
|
||||
?.filter(
|
||||
(f) =>
|
||||
typeof f.name === "string" &&
|
||||
typeof f.url === "string" &&
|
||||
f.url.startsWith("https://") &&
|
||||
f.url.startsWith(s3Prefix) &&
|
||||
isAllowedPublicUrl(f.url) &&
|
||||
typeof f.size === "number"
|
||||
)
|
||||
.map((f) => ({ name: f.name.slice(0, 255), url: f.url, size: Math.max(0, f.size) }));
|
||||
|
||||
@@ -3,6 +3,7 @@ import { headers } from "next/headers";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { sendQuestionCreatedEmail } from "@/lib/email";
|
||||
import { isAllowedPublicUrl } from "@/lib/s3";
|
||||
|
||||
interface FileAttachment {
|
||||
name: string;
|
||||
@@ -10,12 +11,6 @@ interface FileAttachment {
|
||||
size: number;
|
||||
}
|
||||
|
||||
function buildS3Prefix(): string {
|
||||
const endpoint = process.env.S3_ENDPOINT ?? "";
|
||||
const bucket = process.env.S3_BUCKET ?? "";
|
||||
return `${endpoint}/${bucket}/`;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
@@ -82,14 +77,11 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: "title and text are required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const s3Prefix = buildS3Prefix();
|
||||
const safeFiles = files
|
||||
?.filter(
|
||||
(f) =>
|
||||
typeof f.name === "string" &&
|
||||
typeof f.url === "string" &&
|
||||
f.url.startsWith("https://") &&
|
||||
f.url.startsWith(s3Prefix) &&
|
||||
isAllowedPublicUrl(f.url) &&
|
||||
typeof f.size === "number"
|
||||
)
|
||||
.map((f) => ({ name: f.name.slice(0, 255), url: f.url, size: Math.max(0, f.size) }));
|
||||
|
||||
Reference in New Issue
Block a user