Class: Mysigner::Client
- Inherits:
-
Object
- Object
- Mysigner::Client
- Defined in:
- lib/mysigner/client.rb
Constant Summary collapse
- LOOPBACK_HOSTS =
Hosts for which plain http is acceptable (local development). The org API token must NEVER be sent in cleartext to anything else.
%w[localhost 127.0.0.1 0.0.0.0 ::1].freeze
Instance Attribute Summary collapse
-
#api_token ⇒ Object
readonly
Returns the value of attribute api_token.
-
#api_url ⇒ Object
readonly
Returns the value of attribute api_url.
-
#user_email ⇒ Object
readonly
Returns the value of attribute user_email.
Class Method Summary collapse
-
.assert_secure_api_url!(url) ⇒ Object
Refuse to attach the API token to an insecure endpoint.
Instance Method Summary collapse
-
#connection ⇒ Object
Expose connection for direct access (e.g., binary downloads).
-
#delete(path) ⇒ Object
DELETE request.
-
#get(path, params: {}) ⇒ Object
GET request.
-
#initialize(api_url:, api_token:, user_email: nil) ⇒ Client
constructor
A new instance of Client.
-
#patch(path, body: {}) ⇒ Object
PATCH request.
-
#post(path, body: {}) ⇒ Object
POST request.
-
#test_connection ⇒ Object
Test connection with status endpoint.
Constructor Details
#initialize(api_url:, api_token:, user_email: nil) ⇒ Client
Returns a new instance of Client.
45 46 47 48 49 |
# File 'lib/mysigner/client.rb', line 45 def initialize(api_url:, api_token:, user_email: nil) @api_url = api_url @api_token = api_token @user_email = user_email end |
Instance Attribute Details
#api_token ⇒ Object (readonly)
Returns the value of attribute api_token.
10 11 12 |
# File 'lib/mysigner/client.rb', line 10 def api_token @api_token end |
#api_url ⇒ Object (readonly)
Returns the value of attribute api_url.
10 11 12 |
# File 'lib/mysigner/client.rb', line 10 def api_url @api_url end |
#user_email ⇒ Object (readonly)
Returns the value of attribute user_email.
10 11 12 |
# File 'lib/mysigner/client.rb', line 10 def user_email @user_email end |
Class Method Details
.assert_secure_api_url!(url) ⇒ Object
Refuse to attach the API token to an insecure endpoint. api_url comes from ~/.mysigner/config.yml or MYSIGNER_API_URL, so without this a poisoned config/env (or a bare-host URL the normalizer once downgraded to http://) would ship the Bearer token to an attacker-chosen host in cleartext. https is always allowed; http only for a loopback dev host.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/mysigner/client.rb', line 21 def self.assert_secure_api_url!(url) # A blank/nil URL means "not configured" — let the existing not-logged-in # paths handle that rather than reporting a confusing "insecure" error. return if url.to_s.strip.empty? uri = begin URI.parse(url.to_s) rescue URI::InvalidURIError nil end return if uri && uri.scheme == 'https' # uri.hostname (unlike uri.host) already strips IPv6 brackets, so # http://[::1]:3000 resolves to the loopback host "::1". host = uri&.hostname.to_s.downcase return if uri && uri.scheme == 'http' && LOOPBACK_HOSTS.include?(host) raise InsecureUrlError, "Refusing to send your API token over an insecure connection (#{url}). " \ 'Use an https:// URL — plain http is allowed only for localhost. ' \ 'Fix your api_url with `mysigner login` or the MYSIGNER_API_URL env var.' end |
Instance Method Details
#connection ⇒ Object
Expose connection for direct access (e.g., binary downloads)
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/mysigner/client.rb', line 101 def connection Client.assert_secure_api_url!(@api_url) @connection ||= Faraday.new(url: @api_url) do |f| # Fail fast on a stalled connection instead of hanging the CLI # forever. Without these the default adapter applies no timeout, so a # server that accepts the socket but never responds blocks every # command indefinitely and the Faraday::TimeoutError branch below is # effectively unreachable. The read timeout is generous because the # same client streams binary downloads. f..timeout = 120 f..open_timeout = 10 # Assert TLS verification as an in-code invariant rather than relying # on the adapter default — an adapter swap or stray env OpenSSL config # could otherwise silently weaken it. (No effect on http://localhost.) f.ssl.verify = true # Request middleware f.request :authorization, 'Bearer', @api_token f.request :json # Add X-User-Email header if email is present f.headers['X-User-Email'] = @user_email if @user_email # Retry failed requests f.request :retry, { max: 3, interval: 0.5, interval_randomness: 0.5, backoff_factor: 2, retry_statuses: [429, 502, 503, 504], methods: %i[get post patch delete] } # Response middleware f.response :json, content_type: /\bjson$/ # Don't use raise_error - we'll handle errors manually # Adapter f.adapter Faraday.default_adapter end end |
#delete(path) ⇒ Object
DELETE request
84 85 86 87 88 89 |
# File 'lib/mysigner/client.rb', line 84 def delete(path) response = connection.delete(path) handle_response(response) rescue Faraday::Error => e handle_faraday_error(e) end |
#get(path, params: {}) ⇒ Object
GET request
52 53 54 55 56 57 58 59 |
# File 'lib/mysigner/client.rb', line 52 def get(path, params: {}) response = connection.get(path) do |req| req.params = params end handle_response(response) rescue Faraday::Error => e handle_faraday_error(e) end |
#patch(path, body: {}) ⇒ Object
PATCH request
73 74 75 76 77 78 79 80 81 |
# File 'lib/mysigner/client.rb', line 73 def patch(path, body: {}) response = connection.patch(path) do |req| req.headers['Content-Type'] = 'application/json' req.body = body.to_json end handle_response(response) rescue Faraday::Error => e handle_faraday_error(e) end |
#post(path, body: {}) ⇒ Object
POST request
62 63 64 65 66 67 68 69 70 |
# File 'lib/mysigner/client.rb', line 62 def post(path, body: {}) response = connection.post(path) do |req| req.headers['Content-Type'] = 'application/json' req.body = body.to_json end handle_response(response) rescue Faraday::Error => e handle_faraday_error(e) end |
#test_connection ⇒ Object
Test connection with status endpoint
92 93 94 95 96 97 98 |
# File 'lib/mysigner/client.rb', line 92 def test_connection get('/api/v1/status') rescue ClientError => e raise e rescue StandardError => e raise ConnectionError, "Failed to connect: #{e.}" end |