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 } })
修正方針検討
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はコミットを参照してください。