From c147b57f41dd5b0055c2cae7233066085e1c9b5e Mon Sep 17 00:00:00 2001 From: Daniel Neto Date: Mon, 28 Jul 2025 12:16:56 -0300 Subject: [PATCH] Refactor YPTWallet configuration: remove unused checks and add donation notification URL handling https://github.com/WWBN/AVideo/issues/10138 --- plugin/YPTSocket/YPTSocket.php | 3 - plugin/YPTWallet/YPTWallet.php | 156 ++++++- .../YPTWallet/getWalletConfigurationHTML.php | 395 ++++++++++++++++-- plugin/YPTWallet/view/saveConfiguration.php | 77 ++-- 4 files changed, 548 insertions(+), 83 deletions(-) diff --git a/plugin/YPTSocket/YPTSocket.php b/plugin/YPTSocket/YPTSocket.php index aee953114d..102608d587 100644 --- a/plugin/YPTSocket/YPTSocket.php +++ b/plugin/YPTSocket/YPTSocket.php @@ -75,9 +75,6 @@ class YPTSocket extends PluginAbstract 'debugAllUsersSocket', 'allow_self_signed', 'forceNonSecure', - 'showTotalOnlineUsersPerVideo', - 'showTotalOnlineUsersPerLive', - 'showTotalOnlineUsersPerLiveLink', ); } diff --git a/plugin/YPTWallet/YPTWallet.php b/plugin/YPTWallet/YPTWallet.php index 2ce64a4605..cc8d5cab4f 100644 --- a/plugin/YPTWallet/YPTWallet.php +++ b/plugin/YPTWallet/YPTWallet.php @@ -919,17 +919,6 @@ class YPTWallet extends PluginAbstract public function getWalletConfigurationHTML($users_id, $wallet, $walletDataObject) { global $global; - if (empty($walletDataObject->CryptoWalletEnabled)) { - if (User::isAdmin()) { - YPTWallet::showAdminMessage(); - echo ''; - } - return ''; - } include_once $global['systemRootPath'] . 'plugin/YPTWallet/getWalletConfigurationHTML.php'; } @@ -985,4 +974,149 @@ class YPTWallet extends PluginAbstract } return false; } + + static function setDonationNotificationURL($users_id, $url) + { + // Sanitize the URL string for safe database storage + $url = trim($url); + + // Remove any null bytes and control characters that could cause issues + $url = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $url); + + // HTML encode any special characters to prevent XSS when displayed + $url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8'); + + // Limit length to prevent database issues + if (strlen($url) > 2048) { + _error_log("URL too long. Maximum 2048 characters allowed"); + return false; + } + + $user = new User($users_id); + return $user->addExternalOptions('donation_notification_url', $url); + } + + static function getDonationNotificationURL($users_id) + { + $user = new User($users_id); + return $user->getExternalOptions('donation_notification_url'); + } + + + public function afterDonation($from_users_id, $how_much, $videos_id, $users_id, $extraParameters) + { + $donation_notification_url = self::getDonationNotificationURL($users_id); + $webhookSecret = self::getDonationNotificationSecret($users_id); // Get user's secret + + $obj = AVideoPlugin::getObjectData('YPTWallet'); + + $data = array( + 'from_users_id' => $from_users_id, + 'from_users_name' => User::getNameIdentificationById($from_users_id), + 'currency' => $obj->currency, + 'how_much_human' => YPTWallet::formatCurrency($how_much), + 'how_much' => $how_much, + 'message' => $extraParameters['message'] ?? '', + 'videos_id' => $videos_id, + 'users_id' => $users_id, + 'time' => time(), + 'extraParameters' => $extraParameters + ); + + if (!empty($donation_notification_url) && isValidURL($donation_notification_url)) { + _error_log("Sending donation notification via POST to URL: {$donation_notification_url} for user ID: {$users_id}"); + + // Create POST data string + $postData = http_build_query($data); + + // Generate signature using user's webhook secret + $signature = hash_hmac('sha256', $postData, $webhookSecret); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $donation_notification_url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, false); + curl_setopt($ch, CURLOPT_TIMEOUT, 1); + curl_setopt($ch, CURLOPT_NOSIGNAL, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_USERAGENT, getSelfUserAgent()); + + // Add signature to headers + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/x-www-form-urlencoded', + 'X-Webhook-Signature: sha256=' . $signature, + 'X-Webhook-Timestamp: ' . $data['time'] + )); + + // Silent execution + ob_start(); + curl_exec($ch); + ob_end_clean(); + curl_close($ch); + } else { + _error_log("Donation notification URL is not set or invalid for user ID: {$users_id} " . json_encode($data)); + } + } + + /** + * Generate a cryptographically secure random string. + * @param int $length + * @return string + */ + private static function generateRandomString($length = 32) + { + if (function_exists('random_bytes')) { + return bin2hex(random_bytes($length / 2)); + } elseif (function_exists('openssl_random_pseudo_bytes')) { + return bin2hex(openssl_random_pseudo_bytes($length / 2)); + } else { + // fallback (not cryptographically secure) + return substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', $length)), 0, $length); + } + } + + static function setDonationNotificationSecret($users_id, $secret = null) + { + // If no secret provided, generate a new one + if (empty($secret)) { + $secret = self::generateRandomString(32); + } + + // Sanitize the secret + $secret = trim($secret); + + // Limit length for database safety + if (strlen($secret) > 255) { + _error_log("Webhook secret too long. Maximum 255 characters allowed"); + return false; + } + + // Remove any dangerous characters + $secret = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $secret); + + $user = new User($users_id); + return $user->addExternalOptions('donation_notification_secret', $secret); + } + + static function getDonationNotificationSecret($users_id) + { + $user = new User($users_id); + $secret = $user->getExternalOptions('donation_notification_secret'); + + // If no secret exists, generate one + if (empty($secret)) { + $secret = self::generateRandomString(32); + self::setDonationNotificationSecret($users_id, $secret); + } + + return $secret; + } + + static function regenerateDonationNotificationSecret($users_id) + { + $newSecret = self::generateRandomString(32); + return self::setDonationNotificationSecret($users_id, $newSecret); + } } diff --git a/plugin/YPTWallet/getWalletConfigurationHTML.php b/plugin/YPTWallet/getWalletConfigurationHTML.php index 3e4c05fa88..20c7d8805c 100644 --- a/plugin/YPTWallet/getWalletConfigurationHTML.php +++ b/plugin/YPTWallet/getWalletConfigurationHTML.php @@ -1,37 +1,358 @@ - -
-
-
-
-
- - -
- -
-
-
- \ No newline at end of file + +
+
+
+
+
+ + +
+
+ + + +
+
+
+ + + + + +
+ +
+ + +
+
+
+

+ +

+
+
+

+

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
X-Webhook-Signaturesha256=abc123...
X-Webhook-Timestamp
Content-Typeapplication/x-www-form-urlencoded
User-AgentAVideoStreamer_*
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
from_users_id1
from_users_nameJohn Doe
currencyUSD
how_much21
how_much_human$21.00
messageGreat content
videos_id0
users_id1
time
extraParameters[superChat]1
extraParameters[message]Great content
extraParameters[live_transmitions_history_id]32
+
+ +
+ + + +
+ +
+
+
 300) { // 5 minutes tolerance
+    http_response_code(400);
+    exit(\'Request expired\');
+}
+
+// Step 4: Calculate expected signature
+$expectedSignature = \'sha256=\' . hash_hmac(\'sha256\', $rawPostData, $webhookSecret);
+
+// Step 5: Verify signature using timing-safe comparison
+if (!hash_equals($signature, $expectedSignature)) {
+    http_response_code(401);
+    exit(\'Invalid signature\');
+}
+
+// Step 6: Signature is valid, process the webhook data
+parse_str($rawPostData, $donationData);
+
+// Now you can safely use the donation data
+$donorId = $donationData[\'from_users_id\'];
+$donorName = $donationData[\'from_users_name\'];
+$amount = $donationData[\'how_much\'];
+$formattedAmount = $donationData[\'how_much_human\'];
+$message = $donationData[\'message\'];
+$videoId = $donationData[\'videos_id\'];
+$receiverId = $donationData[\'users_id\'];
+
+// Your processing logic here...
+// Example: Save to database, send notifications, etc.
+
+// Always respond with 200 OK
+http_response_code(200);
+echo \'Webhook processed successfully\';
+?>'); ?>
+
+ +
+
+
 {
+    const signature = req.headers[\'x-webhook-signature\'];
+    const timestamp = req.headers[\'x-webhook-timestamp\'];
+    const rawBody = req.body;
+
+    // Your webhook secret
+    const webhookSecret = \'YOUR_WEBHOOK_SECRET_FROM_ABOVE\';
+
+    // Verify timestamp
+    const currentTime = Math.floor(Date.now() / 1000);
+    const timeDifference = currentTime - parseInt(timestamp);
+    if (timeDifference > 300) {
+        return res.status(400).send(\'Request expired\');
+    }
+
+    // Calculate expected signature
+    const expectedSignature = \'sha256=\' + crypto
+        .createHmac(\'sha256\', webhookSecret)
+        .update(rawBody)
+        .digest(\'hex\');
+
+    // Verify signature
+    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
+        return res.status(401).send(\'Invalid signature\');
+    }
+
+    // Parse form data
+    const params = new URLSearchParams(rawBody.toString());
+    const donationData = Object.fromEntries(params);
+
+    // Process webhook data
+    console.log(\'Donation received:\', donationData);
+
+    res.status(200).send(\'OK\');
+});'); ?>
+
+ +
+ + +
    +
  • +
  • +
  • +
  • +
  • +
  • +
+
+ +
+ + + +
+ +
+ + + +
+
+
+
+
+ +
+
+
+ diff --git a/plugin/YPTWallet/view/saveConfiguration.php b/plugin/YPTWallet/view/saveConfiguration.php index 3e004cb8c4..eacec9871c 100644 --- a/plugin/YPTWallet/view/saveConfiguration.php +++ b/plugin/YPTWallet/view/saveConfiguration.php @@ -1,32 +1,45 @@ -error = true; -$obj->msg = ""; -$obj->walletBalance = 0; - -if (!User::isLogged()) { - $obj->msg = ("Is not Loged"); - die(json_encode($obj)); -} -$plugin = AVideoPlugin::loadPluginIfEnabled("YPTWallet"); -if(empty($plugin)){ - $obj->msg = ("Plugin not enabled"); - die(json_encode($obj)); -} -header('Content-Type: application/json'); - -$wallet = new Wallet(0); -$wallet->setUsers_id(User::getId()); -$wallet->setCrypto_wallet_address($_POST['CryptoWallet']); -if($wallet->save()){ - $obj->error = false; -} -$obj->walletBalance = $plugin->getBalanceFormated(User::getId()); - -echo json_encode($obj); \ No newline at end of file +error = true; +$obj->msg = ""; +$obj->walletBalance = 0; + +if (!User::isLogged()) { + $obj->msg = ("Is not Loged"); + die(json_encode($obj)); +} +$plugin = AVideoPlugin::loadPluginIfEnabled("YPTWallet"); +if(empty($plugin)){ + $obj->msg = ("Plugin not enabled"); + die(json_encode($obj)); +} +header('Content-Type: application/json'); + +$wallet = new Wallet(0); +$wallet->setUsers_id(User::getId()); +$wallet->setCrypto_wallet_address($_POST['CryptoWallet']); +if($wallet->save()){ + $obj->error = false; +} + +if(isset($_REQUEST['donation_notification_url'])){ + $obj->donation_notification_url = YPTWallet::setDonationNotificationURL(User::getId(), $_REQUEST['donation_notification_url']); +} + +// Handle webhook secret regeneration +if(isset($_REQUEST['regenerate_webhook_secret']) && $_REQUEST['regenerate_webhook_secret'] == '1'){ + $obj->new_webhook_secret = YPTWallet::regenerateDonationNotificationSecret(User::getId()); +} + +// Always return current webhook secret +$obj->webhook_secret = YPTWallet::getDonationNotificationSecret(User::getId()); + +$obj->walletBalance = $plugin->getBalanceFormated(User::getId()); + +echo json_encode($obj);