feat(06-05): download token utilities + download API route
- Add createDownloadToken and verifyDownloadToken to token.ts (15-min TTL, purpose:'download' claim) - Create GET /api/sign/[token]/download route: validates dt query param JWT, streams signedFilePath as PDF - Path traversal guard: signedFilePath must start with UPLOADS_DIR - Auto-fix: Buffer cast to Uint8Array for Response BodyInit compatibility (Next.js 16 / TypeScript strict)
This commit is contained in:
@@ -30,3 +30,19 @@ export async function verifySigningToken(token: string): Promise<{ documentId: s
|
||||
const { payload } = await jwtVerify(token, getSecret());
|
||||
return payload as { documentId: string; jti: string; exp: number };
|
||||
}
|
||||
|
||||
// Short-lived download token for client copy download (15-min TTL, no DB record)
|
||||
// purpose: 'download' claim distinguishes from signing tokens
|
||||
export async function createDownloadToken(documentId: string): Promise<string> {
|
||||
return await new SignJWT({ documentId, purpose: 'download' })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('15m')
|
||||
.sign(getSecret());
|
||||
}
|
||||
|
||||
export async function verifyDownloadToken(token: string): Promise<{ documentId: string }> {
|
||||
const { payload } = await jwtVerify(token, getSecret());
|
||||
if (payload['purpose'] !== 'download') throw new Error('Not a download token');
|
||||
return { documentId: payload['documentId'] as string };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user