fix: feedback token race condition (FOR UPDATE), generic error for public users
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 6s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 2s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 7s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 16s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled

This commit is contained in:
Jonathan Miller
2026-06-20 20:14:00 -05:00
parent f33012cb90
commit 3cd36b94bf
@@ -51,16 +51,7 @@ class CustomerFeedbackHelper
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('id, wo_id, contact_id, status')
->from('#__mokosuitefield_feedback_requests')
->where($db->quoteName('token') . ' = ' . $db->quote($token)));
$request = $db->loadObject();
if (!$request) return (object) ['success' => false, 'error' => 'Invalid feedback link'];
if ($request->status === 'completed') return (object) ['success' => false, 'error' => 'Feedback already submitted'];
// Validate inputs
// Validate inputs before DB access
$rating = max(1, min(5, $rating));
$npsScore = max(0, min(10, $npsScore));
@@ -69,7 +60,20 @@ class CustomerFeedbackHelper
$db->transactionStart();
try {
// Record feedback
// Lock the request row to prevent race condition on token reuse
$db->setQuery('SELECT id, wo_id, contact_id, status FROM #__mokosuitefield_feedback_requests WHERE '
. $db->quoteName('token') . ' = ' . $db->quote($token) . ' FOR UPDATE');
$request = $db->loadObject();
if (!$request) {
$db->transactionRollback();
return (object) ['success' => false, 'error' => 'Invalid feedback link'];
}
if ($request->status === 'completed') {
$db->transactionRollback();
return (object) ['success' => false, 'error' => 'Feedback already submitted'];
}
$feedback = (object) [
'request_id' => $request->id,
'wo_id' => $request->wo_id,
@@ -81,7 +85,6 @@ class CustomerFeedbackHelper
];
$db->insertObject('#__mokosuitefield_feedback', $feedback);
// Mark request as completed
$db->setQuery($db->getQuery(true)
->update('#__mokosuitefield_feedback_requests')
->set($db->quoteName('status') . ' = ' . $db->quote('completed'))
@@ -91,7 +94,8 @@ class CustomerFeedbackHelper
$db->transactionCommit();
} catch (\Throwable $e) {
$db->transactionRollback();
return (object) ['success' => false, 'error' => $e->getMessage()];
// Don't leak internal errors to public users
return (object) ['success' => false, 'error' => 'Unable to save feedback. Please try again.'];
}
return (object) ['success' => true, 'rating' => $rating, 'nps' => $npsScore];