__( 'You are not allowed to do this.', 'postnl-for-woocommerce' ) ), 403 ); } check_ajax_referer( self::NONCE_ACTION, 'nonce' ); $settings = Settings::get_instance(); $is_sandbox = isset( $_POST['environment'] ) && 'sandbox' === sanitize_key( wp_unslash( $_POST['environment'] ) ); // The key and customer details are taken from the request, not storage, so // a merchant filling in all three fields at once is checked against what // they just typed rather than the empty saved values. $new_key = isset( $_POST['api_key'] ) ? trim( (string) wp_unslash( $_POST['api_key'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- API key is not free text; trimmed and sent as a header only. $customer_code = isset( $_POST['customer_code'] ) ? sanitize_text_field( wp_unslash( $_POST['customer_code'] ) ) : ''; $customer_num = isset( $_POST['customer_num'] ) ? sanitize_text_field( wp_unslash( $_POST['customer_num'] ) ) : ''; $original = $is_sandbox ? trim( (string) $settings->get_api_key_sandbox() ) : trim( (string) $settings->get_api_key() ); // Empty and same-as-old are decidable without touching PostNL. The browser // already skips those, but guard here too so a crafted request cannot spend // a barcode on them. if ( '' === $new_key || $new_key === $original ) { wp_send_json_success( $settings->build_new_key_status( $new_key, $original, false, $is_sandbox, false ) ); } // This exact key already passed validation and is saved, so report the // green "Valid" state without spending another barcode. Without this, a // merchant who just focuses and blurs the pre-filled field would mint a // barcode and see the row drop to amber (or red during an outage). if ( $settings->is_api_key_new_validated_value( $new_key, $is_sandbox ) ) { wp_send_json_success( $settings->build_new_key_status( $new_key, $original, true, $is_sandbox, true ) ); } if ( ! $this->within_rate_limit() ) { wp_send_json_error( array( 'message' => __( 'Too many checks in a short time. Please wait a moment and try again.', 'postnl-for-woocommerce' ) ), 429 ); } $result = Key_Validator::validate( $new_key, $customer_code, $customer_num, $is_sandbox ); $valid = ( true === $result ); $reason = $valid ? Key_Validator::REASON_VALID : $result->get_error_code(); wp_send_json_success( $settings->build_new_key_status( $new_key, $original, $valid, $is_sandbox, false, $reason ) ); } /** * Whether the current user is under the per-window check limit. Increments the * counter as a side effect when it is. * * @return bool */ protected function within_rate_limit() { $transient = 'postnl_key_check_' . get_current_user_id(); $count = (int) get_transient( $transient ); if ( $count >= self::RATE_LIMIT_MAX ) { return false; } set_transient( $transient, $count + 1, self::RATE_LIMIT_WINDOW ); return true; } }