Rescheduling endpoints often receive less scrutiny than creation endpoints, creating an asymmetry where PATCH handlers skip the availability and capacity checks that the POST handler enforces. OWASP A03 (Injection) and CWE-20 both apply when untrusted input reaches a database write without validation. A user who reschedules to a fully-booked or already-taken slot bypasses every guard that protected the original booking — producing a double-booking through the update path instead of the creation path. The fix is a shared validation function, not duplicated logic, so both paths stay in sync as the system evolves.
High because unvalidated reschedule updates produce the same double-booking outcome as unvalidated creation, with no difference in business impact.
Extract availability and capacity checks into a reusable function in src/lib/booking-service.ts and call it from both the create and update handlers.
// shared — called by both POST and PATCH handlers
async function assertSlotAvailable(slotId: string, excludeBookingId?: string) {
const conflict = await db.booking.findFirst({
where: {
slotId,
status: { not: 'CANCELLED' },
...(excludeBookingId ? { NOT: { id: excludeBookingId } } : {}),
}
});
if (conflict) throw new ConflictError('Slot unavailable');
}
// In PATCH /api/bookings/:id
await assertSlotAvailable(newSlotId, bookingId);
await db.booking.update({ where: { id: bookingId }, data: { slotId: newSlotId } });
ID: booking-flow-lifecycle.lifecycle.modification-validation
Label: Modification validation matches creation
Severity: high
What to look for: Locate the "Reschedule" or "Edit" endpoint (e.g., PATCH /api/bookings/:id, updateBooking server action). Count all validation checks in the create handler and verify each one also exists in the update handler. Before evaluating, extract and quote the availability check from the create handler and the equivalent check (or lack thereof) from the update handler. Enumerate: "Create handler has X validation checks; update handler has Y of X."
Pass criteria: Rescheduling validates the new slot's availability, checks capacity, and locks it using the same logic as creation. A shared utility function must be reused (e.g., checkAvailability() called from both handlers). At least 2 validation checks from creation (availability + capacity or availability + contact validation) must also appear in modification. Report: "Y of X creation validations are reused in modification handler. Shared function: [name] in [file]."
Fail criteria: Rescheduling simply updates the startTime field without checking if the new time is free or available. Do NOT pass when the update handler has its own separate availability check that differs from the create handler's logic — they must share the same function or identical query.
Skip (N/A) when: Modifications/rescheduling are explicitly not allowed (no update/patch endpoint or server action for bookings exists).
Detail on fail: "The PATCH /api/bookings/:id endpoint allows changing date/time without checking availability of the new slot."
Cross-reference: The conflict-check in Booking Creation defines the availability check that must be reused here.
Cross-reference: The rescheduling-logic check in this category verifies the atomicity of the reschedule operation.
Cross-reference: The slot-availability-recheck check in Conflict Prevention verifies the recheck happens inside a transaction.
Remediation: Extract availability checks into a reusable function in your service layer (e.g., src/lib/booking-service.ts).
async function checkAvailability(slotId: string, startTime: Date) {
const conflict = await db.booking.findFirst({
where: { slotId, startTime, status: { not: 'CANCELLED' } }
});
if (conflict) throw new Error("Slot unavailable");
}
// In create handler
await checkAvailability(slotId, startTime);
await db.booking.create({...});
// In update handler
await checkAvailability(newSlotId, newStartTime);
await db.booking.update({...});