From ae186ba9d14fc421ee9b057f9e3108caf22b90bf Mon Sep 17 00:00:00 2001 From: xecdev Date: Sun, 20 Apr 2025 12:11:11 +0430 Subject: [PATCH 1/6] Added paybutton prefix to generic option names --- assets/js/addressValidator.bundle.js | 2 +- includes/class-paybutton-activator.php | 28 ++++++++++++++++++------- includes/class-paybutton-admin.php | 18 ++++++++-------- includes/class-paybutton-public.php | 8 +++---- templates/admin/paybutton-generator.php | 2 +- templates/admin/paywall-settings.php | 20 +++++++++--------- templates/public/sticky-header.php | 2 +- 7 files changed, 47 insertions(+), 33 deletions(-) diff --git a/assets/js/addressValidator.bundle.js b/assets/js/addressValidator.bundle.js index 3e67f71..1a7d060 100644 --- a/assets/js/addressValidator.bundle.js +++ b/assets/js/addressValidator.bundle.js @@ -753,7 +753,7 @@ document.addEventListener('DOMContentLoaded', () => { // Find the wallet address input field by its ID. - const addressInput = document.getElementById('pb_paywall_admin_wallet_address'); + const addressInput = document.getElementById('paybutton_admin_wallet_address'); if (!addressInput) return; // Find or create a span for validation feedback. diff --git a/includes/class-paybutton-activator.php b/includes/class-paybutton-activator.php index abd515a..3d29114 100644 --- a/includes/class-paybutton-activator.php +++ b/includes/class-paybutton-activator.php @@ -23,13 +23,27 @@ public static function activate() { } private static function migrate_old_option() { - $old_value = get_option( 'paybutton_paywall_ecash_address', '' ); - $new_value = get_option( 'pb_paywall_admin_wallet_address', '' ); - - // If old_value is present and new_value is empty, copy old to new and remove old. - if ( ! empty( $old_value ) && empty( $new_value ) ) { - update_option( 'pb_paywall_admin_wallet_address', $old_value ); - delete_option( 'paybutton_paywall_ecash_address' ); + // --- 1. admin wallet address --- + $old_admin_addr = get_option( 'pb_paywall_admin_wallet_address', '' ); + $new_admin_addr = get_option( 'paybutton_admin_wallet_address', '' ); + if ( ! empty( $old_admin_addr ) && empty( $new_admin_addr ) ) { + update_option( 'paybutton_admin_wallet_address', $old_admin_addr ); + delete_option( 'pb_paywall_admin_wallet_address' ); + } + + // --- 2. unlocked‑indicator colours --- + $bg_old = get_option( 'unlocked_indicator_bg_color', '' ); + $bg_new = get_option( 'paybutton_unlocked_indicator_bg_color', '' ); + if ( ! empty( $bg_old ) && empty( $bg_new ) ) { + update_option( 'paybutton_unlocked_indicator_bg_color', $bg_old ); + delete_option( 'unlocked_indicator_bg_color' ); + } + + $txt_old = get_option( 'unlocked_indicator_text_color', '' ); + $txt_new = get_option( 'paybutton_unlocked_indicator_text_color', '' ); + if ( ! empty( $txt_old ) && empty( $txt_new ) ) { + update_option( 'paybutton_unlocked_indicator_text_color', $txt_old ); + delete_option( 'unlocked_indicator_text_color' ); } } diff --git a/includes/class-paybutton-admin.php b/includes/class-paybutton-admin.php index 7f3d8b9..965b750 100644 --- a/includes/class-paybutton-admin.php +++ b/includes/class-paybutton-admin.php @@ -78,7 +78,7 @@ public function handle_save_settings() { current_user_can( 'manage_options' ) ) { $this->save_settings(); - wp_cache_delete( 'pb_paywall_admin_wallet_address', 'options' ); + wp_cache_delete( 'paybutton_admin_wallet_address', 'options' ); wp_redirect( admin_url( 'admin.php?page=paybutton-paywall&settings-updated=true' ) ); exit; } @@ -198,7 +198,7 @@ public function paywall_settings_page() { $args = array( 'settings_saved' => $settings_saved, - 'admin_wallet_address' => get_option( 'pb_paywall_admin_wallet_address', '' ), + 'admin_wallet_address' => get_option( 'paybutton_admin_wallet_address', '' ), 'default_price' => get_option( 'paybutton_paywall_default_price', 5.5 ), 'current_unit' => get_option( 'paybutton_paywall_unit', 'XEC' ), 'btn_text' => get_option( 'paybutton_text', 'Pay to Unlock' ), @@ -230,7 +230,7 @@ public function admin_notice_missing_wallet_address() { return; } - $address = get_option('pb_paywall_admin_wallet_address', ''); + $address = get_option('paybutton_admin_wallet_address', ''); if (empty($address)) { echo '
'; echo '

NOTICE: Please set your wallet address in Paywall Settings. If you don\'t have an address yet, create a wallet using Cashtab, Electrum ABC or Electron Cash.

'; @@ -242,7 +242,7 @@ public function admin_notice_missing_wallet_address() { * Save settings submitted via the Paywall Settings page. */ private function save_settings() { - $address = sanitize_text_field( $_POST['pb_paywall_admin_wallet_address'] ); + $address = sanitize_text_field( $_POST['paybutton_admin_wallet_address'] ); $unit = sanitize_text_field( $_POST['unit'] ); $raw_price = floatval( $_POST['default_price'] ); $button_text = sanitize_text_field( $_POST['paybutton_text'] ); @@ -251,14 +251,14 @@ private function save_settings() { $color_secondary = sanitize_hex_color( $_POST['paybutton_color_secondary'] ); $color_tertiary = sanitize_hex_color( $_POST['paybutton_color_tertiary'] ); $hide_comments = isset( $_POST['paybutton_hide_comments_until_unlocked'] ) ? '1' : '0'; - $unlocked_indicator_bg_color = sanitize_hex_color( $_POST['unlocked_indicator_bg_color'] ); - $unlocked_indicator_text_color = sanitize_hex_color( $_POST['unlocked_indicator_text_color'] ); + $paybutton_unlocked_indicator_bg_color = sanitize_hex_color( $_POST['paybutton_unlocked_indicator_bg_color'] ); + $paybutton_unlocked_indicator_text_color = sanitize_hex_color( $_POST['paybutton_unlocked_indicator_text_color'] ); if ( $unit === 'XEC' && $raw_price < 5.5 ) { $raw_price = 5.5; } - update_option( 'pb_paywall_admin_wallet_address', $address ); + update_option( 'paybutton_admin_wallet_address', $address ); update_option( 'paybutton_paywall_unit', $unit ); update_option( 'paybutton_paywall_default_price', $raw_price ); update_option( 'paybutton_text', $button_text ); @@ -278,8 +278,8 @@ private function save_settings() { // New unlocked content indicator option: update_option( 'paybutton_scroll_to_unlocked', isset( $_POST['paybutton_scroll_to_unlocked'] ) ? '1' : '0' ); // Default to #007bff for background, #ffffff for text - update_option('unlocked_indicator_bg_color', $unlocked_indicator_bg_color ?: '#007bff'); - update_option('unlocked_indicator_text_color', $unlocked_indicator_text_color ?: '#ffffff'); + update_option('paybutton_unlocked_indicator_bg_color', $paybutton_unlocked_indicator_bg_color ?: '#007bff'); + update_option('paybutton_unlocked_indicator_text_color', $paybutton_unlocked_indicator_text_color ?: '#ffffff'); // Save the blacklist if ( isset( $_POST['paybutton_blacklist'] ) ) { diff --git a/includes/class-paybutton-public.php b/includes/class-paybutton-public.php index 8b8f4ed..e089bd8 100644 --- a/includes/class-paybutton-public.php +++ b/includes/class-paybutton-public.php @@ -43,8 +43,8 @@ public function enqueue_public_assets() { wp_enqueue_style( 'paywall-styles', PAYBUTTON_PLUGIN_URL . 'assets/css/paywall-styles.css', array(), '1.0' ); // Read the admin-chosen colors for the unlocked content indicator from options - $indicator_bg_color = get_option('unlocked_indicator_bg_color', '#007bff'); - $indicator_text_color = get_option('unlocked_indicator_text_color', '#ffffff'); + $indicator_bg_color = get_option('paybutton_unlocked_indicator_bg_color', '#007bff'); + $indicator_text_color = get_option('paybutton_unlocked_indicator_text_color', '#ffffff'); // Add inline CSS variables. $custom_css = " @@ -113,7 +113,7 @@ public function enqueue_public_assets() { 'nonce' => wp_create_nonce( 'paybutton_paywall_nonce' ), 'isUserLoggedIn' => ! empty( $_SESSION['pb_paywall_user_wallet_address'] ) ? 1 : 0, 'userAddress' => ! empty( $_SESSION['pb_paywall_user_wallet_address'] ) ? sanitize_text_field( $_SESSION['pb_paywall_user_wallet_address'] ) : '', - 'defaultAddress' => get_option( 'pb_paywall_admin_wallet_address', '' ), + 'defaultAddress' => get_option( 'paybutton_admin_wallet_address', '' ), 'scrollToUnlocked' => get_option( 'paybutton_scroll_to_unlocked', '1' ), ) ); } @@ -189,7 +189,7 @@ public function paybutton_paywall_shortcode( $atts, $content = null ) { $atts = shortcode_atts( array( 'price' => $default_price, - 'address' => get_option( 'pb_paywall_admin_wallet_address', '' ), + 'address' => get_option( 'paybutton_admin_wallet_address', '' ), 'unit' => $default_unit, 'button_text' => $default_text, 'hover_text' => $default_hover, diff --git a/templates/admin/paybutton-generator.php b/templates/admin/paybutton-generator.php index e2f7884..d631ef1 100644 --- a/templates/admin/paybutton-generator.php +++ b/templates/admin/paybutton-generator.php @@ -3,7 +3,7 @@ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly //Get admin's wallet address from paywall settings - $admin_address = get_option( 'pb_paywall_admin_wallet_address', '' ); + $admin_address = get_option( 'paybutton_admin_wallet_address', '' ); ?>
diff --git a/templates/admin/paywall-settings.php b/templates/admin/paywall-settings.php index 567ec9b..7ae4576 100644 --- a/templates/admin/paywall-settings.php +++ b/templates/admin/paywall-settings.php @@ -15,10 +15,10 @@ - +
- +

Enter your wallet address to receive paywall payments.

@@ -82,13 +82,13 @@
- + - +

Controls the background color of the indicator.

@@ -96,13 +96,13 @@
- + - +

Controls the text color of the indicator.

diff --git a/templates/public/sticky-header.php b/templates/public/sticky-header.php index 4aa6383..c6f2c52 100644 --- a/templates/public/sticky-header.php +++ b/templates/public/sticky-header.php @@ -3,7 +3,7 @@ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly // Check if the admin has set a wallet address - $admin_wallet_address = get_option('pb_paywall_admin_wallet_address', ''); + $admin_wallet_address = get_option('paybutton_admin_wallet_address', ''); if ( empty( $admin_wallet_address ) ) { // If no valid address is set, do not display the sticky header. return; From 0359dbfd51d7fad9db9b519deb9132d90c0b7228 Mon Sep 17 00:00:00 2001 From: xecdev Date: Sun, 20 Apr 2025 13:23:25 +0430 Subject: [PATCH 2/6] Escaped variables properly when echo'ed --- includes/class-paybutton-public.php | 8 ++++---- templates/admin/dashboard.php | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/class-paybutton-public.php b/includes/class-paybutton-public.php index e089bd8..3c26a21 100644 --- a/includes/class-paybutton-public.php +++ b/includes/class-paybutton-public.php @@ -59,7 +59,7 @@ public function enqueue_public_assets() { --pb-unlocked-text: {$indicator_text_color}; } "; - wp_add_inline_style( 'paybutton-sticky-header', $custom_css ); + wp_add_inline_style( 'paybutton-sticky-header', esc_attr( $custom_css ) ); // Enqueue the PayButton core script. wp_enqueue_script( @@ -129,12 +129,12 @@ public function paybutton_generator_shortcode( $atts, $content = null ) { $decoded = []; } - // Safely encode the config for a data attribute - $encodedConfig = esc_attr( wp_json_encode( $decoded ) ); + // Encode the config for a data attribute + $encodedConfig = wp_json_encode( $decoded ); ob_start(); ?> -
+
From 39bacee8eaff3634638fb1284ba48deb8262eda1 Mon Sep 17 00:00:00 2001 From: xecdev Date: Sun, 20 Apr 2025 13:40:49 +0430 Subject: [PATCH 3/6] Remove obsolete migration blocks --- paybutton.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/paybutton.php b/paybutton.php index ba79997..763f7c8 100644 --- a/paybutton.php +++ b/paybutton.php @@ -50,26 +50,6 @@ session_start(); } - /** - * Migrate any existing session data from the old user-wallet key - * ("cashtab_ecash_address") to the new key ("pb_paywall_user_wallet_address"). - */ - if ( ! empty( $_SESSION['cashtab_ecash_address'] ) ) { - $_SESSION['pb_paywall_user_wallet_address'] = $_SESSION['cashtab_ecash_address']; - unset( $_SESSION['cashtab_ecash_address'] ); - } - - /** - * Migrate any existing cookie data from the old cookie - * ("cashtab_ecash_address") to the new cookie name - * ("pb_paywall_user_wallet_address") for session usage. - */ - if ( ! empty( $_COOKIE['pb_paywall_user_wallet_address'] ) ) { - $_SESSION['pb_paywall_user_wallet_address'] = sanitize_text_field( $_COOKIE['pb_paywall_user_wallet_address'] ); - } elseif ( ! empty( $_COOKIE['cashtab_ecash_address'] ) ) { - $_SESSION['pb_paywall_user_wallet_address'] = sanitize_text_field( $_COOKIE['cashtab_ecash_address'] ); - } - // Initialize admin functionality if in admin area. if ( is_admin() ) { new PayButton_Admin(); From 9807a2eda0e9dbbe9e70a59edaa6df428faaeaa1 Mon Sep 17 00:00:00 2001 From: xecdev Date: Sun, 20 Apr 2025 14:07:49 +0430 Subject: [PATCH 4/6] Data sanitaization --- includes/class-paybutton-ajax.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/class-paybutton-ajax.php b/includes/class-paybutton-ajax.php index 1682dee..a587d05 100644 --- a/includes/class-paybutton-ajax.php +++ b/includes/class-paybutton-ajax.php @@ -51,7 +51,7 @@ public function payment_trigger() { * A wp_nonce cannot be used here (no WP session). * We instead verify a cryptographic Ed25519 signature, which guarantees authenticity. */ - // Read the raw request body + // Read the raw request body (sanitized later when storing the data) $raw_post_data = file_get_contents('php://input'); // Decode JSON data @@ -82,11 +82,11 @@ public function payment_trigger() { // Extract post_id from 'post_id' -> 'rawMessage' $post_id = isset($json_data['post_id']['rawMessage']) ? intval($json_data['post_id']['rawMessage']) : 0; - // Extract transaction details - $tx_hash = $json_data['tx_hash'] ?? ''; - $tx_amount = $json_data['tx_amount'] ?? ''; - $tx_timestamp = $json_data['tx_timestamp'] ?? ''; - $user_address = $json_data['user_address'][0] ?? ''; + // Extract and sanitize transaction details + $tx_hash = isset($json_data['tx_hash']) ? sanitize_text_field($json_data['tx_hash']) : ''; + $tx_amount = isset($json_data['tx_amount']) ? sanitize_text_field($json_data['tx_amount']) : ''; + $tx_timestamp = isset($json_data['tx_timestamp']) ? intval($json_data['tx_timestamp']) : 0; + $user_address = isset($json_data['user_address'][0]) ? sanitize_text_field($json_data['user_address'][0]) : ''; // Convert timestamp to MySQL datetime $mysql_timestamp = is_numeric($tx_timestamp) ? gmdate('Y-m-d H:i:s', intval($tx_timestamp)) : '0000-00-00 00:00:00'; From 132e88ffacb142ebb4b1a66ad77943488ccb79fd Mon Sep 17 00:00:00 2001 From: xecdev Date: Sun, 20 Apr 2025 14:17:17 +0430 Subject: [PATCH 5/6] Eliminate redundancy --- includes/class-paybutton-ajax.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-paybutton-ajax.php b/includes/class-paybutton-ajax.php index a587d05..b69df8d 100644 --- a/includes/class-paybutton-ajax.php +++ b/includes/class-paybutton-ajax.php @@ -89,7 +89,7 @@ public function payment_trigger() { $user_address = isset($json_data['user_address'][0]) ? sanitize_text_field($json_data['user_address'][0]) : ''; // Convert timestamp to MySQL datetime - $mysql_timestamp = is_numeric($tx_timestamp) ? gmdate('Y-m-d H:i:s', intval($tx_timestamp)) : '0000-00-00 00:00:00'; + $mysql_timestamp = $tx_timestamp ? gmdate('Y-m-d H:i:s', $tx_timestamp) : '0000-00-00 00:00:00'; if ($post_id > 0 && !empty($user_address)) { $is_logged_in = 0; From d299eb0e6dbe93db3806befbd92fdd1462484b7a Mon Sep 17 00:00:00 2001 From: xecdev Date: Sun, 20 Apr 2025 16:58:00 +0430 Subject: [PATCH 6/6] Harden payment_trigger(): cap JSON body size and whitelist expected fields --- includes/class-paybutton-ajax.php | 71 +++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/includes/class-paybutton-ajax.php b/includes/class-paybutton-ajax.php index b69df8d..8a7a8cb 100644 --- a/includes/class-paybutton-ajax.php +++ b/includes/class-paybutton-ajax.php @@ -46,18 +46,49 @@ public function __construct() { * It validates the request using a cryptographic signature to ensure authenticity. */ public function payment_trigger() { - /* Note to reviewers: - * This endpoint is called by PayButton.org’s server. - * A wp_nonce cannot be used here (no WP session). - * We instead verify a cryptographic Ed25519 signature, which guarantees authenticity. + /* Note to reviewers: + * A wp_nonce cannot be used here (no WP session). + * We instead verify a cryptographic Ed25519 signature, which guarantees authenticity. + * The PayButton server POSTS JSON (`Content‑Type: application/json`), so `$_POST` + * is empty and we must read php://input and sanitize data. + * Reading the raw body – capped at 4 KB (DoS protection) so we never process an + * arbitrary‑size payload */ - // Read the raw request body (sanitized later when storing the data) - $raw_post_data = file_get_contents('php://input'); + $max_bytes = 4096; // 4 KB is more than enough + $raw_post_data = file_get_contents( + 'php://input', + false, + null, + 0, + $max_bytes + 1 // read one extra byte → oversize flag + ); - // Decode JSON data - $json_data = json_decode($raw_post_data, true); - if (!$json_data || !isset($json_data['signature']['signature']) || !isset($json_data['signature']['payload'])) { - wp_send_json_error(['message' => 'Invalid JSON format or missing signature.']); + if ( strlen( $raw_post_data ) > $max_bytes ) { + wp_send_json_error( array( 'message' => 'Payload too large.' ), 413 ); + return; + } + + //Decode JSON and copy ONLY the fields we actually use + + $json = json_decode( $raw_post_data, true ); + + if ( ! is_array( $json ) ) { + wp_send_json_error( array( 'message' => 'Malformed JSON.' ), 400 ); + return; + } + + $signature = $json['signature']['signature'] ?? ''; + $payload = $json['signature']['payload'] ?? ''; // This is the signed data + $post_id_raw = $json['post_id']['rawMessage'] ?? 0; + $tx_hash_raw = $json['tx_hash'] ?? ''; + $tx_amount_raw = $json['tx_amount'] ?? ''; + $ts_raw = $json['tx_timestamp'] ?? 0; + $user_addr_raw = $json['user_address'][0] ?? ''; + + unset( $json ); // discard the rest immediately + + if ( empty( $signature ) || empty( $payload ) || empty( $post_id_raw ) || empty( $user_addr_raw ) ) { + wp_send_json_error( array( 'message' => 'Required fields missing.' ), 400 ); return; } @@ -68,10 +99,6 @@ public function payment_trigger() { return; } - // Extract signature and payload from nested JSON - $signature = $json_data['signature']['signature']; - $payload = $json_data['signature']['payload']; // This is the signed data - // Verify the signature $verification_result = $this->verify_signature($payload, $signature, $public_key); if (!$verification_result) { @@ -79,15 +106,13 @@ public function payment_trigger() { return; } - // Extract post_id from 'post_id' -> 'rawMessage' - $post_id = isset($json_data['post_id']['rawMessage']) ? intval($json_data['post_id']['rawMessage']) : 0; - - // Extract and sanitize transaction details - $tx_hash = isset($json_data['tx_hash']) ? sanitize_text_field($json_data['tx_hash']) : ''; - $tx_amount = isset($json_data['tx_amount']) ? sanitize_text_field($json_data['tx_amount']) : ''; - $tx_timestamp = isset($json_data['tx_timestamp']) ? intval($json_data['tx_timestamp']) : 0; - $user_address = isset($json_data['user_address'][0]) ? sanitize_text_field($json_data['user_address'][0]) : ''; - + //Sanitize data + $post_id = intval( $post_id_raw ); + $tx_hash = sanitize_text_field( $tx_hash_raw ); + $tx_amount = sanitize_text_field( $tx_amount_raw ); + $tx_timestamp = intval( $ts_raw ); + $user_address = sanitize_text_field( $user_addr_raw ); + // Convert timestamp to MySQL datetime $mysql_timestamp = $tx_timestamp ? gmdate('Y-m-d H:i:s', $tx_timestamp) : '0000-00-00 00:00:00';