From 3cd36b94bfa86a36e63fb9a811d930f30a27f877 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 20:14:00 -0500 Subject: [PATCH] fix: feedback token race condition (FOR UPDATE), generic error for public users --- .../src/Helper/CustomerFeedbackHelper.php | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/source/packages/plg_system_mokosuitefield/src/Helper/CustomerFeedbackHelper.php b/source/packages/plg_system_mokosuitefield/src/Helper/CustomerFeedbackHelper.php index 3536ba0..21df3a5 100644 --- a/source/packages/plg_system_mokosuitefield/src/Helper/CustomerFeedbackHelper.php +++ b/source/packages/plg_system_mokosuitefield/src/Helper/CustomerFeedbackHelper.php @@ -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];