Skip to content

Commit

Permalink
Merge pull request #82 from sul-dlss/less-confusing-token-refresh
Browse files Browse the repository at this point in the history
Less confusing token refresh (maybe?)
  • Loading branch information
jmartin-sul authored Sep 18, 2023
2 parents 7e46594 + 1e1eda0 commit 15ae774
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 20 deletions.
38 changes: 32 additions & 6 deletions lib/folio_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ class << self
def configure(url:, login_params:, okapi_headers:, timeout: default_timeout)
instance.config = OpenStruct.new(
# For the initial token, use a dummy value to avoid hitting any APIs
# during configuration, allowing the `TokenWrapper` to handle auto-magic
# token refreshing. Why not immediately get a valid token? Our apps
# during configuration, allowing `with_token_refresh_when_unauthorized` to handle
# auto-magic token refreshing. Why not immediately get a valid token? Our apps
# commonly invoke client `.configure` methods in the initializer in all
# application environments, even those that are never expected to
# connect to production APIs, such as local development machines.
#
# NOTE: `nil` and blank string cannot be used as dummy values here as
# they lead to a malformed request to be sent, which triggers an
# exception not rescued by `TokenWrapper`
# exception not rescued by `with_token_refresh_when_unauthorized`
token: "a temporary dummy token to avoid hitting the API before it is needed",
url: url,
login_params: login_params,
Expand All @@ -83,7 +83,7 @@ def configure(url:, login_params:, okapi_headers:, timeout: default_timeout)
# @param path [String] the path to the Folio API request
# @param params [Hash] params to get to the API
def get(path, params = {})
response = TokenWrapper.refresh(config, connection) do
response = with_token_refresh_when_unauthorized do
connection.get(path, params, {"x-okapi-token": config.token})
end

Expand All @@ -100,7 +100,7 @@ def get(path, params = {})
# @param body [Object] body to post to the API as JSON
def post(path, body = nil, content_type: "application/json")
req_body = (content_type == "application/json") ? body&.to_json : body
response = TokenWrapper.refresh(config, connection) do
response = with_token_refresh_when_unauthorized do
req_headers = {
"x-okapi-token": config.token,
"content-type": content_type
Expand All @@ -121,7 +121,7 @@ def post(path, body = nil, content_type: "application/json")
# @param body [Object] body to put to the API as JSON
def put(path, body = nil, content_type: "application/json")
req_body = (content_type == "application/json") ? body&.to_json : body
response = TokenWrapper.refresh(config, connection) do
response = with_token_refresh_when_unauthorized do
req_headers = {
"x-okapi-token": config.token,
"content-type": content_type
Expand Down Expand Up @@ -227,4 +227,30 @@ def interface_details(...)
def default_timeout
120
end

private

# Wraps API operations to request new access token if expired.
# @yieldreturn response [Faraday::Response] the response to inspect
#
# @note You likely want to make sure you're wrapping a _single_ HTTP request in this
# method, because 1) all calls in the block will be retried from the top if there's
# an authN failure detected, and 2) only the response returned by the block will be
# inspected for authN failure.
# Related: consider that the client instance and its token will live across many
# invocations of the FolioClient methods once the client is configured by a consuming application,
# since this class is a Singleton. Thus, a token may expire between any two calls (i.e. it
# isn't necessary for a set of operations to collectively take longer than the token lifetime for
# expiry to fall in the middle of that related set of HTTP calls).
def with_token_refresh_when_unauthorized
response = yield

# if unauthorized, token has likely expired. try to get a new token and then retry the same request(s).
if response.status == 401
config.token = Authenticator.token(config.login_params, connection)
response = yield
end

response
end
end
13 changes: 0 additions & 13 deletions lib/folio_client/token_wrapper.rb

This file was deleted.

3 changes: 2 additions & 1 deletion spec/folio_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,8 @@
end
end

# Tests the TokenWrapper that requests a new token, with a method that might first encounter the error
# Tests that we request a new token and then retry the same HTTP call, if the HTTP call
# returns an unauthorized error
context "when token is expired" do
let(:inventory) { instance_double(FolioClient::Inventory, fetch_hrid: nil) }
let(:hrid) { "in56789" }
Expand Down

0 comments on commit 15ae774

Please sign in to comment.