Rails tutorial 12章 ~パスワードリセット~

こんにちは。

社会人一年目のkesuikeです。

相も変わらずtutorialを進めております。

大学も卒業したので、近々学生の間にやったほうがいいことをまとめてブログにUPしようと思います。

さてさて、今日はRails tutorial 12章の要約を載せていきたいと思います。

正直僕はここはすごい嫌いですが、ここを乗り越えれば楽しい楽しい投稿やいいねなどのよりTwitterっぽい実装に入れるので頑張ります。

パスワードリセットの大まかな手順

1.ユーザーがパスワード再設定をリクエストすると送られてきたメールアドレスからユーザーを特定する。

2.再設定用のトークンとリセットダイジェストを作成する

3.リセットダイジェストはDBに保存し、トークンはユーザーに送るメールのリンクに仕込む

4.ユーザーがリンクをクリックしたら、メールアドレスをキーにユーザーを探し、リセットダイジェストと比較。

5.認証されたら再設定用のフォームを表示する

この流れで実装していきたいと思います。

最初にPassword_resetのモデリングからやります。

今回はパスワードを設定するためのモデルが必要なのでnew,editの2つのアクションを作成します。

rails g controller PasswordResets new edit --no-test-framework

次にルーティングの設定も行います。

routes.rb

resources :password_resets, only:[:new, :create, :edit, :update]

これで名前付きルートも使えるようになります。

ログインフォームにパスワードを忘れた時用のリンクを作ります。

sessoins/new.html.erb

<%= f.label :password %>
      <%= link_to "(forgot password)", new_password_reset_path %>
      <%= f.password_field :password, class: 'form-control' %>

これでパスワード再設定画面へのリンクの作成が完了しました。

次にメールアドレス有効化の時と同じようにトークンとダイジェストの設定を行います。

今回はダイジェストだけでなくメールを送信した時間を記憶するreset_sent_atも追加します。

リンクへのアクセスには時間制限を設けたいからです。

カラムの追加のコマンドは何回も紹介しているので割愛します。

実際にメールアドレスを入力するフォームを作成します。

password_resets/new.html.erb

<% provide(:title, "Forget password" %>
<h1>Forget password</h1>

<div class="row>
  <div class="col-md-3 col-md-offset-3">
    <%= form_for(:password_resets, url:password_resets_path) do |f| %>
      <%= f.email %>
      <%= f.email_field:email, class:'form-control' %>

      <%= f.submit"Submit" class:"btn btn-primary" %>
    <% end %>
  </div>
</div>

ここから受けっとたメールアドレスからユーザーを探し出します。

ちなみに、、、気づいた方もいると思いますがこのフォーム、ログインフォームとかなり似ていますよね。後々リファクタリングをした方が良さそうです。

さてさて、フォームから受け取ったメールアドレスからユーザーを探すのですが、これはcreateアクションの出番ですね。

Password_resets_controller.rb

def create
  @user = User.find_by(email: [:password_resets][: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

Userが見つけ出せたら2つのメソッドを実行してroot_urlにリダイレクトするって感じですね。

次にその2つのメソッドを定義しましょう。

User.rb

attr_asseccor :reset_token
.
.
.
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

1つ目のメソッドはトークンを作成して、それをダイジェストにしたものと、メールを送信した時間を保存しています。

次にメールの設定ですが、ここもアカウントの有効化と酷似しているので割愛させていただきます。

続いてリンクをクリックして再設定用のフォームを表示させます。

パスワードの登録のみですのでフォームを作るのは比較的簡単だと思います。

ただ、今回メールアドレスをキーにしてユーザーを探し出しているためupdateアクションでもメールアドレスが必要になります。

そのため、hidden_field_tagというヘルパーを新たに使います。

edit.html.erb

<% provide(:title, 'Reset password') %>
<h1>Reset password</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= hidden_field_tag :email, @user.email %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Update password", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

これでupdateアクションでもメールアドレスが利用できます。

早速このフォームを描画と言いたいところですが、その前に有効化や認証を実装しなければいけません。

これらはeditアクションの前、つまりbefore_actionで実装します。

password_resets_controller.rb

before_action :get_user, only:[:edit, :update]
before_action :valid_user, only:[:edit, :update]
.
.
.
def get_user
  @user = User.find_by(email: params[:email])
end

def valid_user
  unless (@user && @user.activated? &&
          @user.autenticated?(:reset, params[:id]))
    redirect_to root_url
  end
end

これらを通過した人のみ、パスワードを変更できます。

パスワードの更新の際には以下の項目をチェックします。

1.パスワード再設定の期限

2.無効なパスワードなら保存しない

3.空になっていないか

4.正しければ更新する

以上の項目を気をつけ実装します。

Password_resets_controller.rb

before_aciton check_expiration, only:[:edit, :update] #1番の対応
.
.
.
def update
  if params[:user][:password].empty?  #3番の対応
    @user.errors.add(:password, :blank)
    render 'edit'
  elsif @user.update_attribute(user_params) #4番の対応
    log_in @user
    flash[:success] = "Password has been reset"
    redirect_to @user
  else
    render 'edit' #2番の対応
  end
end

def user_params
  params.require(:user).permit(:password, :password_confirmation)
end

def check_expiration
  if @user.password_reset_expired?
    flash[:danger] = "Password reset has expired."
    redirect_to new_password_resert_url
  end
end

User,rb

def password_reset_expired?
  reset_sent_time < 2.hours.ago
end

以上でパスワード再設定の解説はおしまいです。

最後まで見ていただきありがとうございました。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です