diff --git a/.env.example b/.env.example index 4eee95bcb..ac4a2c0e8 100644 --- a/.env.example +++ b/.env.example @@ -191,11 +191,7 @@ USE_INBOX_AVATAR_FOR_BOT=true ## https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md#environment-variables # DD_TRACE_AGENT_URL= -## IP look up configuration -## ref https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md -## works only on accounts with ip look up feature enabled -# IP_LOOKUP_SERVICE=geoip2 -# maxmindb api key to use geoip2 service +# MaxMindDB API key to download GeoLite2 City database # IP_LOOKUP_API_KEY= ## Rack Attack configuration diff --git a/Procfile b/Procfile index 7eff0580d..3cfa8ae13 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,3 @@ release: POSTGRES_STATEMENT_TIMEOUT=600s bundle exec rails db:chatwoot_prepare -web: bin/rails server -p $PORT -e $RAILS_ENV -worker: bundle exec sidekiq -C config/sidekiq.yml +web: bundle exec rails ip_lookup:setup && bin/rails server -p $PORT -e $RAILS_ENV +worker: bundle exec rails ip_lookup:setup && bundle exec sidekiq -C config/sidekiq.yml diff --git a/app/controllers/widgets_controller.rb b/app/controllers/widgets_controller.rb index ac26a36c5..a4e40eb9c 100644 --- a/app/controllers/widgets_controller.rb +++ b/app/controllers/widgets_controller.rb @@ -5,6 +5,7 @@ class WidgetsController < ActionController::Base before_action :set_global_config before_action :set_web_widget before_action :ensure_account_is_active + before_action :ensure_location_is_supported before_action :set_token before_action :set_contact before_action :build_contact @@ -54,6 +55,8 @@ class WidgetsController < ActionController::Base render json: { error: 'Account is suspended' }, status: :unauthorized unless @web_widget.inbox.account.active? end + def ensure_location_is_supported; end + def additional_attributes if @web_widget.inbox.account.feature_enabled?('ip_lookup') { created_at_ip: request.remote_ip } @@ -70,3 +73,5 @@ class WidgetsController < ActionController::Base response.headers.delete('X-Frame-Options') end end + +WidgetsController.prepend_mod_with('WidgetsController') diff --git a/app/jobs/contact_ip_lookup_job.rb b/app/jobs/contact_ip_lookup_job.rb index cf46d4ea3..b637223b7 100644 --- a/app/jobs/contact_ip_lookup_job.rb +++ b/app/jobs/contact_ip_lookup_job.rb @@ -1,11 +1,7 @@ -require 'rubygems/package' - class ContactIpLookupJob < ApplicationJob queue_as :default def perform(contact) - return unless ensure_look_up_service - update_contact_location_from_ip(contact) rescue Errno::ETIMEDOUT => e Rails.logger.warn "Exception: ip resolution failed : #{e.message}" @@ -13,18 +9,8 @@ class ContactIpLookupJob < ApplicationJob private - def ensure_look_up_service - return if ENV['IP_LOOKUP_SERVICE'].blank? || ENV['IP_LOOKUP_API_KEY'].blank? - return true if ENV['IP_LOOKUP_SERVICE'].to_sym != :geoip2 - - ensure_look_up_db - end - def update_contact_location_from_ip(contact) - ip = get_contact_ip(contact) - return if ip.blank? - - geocoder_result = Geocoder.search(ip).first + geocoder_result = IpLookupService.new.perform(get_contact_ip(contact)) return unless geocoder_result contact.additional_attributes ||= {} @@ -37,28 +23,4 @@ class ContactIpLookupJob < ApplicationJob def get_contact_ip(contact) contact.additional_attributes&.dig('updated_at_ip') || contact.additional_attributes&.dig('created_at_ip') end - - def ensure_look_up_db - return true if File.exist?(GeocoderConfiguration::LOOK_UP_DB) - - setup_vendor_db - end - - def setup_vendor_db - base_url = 'https://download.maxmind.com/app/geoip_download' - source_file = Down.download( - "#{base_url}?edition_id=GeoLite2-City&suffix=tar.gz&license_key=#{ENV.fetch('IP_LOOKUP_API_KEY', nil)}" - ) - tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open(source_file)) - tar_extract.rewind - - tar_extract.each do |entry| - next unless entry.full_name.include?('GeoLite2-City.mmdb') && entry.file? - - File.open GeocoderConfiguration::LOOK_UP_DB, 'wb' do |f| - f.print entry.read - end - return true - end - end end diff --git a/app/services/ip_lookup_service.rb b/app/services/ip_lookup_service.rb new file mode 100644 index 000000000..fae3efc00 --- /dev/null +++ b/app/services/ip_lookup_service.rb @@ -0,0 +1,15 @@ +class IpLookupService + def perform(ip_address) + return if ip_address.blank? || !ip_database_available? + + Geocoder.search(ip_address).first + rescue Errno::ETIMEDOUT => e + Rails.logger.warn "Exception: IP resolution failed :#{e.message}" + end + + private + + def ip_database_available? + File.exist?(GeocoderConfiguration::LOOK_UP_DB) + end +end diff --git a/config/initializers/geocoder.rb b/config/initializers/geocoder.rb index 0b4dced86..9c53a1635 100644 --- a/config/initializers/geocoder.rb +++ b/config/initializers/geocoder.rb @@ -23,10 +23,4 @@ module GeocoderConfiguration LOOK_UP_DB = Rails.root.join('vendor/db/GeoLiteCity.mmdb') end -if ENV['IP_LOOKUP_SERVICE'].present? - if ENV['IP_LOOKUP_SERVICE'] == 'geoip2' - Geocoder.configure(ip_lookup: :geoip2, geoip2: { file: GeocoderConfiguration::LOOK_UP_DB }) - else - Geocoder.configure(ip_lookup: ENV['IP_LOOKUP_SERVICE'].to_sym, api_key: ENV.fetch('IP_LOOKUP_API_KEY', nil)) - end -end +Geocoder.configure(ip_lookup: :geoip2, geoip2: { file: GeocoderConfiguration::LOOK_UP_DB }) if ENV['IP_LOOKUP_API_KEY'].present? diff --git a/enterprise/app/controllers/enterprise/widgets_controller.rb b/enterprise/app/controllers/enterprise/widgets_controller.rb new file mode 100644 index 000000000..fecd2a00b --- /dev/null +++ b/enterprise/app/controllers/enterprise/widgets_controller.rb @@ -0,0 +1,14 @@ +module Enterprise::WidgetsController + private + + def ensure_location_is_supported + countries = @web_widget.inbox.account.custom_attributes['allowed_countries'] + return if countries.blank? + + geocoder_result = IpLookupService.new.perform(request.remote_ip) + return unless geocoder_result + + country_enabled = countries.include?(geocoder_result.country_code) + render json: { error: 'Location is not supported' }, status: :unauthorized unless country_enabled + end +end diff --git a/lib/tasks/ip_lookup.rake b/lib/tasks/ip_lookup.rake new file mode 100644 index 000000000..f1ae80100 --- /dev/null +++ b/lib/tasks/ip_lookup.rake @@ -0,0 +1,33 @@ +require 'rubygems/package' + +namespace :ip_lookup do + task setup: :environment do + next if File.exist?(GeocoderConfiguration::LOOK_UP_DB) + + ip_lookup_api_key = ENV.fetch('IP_LOOKUP_API_KEY') + next if ip_lookup_api_key.blank? + + puts '[rake ip_lookup:setup] Fetch GeoLite2-City database' + + begin + base_url = 'https://download.maxmind.com/app/geoip_download' + source_file = Down.download( + "#{base_url}?edition_id=GeoLite2-City&suffix=tar.gz&license_key=#{ip_lookup_api_key}" + ) + + tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open(source_file)) + tar_extract.rewind + + tar_extract.each do |entry| + next unless entry.full_name.include?('GeoLite2-City.mmdb') && entry.file? + + File.open GeocoderConfiguration::LOOK_UP_DB, 'wb' do |f| + f.print entry.read + end + end + puts '[rake ip_lookup:setup] Fetch complete' + rescue StandardError => e + puts "[rake ip_lookup:setup] #{e.message}" + end + end +end