user_email; $credential = array( 'email' => $user_email, 'plugin_id' => RWPAI_PLUGIN_ID, 'website_url' => esc_url_raw( get_bloginfo( 'url' ) ), 'version' => RWPAI_VERSION, ); return $credential; } /** * Verifies the request using a nonce for REST endpoints on non-localhost sites. * * DEV_SEC_NONCE * * @param WP_REST_Request $request The current REST request. (Currently unused). * @param string $nonce_name The nonce action string. Default is RWPAI_NONCE_NAME_ADMIN. (Currently unused). * * @return true|WP_Error True if valid; WP_Error on failure. */ function rwpai_verify_request_using_nonce_rest( $request, $nonce_name = RWPAI_NONCE_NAME_ADMIN ) { $nonce = $request->get_param( 'nonce' ); $nonce = sanitize_text_field( wp_unslash( $nonce ) ); if ( empty( $nonce ) ) { return new WP_Error( 'missing_nonce', 'Missing nonce token. Please refresh page!', array( 'status' => 401 ) ); } // Verify nonce (your custom logic). if ( ! rwpai_nonce_verify( $nonce, $nonce_name ) ) { return new WP_Error( 'invalid_or_expired_nonce', 'Invalid or expired authentication token. Please refresh page!', array( 'status' => 401 ) ); } return true; } /** * Generate a signed nonce token for a specific action. * * This function creates a short-lived, HMAC-signed token that can be used * to verify the authenticity of requests (similar to WordPress nonces). * * @since 2.0.4 * * @param string $action Action name to bind the token to (e.g. 'delete_post', 'save_settings'). * @return string Nonce token string (base64 payload + HMAC signature). * * @example * $token = rwpai_nonce_create('save_settings'); * // Example output: eyJhY3Rpb24iOiJzYXZlX3NldHRpbmdzIiwidGltZSI6MTY5ODQ0OTU1MH0=.2b1f81... */ function rwpai_nonce_create( $action ) { $data = array( 'action' => $action, 'time' => time(), ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Used for safe token encoding, not obfuscation. $payload = base64_encode( wp_json_encode( $data ) ); $signature = hash_hmac( 'sha256', $payload, AUTH_SALT ); return $payload . '.' . $signature; } /** * Verify a nonce token for a specific action. * * This checks that: * - The token signature matches (using AUTH_SALT). * - The token action matches the expected action. * - The token has not expired (1 hour TTL). * * @since 2.0.4 * * @param string $token The nonce token to verify. * @param string $action The expected action name. * @return bool True if the token is valid; false if invalid or expired. * * @example * if (rwpai_nonce_verify($_POST['token'], 'save_settings')) { * // Proceed with action. * } else { * wp_die('Invalid or expired nonce.'); * } */ function rwpai_nonce_verify( $token, $action ) { list($payload, $signature) = explode( '.', $token, 2 ); $expected = hash_hmac( 'sha256', $payload, AUTH_SALT ); if ( ! hash_equals( $expected ?? '', $signature ?? '' ) ) { return false; } // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Used for secure token verification, not code obfuscation. $data = json_decode( base64_decode( $payload ), true ); if ( $data['action'] !== $action ) { return false; } if ( time() - $data['time'] > RWPAI_NONCE_TTL ) { return false; // expired. } return true; } /** * Build a rate-limit identifier using the logged-in user or the request IP. * * @return string */ function rwpai_get_rate_limit_identifier() { if ( is_user_logged_in() ) { return 'user_' . get_current_user_id(); } // Sanitize after unslash. $ip_address = sanitize_text_field( isset( $_SERVER['REMOTE_ADDR'] ) ? wp_unslash( $_SERVER['REMOTE_ADDR'] ) : '' ); if ( empty( $ip_address ) ) { $ip_address = 'unknown'; } return 'ip_' . $ip_address; } /** * Enforce a simple throttle using WordPress transients. * * @param string $action Unique action key (e.g. 'tts_make'). * @param int $limit Maximum requests allowed within the window. * @param int $window Window length in seconds. * * @return true|WP_Error */ function rwpai_rate_limit_check( $action, $limit = 60, $window = MINUTE_IN_SECONDS ) { $identifier = rwpai_get_rate_limit_identifier(); $transient_id = 'rwpai_ratelimit_' . $action . '_' . md5( $identifier ); $data = get_transient( $transient_id ); if ( false === $data || ! is_array( $data ) ) { $data = array( 'count' => 0, ); } ++$data['count']; if ( $data['count'] > $limit ) { return new WP_Error( 'rwpai_rate_limited', esc_html__( 'Too many requests. Please wait a moment!', 'ai-agent' ), array( 'status' => 429, ) ); } set_transient( $transient_id, $data, $window ); return true; }