from('/register')->post('/register', [ 'email' => 'bot1@example.com', 'website' => 'https://spam.example', ]); $response->assertRedirect('/register'); $response->assertSessionHasErrors('website'); $this->assertDatabaseMissing('users', ['email' => 'bot1@example.com']); }); it('throttles excessive registration attempts by ip', function () { Queue::fake(); config()->set('registration.ip_per_minute_limit', 2); config()->set('registration.ip_per_day_limit', 100); for ($i = 0; $i < 2; $i++) { $this->post('/register', [ 'email' => 'user-rate-' . $i . '@example.com', ])->assertRedirect('/register/notice'); } $this->post('/register', [ 'email' => 'user-rate-3@example.com', ])->assertStatus(429); RateLimiter::clear('register:ip:127.0.0.1'); RateLimiter::clear('register:ip:daily:127.0.0.1'); }); it('blocks disposable email domains during registration', function () { Queue::fake(); config()->set('registration.disposable_domains_enabled', true); config()->set('disposable_email_domains.domains', ['tempmail.com']); $response = $this->from('/register')->post('/register', [ 'email' => 'bot@tempmail.com', ]); $response->assertRedirect('/register'); $response->assertSessionHasErrors('email'); $this->assertDatabaseMissing('users', ['email' => 'bot@tempmail.com']); }); it('requires turnstile after suspicious registration attempts', function () { Queue::fake(); config()->set('registration.enable_turnstile', true); config()->set('registration.turnstile_suspicious_attempts', 1); config()->set('services.turnstile.site_key', 'site-key'); config()->set('services.turnstile.secret_key', 'secret-key'); $mock = \Mockery::mock(TurnstileVerifier::class); $mock->shouldReceive('isEnabled')->andReturn(true); $mock->shouldReceive('verify')->once()->andReturn(false); $this->app->instance(TurnstileVerifier::class, $mock); $response = $this->from('/register')->post('/register', [ 'email' => 'captcha-user@example.com', ]); $response->assertRedirect('/register'); $response->assertSessionHasErrors('captcha'); $this->assertDatabaseMissing('users', ['email' => 'captcha-user@example.com']); }); it('shows turnstile when ip is in rate-limited state', function () { config()->set('registration.enable_turnstile', true); config()->set('registration.ip_per_minute_limit', 1); config()->set('services.turnstile.site_key', 'site-key'); config()->set('services.turnstile.secret_key', 'secret-key'); RateLimiter::hit('register:ip:127.0.0.1', 60); $this->get('/register') ->assertOk() ->assertSee('cf-turnstile', false); RateLimiter::clear('register:ip:127.0.0.1'); }); it('enforces verification email cooldown per address', function () { Queue::fake(); $this->post('/register', [ 'email' => 'cooldown2@example.com', ])->assertRedirect('/register/notice'); $response = $this->post('/register', [ 'email' => 'cooldown2@example.com', ]); $response->assertRedirect('/register/notice'); $response->assertSessionHas('status', 'If that email is valid, we sent a verification link.'); Queue::assertPushed(SendVerificationEmailJob::class, 1); }); it('returns generic success for existing verified emails (anti-enumeration)', function () { Queue::fake(); User::factory()->create([ 'email' => 'existing@example.com', 'email_verified_at' => now(), 'onboarding_step' => 'complete', 'is_active' => true, ]); $response = $this->post('/register', [ 'email' => 'existing@example.com', ]); $response->assertRedirect('/register/notice'); $response->assertSessionHas('status', 'If that email is valid, we sent a verification link.'); Queue::assertNothingPushed(); }); it('still allows registration when turnstile passes', function () { Queue::fake(); config()->set('registration.enable_turnstile', true); config()->set('registration.turnstile_suspicious_attempts', 1); config()->set('services.turnstile.site_key', 'site-key'); config()->set('services.turnstile.secret_key', 'secret-key'); $mock = \Mockery::mock(TurnstileVerifier::class); $mock->shouldReceive('isEnabled')->andReturn(true); $mock->shouldReceive('verify')->once()->andReturn(false); $mock->shouldReceive('verify')->once()->andReturn(true); $this->app->instance(TurnstileVerifier::class, $mock); $first = $this->from('/register')->post('/register', [ 'email' => 'captcha-block@example.com', ]); $first->assertRedirect('/register'); $first->assertSessionHasErrors('captcha'); $response = $this->post('/register', [ 'email' => 'captcha-pass@example.com', 'cf-turnstile-response' => 'good-token', ]); $response->assertRedirect('/register/notice'); $this->assertDatabaseHas('users', ['email' => 'captcha-pass@example.com']); });