diff --git a/Status.md b/Status.md index 4de1072d..96fa2a64 100644 --- a/Status.md +++ b/Status.md @@ -8,9 +8,49 @@ ## 🎯 CURRENT SESSION - ZOHO CRM INTEGRATION SETUP (Dec 16, 2025) -### Zoho CRM Integration - Staging Environment +### Zoho CRM Integration - Production Issue (BLOCKING) -**Objective:** Configure and test Zoho CRM sync implementation for staging environment. +**Objective:** Configure and test Zoho CRM sync implementation for production environment. + +**Status:** 🔴 BLOCKING - Credential save hangs on production (400 Bad Request) + +### Active Issue - Credential Save Hanging on Production + +**Problem:** When saving Zoho CRM credentials at `https://upskillhvac.com/wp-admin/admin.php?page=hvac-zoho-sync`, the AJAX request returns a 400 Bad Request error and the form hangs on "Saving...". + +**Console Error:** +``` +POST https://upskillhvac.com/wp-admin/admin-ajax.php 400 (Bad Request) +``` + +**Investigation Completed:** +1. ✅ Fixed Client ID regex to allow lowercase letters (`[A-Z0-9]` → `[A-Za-z0-9]`) +2. ✅ Fixed credential storage mismatch - all methods now use `HVAC_Secure_Storage` +3. ✅ Updated `HVAC_Zoho_CRM_Auth` class to use encrypted storage consistently +4. ✅ Updated OAuth callback to use secure storage +5. ✅ Updated test_connection to use secure storage +6. ✅ Deployed fixes to production - **Issue persists** + +**Files Modified:** +- `includes/admin/class-zoho-admin.php` - Secure storage for credentials, fixed regex +- `includes/zoho/class-zoho-crm-auth.php` - All credential operations use HVAC_Secure_Storage + +**Next Steps for Investigation:** +1. Check PHP error logs on production server for detailed error +2. Test AJAX endpoint directly via curl to isolate frontend vs backend issue +3. Verify nonce generation and validation on production +4. Check if WAF/security plugin is blocking the request +5. Test with browser network tab to see exact request/response + +**Possible Causes:** +- Server-side security rules (Cloudflare, ModSecurity) blocking POST to admin-ajax.php +- Nonce validation failing due to caching +- Plugin conflict on production +- HVAC_Secure_Storage encryption key difference between environments + +--- + +### Zoho CRM Integration - Staging Environment (Working) **Status:** ✅ OAuth Working, Sync Methods Implemented, Dry-Run Tested diff --git a/includes/admin/class-zoho-admin.php b/includes/admin/class-zoho-admin.php index 414f6f67..8f3ee018 100644 --- a/includes/admin/class-zoho-admin.php +++ b/includes/admin/class-zoho-admin.php @@ -386,7 +386,7 @@ class HVAC_Zoho_Admin { } // Validate Client ID format (should start with "1000.") - if (!preg_match('/^1000\.[A-Z0-9]+$/', $client_id)) { + if (!preg_match('/^1000\.[A-Za-z0-9]+$/', $client_id)) { wp_send_json_error(array('message' => 'Invalid Client ID format. Should start with "1000."')); return; } @@ -557,6 +557,11 @@ class HVAC_Zoho_Admin { wp_die('OAuth callback missing state parameter. Possible CSRF attack.'); } + // Load secure storage for credential handling + if (!class_exists('HVAC_Secure_Storage')) { + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-secure-storage.php'; + } + require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-crm-auth.php'; $auth = new HVAC_Zoho_CRM_Auth(); @@ -564,9 +569,12 @@ class HVAC_Zoho_Admin { wp_die('OAuth state validation failed. Please try the authorization again.'); } - // Get credentials from WordPress options - $client_id = get_option('hvac_zoho_client_id', ''); - $client_secret = get_option('hvac_zoho_client_secret', ''); + // Get credentials using secure storage (credentials are stored encrypted) + if (!class_exists('HVAC_Secure_Storage')) { + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-secure-storage.php'; + } + $client_id = HVAC_Secure_Storage::get_credential('hvac_zoho_client_id', ''); + $client_secret = HVAC_Secure_Storage::get_credential('hvac_zoho_client_secret', ''); if (empty($client_id) || empty($client_secret)) { wp_die('OAuth callback error: Missing client credentials. Please configure your Zoho CRM credentials first.'); @@ -609,20 +617,19 @@ class HVAC_Zoho_Admin { exit; } - // Save tokens - update_option('hvac_zoho_access_token', $token_data['access_token']); + // Save tokens using secure storage + HVAC_Secure_Storage::store_credential('hvac_zoho_access_token', $token_data['access_token']); update_option('hvac_zoho_token_expires', time() + ($token_data['expires_in'] ?? 3600)); - + // Refresh token might not be returned on subsequent authorizations if (isset($token_data['refresh_token']) && !empty($token_data['refresh_token'])) { - update_option('hvac_zoho_refresh_token', $token_data['refresh_token']); + HVAC_Secure_Storage::store_credential('hvac_zoho_refresh_token', $token_data['refresh_token']); } else { - $existing_refresh = get_option('hvac_zoho_refresh_token'); + $existing_refresh = HVAC_Secure_Storage::get_credential('hvac_zoho_refresh_token', ''); if (empty($existing_refresh)) { // This is critical - we need a refresh token for long-term access // Store a warning but still complete the flow update_option('hvac_zoho_missing_refresh_token', true); - } else { } } @@ -653,9 +660,12 @@ class HVAC_Zoho_Admin { return; } - // Get credentials from WordPress options - $client_id = get_option('hvac_zoho_client_id', ''); - $client_secret = get_option('hvac_zoho_client_secret', ''); + // Get credentials using secure storage (credentials are stored encrypted) + if (!class_exists('HVAC_Secure_Storage')) { + require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-secure-storage.php'; + } + $client_id = HVAC_Secure_Storage::get_credential('hvac_zoho_client_id', ''); + $client_secret = HVAC_Secure_Storage::get_credential('hvac_zoho_client_secret', ''); // Check configuration before attempting connection if (empty($client_id)) { @@ -679,7 +689,7 @@ class HVAC_Zoho_Admin { } // Check if we have stored refresh token from previous OAuth - $stored_refresh_token = get_option('hvac_zoho_refresh_token'); + $stored_refresh_token = HVAC_Secure_Storage::get_credential('hvac_zoho_refresh_token', ''); if (empty($stored_refresh_token)) { diff --git a/includes/zoho/class-zoho-crm-auth.php b/includes/zoho/class-zoho-crm-auth.php index 933574b5..81f5926b 100644 --- a/includes/zoho/class-zoho-crm-auth.php +++ b/includes/zoho/class-zoho-crm-auth.php @@ -13,7 +13,7 @@ if (!defined('ABSPATH')) { } class HVAC_Zoho_CRM_Auth { - + private $client_id; private $client_secret; private $refresh_token; @@ -21,27 +21,32 @@ class HVAC_Zoho_CRM_Auth { private $access_token; private $token_expiry; private $last_error = null; - + public function __construct() { - // Load credentials from WordPress options (new approach) - $this->client_id = get_option('hvac_zoho_client_id', ''); - $this->client_secret = get_option('hvac_zoho_client_secret', ''); - $this->refresh_token = get_option('hvac_zoho_refresh_token', ''); + // Load secure storage class + if (!class_exists('HVAC_Secure_Storage')) { + require_once plugin_dir_path(dirname(__FILE__)) . 'class-hvac-secure-storage.php'; + } + + // Load credentials from WordPress options using secure storage (encrypted) + $this->client_id = HVAC_Secure_Storage::get_credential('hvac_zoho_client_id', ''); + $this->client_secret = HVAC_Secure_Storage::get_credential('hvac_zoho_client_secret', ''); + $this->refresh_token = HVAC_Secure_Storage::get_credential('hvac_zoho_refresh_token', ''); $this->redirect_uri = get_site_url() . '/oauth/callback'; - + // Fallback to config file if options are empty (backward compatibility) if (empty($this->client_id) || empty($this->client_secret)) { $config_file = plugin_dir_path(__FILE__) . 'zoho-config.php'; if (file_exists($config_file)) { require_once $config_file; - + $this->client_id = empty($this->client_id) && defined('ZOHO_CLIENT_ID') ? ZOHO_CLIENT_ID : $this->client_id; $this->client_secret = empty($this->client_secret) && defined('ZOHO_CLIENT_SECRET') ? ZOHO_CLIENT_SECRET : $this->client_secret; $this->refresh_token = empty($this->refresh_token) && defined('ZOHO_REFRESH_TOKEN') ? ZOHO_REFRESH_TOKEN : $this->refresh_token; $this->redirect_uri = defined('ZOHO_REDIRECT_URI') ? ZOHO_REDIRECT_URI : $this->redirect_uri; } } - + // Load stored access token from WordPress options $this->load_access_token(); } @@ -354,31 +359,31 @@ class HVAC_Zoho_CRM_Auth { } /** - * Save tokens to WordPress options + * Save tokens to WordPress options using secure storage */ private function save_tokens() { - update_option('hvac_zoho_refresh_token', $this->refresh_token); + HVAC_Secure_Storage::store_credential('hvac_zoho_refresh_token', $this->refresh_token); $this->save_access_token(); } - + /** - * Save access token + * Save access token using secure storage */ private function save_access_token() { - update_option('hvac_zoho_access_token', $this->access_token); + HVAC_Secure_Storage::store_credential('hvac_zoho_access_token', $this->access_token); update_option('hvac_zoho_token_expiry', $this->token_expiry); } - + /** - * Load access token from WordPress options + * Load access token from WordPress options using secure storage */ private function load_access_token() { - $this->access_token = get_option('hvac_zoho_access_token'); + $this->access_token = HVAC_Secure_Storage::get_credential('hvac_zoho_access_token', ''); $this->token_expiry = get_option('hvac_zoho_token_expiry', 0); - + // Load refresh token if not set if (!$this->refresh_token) { - $this->refresh_token = get_option('hvac_zoho_refresh_token'); + $this->refresh_token = HVAC_Secure_Storage::get_credential('hvac_zoho_refresh_token', ''); } }