summaryrefslogtreecommitdiff
path: root/vendor/acme-client/lib/acme/client/faraday_middleware.rb
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/acme-client/lib/acme/client/faraday_middleware.rb')
-rw-r--r--vendor/acme-client/lib/acme/client/faraday_middleware.rb123
1 files changed, 123 insertions, 0 deletions
diff --git a/vendor/acme-client/lib/acme/client/faraday_middleware.rb b/vendor/acme-client/lib/acme/client/faraday_middleware.rb
new file mode 100644
index 0000000..21e29c9
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/faraday_middleware.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+class Acme::Client::FaradayMiddleware < Faraday::Middleware
+ attr_reader :env, :response, :client
+
+ repo_url = 'https://github.com/unixcharles/acme-client'
+ USER_AGENT = "Acme::Client v#{Acme::Client::VERSION} (#{repo_url})".freeze
+
+ def initialize(app, client:)
+ super(app)
+ @client = client
+ end
+
+ def call(env)
+ @env = env
+ @env[:request_headers]['User-Agent'] = USER_AGENT
+ @env.body = crypto.generate_signed_jws(header: { nonce: pop_nonce }, payload: env.body)
+ @app.call(env).on_complete { |response_env| on_complete(response_env) }
+ rescue Faraday::TimeoutError
+ raise Acme::Client::Error::Timeout
+ end
+
+ def on_complete(env)
+ @env = env
+
+ raise_on_not_found!
+ store_nonce
+ env.body = decode_body
+ env.response_headers['Link'] = decode_link_headers
+
+ return if env.success?
+
+ raise_on_error!
+ end
+
+ private
+
+ def raise_on_not_found!
+ raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
+ end
+
+ def raise_on_error!
+ raise error_class, error_message
+ end
+
+ def error_message
+ if env.body.is_a? Hash
+ env.body['detail']
+ else
+ "Error message: #{env.body}"
+ end
+ end
+
+ def error_class
+ if error_name && !error_name.empty? && Acme::Client::Error.const_defined?(error_name)
+ Object.const_get("Acme::Client::Error::#{error_name}")
+ else
+ Acme::Client::Error
+ end
+ end
+
+ def error_name
+ @error_name ||= begin
+ return unless env.body.is_a?(Hash)
+ return unless env.body.key?('type')
+
+ env.body['type'].gsub('urn:acme:error:', '').split(/[_-]/).map(&:capitalize).join
+ end
+ end
+
+ def decode_body
+ content_type = env.response_headers['Content-Type']
+
+ if content_type == 'application/json' || content_type == 'application/problem+json'
+ JSON.load(env.body)
+ else
+ env.body
+ end
+ end
+
+ LINK_MATCH = /<(.*?)>;rel="([\w-]+)"/
+
+ def decode_link_headers
+ return unless env.response_headers.key?('Link')
+ link_header = env.response_headers['Link']
+
+ links = link_header.split(', ').map { |entry|
+ _, link, name = *entry.match(LINK_MATCH)
+ [name, link]
+ }
+
+ Hash[*links.flatten]
+ end
+
+ def store_nonce
+ nonces << env.response_headers['replay-nonce']
+ end
+
+ def pop_nonce
+ if nonces.empty?
+ get_nonce
+ else
+ nonces.pop
+ end
+ end
+
+ def get_nonce
+ response = Faraday.head(env.url, nil, 'User-Agent' => USER_AGENT)
+ response.headers['replay-nonce']
+ end
+
+ def nonces
+ client.nonces
+ end
+
+ def private_key
+ client.private_key
+ end
+
+ def crypto
+ @crypto ||= Acme::Client::Crypto.new(private_key)
+ end
+end