Merge pull request #6908 from pixelfed/refactor/pixelfed-username-rule

Deduplication - username validation in 8 places
pull/6911/head
Shlee 4 weeks ago committed by GitHub
commit 30810936e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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' => [

@ -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',
]);

@ -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,
];
}

@ -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 = [

@ -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',

@ -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',

@ -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.');

@ -0,0 +1,66 @@
<?php
use App\Rules\PixelfedUsername;
/**
* Run the PixelfedUsername rule and return the failure message, or null if it passed.
*/
function validateUsername(string $value): ?string
{
$rule = new PixelfedUsername;
$message = null;
$rule->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.');
});
});
Loading…
Cancel
Save