feat: ochrona przed podwójnym składaniem zamówienia (order submit token)
Token CSRF w sesji zapobiega duplikowaniu zamówień przy wielokrotnym kliknięciu przycisku. Przy duplikacie przekierowanie do istniejącego zamówienia. JS naprawiony — nasłuch na submit formularza zamiast click. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,9 @@ namespace front\Controllers;
|
||||
|
||||
class ShopBasketController
|
||||
{
|
||||
private const ORDER_SUBMIT_TOKEN_SESSION_KEY = 'order-submit-token';
|
||||
private const ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY = 'order-submit-last-order-id';
|
||||
|
||||
public static $title = [
|
||||
'mainView' => 'Koszyk'
|
||||
];
|
||||
@@ -274,6 +277,7 @@ class ShopBasketController
|
||||
}
|
||||
|
||||
$client = \Shared\Helpers\Helpers::get_session( 'client' );
|
||||
$orderSubmitToken = $this->createOrderSubmitToken();
|
||||
|
||||
return \Shared\Tpl\Tpl::view( 'shop-basket/summary-view', [
|
||||
'lang_id' => $lang_id,
|
||||
@@ -284,12 +288,35 @@ class ShopBasketController
|
||||
'addresses' => ( new \Domain\Client\ClientRepository( $GLOBALS['mdb'] ) )->clientAddresses( (int)$client['id'] ),
|
||||
'settings' => $settings,
|
||||
'coupon' => \Shared\Helpers\Helpers::get_session( 'coupon' ),
|
||||
'basket_message' => \Shared\Helpers\Helpers::get_session( 'basket_message' )
|
||||
'basket_message' => \Shared\Helpers\Helpers::get_session( 'basket_message' ),
|
||||
'order_submit_token' => $orderSubmitToken
|
||||
] );
|
||||
}
|
||||
|
||||
public function basketSave()
|
||||
{
|
||||
$orderSubmitToken = (string)\Shared\Helpers\Helpers::get( 'order_submit_token', true );
|
||||
$existingOrderId = isset( $_SESSION[ self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY ] ) ? (int)$_SESSION[ self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY ] : 0;
|
||||
|
||||
if ( !$this->isValidOrderSubmitToken( $orderSubmitToken ) )
|
||||
{
|
||||
if ( $existingOrderId > 0 )
|
||||
{
|
||||
$existingOrderHash = $this->orderRepository->findHashById( $existingOrderId );
|
||||
if ( $existingOrderHash )
|
||||
{
|
||||
header( 'Location: /zamowienie/' . $existingOrderHash );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
\Shared\Helpers\Helpers::error( \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-zlozone-komunikat-blad' ) );
|
||||
header( 'Location: /koszyk' );
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->consumeOrderSubmitToken();
|
||||
|
||||
$client = \Shared\Helpers\Helpers::get_session( 'client' );
|
||||
|
||||
if ( \Domain\Basket\BasketCalculator::checkProductQuantityInStock( \Shared\Helpers\Helpers::get_session( 'basket' ) ) )
|
||||
@@ -322,6 +349,7 @@ class ShopBasketController
|
||||
\Shared\Helpers\Helpers::get_session( 'basket_message' )
|
||||
) )
|
||||
{
|
||||
\Shared\Helpers\Helpers::set_session( self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY, (int)$order_id );
|
||||
\Shared\Helpers\Helpers::alert( \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-zlozone-komunikat' ) );
|
||||
\Shared\Helpers\Helpers::delete_session( 'basket' );
|
||||
\Shared\Helpers\Helpers::delete_session( 'basket-transport-method-id' );
|
||||
@@ -414,4 +442,45 @@ class ShopBasketController
|
||||
] );
|
||||
exit;
|
||||
}
|
||||
|
||||
private function createOrderSubmitToken()
|
||||
{
|
||||
$token = $this->generateOrderSubmitToken();
|
||||
\Shared\Helpers\Helpers::set_session( self::ORDER_SUBMIT_TOKEN_SESSION_KEY, $token );
|
||||
\Shared\Helpers\Helpers::delete_session( self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY );
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
private function generateOrderSubmitToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
return bin2hex( random_bytes( 16 ) );
|
||||
}
|
||||
catch ( \Exception $exception )
|
||||
{
|
||||
return md5( uniqid( (string)mt_rand(), true ) );
|
||||
}
|
||||
}
|
||||
|
||||
private function isValidOrderSubmitToken( $token )
|
||||
{
|
||||
if ( !$token )
|
||||
return false;
|
||||
|
||||
$sessionToken = isset( $_SESSION[ self::ORDER_SUBMIT_TOKEN_SESSION_KEY ] ) ? (string)$_SESSION[ self::ORDER_SUBMIT_TOKEN_SESSION_KEY ] : '';
|
||||
if ( !$sessionToken )
|
||||
return false;
|
||||
|
||||
if ( function_exists( 'hash_equals' ) )
|
||||
return hash_equals( $sessionToken, $token );
|
||||
|
||||
return $sessionToken === $token;
|
||||
}
|
||||
|
||||
private function consumeOrderSubmitToken()
|
||||
{
|
||||
\Shared\Helpers\Helpers::delete_session( self::ORDER_SUBMIT_TOKEN_SESSION_KEY );
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user