Skip to content

Commit

Permalink
DACCESS-261: Merge changes for new authentication token handling (#15)
Browse files Browse the repository at this point in the history
* Add rspec

* Add vcr and webmock

* Add dotenv

* Add tests for authenticate

* Filter token and tenant out of VCR cassettes

* Add env example and ruby-version

* Add .env to gitignore

* Add rudimentary tests for request_item

* Update release notes & Gemfile.lock

* changes to support the spelling change in the requests API 'fulfillment'
update the authenticate method to use the new API endpoint

* Update cassettes and data obfuscation

* Comments

* return the token expiration in the authenticate method

* Update release notes

* DACCESS-207: Update request_item for Poppy (#13)

* Update request_item for Poppy (DACCESS-207)

* Update README a bit finally after all these years

* Revert version to 2.0.1

* Re-bump version to 3.0

* Update release notes

---------

Co-authored-by: Sarah Chintomby <[email protected]>
  • Loading branch information
Baroquem and sarah-cul authored Aug 12, 2024
1 parent bc7098b commit c34dab4
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 42 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The wrapped methods include:

| method | FOLIO API endpoint |
| ------ | ----- |
| authenticate | /authn/login |
| authenticate | /authn/login-with-expiry |
| patron_record | /users |
| patron_account | /patron/account |
| renew_item | /patron/account |
Expand Down
30 changes: 23 additions & 7 deletions lib/cul/folio/edge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
module CUL
module FOLIO
module Edge
class Error < StandardError; end
class Error < StandardError; end

##
# Connects to an Okapi instance and uses the +/authn/login+ endpoint
# to authenticate the user.
# Connects to an Okapi instance and uses the +/authn/login-with-expiry+ endpoint
# to authenticate the user. This version of the method is intended for use with
# FOLIO's new Refresh Token Rotation approach to authentication, which is detailed
# at https://folio-org.atlassian.net/wiki/spaces/FOLIJET/pages/1396980/Refresh+Token+Rotation+RTR.
#
# Note that the return value ignores the refresh token that FOLIO provides. Since this library
# is intended for short-lived interactions with FOLIO, the refresh token is not needed. The access
# token should have a sufficient TTL for the duration of the interaction.
#
# Params:
# +okapi+:: URL of an okapi instance (e.g., "https://folio-snapshot-okapi.dev.folio.org")
Expand All @@ -19,12 +25,13 @@ class Error < StandardError; end
#
# Return:
# A hash containing:
# +:token+:: An Okapi X-Okapi-Token string, or nil
# +:token+:: An Okapi Access Token, or nil
# +:token_exp+:: The expiration date of the token, or nil
# +:code+:: An HTTP response code
# +:error+:: An error message, or nil
##
def self.authenticate(okapi, tenant, username, password)
url = "#{okapi}/authn/login"
url = "#{okapi}/authn/login-with-expiry"
headers = {
'X-Okapi-Tenant' => tenant,
:accept => 'application/json',
Expand All @@ -42,15 +49,24 @@ def self.authenticate(okapi, tenant, username, password)
}

begin
# A successful login will return a 201 with a Set-Cookie header that includes both the Access Token
# and the Refresh Token. We're only interested in the Access Token, so we'll parse that out from Set-Cookie.
response = RestClient.post(url, body, headers)
return_value[:token] = response.headers[:x_okapi_token]
cookies = response.headers[:set_cookie]
cookies.each do |cookie|
if cookie.start_with?('folioAccessToken=')
return_value[:token] = cookie.match(/folioAccessToken=(.*?);/)[1]
return_value[:token_exp] = JSON.parse(response.body)['accessTokenExpiration']
break
end
end
return_value[:code] = response.code
rescue RestClient::ExceptionWithResponse => err
return_value[:code] = err.response.code
return_value[:error] = err.response.body
end

return return_value
return_value
end

##
Expand Down
2 changes: 1 addition & 1 deletion lib/cul/folio/edge/version.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Cul
module Folio
module Edge
VERSION = "3.0"
VERSION = "3.1"
end
end
end
4 changes: 4 additions & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release Notes - cul-folio-edge

## [3.1] - 2024-08-12
### Changed
- Updated the `authenticate` method to use the new token rotation/refresh system implemented in Quesnalia, Ransoms, and higher (DACCESS-261)

## 3.0
- Update the `request_item` method for FOLIO Poppy change to spelling of `fulfillmentPreference` (DACCESS-207)
- Add basic test setup using RSpec, VCR, and initial tests (cf. DACCESS-97)
Expand Down
2 changes: 2 additions & 0 deletions spec/authenticate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
VCR.use_cassette('authentication_successful') do
response = CUL::FOLIO::Edge.authenticate(okapi, tenant, ENV['OKAPI_USER'], ENV['OKAPI_PW'])
expect(response[:token]).to_not be_nil
expect { DateTime.parse(response[:token_exp]) }.not_to raise_error(TypeError)
expect(response[:code]).to be(201)
expect(response[:error]).to be_nil
end
Expand All @@ -18,6 +19,7 @@
VCR.use_cassette('authentication_unsuccessful') do
response = CUL::FOLIO::Edge.authenticate(okapi, tenant, 'George', 'letmein')
expect(response[:token]).to be_nil
expect { DateTime.parse(response[:token_exp]) }.to raise_error(TypeError)
expect(response[:code]).to be(422)
expect(response[:error]).to_not be_nil
end
Expand Down
21 changes: 9 additions & 12 deletions spec/cassettes/authentication_successful.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions spec/cassettes/authentication_unsuccessful.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 9 additions & 7 deletions spec/cassettes/request_item_error.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions spec/cassettes/request_item_successful.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
expectations.on_potential_false_positives = :nothing
end

# rspec-mocks config goes here. You can use an alternate test double
Expand Down Expand Up @@ -142,6 +143,7 @@
refreshToken = interaction.response.headers['Refreshtoken']&.first
interaction.response.body.gsub!(token, '<TOKEN>') if token
interaction.response.body.gsub!(refreshToken, '<TOKEN>') if refreshToken
interaction.response.headers['Set-Cookie'] = 'folioAccessToken=<TOKEN>; folioRefreshToken=<TOKEN2>'
interaction.response.headers['X-Okapi-Token'] = '<TOKEN>'
interaction.response.headers['Refreshtoken'] = '<TOKEN>'
end
Expand Down

0 comments on commit c34dab4

Please sign in to comment.