CRITICAL: block direct access to customer license/insurance uploads (was fully downloadable with no auth); fix double-booking race condition and unstable deposit-hold idempotency key

This commit is contained in:
2026-07-06 07:53:11 +00:00
parent 3bf7fe7620
commit 671df18039
3 changed files with 51 additions and 30 deletions
+7
View File
@@ -12,3 +12,10 @@ RewriteRule .* - [F,L]
# issue found and fixed on sibling sites this session.
RewriteCond %{REQUEST_URI} ^/\.git
RewriteRule .* - [F,L]
# uploads/ (customer license/insurance docs) must never be directly downloadable -
# same Order/Deny-doesn't-work-on-OpenLiteSpeed gotcha as .git above. Docs are
# served only through view-doc.php / admin/view-doc.php, which read by filesystem
# path via readfile() - blocking direct URL access here doesn't break that flow.
RewriteCond %{REQUEST_URI} ^/uploads/
RewriteRule .* - [F,L]
+19 -2
View File
@@ -39,7 +39,18 @@ $pkg = PACKAGES[$package];
$rentalDate = $date;
$endDate = date('Y-m-d', strtotime($date . ' +' . $pkg['days'] . ' days'));
// Check availability
// Check availability + create booking atomically. A named advisory lock (scoped to the
// requested date range) prevents two concurrent submissions from both passing the
// conflict check before either has inserted, which would otherwise double-book the
// vehicle for overlapping dates.
$lockName = 'psr_booking_' . $rentalDate . '_' . $endDate;
$gotLock = (int) db()->query("SELECT GET_LOCK('" . addslashes($lockName) . "', 5)")->fetchColumn();
if (!$gotLock) {
http_response_code(503);
echo json_encode(['success'=>false,'error'=>'Please try again in a moment.']); exit;
}
try {
$conflict = db()->prepare(
"SELECT id FROM bookings
WHERE status IN ('pending','confirmed')
@@ -62,6 +73,9 @@ $stmt = db()->prepare(
VALUES (?,?,?,?,?,?,?,?,?)"
);
$stmt->execute([$ref, $name, $email, $phone, $package, $rentalDate, $endDate, $pkg['amount'], $message]);
} finally {
db()->query("SELECT RELEASE_LOCK('" . addslashes($lockName) . "')");
}
$dateLabel = date('F j, Y', strtotime($rentalDate));
$pkgLabel = $pkg['label'];
@@ -127,7 +141,10 @@ $sqId = null;
if ($squareToken) {
$sqResp = squareApi('POST', '/payments', [
'source_id' => $squareToken,
'idempotency_key' => $ref . '-dep-' . time(),
// Stable per booking ref (not time()-suffixed) so a network retry or
// accidental double-submit of this same deposit hold is recognized by
// Square as a duplicate instead of placing a second hold on the card.
'idempotency_key' => $ref . '-dep',
'amount_money' => ['amount' => (int)(DEPOSIT_AMOUNT * 100), 'currency' => 'USD'],
'autocomplete' => false, // hold only — capture when confirmed
'location_id' => SQUARE_LOCATION_ID,
+5 -8
View File
@@ -1,8 +1,5 @@
# Block direct web access documents served only through admin/view-doc.php
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
# Block direct web access - documents served only through view-doc.php / admin/view-doc.php.
# Require all denied / Order deny,allow do NOT work on this OpenLiteSpeed setup -
# only RewriteRule [F,L] actually blocks requests here (confirmed this session).
RewriteEngine On
RewriteRule .* - [F,L]