読者です 読者をやめる 読者になる 読者になる

tanaka's Programming Memo

プログラミングについてのメモ。

Laravel5.3でSentinelを利用する(6)パスワードのリセット

PHP Sentinel Laravel

前へ | 次へ

パスワードを忘れた時の対応を実装します。

(2016/12/21 HTMLのidをケバブケースに修正)
(2016/12/21 テストにwantToを追加)


はじめに

login画面のパスワードリセットに対応します。SentinelとLaravelでは手続きの内容が異なるので、自前で実装します。流れは以下の通りです。

  1. GET password/reset
    1. リセット用の画面を表示
    2. メールアドレスを入力させて、POSTで送信
  2. POST password/email
    1. メールアドレスを受け取る
    2. SentinelのReminderを作成して、リセットトークンを生成
    3. コードを添えたメールを送信
  3. GET password/reset/{token}
    1. メールから上記にアクセスさせて、リセットトークンを送信内容に含める
    2. メールアドレスとパスワード、パスワードの確認の3つを入力してPOST送信
  4. POST password/reset
    1. 上記のデータを受け取って、パスワードのリセットを実施する



テストの作成

パスワードの再発行のためのテストを作成します。

ボタンを押すフォームを指定するためのIDをビューに追加します。

  • resources/views/auth/passwords/email.blade.php を開く
  • 「<form」から始まる行を探して、以下のように id 属性を追加する(2016/12/21 HTMLのidをケバブケースに修正)
                    <form id="send-reset-form" class="form-horizontal" role="form" method="POST" action="{{ url('/password/email') }}">
  • resources/views/auth/passwords/reset.blade.php を開く
  • <form から始まる行を探して、以下のように id 属性を追加する(2016/12/21 HTMLのidをケバブケースに修正)
                    <form id="reset-password-form" class="form-horizontal" role="form" method="POST" action="{{ url('/password/reset') }}">

必要であれば、ページの内容も書き換えてください。

続いて、テストコードを作成します。

  • ターミナルで以下を実行して、テスト用のCestファイルを作成
composer exec codecept g:cest functional ResetPassword
  • tests/functional/ResetPasswordCest.php をエディターで開く
  • 以下のようにする(2016/12/21 テストにwantToの追加と、submitFormのIDをケバブケースに修正)
<?php

use Sentinel;
use Reminder;

class ResetPasswordCest
{
    private $cre = [
        'name' => 'パスワード再発行',
        'email' => 'reset@test.com',
        'password' => 'password'
    ];

    public function _before(FunctionalTester $I)
    {
        // ユーザーを作成
        Sentinel::registerAndActivate($this->cre);
    }

    public function _after(FunctionalTester $I)
    {
    }

    // tests
    public function tryToTest(FunctionalTester $I)
    {
        // 存在しないメールへのリクエスト
        $I->wantTo(' 存在しないメールへのリクエストの動作確認.');
        $I->amOnPage('/password/reset');
        $I->submitForm('#send-reset-form', [
            'email' => 'nobody@test.com'
        ]);
        $I->seeInCurrentUrl('/login');

        // メール発行(resetとemailのテスト)
        $I->wantTo(' メール発行(resetとemailのテスト)');
        $I->amOnPage('/password/reset');
        $I->submitForm('#send-reset-form', [
            'email' => $this->cre['email']
        ]);
        $I->seeInCurrentUrl('login');
        $I->see(trans('sentinel.password_reset_sent'));

        // メール発行テスト
        $I->expect(' 登録したemailアドレスが見つかる.');
        $user = Sentinel::findByCredentials($this->cre);
        \PHPUnit_Framework_Assert::assertEquals($user->email, $this->cre['email']);
        $I->expect(' 登録したユーザーのリマインダーが登録されている.');
        $reminder = Reminder::exists($user);
        \PHPUnit_Framework_Assert::assertNotFalse($reminder);

        // 不正なトークンでのアクセスして、パスワードの変更を試みる
        $I->wantTo(' 不正なトークンでアクセスして、パスワードの変更を試みた時に失敗.');
        $I->amOnPage('/password/reset/01234567891123456789212345678931');
        $I->submitForm('#reset-password-form', [
            'email' => $this->cre['email'],
            'password' => $this->cre['password'],
            'password_confirmation' => $this->cre['password'],
        ]);
        $I->see(trans('sentinel.password_reset_failed'));

        // 正しいトークンでのアクセスして、パスワード確認のミス
        $I->wantTo(' 正しいトークンでアクセスして、パスワードの確認が間違いの時の動作.');
        $I->amOnPage('/password/reset/'.$reminder->code);
        $I->submitForm('#reset-password-form', [
            'email' => $this->cre['email'],
            'password' => $this->cre['password'],
            'password_confirmation' => 'invalidinvalid',
        ]);
        $I->see('does not match');

        // 正しいトークンでのアクセスして、パスワード変更
        $I->wantTo(' パスワード変更の実行確認.');
        $I->amOnPage('/password/reset/'.$reminder->code);
        $I->submitForm('#reset-password-form', [
            'email' => $this->cre['email'],
            'password' => 'resetpassword',
            'password_confirmation' => 'resetpassword',
        ]);
        $I->see(trans('sentinel.password_reset_done'));

        // パスワードが変更されたことを確認
        $I->expect(' 変更後のデータが見つかる.');
        $check = [
            'email' => 'reset@test.com',
            'password' => 'resetpassword'
        ];
        \PHPUnit_Framework_Assert::assertNotFalse(Sentinel::authenticate($check));
        // パスワードが変更されたことを確認
        $I->expect(' パスワードが変更されていることを確認.');
        $check = [
            'email' => 'reset@test.com',
            'password' => $this->cre['password']
        ];
        \PHPUnit_Framework_Assert::assertFalse(Sentinel::authenticate($check));
    }
}



ルートの作成

パスワードリセット画面の呼び出しと、パスワードリセットを実行するPOSTルートを定義します。

// パスワードのリセット用のルート
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm');
Route::post('password/reset', 'Auth\ResetPasswordController@reset');



リマインダーに必要なメッセージを追加

リマインダーに関連する文言をメッセージに追加しましょう。

  • resources/lang/ja/sentinel.php をエディターで開く
  • 以下のメッセージを配列に追加する
    // パスワードの再設定関連
    'password_reset_sent' => 'パスワードを再設定するためのメールを送信しました。届いたメールに記載のリンクをクリックして、パスワードの再設定画面を開いてください。',
    'reminder_title' => 'パスワードをリセットできます。',
    'password_reset_done' => 'パスワードをリセットしました。',
    'password_reset_failed' => 'リセットコードが古くなっていました。もう一度、リセットし直してください。',



メールのビューを変更する場合

パスワードをリセットするためのメールの本文は、Laravelのものをそのまま利用します。変更したい場合は、以下のファイルを書き換えてください。

  • resources/views/auth/passwords/email.blade.php



パスワードのリセットメールを送付するメソッドの作成

既存の ForgotPasswordController.php の該当するメソッドを上書きします。

  • app/Http/Controllers/Auth/ForgotPasswordController.php をエディターで開く
  • SentinelとReminder、既存のメール通知、Requestを使えるように、冒頭に以下の4つのuseを追加する。既存のuse文も必要なので、そのまま残しておく
use Reminder;
use Sentinel;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
use Illuminate\Http\Request;

リマインダーメールを送信する処理を追加します。

  • クラス内に以下のコードを追加。既存のコードは残しておくこと
    /**
     * パスワードを再設定するための処理
     * ユーザーが有効で、パスワードが条件に合致していたら、SentinelのReminderを使って処理する
     */
    protected function sendResetLinkEmail(Request $request) {
        // 古いリマインダーコードを削除
        Reminder::removeExpired();

        // チェック
        $this->validate($request, [
            // emailは必須で、emailの形式で、255文字まで
            // メールアドレスの有無は、不正を避けるためにチェックしない
            'email' => 'required|email|max:255'
        ]);

        // ユーザーを検索
        $user = Sentinel::findByCredentials(['email'=>$request->email]);
        if (is_null($user)) {
            // ユーザーがいなければ成功したような感じにしてログイン画面へ
            return redirect('login')->with(['info'=>trans('sentinel.password_reset_sent')]);
        }

        // リマインダーが作成済みなら、それを再送信する
        $code = "";
        $exists = Reminder::exists($user);
        if ($exists) {
            // すでに設定されているので、リマインダーコードを設定
            $code = $exists->code;
        }
        else {
            // 新規にリマインダーを作成して、コードを返す
            $reminder = Reminder::create($user);
            $code = $reminder->code;
        }

        // メールを送信
        $user->notify(new ResetPasswordNotification($code));

        // 成功したら、login画面へ移動
        return redirect('login')->with(['info'=>trans('sentinel.password_reset_sent')]);
    }

以上で、既存の ResetPasswordNotification を使って、リセット用のコードをメールで送付します。 npm test を実行すると、エラーが出ますが、メールは送信されます。 http://mailtrap.io にメールをリセットするためのメールが届いていることを確認してください。


パスワードのリセットの実行

パスワードのリセット画面の表示は、既存のままでOKです。最後に、実際にリセットを実行するコントロールメソッドを作成します。

  • app/Http/Controllers/Auth/ResetPasswordController.php をエディターで開く
  • ファイルの最初の方に、利用したいFacadeのための以下のuseを追加
use Sentinel;
use Reminder;
use Illuminate\Http\Request;
  • 以下の reset メソッドを、クラス内に追加する
    /**
     * パスワードを再設定するための処理
     * ユーザーが有効で、パスワードが条件に合致していたら、SentinelのReminderを使って処理する
     */
    protected function reset(Request $request) {
        // 古いリマインダーコードを削除
        Reminder::removeExpired();

        // チェック
        $this->validate($request, [
           // emailは必須で、emailの形式で、255文字まで
           // メールアドレスの有無は、不正を避けるためにチェックしない
           'email' => 'required|email|max:255',
           // passwordは必須で、6文字以上255文字以下で、確認欄と一致する必要がある
           'password' => 'required|between:6,255|confirmed',
           // トークンは必須で、32文字
           'token' => 'required|size:32'
       ]);

       // ユーザーを検索
       $user = Sentinel::findByCredentials(['email'=>$request->email]);
       if (is_null($user)) {
           // ユーザーがいなければ成功したような感じにしてログイン画面へ
           return redirect('login')->with(['info'=>trans('sentinel.password_reset_sent')]);
       }

       // リマインダー実行
       $reminder = Reminder::complete($user, $request->token, $request->password);
       if ($reminder) {
           // 成功
           return redirect('login')->with(['info' => trans('sentinel.password_reset_done')]);
       }

       // 失敗
       return redirect('login')->with(['myerror'=>trans('sentinel.password_reset_failed')]);
   }

以上で完了です。 npm test でテストが成功します。 http://localhost:8000/ を開いて、手動でパスワードのリセットができることも確認してみてください。



前へ | 次へ