diff --git a/app/Http/Controllers/AdminInviteController.php b/app/Http/Controllers/AdminInviteController.php index 8b25ff9de..ae33aebfd 100644 --- a/app/Http/Controllers/AdminInviteController.php +++ b/app/Http/Controllers/AdminInviteController.php @@ -4,8 +4,8 @@ namespace App\Http\Controllers; use App\Models\AdminInvite; use App\Models\User; +use App\Rules\PixelfedUsername; use App\Services\EmailService; -use App\Util\Lexer\RestrictedNames; use Illuminate\Auth\Events\Registered; use Illuminate\Contracts\View\View; use Illuminate\Http\JsonResponse; @@ -14,7 +14,6 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; -use Illuminate\Support\Str; use Purify; class AdminInviteController extends Controller @@ -69,37 +68,7 @@ class AdminInviteController extends Controller 'min:2', 'max:30', 'unique:users', - function ($attribute, $value, $fail) { - $dash = substr_count($value, '-'); - $underscore = substr_count($value, '_'); - $period = substr_count($value, '.'); - - if (Str::endsWith($value, ['.php', '.js', '.css'])) { - return $fail('Username is invalid.'); - } - - if (($dash + $underscore + $period) > 1) { - return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).'); - } - - if (! ctype_alnum($value[0])) { - return $fail('Username is invalid. Must start with a letter or number.'); - } - - if (! ctype_alnum($value[strlen($value) - 1])) { - return $fail('Username is invalid. Must end with a letter or number.'); - } - - $val = str_replace(['_', '.', '-'], '', $value); - if (! ctype_alnum($val)) { - return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).'); - } - - $restricted = RestrictedNames::get(); - if (in_array(strtolower($value), array_map('strtolower', $restricted))) { - return $fail('Username cannot be used.'); - } - }, + new PixelfedUsername, ]; $rules = ['username' => $usernameRules]; @@ -157,37 +126,7 @@ class AdminInviteController extends Controller 'min:2', 'max:30', 'unique:users', - function ($attribute, $value, $fail) { - $dash = substr_count($value, '-'); - $underscore = substr_count($value, '_'); - $period = substr_count($value, '.'); - - if (Str::endsWith($value, ['.php', '.js', '.css'])) { - return $fail('Username is invalid.'); - } - - if (($dash + $underscore + $period) > 1) { - return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).'); - } - - if (! ctype_alnum($value[0])) { - return $fail('Username is invalid. Must start with a letter or number.'); - } - - if (! ctype_alnum($value[strlen($value) - 1])) { - return $fail('Username is invalid. Must end with a letter or number.'); - } - - $val = str_replace(['_', '.', '-'], '', $value); - if (! ctype_alnum($val)) { - return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).'); - } - - $restricted = RestrictedNames::get(); - if (in_array(strtolower($value), array_map('strtolower', $restricted))) { - return $fail('Username cannot be used.'); - } - }, + new PixelfedUsername, ], 'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'), 'email' => [ diff --git a/app/Http/Controllers/Api/ApiV1Dot1Controller.php b/app/Http/Controllers/Api/ApiV1Dot1Controller.php index ae2ab2c93..fe6e5affa 100644 --- a/app/Http/Controllers/Api/ApiV1Dot1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Dot1Controller.php @@ -26,6 +26,7 @@ use App\Models\Story; use App\Models\User; use App\Models\UserSetting; use App\Rules\ExpoPushTokenRule; +use App\Rules\PixelfedUsername; use App\Services\AccountService; use App\Services\BouncerService; use App\Services\EmailService; @@ -43,7 +44,6 @@ use App\Services\UserAgentService; use App\Services\UserRoleService; use App\Services\UserStorageService; use App\Transformer\Api\AccountTransformer; -use App\Util\Lexer\RestrictedNames; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -547,37 +547,7 @@ class ApiV1Dot1Controller extends Controller 'min:2', 'max:30', 'unique:users', - function ($attribute, $value, $fail) { - $dash = substr_count($value, '-'); - $underscore = substr_count($value, '_'); - $period = substr_count($value, '.'); - - if (Str::endsWith($value, ['.php', '.js', '.css'])) { - return $fail('Username is invalid.'); - } - - if (($dash + $underscore + $period) > 1) { - return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).'); - } - - if (! ctype_alnum($value[0])) { - return $fail('Username is invalid. Must start with a letter or number.'); - } - - if (! ctype_alnum($value[strlen($value) - 1])) { - return $fail('Username is invalid. Must end with a letter or number.'); - } - - $val = str_replace(['_', '.', '-'], '', $value); - if (! ctype_alnum($val)) { - return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).'); - } - - $restricted = RestrictedNames::get(); - if (in_array(strtolower($value), array_map('strtolower', $restricted))) { - return $fail('Username cannot be used.'); - } - }, + new PixelfedUsername, ], 'password' => 'required|string|min:8', ]); diff --git a/app/Http/Controllers/AppRegisterController.php b/app/Http/Controllers/AppRegisterController.php index 60b89ca67..b0f57689c 100644 --- a/app/Http/Controllers/AppRegisterController.php +++ b/app/Http/Controllers/AppRegisterController.php @@ -5,8 +5,8 @@ namespace App\Http\Controllers; use App\Mail\InAppRegisterEmailVerify; use App\Models\AppRegister; use App\Models\User; +use App\Rules\PixelfedUsername; use App\Services\AccountService; -use App\Util\Lexer\RestrictedNames; use Illuminate\Contracts\View\View; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; @@ -303,41 +303,7 @@ class AppRegisterController extends Controller 'min:2', 'max:30', 'unique:users', - function ($attribute, $value, $fail) { - $dash = substr_count($value, '-'); - $underscore = substr_count($value, '_'); - $period = substr_count($value, '.'); - - if (Str::endsWith($value, ['.php', '.js', '.css'])) { - return $fail('Username is invalid.'); - } - - if (($dash + $underscore + $period) > 1) { - return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).'); - } - - if (! ctype_alnum($value[0])) { - return $fail('Username is invalid. Must start with a letter or number.'); - } - - if (! ctype_alnum($value[strlen($value) - 1])) { - return $fail('Username is invalid. Must end with a letter or number.'); - } - - $val = str_replace(['_', '.', '-'], '', $value); - if (! ctype_alnum($val)) { - return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).'); - } - - if (! preg_match('/[a-zA-Z]/', $value)) { - return $fail('Username is invalid. Must contain at least one alphabetical character.'); - } - - $restricted = RestrictedNames::get(); - if (in_array(strtolower($value), array_map('strtolower', $restricted))) { - return $fail('Username cannot be used.'); - } - }, + new PixelfedUsername, ]; } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index daf4fd455..91304849c 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -4,9 +4,9 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use App\Models\User; +use App\Rules\PixelfedUsername; use App\Services\BouncerService; use App\Services\EmailService; -use App\Util\Lexer\RestrictedNames; use Illuminate\Auth\Events\Registered; use Illuminate\Contracts\View\View; use Illuminate\Foundation\Auth\RegistersUsers; @@ -76,44 +76,7 @@ class RegisterController extends Controller 'min:2', 'max:30', 'unique:users', - function ($attribute, $value, $fail) { - $dash = substr_count($value, '-'); - $underscore = substr_count($value, '_'); - $period = substr_count($value, '.'); - - $disallowedExtensions = ['.php', '.js', '.css', '.html', '.htm', '.json', '.xml', '.env', '.sh', '.exe', '.bat']; - foreach ($disallowedExtensions as $ext) { - if (str_ends_with($value, $ext)) { - return $fail('Username is invalid. No common file extensions.'); - } - } - - if (($dash + $underscore + $period) > 1) { - return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).'); - } - - if (! ctype_alnum($value[0])) { - return $fail('Username is invalid. Must start with a letter or number.'); - } - - if (! ctype_alnum($value[strlen($value) - 1])) { - return $fail('Username is invalid. Must end with a letter or number.'); - } - - $val = str_replace(['_', '.', '-'], '', $value); - if (! ctype_alnum($val)) { - return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).'); - } - - if (! preg_match('/[a-zA-Z]/', $value)) { - return $fail('Username is invalid. Must contain at least one alphabetical character.'); - } - - $restricted = RestrictedNames::get(); - if (in_array(strtolower($value), array_map('strtolower', $restricted))) { - return $fail('Username cannot be used.'); - } - }, + new PixelfedUsername, ]; $emailRules = [ diff --git a/app/Http/Controllers/CuratedRegisterController.php b/app/Http/Controllers/CuratedRegisterController.php index ed6b8d096..cd04d3cca 100644 --- a/app/Http/Controllers/CuratedRegisterController.php +++ b/app/Http/Controllers/CuratedRegisterController.php @@ -6,8 +6,8 @@ use App\Jobs\CuratedOnboarding\CuratedOnboardingNotifyAdminNewApplicationPipelin use App\Mail\CuratedRegisterConfirmEmail; use App\Models\CuratedRegister; use App\Models\CuratedRegisterActivity; +use App\Rules\PixelfedUsername; use App\Services\EmailService; -use App\Util\Lexer\RestrictedNames; use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -361,37 +361,7 @@ class CuratedRegisterController extends Controller 'max:30', 'unique:curated_registers', 'unique:users', - function ($attribute, $value, $fail) { - $dash = substr_count($value, '-'); - $underscore = substr_count($value, '_'); - $period = substr_count($value, '.'); - - if (Str::endsWith($value, ['.php', '.js', '.css'])) { - return $fail('Username is invalid.'); - } - - if (($dash + $underscore + $period) > 1) { - return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).'); - } - - if (! ctype_alnum($value[0])) { - return $fail('Username is invalid. Must start with a letter or number.'); - } - - if (! ctype_alnum($value[strlen($value) - 1])) { - return $fail('Username is invalid. Must end with a letter or number.'); - } - - $val = str_replace(['_', '.', '-'], '', $value); - if (! ctype_alnum($val)) { - return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).'); - } - - $restricted = RestrictedNames::get(); - if (in_array(strtolower($value), array_map('strtolower', $restricted))) { - return $fail('Username cannot be used.'); - } - }, + new PixelfedUsername, ], 'email' => [ 'required', diff --git a/app/Http/Controllers/RemoteAuthController.php b/app/Http/Controllers/RemoteAuthController.php index e65e86637..5f7c2a32e 100644 --- a/app/Http/Controllers/RemoteAuthController.php +++ b/app/Http/Controllers/RemoteAuthController.php @@ -10,7 +10,6 @@ use App\Services\EmailService; use App\Services\MediaStorageService; use App\Services\SanitizeService; use App\Util\ActivityPub\Helpers; -use App\Util\Lexer\RestrictedNames; use Illuminate\Auth\Events\Registered; use Illuminate\Contracts\View\View; use Illuminate\Http\Client\RequestException; @@ -468,37 +467,7 @@ class RemoteAuthController extends Controller 'min:2', 'max:30', 'unique:users,username', - function ($attribute, $value, $fail) { - $dash = substr_count($value, '-'); - $underscore = substr_count($value, '_'); - $period = substr_count($value, '.'); - - if (Str::endsWith($value, ['.php', '.js', '.css'])) { - return $fail('Username is invalid.'); - } - - if (($dash + $underscore + $period) > 1) { - return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).'); - } - - if (! ctype_alnum($value[0])) { - return $fail('Username is invalid. Must start with a letter or number.'); - } - - if (! ctype_alnum($value[strlen($value) - 1])) { - return $fail('Username is invalid. Must end with a letter or number.'); - } - - $val = str_replace(['_', '.', '-'], '', $value); - if (! ctype_alnum($val)) { - return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).'); - } - - $restricted = RestrictedNames::get(); - if (in_array(strtolower($value), array_map('strtolower', $restricted))) { - return $fail('Username cannot be used.'); - } - }, + new PixelfedUsername, ], 'password' => 'required|string|min:8|confirmed', 'name' => 'nullable|max:30', diff --git a/app/Rules/PixelfedUsername.php b/app/Rules/PixelfedUsername.php index 2575eeef3..292c6da02 100644 --- a/app/Rules/PixelfedUsername.php +++ b/app/Rules/PixelfedUsername.php @@ -52,6 +52,12 @@ class PixelfedUsername implements ValidationRule return; } + // if (! preg_match('/[a-zA-Z]/', $value)) { + // $fail('Username is invalid. Must contain at least one alphabetical character.'); + + // return; + // } + $restricted = RestrictedNames::get(); if (in_array(strtolower($value), array_map('strtolower', $restricted))) { $fail('Username cannot be used.'); diff --git a/tests/Feature/PixelfedUsernameTest.php b/tests/Feature/PixelfedUsernameTest.php new file mode 100644 index 000000000..b57665e51 --- /dev/null +++ b/tests/Feature/PixelfedUsernameTest.php @@ -0,0 +1,66 @@ +validate('username', $value, function ($msg) use (&$message) { + $message = $msg; + }); + + return $message; +} + +describe('PixelfedUsername', function () { + it('accepts a simple alphanumeric username', function () { + expect(validateUsername('dansup'))->toBeNull(); + }); + + it('accepts a username with a single allowed separator', function () { + expect(validateUsername('dan_sup'))->toBeNull(); + expect(validateUsername('dan.sup'))->toBeNull(); + expect(validateUsername('dan-sup'))->toBeNull(); + }); + + it('rejects usernames ending in a disallowed file extension', function (string $value) { + expect(validateUsername($value))->toBe('Username is invalid.'); + })->with([ + 'user.php', + 'user.js', + 'user.css', + ]); + + it('rejects more than one separator', function () { + expect(validateUsername('a_b.c')) + ->toBe('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).'); + }); + + it('rejects usernames not starting with a letter or number', function () { + expect(validateUsername('_dansup')) + ->toBe('Username is invalid. Must start with a letter or number.'); + }); + + it('rejects usernames not ending with a letter or number', function () { + expect(validateUsername('dansup_')) + ->toBe('Username is invalid. Must end with a letter or number.'); + }); + + it('rejects usernames with disallowed characters', function () { + expect(validateUsername('dan$up')) + ->toBe('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).'); + }); + + // it('rejects all-numeric usernames (must contain at least one letter)', function () { + // expect(validateUsername('12345')) + // ->toBe('Username is invalid. Must contain at least one alphabetical character.'); + // }); + + it('rejects restricted names', function () { + expect(validateUsername('admin'))->toBe('Username cannot be used.'); + }); +});