2e688c8e52
The application source was untracked, so this commit brings it under version control together with fixes for the issues found while auditing it. Notable fixes: Authorization - Require a session and scope to senderEmail on /api/transfers/[id] (GET, DELETE) and .../resend. These were unauthenticated over an autoincrement id, so the ids could be walked to soft-delete any transfer, read sender/recipient metadata, or make the app mail arbitrary recipients. - Require a session on the legacy /api/send and /api/chunk-upload endpoints, and take the sender from the session rather than a request field so transfers cannot be posted as another user. Encryption - Replace the chunk encryption scheme. Every chunk was encrypted under one shared session IV with its auth tag discarded, which reuses the AES-GCM keystream (XORing two ciphertexts recovers plaintext without the key) and left the stored file undecryptable, surfacing to users as a wrong-password error. Chunks are now self-contained frames carrying their own random IV and auth tag, behind a magic+salt header. - Files written by the previous format now report UNSUPPORTED_FORMAT instead of a misleading password error. Download - Verify the password against the stored bcrypt hash before serving a file, and enforce expiresAt and DELETED/EXPIRED status. - Move the password from the query string into a POST body so it stays out of access logs and Referer headers. - Record a download only after successful authentication. - Decrypt frame by frame through a stream instead of buffering the whole file, and encode the Content-Disposition filename per RFC 5987. Data exposure - /api/download ran before the password prompt and returned the full transfer row, including absolute server file paths. It now returns only what the pre-password screen renders; filenames, message and recipient are withheld until /api/verify succeeds. Correctness - Fix BigInt handling that made /api/transfers and /api/transfers/[id] fail unconditionally (JSON.stringify cannot serialize BigInt, and seeding a BigInt reduce with 0 throws). - Fail loudly on a missing chunk during reassembly rather than silently writing a corrupt file. - Meter plan usage in plaintext bytes rather than on-disk encrypted size. Ignore /uploads: it holds runtime transfer payloads, not source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
1.7 KiB
TypeScript
73 lines
1.7 KiB
TypeScript
import { getServerSession } from 'next-auth';
|
|
import { authOptions } from '@/lib/auth';
|
|
import { prisma } from '@/lib/prisma';
|
|
import { NextResponse } from 'next/server';
|
|
|
|
export async function GET() {
|
|
const session = await getServerSession(authOptions);
|
|
|
|
if (!session?.user?.email) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
const email = session.user.email;
|
|
|
|
// Transfers sent
|
|
const transfersSent = await prisma.transfer.count({
|
|
where: { senderEmail: email },
|
|
});
|
|
|
|
// Total files sent - sum of totalFiles field on transfers
|
|
const totalFilesData = await prisma.transfer.aggregate({
|
|
where: { senderEmail: email },
|
|
_sum: {
|
|
totalFiles: true,
|
|
},
|
|
});
|
|
const totalFilesSent = totalFilesData._sum.totalFiles ?? 0;
|
|
|
|
// Downloads - count of fileDownload for files in transfers by this sender
|
|
const downloads = await prisma.fileDownload.count({
|
|
where: {
|
|
file: {
|
|
transfer: {
|
|
senderEmail: email,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
// Active transfers - example: transfers not expired yet
|
|
const activeTransfers = await prisma.transfer.count({
|
|
where: {
|
|
senderEmail: email,
|
|
expiresAt: {
|
|
gt: new Date(),
|
|
},
|
|
},
|
|
});
|
|
|
|
const startOfMonth = new Date();
|
|
startOfMonth.setDate(1);
|
|
startOfMonth.setHours(0, 0, 0, 0);
|
|
|
|
const transfersThisMonth = await prisma.transfer.count({
|
|
where: {
|
|
senderEmail: email,
|
|
createdAt: {
|
|
gte: startOfMonth,
|
|
},
|
|
},
|
|
});
|
|
|
|
const remainingTransfers = Math.max(10 - transfersThisMonth, 0);
|
|
|
|
return NextResponse.json({
|
|
transfersSent,
|
|
totalFilesSent,
|
|
downloads,
|
|
activeTransfers,
|
|
remainingTransfers,
|
|
});
|
|
}
|