Rails Diary

プログラミングの学習記録です。

パスワードのリセット機能①

何が何だか分からなくなってきたので、小分けにしてまとめ直します。

まず、sorceryのreset_passwordサブモジュールを使ったパスワードリセット機能から。

reset_passwordサブモジュールをインストール

bundle exec rails g sorcery:install reset_password --only-submodules

このコマンドにより、以下のマイグレーションファイルが生成される。

  • リセットパスワードトーク
  • リセットパスワードトークン期限切れ
  • リセットパスワードトークン送信の日付
class SorceryResetPassword < ActiveRecord::Migration
  def change
    add_column :users, :reset_password_token, :string, default: nil
    add_column :users, :reset_password_token_expires_at, :datetime, default: nil
    add_column :users, :reset_password_email_sent_at, :datetime, default: nil

# wikiに書いていない以下二つも自動的に作成されていた
    add_column :users, :access_count_to_reset_password_page, :integer, default: 0
    add_index :users, :reset_password_token
  end
end

※ reset_password_tokenにunique制約を付与する。
add_index :users, reset_password_token, unique: true

反映させておく。

user.rbにバリデーションを追加

いつも通り、DB側にunique: trueを追加したので、バリデーションにもuniqueness: trueを追加する。加えて、allow_nil: trueも追加する。

トークンはデフォルトがnilで設定されているので、nilを許容してあげないとデフォルトのnilが重複と見なされて複数のユーザーが登録できなくなってしまう。

validates :reset_password_token, uniqueness: true, allow_nil: true

メイラーの作成

bundle exec rails g mailer UserMailer reset_password_email

コントローラーと似ている(違う点もあるらしい)
上のコマンドにより、生成されたuser_mailer.rb内にreset_password_emailメソッドが記載されている。

def reset_password_email
end

# 引数にuserを追加する
def reset_password_email(user)
  @user = User.find(user.id)
  @url  = edit_password_reset_url(@user.reset_password_token)
  mail(to: user.email, subject: "Your password has been reset")
end

↓ user_mailer.rbで取得した変数をメールテキストで用いる

# app/views/user_mailer/reset_password_email.text.erb
Hello, <%= @user.email %>
===============================================
 
You have requested to reset your password.

To choose a new password, just follow this link: <%= @url %>.
# このリンクで後に作成するコントローラのeditアクションに飛ばされる
 
Have a great day!

パスワードリセットに関するコントローラーを作成する

bundle exec rails g controller PasswordResets new edit
# app/controllers/password_resets_controller.rb
class PasswordResetController < ApplicationController
# before_action:require_loginが宣言されていない場合、エラーが発生する
  skip_before_action :require_login

def new; end

# パスワードのリセット要求
  def create
# ユーザーがパスワードのリセットフォームに入力したメアドがここに送られる。
    @user = User.find_by_email(params[:email])
# @userがtrueならば、リセット方法が書かれたメールをユーザーに送信する
    @user.deliver_reset_password_instructions! if @user
    redirect_to(root_path, notice: ‘Instructions have been sent to your email.’)
# メール自体が送信成功していてもいなくてもメールが送信された内容のフラッシュメッセージを出す。システムに存在する電子メールに関する情報を、悪意あるユーザーに漏らさないため。
  end
  
# パスワードのリセットフォーム 
# メールに記載されたトークンを含むURLからトークンを取得
  def edit
    @token = params[:id]
    @user = User.load_from_reset_password_token(params[:id])

    if @user.blank?
      not_authenticated
      return
    end
  end

# ユーザーがパスワードのリセットフォームを送信した時に発生する
  def update
    @token = params[:id]
    @user = User.load_from_reset_password_token(params[:id])
    if @user.blank?
      not_authenticated
      return
    end

    @user.password_confirmation = params[:user][:password_confirmation]
    if @user.change_password(params[:user][:password])
      redirect_to(root_path, notice: ‘Password was successfully updated.’)
    else
      render :edit
    end
  end
end
resources :password_resets, only: %i[new create edit update]

new.html.erbにパスワード変更メールを送るメールアドレス入力フォームを作成する

<!-- createアクションへ送信 -->
<%= form_with url: password_resets_path, local: true do |f| %>
  <div class="form-group">
    <%= f.label :email, User.human_attribute_name(:email) %>
    <%= f.email_field :email, class: 'form-control' %>
  </div>
  <div class="text-center">
    <%= f.submit (t'.submit'), class: 'btn btn-primary' %>
  </div>
<% end %>

このフォームにメールアドレスを入力すると、password_resetsコントローラのcreateアクションが起動し、送られたメアドからユーザーを検索、ユーザーが存在するならばそのユーザーにメールを送信する。トップページに遷移し、メールが送られたかどうかに関わらずフラッシュメッセージが表示される。(実在しないメアドだったときに失敗メッセージを表示してしまう仕様だと、もし適当にメールアドレスを入力してもそれが実在するかどうか他者が判別できてしまうため。)

def create
  @user = User.find_by_email(params[:email])
  @user.deliver_reset_password_instructions! if @user
  redirect_to(root_path, notice: ‘Instructions have been sent to your email.’)
end

次回、letter_opener_webを用いた擬似メールプレビュー
続く>>>