NuxtとRailsアプリ(Devise Token Auth)の結合テストをする をしてみて気付きましたが、「メールアドレス変更時に送信されるメールの確認URL」と「アカウントロック時に送信されるメールの解除URL」がフロントURLにならなかったので対応しました。
デフォルトURLを設定している場合は、それが使われます。(確認URLのみ)

そもそも

フロントのURLをバックエンドが知っている必要はないので、APIで渡したものを使いたい。
(但し、ドメインはホワイトリストで縛ってセキュアにする)

登録情報変更APIとログインAPIでURLを渡していない。
渡せるか確認しましたが、受け取るようになっていなかった。

ソース確認

devise_token_auth-1.2.0/app/models/devise_token_auth/concerns/user.rb

    # override devise method to include additional info as opts hash
    def send_confirmation_instructions(opts = {})
      generate_confirmation_token! unless @raw_confirmation_token

      # fall back to "default" config name
      opts[:client_config] ||= 'default'
      opts[:to] = unconfirmed_email if pending_reconfirmation?
      opts[:redirect_url] ||= DeviseTokenAuth.default_confirm_success_url

      send_devise_notification(:confirmation_instructions, @raw_confirmation_token, opts)
    end

RegistrationsControllerのcreateとConfirmationsControllerのcreateから、このメソッドが呼ばれているが、RegistrationsControllerのupdateからは直接呼ばれていない。

    # override devise method to include additional info as opts hash
    def send_unlock_instructions(opts = {})
      raw, enc = Devise.token_generator.generate(self.class, :unlock_token)
      self.unlock_token = enc
      save(validate: false)

      # fall back to "default" config name
      opts[:client_config] ||= 'default'

      send_devise_notification(:unlock_instructions, raw, opts)
      raw
    end

UnlocksControllerのcreateからこのメソッドが呼ばれているが、SessionsControllerのcreateからは直接呼ばれていない。

コメント通りdeviseをoverrideしているが、RegistrationsControllerのupdateやSessionsControllerのcreateは、deviseの処理から呼ばれる為、optsが渡されてないし、そっちをoverrideしないと渡せない。

フロント修正

まずは、それぞれのAPIにredirect_urlを追加。
作成しているNuxtアプリに入れます。

components/users/edit/InfoEdit.vue

      await this.$axios.put(this.$config.apiBaseURL + this.$config.userUpdateUrl, {
        name: this.name,
        email: this.email,
        password: this.password,
        password_confirmation: this.password_confirmation,
        current_password: this.current_password,
+        confirm_redirect_url: this.$config.frontBaseURL + this.$config.confirmationSuccessUrl
      })

pages/users/sign_in.vue

      await this.$auth.loginWith('local', {
        data: {
          email: this.email,
          password: this.password,
+          unlock_redirect_url: this.$config.frontBaseURL + this.$config.unlockRedirectUrl
        }
      })

ここまでのコミット内容
https://dev.azure.com/nightonly/nuxt-app-origin/_git/nuxt-app-origin/commit/e34bdae073a41c5dde70b5e4d1f65afcec52419d

修正方針検討

overrideを増やしたくない。<- Gemに任せている範囲が狭くなるので、保守コストが上がる。
グローバル変数は使いたくない。<- スコープが広過ぎるので、危うい。

Devise::Mailer継承して(既にしていますが)、メールテンプレに渡すのが良さそう。
ただ、paramsやrequestは、ここでは参照できない。

ベストではないですが、controllerから渡せる値はrecord(Users)ぐらいなので、attr_accessorを使う事にしました。
作成しているRailsアプリに入れます。

app/mailers/devise_mailer.rb

  def confirmation_instructions(record, token, opts = {})
    send_mail(super)
  end

  def unlock_instructions(record, token, opts = {})
    send_mail(super)
  end

バックエンド修正

app/models/user.rb に追加

  attr_accessor :redirect_url # Tips: /users/auth/{update,sign_in}で使用

app/controllers/users/auth/registrations_controller.rb

  def update
    if params[:confirm_redirect_url].blank?
      return render './failure', locals: { alert: t('devise_token_auth.registrations.confirm_redirect_url_blank') }, status: :unprocessable_entity
    end
    if blacklisted_redirect_url?(params[:confirm_redirect_url])
      return render './failure', locals: { alert: t('devise_token_auth.registrations.confirm_redirect_url_not_allowed') }, status: :unprocessable_entity
    end

    @resource.redirect_url = params[:confirm_redirect_url]
    super
  end

confirm_redirect_urlパラメータのチェックと値を@resource(Users)にセットしています。

app/controllers/users/auth/sessions_controller.rb

  def create
    return render './failure', locals: { alert: t('devise_token_auth.sessions.bad_credentials') }, status: :bad_request if request.request_parameters.blank?
    if params[:unlock_redirect_url].blank?
      return render './failure', locals: { alert: t('devise_token_auth.sessions.unlock_redirect_url_blank') }, status: :unprocessable_entity
    end
    if blacklisted_redirect_url?(params[:unlock_redirect_url])
      return render './failure', locals: { alert: t('devise_token_auth.sessions.unlock_redirect_url_not_allowed') }, status: :unprocessable_entity
    end
    return render_create_error_bad_credentials if params[:email].blank? || params[:password].blank?

    super
  end

  private

  def find_resource(field, value)
    super
    @resource.redirect_url = params[:unlock_redirect_url] if @resource.present? && params[:unlock_redirect_url].present?
    @resource
  end

同様にunlock_redirect_urlパラメータのチェックと値を@resource(Users)にセットしています。
ただ、こちらは@resourceがセットされてない状態で、createが呼ばれるので、find_resourceをoverrideして、直後にセットするようにしました。

app/mailers/devise_mailer.rb

class DeviseMailer < Devise::Mailer
  layout 'mailer'

  # メールアドレス確認のお願い
  def confirmation_instructions(record, token, opts = {})
+    opts[:redirect_url] = record.redirect_url if record.is_a?(User) && record.redirect_url.present? # Tips: /users/auth/updateで使用
    send_mail(super)
  end

  # アカウントロックのお知らせ
  def unlock_instructions(record, token, opts = {})
+    opts[:redirect_url] = record.redirect_url if record.is_a?(User) && record.redirect_url.present? # Tips: /users/auth/sign_inで使用
    send_mail(super)
  end

controllerでセットしたURLをoptsに詰めれば、メールテンプレートで受け取れるようになります。


実際はテスト駆動で開発していますので、Specはコミットを参照してください。

ここまでのコミット内容
https://dev.azure.com/nightonly/rails-app-origin/_git/rails-app-origin/commit/2de15f847ab43525bf222ca23b5b29551458f90f

コメントを残す

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