Блог на Laravel 7, часть 3. Checkbox «Запомнить меня» и подтверждение адреса почты

08.12.2020

Теги: LaravelMySQLPHPWeb-разработкаАутентификацияБазаДанныхБлогПользовательПрактикаФреймворкШаблонСайта

Запомнить меня

Добавим в форму входа checkbox «Запомнить меня» — это позволит пользователю при следующем визите войти автоматически, без ввода логина и пароля.

@extends('layout.site', ['title' => 'Вход в личный кабинет'])

@section('content')
    <h1>Вход в личный кабинет</h1>
    <form method="post" action="{{ route('auth.auth') }}">
        @csrf
        <div class="form-group">
            <input type="email" class="form-control" name="email" placeholder="Адрес почты"
                   required maxlength="255" value="{{ old('email') ?? '' }}">
        </div>
        <div class="form-group">
            <input type="text" class="form-control" name="password" placeholder="Ваш пароль"
                   required maxlength="255" value="">
        </div>
        <div class="form-group form-check">
            <input type="checkbox" class="form-check-input" name="remember" id="remember">
            <label class="form-check-label" for="remember">
                Запомнить меня
            </label>
        </div>
        <div class="form-group">
            <button type="submit" class="btn btn-info text-white">Войти</button>
        </div>
        <a href="{{ route('auth.forgot-form') }}">Забыли пароль?</a>
    </form>
@endsection

И внесем небольшие изменения в контроллер LoginController:

class LoginController extends Controller {
    /* ... */
    public function authenticate(Request $request) {
        /* ... */
        if (Auth::attempt($credentials, $request->has('remember'))) {
            /* ... */
        }
        /* ... */
    }
    /* ... */
}

Проверка адреса почты

Чтобы на сайте не регистрировались роботы, можно организовать проверку адреса почты. Сразу после регистрации на почту отправляется письмо, содержащее ссылку, по которой новый пользователь должен перейти, чтобы аккаунт был активирован. Ативация аккаунта подразумевает изменение значения поля email_verified_at таблицы базы данных users, которое изначально имеет значение null.

1. Добавляем маршруты

Добавляем маршруты в файл routes/web.php:

/*
 * Регистрация, вход в ЛК, восстановление пароля
 */
Route::group([
    'as' => 'auth.', // имя маршрута, например auth.index
    'prefix' => 'auth', // префикс маршрута, например auth/index
], function () {
    // форма регистрации
    Route::get('register', 'Auth\RegisterController@register')
        ->name('register');
    // создание пользователя
    Route::post('register', 'Auth\RegisterController@create')
        ->name('create');
    // форма входа
    Route::get('login', 'Auth\LoginController@login')
        ->name('login');
    // аутентификация
    Route::post('login', 'Auth\LoginController@authenticate')
        ->name('auth');
    // выход
    Route::get('logout', 'Auth\LoginController@logout')
        ->name('logout');
    // форма ввода адреса почты
    Route::get('forgot-password', 'Auth\ForgotPasswordController@form')
        ->name('forgot-form');
    // письмо на почту
    Route::post('forgot-password', 'Auth\ForgotPasswordController@mail')
        ->name('forgot-mail');
    // форма восстановления пароля
    Route::get(
        'reset-password/token/{token}/email/{email}',
        'Auth\ResetPasswordController@form'
    )->name('reset-form');
    // восстановление пароля
    Route::post('reset-password', 'Auth\ResetPasswordController@reset')
        ->name('reset-password');
    // сообщение о необходимости проверки адреса почты
    Route::get('verify-message', 'Auth\VerifyEmailController@message')
        ->name('verify-message');
    // подтверждение адреса почты нового пользователя
    Route::get('verify-email/token/{token}/id/{id}', 'Auth\VerifyEmailController@verify')
        ->where('token', '[a-f0-9]{32}')
        ->where('id', '[0-9]+')
        ->name('verify-email');
});

2. Контроллер VerifyEmailController

Создаем контроллер VerifyEmailController:

> php artisan make:controller Auth/VerifyEmailController
namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;

class VerifyEmailController extends Controller {

    public function __construct() {
        $this->middleware('guest');
    }

    /**
     * Сообщение сразу после регистрации
     */
    public function message() {
        return view('auth.verify-message');
    }

    /**
     * Активация аккаунта после перехода по ссылке
     */
    public function verify($token, $id) {
        // удаляем пользователей, которые не подтвердили
        $expire = Carbon::now()->subMinute(60);
        User::whereNull('email_verified_at')->where('cteated_at', '<', $expire)->delete();
        // пробуем найти пользователя по идентификатору
        $user = User::find($id);
        $condition = $user && md5($user->email . $user->name) === $token;
        if (!$condition) {
            return redirect()
                ->route('auth.register')
                ->withErrors('Ссылка для проверки адреса почты устарела');
        }
        // все проверки пройдены, активируем аккаунт
        $user->update(['email_verified_at' => Carbon::now()]);
        return redirect()
            ->route('auth.login')
            ->with('success', 'Вы успешно подтвердили свой адрес почты');
    }
}

Все маршруты контроллера защищены посредником guest, так что аутентифицированный пользователь не сможет перейти по ним.

3. Изменения в RegisterController

Нам нужно отправить письмо и отправить пользователя на страницу с сообщением о необходимости подтвердить свой адрес почты.

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\User;
use Hash;
use Illuminate\Support\Facades\Mail;

class RegisterController extends Controller {

    public function __construct() {
        $this->middleware('guest');
    }

    /**
     * Форма регистрации
     */
    public function register() {
        return view('auth.register');
    }

    /**
     * Добавление пользователя
     */
    public function create(Request $request) {
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:8|confirmed',
            'password_confirmation' => 'required',
        ]);

        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);

        // ссылка для проверки адреса почты
        $token = md5($user->email . $user->name);
        $link = route('auth.verify-email', ['token' => $token, 'id' => $user->id]);
        Mail::send(
            'email.verify-email',
            ['link' => $link],
            function($message) use ($request) {
                $message->to($request->email);
                $message->subject('Подтверждение адреса почты');
            }
        );

        // необходимо подтвердить адрес почты
        return redirect()->route('auth.verify-message');
        // return redirect()->route('auth.login');
    }
}

4. Класс модели User

Поскольку нам надо обновлять поле email_verified_at — добавляем это поле в свойство $fillable класса модели User.

class User extends Authenticatable {
    /* ... */
    protected $fillable = [
        'name', 'email', 'password', 'email_verified_at'
    ];
    /* ... */
}

5. Страница сообщения

Создаем шаблон resources/views/auth/verify-message.blade.php:

@extends('layout.site', ['title' => 'Подтверждение адреса почты'])

@section('content')
    <h1>Подтверждение адреса почты</h1>
    <p>
        Вам на почту было отправлено письмо. Для подтверждения адреса почты необходимо перейти
        по ссылке, которая будет в письме. Если в течение часа этого не произойдет — аккаунт
        будет удален. Но всегда можно зарегистрироваться еще раз.
    </p>
@endsection

6. Шаблон письма

Создаем шаблон resources/views/email/verify-email.blade.php:

<h3>Подтверждение адреса почты</h3>

<p>Для подтверждения адреса почты перейдите по этой <a href="{{ $link }}">ссылке</a>.</p>

7. Контроллер LoginController

Мы должны теперь проверять не только совпадение логина и пароля, но и подтверждение адреса почты.

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Auth;

class LoginController extends Controller {

    public function __construct() {
        $this->middleware('guest', ['except' => 'logout']);
    }

    /**
     * Форма входа в личный кабинет
     */
    public function login() {
        return view('auth.login');
    }

    /**
     * Аутентификация пользователя
     */
    public function authenticate(Request $request) {
        $request->validate([
            'email' => 'required|string|email',
            'password' => 'required|string',
        ]);

        $credentials = $request->only('email', 'password');

        if (Auth::attempt($credentials, $request->has('remember'))) {
            /*
             * Адрес почты не подтвержден
             */
            if (is_null(Auth::user()->email_verified_at)) {
                Auth::logout();
                return redirect()
                    ->route('auth.verify-message')
                    ->withErrors('Адрес почты не подтвержден');
            }
            return redirect()
                ->route('user.index')
                ->with('success', 'Вы вошли в личный кабинет');
        }

        return redirect()
            ->route('auth.login')
            ->withErrors('Неверный логин или пароль');
    }

    /**
     * Выход из личного кабинета
     */
    public function logout() {
        Auth::logout();
        return redirect()
            ->route('auth.login')
            ->with('success', 'Вы вышли из личного кабинета');
    }
}

8. Проверяем почту

После регистрации проверяем, что на почту было отправлено письмо:

И переходим по ссылке в письме, чтобы подтвердить свой адрес почты:

Роли и Права пользователей

1. Модели Role и Permission

Создаем две новые модели под названием Role и Permission вместе с миграциями:

> php artisan make:model Role -m
> php artisan make:model Permission -m
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateRolesTable extends Migration {
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        Schema::create('roles', function (Blueprint $table) {
            $table->id();
            $table->string('name', 50);
            $table->string('slug', 50)->unique();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        Schema::dropIfExists('roles');
    }
}
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePermissionsTable extends Migration {
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        Schema::create('permissions', function (Blueprint $table) {
            $table->id();
            $table->string('name', 50);
            $table->string('slug', 50)->unique();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        Schema::dropIfExists('permissions');
    }
}

2. Отношения между моделями

Давайте определимся с отношениями, которые нужно реализовать для Ролей и Прав:

  1. Пользователь может иметь Права
  2. Пользователь может иметь Роли
  3. Роль может иметь Права

Для этих трех отношений нам нужно добавить три сводные таблицы, чтобы создать отношение «многие ко многим» между моделями.

3. Сводные таблицы

Сводная таблица для связи моделей User и Permission:

> php artisan make:migration create_user_permission_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUserPermissionTable extends Migration {
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        Schema::create('user_permission', function (Blueprint $table) {
            $table->unsignedBigInteger('user_id');
            $table->unsignedBigInteger('permission_id');
            $table->timestamps();
            // внешний ключ, ссылается на поле id таблицы users
            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->onDelete('cascade');
            // внешний ключ, ссылается на поле id таблицы permissions
            $table->foreign('permission_id')
                ->references('id')
                ->on('permissions')
                ->onDelete('cascade');
            // составной первичный ключ
            $table->primary(['user_id', 'permission_id']);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        Schema::dropIfExists('user_permission');
    }
}

Сводная таблица для связи моделей User и Role:

> php artisan make:migration create_user_role_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUserRoleTable extends Migration {
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        Schema::create('user_role', function (Blueprint $table) {
            $table->unsignedBigInteger('user_id');
            $table->unsignedBigInteger('role_id');
            $table->timestamps();
            // внешний ключ, ссылается на поле id таблицы users
            $table->foreign('user_id')
                ->references('id')
                ->on('users')->onDelete('cascade');
            // внешний ключ, ссылается на поле id таблицы roles
            $table->foreign('role_id')
                ->references('id')
                ->on('roles')
                ->onDelete('cascade');
            // составной первичный ключ
            $table->primary(['user_id', 'role_id']);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        Schema::dropIfExists('user_role');
    }
}

Сводная таблица для связи моделей Role и Permission:

> php artisan make:migration create_role_permission_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateRolePermissionTable extends Migration {
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        Schema::create('role_permission', function (Blueprint $table) {
            $table->unsignedBigInteger('role_id');
            $table->unsignedBigInteger('permission_id');
            $table->timestamps();
            // внешний ключ, ссылается на поле id таблицы roles
            $table->foreign('role_id')
                ->references('id')
                ->on('roles')
                ->onDelete('cascade');
            // внешний ключ, ссылается на поле id таблицы permissions
            $table->foreign('permission_id')
                ->references('id')
                ->on('permissions')
                ->onDelete('cascade');
            // составной первичный ключ
            $table->primary(['role_id','permission_id']);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        Schema::dropIfExists('role_permission');
    }
}

4. Отношения между моделями

Отношения между моделями Role и Permission:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model {
    public function permissions() {
        return $this
            ->belongsToMany(Permission::class, 'role_permission')
            ->withTimestamps();
    }
}
namespace App;

use Illuminate\Database\Eloquent\Model;

class Permission extends Model {
    public function roles() {
        return $this
            ->belongsToMany(Role::class, 'role_permission')
            ->withTimestamps();
    }
}

Теперь займемся моделью User. Пользователь может иметь много Прав и много Ролей. То же самое наоборот — Роль может иметь много Пользователей, Право может иметь много Пользователей. Поэтому нам нужно создать отношение «Многие ко Многим» в модели User.

Создадим эти отношения в трейте, а затем используем его в модели User — для этого создаем файл app/Traits/HasRolesAndPermissions.php.

namespace App\Traits;

use App\Role;
use App\Permission;

trait HasRolesAndPermissions {

    /**
     * Связь модели User с моделью Role
     */
    public function roles() {
        return $this
            ->belongsToMany(Role::class, 'user_role')
            ->withTimestamps();
    }

    /**
     * Связь модели User с моделью Permission
     */
    public function permissions() {
        return $this
            ->belongsToMany(Permission::class, 'user_permission')
            ->withTimestamps();
    }
}

Поиск: Laravel • MySQL • PHP • Web-разработка • Аутентификация • База данных • Блог • Пользователь • Практика • Фреймворк • Шаблон сайта

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.