From 347a810d888b3fa3ee24b13bff794202364160ca Mon Sep 17 00:00:00 2001 From: Matthew Rider Date: Tue, 17 Sep 2024 11:18:04 +0200 Subject: [PATCH] Remove global env variables from organizations (#2016) Closes #1947 @soey there is still the `three_sixty_dialog_partner_token` attribute that lives at the organization level, which it probably shouldn't. This is also a value that is global and is not unique to the organization. The token is only good for 24 hours, which makes it difficult to get the value from env variables. Previously, with `rails-settings-cached`, we would save the value to the db in the `settings` table and then check to see if there was a value and that it had been updated within 24 hours before asking for a new token, or using the valid one. Currently, this is used for creating the api key for a newly onboarded client. It then is used to create the templates needed to send out messages for that client. I think a better way to handle this would be to assume the token is not valid, if it even exists, since we are unlikely to be onboarding multiple clients in the same 24-hour period and always ask for a new token when we run the create api key job. The create template job is currently called from a rake task that collects the templates from our `de.yml` before scheduling the job. We would need to rethink this flow if we wanted to be able to use the same token to create the templates. --- app/jobs/whats_app_adapter/create_api_key.rb | 20 +++--- ...create_template.rb => create_templates.rb} | 69 ++++++++----------- app/models/organization.rb | 2 +- ...global_env_variables_from_organizations.rb | 14 ++++ db/schema.rb | 6 -- lib/tasks/whats_app/create_templates.rake | 18 ----- 6 files changed, 55 insertions(+), 74 deletions(-) rename app/jobs/whats_app_adapter/{create_template.rb => create_templates.rb} (72%) create mode 100644 db/migrate/20240905104347_remove_global_env_variables_from_organizations.rb delete mode 100644 lib/tasks/whats_app/create_templates.rake diff --git a/app/jobs/whats_app_adapter/create_api_key.rb b/app/jobs/whats_app_adapter/create_api_key.rb index 1b4ce4030..d4eb5ff0f 100644 --- a/app/jobs/whats_app_adapter/create_api_key.rb +++ b/app/jobs/whats_app_adapter/create_api_key.rb @@ -6,13 +6,12 @@ module WhatsAppAdapter class CreateApiKey < ApplicationJob def perform(organization_id:, channel_id:) @organization = Organization.find_by(id: organization_id) - return unless organization && organization.three_sixty_dialog_partner_id.present? + return unless organization && ENV.fetch('THREE_SIXTY_DIALOG_PARTNER_ID', nil).present? @base_uri = ENV.fetch('THREE_SIXTY_DIALOG_PARTNER_REST_API_ENDPOINT', 'https://stoplight.io/mocks/360dialog/360dialog-partner-api/24588693') - token = organization.three_sixty_dialog_partner_token - fetch_token unless token.present? && organization.updated_at > 24.hours.ago - partner_id = organization.three_sixty_dialog_partner_id + @token = fetch_token + partner_id = ENV.fetch('THREE_SIXTY_DIALOG_PARTNER_ID', nil) url = URI.parse( "#{base_uri}/partners/#{partner_id}/channels/#{channel_id}/api_keys" @@ -20,7 +19,7 @@ def perform(organization_id:, channel_id:) headers = { Accept: 'application/json', 'Content-Type': 'application/json', - Authorization: "Bearer #{organization.three_sixty_dialog_partner_token}" + Authorization: "Bearer #{token}" } request = Net::HTTP::Post.new(url.to_s, headers) response = Net::HTTP.start(url.host, url.port, use_ssl: true) do |http| @@ -31,7 +30,7 @@ def perform(organization_id:, channel_id:) private - attr_reader :base_uri, :organization + attr_reader :base_uri, :organization, :token def fetch_token url = URI.parse("#{base_uri}/token") @@ -40,14 +39,13 @@ def fetch_token } request = Net::HTTP::Post.new(url.to_s, headers) request.body = { - username: organization.three_sixty_dialog_partner_username, - password: organization.three_sixty_dialog_partner_password + username: ENV.fetch('THREE_SIXTY_DIALOG_PARTNER_USERNAME', nil), + password: ENV.fetch('THREE_SIXTY_DIALOG_PARTNER_PASSWORD', nil) }.to_json response = Net::HTTP.start(url.host, url.port, use_ssl: true) do |http| http.request(request) end - token = JSON.parse(response.body)['access_token'] - organization.update!(three_sixty_dialog_partner_token: token) + JSON.parse(response.body)['access_token'] end def handle_response(response) @@ -57,6 +55,8 @@ def handle_response(response) Rails.logger.debug api_key organization.update!(three_sixty_dialog_client_api_key: api_key) WhatsAppAdapter::SetWebhookUrl.perform_later(organization_id: organization.id) + WhatsAppAdapter::CreateTemplates.perform_later(organization_id: organization.id, token: token) + when 400..599 exception = WhatsAppAdapter::ThreeSixtyDialogError.new(error_code: response.code, message: response.body) ErrorNotifier.report(exception) diff --git a/app/jobs/whats_app_adapter/create_template.rb b/app/jobs/whats_app_adapter/create_templates.rb similarity index 72% rename from app/jobs/whats_app_adapter/create_template.rb rename to app/jobs/whats_app_adapter/create_templates.rb index 2fd889fd8..a77170858 100644 --- a/app/jobs/whats_app_adapter/create_template.rb +++ b/app/jobs/whats_app_adapter/create_templates.rb @@ -2,33 +2,46 @@ require 'net/http' -# rubocop:disable Metrics/ClassLength module WhatsAppAdapter - class CreateTemplate < ApplicationJob - def perform(organization_id:, template_name:, template_text:) + class CreateTemplates < ApplicationJob + def perform(organization_id:, token:) @organization = Organization.find_by(id: organization_id) return unless organization @base_uri = ENV.fetch('THREE_SIXTY_DIALOG_PARTNER_REST_API_ENDPOINT', 'https://stoplight.io/mocks/360dialog/360dialog-partner-api/24588693') @partner_id = ENV.fetch('THREE_SIXTY_DIALOG_PARTNER_ID', nil) - @template_name = template_name - @template_text = template_text - - @token = organization.three_sixty_dialog_partner_token - @token = fetch_token unless token.present? && organization.updated_at > 24.hours.ago - + @token = token @waba_account_id = organization.three_sixty_dialog_client_waba_account_id - waba_accont_namespace = organization.three_sixty_dialog_whats_app_template_namespace - @waba_account_id = fetch_client_info if waba_account_id.blank? || waba_accont_namespace.blank? + @waba_account_id = fetch_client_info if waba_account_id.blank? || organization.three_sixty_dialog_whats_app_template_namespace.blank? + + templates_to_create_array = whats_app_templates.keys.difference(existing_templates) + templates_to_create = whats_app_templates.select { |key, _value| key.in?(templates_to_create_array) } + templates_to_create.each do |key, value| + @template_name = key + @template_text = value - conditionally_create_template + create_template + end end attr_reader :organization, :base_uri, :partner_id, :template_name, :template_text, :token, :waba_account_id private - def conditionally_create_template + # rubocop:disable Style/FormatStringToken + def whats_app_templates + default_welcome_message = ["*#{File.read(File.join('config', 'locales', 'onboarding', 'success_heading.txt'))}*", + File.read(File.join('config', 'locales', 'onboarding', + 'success_text.txt'))].join("\n\n").gsub('100eyes', '{{1}}') + default_welcome_message_hash = { default_welcome_message: default_welcome_message } + requests_hash = I18n.t('.')[:adapter][:whats_app][:request_template].transform_values do |value| + value.gsub('%{first_name}', '{{1}}').gsub('%{request_title}', '{{2}}') + end + default_welcome_message_hash.merge(requests_hash) + end + # rubocop:enable Style/FormatStringToken + + def existing_templates url = URI.parse( "#{base_uri}/partners/#{partner_id}/waba_accounts/#{waba_account_id}/waba_templates" ) @@ -38,10 +51,7 @@ def conditionally_create_template http.request(request) end waba_templates = JSON.parse(response.body)['waba_templates'] - template_names_array = waba_templates.pluck('name') - return if template_name.in?(template_names_array) - - create_template + waba_templates.pluck('name').map(&:to_sym) end def create_template @@ -61,33 +71,15 @@ def create_template def set_headers { + Accept: 'application/json', 'Content-Type': 'application/json', - Authorization: "Bearer #{organization.three_sixty_dialog_partner_token}" + Authorization: "Bearer #{token}" } end - def fetch_token - url = URI.parse("#{base_uri}/token") - headers = { 'Content-Type': 'application/json' } - request = Net::HTTP::Post.new(url.to_s, headers) - request.body = { - username: organization.three_sixty_dialog_partner_username, - password: organization.three_sixty_dialog_partner_password - }.to_json - response = Net::HTTP.start(url.host, url.port, use_ssl: true) do |http| - http.request(request) - end - token = JSON.parse(response.body)['access_token'] - organization.update!(three_sixty_dialog_partner_token: token) - end - def fetch_client_info url = URI.parse("#{base_uri}/partners/#{partner_id}/channels") - headers = { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: "Bearer #{organization.three_sixty_dialog_partner_token}" - } + headers = set_headers request = Net::HTTP::Get.new(url.to_s, headers) response = Net::HTTP.start(url.host, url.port, use_ssl: true) do |http| http.request(request) @@ -170,4 +162,3 @@ def handle_response(response) end end end -# rubocop:enable Metrics/ClassLength diff --git a/app/models/organization.rb b/app/models/organization.rb index 349b8ab0e..c87e75d59 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -4,7 +4,7 @@ class Organization < ApplicationRecord attr_encrypted_options.merge!(key: Base64.decode64(ENV.fetch('ATTR_ENCRYPTED_KEY', nil))) attr_encrypted :threemarb_api_secret, :threemarb_private attr_encrypted :twilio_api_key_secret - attr_encrypted :three_sixty_dialog_partner_password, :three_sixty_dialog_partner_token, :three_sixty_dialog_client_api_key + attr_encrypted :three_sixty_dialog_client_api_key attr_encrypted :telegram_bot_api_key belongs_to :business_plan diff --git a/db/migrate/20240905104347_remove_global_env_variables_from_organizations.rb b/db/migrate/20240905104347_remove_global_env_variables_from_organizations.rb new file mode 100644 index 000000000..e37fe2917 --- /dev/null +++ b/db/migrate/20240905104347_remove_global_env_variables_from_organizations.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class RemoveGlobalEnvVariablesFromOrganizations < ActiveRecord::Migration[6.1] + def change + change_table :organizations, bulk: true do |t| + t.remove :three_sixty_dialog_partner_id, type: :string + t.remove :three_sixty_dialog_partner_username, type: :string + t.remove :encrypted_three_sixty_dialog_partner_password, type: :string + t.remove :encrypted_three_sixty_dialog_partner_password_iv, type: :string + t.remove :encrypted_three_sixty_dialog_partner_token, type: :string + t.remove :encrypted_three_sixty_dialog_partner_token_iv, type: :string + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4dbdab5cb..0a58c2cd0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -225,12 +225,6 @@ t.string "twilio_account_sid" t.string "whats_app_server_phone_number" t.string "three_sixty_dialog_whats_app_template_namespace" - t.string "three_sixty_dialog_partner_id" - t.string "three_sixty_dialog_partner_username" - t.string "encrypted_three_sixty_dialog_partner_password" - t.string "encrypted_three_sixty_dialog_partner_password_iv" - t.string "encrypted_three_sixty_dialog_partner_token" - t.string "encrypted_three_sixty_dialog_partner_token_iv" t.string "encrypted_three_sixty_dialog_client_api_key" t.string "encrypted_three_sixty_dialog_client_api_key_iv" t.string "three_sixty_dialog_client_id" diff --git a/lib/tasks/whats_app/create_templates.rake b/lib/tasks/whats_app/create_templates.rake deleted file mode 100644 index cf04ef93e..000000000 --- a/lib/tasks/whats_app/create_templates.rake +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -# rubocop:disable Style/FormatStringToken -desc 'Create WhatsApp templates' -task create_whats_app_templates: :environment do - default_welcome_message = ["*#{File.read(File.join('config', 'locales', 'onboarding', 'success_heading.txt'))}*", - File.read(File.join('config', 'locales', 'onboarding', - 'success_text.txt'))].join("\n\n").gsub('100eyes', '{{1}}') - default_welcome_message_hash = { default_welcome_message: default_welcome_message } - requests_hash = I18n.t('.')[:adapter][:whats_app][:request_template].transform_values do |value| - value.gsub('%{first_name}', '{{1}}').gsub('%{request_title}', '{{2}}') - end - template_hash = default_welcome_message_hash.merge(requests_hash) - template_hash.each do |key, value| - WhatsAppAdapter::CreateTemplate.perform_later(template_name: key, template_text: value) - end -end -# rubocop:enable Style/FormatStringToken