'test-key', 'admin-sms.cache_store' => 'array', ]); Cache::store('array')->flush(); } public function test_challenge_is_six_digits_hashed_and_single_use(): void { $service = app(AdminSmsChallengeService::class); $sentCode = null; $challengeId = $service->issueChallenge('13800138000', '127.0.0.1', function ($code) use (&$sentCode) { $sentCode = $code; return true; }); $this->assertNotNull($challengeId); $this->assertMatchesRegularExpression('/^\d{6}$/', $sentCode); $stored = Cache::store('array')->get($service->challengeKey($challengeId)); $this->assertArrayNotHasKey('code', $stored); $this->assertNotSame($sentCode, $stored['code_hash']); $this->assertTrue(Hash::check($sentCode, $stored['code_hash'])); $admin = new Admin(); $admin->forceFill(['id' => 7, 'mobile' => '13800138000']); $success = $service->verify($challengeId, '13800138000', $sentCode, '127.0.0.1', $admin); $replay = $service->verify($challengeId, '13800138000', $sentCode, '127.0.0.1', $admin); $this->assertTrue($success['ok']); $this->assertFalse($replay['ok']); } public function test_fifth_failed_attempt_locks_and_invalidates_challenge(): void { $service = app(AdminSmsChallengeService::class); $challengeId = $service->issueChallenge('13800138000', '127.0.0.1', fn () => true); $admin = new Admin(); $admin->forceFill(['id' => 7, 'mobile' => '13800138000']); $result = null; for ($attempt = 0; $attempt < 5; $attempt++) { $result = $service->verify($challengeId, '13800138000', '000000', '127.0.0.1', $admin); } $this->assertFalse($result['ok']); $this->assertTrue($result['locked']); $this->assertNull(Cache::store('array')->get($service->challengeKey($challengeId))); } public function test_phone_send_limit_allows_one_request_per_minute(): void { $service = app(AdminSmsChallengeService::class); $this->assertTrue($service->canSend('13800138000')); $this->assertFalse($service->canSend('13800138000')); } public function test_ip_limit_applies_across_multiple_mobile_numbers(): void { config([ 'admin-sms.ip_max_failures' => 2, 'admin-sms.phone_max_failures' => 10, ]); $service = app(AdminSmsChallengeService::class); $admin = new Admin(); $admin->forceFill(['id' => 7, 'mobile' => '13800138000']); foreach (['13800138000', '13900139000'] as $mobile) { $challengeId = $service->issueChallenge($mobile, '127.0.0.1', fn () => true); $result = $service->verify($challengeId, $mobile, '000000', '127.0.0.1', $mobile === $admin->mobile ? $admin : null); $this->assertFalse($result['ok']); } $third = $service->issueChallenge('13700137000', '127.0.0.1', fn () => true); $result = $service->verify($third, '13700137000', '000000', '127.0.0.1', null); $this->assertTrue($result['locked']); } public function test_account_lock_applies_across_challenges(): void { config([ 'admin-sms.account_max_failures' => 2, 'admin-sms.phone_max_failures' => 10, 'admin-sms.ip_max_failures' => 30, ]); $service = app(AdminSmsChallengeService::class); $admin = new Admin(); $admin->forceFill(['id' => 8, 'mobile' => '13800138000']); for ($attempt = 0; $attempt < 2; $attempt++) { $challengeId = $service->issueChallenge('13800138000', '127.0.0.' . ($attempt + 1), fn () => true); $result = $service->verify($challengeId, '13800138000', '000000', '127.0.0.' . ($attempt + 1), $admin); } $this->assertTrue($result['locked']); $third = $service->issueChallenge('13800138000', '127.0.0.3', fn () => true); $result = $service->verify($third, '13800138000', '000000', '127.0.0.3', $admin); $this->assertTrue($result['locked']); } public function test_production_sms_sending_is_restricted_to_configured_ips(): void { config([ 'admin-sms.allowed_ips' => ['10.0.0.8'], ]); $this->app->instance('env', 'production'); $service = app(AdminSmsChallengeService::class); $this->assertFalse($service->canSend('13800138000', '10.0.0.9')); $this->assertTrue($service->canSend('13800138000', '10.0.0.8')); config(['admin-sms.allowed_ips' => []]); $this->assertTrue($service->canSend('13900139000', '10.0.0.9')); $this->app->instance('env', 'testing'); } }