| @@ -7,10 +7,10 @@ | ||
| 7 | 7 | */ |
| 8 | 8 | |
| 9 | 9 | namespace SRFM\Inc\AI_Form_Builder; |
| 10 | 10 | |
| 11 | +use SRFM\Inc\Helper; | |
| 11 | 12 | use SRFM\Inc\Traits\Get_Instance; |
| 12 | -use SRFM\Inc\Helper; | |
| 13 | 13 | |
| 14 | 14 | // Exit if accessed directly. |
| 15 | 15 | if ( ! defined( 'ABSPATH' ) ) { |
| 16 | 16 | exit; |
| @@ -41,17 +41,29 @@ | ||
| 41 | 41 | |
| 42 | 42 | $nonce = Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) ); |
| 43 | 43 | |
| 44 | 44 | if ( ! wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ) ) { |
| 45 | - wp_send_json_error( 'Nonce verification failed.' ); | |
| 45 | + wp_send_json_error( __( 'Nonce verification failed.', 'sureforms' ) ); | |
| 46 | 46 | } |
| 47 | 47 | |
| 48 | 48 | // Generate a random key of 16 characters. |
| 49 | 49 | $this->key = wp_generate_password( 16, false ); |
| 50 | + // Persist key for decryption in handle_access_key (10 min TTL). | |
| 51 | + set_transient( 'srfm_ai_auth_key_' . get_current_user_id(), $this->key, 10 * MINUTE_IN_SECONDS ); | |
| 50 | 52 | |
| 53 | + // Get the source parameter from the query. | |
| 54 | + $source = sanitize_text_field( Helper::get_string_value( $request->get_param( 'source' ) ?? '' ) ); | |
| 55 | + switch ( $source ) { | |
| 56 | + case 'onboarding': | |
| 57 | + $redirect_back = site_url() . '/wp-admin/admin.php?page=sureforms_menu#/onboarding/email-delivery'; | |
| 58 | + break; | |
| 59 | + default: | |
| 60 | + $redirect_back = site_url() . '/wp-admin/admin.php?page=add-new-form'; | |
| 61 | + } | |
| 62 | + | |
| 51 | 63 | // Prepare the token data. |
| 52 | 64 | $token_data = [ |
| 53 | - 'redirect-back' => site_url() . '/wp-admin/admin.php?page=add-new-form&method=ai', | |
| 65 | + 'redirect-back' => $redirect_back, | |
| 54 | 66 | 'key' => $this->key, |
| 55 | 67 | 'site-url' => site_url(), |
| 56 | 68 | 'nonce' => wp_create_nonce( 'ai_auth_nonce' ), |
| 57 | 69 | ]; |
| @@ -58,14 +70,13 @@ | ||
| 58 | 70 | |
| 59 | 71 | $encoded_token_data = wp_json_encode( $token_data ); |
| 60 | 72 | |
| 61 | 73 | if ( empty( $encoded_token_data ) ) { |
| 62 | - wp_send_json_error( [ 'message' => 'Failed to encode the token data.' ] ); | |
| 74 | + wp_send_json_error( [ 'message' => __( 'Failed to encode the token data.', 'sureforms' ) ] ); | |
| 63 | 75 | } |
| 64 | 76 | |
| 65 | 77 | // Send the token data to the frontend for redirection. |
| 66 | 78 | wp_send_json_success( SRFM_BILLING_PORTAL . 'auth/?token=' . base64_encode( $encoded_token_data ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode |
| 67 | - | |
| 68 | 79 | } |
| 69 | 80 | |
| 70 | 81 | /** |
| 71 | 82 | * Handles the access key. |
| @@ -78,16 +89,19 @@ | ||
| 78 | 89 | |
| 79 | 90 | $nonce = Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) ); |
| 80 | 91 | |
| 81 | 92 | if ( ! wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ) ) { |
| 82 | - wp_send_json_error( 'Nonce verification failed.' ); | |
| 93 | + wp_send_json_error( __( 'Nonce verification failed.', 'sureforms' ) ); | |
| 83 | 94 | } |
| 84 | 95 | |
| 85 | 96 | // get body data. |
| 86 | - $body = json_decode( $request->get_body(), true ); | |
| 97 | + // get_body() is null for an empty request, and passing null to json_decode() | |
| 98 | + // is deprecated on PHP 8.1+ - the notice lands in the response body and makes | |
| 99 | + // the JSON unparseable wherever display_errors is on. | |
| 100 | + $body = json_decode( Helper::get_string_value( $request->get_body() ), true ); | |
| 87 | 101 | |
| 88 | 102 | if ( empty( $body ) ) { |
| 89 | - wp_send_json_error( [ 'message' => 'Error processing Access Key.' ] ); | |
| 103 | + wp_send_json_error( [ 'message' => __( 'Error processing Access Key.', 'sureforms' ) ] ); | |
| 90 | 104 | } |
| 91 | 105 | |
| 92 | 106 | // get access key. |
| 93 | 107 | $access_key = is_array( $body ) && ! empty( |
| @@ -93,16 +107,19 @@ | ||
| 93 | 107 | $access_key = is_array( $body ) && ! empty( |
| 94 | 108 | $body['accessKey'] |
| 95 | 109 | ) ? Helper::get_string_value( $body['accessKey'] ) : ''; |
| 96 | 110 | |
| 111 | + $stored_key = Helper::get_string_value( get_transient( 'srfm_ai_auth_key_' . get_current_user_id() ) ); | |
| 112 | + | |
| 113 | + if ( empty( $stored_key ) ) { | |
| 114 | + wp_send_json_error( [ 'message' => __( 'Authentication session expired. Please try again.', 'sureforms' ) ] ); | |
| 115 | + } | |
| 116 | + | |
| 97 | 117 | // decrypt the access key. |
| 98 | 118 | if ( ! empty( $access_key ) ) { |
| 99 | - $this->decrypt_access_key( | |
| 100 | - $access_key, | |
| 101 | - $this->key | |
| 102 | - ); | |
| 119 | + $this->decrypt_access_key( $access_key, $stored_key ); | |
| 103 | 120 | } else { |
| 104 | - wp_send_json_error( [ 'message' => 'No access key provided.' ] ); | |
| 121 | + wp_send_json_error( [ 'message' => __( 'No access key provided.', 'sureforms' ) ] ); | |
| 105 | 122 | } |
| 106 | 123 | } |
| 107 | 124 | |
| 108 | 125 | /** |
| @@ -122,17 +139,24 @@ | ||
| 122 | 139 | if ( empty( $decoded_data ) ) { |
| 123 | 140 | return false; |
| 124 | 141 | } |
| 125 | 142 | |
| 126 | - // split the key and encrypted data. | |
| 127 | - list($key, $encrypted) = explode( '::', $decoded_data, 2 ); | |
| 143 | + // Extract the IV and encrypted data (billing portal sends IV::ENCRYPTED_DATA format). | |
| 144 | + $parts = explode( '::', $decoded_data, 2 ); | |
| 128 | 145 | |
| 129 | - // Decrypt the data using the key. | |
| 130 | - $decrypted = openssl_decrypt( $encrypted, $method, $key, 0, $key ); | |
| 146 | + if ( ! isset( $parts[1] ) ) { | |
| 147 | + wp_send_json_error( [ 'message' => __( 'Invalid access key format.', 'sureforms' ) ] ); | |
| 148 | + } | |
| 131 | 149 | |
| 150 | + $iv = $parts[0]; | |
| 151 | + $encrypted = $parts[1]; | |
| 152 | + | |
| 153 | + // Decrypt the data using the server-stored key and the billing-portal-supplied IV. | |
| 154 | + $decrypted = openssl_decrypt( $encrypted, $method, $key, 0, $iv ); | |
| 155 | + | |
| 132 | 156 | // if the decryption returns false then send error. |
| 133 | 157 | if ( empty( $decrypted ) ) { |
| 134 | - wp_send_json_error( [ 'message' => 'Failed to decrypt the access key.' ] ); | |
| 158 | + wp_send_json_error( [ 'message' => __( 'Failed to decrypt the access key.', 'sureforms' ) ] ); | |
| 135 | 159 | } |
| 136 | 160 | |
| 137 | 161 | // json decode the decrypted data. |
| 138 | 162 | $decrypted_data_array = json_decode( $decrypted, true ); |
| @@ -137,27 +161,48 @@ | ||
| 137 | 161 | // json decode the decrypted data. |
| 138 | 162 | $decrypted_data_array = json_decode( $decrypted, true ); |
| 139 | 163 | |
| 140 | 164 | if ( ! is_array( $decrypted_data_array ) || empty( $decrypted_data_array ) ) { |
| 141 | - wp_send_json_error( [ 'message' => 'Failed to json decode the decrypted data.' ] ); | |
| 165 | + wp_send_json_error( [ 'message' => __( 'Failed to json decode the decrypted data.', 'sureforms' ) ] ); | |
| 142 | 166 | } |
| 143 | 167 | |
| 144 | 168 | // verify the nonce that comes in $encrypted_email_array. |
| 145 | - if ( ! empty( $decrypted_data_array['nonce'] ) && ! wp_verify_nonce( $decrypted_data_array['nonce'], 'ai_auth_nonce' ) ) { | |
| 146 | - wp_send_json_error( [ 'message' => 'Nonce verification failed.' ] ); | |
| 169 | + if ( empty( $decrypted_data_array['nonce'] ) || ! wp_verify_nonce( $decrypted_data_array['nonce'], 'ai_auth_nonce' ) ) { | |
| 170 | + wp_send_json_error( [ 'message' => __( 'Nonce verification failed.', 'sureforms' ) ] ); | |
| 147 | 171 | } |
| 148 | 172 | |
| 149 | 173 | // check if the user email is present in the decrypted data. |
| 150 | 174 | if ( empty( $decrypted_data_array['user_email'] ) ) { |
| 151 | - wp_send_json_error( [ 'message' => 'No user email found in the decrypted data.' ] ); | |
| 175 | + wp_send_json_error( [ 'message' => __( 'No user email found in the decrypted data.', 'sureforms' ) ] ); | |
| 152 | 176 | } |
| 153 | 177 | |
| 178 | + // Extract is_subscribed value if present. | |
| 179 | + $is_subscribed = false; | |
| 180 | + if ( isset( $decrypted_data_array['is_subscribed'] ) ) { | |
| 181 | + // Convert string 'true'/'false' to boolean if needed. | |
| 182 | + if ( is_string( $decrypted_data_array['is_subscribed'] ) ) { | |
| 183 | + $is_subscribed = 'true' === $decrypted_data_array['is_subscribed']; | |
| 184 | + } else { | |
| 185 | + $is_subscribed = (bool) $decrypted_data_array['is_subscribed']; | |
| 186 | + } | |
| 187 | + | |
| 188 | + // Update the analytics option based on the preference. | |
| 189 | + // Set 'yes' if opted in, empty string if not. | |
| 190 | + $enable_contribution = $is_subscribed ? 'yes' : ''; | |
| 191 | + update_option( 'sureforms_usage_optin', $enable_contribution ); | |
| 192 | + | |
| 193 | + // Remove is_subscribed from the decrypted data. | |
| 194 | + unset( $decrypted_data_array['is_subscribed'] ); | |
| 195 | + } | |
| 196 | + | |
| 154 | 197 | // remove the nonce from the decrypted data before saving it to the options. |
| 155 | 198 | unset( $decrypted_data_array['nonce'] ); |
| 156 | 199 | |
| 200 | + // Clean up the auth key transient. | |
| 201 | + delete_transient( 'srfm_ai_auth_key_' . get_current_user_id() ); | |
| 202 | + | |
| 157 | 203 | // save the user email to the options. |
| 158 | 204 | update_option( 'srfm_ai_auth_user_email', $decrypted_data_array ); |
| 159 | 205 | |
| 160 | 206 | wp_send_json_success(); |
| 161 | 207 | } |
| 162 | - | |
| 163 | 208 | } |