dhc 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dbcbbfbe466771c1ad389aebe29d997915137216b50b0aceff73f3cbc589f050
4
- data.tar.gz: a659a0b022d803a700352d20fe96482731470b31234e8db9df3f665da5547ae7
3
+ metadata.gz: 674d43c54e6a69f47876b85ef1cc08fdd015002ac96f59fada268959d970d356
4
+ data.tar.gz: 1e9babbad39edeeedde2269d93108a2a0f604ff56cd7f66de5c960de68328bf8
5
5
  SHA512:
6
- metadata.gz: 1851454238cb9552457a56c020edcb88b8b7e37eed6ebc0c6d1a8574c8250c520cfd48d8ca1e9f82ddcd334085daf1651fb0c31221ea638921aeb5873218846f
7
- data.tar.gz: 4432965dcb69081d1d082cfc47101644a817f387d79213f57b8d2b80192901148e49b6a9ceb4ddb10c632e425385b30026de4c2bbbe69ffdaeefb5bf105c6bf5
6
+ metadata.gz: 2f29a2c073127d45a6fcd88fab98b8b82b27f3e64aa40a71eab788af580d972d0df7d2dba16e7ba528a28e21a59bf44bfb7d15a93f13bacfd03b1e6d7a0bf78b
7
+ data.tar.gz: 42aadb278b925b63b2c947616ae7c1c6f6d1c7b54487287303c8b3c0b59b54ed0f7b45de501615b8ab854bd73d9e125218ac7c8c205b033566ada8747766ae06
data/README.md CHANGED
@@ -471,6 +471,27 @@ Adds the following header to the request:
471
471
 
472
472
  Assuming the method `access_token` responds on runtime of the request with `123456`.
473
473
 
474
+ ###### Refresh Bearer Authentication
475
+
476
+ If you configure `expires_at` and `refresh` proc in addition to `bearer`, DHC will refresh the bearer token using the defined `refresh` proc in two cases:
477
+
478
+ 1. before the request if `expires_at` < `DateTime.now + 1.minute`
479
+ 2. if the request fails and `refresh(response)` responds to true
480
+
481
+ ```ruby
482
+ refresh = ->(response = nil){
483
+ if response
484
+ if response.code == 401 && response.data && response.data.error_code == 'ACCESS_TOKEN_EXPIRED'
485
+ session[:access_token] = new_access_token
486
+ end
487
+ else
488
+ session[:access_token] = new_access_token
489
+ end
490
+ }
491
+
492
+ DHC.get('http://depay.fi', auth: { bearer: -> { session[:access_token] }, refresh: refresh, expires_at: DateTime.now + 1.day })
493
+ ```
494
+
474
495
  ##### Basic Authentication
475
496
 
476
497
  ```ruby
@@ -6,6 +6,7 @@ class DHC::Auth < DHC::Interceptor
6
6
 
7
7
  def before_init
8
8
  body_authentication! if auth_options[:body]
9
+ auth_options[:refresh].call if refresh_bearer?
9
10
  end
10
11
 
11
12
  def before_request
@@ -14,13 +15,23 @@ class DHC::Auth < DHC::Interceptor
14
15
  end
15
16
 
16
17
  def after_response
17
- return unless configuration_correct?
18
- return unless reauthenticate?
19
- reauthenticate!
18
+ reauthenticate! if configuration_correct? && reauthenticate?
19
+ retry_with_refreshed_token! if retry_with_refreshed_token?
20
20
  end
21
21
 
22
22
  private
23
23
 
24
+ def refresh_bearer?
25
+ auth_options[:bearer] &&
26
+ auth_options[:refresh].is_a?(Proc) &&
27
+ bearer_expired?(auth_options[:expires_at])
28
+ end
29
+
30
+ def bearer_expired?(expires_at)
31
+ expires_at = DateTime.parse(expires_at) if expires_at.is_a?(String)
32
+ expires_at < DateTime.now + 1.minute
33
+ end
34
+
24
35
  def body_authentication!
25
36
  auth = auth_options[:body]
26
37
  request.options[:body] = (request.options[:body] || {}).merge(auth)
@@ -65,6 +76,19 @@ class DHC::Auth < DHC::Interceptor
65
76
  DHC::Error.find(response) == DHC::Unauthorized
66
77
  end
67
78
 
79
+ def retry_with_refreshed_token!
80
+ bearer_authentication!
81
+ new_options = request.options.dup
82
+ new_options = new_options.merge(retry: { max: 1 })
83
+ request.options = new_options
84
+ end
85
+
86
+ def retry_with_refreshed_token?
87
+ auth_options[:bearer] &&
88
+ auth_options[:refresh].is_a?(Proc) &&
89
+ auth_options[:refresh].call(response)
90
+ end
91
+
68
92
  def bearer_header_present?
69
93
  @has_bearer_header ||= request.headers['Authorization'] =~ /^Bearer .+$/i
70
94
  end
@@ -28,7 +28,7 @@ class DHC::Rollbar < DHC::Interceptor
28
28
  }.merge additional_params
29
29
  begin
30
30
  Rollbar.warning("Status: #{response.code} URL: #{request.url}", data)
31
- rescue Encoding::UndefinedConversionError
31
+ rescue JSON::GeneratorError
32
32
  sanitized_data = data.deep_transform_values { |value| self.class.fix_invalid_encoding(value) }
33
33
  Rollbar.warning("Status: #{response.code} URL: #{request.url}", sanitized_data)
34
34
  end
@@ -59,7 +59,7 @@ class DHC::Throttle < DHC::Interceptor
59
59
  remaining = tracker[:remaining] * 100
60
60
  limit = tracker[:limit]
61
61
  remaining_quota = 100 - options[:break].to_i
62
- remaining < remaining_quota * limit
62
+ remaining <= remaining_quota * limit
63
63
  end
64
64
  end
65
65
 
data/lib/dhc/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DHC
4
- VERSION ||= '2.2.0'
4
+ VERSION ||= '2.3.0'
5
5
  end
@@ -15,7 +15,7 @@ describe DHC::Error do
15
15
  expect { "#{valid} #{invalid}" }.to raise_error Encoding::CompatibilityError
16
16
  end
17
17
  it 'to_json on an array raises an error' do
18
- expect { [valid, invalid].to_json }.to raise_error Encoding::UndefinedConversionError
18
+ expect { [valid, invalid].to_json }.to raise_error JSON::GeneratorError
19
19
  end
20
20
 
21
21
  it 'to_s on a hash does not raise an error' do
@@ -23,7 +23,7 @@ describe DHC::Error do
23
23
  end
24
24
 
25
25
  it 'to_json on a hash does raise an error' do
26
- expect { { valid: valid, invalid: invalid }.to_json }.to raise_error Encoding::UndefinedConversionError
26
+ expect { { valid: valid, invalid: invalid }.to_json }.to raise_error JSON::GeneratorError
27
27
  end
28
28
  end
29
29
 
@@ -3,17 +3,61 @@
3
3
  require 'rails_helper'
4
4
 
5
5
  describe DHC::Auth do
6
- before(:each) do
7
- DHC.config.interceptors = [DHC::Auth]
6
+ context 'simple bearer token authentication' do
7
+ before(:each) do
8
+ DHC.config.interceptors = [DHC::Auth]
9
+ end
10
+
11
+ it 'adds the bearer token to every request' do
12
+ def bearer_token
13
+ '123456'
14
+ end
15
+ options = { bearer: -> { bearer_token } }
16
+ DHC.config.endpoint(:local, 'http://depay.fi', auth: options)
17
+ stub_request(:get, 'http://depay.fi').with(headers: { 'Authorization' => 'Bearer 123456' })
18
+ DHC.get(:local)
19
+ end
8
20
  end
9
21
 
10
- it 'adds the bearer token to every request' do
11
- def bearer_token
12
- '123456'
22
+ context 'refresh' do
23
+ before(:each) do
24
+ DHC.config.interceptors = [DHC::Auth, DHC::Retry]
25
+ end
26
+
27
+ let(:first_access_token) { '1_ACCESS_TOKEN' }
28
+ let(:second_access_token) { '2_ACCESS_TOKEN' }
29
+ let(:third_access_token) { '3_ACCESS_TOKEN' }
30
+
31
+ let :session do
32
+ {
33
+ access_token: first_access_token
34
+ }
35
+ end
36
+
37
+ refresh = -> {}
38
+
39
+ before do
40
+ refresh = ->(response = nil) {
41
+ if response
42
+ if response.code == 401 && response.data && response.data.error_code == 'ACCESS_TOKEN_EXPIRED'
43
+ session[:access_token] = third_access_token
44
+ end
45
+ else
46
+ session[:access_token] = second_access_token
47
+ end
48
+ }
49
+ end
50
+
51
+ it 'refreshes the bearer if it expired' do
52
+ stub_request(:get, 'http://depay.fi/').with(headers: { 'Authorization' => 'Bearer 2_ACCESS_TOKEN' })
53
+ DHC.get('http://depay.fi', auth: { bearer: -> { session[:access_token] }, refresh: refresh, expires_at: (DateTime.now - 1.minute).to_s })
54
+ end
55
+
56
+ it 'can evaluate response errors (like unauthorized) inside the refresh proc' do
57
+ stub_request(:get, 'http://depay.fi/').with(headers: { 'Authorization' => 'Bearer 2_ACCESS_TOKEN' })
58
+ .to_return(status: 401, body: { "error_code": 'ACCESS_TOKEN_EXPIRED' }.to_json)
59
+ stub_request(:get, 'http://depay.fi/').with(headers: { 'Authorization' => 'Bearer 3_ACCESS_TOKEN' })
60
+ DHC.get('http://depay.fi', auth: { bearer: -> { session[:access_token] }, refresh: refresh, expires_at: (DateTime.now - 1.minute).to_s })
13
61
  end
14
- options = { bearer: -> { bearer_token } }
15
- DHC.config.endpoint(:local, 'http://depay.fi', auth: options)
16
- stub_request(:get, 'http://depay.fi').with(headers: { 'Authorization' => 'Bearer 123456' })
17
- DHC.get(:local)
18
62
  end
19
63
  end
@@ -14,7 +14,7 @@ describe DHC::Rollbar do
14
14
  class Rollbar; end
15
15
  ::Rollbar.stub(:warning) do
16
16
  call_counter += 1
17
- raise Encoding::UndefinedConversionError if call_counter == 1
17
+ raise JSON::GeneratorError if call_counter == 1
18
18
  end
19
19
 
20
20
  # the response for the caller is still DHC::BadRequest
@@ -24,7 +24,7 @@ describe DHC::Rollbar do
24
24
  let(:invalid) { (+"in\xc3lid").force_encoding('ASCII-8BIT') }
25
25
  let(:valid) { described_class.fix_invalid_encoding(invalid) }
26
26
 
27
- it 'calls fix_invalid_encoding incase a Encoding::UndefinedConversionError was encountered' do
27
+ it 'calls fix_invalid_encoding incase a JSON::GeneratorError was encountered' do
28
28
  expect(described_class).to have_received(:fix_invalid_encoding).with(invalid)
29
29
  end
30
30
 
@@ -33,11 +33,11 @@ describe DHC::Throttle do
33
33
  end
34
34
 
35
35
  context 'breaks' do
36
- let(:quota_limit) { 10 }
36
+ let(:quota_limit) { 100 }
37
37
  let(:break_after) { '79%' }
38
38
 
39
39
  it 'hit the breaks if throttling quota is reached' do
40
- 8.times do
40
+ 79.times do
41
41
  DHC.get('http://depay.fi', options)
42
42
  end
43
43
  expect { DHC.get('http://depay.fi', options) }.to raise_error(
@@ -50,19 +50,29 @@ describe DHC::Throttle do
50
50
  let(:break_after) { '80%' }
51
51
 
52
52
  it 'does not hit the breaks' do
53
- 9.times do
53
+ 80.times do
54
54
  DHC.get('http://depay.fi', options)
55
55
  end
56
56
  end
57
+
58
+ it 'does hit the breaks surpassing quota' do
59
+ 80.times do
60
+ DHC.get('http://depay.fi', options)
61
+ end
62
+ expect { DHC.get('http://depay.fi', options) }.to raise_error(
63
+ DHC::Throttle::OutOfQuota,
64
+ 'Reached predefined quota for depay.fi'
65
+ )
66
+ end
57
67
  end
58
68
  end
59
69
 
60
70
  context 'expires' do
61
71
  let(:break_after) { '80%' }
62
- let(:quota_limit) { 10 }
72
+ let(:quota_limit) { 100 }
63
73
 
64
74
  it 'attempts another request if the quota expired' do
65
- 9.times do
75
+ 80.times do
66
76
  DHC.get('http://depay.fi', options)
67
77
  end
68
78
  expect { DHC.get('http://depay.fi', options) }.to raise_error(
@@ -70,7 +80,7 @@ describe DHC::Throttle do
70
80
  'Reached predefined quota for depay.fi'
71
81
  )
72
82
  Timecop.travel(Time.zone.now + 1.minute)
73
- 9.times do
83
+ 80.times do
74
84
  DHC.get('http://depay.fi', options)
75
85
  end
76
86
  expect { DHC.get('http://depay.fi', options) }.to raise_error(
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dhc
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/DePayFi/dhc/contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-23 00:00:00.000000000 Z
11
+ date: 2023-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -399,7 +399,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
399
399
  version: '0'
400
400
  requirements:
401
401
  - Ruby >= 2.0.0
402
- rubygems_version: 3.2.22
402
+ rubygems_version: 3.4.1
403
403
  signing_key:
404
404
  specification_version: 4
405
405
  summary: Advanced HTTP Client for Ruby, fueled with interceptors