From 6cbbdf83e579b65b2515275c118bb497003b5192 Mon Sep 17 00:00:00 2001 From: Jarrett Lusso Date: Wed, 21 May 2025 18:47:45 -0400 Subject: [PATCH 1/5] Add support for access token authentication - Added ability to specify `api_key` or `access_token` at request time - Changed authentication credentials to always be passed in headers - Removed `pry`, added `amazing_print`, and fixed require order for `active_model`. - Updated CI and fixed RuboCop violations --- .github/workflows/ci.yml | 5 ++--- .rubocop.yml | 2 -- .rubocop_todo.yml | 7 ------- Gemfile | 2 +- emailable.gemspec | 9 ++++----- lib/emailable.rb | 6 +++--- lib/emailable/client.rb | 21 ++++++++++++--------- lib/emailable/email_validator.rb | 4 ++-- test/authentication_test.rb | 27 +++++++++++++++++++++++++++ test/email_validator_test.rb | 1 - test/test_helper.rb | 2 +- 11 files changed, 52 insertions(+), 34 deletions(-) delete mode 100644 .rubocop_todo.yml create mode 100644 test/authentication_test.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7728c8d..6e9dbc4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,8 @@ name: CI on: push: - branches: [ master ] + branches: [ 'master' ] pull_request: - branches: [ master ] jobs: tests: @@ -12,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby-version: ['2.7', '3.0', '3.1', '3.2', '3.3'] + ruby-version: ['2.7', '3.0', '3.1', '3.2', '3.3', '3.4'] steps: - uses: actions/checkout@v4 diff --git a/.rubocop.yml b/.rubocop.yml index 6e0df8a..6c031db 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,2 @@ -inherit_from: .rubocop_todo.yml - inherit_gem: rubocop-cache-ventures: rubocop.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml deleted file mode 100644 index ffb0488..0000000 --- a/.rubocop_todo.yml +++ /dev/null @@ -1,7 +0,0 @@ -# This configuration was generated by -# `rubocop --auto-gen-config --exclude-limit 10000` -# on 2024-03-18 16:04:55 UTC using RuboCop version 1.62.1. -# The point is for the user to remove these configuration records -# one by one as the offenses are removed from the code base. -# Note that changes in the inspected code, or installation of new -# versions of RuboCop, may require this file to be generated again. diff --git a/Gemfile b/Gemfile index 9fed709..b736565 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source "https://rubygems.org" +source 'https://rubygems.org' # Specify your gem's dependencies in emailable-ruby.gemspec gemspec diff --git a/emailable.gemspec b/emailable.gemspec index 86ab291..d9bd82e 100644 --- a/emailable.gemspec +++ b/emailable.gemspec @@ -15,9 +15,9 @@ Gem::Specification.new do |s| s.email = 'support@emailable.com' s.license = 'MIT' s.metadata = { - "bug_tracker_uri" => "https://github.com/emailable/emailable-ruby/issues", - "documentation_uri" => "https://docs.emailable.com/?ruby", - "source_code_uri" => "https://github.com/emailable/emailable-ruby" + 'bug_tracker_uri' => 'https://github.com/emailable/emailable-ruby/issues', + 'documentation_uri' => 'https://emailable.com/docs/api/?ruby', + 'source_code_uri' => 'https://github.com/emailable/emailable-ruby' } s.files = `git ls-files`.split("\n") @@ -29,8 +29,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'bundler' s.add_development_dependency 'rake', '~> 13.0' - s.add_development_dependency 'pry' - s.add_development_dependency 'awesome_print' + s.add_development_dependency 'amazing_print' s.add_development_dependency 'minitest', '~> 5.0' s.add_development_dependency 'minitest-reporters' s.add_development_dependency 'activemodel' diff --git a/lib/emailable.rb b/lib/emailable.rb index 0dabd90..5d2cbb4 100644 --- a/lib/emailable.rb +++ b/lib/emailable.rb @@ -32,7 +32,7 @@ def verify(email, parameters = {}) parameters[:email] = email client = Emailable::Client.new - response = client.request(:get, 'verify', parameters) + response = client.request(:post, 'verify', parameters) if response.status == 249 raise Emailable::TimeoutError.new(response.body) @@ -41,9 +41,9 @@ def verify(email, parameters = {}) end end - def account + def account(parameters = {}) client = Emailable::Client.new - response = client.request(:get, 'account') + response = client.request(:get, 'account', parameters) Account.new(response.body) end diff --git a/lib/emailable/client.rb b/lib/emailable/client.rb index 7e2cc54..7884dac 100644 --- a/lib/emailable/client.rb +++ b/lib/emailable/client.rb @@ -16,19 +16,22 @@ def initialize @connection = create_connection(URI(@base_url)) end - def request(method, endpoint, params = {}) + def request(method, endpoint, params = {}, api_key: nil, access_token: nil) + uri = URI("#{@base_url}/#{endpoint}") + request_options = { + 'Authorization': "Bearer #{Emailable.api_key || api_key || access_token}", + 'Content-Type': 'application/json' + } + begin tries ||= 3 - - uri = URI("#{@base_url}/#{endpoint}") - params[:api_key] = Emailable.api_key - http_response = if method == :get - uri.query = URI.encode_www_form(params) - @connection.get(uri) + request = Net::HTTP::Get.new(uri, request_options) + request.set_form_data(params) + @connection.request(request) elsif method == :post - request = Net::HTTP::Post.new(uri, 'Content-Type': 'application/json') + request = Net::HTTP::Post.new(uri, request_options) request.body = params.to_json @connection.request(request) end @@ -65,7 +68,7 @@ def create_connection(uri) if connection.respond_to?(:write_timeout=) connection.write_timeout = Emailable.write_timeout end - connection.use_ssl = uri.scheme == "https" + connection.use_ssl = uri.scheme == 'https' connection end diff --git a/lib/emailable/email_validator.rb b/lib/emailable/email_validator.rb index a430bd0..18a1d0e 100644 --- a/lib/emailable/email_validator.rb +++ b/lib/emailable/email_validator.rb @@ -18,7 +18,7 @@ def validate_each(record, attribute, value) states = options.fetch(:states, %i(deliverable risky unknown)) allowed_states = %i[deliverable undeliverable risky unknown] unless (states - allowed_states).empty? - raise ArgumentError, ":states must be an array of symbols containing "\ + raise ArgumentError, ':states must be an array of symbols containing '\ "any or all of :#{allowed_states.join(', :')}" end @@ -29,7 +29,7 @@ def validate_each(record, attribute, value) timeout = options.fetch(:timeout, 3) unless timeout.is_a?(Integer) && timeout > 1 - raise ArgumentError, ":timeout must be an Integer greater than 1" + raise ArgumentError, ':timeout must be an Integer greater than 1' end return if record.errors[attribute].present? diff --git a/test/authentication_test.rb b/test/authentication_test.rb new file mode 100644 index 0000000..7ec2f82 --- /dev/null +++ b/test/authentication_test.rb @@ -0,0 +1,27 @@ +require 'test_helper' + +class AuthenticationTest < Minitest::Test + + def setup + @api_key = 'test_7aff7fc0142c65f86a00' + @email = 'jarrett@emailable.com' + @emails = ['jarrett@emailable.com', 'support@emailable.com'] + end + + def test_global_api_key_authentication + Emailable.api_key = @api_key + + refute_nil Emailable.verify(@email).domain + refute_nil Emailable.account.owner_email + refute_nil bid = Emailable::Batch.new(@emails).verify + refute_nil Emailable::Batch.new(bid).status.id + end + + def test_request_time_api_key_authentication + refute_nil Emailable.verify(@email, api_key: @api_key).domain + refute_nil Emailable.account(api_key: @api_key).owner_email + refute_nil bid = Emailable::Batch.new(@emails).verify(api_key: @api_key) + refute_nil Emailable::Batch.new(bid).status(api_key: @api_key).id + end + +end diff --git a/test/email_validator_test.rb b/test/email_validator_test.rb index 96c9394..f0b6541 100644 --- a/test/email_validator_test.rb +++ b/test/email_validator_test.rb @@ -1,4 +1,3 @@ -require 'active_model' require 'test_helper' class EmailValidatorTest < Minitest::Test diff --git a/test/test_helper.rb b/test/test_helper.rb index 1e1c196..4660ea3 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,6 +1,6 @@ +require 'active_model' require 'emailable' -require 'pry' require 'minitest/autorun' require 'minitest/reporters' Minitest::Reporters.use! [ From 2a31746f1d9840037d78ea9b92e3fd3690eecd15 Mon Sep 17 00:00:00 2001 From: Daniel Arnold Date: Tue, 27 May 2025 12:13:15 -0400 Subject: [PATCH 2/5] fix keyword argument incompatibility caused by Emailable::Client#request mixing positional and keyword arguments with implicit hash conversion. update the project's ruby to 3.4.4 (latest). --- .ruby-version | 2 +- lib/emailable/client.rb | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.ruby-version b/.ruby-version index 15a2799..f989260 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.0 +3.4.4 diff --git a/lib/emailable/client.rb b/lib/emailable/client.rb index 7884dac..3e71e79 100644 --- a/lib/emailable/client.rb +++ b/lib/emailable/client.rb @@ -16,7 +16,10 @@ def initialize @connection = create_connection(URI(@base_url)) end - def request(method, endpoint, params = {}, api_key: nil, access_token: nil) + def request(method, endpoint, params = {}) + api_key = params.delete(:api_key) + access_token = params.delete(:access_token) + uri = URI("#{@base_url}/#{endpoint}") request_options = { 'Authorization': "Bearer #{Emailable.api_key || api_key || access_token}", From 60a501a6c9371ebe2374d13cfead51038d889951 Mon Sep 17 00:00:00 2001 From: Jarrett Lusso Date: Thu, 29 May 2025 08:40:21 -0400 Subject: [PATCH 3/5] Renamed request_options to headers and fixed GET not properly setting query params --- lib/emailable/client.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/emailable/client.rb b/lib/emailable/client.rb index 7884dac..5f14b6e 100644 --- a/lib/emailable/client.rb +++ b/lib/emailable/client.rb @@ -18,7 +18,7 @@ def initialize def request(method, endpoint, params = {}, api_key: nil, access_token: nil) uri = URI("#{@base_url}/#{endpoint}") - request_options = { + headers = { 'Authorization': "Bearer #{Emailable.api_key || api_key || access_token}", 'Content-Type': 'application/json' } @@ -27,11 +27,11 @@ def request(method, endpoint, params = {}, api_key: nil, access_token: nil) tries ||= 3 http_response = if method == :get - request = Net::HTTP::Get.new(uri, request_options) - request.set_form_data(params) + uri.query = URI.encode_www_form(params) unless params.empty? + request = Net::HTTP::Get.new(uri, headers) @connection.request(request) elsif method == :post - request = Net::HTTP::Post.new(uri, request_options) + request = Net::HTTP::Post.new(uri, headers) request.body = params.to_json @connection.request(request) end From ce1d69a9086e8089508b1655d259a2154bbe06dc Mon Sep 17 00:00:00 2001 From: Jarrett Lusso Date: Thu, 29 May 2025 15:50:55 -0400 Subject: [PATCH 4/5] Updated the README --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9eac84e..1665d47 100644 --- a/README.md +++ b/README.md @@ -30,17 +30,24 @@ Or install it yourself as: ## Usage -The library needs to be configured with your account's API key which is -available in your [Emailable Dashboard](https://app.emailable.com/api). Set -`Emailable.api_key` to its value: +### Authentication -### Setup +The Emailable API can be authenticated with in a few ways. You will need an API key or an Access Token to authenticate. API keys are available in your [Emailable Dashboard](https://app.emailable.com/api). + +The library can be configured globally with your account's API key + +```ruby +Emailable.api_key = 'your_api_key +``` + +Alternatively, you can pass an `api_key` or an `access_token` at request time to any of the endpoint methods. ```ruby -require 'emailable' +# set api_key at request time +Emailable.verify(api_key: 'your_api_key') -# set api key -Emailable.api_key = 'live_...' +# set access_token at request_time +Emailable.verify(access_token: 'your_access_token') ``` ### Verification From 5bc8eb1f4eded7d635e62f3e53bd0e96aa815a47 Mon Sep 17 00:00:00 2001 From: Daniel Arnold Date: Thu, 29 May 2025 17:06:40 -0400 Subject: [PATCH 5/5] README changes based on changes in add-access-token-auth --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1665d47..a94049a 100644 --- a/README.md +++ b/README.md @@ -32,21 +32,23 @@ Or install it yourself as: ### Authentication -The Emailable API can be authenticated with in a few ways. You will need an API key or an Access Token to authenticate. API keys are available in your [Emailable Dashboard](https://app.emailable.com/api). +The Emailable API requires either an API key or an access token for +authentication. API keys can be created and managed in the +[Emailable Dashboard](https://app.emailable.com/api). -The library can be configured globally with your account's API key +An API key can be set globally for the Emailable client: ```ruby -Emailable.api_key = 'your_api_key +Emailable.api_key = 'your_api_key' ``` -Alternatively, you can pass an `api_key` or an `access_token` at request time to any of the endpoint methods. +Or, you can specify an `api_key` or an `access_token` with each request: ```ruby # set api_key at request time Emailable.verify(api_key: 'your_api_key') -# set access_token at request_time +# set access_token at request time Emailable.verify(access_token: 'your_access_token') ```