Fixed validation

This commit is contained in:
2025-02-27 12:52:24 -06:00
parent ea905066ed
commit 0781e133e6

View File

@ -30,6 +30,46 @@ const objectsToCSV = (data: RSVPItem[]): string => {
return csvRows.join("\n");
};
// Sanitization helpers
const sanitizeString = (value: unknown): string => {
if (typeof value !== 'string') return '';
// Remove any control characters and normalize whitespace
return value
.replace(/[\x00-\x1F\x7F-\x9F]/g, '') // Remove control characters
.replace(/\s+/g, ' ') // Normalize whitespace
.trim();
};
const sanitizeBoolean = (value: unknown): boolean => {
if (typeof value === 'boolean') return value;
if (typeof value === 'string') {
const lowered = value.toLowerCase().trim();
return !(lowered === 'false' || lowered === '0' || lowered === 'no');
}
return Boolean(value);
};
const sanitizeTimestamp = (value: unknown): string => {
if (typeof value !== 'string') return new Date().toISOString();
try {
const date = new Date(value);
if (isNaN(date.getTime())) throw new Error('Invalid date');
return date.toISOString();
} catch {
return new Date().toISOString();
}
};
const sanitizeRSVP = (data: Record<string, unknown>): RSVPItem => {
return {
name: sanitizeString(data.name),
attending: sanitizeBoolean(data.attending),
dietaryRestrictions: sanitizeString(data.dietaryRestrictions),
notes: sanitizeString(data.notes),
timestamp: sanitizeTimestamp(data.timestamp),
};
};
const csvToObjects = (csv: string): RSVPItem[] => {
const lines = csv.split("\n");
const headers = lines[0].split(",").map((h) => h.trim());
@ -42,6 +82,7 @@ const csvToObjects = (csv: string): RSVPItem[] => {
const values: string[] = [];
let inQuote = false;
let currentValue = '';
let isEscaped = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
@ -51,41 +92,37 @@ const csvToObjects = (csv: string): RSVPItem[] => {
// Handle escaped quotes
currentValue += '"';
i++;
} else {
isEscaped = true;
} else if (!isEscaped || (isEscaped && inQuote)) {
inQuote = !inQuote;
isEscaped = false;
} else {
currentValue += char;
}
} else if (char === ',' && !inQuote) {
values.push(currentValue.trim());
currentValue = '';
isEscaped = false;
} else {
currentValue += char;
}
}
values.push(currentValue.trim());
// Create the entry object
const entry: Partial<RSVPItem> = {};
// Create a data object from the CSV values
const data: Record<string, unknown> = {};
headers.forEach((header, index) => {
let value = values[index] || "";
value = value.replace(/^"(.*)"$/, "$1").replace(/""/g, '"');
if (header === "attending") {
// Explicitly convert to boolean
entry.attending = value.toLowerCase() === "true";
} else {
entry[header as keyof RSVPItem] = value;
if (index < values.length) {
let value = values[index];
value = value.replace(/^"(.*)"$/, "$1").replace(/""/g, '"');
data[header] = value;
}
});
return entry as RSVPItem;
// Sanitize the data
return sanitizeRSVP(data);
})
.filter(entry =>
// Filter out malformed entries
entry.name &&
typeof entry.attending === 'boolean' &&
typeof entry.timestamp === 'string' &&
entry.timestamp.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
);
.filter(entry => entry.name.length > 0); // Only keep entries with non-empty names
};
// GET: Retrieve all RSVPs (requires admin role)
@ -133,7 +170,27 @@ const handlePost: APIRoute = async ({ request }) => {
try {
const FILE_KEY = "rsvp.csv";
const newRsvp = await request.json();
const rawData = await request.json();
// Validate required fields
if (!rawData.name) {
return new Response(
JSON.stringify({
success: false,
error: "Name is required",
}),
{
status: 400,
headers,
}
);
}
// Sanitize the input data
const newRsvp = sanitizeRSVP({
...rawData,
timestamp: rawData.timestamp || new Date().toISOString(),
});
let existingRsvps: RSVPItem[] = [];
const fileContent = await getS3Data<string>(FILE_KEY);
@ -141,13 +198,7 @@ const handlePost: APIRoute = async ({ request }) => {
existingRsvps = csvToObjects(fileContent);
}
existingRsvps.push({
...newRsvp,
attending: Boolean(newRsvp.attending),
notes: newRsvp.notes || "",
dietaryRestrictions: newRsvp.dietaryRestrictions || "",
timestamp: newRsvp.timestamp || new Date().toISOString(),
});
existingRsvps.push(newRsvp);
const csvContent = objectsToCSV(existingRsvps);
await putS3Data(FILE_KEY, csvContent, "text/csv");