0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Laravel で Ruby on Rails チュートリアル【12章】

Posted at

環境

  • Laravel 5.5
  • PHP 7.2

この章でやること

12.1 PasswordResetsリソース

12.1.1 PasswordResetsコントローラー

ルート追加(/routes/web.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)

{{ Html::linkRoute("resets.create", "(forgot password)") }}

12.1.2 新しいパスワードの設定

ミグレーション(/dataase/migrations/[timestamp]_add_reset_to_users.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)

@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)

    public function create()
    {
        return view("password_resets.create");
    }

12.1.3 createアクションでパスワード再設定

(/app/Http/Conttollers/PasswordResetsController.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)

    public function sendPasswordResetMail($token)
    {
        Mail::to($this)->send(new PasswordReset($this, $token));
    }

12.2 パスワード再設定のメール送信

12.2.1 パスワード再設定のメールとテンプレート

(/app/Mail/PasswordReset.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');
    }
}

(/resources/views/emails/password_reset.blade.php)

<h1>Password reset</h1>

<p>To reset your password click the link below:</p>

{{ Html::linkRoute("resets.edit", "Reset password", ["token" => $reset_token, "email" => $user->email]) }}

<p>This link will expire in two hours.</p>

<p>
If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
</p>

12.2.2 送信メールのテスト

(/tests/Unit/MailerTest.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)

@extends('layouts.application')

@section('title', 'Reset password')

@section('content')
<h1>Reset password</h1>

<div class="row">
    <div class="col-md-6 col-md-offset-3">
        {{ Form::model($user, ["route" => ["resets.update", $token], "method" => "patch"]) }}
        @include('shared.error_messages')
        {{ Form::hidden('email', $user->email) }}
        {{ Form::label('password') }}
        {{ Form::password('password', ["class" => "form-control"]) }}
        {{ Form::label('password_confirmation') }}
        {{ Form::password('password_confirmation', ["class" => "form-control"]) }}
        
        {{ Form::submit("Update password", ["class" => "btn btn-primary"]) }}
        {{ Form::close() }}
    </div>
</div>
@endsection

(/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)

    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)

    public function checkExpiration()
    {
        return $this->reset_sent_at < Carbon::now()->subHours(2);
    }

12.3.3 パスワードの再設定をテストする

(/tests/Feature/PasswordResetsTest)

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");
    }
}

12.4 本番環境でのメール送信(再掲)

12.5 最後に

12.6 照明期限切れの比較

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?