Ryota400’s blog

エンジニアを目指して書いてます。

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

sourceryのreset_passwordモジュールの導入

$ rails g sorcery:install reset_password --only-submodules
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
rails db:migrate

UserMailerという名前でパスワードリセットメール用のMailerを作成

$ rails g mailer UserMailer reset_password_email

sorcery.rbにreset_passwordサブモジュールを追加し、パスワードリセットに使用する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|
    user.reset_password_mailer = UserMailer
  end
end

ルーティング、コントローラー、viewファイル作成

・コントローラー

rails g controller PasswordResets new create edit update

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])
    @user&.deliver_reset_password_instructions!
    # 「存在しないメールアドレスです」という旨の文言を表示すると、逆に存在するメールアドレスを特定されてしまうため、
    # あえて成功時のメッセージを送信させている
    redirect_to login_path, success: t('.success')
  end

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

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

    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: t('.success')
    else
      flash.now[:danger] = t('.fail')
      render :edit
    end
  end
end

・ルーティングの設定

Rails.application.routes.draw do

  mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?

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

end

・viewファイル作成

app/views/password_resets/edit.html.erb

パスワードリセット申請後、送られてきたメールのURLにアクセスすると、表示される画面の実装

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

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

app/views/password_resets/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, local: true do |f| %>
        <div class="form-group">
          <%= f.label :email, t(User.human_attribute_name(:email)) %><br />
          <%= f.email_field :email, class: 'form-control' %>
        </div>
        <%= f.submit t('password_resets.new.submit'), class: 'btn btn-primary' %>
    <% end %>
    </div>
  </div>
</div>

app/views/user_sessions/new.html.erb

 <%= link_to (t '.password_forget'), new_password_reset_path %>

メール文の作成

app/views/user_mailer/reset_password_email.html.erb

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

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

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

app/views/user_mailer/reset_password_email.text.erb

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

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

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

ユーザーにメールを送信するメソッド部分

app/mailers/user_mailer.rb

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: t('defaults.password_reset'))
  end
end

Gemfileにletter_opener_webを追加

group :development do
  gem 'letter_opener_web', '~> 1.0'
end
bundle install

config/environments/development.rbに設定を追加

config.action_mailer.delivery_method = :letter_opener_web config.action_mailer.default_url_options = Settings.default_url_options.to_h

config/environments/test.rb

config.action_mailer.default_url_options = Settings.default_url_options.to_h

環境変数設定

gem 'config'
bundle install

gemの機能で設定ファイルを生成

rails g config:install

カスタマイズ可能な設定ファイルconfig/initializers/config.rbとデフォルト設定ファイルのセットを生成 → config/settings.yml

config/settings/development.yml

config/settings/production.yml

config/settings/test.yml

config/settings/development.yml

default_url_options:
  host: 'localhost:3000'

config/settings/test.ym

default_url_options:
  host: 'localhost:3000'

参考文献

github.com