boldsign 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9fc2965681164f6e721c5d7a18b9370d1ddfd7570916cde257078b29d3be34d5
4
- data.tar.gz: 9210a75137622829403d8b6ae47dfa1f51e3f71462313b12ef076e6b240391a9
3
+ metadata.gz: 9fc744443078d2ffab4156204b7921ef617c3788d373253956a0fe9f3455a14a
4
+ data.tar.gz: 133c334403bb3a2655f61ab7c14f8d9d75c9e3adf5212f9bdf7e69811a2cda2a
5
5
  SHA512:
6
- metadata.gz: 00ab26917b2ad3ab3af835ec58842b1fa75ba19d61254447466a5eee7b5db388f9c5800ec7807806fd737526772516f43277520ba1005d18539a8e54356f0ee8
7
- data.tar.gz: '038338c9aca83500340f85e0d6d2e6247466701739ff3606ae8bf59905f930541514e9e3130ac5296b9ee658085993c601126d84ee5e6527451b4ba1d75df1ab'
6
+ metadata.gz: 96e5622f76bf23b2df6be9663542fb0db5da070f40480e6649960da33d336c14afcafc36c308b5dad867fa4ce90ab3faf574c3c956011c330c7599713cf3afe2
7
+ data.tar.gz: 640539d3b260f4b50de92bddca66ed45a794c0c54c722d32d807134eee505d06f737542d077403ab71f5699f23e106567d1bf19dfb4954420398904e9cd756e2
data/CHANGELOG.md CHANGED
@@ -7,6 +7,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] — 2026-06-02
11
+
12
+ ### Added
13
+ - OAuth 2.0 authentication via the **client credentials** grant. Configure
14
+ `client_id` / `client_secret` (on `Boldsign.configure` or `Client.new`) and
15
+ the client fetches a bearer token from the region's account host
16
+ (`https://account.boldsign.com/connect/token` for `:us`), caches it until
17
+ shortly before `expires_in` lapses, and sends `Authorization: Bearer …`
18
+ instead of `X-API-KEY`. Token fetches are mutex-guarded for shared clients.
19
+ - `scope:` (defaults to `BoldSign.Documents.All`) and `token_url:` options to
20
+ override the requested scope and token endpoint.
21
+ - `access_token:` option to supply a pre-obtained bearer token that is used
22
+ as-is (not refreshed).
23
+ - `Boldsign::AccessToken` encapsulating the client-credentials token lifecycle.
24
+ - `TOKEN_REGIONS` / `DEFAULT_TOKEN_BASE_URL` constants and `Client#auth_mode`.
25
+
26
+ ### Changed
27
+ - `Client.new` no longer requires an API key. It accepts any one of
28
+ `client_id`/`client_secret`, `access_token`, or `api_key`, and raises
29
+ `ConfigurationError` only when none is provided. The API-key path is
30
+ unchanged, so existing `api_key:` callers keep working.
31
+
32
+ ## [0.4.0] — 2026-05-22
33
+
34
+ ### Changed
35
+ - Request body hash keys are now recursively converted to **camelCase** (not
36
+ PascalCase). This matches the casing used by the BoldSign OpenAPI/Swagger
37
+ schema for all request body DTOs, including nested objects like
38
+ `DocumentSigner` and `FormField`. Callers pass idiomatic snake_case (or
39
+ camelCase / PascalCase) Ruby keys and the gem emits the casing BoldSign
40
+ validates against. **Breaking** for callers that relied on the 0.3.0
41
+ PascalCase output.
42
+ - Multipart file uploads are still keyed `Files` (capital F) for the
43
+ document send endpoint; only request body field names are camelCased.
44
+ - Multipart `send_document`: single-element arrays for both `signers:` and
45
+ `files:` are now unwrapped before encoding. BoldSign's POST
46
+ `/v1/document/send` rejects `signers=[{...}]` (JSON-array string) and
47
+ `Files[]=…` (Faraday's automatic array-bracket form). The gem now emits
48
+ `signers={...}` (single JSON object) and `Files=<file>` (single FilePart)
49
+ for the one-signer/one-file case. Multi-signer / multi-file uploads are
50
+ not yet supported via this helper and raise `NotImplementedError`.
51
+
52
+ ### Removed
53
+ - `Boldsign::CaseConvert.pascalize` / `.pascalize_key`. Replaced by
54
+ `.camelize` / `.camelize_key`.
55
+
10
56
  ## [0.3.0] — 2026-05-22
11
57
 
12
58
  ### Changed
@@ -44,5 +90,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
44
90
  - 100% line and branch test coverage enforced via SimpleCov.
45
91
  - GitHub Actions CI on Ruby 3.4 and 4.0.
46
92
 
47
- [Unreleased]: https://github.com/kleinjm/boldsign-ruby/compare/v0.1.0...HEAD
93
+ [Unreleased]: https://github.com/kleinjm/boldsign-ruby/compare/v0.5.0...HEAD
94
+ [0.5.0]: https://github.com/kleinjm/boldsign-ruby/compare/v0.4.0...v0.5.0
48
95
  [0.1.0]: https://github.com/kleinjm/boldsign-ruby/releases/tag/v0.1.0
data/README.md CHANGED
@@ -17,6 +17,32 @@ gem "boldsign"
17
17
 
18
18
  ## Configuration
19
19
 
20
+ The client authenticates with either an API key or OAuth 2.0.
21
+
22
+ ### OAuth 2.0 (client credentials)
23
+
24
+ Register an OAuth app in the BoldSign dashboard (API → OAuth Apps) and configure
25
+ its `client_id` / `client_secret`. The client fetches a bearer token from the
26
+ region's account host, caches it until shortly before it expires, and refreshes
27
+ automatically — no token plumbing on your side.
28
+
29
+ ```ruby
30
+ Boldsign.configure do |c|
31
+ c.client_id = ENV["BOLDSIGN_CLIENT_ID"]
32
+ c.client_secret = ENV["BOLDSIGN_CLIENT_SECRET"]
33
+ c.region = :us # :us, :eu, :ca, :au
34
+ # c.scope = "BoldSign.Documents.All" # optional; empty grants all the app allows
35
+ end
36
+ ```
37
+
38
+ You can also pass a token you obtained yourself (used as-is, not refreshed):
39
+
40
+ ```ruby
41
+ Boldsign::Client.new(access_token: "…", region: :us)
42
+ ```
43
+
44
+ ### API key
45
+
20
46
  ```ruby
21
47
  Boldsign.configure do |c|
22
48
  c.api_key = ENV["BOLDSIGN_API_KEY"]
@@ -30,14 +56,17 @@ Or instantiate a client directly:
30
56
  client = Boldsign::Client.new(api_key: "…", region: :us)
31
57
  ```
32
58
 
33
- Region base URLs:
59
+ Region hosts:
60
+
61
+ | Region | API base URL | OAuth account host |
62
+ | ------ | ------------ | ------------------ |
63
+ | `:us` | `https://api.boldsign.com` | `https://account.boldsign.com` |
64
+ | `:eu` | `https://api-eu.boldsign.com` | `https://account-eu.boldsign.com` |
65
+ | `:ca` | `https://api-ca.boldsign.com` | `https://account-ca.boldsign.com` |
66
+ | `:au` | `https://api-au.boldsign.com` | `https://account-au.boldsign.com` |
34
67
 
35
- | Region | URL |
36
- | ------ | --- |
37
- | `:us` | `https://api.boldsign.com` |
38
- | `:eu` | `https://api-eu.boldsign.com` |
39
- | `:ca` | `https://api-ca.boldsign.com` |
40
- | `:au` | `https://api-au.boldsign.com` |
68
+ The token endpoint is the account host + `/connect/token`; override the full URL
69
+ with `token_url:` if a region differs.
41
70
 
42
71
  ## Usage
43
72
 
@@ -0,0 +1,95 @@
1
+ module Boldsign
2
+ # Fetches and caches an OAuth 2.0 access token using the client credentials
3
+ # grant (server-to-server; no user interaction).
4
+ #
5
+ # A single instance is shared by a {Client} and may be hit from multiple
6
+ # threads, so token reads/refreshes are guarded by a mutex. The token is
7
+ # cached until shortly before it expires ({EXPIRY_LEEWAY_SECONDS}), then a new
8
+ # one is requested on the next call. The client credentials grant does not
9
+ # issue refresh tokens — expiry is handled by re-requesting.
10
+ #
11
+ # @see https://developers.boldsign.com/authentication/oauth-2-0/
12
+ class AccessToken
13
+ # Token endpoint path, appended to the region's account host.
14
+ TOKEN_PATH = "/connect/token".freeze
15
+
16
+ # Default scope requested for the access token. Covers the document /
17
+ # signature endpoints this gem is primarily used for. Pass a different
18
+ # `scope:` to request others (an empty scope grants all the app is allowed).
19
+ DEFAULT_SCOPE = "BoldSign.Documents.All".freeze
20
+
21
+ # Refresh this many seconds before the token actually expires, so an
22
+ # in-flight request never carries a token that lapses server-side mid-call.
23
+ EXPIRY_LEEWAY_SECONDS = 60
24
+
25
+ # Fallback lifetime (seconds) when the token response omits `expires_in`.
26
+ DEFAULT_EXPIRES_IN = 3600
27
+
28
+ # @param client_id [String] OAuth app client ID.
29
+ # @param client_secret [String] OAuth app client secret.
30
+ # @param token_url [String] Full token endpoint URL (host + {TOKEN_PATH}).
31
+ # @param scope [String] OAuth scope to request.
32
+ # @param adapter [Symbol] Faraday adapter.
33
+ def initialize(client_id:, client_secret:, token_url:, scope: DEFAULT_SCOPE,
34
+ adapter: Faraday.default_adapter)
35
+ @client_id = client_id
36
+ @client_secret = client_secret
37
+ @token_url = token_url
38
+ @scope = scope
39
+ @adapter = adapter
40
+ @mutex = Mutex.new
41
+ end
42
+
43
+ # @return [String] a currently-valid bearer token, fetching or refreshing
44
+ # one if the cached token is missing or near expiry.
45
+ def value
46
+ @mutex.synchronize do
47
+ refresh! if expired?
48
+ @token
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def expired?
55
+ @token.nil? || Time.now >= @expires_at
56
+ end
57
+
58
+ def refresh!
59
+ response = connection.post(@token_url) do |req|
60
+ req.headers["Content-Type"] = "application/x-www-form-urlencoded"
61
+ req.body = URI.encode_www_form(
62
+ grant_type: "client_credentials",
63
+ client_id: @client_id,
64
+ client_secret: @client_secret,
65
+ scope: @scope
66
+ )
67
+ end
68
+ raise_token_error(response) unless response.success?
69
+
70
+ body = JSON.parse(response.body)
71
+ @token = body["access_token"]
72
+ expires_in = (body["expires_in"] || DEFAULT_EXPIRES_IN).to_i
73
+ @expires_at = Time.now + expires_in - EXPIRY_LEEWAY_SECONDS
74
+ end
75
+
76
+ def connection
77
+ Faraday.new do |f|
78
+ f.adapter @adapter
79
+ end
80
+ end
81
+
82
+ def raise_token_error(response)
83
+ body = begin
84
+ JSON.parse(response.body)
85
+ rescue StandardError
86
+ response.body
87
+ end
88
+ message = body.is_a?(Hash) ? (body["error_description"] || body["error"] || body.to_s) : body.to_s
89
+ raise AuthenticationError.new(
90
+ "BoldSign OAuth token request failed (#{response.status}): #{message}",
91
+ status: response.status, body: body, response: response
92
+ )
93
+ end
94
+ end
95
+ end
@@ -1,36 +1,39 @@
1
1
  module Boldsign
2
- # Converts arbitrary Ruby hash keys (snake_case or camelCase, Symbol or
3
- # String) to PascalCase string keys recursively, matching the casing the
4
- # BoldSign REST API expects in request bodies. Non-Hash and non-Array values
5
- # — including binary IO objects, FilePart instances, scalars, dates, etc. —
6
- # pass through untouched.
2
+ # Converts arbitrary Ruby hash keys (snake_case, camelCase, or PascalCase;
3
+ # Symbol or String) to camelCase string keys recursively, matching the
4
+ # casing the BoldSign REST API expects in request bodies. Non-Hash and
5
+ # non-Array values — including binary IO objects, FilePart instances,
6
+ # scalars, dates, etc. — pass through untouched.
7
7
  module CaseConvert
8
8
  module_function
9
9
 
10
- # Recursively PascalCase every Hash key reachable from `obj`.
10
+ # Recursively camelCase every Hash key reachable from `obj`.
11
11
  # @param obj [Object]
12
- # @return [Object] new object with the same shape and PascalCase keys
13
- def pascalize(obj)
12
+ # @return [Object] new object with the same shape and camelCase keys
13
+ def camelize(obj)
14
14
  case obj
15
15
  when Hash
16
- obj.each_with_object({}) { |(k, v), acc| acc[pascalize_key(k)] = pascalize(v) }
16
+ obj.each_with_object({}) { |(k, v), acc| acc[camelize_key(k)] = camelize(v) }
17
17
  when Array
18
- obj.map { |v| pascalize(v) }
18
+ obj.map { |v| camelize(v) }
19
19
  else
20
20
  obj
21
21
  end
22
22
  end
23
23
 
24
24
  # @param key [String, Symbol]
25
- # @return [String] PascalCase string
26
- def pascalize_key(key)
25
+ # @return [String] camelCase string
26
+ def camelize_key(key)
27
27
  str = key.to_s
28
28
  return str if str.empty?
29
29
 
30
30
  if str.include?("_")
31
- str.split("_").map { |part| capitalize_word(part) }.join
31
+ parts = str.split("_").reject(&:empty?)
32
+ return str if parts.empty?
33
+
34
+ ([parts.first.downcase] + parts.drop(1).map { |part| capitalize_word(part) }).join
32
35
  else
33
- capitalize_word(str)
36
+ lowercase_first(str)
34
37
  end
35
38
  end
36
39
 
@@ -39,5 +42,11 @@ module Boldsign
39
42
 
40
43
  str[0].upcase + str[1..]
41
44
  end
45
+
46
+ def lowercase_first(str)
47
+ return str if str.empty?
48
+
49
+ str[0].downcase + str[1..]
50
+ end
42
51
  end
43
52
  end
@@ -8,9 +8,21 @@ module Boldsign
8
8
  # Low-level verb methods ({#get}, {#post}, {#put}, {#patch}, {#delete},
9
9
  # {#download}) are also available for hitting any endpoint directly.
10
10
  #
11
- # @example
11
+ # Authenticates with either an API key (`X-API-KEY`) or OAuth 2.0
12
+ # (`Authorization: Bearer`). For OAuth, pass `client_id`/`client_secret` and
13
+ # the client fetches + caches a token via the client credentials grant; or
14
+ # pass a `access_token` you obtained yourself.
15
+ #
16
+ # @example API key
12
17
  # client = Boldsign::Client.new(api_key: ENV["BOLDSIGN_API_KEY"], region: :us)
13
18
  # client.documents.send_document(title: "NDA", signers: [...])
19
+ #
20
+ # @example OAuth client credentials
21
+ # client = Boldsign::Client.new(
22
+ # client_id: ENV["BOLDSIGN_CLIENT_ID"],
23
+ # client_secret: ENV["BOLDSIGN_CLIENT_SECRET"],
24
+ # region: :us
25
+ # )
14
26
  class Client
15
27
  # Default region (US).
16
28
  DEFAULT_BASE_URL = "https://api.boldsign.com".freeze
@@ -18,25 +30,50 @@ module Boldsign
18
30
  # User-Agent header value sent on every request.
19
31
  USER_AGENT = "boldsign-ruby/#{Boldsign::VERSION}".freeze
20
32
 
21
- # @return [String] the API key in use.
33
+ # @return [String, nil] the API key in use, when authenticating via API key.
22
34
  attr_reader :api_key
23
35
 
36
+ # @return [Symbol] the resolved auth mode (`:api_key`, `:oauth`, or `:bearer`).
37
+ attr_reader :auth_mode
38
+
24
39
  # @return [String] the resolved base URL (region or explicit override).
25
40
  attr_reader :base_url
26
41
 
27
42
  # @param api_key [String, nil] BoldSign API key. Falls back to `ENV["BOLDSIGN_API_KEY"]`.
28
- # @param region [Symbol, nil] One of `:us`, `:eu`, `:ca`, `:au`. Ignored if `base_url` is given.
29
- # @param base_url [String, nil] Explicit base URL override.
43
+ # @param client_id [String, nil] OAuth app client ID (enables the client credentials grant).
44
+ # @param client_secret [String, nil] OAuth app client secret (required with `client_id`).
45
+ # @param access_token [String, nil] A pre-obtained OAuth bearer token (used as-is, not refreshed).
46
+ # @param scope [String, nil] OAuth scope to request (defaults to {AccessToken::DEFAULT_SCOPE}).
47
+ # @param token_url [String, nil] Override for the full OAuth token endpoint URL.
48
+ # @param region [Symbol, nil] One of `:us`, `:eu`, `:ca`, `:au`. Ignored if `base_url`/`token_url` is given.
49
+ # @param base_url [String, nil] Explicit API base URL override.
30
50
  # @param adapter [Symbol] Faraday adapter (defaults to `Faraday.default_adapter`).
31
51
  # @param logger [Logger, nil] Optional logger; when present, Faraday's logger middleware is enabled.
32
- # @raise [ConfigurationError] when no API key is available.
33
- def initialize(api_key: nil, region: nil, base_url: nil, adapter: Faraday.default_adapter, logger: nil)
52
+ # @raise [ConfigurationError] when no usable credentials are provided.
53
+ def initialize(api_key: nil, client_id: nil, client_secret: nil, access_token: nil,
54
+ scope: nil, token_url: nil, region: nil, base_url: nil,
55
+ adapter: Faraday.default_adapter, logger: nil)
34
56
  @api_key = api_key || ENV["BOLDSIGN_API_KEY"]
35
- raise ConfigurationError, "Missing BoldSign API key" if @api_key.nil? || @api_key.empty?
36
-
57
+ @static_access_token = access_token
37
58
  @base_url = base_url || Boldsign::REGIONS[region&.to_sym] || DEFAULT_BASE_URL
38
59
  @adapter = adapter
39
60
  @logger = logger
61
+
62
+ if present?(client_id) && present?(client_secret)
63
+ @auth_mode = :oauth
64
+ @access_token = AccessToken.new(
65
+ client_id: client_id, client_secret: client_secret,
66
+ token_url: token_url || default_token_url(region),
67
+ scope: scope || AccessToken::DEFAULT_SCOPE, adapter: adapter
68
+ )
69
+ elsif present?(@static_access_token)
70
+ @auth_mode = :bearer
71
+ elsif present?(@api_key)
72
+ @auth_mode = :api_key
73
+ else
74
+ raise ConfigurationError,
75
+ "Missing BoldSign credentials: provide client_id/client_secret, access_token, or api_key"
76
+ end
40
77
  end
41
78
 
42
79
  # @return [Resources::Brand]
@@ -125,7 +162,7 @@ module Boldsign
125
162
  req.body = body
126
163
  else
127
164
  req.headers["Content-Type"] = "application/json"
128
- req.body = body.is_a?(String) ? body : JSON.generate(CaseConvert.pascalize(body))
165
+ req.body = body.is_a?(String) ? body : JSON.generate(CaseConvert.camelize(body))
129
166
  end
130
167
  end
131
168
  end
@@ -137,7 +174,7 @@ module Boldsign
137
174
  Faraday.new(url: @base_url) do |f|
138
175
  f.request :multipart if multipart
139
176
  f.request :url_encoded
140
- f.headers["X-API-KEY"] = @api_key
177
+ apply_auth(f.headers)
141
178
  f.headers["Accept"] = "application/json"
142
179
  f.headers["User-Agent"] = USER_AGENT
143
180
  f.response :logger, @logger if @logger
@@ -145,6 +182,27 @@ module Boldsign
145
182
  end
146
183
  end
147
184
 
185
+ def apply_auth(headers)
186
+ if @auth_mode == :api_key
187
+ headers["X-API-KEY"] = @api_key
188
+ else
189
+ headers["Authorization"] = "Bearer #{bearer_token}"
190
+ end
191
+ end
192
+
193
+ def bearer_token
194
+ @auth_mode == :oauth ? @access_token.value : @static_access_token
195
+ end
196
+
197
+ def present?(value)
198
+ !value.nil? && !value.empty?
199
+ end
200
+
201
+ def default_token_url(region)
202
+ base = Boldsign::TOKEN_REGIONS[region&.to_sym] || Boldsign::DEFAULT_TOKEN_BASE_URL
203
+ "#{base}#{AccessToken::TOKEN_PATH}"
204
+ end
205
+
148
206
  def parse_body(response)
149
207
  return nil if response.body.nil? || response.body.empty?
150
208
  content_type = response.headers["content-type"].to_s
@@ -73,21 +73,36 @@ module Boldsign
73
73
 
74
74
  private
75
75
 
76
+ # BoldSign's POST /v1/document/send multipart endpoint is fussy about
77
+ # repeated/array-valued parts. Single signers must be sent as one
78
+ # multipart part whose value is the signer JSON OBJECT (not a JSON array
79
+ # containing one object). Files must be sent as a single FilePart value
80
+ # for one upload; Faraday's automatic `Files[]` bracketing breaks the
81
+ # request. For the single-signer, single-file flow (the common case) we
82
+ # unwrap one-element arrays here. Multi-signer / multi-file is not yet
83
+ # supported via this helper and will raise.
76
84
  def multipart_send_body(body, files)
77
85
  parts = body.each_with_object({}) do |(key, value), acc|
78
86
  next if value.nil?
79
87
 
80
- pascal_key = Boldsign::CaseConvert.pascalize_key(key)
81
- acc[pascal_key] = if scalar?(value)
82
- value.to_s
83
- else
84
- JSON.generate(Boldsign::CaseConvert.pascalize(value))
85
- end
88
+ camel_key = Boldsign::CaseConvert.camelize_key(key)
89
+ acc[camel_key] = encode_multipart_value(value)
86
90
  end
87
- parts["Files"] = Array(files).map { |f| file_part(f) }
91
+ files_array = Array(files)
92
+ raise NotImplementedError, "multi-file uploads are not supported yet" if files_array.size > 1
93
+
94
+ parts["Files"] = file_part(files_array.first)
88
95
  parts
89
96
  end
90
97
 
98
+ def encode_multipart_value(value)
99
+ return value.to_s if scalar?(value)
100
+ return JSON.generate(Boldsign::CaseConvert.camelize(value)) unless value.is_a?(Array)
101
+ raise NotImplementedError, "multi-element arrays are not supported in multipart bodies yet" if value.size > 1
102
+
103
+ JSON.generate(Boldsign::CaseConvert.camelize(value.first))
104
+ end
105
+
91
106
  def file_part(file)
92
107
  return file if file.is_a?(Faraday::Multipart::FilePart)
93
108
 
@@ -1,3 +1,3 @@
1
1
  module Boldsign
2
- VERSION = "0.3.0"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/boldsign.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  require "faraday"
2
2
  require "faraday/multipart"
3
3
  require "json"
4
+ require "uri"
4
5
 
5
6
  require_relative "boldsign/version"
6
7
  require_relative "boldsign/error"
7
8
  require_relative "boldsign/case_convert"
9
+ require_relative "boldsign/access_token"
8
10
  require_relative "boldsign/client"
9
11
  require_relative "boldsign/resource"
10
12
  require_relative "boldsign/resources/brand"
@@ -24,10 +26,11 @@ require_relative "boldsign/resources/user"
24
26
  # Configure once at boot and access a shared {Client} via {Boldsign.client}, or
25
27
  # instantiate {Client} directly when you need multiple credentials/regions.
26
28
  #
27
- # @example Configure and use the shared client
29
+ # @example Configure with OAuth and use the shared client
28
30
  # Boldsign.configure do |c|
29
- # c.api_key = ENV["BOLDSIGN_API_KEY"]
30
- # c.region = :us
31
+ # c.client_id = ENV["BOLDSIGN_CLIENT_ID"]
32
+ # c.client_secret = ENV["BOLDSIGN_CLIENT_SECRET"]
33
+ # c.region = :us
31
34
  # end
32
35
  # Boldsign.client.documents.list
33
36
  module Boldsign
@@ -39,10 +42,37 @@ module Boldsign
39
42
  au: "https://api-au.boldsign.com"
40
43
  }.freeze
41
44
 
45
+ # Region → account host map for the OAuth token endpoint. Non-US hosts follow
46
+ # BoldSign's regional naming; override with `token_url:` if a region differs.
47
+ TOKEN_REGIONS = {
48
+ us: "https://account.boldsign.com",
49
+ eu: "https://account-eu.boldsign.com",
50
+ ca: "https://account-ca.boldsign.com",
51
+ au: "https://account-au.boldsign.com"
52
+ }.freeze
53
+
54
+ # Default account host (US) for the OAuth token endpoint.
55
+ DEFAULT_TOKEN_BASE_URL = "https://account.boldsign.com".freeze
56
+
42
57
  class << self
43
58
  # @return [String, nil] API key used by the shared {client}.
44
59
  attr_accessor :api_key
45
60
 
61
+ # @return [String, nil] OAuth app client ID used by the shared {client}.
62
+ attr_accessor :client_id
63
+
64
+ # @return [String, nil] OAuth app client secret used by the shared {client}.
65
+ attr_accessor :client_secret
66
+
67
+ # @return [String, nil] Pre-obtained OAuth bearer token (used as-is, not refreshed).
68
+ attr_accessor :access_token
69
+
70
+ # @return [String, nil] OAuth scope to request (defaults to {AccessToken::DEFAULT_SCOPE}).
71
+ attr_accessor :scope
72
+
73
+ # @return [String, nil] Override for the full OAuth token endpoint URL.
74
+ attr_accessor :token_url
75
+
46
76
  # @return [Symbol, nil] Region key (`:us`, `:eu`, `:ca`, `:au`).
47
77
  attr_accessor :region
48
78
 
@@ -58,7 +88,11 @@ module Boldsign
58
88
 
59
89
  # @return [Client] memoized shared client built from module-level config.
60
90
  def client
61
- @client ||= Client.new(api_key: api_key, region: region, base_url: base_url)
91
+ @client ||= Client.new(
92
+ api_key: api_key, client_id: client_id, client_secret: client_secret,
93
+ access_token: access_token, scope: scope, token_url: token_url,
94
+ region: region, base_url: base_url
95
+ )
62
96
  end
63
97
 
64
98
  # Clears the memoized shared client so the next call to {client} rebuilds it.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boldsign
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Klein
@@ -92,6 +92,7 @@ files:
92
92
  - README.md
93
93
  - boldsign.gemspec
94
94
  - lib/boldsign.rb
95
+ - lib/boldsign/access_token.rb
95
96
  - lib/boldsign/case_convert.rb
96
97
  - lib/boldsign/client.rb
97
98
  - lib/boldsign/error.rb