diff --git a/app/controllers/webhooks/facebook_controller.rb b/app/controllers/webhooks/facebook_controller.rb new file mode 100644 index 000000000..17456f291 --- /dev/null +++ b/app/controllers/webhooks/facebook_controller.rb @@ -0,0 +1,48 @@ +class Webhooks::FacebookController < ActionController::API + class InvalidDigestError < StandardError; end + + def process_deletion + signed_request = params['signed_request'] + payload = parse_fb_signed_request(signed_request) + id_to_process = payload['user_id'] + + set_processing + Webhooks::MetaDeleteJob.perform_later(id_to_process) + status_url = "#{app_url_base}/meta/delete-status/#{id_to_process}" + + render json: { status_url: status_url, code: id_to_process }, status: :ok + rescue InvalidDigestError + render json: { error: 'Invalid signature' }, status: :unprocessable_entity + end + + private + + def set_processing + # we use this key to check if the deletion is completed or not + # Once the key is gone, we know the deletion is completed + # And we can show as such + key = format(::Redis::Alfred::META_DELETE_PROCESSING, id: id_to_process) + ::Redis::Alfred.set(key, true) + end + + def app_url_base + ENV.fetch('FRONTEND_URL', nil) + end + + def parse_fb_signed_request(signed_request) + encoded_signature, payload = signed_request.split('.', 2) + + decoded_signature = Base64.urlsafe_decode64(encoded_signature) + decoded_payload = JSON.parse(Base64.urlsafe_decode64(payload)) + + expected_signature = OpenSSL::HMAC.digest('sha256', app_secret, payload) + + raise InvalidDigestError if decoded_signature != expected_signature + + decoded_payload + end + + def app_secret + GlobalConfigService.load('FB_APP_SECRET', '') + end +end diff --git a/app/jobs/webhooks/meta_delete_job.rb b/app/jobs/webhooks/meta_delete_job.rb new file mode 100644 index 000000000..daa1d76d2 --- /dev/null +++ b/app/jobs/webhooks/meta_delete_job.rb @@ -0,0 +1,44 @@ +class Webhooks::MetaDeleteJob < ApplicationJob + queue_as :low + + attr_reader :id_to_process + + def perform(id_to_process) + @id_to_process = id_to_process + + delete_channel_if_present + delete_contact_if_present + + unset_processing + end + + private + + def unset_processing + key = format(::Redis::Alfred::META_DELETE_PROCESSING, id: id_to_process) + ::Redis::Alfred.del(key) + end + + def delete_channel_if_present + channel = Channel::FacebookPage.find_by(page_id: id_to_process) + return unless channel + + inbox = channel.inbox + inbox.destroy + end + + def remove_contact_if_present + contact_inbox = ContactInbox.find_by(source_id: id_to_process) + return unless contact_inbox + + contact = contact_inbox.contact + contact.update!( + name: 'Deleted User', + email: nil, + phone_number: nil, + identifier: nil, + additional_attributes: {}, + custom_attributes: {} + ) + end +end diff --git a/config/routes.rb b/config/routes.rb index 56704d9aa..6307ade0b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -448,6 +448,7 @@ Rails.application.routes.draw do post 'webhooks/whatsapp/:phone_number', to: 'webhooks/whatsapp#process_payload' get 'webhooks/instagram', to: 'webhooks/instagram#verify' post 'webhooks/instagram', to: 'webhooks/instagram#events' + post 'webhooks/facebook/delete', to: 'webhooks/facebook#process_deletion' namespace :twitter do resource :callback, only: [:show] diff --git a/lib/redis/redis_keys.rb b/lib/redis/redis_keys.rb index f8c467c9a..85ef6d015 100644 --- a/lib/redis/redis_keys.rb +++ b/lib/redis/redis_keys.rb @@ -41,4 +41,7 @@ module Redis::RedisKeys IG_MESSAGE_MUTEX = 'IG_MESSAGE_CREATE_LOCK::%s::%s'.freeze SLACK_MESSAGE_MUTEX = 'SLACK_MESSAGE_LOCK::%s::%s'.freeze EMAIL_MESSAGE_MUTEX = 'EMAIL_CHANNEL_LOCK::%s'.freeze + + ## Meta deletion flags + META_DELETE_PROCESSING = 'META_DELETE_PROCESSING::%s'.freeze end