emails: include logging information in email headers.
Log the following information in email headers: * X-Danbooru-User: the user's name and ID. * X-Danbooru-IP: the user's IP. * X-Danbooru-Session: the users' session ID. * X-Danbooru-URL: the page that triggered the email. * X-Danbooru-Job-Id: the ID of the background job that sent the email. * X-Danbooru-Enqueued-At: when the email was queued as a background job. * X-Danbooru-Dmail: for Dmail notifications, the link to the Dmail. * X-Request-Id: the request ID of the HTTP request that triggered the email. Also make it so we log an event in the APM when we send an email.
This commit is contained in:
@@ -58,7 +58,7 @@ class EmailsController < ApplicationController
|
||||
|
||||
def send_confirmation
|
||||
@user = authorize User.find(params[:user_id]), policy_class: EmailAddressPolicy
|
||||
UserMailer.welcome_user(@user).deliver_later
|
||||
UserMailer.with_request(request).welcome_user(@user).deliver_later
|
||||
|
||||
flash[:notice] = "Confirmation email sent to #{@user.email_address.address}. Check your email to confirm your address"
|
||||
redirect_to @user
|
||||
|
||||
@@ -12,7 +12,7 @@ class PasswordResetsController < ApplicationController
|
||||
flash[:notice] = "That account does not exist"
|
||||
redirect_to password_reset_path
|
||||
elsif @user.can_receive_email?(require_verified_email: false)
|
||||
UserMailer.password_reset(@user).deliver_later
|
||||
UserMailer.with_request(request).password_reset(@user).deliver_later
|
||||
UserEvent.create_from_request!(@user, :password_reset, request)
|
||||
flash[:notice] = "Password reset email sent. Check your email"
|
||||
respond_with(@user, location: new_session_path)
|
||||
|
||||
@@ -90,7 +90,7 @@ class UsersController < ApplicationController
|
||||
flash[:notice] = "Sign up failed: #{@user.errors.full_messages.join("; ")}"
|
||||
else
|
||||
session[:user_id] = @user.id
|
||||
UserMailer.welcome_user(@user).deliver_later
|
||||
UserMailer.with_request(request).welcome_user(@user).deliver_later
|
||||
set_current_user
|
||||
end
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class ApplicationJob < ActiveJob::Base
|
||||
PruneRateLimitsJob, ProcessUploadJob, RegeneratePostCountsJob,
|
||||
RegeneratePostJob, RetireTagRelationshipsJob, VacuumDatabaseJob,
|
||||
DiscordNotificationJob, BigqueryExportJob, ProcessBulkUpdateRequestJob,
|
||||
PruneJobsJob, ActionMailer::MailDeliveryJob
|
||||
PruneJobsJob, MailDeliveryJob
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
22
app/jobs/mail_delivery_job.rb
Normal file
22
app/jobs/mail_delivery_job.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# A replacement for the default ActionMailer::MailDeliveryJob that inherits from ApplicationJob, so
|
||||
# it inherits the same behavior as other jobs. It also inserts the job ID into the mail headers
|
||||
# for logging purposes.
|
||||
#
|
||||
# @see https://github.com/rails/rails/blob/main/actionmailer/lib/action_mailer/mail_delivery_job.rb
|
||||
# @see https://guides.rubyonrails.org/configuring.html#config-action-mailer-delivery-job
|
||||
# @see config/application.rb (config.action_mailer.delivery_job = "MailDeliveryJob")
|
||||
class MailDeliveryJob < ApplicationJob
|
||||
def perform(mailer, mail_method, delivery_method, args:, kwargs: nil, params: nil)
|
||||
mailer_class = mailer.constantize.with(params.to_h) # mailer_class = UserMailer.with(params)
|
||||
mail = mailer_class.public_send(mail_method, *args, **kwargs.to_h) # mail = UserMailer.welcome_user(user)
|
||||
|
||||
mail.headers(
|
||||
"X-Danbooru-Job-Id": job_id,
|
||||
"X-Danbooru-Enqueued-At": enqueued_at,
|
||||
)
|
||||
|
||||
mail.send(delivery_method) # mail.deliver_now
|
||||
end
|
||||
end
|
||||
11
app/logical/email_delivery_logger.rb
Normal file
11
app/logical/email_delivery_logger.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This is called just before an email is sent out to log information about the email.
|
||||
#
|
||||
# @see config/application.rb (config.action_mailer.interceptors)
|
||||
# @see https://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
|
||||
class EmailDeliveryLogger
|
||||
def self.delivering_email(email)
|
||||
DanbooruLogger.info("Delivering email to #{email.to}", headers: email.headers)
|
||||
end
|
||||
end
|
||||
@@ -3,12 +3,14 @@
|
||||
# The base class for emails sent by Danbooru.
|
||||
#
|
||||
# @see https://guides.rubyonrails.org/action_mailer_basics.html
|
||||
# @see app/logical/email_interceptor.rb
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
helper :application
|
||||
helper :users
|
||||
include UsersHelper
|
||||
|
||||
default from: "#{Danbooru.config.canonical_app_name} <#{Danbooru.config.contact_email}>", content_type: "text/html"
|
||||
default "Message-ID": -> { "<#{SecureRandom.uuid}@#{Danbooru.config.hostname}>" }
|
||||
|
||||
def mail(user, require_verified_email:, **options)
|
||||
# https://www.rfc-editor.org/rfc/rfc8058#section-3.1
|
||||
@@ -19,8 +21,29 @@ class ApplicationMailer < ActionMailer::Base
|
||||
headers["List-Unsubscribe"] = "<#{disable_email_notifications_url(user)}>"
|
||||
headers["List-Unsubscribe-Post"] = "List-Unsubscribe=One-Click"
|
||||
|
||||
headers["X-Danbooru-User"] = "#{user.name} <#{user_url(user)}>"
|
||||
if params.to_h[:request]
|
||||
headers["X-Danbooru-URL"] = params[:request][:url]
|
||||
headers["X-Danbooru-IP"] = params[:request][:remote_ip]
|
||||
headers["X-Danbooru-Session"] = params[:request][:session_id]
|
||||
headers["X-Request-Id"] = params[:request][:request_id]
|
||||
end
|
||||
|
||||
headers(params.to_h[:headers].to_h)
|
||||
|
||||
message = super(to: user.email_address&.address, **options)
|
||||
message.perform_deliveries = user.can_receive_email?(require_verified_email: require_verified_email)
|
||||
message
|
||||
end
|
||||
|
||||
def self.with_request(request)
|
||||
with(
|
||||
request: {
|
||||
url: "#{request.method} #{request.url}",
|
||||
remote_ip: request.remote_ip.to_s,
|
||||
request_id: request.request_id.to_s,
|
||||
session_id: request.session.id.to_s,
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -146,7 +146,7 @@ class Dmail < ApplicationRecord
|
||||
|
||||
def send_email
|
||||
if is_recipient? && !is_deleted? && to.receive_email_notifications?
|
||||
UserMailer.dmail_notice(self).deliver_later
|
||||
UserMailer.with(headers: { "X-Danbooru-Dmail": Routes.dmail_url(self) }).dmail_notice(self).deliver_later
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -379,7 +379,7 @@ class User < ApplicationRecord
|
||||
|
||||
if errors.none?
|
||||
UserEvent.create_from_request!(self, :email_change, request)
|
||||
UserMailer.email_change_confirmation(self).deliver_later
|
||||
UserMailer.with_request(request).email_change_confirmation(self).deliver_later
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,6 +74,14 @@ module Danbooru
|
||||
config.action_mailer.sendmail_settings = Danbooru.config.mail_settings
|
||||
end
|
||||
|
||||
# https://guides.rubyonrails.org/action_mailer_basics.html#intercepting-and-observing-emails
|
||||
# app/logical/email_delivery_logger.rb
|
||||
config.action_mailer.interceptors = ["EmailDeliveryLogger"]
|
||||
|
||||
# https://guides.rubyonrails.org/configuring.html#config-action-mailer-delivery-job
|
||||
# app/jobs/mail_delivery_job.rb
|
||||
config.action_mailer.delivery_job = "MailDeliveryJob"
|
||||
|
||||
config.log_tags = [->(req) {"PID:#{Process.pid}"}]
|
||||
config.action_controller.action_on_unpermitted_parameters = :raise
|
||||
|
||||
|
||||
@@ -147,6 +147,9 @@ class DmailsControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
assert_redirected_to Dmail.last
|
||||
assert_enqueued_emails 1
|
||||
|
||||
perform_enqueued_jobs
|
||||
assert_performed_jobs(1, only: MailDeliveryJob)
|
||||
end
|
||||
|
||||
should "not allow banned users to send dmails" do
|
||||
|
||||
@@ -113,8 +113,11 @@ class EmailsControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_redirected_to(settings_path)
|
||||
assert_equal("abc@ogres.net", @user.reload.email_address.address)
|
||||
assert_equal(false, @user.email_address.is_verified)
|
||||
assert_enqueued_email_with UserMailer, :email_change_confirmation, args: [@user], queue: "default"
|
||||
assert_equal(true, @user.user_events.email_change.exists?)
|
||||
|
||||
perform_enqueued_jobs
|
||||
assert_performed_jobs(1, only: MailDeliveryJob)
|
||||
# assert_enqueued_email_with UserMailer.with_request(request), :email_change_confirmation, args: [@user], queue: "default"
|
||||
end
|
||||
|
||||
should "create a new address" do
|
||||
@@ -127,8 +130,11 @@ class EmailsControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_redirected_to(settings_path)
|
||||
assert_equal("abc@ogres.net", @user.reload.email_address.address)
|
||||
assert_equal(false, @user.reload.email_address.is_verified)
|
||||
assert_enqueued_email_with UserMailer, :email_change_confirmation, args: [@user], queue: "default"
|
||||
assert_equal(true, @user.user_events.email_change.exists?)
|
||||
|
||||
perform_enqueued_jobs
|
||||
assert_performed_jobs(1, only: MailDeliveryJob)
|
||||
# assert_enqueued_email_with UserMailer.with_request(request), :email_change_confirmation, args: [@user], queue: "default"
|
||||
end
|
||||
|
||||
should "not allow banned users to change their email address" do
|
||||
|
||||
@@ -15,8 +15,11 @@ class PasswordResetsControllerTest < ActionDispatch::IntegrationTest
|
||||
post password_reset_path, params: { user: { name: @user.name } }
|
||||
|
||||
assert_redirected_to new_session_path
|
||||
assert_enqueued_email_with UserMailer, :password_reset, args: [@user], queue: "default"
|
||||
assert_equal(true, @user.user_events.password_reset.exists?)
|
||||
|
||||
perform_enqueued_jobs
|
||||
assert_performed_jobs(1, only: MailDeliveryJob)
|
||||
#assert_enqueued_email_with UserMailer.with_request(request), :password_reset, args: [@user], queue: "default"
|
||||
end
|
||||
|
||||
should "should fail if the user doesn't have a verified email address" do
|
||||
|
||||
@@ -266,8 +266,11 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_equal(User::Levels::MEMBER, User.last.level)
|
||||
assert_equal(User.last, User.last.authenticate_password("xxxxx1"))
|
||||
assert_nil(User.last.email_address)
|
||||
assert_enqueued_email_with UserMailer, :welcome_user, args: [User.last], queue: "default"
|
||||
assert_equal(true, User.last.user_events.user_creation.exists?)
|
||||
|
||||
perform_enqueued_jobs
|
||||
assert_performed_jobs(1, only: MailDeliveryJob)
|
||||
# assert_enqueued_email_with UserMailer.with_request(request), :welcome_user, args: [User.last], queue: "default"
|
||||
end
|
||||
|
||||
should "create a user with a valid email" do
|
||||
@@ -277,8 +280,11 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
||||
assert_equal("xxx", User.last.name)
|
||||
assert_equal(User.last, User.last.authenticate_password("xxxxx1"))
|
||||
assert_equal("webmaster@danbooru.donmai.us", User.last.email_address.address)
|
||||
assert_enqueued_email_with UserMailer, :welcome_user, args: [User.last], queue: "default"
|
||||
assert_equal(true, User.last.user_events.user_creation.exists?)
|
||||
|
||||
perform_enqueued_jobs
|
||||
assert_performed_jobs(1, only: MailDeliveryJob)
|
||||
# assert_enqueued_email_with UserMailer.with_request(request), :welcome_user, args: [User.last], queue: "default"
|
||||
end
|
||||
|
||||
should "not create a user with an invalid email" do
|
||||
|
||||
Reference in New Issue
Block a user