From 4d674acc2516d5b4e57429df1f83769e62d89959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 13 Feb 2019 21:26:40 +0100 Subject: [PATCH] FEATURE: AWS SNS bounce notifications webhooks --- Gemfile | 1 + Gemfile.lock | 4 +++ app/controllers/webhooks_controller.rb | 28 +++++++++++---- app/jobs/regular/confirm_sns_subscription.rb | 21 ++++++++++++ app/jobs/regular/process_sns_notification.rb | 36 ++++++++++++++++++++ config/routes.rb | 3 +- 6 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 app/jobs/regular/confirm_sns_subscription.rb create mode 100644 app/jobs/regular/process_sns_notification.rb diff --git a/Gemfile b/Gemfile index 1bc558f68da..f413a3d83de 100644 --- a/Gemfile +++ b/Gemfile @@ -65,6 +65,7 @@ gem 'fast_xor', platform: :mri gem 'fastimage' gem 'aws-sdk-s3', require: false +gem 'aws-sdk-sns', require: false gem 'excon', require: false gem 'unf', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 2acdb78709b..7db79ab7b40 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -57,6 +57,9 @@ GEM aws-sdk-core (~> 3, >= 3.26.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.0) + aws-sdk-sns (1.2.0) + aws-sdk-core (~> 3) + aws-sigv4 (~> 1.0) aws-sigv4 (1.0.3) barber (0.12.0) ember-source (>= 1.0, < 3.1) @@ -452,6 +455,7 @@ DEPENDENCIES activesupport (= 5.2.2) annotate aws-sdk-s3 + aws-sdk-sns barber better_errors binding_of_caller diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb index b5f3fdd4f6b..6a1bd810c01 100644 --- a/app/controllers/webhooks_controller.rb +++ b/app/controllers/webhooks_controller.rb @@ -24,7 +24,7 @@ class WebhooksController < ActionController::Base end end - render body: nil, status: 200 + success end def mailjet @@ -41,7 +41,7 @@ class WebhooksController < ActionController::Base end end - render body: nil, status: 200 + success end def mandrill @@ -58,7 +58,7 @@ class WebhooksController < ActionController::Base end end - render body: nil, status: 200 + success end def sparkpost @@ -84,7 +84,21 @@ class WebhooksController < ActionController::Base end end - render body: nil, status: 200 + success + end + + def aws + raw = request.raw_post + json = JSON.parse(raw) + + case json["Type"] + when "SubscriptionConfirmation" + Jobs.enqueue(:confirm_sns_subscription, raw: raw, json: json) + when "Notification" + Jobs.enqueue(:process_sns_notification, raw: raw, json: json) + end + + success end private @@ -93,7 +107,7 @@ class WebhooksController < ActionController::Base render body: nil, status: 406 end - def mailgun_success + def success render body: nil, status: 200 end @@ -129,7 +143,7 @@ class WebhooksController < ActionController::Base process_bounce(message_id, to_address, SiteSetting.hard_bounce_score) end - mailgun_success + success end def handle_mailgun_new(params) @@ -149,7 +163,7 @@ class WebhooksController < ActionController::Base end end - mailgun_success + success end def process_bounce(message_id, to_address, bounce_score) diff --git a/app/jobs/regular/confirm_sns_subscription.rb b/app/jobs/regular/confirm_sns_subscription.rb new file mode 100644 index 00000000000..8019ea63c50 --- /dev/null +++ b/app/jobs/regular/confirm_sns_subscription.rb @@ -0,0 +1,21 @@ +require "aws-sdk-sns" + +module Jobs + + class ConfirmSnsSubscription < Jobs::Base + sidekiq_options retry: false + + def execute(args) + return unless raw = args[:raw].presence + return unless json = args[:json].presence + + return unless subscribe_url = json["SubscribeURL"].presence + return unless Aws::SNS::MessageVerifier.new.authentic?(raw) + + # confirm subscription by visiting the URL + open(subscribe_url) + end + + end + +end diff --git a/app/jobs/regular/process_sns_notification.rb b/app/jobs/regular/process_sns_notification.rb new file mode 100644 index 00000000000..feb65b69a0b --- /dev/null +++ b/app/jobs/regular/process_sns_notification.rb @@ -0,0 +1,36 @@ +require "aws-sdk-sns" + +module Jobs + + class ProcessSnsNotification < Jobs::Base + sidekiq_options retry: false + + def execute(args) + return unless raw = args[:raw].presence + return unless json = args[:json].presence + + return unless message = json["Message"].presence + return unless message["notificationType"] == "Bounce" + return unless message_id = message.dig("mail", "messageId").presence + return unless bounce_type = message.dig("bounce", "bounceType").presence + + return unless Aws::SNS::MessageVerifier.new.authentic?(raw) + + message.dig("bounce", "bouncedRecipients").each do |r| + if email_log = EmailLog.find_by(message_id: message_id, to_address: r["emailAddress"]) + email_log.update_columns(bounced: true) + + if email_log.user&.email.present? + if r["status"]&.start_with?["4."] || bounce_type == "Transient" + Email::Receiver.update_bounce_score(email_log.user.email, SiteSetting.soft_bounce_score) + else + Email::Receiver.update_bounce_score(email_log.user.email, SiteSetting.hard_bounce_score) + end + end + end + end + end + + end + +end diff --git a/config/routes.rb b/config/routes.rb index dca765bef86..eb9f3ef7d82 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,10 +15,11 @@ Discourse::Application.routes.draw do match "/404", to: "exceptions#not_found", via: [:get, :post] get "/404-body" => "exceptions#not_found_body" + post "webhooks/aws" => "webhooks#aws" post "webhooks/mailgun" => "webhooks#mailgun" - post "webhooks/sendgrid" => "webhooks#sendgrid" post "webhooks/mailjet" => "webhooks#mailjet" post "webhooks/mandrill" => "webhooks#mandrill" + post "webhooks/sendgrid" => "webhooks#sendgrid" post "webhooks/sparkpost" => "webhooks#sparkpost" if Rails.env.development?