pull/7309/head
Your Name 1 week ago
parent d0f056058b
commit edde5b985f

@ -14,8 +14,8 @@ CAPTCHA_ENABLED_ON_PASSWORD_RESET=false
CAPTCHA_ENABLED_ON_CURATED_REGISTER=false
# --- hCaptcha (driver: hcaptcha) ---
CAPTCHA_SECRET=
CAPTCHA_SITEKEY=
CAPTCHA_H_SITEKEY=
CAPTCHA_H_SECRET=
# --- Cloudflare Turnstile (driver: turnstile) ---
CAPTCHA_TURNSTILE_SITEKEY=

@ -718,14 +718,10 @@ trait AdminSettingsController
};
// hCaptcha credentials. Persist to the canonical captcha.hcaptcha.*
// keys and mirror to the top-level keys the buzz/laravel-h-captcha
// package reads internally.
$hcaptchaSecret = $request->input('captcha_hcaptcha_secret');
$hcaptchaSitekey = $request->input('captcha_hcaptcha_sitekey');
$putIfChanged('captcha.hcaptcha.secret', $hcaptchaSecret);
$putIfChanged('captcha.hcaptcha.sitekey', $hcaptchaSitekey);
$putIfChanged('captcha.secret', $hcaptchaSecret);
$putIfChanged('captcha.sitekey', $hcaptchaSitekey);
// keys. CaptchaServiceProvider hydrates the top-level captcha.secret
// / captcha.sitekey that the buzz/laravel-h-captcha package reads.
$putIfChanged('captcha.hcaptcha.secret', $request->input('captcha_hcaptcha_secret'));
$putIfChanged('captcha.hcaptcha.sitekey', $request->input('captcha_hcaptcha_sitekey'));
// Turnstile credentials (sitekey is public, store as-is when present)
$putIfChanged('captcha.turnstile.secret', $request->input('captcha_turnstile_secret'));

@ -17,10 +17,42 @@ class CaptchaServiceProvider extends ServiceProvider
public function boot(): void
{
$this->hydrateHcaptchaConfig();
$this->registerValidationRule();
$this->registerBladeDirectives();
}
/**
* The buzz/laravel-h-captcha package reads its config at the TOP LEVEL of
* the "captcha" config (captcha.secret, captcha.sitekey, captcha.http_client,
* captcha.options, captcha.attributes) via the plain config repository — it
* does not consult the DB-backed config cache.
*
* We keep all hCaptcha config under captcha.hcaptcha.*, so hydrate the
* top-level keys the package expects here. Secret/sitekey use config_cache()
* so admin-panel values (not just .env) are honored; the static widget
* options come straight from the config file.
*/
private function hydrateHcaptchaConfig(): void
{
$secret = config_cache('captcha.hcaptcha.secret');
$sitekey = config_cache('captcha.hcaptcha.sitekey');
if (! empty($secret)) {
config(['captcha.secret' => $secret]);
}
if (! empty($sitekey)) {
config(['captcha.sitekey' => $sitekey]);
}
config([
'captcha.http_client' => config('captcha.hcaptcha.http_client'),
'captcha.options' => config('captcha.hcaptcha.options'),
'captcha.attributes' => config('captcha.hcaptcha.attributes'),
]);
}
/**
* Driver-agnostic validation rule.
*

@ -10,6 +10,7 @@ use LaravelCap\Cap;
* Cap driver (self-hosted proof-of-work CAPTCHA).
*
* Wraps the oliweb/laravel-cap package for verification, and renders the
*
* @cap.js/widget from the jsDelivr CDN.
*
* The full API endpoint the widget and verifier talk to is composed from a base

@ -24,27 +24,24 @@ return [
|--------------------------------------------------------------------------
| hCaptcha
|--------------------------------------------------------------------------
| Canonical, provider-namespaced credentials used throughout the app.
| Canonical, provider-namespaced hCaptcha config used throughout the app.
|
| The buzz/laravel-h-captcha package reads these values at the top level of
| the "captcha" config (captcha.secret, captcha.sitekey, captcha.http_client,
| captcha.options, captcha.attributes). CaptchaServiceProvider hydrates those
| top-level keys from this block at boot, so everything lives here.
*/
'hcaptcha' => [
'secret' => env('CAPTCHA_SECRET', 'default_secret'),
'sitekey' => env('CAPTCHA_SITEKEY', 'default_sitekey'),
],
/*
| The top-level secret/sitekey keys below are what the buzz/laravel-h-captcha
| package reads internally (config('captcha.secret') / config('captcha.sitekey')).
| They mirror captcha.hcaptcha.* and are kept in sync when settings are saved.
*/
'secret' => env('CAPTCHA_SECRET', 'default_secret'),
'sitekey' => env('CAPTCHA_SITEKEY', 'default_sitekey'),
'http_client' => HttpClient::class,
'options' => [
'multiple' => false,
'lang' => app()->getLocale(),
],
'attributes' => [
'theme' => 'light',
'secret' => env('CAPTCHA_H_SECRET', 'default_secret'),
'sitekey' => env('CAPTCHA_H_SITEKEY', 'default_sitekey'),
'http_client' => HttpClient::class,
'options' => [
'multiple' => false,
'lang' => app()->getLocale(),
],
'attributes' => [
'theme' => 'light',
],
],
/*

@ -0,0 +1,78 @@
<?php
namespace Tests\Feature;
use App\Providers\CaptchaServiceProvider;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
/**
* The buzz/laravel-h-captcha package reads config('captcha.secret') and
* config('captcha.sitekey') via the plain config repository (not the DB config
* cache). CaptchaServiceProvider::boot() must hydrate those runtime values from
* the admin-managed captcha.hcaptcha.* keys so admin-panel credentials work.
*
* Note: the test env sets ENABLE_CONFIG_CACHE=false, so config_cache() reads
* straight from config(), which is what we seed here.
*/
class CaptchaHydrationTest extends TestCase
{
private function bootProvider(): void
{
(new CaptchaServiceProvider($this->app))->boot();
}
#[Test]
public function it_hydrates_buzz_config_from_admin_managed_hcaptcha_keys(): void
{
config([
'captcha.hcaptcha.secret' => 'admin-saved-secret',
'captcha.hcaptcha.sitekey' => 'admin-saved-sitekey',
// Stale/.env-only values the buzz package would otherwise read.
'captcha.secret' => 'default_secret',
'captcha.sitekey' => 'default_sitekey',
]);
$this->bootProvider();
$this->assertSame('admin-saved-secret', config('captcha.secret'));
$this->assertSame('admin-saved-sitekey', config('captcha.sitekey'));
}
#[Test]
public function it_hydrates_widget_options_from_the_hcaptcha_block(): void
{
config([
'captcha.hcaptcha.http_client' => 'Some\\Custom\\Client',
'captcha.hcaptcha.options' => ['multiple' => true, 'lang' => 'fr'],
'captcha.hcaptcha.attributes' => ['theme' => 'dark'],
// Ensure the top-level keys start out different.
'captcha.http_client' => null,
'captcha.options' => null,
'captcha.attributes' => null,
]);
$this->bootProvider();
$this->assertSame('Some\\Custom\\Client', config('captcha.http_client'));
$this->assertSame(['multiple' => true, 'lang' => 'fr'], config('captcha.options'));
$this->assertSame(['theme' => 'dark'], config('captcha.attributes'));
}
#[Test]
public function it_leaves_buzz_config_untouched_when_hcaptcha_keys_are_empty(): void
{
config([
'captcha.hcaptcha.secret' => '',
'captcha.hcaptcha.sitekey' => null,
'captcha.secret' => 'env_secret',
'captcha.sitekey' => 'env_sitekey',
]);
$this->bootProvider();
// Nothing to hydrate -> the existing (env/config-file) values remain.
$this->assertSame('env_secret', config('captcha.secret'));
$this->assertSame('env_sitekey', config('captcha.sitekey'));
}
}
Loading…
Cancel
Save