Skip to content

Commit

Permalink
Retrying block if encountered 401 error
Browse files Browse the repository at this point in the history
  • Loading branch information
zzooeeyy committed Apr 2, 2024
1 parent 41bb033 commit f5f33bf
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 1 deletion.
13 changes: 12 additions & 1 deletion lib/shopify_app/controller_concerns/token_exchange.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,24 @@ def activate_shopify_session
end

if ShopifyApp.configuration.check_session_expiry_date && current_shopify_session.expired?
@current_shopify_session = nil
retrieve_session_from_token_exchange
end

attempts = 0
begin
ShopifyApp::Logger.debug("Activating Shopify session")
ShopifyAPI::Context.activate_session(current_shopify_session)
yield
rescue ShopifyAPI::Errors::HttpResponseError => error
if error.code == 401 && attempts.zero?
ShopifyApp::Logger.debug("Encountered 401 error, exchanging token and retrying with new access token")
attempts += 1
retrieve_session_from_token_exchange
retry
else
ShopifyApp::Logger.debug("Encountered error: #{error.code} - #{error.message}, re-raising")
raise
end
ensure
ShopifyApp::Logger.debug("Deactivating session")
ShopifyAPI::Context.deactivate_session
Expand Down Expand Up @@ -46,6 +56,7 @@ def current_shopify_domain
private

def retrieve_session_from_token_exchange
@current_shopify_session = nil
# TODO: Right now JWT Middleware only updates env['jwt.shopify_domain'] from request headers tokens,
# which won't work for new installs.
# we need to update the middleware to also update the env['jwt.shopify_domain'] from the query params
Expand Down
81 changes: 81 additions & 0 deletions test/shopify_app/controller_concerns/token_exchange_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
require "action_controller"
require "action_controller/base"

class ApiClass
def self.perform; end
end

class TokenExchangeController < ActionController::Base
include ShopifyApp::TokenExchange

Expand All @@ -12,6 +16,11 @@ class TokenExchangeController < ActionController::Base
def index
render(plain: "OK")
end

def make_api_call
ApiClass.perform
render(plain: "OK")
end
end

class TokenExchangeControllerTest < ActionController::TestCase
Expand Down Expand Up @@ -190,14 +199,86 @@ class TokenExchangeControllerTest < ActionController::TestCase
end
end

test "Handles 401 error by exchanging token before retrying with new access token" do
ShopifyApp::SessionRepository.store_shop_session(@offline_session)
ShopifyAPI::Utils::SessionUtils.stubs(:current_session_id).returns(@offline_session_id)

ApiClass.expects(:perform).times(2)
.raises(http_response_401_error)
.then.returns(true)

ShopifyAPI::Auth::TokenExchange.expects(:exchange_token).with(
shop: @shop,
session_token: @session_token,
requested_token_type: ShopifyAPI::Auth::TokenExchange::RequestedTokenType::OFFLINE_ACCESS_TOKEN,
).returns(@offline_session)

with_application_test_routes do
get :make_api_call, params: { shop: @shop }
end
end

test "Only retry once when encountering 401 error and raises the second error" do
ShopifyApp::SessionRepository.store_shop_session(@offline_session)
ShopifyAPI::Utils::SessionUtils.stubs(:current_session_id).returns(@offline_session_id)
ShopifyAPI::Auth::TokenExchange.stubs(:exchange_token).returns(@offline_session)

ApiClass.expects(:perform).times(2).raises(http_response_401_error)

with_application_test_routes do
actual_error = assert_raises(ShopifyAPI::Errors::HttpResponseError) do
get :make_api_call, params: { shop: @shop }
end

assert_equal http_response_401_error.code, actual_error.code
end
end

test "Raises HttpResponseError without retrying if not a 401 error" do
ShopifyApp::SessionRepository.store_shop_session(@offline_session)
ShopifyAPI::Utils::SessionUtils.stubs(:current_session_id).returns(@offline_session_id)
ShopifyAPI::Auth::TokenExchange.stubs(:exchange_token).returns(@offline_session)

ApiClass.expects(:perform).raises(http_response_500_error)

with_application_test_routes do
actual_error = assert_raises(ShopifyAPI::Errors::HttpResponseError) do
get :make_api_call, params: { shop: @shop }
end

assert_equal http_response_500_error.code, actual_error.code
end
end

private

def with_application_test_routes
with_routing do |set|
set.draw do
get "/" => "token_exchange#index"
get "/make_api_call" => "token_exchange#make_api_call"
end
yield
end
end

def http_response_401_error
ShopifyAPI::Errors::HttpResponseError.new(
response: ShopifyAPI::Clients::HttpResponse.new(
code: 401,
headers: {},
body: "Invalid API key or access token (unrecognized login or wrong password)",
),
)
end

def http_response_500_error
ShopifyAPI::Errors::HttpResponseError.new(
response: ShopifyAPI::Clients::HttpResponse.new(
code: 500,
headers: {},
body: "Internal Server Error",
),
)
end
end

0 comments on commit f5f33bf

Please sign in to comment.