今日やったこと12
第12章 パスワードの再設定
全体の流れ
1.ユーザーがパスワードの再設定をリクエストすると、ユーザーが送信したメールアドレスをキーにしてデータベースからユーザーを見つける
2.該当のメールアドレスがデータベースにある場合は、再設定用トークンとそれに対応するリセットダイジェストを生成する
3.再設定用ダイジェストはデータベースに保存しておき、再設定用トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく
4.ユーザーがメールのリンクをクリックしたら、メールアドレスをキーとしてユーザーを探し、データベース内に保存しておいた再設定用ダイジェストと比較する (トークンを認証する)
5.認証に成功したら、パスワード変更用のフォームをユーザーに表示する
12.1 PasswordResetsリソース
トピックブランチを作成。
$ git checkout -b password-reset
パスワード再設定用のコントローラを作る。その際、newアクションとeditアクションも一緒に生成する。
$ rails generate controller PasswordResets new edit --no-test-framework
ルーティングを設定。
config/routes.rb Rails.application.routes.draw do ・ ・ ・ resources :users resources :account_activations, only: [:edit] resources :password_resets, only: [:new, :create, :edit, :update] end
HTTPリクエスト | URL | Action | 名前付きルート |
---|---|---|---|
GET | /password_reset/new | new | new_password_resets_path |
POST | /password_reset | create | password_resets_path |
GET | /password_reset/(token)/edit | edit | edit_password_resets_url(token) |
PATCH | /password_reset/(token) | update | password_resets_url(token) |
パスワード再設定画面へのリンクを追加する。
app/views/sessions/new.html.erb <% provide(:title, "Log in") %> <h1>Log in</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(:session, url: login_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= link_to "(forgot password)", new_password_reset_path %> <!-- リンクを追加 --> <%= f.password_field :password, class: 'form-control' %> <%= f.label :remember_me, class: "checkbox inline" do %> <%= f.check_box :remember_me %> <span>Remember me on this computer</span> <% end %> <%= f.submit "Log in", class: "btn btn-primary" %> <% end %> <p>New user? <%= link_to "Sign up now!", signup_path %></p> </div> </div>
reset_digest属性とreset_sent_at属性をUserモデルに追加する。
下記を実行して、マイグレーションに属性を追加。
$ rails generate migration add_reset_to_users reset_digest:string reset_sent_at:datetime
$ rails db:migrate
新しいパスワード再設定画面ビューを作成する。
app/views/password_resets/new.html.erb <% provide(:title, "Forgot password") %> <h1>Forgot password</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(:password_reset, url: password_resets_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.submit "Submit", class: "btn btn-primary" %> <% end %> </div> </div>
フォームから送信後、メールアドレスをキーとしてユーザーをデータベースから見つけ、パスワード再設定用トークンと送信時のタイムスタンプでデータベースの属性を更新する必要がある。続いてルートURLにリダイレクトし、フラッシュメッセージをユーザーに表示する。送信が無効の場合は、ログインと同様にnewページを出力してflash.nowメッセージを表示する。
app/controllers/password_resets_controller.rb class PasswordResetsController < ApplicationController def new end def create @user = User.find_by(email: params[:password_reset][:email].downcase) if @user @user.create_reset_digest @user.send_password_reset_email flash[:info] = "Email sent with password reset instructions" redirect_to root_url else flash.now[:danger] = "Email address not found" render 'new' end end def edit end end
Userモデルにパスワード再設定用メソッドを追加。
app/models/user.rb class User < ApplicationRecord attr_accessor :remember_token, :activation_token, :reset_token before_save :downcase_email before_create :create_activation_digest . . . # パスワード再設定の属性を設定する def create_reset_digest self.reset_token = User.new_token update_attribute(:reset_digest, User.digest(reset_token)) update_attribute(:reset_sent_at, Time.zone.now) end # パスワード再設定のメールを送信する def send_password_reset_email UserMailer.password_reset(self).deliver_now end private . . . end
12.2 パスワード再設定のメール送信
Userメイラーのpassword_resetメソッドを変更し、テキストメールのテンプレートとHTMLメールのテンプレートもそれぞれ変更する。
app/mailers/user_mailer.rb class UserMailer < ApplicationMailer ・ ・ ・ def password_reset(user) @user = user mail to: user.email, subject: "Password reset" end end
app/views/user_mailer/password_reset.text.erb
To reset your password click the link below: <%= edit_password_reset_url(@user.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.
app/views/user_mailer/password_reset.html.erb
<h1>Password reset</h1> <p>To reset your password click the link below:</p> <%= link_to "Reset password", edit_password_reset_url(@user.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>
Railsのメールプレビュー機能でパスワード再設定のメールをプレビューするためにコードを追加する。
test/mailers/previews/user_mailer_preview.rb # Preview all emails at http://localhost:3000/rails/mailers/user_mailer class UserMailerPreview < ActionMailer::Preview ・ ・ ・ # Preview this email at # http://localhost:3000/rails/mailers/user_mailer/password_reset def password_reset user = User.first user.reset_token = User.new_token UserMailer.password_reset(user) end end
パスワード再設定用メイラーメソッドのテストを追加する。
test/mailers/user_mailer_test.rb require 'test_helper' class UserMailerTest < ActionMailer::TestCase ・ ・ ・ test "password_reset" do user = users(:michael) user.reset_token = User.new_token mail = UserMailer.password_reset(user) assert_equal "Password reset", mail.subject assert_equal [user.email], mail.to assert_equal ["noreply@example.com"], mail.from assert_match user.reset_token, mail.body.encoded assert_match CGI.escape(user.email), mail.body.encoded end end