diff --git a/src/.env.example b/src/.env.example --- a/src/.env.example +++ b/src/.env.example @@ -16,6 +16,9 @@ APP_WITH_RESELLER=1 APP_WITH_SERVICES=1 +SIGNUP_LIMIT_EMAIL=0 +SIGNUP_LIMIT_IP=0 + ASSET_URL=http://127.0.0.1:8000 WEBMAIL_URL=/apps diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php --- a/src/app/Http/Controllers/API/SignupController.php +++ b/src/app/Http/Controllers/API/SignupController.php @@ -8,7 +8,7 @@ use App\Discount; use App\Domain; use App\Plan; -use App\Rules\ExternalEmail; +use App\Rules\SignupExternalEmail; use App\Rules\UserEmailDomain; use App\Rules\UserEmailLocal; use App\SignupCode; @@ -375,7 +375,7 @@ $v = Validator::make( ['email' => $input], - ['email' => ['required', 'string', new ExternalEmail()]] + ['email' => ['required', 'string', new SignupExternalEmail()]] ); if ($v->fails()) { diff --git a/src/app/Rules/ExternalEmail.php b/src/app/Rules/ExternalEmail.php --- a/src/app/Rules/ExternalEmail.php +++ b/src/app/Rules/ExternalEmail.php @@ -7,7 +7,7 @@ class ExternalEmail implements Rule { - private $message; + protected $message; /** * Determine if the validation rule passes. diff --git a/src/app/Rules/SignupExternalEmail.php b/src/app/Rules/SignupExternalEmail.php new file mode 100644 --- /dev/null +++ b/src/app/Rules/SignupExternalEmail.php @@ -0,0 +1,50 @@ + 191) { + $this->message = \trans('validation.emailinvalid'); + return false; + } + + // Don't allow multiple open registrations against the same email address + if (($limit = \config('app.signup.email_limit')) > 0) { + $signups = SignupCode::where('email', $email) + ->whereDate('expires_at', '>', \Carbon\Carbon::now()); + + if ($signups->count() >= $limit) { + // @kanarip: this is deliberately an "email invalid" message + $this->message = \trans('validation.emailinvalid'); + return false; + } + } + + // Don't allow multiple open registrations against the same source ip address + if (($limit = \config('app.signup.ip_limit')) > 0) { + $signups = SignupCode::where('ip_address', request()->ip()) + ->whereDate('expires_at', '>', \Carbon\Carbon::now()); + + if ($signups->count() >= $limit) { + // @kanarip: this is deliberately an "email invalid" message + $this->message = \trans('validation.emailinvalid'); + return false; + } + } + + return true; + } +} diff --git a/src/config/app.php b/src/config/app.php --- a/src/config/app.php +++ b/src/config/app.php @@ -288,4 +288,9 @@ 'with_admin' => (bool) env('APP_WITH_ADMIN', false), 'with_reseller' => (bool) env('APP_WITH_RESELLER', false), 'with_services' => (bool) env('APP_WITH_SERVICES', false), + + 'signup' => [ + 'email_limit' => (int) env('SIGNUP_LIMIT_EMAIL', 0), + 'ip_limit' => (int) env('SIGNUP_LIMIT_IP', 0), + ], ]; diff --git a/src/tests/Feature/Controller/SignupTest.php b/src/tests/Feature/Controller/SignupTest.php --- a/src/tests/Feature/Controller/SignupTest.php +++ b/src/tests/Feature/Controller/SignupTest.php @@ -190,6 +190,76 @@ $this->assertCount(1, $json['errors']); $this->assertArrayHasKey('voucher', $json['errors']); + // Email address too long + $data = [ + 'email' => str_repeat('a', 190) . '@example.org', + 'first_name' => 'Signup', + 'last_name' => 'User', + ]; + + $response = $this->post('/api/auth/signup/init', $data); + $json = $response->json(); + + $response->assertStatus(422); + + $this->assertSame('error', $json['status']); + $this->assertCount(1, $json['errors']); + $this->assertSame("The specified email address is invalid.", $json['errors']['email']); + + SignupCode::truncate(); + + // Email address limit check + $data = [ + 'email' => 'test@example.org', + 'first_name' => 'Signup', + 'last_name' => 'User', + ]; + + \config(['app.signup.email_limit' => 0]); + + $response = $this->post('/api/auth/signup/init', $data); + $json = $response->json(); + + $response->assertStatus(200); + + \config(['app.signup.email_limit' => 1]); + + $response = $this->post('/api/auth/signup/init', $data); + $json = $response->json(); + + $response->assertStatus(422); + $this->assertSame('error', $json['status']); + $this->assertCount(1, $json['errors']); + // TODO: This probably should be a different message? + $this->assertSame("The specified email address is invalid.", $json['errors']['email']); + + // IP address limit check + $data = [ + 'email' => 'ip@example.org', + 'first_name' => 'Signup', + 'last_name' => 'User', + ]; + + \config(['app.signup.email_limit' => 0]); + \config(['app.signup.ip_limit' => 0]); + + $response = $this->post('/api/auth/signup/init', $data, ['REMOTE_ADDR' => '10.1.1.1']); + $json = $response->json(); + + $response->assertStatus(200); + + \config(['app.signup.ip_limit' => 1]); + + $response = $this->post('/api/auth/signup/init', $data, ['REMOTE_ADDR' => '10.1.1.1']); + $json = $response->json(); + + $response->assertStatus(422); + + $this->assertSame('error', $json['status']); + $this->assertCount(1, $json['errors']); + // TODO: This probably should be a different message? + $this->assertSame("The specified email address is invalid.", $json['errors']['email']); + // TODO: Test phone validation }