📌

【Rails】論理削除のあるモデルのログインをDeviseで作成

2023/12/13に公開

abstract

  • 論理削除(的なもの)のある会員モデルのログイン機能をdeviseで作成した
  • ログイン処理をオーバーライドし、メールアドレスから会員情報を取得する際に退会日時も見るようにしたが、退会済みユーザーもログインできる状態になってしまった
  • 退会処理時にパスワードを空にしてしまうのが手っ取り早そう

やったこと

会員(User)モデルのログイン機能をdeviseを用いて作成しました。
userが退会した際には、退会日時retired_atをセットすることで論理削除を行い、退会後一定期間が経過してから物理削除を行う仕様にしていました。

まずsessions_controllerをカスタマイズし

class Users::SessionsController < Devise::SessionsController
  def create
    @user = User.new(sign_in_params)
    auth_user = @user.authenticate!
    sign_in(resource_name, auth_user)
    respond_with auth_user, location: after_sign_in_path_for(auth_user)
  rescue
    render :new
  end
end

Userモデルのauthenticate!メソッドで、メールアドレスから該当ユーザーを取得するタイミングで退会日時がセットされていないことも条件に追加

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :confirmable, :trackable
	 
  def authenticate!
    validate!(:session)
    resource = User.find_by(email: email, retired_at: nil)
    if resource && resource.valid_password?(password)
      resource
    else
      clean_up_passwords
      raise ActiveRecord::RecordInvalid.new(self.new)
    end
  end
end

※このへんの処理はちょっと違和感があるんですけど普通にユーザー認証として機能します

そして適当にログインフォームを用意

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
  <%= f.label :email %>
  <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  <%= f.label :password %>
  <%= f.password_field :password, autocomplete: "current-password" %>
  <div class="btn">
    <%= f.submit "ログイン" %>
  </div>
<% end %>

発生した問題

退会済みユーザーもログインできてしまう。
authenticate!メソッドでは意図通り例外が発生していて、ログイン画面に返されてログイン失敗のエラーは出るのですが、その後ページを移動するとログイン状態になっていました。

原因(分かったところまで)

原因はテンプレートファイル内でログイン/非ログイン時の表示の出し分けに使っていた、deviseのヘルパーメソッドuser_signed_in?でした。
認証失敗で例外が発生しログイン画面がレンダリングされる際に呼び出されていました。
このメソッドが定義されているのはdeviseの/lib/devise/controllers/helpers.rb内の以下の部分と思われます

            def #{group_name}_signed_in?
              #{mappings}.any? do |mapping|
                warden.authenticate?(scope: mapping)
              end
            end

モデルに自前で実装したauthenticate!では退会日時を見ていますが、warden.authenticate?の部分でメールアドレスとパスワードで認証されてしまうようです。
wardenはdeviseのベースとなっているgemで、Rack内で認証を行うミドルウェアです。warden.authenticate?の詳細な挙動までは追い切れていません。気が向いたらwardenのコードリーディングもしてみたいと思っています。

対処

退会処理の際、retired_atに退会時刻を入れるだけでしたが、この時encrypted_passwordのカラムを空にしてしまうのが面倒がなさそうです。退会時すぐに物理削除しなかったのは一定期間メールアドレスを残しておくことが目的だったため今回のケースではこれで支障はありません。

Discussion