feat: Turnstile captcha, menu khách + nút tắt topbar cấu hình từ admin, ẩn setting nhạy cảm
- Turnstile (App\Support\Turnstile, middleware turnstile) cho login admin và login/register/forgot/reset của khách; cấu hình ở setting key=turnstile, secret mã hoá SignRSA. - Menu sidebar khách (setting client_menu) và nút tắt topbar (setting client_quick_links) do admin sửa qua administrator/client-menu[/quick-links], khách đọc v1/setting/menu, v1/setting/quick-links. - v1/setting và settings/public chỉ trả whitelist PublicSetting::FIELDS (trước đây lộ bot_token, inet_api_key). - Cài đặt tài khoản khách (preferences), rate limit theo IP cho register/resend-verify. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
823af4a56a
commit
e888a6ed5d
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Support;
|
||||
|
||||
use App\Http\Facade\SignRSA;
|
||||
use App\Models\Setting;
|
||||
use App\Support\Turnstile;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use InvalidArgumentException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TurnstileTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
private function configure(bool $enabled): void
|
||||
{
|
||||
Setting::updateOrCreate(['key' => Turnstile::SETTING_KEY], ['value' => json_encode([
|
||||
'enabled' => $enabled,
|
||||
'site_key' => 'site',
|
||||
'secret_key' => SignRSA::EncryptFunc('secret'),
|
||||
])]);
|
||||
}
|
||||
|
||||
public function test_disabled_passes_without_token(): void
|
||||
{
|
||||
$this->configure(false);
|
||||
Http::fake();
|
||||
|
||||
$this->assertNull(Turnstile::siteKey());
|
||||
$this->assertTrue(Turnstile::verify(null, null));
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_enabled_requires_valid_token(): void
|
||||
{
|
||||
$this->configure(true);
|
||||
Http::fake(['challenges.cloudflare.com/*' => Http::sequence()
|
||||
->push(['success' => true])
|
||||
->push(['success' => false, 'error-codes' => ['timeout-or-duplicate']])]);
|
||||
|
||||
$this->assertSame('site', Turnstile::siteKey());
|
||||
$this->assertFalse(Turnstile::verify('', '1.1.1.1'));
|
||||
$this->assertTrue(Turnstile::verify('ok', '1.1.1.1'));
|
||||
$this->assertFalse(Turnstile::verify('reused', '1.1.1.1'));
|
||||
Http::assertSent(fn ($request) => $request['secret'] === 'secret' && $request['response'] === 'ok');
|
||||
}
|
||||
|
||||
public function test_save_keeps_masked_secret_and_rejects_enabling_without_keys(): void
|
||||
{
|
||||
$this->configure(false);
|
||||
|
||||
Turnstile::save(['enabled' => true, 'site_key' => 'new-site', 'secret_key' => Turnstile::SECRET_MASK]);
|
||||
$this->assertSame('secret', SignRSA::DecryptFunc(Turnstile::stored()['secret_key']));
|
||||
$this->assertSame(['enabled' => true, 'site_key' => 'new-site', 'secret_key' => Turnstile::SECRET_MASK], Turnstile::forAdmin());
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
Turnstile::save(['secret_key' => '']);
|
||||
}
|
||||
|
||||
public function test_admin_login_rejects_missing_token_when_enabled(): void
|
||||
{
|
||||
$this->configure(true);
|
||||
Http::fake();
|
||||
|
||||
$this->postJson('/api/v1/administrator/login', ['username' => 'x', 'password' => 'y'])
|
||||
->assertStatus(422)
|
||||
->assertJsonPath('error', true);
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user