Fix security issues from code review: remove plaintext password comment, enable TLS verification, harden booking ref entropy, fix XSS/injection in signature emails, fix broken waiver link, consolidate duplicated auth/file-serving logic, add missing document view links, sync schema.sql with live DB

This commit is contained in:
2026-07-04 14:01:33 -05:00
parent 72ba4743f9
commit d12baebcbc
8 changed files with 75 additions and 51 deletions
+13 -8
View File
@@ -14,13 +14,6 @@ function _createToken(): string {
db()->exec("DELETE FROM admin_tokens WHERE expires_at < NOW()");
return $token;
}
function _verifyToken(string $token): bool {
if (!preg_match('/^[a-f0-9]{64}$/', $token)) return false;
$stmt = db()->prepare("SELECT token FROM admin_tokens WHERE token=? AND expires_at > NOW()");
$stmt->execute([$token]);
return (bool)$stmt->fetch();
}
$isAjax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) || (($_SERVER['HTTP_ACCEPT'] ?? '') === 'application/json');
// ── Auth ──────────────────────────────────────────────────────────────────────
@@ -38,7 +31,7 @@ if (($_GET['action'] ?? '') === 'logout') {
if ($rawToken) db()->prepare("DELETE FROM admin_tokens WHERE token=?")->execute([$rawToken]);
header('Location: /admin/'); exit;
}
$authed = $rawToken !== '' && _verifyToken($rawToken);
$authed = $rawToken !== '' && verifyAdminToken($rawToken);
$token = $authed ? $rawToken : '';
// ── AJAX handlers ─────────────────────────────────────────────────────────────
@@ -696,6 +689,9 @@ textarea.notes-ta:focus{border-color:#f97316}
onclick="toggleReq(<?= $bid ?>,'insurance_verified',this)">
<?= $stepInsurance?'✓ Marked Received':'Mark Received' ?>
</button>
<?php if (!empty($b['insurance_file'])): ?>
<a class="flow-link" href="/admin/view-doc.php?ref=<?= urlencode($b['booking_ref']) ?>&type=insurance&_t=<?= urlencode($token) ?>" target="_blank">View Document ↗</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
@@ -763,6 +759,9 @@ textarea.notes-ta:focus{border-color:#f97316}
onclick="toggleReq(<?= $bid ?>,'license_verified',this)">
<?= $stepLicense?'✓ License Verified':'Mark License Verified' ?>
</button>
<?php if (!empty($b['license_file'])): ?>
<a class="flow-link" href="/admin/view-doc.php?ref=<?= urlencode($b['booking_ref']) ?>&type=license&_t=<?= urlencode($token) ?>" target="_blank">View Document ↗</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
@@ -1060,6 +1059,9 @@ textarea.notes-ta:focus{border-color:#f97316}
onclick="cvToggleReq(<?= $bid ?>,'insurance_verified',this)">
<?= $stepInsurance?'✓ Marked Received':'Mark Received' ?>
</button>
<?php if (!empty($cb['insurance_file'])): ?>
<a class="flow-link" href="/admin/view-doc.php?ref=<?= urlencode($cb['booking_ref']) ?>&type=insurance&_t=<?= urlencode($token) ?>" target="_blank">View Document ↗</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
@@ -1118,6 +1120,9 @@ textarea.notes-ta:focus{border-color:#f97316}
onclick="cvToggleReq(<?= $bid ?>,'license_verified',this)">
<?= $stepLicense?'✓ License Verified':'Mark License Verified' ?>
</button>
<?php if (!empty($cb['license_file'])): ?>
<a class="flow-link" href="/admin/view-doc.php?ref=<?= urlencode($cb['booking_ref']) ?>&type=license&_t=<?= urlencode($token) ?>" target="_blank">View Document ↗</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
+6 -12
View File
@@ -2,9 +2,7 @@
require_once dirname(__DIR__) . '/db.php';
$token = preg_replace('/[^a-f0-9]/', '', $_GET['_t'] ?? '');
$stmt = db()->prepare("SELECT token FROM admin_tokens WHERE token=? AND expires_at > NOW()");
$stmt->execute([$token]);
if (!$stmt->fetch()) {
if (!verifyAdminToken($token)) {
http_response_code(403);
header('Content-Type: text/plain');
exit('Unauthorized — please log in to the admin panel first.');
@@ -29,25 +27,21 @@ if (!$row || !$row['file_path']) {
exit('No document on file.');
}
$root = dirname(__DIR__);
$base = realpath($root . '/uploads');
$path = realpath($root . '/' . $row['file_path']);
if (!$path || !$base || !str_starts_with($path, $base . DIRECTORY_SEPARATOR)) {
$path = resolveUploadPath(dirname(__DIR__), $row['file_path']);
if (!$path) {
http_response_code(404);
header('Content-Type: text/plain');
exit('File not found.');
}
$mime = mime_content_type($path);
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'application/pdf' => 'pdf'];
if (!isset($allowed[$mime])) {
$mime = detectAllowedMime($path);
if (!$mime) {
http_response_code(403);
header('Content-Type: text/plain');
exit('Invalid file type.');
}
$fname = $type . '-' . $ref . '.' . $allowed[$mime];
$fname = $type . '-' . $ref . '.' . ALLOWED_DOC_MIMES[$mime];
header('Content-Type: ' . $mime);
header('Content-Disposition: inline; filename="' . $fname . '"');
header('Content-Length: ' . filesize($path));