feat: secure combined uploads and update UI components

Changes:
- Secure uploadUtilBillsProofOfPayment with checksum validation
- Update ViewLocationCard to accept and use shareId prop
- Update ViewBillCard to accept shareId and use it for uploads
- Update ViewBillBadge to pass shareId to bill detail pages
- Add client-side validation check for shareId before upload
- Update back button links to use shareId

Security improvements:
- Both per-bill and combined uploads now validate checksum and TTL
- IP-based rate limiting applied to both upload types
- PDF magic bytes validation for both upload types

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Knee Cola
2025-12-08 00:25:26 +01:00
parent 844e386e18
commit 81dddb526a
4 changed files with 102 additions and 49 deletions

View File

@@ -11,11 +11,12 @@ import { Pdf417Barcode } from "./Pdf417Barcode";
import { uploadProofOfPayment } from "../lib/actions/billActions";
export interface ViewBillCardProps {
location: BillingLocation,
bill: Bill,
location: BillingLocation;
bill: Bill;
shareId?: string;
}
export const ViewBillCard: FC<ViewBillCardProps> = ({ location, bill }) => {
export const ViewBillCard: FC<ViewBillCardProps> = ({ location, bill, shareId }) => {
const router = useRouter();
const t = useTranslations("bill-edit-form");
@@ -31,23 +32,28 @@ export const ViewBillCard: FC<ViewBillCardProps> = ({ location, bill }) => {
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
// Validate file type
// Validate file type client-side (quick feedback)
if (file.type !== 'application/pdf') {
setUploadError('Only PDF files are accepted');
e.target.value = ''; // Reset input
return;
}
if (!shareId) {
setUploadError('Invalid upload link');
return;
}
setIsUploading(true);
setUploadError(null);
try {
const formData = new FormData();
formData.append('proofOfPayment', file);
const result = await uploadProofOfPayment(locationID, billID as string, formData);
const result = await uploadProofOfPayment(shareId, billID as string, formData);
if (result.success) {
setProofOfPaymentFilename(file.name);
setProofOfPaymentUploadedAt(new Date());
@@ -161,7 +167,7 @@ export const ViewBillCard: FC<ViewBillCardProps> = ({ location, bill }) => {
}
<div className="text-right">
<Link className="btn btn-neutral ml-3" href={`/share/location/${locationID}`}>{t("back-button")}</Link>
<Link className="btn btn-neutral ml-3" href={`/share/location/${shareId || locationID}`}>{t("back-button")}</Link>
</div>
</div>