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はコミットを参照してください。
