Commit a7d2e820 authored by Tiago's avatar Tiago
Browse files

auth: always retry on 401 status code error when the token generation is dynamic

this bypasses the change_requests check, as it's assumed that the next request with a refreshed token will succeed
git add lib -p
parent 4f44d660
Loading
Loading
Loading
Loading
+21 −2
Original line number Diff line number Diff line
@@ -81,10 +81,14 @@ module HTTPX
        def generate_auth_token
          return unless (auth_value = @options.auth_header_value)

          auth_value = auth_value.call(self) if auth_value.respond_to?(:call)
          auth_value = auth_value.call(self) if dynamic_auth_token?(auth_value)

          auth_value
        end

        def dynamic_auth_token?(auth_header_value)
          auth_header_value&.respond_to?(:call)
        end
      end

      module RequestMethods
@@ -99,14 +103,29 @@ module HTTPX

      module AuthRetries
        module InstanceMethods
          private

          def retryable_request?(request, response, options)
            super || auth_error?(response, options)
          end

          def retryable_response?(response, options)
            auth_error?(response, options) || super
          end

          def prepare_to_retry(request, response)
            super

            return unless @options.generate_auth_value_on_retry && @options.generate_auth_value_on_retry.call(response)
            return unless auth_error?(response, request.options) ||
                          (@options.generate_auth_value_on_retry && @options.generate_auth_value_on_retry.call(response))

            request.headers.get("authorization").pop
            @auth_header_value = generate_auth_token
          end

          def auth_error?(response, options)
            response.is_a?(Response) && response.status == 401 && dynamic_auth_token?(options.auth_header_value)
          end
        end
      end
    end
+8 −18
Original line number Diff line number Diff line
@@ -259,32 +259,22 @@ module HTTPX

          @oauth_session.fetch_access_token(self)
        end
      end

      module OAuthRetries
        class << self
          def extra_options(options)
            options.merge(
              retry_on: method(:response_oauth_error?),
              generate_auth_value_on_retry: method(:response_oauth_error?)
            )
          end

          def response_oauth_error?(res)
            res.is_a?(Response) && res.status == 401
        def dynamic_auth_token?(auth_header_value)
          @oauth_session
        end
      end

      module OAuthRetries
        module InstanceMethods
          def prepare_to_retry(_request, response)
            unless @oauth_session && @options.generate_auth_value_on_retry && @options.generate_auth_value_on_retry.call(response)
              return super
            end
          private

            @oauth_session.reset!
          def prepare_to_retry(_request, response)
            @oauth_session.reset! if @oauth_session

            super
          end

        end
      end
    end
+10 −1
Original line number Diff line number Diff line
module HTTPX
  module Plugins
    module Auth
      type auth_header_value_type = String | ^(Request request) -> string

      interface _AuthOptions
        def auth_header_value: () -> (String | ^(Request request) -> string)?
        def auth_header_value: () -> auth_header_value_type?

        def auth_header_type: () -> String?
      end
@@ -22,6 +24,8 @@ module HTTPX
        private

        def generate_auth_token: () -> String?

        def dynamic_auth_token?: (auth_header_value_type auth_header_value) -> boolish
      end

      module RequestMethods
@@ -29,6 +33,11 @@ module HTTPX
      end

      module AuthRetries
        module InstanceMethods
          private

          def auth_error?: (response response, Options options) -> boolish
        end
      end
    end

+0 −2
Original line number Diff line number Diff line
@@ -87,8 +87,6 @@ module HTTPX
      end

      module OAuthRetries
        def self?.response_oauth_error?: (response res) -> bool

        module InstanceMethods
        end
      end
+14 −5
Original line number Diff line number Diff line
@@ -50,18 +50,27 @@ module Requests
      end

      def test_plugin_auth_regenerate_on_retry
        uri = build_uri("/status/401")
        i = 0
        session = HTTPX.plugin(RequestInspector)
                       .plugin(:retries, max_retries: 1, retry_on: ->(res) { res.status == 401 })
                       .plugin(:auth, generate_auth_value_on_retry: ->(res) { res.status == 401 })
                       .plugin(:retries, max_retries: 1, retry_on: ->(res) { res.status == 400 })
                       .plugin(:auth, generate_auth_value_on_retry: ->(res) { res.status == 400 })
                       .authorization { "TOKEN#{i += 1}" }
        response = session.get(uri)
        verify_status(response, 401)

        response = session.get(build_uri("/status/400"))
        verify_status(response, 400)
        assert session.calls == 1, "expected two errors to have been sent"
        req1, req2 = session.total_requests
        assert req1.headers["authorization"] == "TOKEN1"
        assert req2.headers["authorization"] == "TOKEN2"
        session.reset

        # 401 errors are always retried with a fresh token, no matter the verb
        response = session.get(build_uri("/status/401"))
        verify_status(response, 401)
        assert session.calls == 1, "expected two errors to have been sent"
        req1, req2 = session.total_requests
        assert req1.headers["authorization"] == "TOKEN2", "the last successful token should have been reused"
        assert req2.headers["authorization"] == "TOKEN3"
      end

      # Bearer Auth