【Rails】sorcery(Reset password)

はじめに

 sorceryとは、Railsに認証機能の実装を行うためのライブラリです。 同じように認証機能を提供してくれているものとしてdeviseなどが挙げられますが、sorceryの方がよりシンプルで、カスタマイズ性に富んでいるという特徴を持ちます。今回はsorceryのReset Passwordモジュールの実装方法を記していきます。

実装方法

モジュールのインストール

$ rails g sorcery:install reset_password --only-submodules
Running via Spring preloader in process 30110
        gsub  config/initializers/sorcery.rb
      insert  app/models/user.rb
      create  db/migrate/20200419075054_sorcery_reset_password.rb

これで、password_resetサブモジュールを使用するための記述がconfig/initializers/sorcery.rbに、自動で行われました。また、マイグレーションファイルなども作成されます。

マイグレーションファイル

class SorceryResetPassword < ActiveRecord::Migration[5.2]
  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
    add_column :users, :access_count_to_reset_password_page, :integer, default: 0

    add_index :users, :reset_password_token
  end
end

この、マイグレーションファイルをDBに反映させます。

$ rails db:migrate

パスワードリセット用のMailerを作成

まず、Mailerを作成します。

$ rails g mailer UserMailer reset_password_email
Running via Spring preloader in process 88211
      create  app/mailers/user_mailer.rb
      invoke  erb
      create    app/views/user_mailer
      create    app/views/user_mailer/reset_password_email.text.erb
      create    app/views/user_mailer/reset_password_email.html.erb

次に、config/initializers/sorcery.rbの中で、sorceryのパスワードリセットに使用するActionMailerをUserMailerで指定します。

config/initializers/sorcery.rb

 Rails.application.config.sorcery.submodules = [:reset_password]

 Rails.application.config.sorcery.configure do |config|
   config.user_config do |user|
 # パスワードリセット用のMailerにUserMailerを指定する
     user.reset_password_mailer = UserMailer
  end
end

それでは、パスワードリセット用のメソッドを記述します。

class UserMailer < ApplicationMailer
  def reset_password_email(user)
    @user = User.find(user.id)
    @url  = edit_password_reset_url(@user.reset_password_token)
    mail(to: user.email,
         subject: 'パスワードリセット')
  end
end
# reset_password_emailメソッドなので、reset_password_email.〇〇のビューがメールのフォーマットになる
# メイラーのメソッド内で定義されたインスタンス変数はメイラーのビューで使える。

最後に、メイラービューの設定をします。

app/views/user_mailer/reset_password_email.html.erb

<h1><%= @user.decorate.full_name %>様</h1> 
<p>=====================================</p>

<p>パスワード再発行のご依頼を受け付けました。</p><br>

<p>こちらのリンクからパスワードの再発行を行ってください。</p>

<p><a href="<%= @url %>"><%= @url %></a></p>

app/views/user_mailer/reset_password_email.text.erb

<%= @user.decorate.full_name %>様
===========================================

パスワード再発行のご依頼を受け付けました。

こちらのリンクからパスワードの再発行を行ってください。
<%= @url %>

リセットをするためのフォームからコントローラまで

routes.rb

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

まずは、パスワードリセットのトークンを発行しメールを送るためのフォーム。

passowrd_reserts/new.html.erb

<% content_for(:title, t('.title')) %>
<div class="container">
  <div class="row">
    <div class=" col-md-10 offset-md-1 col-lg-8 offset-lg-2">
      <h1><%= t('.title') %></h1>
      <%= form_with url: password_resets_path, method: :post, 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="actions">
          <%= f.submit t('default.sent'), class: 'btn btn-primary' %>
        </div>
      <% end %>
    </div>
  </div>
</div>

email情報からパスワードをリセットしたい、userを抜き出す。

app/controllers/password_resets_controller.rb

 class PasswordResetsController < ApplicationController
  skip_before_action :require_login

  def new; end

  def create
    @user = User.find_by_email(params[:email])
    # ここで先ほど定義したメソッドのapp/mailers/user_mailer.rbへ
    @user&.deliver_reset_password_instructions!
    redirect_to login_path, success: 'パスワードリセット手順を送信しました'
  end

  def edit
    @token = params[:id]
    @user = User.load_from_reset_password_token(params[:id])
    not_authenticated if @user.blank?
  end

  def update
    @token = params[:id]
    @user = User.load_from_reset_password_token(params[:id])

    return not_authenticated if @user.blank?

    @user.password_confirmation = params[:user][:password_confirmation]
    if @user.change_password(params[:user][:password])
      redirect_to login_path, success: 'パスワードを変更しました'
    else
      flash.now[:danger] = 'パスワードを変更できませんでした'
      render action: 'edit'
    end
  end
end

createメソッドが動き、さきほどの設定したメールテンプレートが送信されます。そのトークンが含まれたURLからeditアクションを起動しapp/views/password_resets/edit.html.erbへ遷移されます。

app/views/password_resets/edit.html.erb

<% content_for(:title, t('.title')) %>
<div class="container">
  <div class="row">
    <div class=" col-md-10 offset-md-1 col-lg-8 offset-lg-2">
    <h1><%= t('.title') %></h1>
      <%= form_for @user, :url => password_reset_path(@token), :html => {:method => :put} do |f| %>
        <%= render 'shared/error_messages', object: f.object %>

        <div class="form-group">
          <%= f.label :email %><br />
          <%= @user.email %>
        </div>
        <div class="form-group">
          <%= f.label :password %><br />
          <%= f.password_field :password, class: 'form-control' %>
        </div>
        <div class="form-group">
          <%= f.label :password_confirmation %><br />
          <%= f.password_field :password_confirmation, class: 'form-control' %>
        </div>
        <div class="actions">
          <%= f.submit class: 'btn btn-primary' %>
        </div>
      <% end %>
    </div>
  </div>
</div>

このフォームでボタンを押すと、updateアクションが起動し、パスワードリセット完了です。

参考資料

sourcery公式