From 783ad5942cd3e80ed6e54ef6187efdc4cc4b40b6 Mon Sep 17 00:00:00 2001 From: eug-L Date: Thu, 6 Mar 2025 14:22:51 +0800 Subject: [PATCH 01/11] add error alert message --- admin/view/stylesheet/index.css | 11 +-- admin/view/template/module/tawkto.twig | 98 ++++++++++++++------------ 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/admin/view/stylesheet/index.css b/admin/view/stylesheet/index.css index ff73bcb..948547c 100644 --- a/admin/view/stylesheet/index.css +++ b/admin/view/stylesheet/index.css @@ -29,13 +29,16 @@ html { display: none; } -#optionsSuccessMessage { +.alert { position: absolute; + font-weight: bold; + display: none; +} + +#optionsSuccessMessage { background-color: #dff0d8; color: #3c763d; border-color: #d6e9c6; - font-weight: bold; - display: none; } .pull-right { @@ -50,7 +53,7 @@ html { } @media only screen and (max-width: 1200px) { - #optionsSuccessMessage { + .alert { position: relative; margin-top: 1rem; } diff --git a/admin/view/template/module/tawkto.twig b/admin/view/template/module/tawkto.twig index 7db82e3..32d0516 100644 --- a/admin/view/template/module/tawkto.twig +++ b/admin/view/template/module/tawkto.twig @@ -219,6 +219,9 @@
Successfully set widget options to your site
+
+ Failed to set widget options to your site +
@@ -402,59 +405,62 @@ store: store_id, options: form.serialize(), }, function (r) { - if (r.success) { - $('#optionsSuccessMessage').toggle().delay(3000).fadeOut(); + if (!r.success) { + $('#optionsErrorMessage').text(r.message).toggle().delay(3000).fadeOut(); + return; + } - // Update saved options - var fields = form.serializeArray(); - for (store of storeHierarchy) { - if (store.id !== store_id) { - continue; - } + $('#optionsSuccessMessage').toggle().delay(3000).fadeOut(); + + // Update saved options + var fields = form.serializeArray(); + for (store of storeHierarchy) { + if (store.id !== store_id) { + continue; + } - store.display_opts = { - 'always_display': false, - 'show_onfrontpage': false, - 'show_oncategory': false, - 'show_oncustom': [], - 'hide_oncustom': [], - }; + store.display_opts = { + 'always_display': false, + 'show_onfrontpage': false, + 'show_oncategory': false, + 'show_oncustom': [], + 'hide_oncustom': [], + }; - store.privacy_opts = { - 'enable_visitor_recognition': false, + store.privacy_opts = { + 'enable_visitor_recognition': false, + } + + store.cart_opts = { + 'monitor_customer_cart': false, + } + + for (field of fields) { + if (field.name === 'show_oncustom') { + store.display_opts['show_oncustom'] = field.value.replaceAll('\r', '\n').split('\n').filter(Boolean); + continue; } - store.cart_opts = { - 'monitor_customer_cart': false, + if (field.name === 'hide_oncustom') { + store.display_opts['hide_oncustom'] = field.value.replaceAll('\r', '\n').split('\n').filter(Boolean); + continue; } - for (field of fields) { - if (field.name === 'show_oncustom') { - store.display_opts['show_oncustom'] = field.value.replaceAll('\r', '\n').split('\n').filter(Boolean); - continue; - } - - if (field.name === 'hide_oncustom') { - store.display_opts['hide_oncustom'] = field.value.replaceAll('\r', '\n').split('\n').filter(Boolean); - continue; - } - - // serializeArray() only includes "successful controls" - switch (field.name) { - case 'always_display': - case 'show_onfrontpage': - case 'show_oncategory': - store.display_opts[field.name] = true; - break; - - case 'enable_visitor_recognition': - store.privacy_opts[field.name] = true; - break; - - case 'monitor_customer_cart': - store.cart_opts[field.name] = true; - break; - } + // serializeArray() only includes "successful controls" + switch (field.name) { + case 'always_display': + case 'show_onfrontpage': + case 'show_oncategory': + store.display_opts[field.name] = true; + break; + + case 'enable_visitor_recognition': + store.privacy_opts[field.name] = true; + break; + + case 'monitor_customer_cart': + store.cart_opts[field.name] = true; + break; } } } From 8260001aa65b60c9559aa53b81d0ab9b0168b9a2 Mon Sep 17 00:00:00 2001 From: eug-L Date: Thu, 6 Mar 2025 17:37:52 +0800 Subject: [PATCH 02/11] add security settings --- admin/controller/module/tawkto.php | 131 ++++++++++++++++++++++++- admin/view/template/module/tawkto.twig | 28 ++++++ system/config/tawkto.php | 6 +- 3 files changed, 161 insertions(+), 4 deletions(-) diff --git a/admin/controller/module/tawkto.php b/admin/controller/module/tawkto.php index 1f69f88..7238e2c 100644 --- a/admin/controller/module/tawkto.php +++ b/admin/controller/module/tawkto.php @@ -13,6 +13,8 @@ class Tawkto extends Controller { + public const NO_CHANGE = 'nochange'; + /** * __construct */ @@ -148,6 +150,7 @@ private function getStoreHierarchy() 'display_opts' => $this->getDisplayOpts($currentSettings), 'privacy_opts' => $this->getPrivacyOpts($currentSettings), 'cart_opts' => $this->getCartOpts($currentSettings), + 'security_opts' => $this->getSecurityOpts($currentSettings), ); foreach ($stores as $store) { @@ -160,6 +163,7 @@ private function getStoreHierarchy() 'display_opts' => $this->getDisplayOpts($currentSettings), 'privacy_opts' => $this->getPrivacyOpts($currentSettings), 'cart_opts' => $this->getCartOpts($currentSettings), + 'security_opts' => $this->getSecurityOpts($currentSettings), ); } @@ -250,6 +254,8 @@ public function setoptions() $cartOpts = $this->config->get('tawkto_cart'); + $securityOpts = $this->config->get('tawkto_security'); + if (isset($_POST['options']) && !empty($_POST['options'])) { $options = explode('&', $_POST['options']); @@ -286,14 +292,55 @@ public function setoptions() case 'monitor_customer_cart': $cartOpts[$key] = true; break; + + case 'secure_mode_enabled': + $securityOpts[$key] = true; + break; + + case 'js_api_key': + if ($value === self::NO_CHANGE) { + unset($securityOpts['js_api_key']); + break; + } + + if ($value === '') { + break; + } + + try { + if (strlen(trim($value)) !== 40) { + throw new \Exception('Invalid API key. Please provide value with 40 characters'); + } + + $securityOpts['js_api_key'] = $this->encryptData($value); + } catch (\Exception $e) { + unset($securityOpts['js_api_key']); + + echo json_encode(array('success' => false, 'message' => $e->getMessage())); + die(); + } } } } $currentSettings = $this->getCurrentSettingsFor($store_id); - $currentSettings['module_tawkto_visibility'] = $visibilityOpts; - $currentSettings['module_tawkto_privacy'] = $privacyOpts; - $currentSettings['module_tawkto_cart'] = $cartOpts; + if (!isset($currentSettings['module_tawkto_visibility'])) { + $currentSettings['module_tawkto_visibility'] = array(); + } + if (!isset($currentSettings['module_tawkto_privacy'])) { + $currentSettings['module_tawkto_privacy'] = array(); + } + if (!isset($currentSettings['module_tawkto_cart'])) { + $currentSettings['module_tawkto_cart'] = array(); + } + if (!isset($currentSettings['module_tawkto_security'])) { + $currentSettings['module_tawkto_security'] = array(); + } + + $currentSettings['module_tawkto_visibility'] = array_merge($currentSettings['module_tawkto_visibility'], $visibilityOpts); + $currentSettings['module_tawkto_privacy'] = array_merge($currentSettings['module_tawkto_privacy'], $privacyOpts); + $currentSettings['module_tawkto_cart'] = array_merge($currentSettings['module_tawkto_cart'], $cartOpts); + $currentSettings['module_tawkto_security'] = array_merge($currentSettings['module_tawkto_security'], $securityOpts); $this->model_setting_setting->editSetting('module_tawkto', $currentSettings, $store_id); echo json_encode(array('success' => true)); @@ -396,4 +443,82 @@ public function getCartOpts($settings) return $options; } + + /** + * Get security options from setting + * + * @return Array + */ + public function getSecurityOpts($settings) { + $options = $this->config->get('tawkto_security'); + + if (isset($settings['module_tawkto_security'])) { + $options = $settings['module_tawkto_security']; + } + + if (!empty($options['js_api_key'])) { + $options['js_api_key'] = self::NO_CHANGE; + } + + return $options; + } + + + /** + * Get credentials + * + * @return Array + */ + private function getCredentials() { + $filePath = DIR_EXTENSION . 'tawkto/system/config/credentials.json'; + + if (file_exists($filePath)) { + return json_decode(file_get_contents($filePath), true); + } + + $credentials = array( + 'encryption_key' => bin2hex(random_bytes(32)), + ); + + file_put_contents($filePath, json_encode($credentials)); + + return $credentials; + } + + /** + * Encrypt data + * + * @param string $data Data to encrypt + * + * @return string Encrypted data + * + * @throws \Exception Error encrypting data + */ + private function encryptData($data) { + try { + $encryptionKey = $this->getCredentials()['encryption_key']; + } catch (\Exception $e) { + throw new \Exception('Failed to get encryption key'); + } + + try { + $iv = random_bytes(16); + } catch (\Exception $e) { + throw new \Exception('Failed to generate IV'); + } + + $encrypted = openssl_encrypt($data, 'AES-256-CBC', $encryptionKey, 0, $iv); + + if ($encrypted === false) { + throw new \Exception('Failed to encrypt data'); + } + + $encrypted = base64_encode($iv . $encrypted); + + if ($encrypted === false) { + throw new \Exception('Failed to encode data'); + } + + return $encrypted; + } } diff --git a/admin/view/template/module/tawkto.twig b/admin/view/template/module/tawkto.twig index 32d0516..19328a0 100644 --- a/admin/view/template/module/tawkto.twig +++ b/admin/view/template/module/tawkto.twig @@ -195,6 +195,34 @@

+
+
Security Settings
+
+
+ +
+ +
+
+
+ +
+ +
+
+

Cart Integration
diff --git a/system/config/tawkto.php b/system/config/tawkto.php index 36ad268..5700efa 100644 --- a/system/config/tawkto.php +++ b/system/config/tawkto.php @@ -11,5 +11,9 @@ ); $_['tawkto_cart'] = array( 'monitor_customer_cart' => false, -) +); +$_['tawkto_security'] = array( + 'secure_mode_enabled' => false, + 'js_api_key' => null, +); ?> From 50dcb7bd930fb491113574b9cdaf09a226fac459 Mon Sep 17 00:00:00 2001 From: eug-L Date: Fri, 7 Mar 2025 13:46:06 +0800 Subject: [PATCH 03/11] add hashing for visitor recognition --- catalog/controller/module/tawkto.php | 92 +++++++++++++++++++++++- catalog/view/template/module/tawkto.twig | 2 +- 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/catalog/controller/module/tawkto.php b/catalog/controller/module/tawkto.php index c765cea..53d812b 100644 --- a/catalog/controller/module/tawkto.php +++ b/catalog/controller/module/tawkto.php @@ -31,13 +31,17 @@ public function __construct($registry) { */ public function index() { + if (session_status() === PHP_SESSION_NONE && !headers_sent()) { + session_start(); + } + $this->load->model('setting/setting'); $data = array(); - $data['visitor'] = $this->getVisitor(); $privacy_opts = $this->config->get('tawkto_privacy'); $cart_opts = $this->config->get('tawkto_cart'); + $security_opts = $this->config->get('tawkto_security'); $settings = $this->getCurrentSettings(); if (isset($settings['module_tawkto_privacy'])) { @@ -46,8 +50,15 @@ public function index() if (isset($settings['module_tawkto_cart'])) { $cart_opts = $settings['module_tawkto_cart']; } + if (isset($settings['module_tawkto_security'])) { + $security_opts = $settings['module_tawkto_security']; + } - $data['enable_visitor_recognition'] = $privacy_opts['enable_visitor_recognition']; + $data['visitor'] = $this->getVisitor(array( + 'enable_visitor_recognition' => $privacy_opts['enable_visitor_recognition'], + 'secure_mode_enabled' => $security_opts['secure_mode_enabled'], + 'js_api_key' => $security_opts['js_api_key'], + )); $data['can_monitor_customer_cart'] = $cart_opts['monitor_customer_cart']; $widget = $this->getWidget(); @@ -143,16 +154,36 @@ private function matchPatterns($current_page, $pages) /** * Get visitor details * + * @param array $params * @return string|null */ - private function getVisitor() + private function getVisitor($params) { + $enable_visitor_recognition = $params['enable_visitor_recognition']; + $secure_mode_enabled = $params['secure_mode_enabled']; + $encrypted_js_api_key = $params['js_api_key']; + + if (!$enable_visitor_recognition) { + return null; + } + $logged_in = $this->customer->isLogged(); if ($logged_in) { $data = array( 'name' => $this->customer->getFirstName().' '.$this->customer->getLastName(), 'email' => $this->customer->getEmail(), ); + + if ($secure_mode_enabled && !is_null($encrypted_js_api_key)) { + try { + $js_api_key = $this->getJsApiKey($encrypted_js_api_key); + } catch (\Exception $e) { + return null; + } + + $data['hash'] = hash_hmac('sha256', $this->customer->getEmail(), $js_api_key); + } + return json_encode($data); } @@ -169,4 +200,59 @@ private function getCurrentSettings() $store_id = $this->config->get('config_store_id'); return $this->model_setting_setting->getSetting('module_tawkto', $store_id); } + + /** + * Get js_api_key + * @param string $encrypted_js_api_key + * @return string JS API key + */ + private function getJsApiKey($encrypted_js_api_key) + { + if (isset($_SESSION['tawkto_js_api_key'])) { + return $_SESSION['tawkto_js_api_key']; + } + + $js_api_key = $this->decryptData($encrypted_js_api_key); + + $_SESSION['tawkto_js_api_key'] = $js_api_key; + + return $js_api_key; + } + + /** + * Decrypt data + * @param mixed $data + * @return string Decrypted data + */ + private function decryptData($data) + { + $filePath = DIR_EXTENSION . 'tawkto/system/config/credentials.json'; + + if (!file_exists($filePath)) { + throw new \Exception('Credentials file not found'); + } + + $credentials = json_decode(file_get_contents($filePath), true); + + if (!isset($credentials['encryption_key'])) { + throw new \Exception('Encryption key not found'); + } + + $decoded = base64_decode($data); + + if ($decoded === false) { + throw new \Exception('Failed to decode data'); + } + + $iv = substr($decoded, 0, 16); + $encrypted_data = substr($decoded, 16); + + $decrypted_data = openssl_decrypt($encrypted_data, 'AES-256-CBC', $credentials['encryption_key'], 0, $iv); + + if ($decrypted_data === false) { + throw new \Exception('Failed to decrypt data'); + } + + return $decrypted_data; + } } diff --git a/catalog/view/template/module/tawkto.twig b/catalog/view/template/module/tawkto.twig index 23f1302..6f02633 100644 --- a/catalog/view/template/module/tawkto.twig +++ b/catalog/view/template/module/tawkto.twig @@ -9,7 +9,7 @@