Skip to content

Commit 5eac414

Browse files
Add auth key and server-side testing to WP plugin
- Added an optional `Auth Key` field to the LibreCaptcha plugin settings. - Modified CAPTCHA validation API requests (`wp_remote_post` to `/v2/answer`) to pass the `Auth` header if configured. - Refactored the test connection functionality in the settings page to perform all LibreCaptcha API calls server-side (via WP AJAX hooks `lc_test_load` and `lc_test_check`) using the admin's currently configured settings (including auth key). Co-authored-by: hrj <345879+hrj@users.noreply.github.com>
1 parent 98289e9 commit 5eac414

1 file changed

Lines changed: 127 additions & 55 deletions

File tree

plugins/wordpress/librecaptcha.php

Lines changed: 127 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ public function __construct() {
3030
// Register shortcode
3131
add_shortcode( 'librecaptcha', array( $this, 'render_captcha_shortcode' ) );
3232

33+
// AJAX actions for testing settings
34+
add_action( 'wp_ajax_lc_test_load', array( $this, 'ajax_test_load' ) );
35+
add_action( 'wp_ajax_lc_test_check', array( $this, 'ajax_test_check' ) );
36+
3337
// Verification hooks
3438
if ( get_option( 'lc_enable_login', 0 ) ) {
3539
add_filter( 'authenticate', array( $this, 'verify_login_captcha' ), 20, 3 );
@@ -56,9 +60,15 @@ private function verify_captcha() {
5660
$captcha_answer = sanitize_text_field( $_POST['lc_captcha_answer'] );
5761

5862
$server_url = rtrim( $server_url, '/' );
63+
$auth_key = get_option( 'lc_auth_key', '' );
64+
65+
$headers = array( 'Content-Type' => 'application/json' );
66+
if ( ! empty( $auth_key ) ) {
67+
$headers['Auth'] = $auth_key;
68+
}
5969

6070
$response = wp_remote_post( $server_url . '/v2/answer', array(
61-
'headers' => array( 'Content-Type' => 'application/json' ),
71+
'headers' => $headers,
6272
'body' => wp_json_encode( array(
6373
'id' => $captcha_id,
6474
'answer' => $captcha_answer,
@@ -110,6 +120,86 @@ public function verify_comment_captcha( $comment_post_id ) {
110120
}
111121
}
112122

123+
public function ajax_test_load() {
124+
if ( ! current_user_can( 'manage_options' ) ) {
125+
wp_send_json_error( 'Unauthorized' );
126+
}
127+
128+
$server_url = rtrim( get_option( 'lc_server_url', '' ), '/' );
129+
$auth_key = get_option( 'lc_auth_key', '' );
130+
$config_json_string = get_option( 'lc_config_json', '{"level":"easy","media":"image/png","input_type":"text","size":"350x100"}' );
131+
132+
if ( empty( $server_url ) ) {
133+
wp_send_json_error( 'Server URL is not configured.' );
134+
}
135+
136+
$headers = array( 'Content-Type' => 'application/json' );
137+
if ( ! empty( $auth_key ) ) {
138+
$headers['Auth'] = $auth_key;
139+
}
140+
141+
$response = wp_remote_post( $server_url . '/v2/captcha', array(
142+
'headers' => $headers,
143+
'body' => $config_json_string,
144+
'method' => 'POST',
145+
'data_format' => 'body',
146+
) );
147+
148+
if ( is_wp_error( $response ) ) {
149+
wp_send_json_error( 'Failed to connect to LibreCaptcha server.' );
150+
}
151+
152+
$body = wp_remote_retrieve_body( $response );
153+
$data = json_decode( $body, true );
154+
155+
if ( isset( $data['id'] ) ) {
156+
// Also return the full server URL so the client can load the image
157+
wp_send_json_success( array( 'id' => $data['id'], 'server_url' => $server_url ) );
158+
} else {
159+
wp_send_json_error( 'Invalid response from LibreCaptcha server.' );
160+
}
161+
}
162+
163+
public function ajax_test_check() {
164+
if ( ! current_user_can( 'manage_options' ) ) {
165+
wp_send_json_error( 'Unauthorized' );
166+
}
167+
168+
$captcha_id = sanitize_text_field( $_POST['captcha_id'] ?? '' );
169+
$captcha_answer = sanitize_text_field( $_POST['captcha_answer'] ?? '' );
170+
171+
if ( empty( $captcha_id ) || empty( $captcha_answer ) ) {
172+
wp_send_json_error( 'Missing ID or Answer.' );
173+
}
174+
175+
$server_url = rtrim( get_option( 'lc_server_url', '' ), '/' );
176+
$auth_key = get_option( 'lc_auth_key', '' );
177+
178+
$headers = array( 'Content-Type' => 'application/json' );
179+
if ( ! empty( $auth_key ) ) {
180+
$headers['Auth'] = $auth_key;
181+
}
182+
183+
$response = wp_remote_post( $server_url . '/v2/answer', array(
184+
'headers' => $headers,
185+
'body' => wp_json_encode( array(
186+
'id' => $captcha_id,
187+
'answer' => $captcha_answer,
188+
) ),
189+
'method' => 'POST',
190+
'data_format' => 'body',
191+
) );
192+
193+
if ( is_wp_error( $response ) ) {
194+
wp_send_json_error( 'Failed to connect to LibreCaptcha server.' );
195+
}
196+
197+
$body = wp_remote_retrieve_body( $response );
198+
$data = json_decode( $body, true );
199+
200+
wp_send_json_success( $data );
201+
}
202+
113203
public function render_captcha_shortcode() {
114204
ob_start();
115205
$this->render_captcha();
@@ -182,6 +272,7 @@ public function add_admin_menu() {
182272

183273
public function register_settings() {
184274
register_setting( 'librecaptcha_options_group', 'lc_server_url' );
275+
register_setting( 'librecaptcha_options_group', 'lc_auth_key' );
185276
register_setting( 'librecaptcha_options_group', 'lc_config_json' );
186277
register_setting( 'librecaptcha_options_group', 'lc_enable_login' );
187278
register_setting( 'librecaptcha_options_group', 'lc_enable_registration' );
@@ -203,6 +294,13 @@ public function settings_page() {
203294
<p class="description">The URL to your LibreCaptcha instance (e.g. http://localhost:8888). Leave empty to disable.</p>
204295
</td>
205296
</tr>
297+
<tr valign="top">
298+
<th scope="row">Auth Key (Optional)</th>
299+
<td>
300+
<input type="text" name="lc_auth_key" value="<?php echo esc_attr( get_option('lc_auth_key', '') ); ?>" class="regular-text" placeholder="Secret Key" />
301+
<p class="description">Optional auth key if your server requires it.</p>
302+
</td>
303+
</tr>
206304
<tr valign="top">
207305
<th scope="row">Config JSON</th>
208306
<td>
@@ -258,58 +356,29 @@ public function settings_page() {
258356
var idInput = document.getElementById('lc-test-captcha-id');
259357
var answerInput = document.getElementById('lc-test-captcha-answer');
260358

261-
function getServerUrl() {
262-
return document.querySelector('input[name="lc_server_url"]').value.replace(/\/$/, '');
263-
}
264-
265-
function getConfigJson() {
266-
try {
267-
return JSON.parse(document.querySelector('textarea[name="lc_config_json"]').value);
268-
} catch (e) {
269-
return null;
270-
}
271-
}
272-
273359
loadBtn.addEventListener('click', function() {
274-
var serverUrl = getServerUrl();
275-
var configJson = getConfigJson();
276-
277-
if (!serverUrl) {
278-
statusEl.innerText = 'Error: Server URL is empty.';
279-
statusEl.style.color = 'red';
280-
return;
281-
}
282-
283-
if (!configJson) {
284-
statusEl.innerText = 'Error: Invalid Config JSON.';
285-
statusEl.style.color = 'red';
286-
return;
287-
}
288-
289360
statusEl.innerText = 'Loading CAPTCHA...';
290361
statusEl.style.color = '#0073aa';
291362
captchaArea.style.display = 'none';
292363
imageContainer.innerHTML = '';
293364
answerInput.value = '';
294365

295-
fetch(serverUrl + '/v2/captcha', {
366+
var formData = new FormData();
367+
formData.append('action', 'lc_test_load');
368+
369+
fetch(ajaxurl, {
296370
method: 'POST',
297-
headers: {
298-
'Content-Type': 'application/json'
299-
},
300-
body: JSON.stringify(configJson)
371+
body: formData
301372
})
302373
.then(function(response) {
303-
if (!response.ok) {
304-
throw new Error('Server responded with ' + response.status);
305-
}
306374
return response.json();
307375
})
308-
.then(function(data) {
309-
if (data && data.id) {
376+
.then(function(responseJson) {
377+
if (responseJson.success && responseJson.data.id) {
378+
var data = responseJson.data;
310379
idInput.value = data.id;
311380
var img = document.createElement('img');
312-
img.src = serverUrl + '/v1/media?id=' + data.id;
381+
img.src = data.server_url + '/v1/media?id=' + data.id;
313382
img.alt = 'Test CAPTCHA';
314383
img.style.maxWidth = '100%';
315384
img.onload = function() {
@@ -323,7 +392,7 @@ function getConfigJson() {
323392
};
324393
imageContainer.appendChild(img);
325394
} else {
326-
throw new Error('Invalid response format');
395+
throw new Error(responseJson.data || 'Invalid response format');
327396
}
328397
})
329398
.catch(function(error) {
@@ -334,7 +403,6 @@ function getConfigJson() {
334403
});
335404

336405
checkBtn.addEventListener('click', function() {
337-
var serverUrl = getServerUrl();
338406
var captchaId = idInput.value;
339407
var answer = answerInput.value;
340408

@@ -346,26 +414,30 @@ function getConfigJson() {
346414
statusEl.innerText = 'Checking answer...';
347415
statusEl.style.color = '#0073aa';
348416

349-
fetch(serverUrl + '/v2/answer', {
417+
var formData = new FormData();
418+
formData.append('action', 'lc_test_check');
419+
formData.append('captcha_id', captchaId);
420+
formData.append('captcha_answer', answer);
421+
422+
fetch(ajaxurl, {
350423
method: 'POST',
351-
headers: {
352-
'Content-Type': 'application/json'
353-
},
354-
body: JSON.stringify({ id: captchaId, answer: answer })
424+
body: formData
355425
})
356426
.then(function(response) {
357-
if (!response.ok) {
358-
throw new Error('Server responded with ' + response.status);
359-
}
360427
return response.json();
361428
})
362-
.then(function(data) {
363-
if (data && (data.result === 'True' || data.result === true)) {
364-
statusEl.innerText = 'Success! Answer is correct.';
365-
statusEl.style.color = 'green';
429+
.then(function(responseJson) {
430+
if (responseJson.success) {
431+
var data = responseJson.data;
432+
if (data && (data.result === 'True' || data.result === true)) {
433+
statusEl.innerText = 'Success! Answer is correct.';
434+
statusEl.style.color = 'green';
435+
} else {
436+
statusEl.innerText = 'Incorrect answer or expired.';
437+
statusEl.style.color = 'red';
438+
}
366439
} else {
367-
statusEl.innerText = 'Incorrect answer or expired.';
368-
statusEl.style.color = 'red';
440+
throw new Error(responseJson.data || 'Failed to verify');
369441
}
370442
})
371443
.catch(function(error) {

0 commit comments

Comments
 (0)