パスワードを忘れた時の対応を実装します。
(2016/12/21 HTMLのidをケバブケースに修正)
(2016/12/21 テストにwantToを追加)
はじめに
login画面のパスワードリセットに対応します。SentinelとLaravelでは手続きの内容が異なるので、自前で実装します。流れは以下の通りです。
- GET password/reset
- リセット用の画面を表示
- メールアドレスを入力させて、POSTで送信
- POST password/email
- メールアドレスを受け取る
- SentinelのReminderを作成して、リセットトークンを生成
- コードを添えたメールを送信
- GET password/reset/{token}
- メールから上記にアクセスさせて、リセットトークンを送信内容に含める
- メールアドレスとパスワード、パスワードの確認の3つを入力してPOST送信
- POST password/reset
- 上記のデータを受け取って、パスワードのリセットを実施する
テストの作成
パスワードの再発行のためのテストを作成します。
ボタンを押すフォームを指定するための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/ を開いて、手動でパスワードのリセットができることも確認してみてください。