環境
- Laravel 5.5
- PHP 7.2
この章でやること
- 12.1 PasswordResetsリソース
- 12.2 パスワード再設定のメール送信
- 12.3 パスワードを再設定する
- 12.4 本番環境でのメール送信(再掲)
- 12.5 最後に
- 12.6 照明期限切れの比較
12.1 PasswordResetsリソース
12.1.1 PasswordResetsコントローラー
ルート追加(/routes/web.php)
php
Route::get('password_resets/create', "PasswordResetsController@create")->name("resets.create");
Route::post('password_resets', "PasswordResetsController@store")->name("resets.store");
Route::get('password_resets/{token}/edit', "PasswordResetsController@edit")->name("resets.edit");
Route::patch('password_resets/{token}', "PasswordResetsController@update")->name("resets.update");
レイアウト(/resources/views/sessions/create.blade.php)
ppp
{{ Html::linkRoute("resets.create", "(forgot password)") }}
12.1.2 新しいパスワードの設定
ミグレーション(/dataase/migrations/[timestamp]_add_reset_to_users.php)
```php
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('reset_digest')->nullable();
$table->dateTime('reset_sent_at')->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('reset_digest');
$table->dropColumn('reset_sent_at');
});
}
(/resources/views/password_resets/create.blade.php)
```html
@extends('layouts.application')
@section('title', 'Forgot password')
@section('content')
<h1>Forgot password</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
{{ Form::open(['route' => 'resets.store']) }}
{{ Form::label('email') }}
{{ Form::text('email', "", ["class" => "form-control"]) }}
{{ Form::submit("Submit", ["class" => "btn btn-primary"]) }}
{{ Form::close() }}
</div>
</div>
@endsection
(/app/Http/Controllers/PasswordResetsController.php)
php
public function create()
{
return view("password_resets.create");
}
12.1.3 createアクションでパスワード再設定
(/app/Http/Conttollers/PasswordResetsController.php)
php
public function store(Request $request)
{
$user = User::where("email", $request->email)->first();
if (!$user) {
session()->flash('message', ['danger' => 'Email address not found']);
return redirect()->back();
}
$reset_token = str_random(22);
$user->reset_digest = bcrypt($reset_token);
$user->reset_sent_at = Carbon::now();
$user->save();
$user->sendPasswordResetMail($reset_token);
session()->flash('message', ['info' => 'Email sent with password reset instructions']);
return redirect("/");
}
(/app/User.php)
php
public function sendPasswordResetMail($token)
{
Mail::to($this)->send(new PasswordReset($this, $token));
}
12.2 パスワード再設定のメール送信
12.2.1 パスワード再設定のメールとテンプレート
(/app/Mail/PasswordReset.php)
```php
class PasswordReset extends Mailable
{
use Queueable, SerializesModels;
public $user;
public $reset_token;
public function __construct($user, $reset_token)
{
$this->user = $user;
$this->reset_token = $reset_token;
}
public function build()
{
return $this->from("noreply@example.com")
->subject("Password reset")
->view('emails.password_reset');
}
}
html
(/resources/views/emails/password_reset.blade.php)
Password reset
To reset your password click the link below:
{{ Html::linkRoute("resets.edit", "Reset password", ["token" => $reset_token, "email" => $user->email]) }}
This link will expire in two hours.
If you did not request your password to be reset, please ignore this email and your password will stay as it is.
# 12.2.2 送信メールのテスト
(/tests/Unit/MailerTest.php)
```php
public function testPasswordReset()
{
Mail::fake();
$user = User::find(1);
$reset_token = str_random(22);
Mail::to($user)->send(new PasswordReset($user, $reset_token));
Mail::assertSent(PasswordReset::class, function ($mail) use ($user, $reset_token) {
$mail->build();
$this->assertEquals("Password reset", $mail->subject);
$this->assertTrue($mail->hasTo($user->email));
$this->assertTrue($mail->hasFrom("noreply@example.com"));
$this->assertEquals($user->name, $mail->user->name);
$this->assertEquals($reset_token, $mail->reset_token);
$this->assertEquals($user->email, $mail->user->email);
return true;
});
}
12.3 パスワードを再設定する
12.3.1 editアクションで再設定
(/resources/views/password_resets/edit.blade.php)
```html
@extends('layouts.application')
@section('title', 'Reset password')
@section('content')
Reset password
@endsection
php
(/app/Http/Controllers/PasswordResetController.php)
public function __construct()
{
$this->middleware(function ($request, $next) {
$user = User::where("email", $request->email)->first();
if (!$user || !$user->activated || !Hash::check($request->token, $user->reset_digest)) {
return redirect('/');
}
return $next($request);
})->only(["edit", "update"]);
}
...
public function edit(Request $request)
{
$user = User::where("email", $request->email)->first();
return view("password_resets.edit")->with(["user" => $user, "token" => $request->token]);
}
```
12.3.2 パスワードを更新する
(/app/Http/Controllers/PasswordResetsController.php)
```php
public function __construct()
{
$this->middleware(function ($request, $next) {
$user = User::where("email", $request->email)->first();
if ($user->checkExpiration()) {
session()->flash('message', ['danger' => 'Password reset has expired']);
return redirect()->back();
}
return $next($request);
})->only(["edit", "update"]);
}
public function update(Request $request)
{
$request->validate([
'password' => 'required|min:6|confirmed',
'password_confirmation' => 'required|min:6',
]);
$user = User::where("email", $request->email)->first();
$user->password = bcrypt($request->password);
$user->reset_digest = null;
$user->save();
Auth::login($user);
session()->flash('message', ['success' => 'Password has been reset.']);
return redirect()->route("users.show", $user->id);
}
(/app/User.php)
```php
public function checkExpiration()
{
return $this->reset_sent_at < Carbon::now()->subHours(2);
}
12.3.3 パスワードの再設定をテストする
(/tests/Feature/PasswordResetsTest)
```php
class PasswordResetsTest extends TestCase
{
private $user;
protected function setUp()
{
parent::setUp();
Artisan::call('migrate:fresh');
$this->seed('TestSeeder');
$this->user = User::find(1);
}
public function testPasswordResets()
{
Mail::fake();
$response = $this->get(route("resets.create"));
$response->assertViewIs("password_resets.create");
$response = $this->post(route("resets.store"), ["email" => ""]);
$response->assertSessionHas("message");
$response->assertRedirect(route("resets.create"));
$response = $this->post(route("resets.store"), ["email" => $this->user->email]);
$this->assertNotEquals($this->user->reset_digest, User::find(1)->reset_digest);
Mail::assertSent(PasswordReset::class, 1);
$response->assertSessionHas("message");
$response->assertRedirect("/");
$reset_token = str_random(22);
$this->user->update(["reset_digest" => bcrypt($reset_token)]);
$response = $this->get(route("resets.edit", ["token" => $reset_token, "email" => ""]));
$response->assertRedirect("/");
$this->user->update(["activated" => false]);
$response = $this->get(route("resets.edit", ["token" => $reset_token, "email" => $this->user->email]));
$response->assertRedirect("/");
$this->user->update(["activated" => true]);
$response = $this->get(route("resets.edit", ["token" => "wrong token", "email" => $this->user->email]));
$response->assertRedirect("/");
$response = $this->followingRedirects()
->get(route("resets.edit", ["token" => $reset_token, "email" => $this->user->email]));
$response->assertViewIs("password_resets.edit");
$dom = $this->dom($response->content());
$this->assertSame(1, $dom->filter("input[name=email][type=hidden][value=\"{$this->user->email}\"]")->count());
$response = $this->followingRedirects()
->patch(route("resets.update", $reset_token), [
"email" => $this->user->email,
"password" => "foobaz",
"password_confirmation" => "barquux"
]);
$this->assertSame(1, $this->dom($response->content())->filter("div#error_explanation")->count());
$response = $this->followingRedirects()
->patch(route("resets.update", $reset_token), [
"email" => $this->user->email,
"password" => "",
"password_confirmation" => ""
]);
$this->assertSame(1, $this->dom($response->content())->filter("div#error_explanation")->count());
$response = $this->followingRedirects()
->patch(route("resets.update", $reset_token), [
"email" => $this->user->email,
"password" => "foobaz",
"password_confirmation" => "foobaz"
]);
$this->assertTrue(Auth::check());
$response->assertSeeText("Password has been reset.");
$response->assertViewIs("users.show");
}
}
```