From 14efa1dce4b4531bf77a429723461bd1634b54e1 Mon Sep 17 00:00:00 2001 From: Chandler Copeland Date: Fri, 3 Apr 2026 15:43:40 -0600 Subject: [PATCH] feat(15-01): create public signer download route GET /api/sign/download/[token] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Public route — no auth session required - Validates signer-download JWT via verifySignerDownloadToken - Guards: expired/invalid token (401), incomplete doc (404), path traversal (403) - Serves signed PDF with Content-Disposition attachment header --- .../app/api/sign/download/[token]/route.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 teressa-copeland-homes/src/app/api/sign/download/[token]/route.ts diff --git a/teressa-copeland-homes/src/app/api/sign/download/[token]/route.ts b/teressa-copeland-homes/src/app/api/sign/download/[token]/route.ts new file mode 100644 index 0000000..569babc --- /dev/null +++ b/teressa-copeland-homes/src/app/api/sign/download/[token]/route.ts @@ -0,0 +1,46 @@ +import { NextRequest } from 'next/server'; +import { verifySignerDownloadToken } from '@/lib/signing/token'; +import { db } from '@/lib/db'; +import { documents } from '@/lib/db/schema'; +import { eq } from 'drizzle-orm'; +import path from 'node:path'; +import { readFile } from 'node:fs/promises'; + +const UPLOADS_DIR = path.join(process.cwd(), 'uploads'); + +// Public route — no auth session required +export async function GET( + _req: NextRequest, + { params }: { params: Promise<{ token: string }> } +) { + const { token } = await params; + + let documentId: string; + try { + ({ documentId } = await verifySignerDownloadToken(token)); + } catch { + return new Response('Invalid or expired download link', { status: 401 }); + } + + const doc = await db.query.documents.findFirst({ + where: eq(documents.id, documentId), + columns: { signedFilePath: true, status: true, name: true }, + }); + + if (!doc || doc.status !== 'Signed' || !doc.signedFilePath) { + return new Response('Document not yet complete', { status: 404 }); + } + + const absPath = path.join(UPLOADS_DIR, doc.signedFilePath); + if (!absPath.startsWith(UPLOADS_DIR)) { + return new Response('Forbidden', { status: 403 }); + } + + const fileBytes = await readFile(absPath); + return new Response(fileBytes, { + headers: { + 'Content-Type': 'application/pdf', + 'Content-Disposition': `attachment; filename="${doc.name}-signed.pdf"`, + }, + }); +}