Студия разработки сайтов и приложений

Netspark.ru

Скрипт для типографики

Typofilter.js

Laravel Livewire — вход по имени пользователя или электронной почте

Если поставить Laravel Starter Kit с Livewire внутри, который мы в прошлый раз немного поизучали, то у нас сразу, из коробки будет поддержка аутентификации. Но — только по электронной почте и паролю. А заказчики могут хотеть (и хотят) чтобы можно было входить с помощью логина, либо вместо почты, либо в качестве альтернативы. Причины могут быть разные, не будем зацикливаться. Вместо этого давайте посмотрим, как сделать, чтобы можно было использовать и логин, и почту в качестве идентификатора пользователя при входе.

Начнём как положено, с тестов. В tests/Feature/Auth у нас есть AuthenticationTest.php, в котором уже находятся дефолтные тесты аутентификации. Выберем главный опорный тест, на базе которого проведем изменения:

public function test_users_can_authenticate_using_the_login_screen(): void
{
    $user = User::factory()->create();

    $response = LivewireVolt::test('auth.login')
        ->set('email', $user->email)
        ->set('password', 'password')
        ->call('login');

    $response
        ->assertHasNoErrors()
        ->assertRedirect(route('platform.main', absolute: false));

    $this->assertAuthenticated();
}

мы видим что тест проверяет, что пользователь может задать на экране логина свой мейл и пароль, и тогда он войдет на сайт. Допустим, новое поле ввода (вместо почты) у нас будет называться login и мы хотим использовать его как для почты, так и для имени пользователя. В качестве имени будем использовать $user->name, который тоже есть из коробки.

Изменим наш тест так, чтобы получилось два:

public function test_users_can_authenticate_using_the_login_screen(): void
{
    $user = User::factory()->create();

    $response = LivewireVolt::test('auth.login')
        ->set('login', $user->email)
        ->set('password', 'password')
        ->call('login');

    $response
        ->assertHasNoErrors()
        ->assertRedirect(route('platform.main', absolute: false));

    $this->assertAuthenticated();
}

public function test_users_can_authenticate_with_name(): void
{
    $user = User::factory()->create();

    $response = LivewireVolt::test('auth.login')
        ->set('login', $user->name)
        ->set('password', 'password')
        ->call('login');

    $response
        ->assertHasNoErrors()
        ->assertRedirect(route('platform.main', absolute: false));

    $this->assertAuthenticated();
}

У нас есть два теста, проверяющие, что если пользователь введет в поле login свое имя или почту, а также верный пароль, он успешно войдет на сайт. Однако пока эта логика не реализована. Реализуем её.

Все изменения будут сосредоточены в одном Volt-компоненте resources/views/livewire/auth/login.blade.php. Поэтому приведу сразу измененный код компонента с комментариями:

// ...

new #[Layout('components.layouts.auth')] class extends Component {
    // Изменим свойство $email на $login, уберем email из аннотации Validate, т.к. теперь можно ввести не только почту
    #[Validate('required|string')]
    public string $login = '';

    // ...

    /**
    * Handle an incoming authentication request.
    */
    // Мы переименовали метод login() в authenticate(), чтобы не было конфликта с полем $login. 
    // Если этого не сделать, компонент забуксует и работать не будет, и даже ошибку не бросит ни в бэке, ни в js.
    public function authenticate(): void
    {
        $this->validate();

        $this->ensureIsNotRateLimited();

        // Определим, ввёл пользователь email или имя, для этого попробуем проверить $this->login фильтром по email.
        // Если это имейл, в массиве $credentials укажем ключ email, иначе укажем name.
        $credentials = filter_var($this->login, FILTER_VALIDATE_EMAIL)
            ? ['email' => $this->login, 'password' => $this->password]
            : ['name' => $this->login, 'password' => $this->password];

        // и передадим массив $credentials для авторизации
        if (!Auth::attempt($credentials, $this->remember)) {
            RateLimiter::hit($this->throttleKey()); 
            throw ValidationException::withMessages([
                // Здесь мы тоже поменяли ключ с email на login, поскольку инпута email в форме логина больше нет.
                'login' => __('auth.failed'),
            ]);
        }

        // ...
    }

    // ...

    /**
    * Get the authentication rate limiting throttle key.
    */
    protected function throttleKey(): string
    {
        // Тут тоже поменяли поле email на login
        return Str::transliterate(Str::lower($this->login) . '|' . request()->ip());
    }

А в вёрстке компонента мы просто меняем инпут имейла на login (меняем атрибут type, убираем autocomplete):

    <!-- ... -->
    <form wire:submit="authenticate" class="flex flex-col gap-6">
    <!-- Login -->
    <flux:input
        wire:model="login"
        :label="'Логин или email'"
        type="text"
        required
        autofocus
        placeholder="Имя пользователя или email@example.com"
    />

    <!-- ... -->

Да, и поскольку теперь мы используем для входа $user->name, нужно сделать поле уникальным (в дефолтной миграции этого нет). Нужно создать новую миграцию и добавить в метод up() код:

Schema::table('users', function (Blueprint $table) {
    $table->unique('name');
});

Также стоит добавить 'unique:' . User::class в правила валидации компонента resources/views/livewire/auth/register.blade.php, чтобы юзеры не видели ошибку http 500, когда введут имя, которое уже есть. Ну и если вы используете еще какие-то формы создания или редактирования пользователя, там тоже стоит добавить валидацию на уникальность.

Напоследок запустите все тесты в AuthenticationTest.php и исправить email на login в оставшихся тестах, чтобы они проходили.

Обсуждение

Чтобы обсудить заметку, написать комментарий, или просто связаться, заходите в Телеграм-канал. У нас весело и всем рады!

Также меня можно найти в Хвиттере, VC.ru, Дзене, или Тенчате.