Джерело: https://laravelukraine.com/docs/13.x/passwords

# Скидання паролів

- [Вступ](#introduction)
    - [Конфігурація](#configuration)
    - [Передумови для драйверів](#driver-prerequisites)
    - [Підготовка моделі](#model-preparation)
    - [Налаштування довірених хостів](#configuring-trusted-hosts)
- [Маршрутизація](#routing)
    - [Запит посилання для скидання пароля](#requesting-the-password-reset-link)
    - [Скидання пароля](#resetting-the-password)
- [Видалення прострочених токенів](#deleting-expired-tokens)
- [Налаштування](#password-customization)

<a name="introduction"></a>
## Вступ

Більшість вебзастосунків дають користувачам змогу скинути забутий пароль. Замість того щоб змушувати вас реалізовувати це вручну в кожному новому застосунку, Laravel надає зручні сервіси для надсилання посилань на скидання пароля та безпечного скидання паролів.

> [!NOTE]
> Хочете швидко почати? Встановіть [стартовий набір застосунку](/docs/{{version}}/starter-kits) Laravel у свіжий застосунок Laravel. Стартові набори Laravel створять усю вашу систему автентифікації, зокрема й скидання забутих паролів.

<a name="configuration"></a>
### Конфігурація

Конфігураційний файл скидання паролів вашого застосунку розташований у `config/auth.php`. Обов'язково перегляньте доступні в ньому опції. За замовчуванням Laravel налаштований на драйвер скидання паролів `database`.

Опція конфігурації `driver` для скидання паролів визначає, де зберігатимуться дані скидання. Laravel містить два драйвери:

<div class="content-list" markdown="1">

- `database` - дані скидання пароля зберігаються в реляційній базі даних.
- `cache` - дані скидання пароля зберігаються в одному з ваших сховищ кешу.

</div>

<a name="driver-prerequisites"></a>
### Передумови для драйверів

<a name="database"></a>
#### База даних

Коли ви користуєтеся драйвером `database` за замовчуванням, потрібно створити таблицю для зберігання токенів скидання паролів вашого застосунку. Зазвичай вона вже є в стандартній міграції Laravel `0001_01_01_000000_create_users_table.php`.

<a name="cache"></a>
#### Кеш

Для обробки скидання паролів доступний також драйвер кешу, який не потребує окремої таблиці в базі даних. Записи мають ключем адресу електронної пошти користувача, тож переконайтеся, що ви не використовуєте адреси електронної пошти як ключі кешу деінде у вашому застосунку:

```php
'passwords' => [
    'users' => [
        'driver' => 'cache',
        'provider' => 'users',
        'store' => 'passwords', // Optional...
        'expire' => 60,
        'throttle' => 60,
    ],
],
```

Щоб виклик `artisan cache:clear` не змив ваші дані скидання паролів, ви можете за бажанням вказати окреме сховище кешу ключем конфігурації `store`. Значення має відповідати сховищу, налаштованому у вашому конфігураційному файлі `config/cache.php`.

<a name="model-preparation"></a>
### Підготовка моделі

Перш ніж користуватися можливостями скидання паролів у Laravel, модель `App\Models\User` вашого застосунку має використовувати трейт `Illuminate\Notifications\Notifiable`. Зазвичай цей трейт уже підключено до стандартної моделі `App\Models\User`, яка створюється в нових застосунках Laravel.

Далі переконайтеся, що ваша модель `App\Models\User` реалізує контракт `Illuminate\Contracts\Auth\CanResetPassword`. Модель `App\Models\User`, що входить до фреймворку, вже реалізує цей інтерфейс і використовує трейт `Illuminate\Auth\Passwords\CanResetPassword`, який містить потрібні для цього методи.

<a name="configuring-trusted-hosts"></a>
### Налаштування довірених хостів

За замовчуванням Laravel відповідає на всі отримані запити незалежно від вмісту заголовка `Host` HTTP-запиту. До того ж значення заголовка `Host` використовується для генерування абсолютних URL до вашого застосунку під час вебзапиту.

Зазвичай ваш вебсервер (наприклад, Nginx чи Apache) варто налаштувати так, щоб він надсилав до застосунку лише запити, які відповідають заданому імені хоста. Проте якщо ви не можете налаштувати вебсервер напряму й вам потрібно вказати Laravel відповідати лише певним іменам хостів, скористайтеся методом `middleware` `trustHosts` у файлі `bootstrap/app.php` вашого застосунку. Це особливо важливо, коли ваш застосунок пропонує скидання паролів.

Щоб дізнатися більше про цей метод, зверніться до [документації про `middleware` TrustHosts](/docs/{{version}}/requests#configuring-trusted-hosts).

<a name="routing"></a>
## Маршрутизація

Щоб належно реалізувати підтримку скидання паролів користувачами, нам знадобиться визначити кілька маршрутів. Спершу потрібна пара маршрутів, які дозволять користувачеві запросити посилання на скидання пароля за адресою електронної пошти. Далі потрібна пара маршрутів, які власне скидатимуть пароль, коли користувач перейде за надісланим йому посиланням і заповнить форму скидання.

<a name="requesting-the-password-reset-link"></a>
### Запит посилання для скидання пароля

<a name="the-password-reset-link-request-form"></a>
#### Форма запиту посилання на скидання пароля

Спершу визначимо маршрути, потрібні для запиту посилань на скидання пароля. Для початку визначимо маршрут, який повертає представлення з формою запиту посилання:

```php
Route::get('/forgot-password', function () {
    return view('auth.forgot-password');
})->middleware('guest')->name('password.request');
```

Представлення, яке повертає цей маршрут, має містити форму з полем `email` - воно дозволить користувачеві запросити посилання на скидання пароля для заданої адреси електронної пошти.

<a name="password-reset-link-handling-the-form-submission"></a>
#### Обробка надсилання форми

Далі визначимо маршрут, який оброблятиме надсилання форми з представлення «забув пароль». Цей маршрут відповідатиме за валідацію адреси електронної пошти та надсилання запиту на скидання пароля відповідному користувачеві:

```php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;

Route::post('/forgot-password', function (Request $request) {
    $request->validate(['email' => 'required|email']);

    $status = Password::sendResetLink(
        $request->only('email')
    );

    return $status === Password::ResetLinkSent
        ? back()->with(['status' => __($status)])
        : back()->withErrors(['email' => __($status)]);
})->middleware('guest')->name('password.email');
```

Перш ніж рухатися далі, розгляньмо цей маршрут докладніше. Спершу валідується атрибут запиту `email`. Далі ми скористаємося вбудованим у Laravel «брокером паролів» (через фасад `Password`), щоб надіслати користувачеві посилання на скидання пароля. Брокер паролів подбає про пошук користувача за заданим полем (у цьому випадку - за адресою електронної пошти) і надішле йому посилання через вбудовану [систему сповіщень](/docs/{{version}}/notifications) Laravel.

Метод `sendResetLink` повертає слаг «статусу». Цей статус можна перекласти за допомогою хелперів [локалізації](/docs/{{version}}/localization) Laravel, щоб показати користувачеві зрозуміле повідомлення про стан його запиту. Переклад статусу скидання пароля визначається мовним файлом `lang/{lang}/passwords.php` вашого застосунку. Запис для кожного можливого значення слага статусу міститься у мовному файлі `passwords`.

> [!NOTE]
> За замовчуванням каркас застосунку Laravel не містить каталогу `lang`. Якщо ви хочете налаштувати мовні файли Laravel, опублікуйте їх артизан-командою `lang:publish`.

Ви можете замислитися, звідки Laravel знає, як дістати запис користувача з бази даних вашого застосунку при виклику методу `sendResetLink` фасада `Password`. Брокер паролів Laravel використовує «провайдери користувачів» вашої системи автентифікації, щоб отримувати записи з бази даних. Провайдер користувачів, який використовує брокер паролів, налаштовується в масиві конфігурації `passwords` вашого файлу `config/auth.php`. Щоб дізнатися більше про написання власних провайдерів користувачів, зверніться до [документації з автентифікації](/docs/{{version}}/authentication#adding-custom-user-providers).

> [!NOTE]
> Реалізуючи скидання паролів вручну, ви маєте самі визначити вміст представлень і маршрутів. Якщо вам потрібен готовий каркас з усією логікою автентифікації та підтвердження, погляньте на [стартові набори застосунку Laravel](/docs/{{version}}/starter-kits).

<a name="resetting-the-password"></a>
### Скидання пароля

<a name="the-password-reset-form"></a>
#### Форма скидання пароля

Далі визначимо маршрути, потрібні для власне скидання пароля після того, як користувач перейде за надісланим йому посиланням і введе новий пароль. Спершу визначмо маршрут, який показуватиме форму скидання пароля при переході за посиланням. Цей маршрут отримає параметр `token`, який ми пізніше використаємо для перевірки запиту на скидання:

```php
Route::get('/reset-password/{token}', function (string $token) {
    return view('auth.reset-password', ['token' => $token]);
})->middleware('guest')->name('password.reset');
```

Представлення, яке повертає цей маршрут, має показувати форму з полями `email`, `password`, `password_confirmation` і прихованим полем `token`, що містить значення секретного `$token`, отриманого нашим маршрутом.

<a name="password-reset-handling-the-form-submission"></a>
#### Обробка надсилання форми

Звісно, нам треба визначити маршрут, який власне оброблятиме надсилання форми скидання пароля. Цей маршрут відповідатиме за валідацію вхідного запиту та оновлення пароля користувача в базі даних:

```php
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;

Route::post('/reset-password', function (Request $request) {
    $request->validate([
        'token' => 'required',
        'email' => 'required|email',
        'password' => 'required|min:8|confirmed',
    ]);

    $status = Password::reset(
        $request->only('email', 'password', 'password_confirmation', 'token'),
        function (User $user, string $password) {
            $user->forceFill([
                'password' => Hash::make($password)
            ])->setRememberToken(Str::random(60));

            $user->save();

            event(new PasswordReset($user));
        }
    );

    return $status === Password::PasswordReset
        ? redirect()->route('login')->with('status', __($status))
        : back()->withErrors(['email' => [__($status)]]);
})->middleware('guest')->name('password.update');
```

Перш ніж рухатися далі, розгляньмо цей маршрут докладніше. Спершу валідуються атрибути запиту `token`, `email` і `password`. Далі ми скористаємося вбудованим у Laravel «брокером паролів» (через фасад `Password`), щоб перевірити облікові дані запиту на скидання пароля.

Якщо переданий брокеру паролів токен, адреса електронної пошти та пароль дійсні, буде викликано замикання, передане методу `reset`. Усередині цього замикання, яке отримує екземпляр користувача та введений у форму пароль відкритим текстом, ми можемо оновити пароль користувача в базі даних.

Метод `reset` повертає слаг «статусу». Цей статус можна перекласти за допомогою хелперів [локалізації](/docs/{{version}}/localization) Laravel, щоб показати користувачеві зрозуміле повідомлення про стан його запиту. Переклад статусу скидання пароля визначається мовним файлом `lang/{lang}/passwords.php` вашого застосунку. Запис для кожного можливого значення слага статусу міститься у мовному файлі `passwords`. Якщо у вашому застосунку немає каталогу `lang`, створіть його артизан-командою `lang:publish`.

Перш ніж рухатися далі, ви можете замислитися, звідки Laravel знає, як дістати запис користувача з бази даних вашого застосунку при виклику методу `reset` фасада `Password`. Брокер паролів Laravel використовує «провайдери користувачів» вашої системи автентифікації, щоб отримувати записи з бази даних. Провайдер користувачів, який використовує брокер паролів, налаштовується в масиві конфігурації `passwords` вашого файлу `config/auth.php`. Щоб дізнатися більше про написання власних провайдерів користувачів, зверніться до [документації з автентифікації](/docs/{{version}}/authentication#adding-custom-user-providers).

<a name="deleting-expired-tokens"></a>
## Видалення прострочених токенів

Якщо ви користуєтеся драйвером `database`, прострочені токени скидання паролів усе одно залишатимуться у вашій базі даних. Проте ви можете легко видалити ці записи артизан-командою `auth:clear-resets`:

```shell
php artisan auth:clear-resets
```

Якщо ви хочете автоматизувати цей процес, подумайте про додавання команди до [планувальника](/docs/{{version}}/scheduling) вашого застосунку:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('auth:clear-resets')->everyFifteenMinutes();
```

<a name="password-customization"></a>
## Налаштування

<a name="reset-link-customization"></a>
#### Налаштування посилання на скидання

Ви можете налаштувати URL посилання на скидання пароля методом `createUrlUsing`, який надає клас сповіщення `ResetPassword`. Цей метод приймає замикання, що отримує екземпляр користувача - отримувача сповіщення - і токен посилання на скидання пароля. Зазвичай цей метод викликають у методі `boot` вашого `AppServiceProvider`:

```php
use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    ResetPassword::createUrlUsing(function (User $user, string $token) {
        return 'https://example.com/reset-password?token='.$token;
    });
}
```

<a name="reset-email-customization"></a>
#### Налаштування листа про скидання

Ви можете легко змінити клас сповіщення, яким користувачеві надсилається посилання на скидання пароля. Для початку перевизначте метод `sendPasswordResetNotification` на вашій моделі `App\Models\User`. Усередині цього методу ви можете надіслати сповіщення будь-яким [класом сповіщення](/docs/{{version}}/notifications) власного авторства. Токен скидання пароля `$token` - перший аргумент, який отримує метод. Ви можете скористатися цим `$token`, щоб побудувати потрібний вам URL скидання пароля й надіслати користувачеві сповіщення:

```php
use App\Notifications\ResetPasswordNotification;

/**
 * Send a password reset notification to the user.
 *
 * @param  string  $token
 */
public function sendPasswordResetNotification($token): void
{
    $url = 'https://example.com/reset-password?token='.$token;

    $this->notify(new ResetPasswordNotification($url));
}
```