- 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>
73 lines
2.5 KiB
PHP
73 lines
2.5 KiB
PHP
<?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();
|
|
}
|
|
}
|