Add invite-only registration mode

Support configurable on, invite-only, and off registration modes, including case-insensitive invitation checks and test coverage.
This commit is contained in:
Constantin Graf
2026-08-25 16:38:07 +02:00
parent b2849ec04a
commit 303de4930d
7 changed files with 157 additions and 3 deletions

View File

@@ -4,9 +4,11 @@ declare(strict_types=1);
namespace App\Actions\Fortify;
use App\Enums\RegistrationMode;
use App\Enums\Weekday;
use App\Events\NewsletterRegistered;
use App\Models\User;
use App\Service\InvitationService;
use App\Service\IpLookup\IpLookupServiceContract;
use App\Service\TimezoneService;
use App\Service\UserService;
@@ -31,13 +33,14 @@ class CreateNewUser implements CreatesNewUsers
*/
public function create(array $input): User
{
if (! config('app.enable_registration')) {
$registrationMode = RegistrationMode::fromConfig(config('app.enable_registration'));
if ($registrationMode === RegistrationMode::Off) {
throw ValidationException::withMessages([
'email' => [__('Registration is disabled.')],
]);
}
Validator::make($input, [
$validated = Validator::make($input, [
'name' => [
'required',
'string',
@@ -60,6 +63,13 @@ class CreateNewUser implements CreatesNewUsers
],
])->validate();
if ($registrationMode === RegistrationMode::InviteOnly
&& ! app(InvitationService::class)->hasInvitationForEmail((string) $validated['email'])) {
throw ValidationException::withMessages([
'email' => [__('Registration is only available to invited users.')],
]);
}
$timezone = null;
if (array_key_exists('timezone', $input) && is_string($input['timezone'])) {
if (app(TimezoneService::class)->isValid($input['timezone'])) {

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Enums;
enum RegistrationMode: string
{
case On = 'on';
case InviteOnly = 'invite-only';
case Off = 'off';
public static function fromConfig(mixed $value): self
{
if ($value === true) {
return self::On;
}
if ($value === false || $value === null) {
return self::Off;
}
return match (strtolower(trim((string) $value))) {
'1', 'on', 'true' => self::On,
'invite', 'invite-only' => self::InviteOnly,
default => self::Off,
};
}
}

View File

@@ -18,6 +18,13 @@ use Illuminate\Support\Facades\Mail;
class InvitationService
{
public function hasInvitationForEmail(string $email): bool
{
return OrganizationInvitation::query()
->whereRaw('lower(email) = ?', [strtolower($email)])
->exists();
}
/**
* @throws UserIsAlreadyMemberOfOrganizationApiException|InvitationForTheEmailAlreadyExistsApiException
*/