diff --git a/docs/middleware/request/authentication.md b/docs/middleware/request/authentication.md index 3342f1fb5..867289dc2 100644 --- a/docs/middleware/request/authentication.md +++ b/docs/middleware/request/authentication.md @@ -23,6 +23,16 @@ Faraday.new(...) do |conn| end ``` +### With a proc + +You can also provide a proc, which will be evaluated on each request: + + ```ruby + Faraday.new(...) do |conn| + conn.request :authorization, 'Bearer', -> { MyAuthStorage.get_auth_token } + end + ``` + ### Basic Authentication `BasicAuthentication` adds a 'Basic' type Authorization header to a Faraday request. diff --git a/lib/faraday/request/authorization.rb b/lib/faraday/request/authorization.rb index f9b56c871..8af00d7f7 100644 --- a/lib/faraday/request/authorization.rb +++ b/lib/faraday/request/authorization.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'base64' + module Faraday class Request # Request middleware for the Authorization HTTP header @@ -39,16 +41,47 @@ def self.build_hash(type, hash) # @param app [#call] # @param type [String, Symbol] Type of Authorization - # @param token [String, Symbol, Hash] Token value for the Authorization - def initialize(app, type, token) - @header_value = self.class.header(type, token) + # @param params [Array] parameters to build the Authorization header. + # If the type is `:basic`, then these can be a login and password pair. + # Otherwise, a single value is expected that will be appended after the type. + # This value can be a proc, in which case it will be invoked on each request. + def initialize(app, type, *params) + @type = type + @params = params + @header_value = self.class.header(type, params[0]) unless params[0].is_a? Proc super(app) end # @param env [Faraday::Env] - def call(env) - env.request_headers[KEY] = @header_value unless env.request_headers[KEY] - @app.call(env) + def on_request(env) + return if env.request_headers[KEY] + + env.request_headers[KEY] = header_from(@type, *@params) + end + + private + + # @param type [String, Symbol] + # @param params [Array] + # @return [String] a header value + def header_from(type, *params) + return @header_value if @header_value + + if type.to_s.casecmp('basic').zero? && params.size == 2 + basic_header_from(*params) + elsif params.size != 1 + raise ArgumentError, "Unexpected params received (got #{params.size} instead of 1)" + else + value = params.first + value = value.call if value.is_a?(Proc) + "#{type} #{value}" + end + end + + def basic_header_from(login, pass) + value = Base64.encode64("#{login}:#{pass}") + value.delete!("\n") + "Basic #{value}" end end end diff --git a/spec/faraday/request/authorization_spec.rb b/spec/faraday/request/authorization_spec.rb index 3f13392e6..9bc23f03d 100644 --- a/spec/faraday/request/authorization_spec.rb +++ b/spec/faraday/request/authorization_spec.rb @@ -84,5 +84,13 @@ include_examples 'does not interfere with existing authentication' end + + context 'when passed a string and a proc' do + let(:auth_config) { ['Bearer', -> { 'custom_from_proc' }] } + + it { expect(response.body).to eq('Bearer custom_from_proc') } + + include_examples 'does not interfere with existing authentication' + end end end