Files
openmonetis/src/app/api/attachments/[attachmentId]/presign/route.ts
Felipe Coutinho 10afef9fec fix(segurança): corrigir 10 vulnerabilidades do relatório de segurança
- tokens: remover aceite de expiresAt NULL e forçar TTL de 1 ano
- tokens: corrigir refresh que invalidava access token anterior
- xlsx: desabilitar parsing de fórmulas (CVE-2024-44294)
- csp: expandir Content-Security-Policy com origens explícitas
- headers: adicionar Referrer-Policy e X-Permitted-Cross-Domain-Policies
- api: retornar 401 JSON em vez de redirect 302 em rotas autenticadas
- health: remover version disclosure do /api/health
- robots.txt: simplificar para não expor rotas internas
- sitemap: corrigir URL com protocolo duplicado
- criar security.txt (RFC 9116)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 02:47:05 +00:00

55 lines
1.2 KiB
TypeScript

import { and, eq } from "drizzle-orm";
import { NextResponse } from "next/server";
import { attachments } from "@/db/schema";
import { getOptionalUserSession } from "@/shared/lib/auth/server";
import { db } from "@/shared/lib/db";
import { createPresignedGetUrl } from "@/shared/lib/storage/presign";
const PRIVATE_RESPONSE_HEADERS = {
"Cache-Control": "private, no-store",
};
export async function GET(
_request: Request,
{ params }: { params: Promise<{ attachmentId: string }> },
) {
const [session, { attachmentId }] = await Promise.all([
getOptionalUserSession(),
params,
]);
if (!session?.user) {
return NextResponse.json(
{ error: "Não autenticado" },
{ status: 401, headers: PRIVATE_RESPONSE_HEADERS },
);
}
const userId = session.user.id;
const [row] = await db
.select({ fileKey: attachments.fileKey })
.from(attachments)
.where(
and(eq(attachments.id, attachmentId), eq(attachments.userId, userId)),
);
if (!row) {
return NextResponse.json(
{ error: "Not found" },
{
status: 404,
headers: PRIVATE_RESPONSE_HEADERS,
},
);
}
const url = await createPresignedGetUrl(row.fileKey);
return NextResponse.json(
{ url },
{
headers: PRIVATE_RESPONSE_HEADERS,
},
);
}