Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/Modules/OAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Sendy\WooCommerce\Modules;

use GuzzleHttp\Exception\GuzzleException;
use Sendy\Api\Exceptions\SendyException;
use Sendy\WooCommerce\ApiClientFactory;

class OAuth
Expand Down Expand Up @@ -52,6 +52,8 @@ public function reset_credentials_when_access_token_nullified($old_value, $value

update_option('sendy_refresh_token', null, false);
update_option('sendy_token_expires', null, false);

delete_option('sendy_webhook_secret');
}
}

Expand All @@ -76,10 +78,12 @@ public function oauth_callback(): void
try {
ApiClientFactory::buildConnectionUsingCode(sanitize_key($_GET['code']))->checkOrAcquireAccessToken();

Webhooks::regenerateWebhookSecret();

sendy_flash_admin_notice('success', __('Authentication successful', 'sendy'));

wp_safe_redirect(admin_url('admin.php?page=sendy'));
} catch (GuzzleException $e) {
} catch (SendyException $e) {
sendy_flash_admin_notice('warning', __('Authentication failed. Please try again', 'sendy'));

wp_safe_redirect(admin_url('admin.php?page=sendy'));
Expand Down
2 changes: 0 additions & 2 deletions lib/Modules/Orders/OrdersModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ protected function create_shipment_with_smart_rules(\WC_Order $order, bool $exec

/**
* Fetch the labels from the Sendy API and offer them as download to the user
*
* @throws \GuzzleHttp\Exception\GuzzleException
*/
protected function offer_labels_as_download(array $shipment_ids): void
{
Expand Down
49 changes: 49 additions & 0 deletions lib/Modules/Webhooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Sendy\WooCommerce\Modules;

use Sendy\Api\ApiException;
use Sendy\Api\Exceptions\SendyException;
use Sendy\WooCommerce\ApiClientFactory;
use Sendy\WooCommerce\Enums\ProcessingMethod;
use WC_Order;
Expand Down Expand Up @@ -53,6 +54,12 @@ public function init_rest_api_endpoint(): void

public function webhook_callback(\WP_REST_Request $request)
{
$verificationError = $this->verifySignature($request);

if ($verificationError) {
return $verificationError;
}

$payload = $request->get_json_params() ?? [];

if (! array_key_exists('data', $payload)) {
Expand Down Expand Up @@ -107,13 +114,55 @@ public function ensure_webhook_installed(): void
}
}

/**
* @throws SendyException
*/
public static function regenerateWebhookSecret(): void
{
$clientId = get_option('sendy_client_id');
$response = ApiClientFactory::buildConnectionUsingTokens()
->post("/regenerate-webhook-secret/{$clientId}");

update_option('sendy_webhook_secret', $response['webhook_secret'], false);
}

public function deactivate(): void
{
$this->deleteWebhook();

delete_option('sendy_webhook_last_checked');
}

private function verifySignature(\WP_REST_Request $request): ?\WP_REST_Response
{
$signature = $request->get_header('X-Signature');
$timestamp = $request->get_header('X-Timestamp');

if (! $signature || ! $timestamp) {
return new \WP_REST_Response(['error' => 'Missing signature headers'], 401);
}

$secret = get_option('sendy_webhook_secret');

if (! $secret) {
try {
self::regenerateWebhookSecret();
} catch (\Exception $e) {
return new \WP_REST_Response(['error' => 'Failed to regenerate webhook secret'], 500);
}

return new \WP_REST_Response(['error' => 'Webhook secret not configured'], 401);
}

$expected = hash_hmac('sha256', $timestamp . $request->get_body(), $secret);

if (! hash_equals($expected, $signature)) {
return new \WP_REST_Response(['error' => 'Invalid signature'], 401);
}

return null;
}

/**
* Delete the webhook in the API
*/
Expand Down
2 changes: 1 addition & 1 deletion lib/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

class Plugin
{
public const VERSION = '3.4.1';
public const VERSION = '3.4.2';

public const SETTINGS_ID = 'sendy';

Expand Down
7 changes: 4 additions & 3 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
Plugin Name: Sendy
Plugin URI: https://app.sendy.nl/
Description: A WooCommerce plugin that connects your site to the Sendy platform
Version: 3.4.1
Stable tag: 3.4.1
Version: 3.4.2
Stable tag: 3.4.2
License: MIT
Author: Sendy
Author URI: https://sendy.nl/
Expand Down Expand Up @@ -52,8 +52,9 @@ Hierop zijn onze [algemene voorwaarden](https://sendy.nl/algemene-voorwaarden/)

== Changelog ==

= Unreleased =
= 3.4.2 =
* Improve error handling on order pages
* Fix CVE-2025-68564 - Verify webhook requests using the signature

= 3.4.1 =
* Fix an error handling issue when creating shipments
Expand Down
2 changes: 1 addition & 1 deletion sendy.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Plugin Name: Sendy
* Plugin URI: https://app.sendy.nl/
* Description: A WooCommerce plugin that connects your site to the Sendy platform
* Version: 3.4.1
* Version: 3.4.2
* Author: Sendy
* Author URI: https://sendy.nl/
* License: MIT
Expand Down