From d3a0aa18d0b9dc9083f2bfcdf016b05dfc5eb784 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Wed, 3 Apr 2024 15:05:37 -0600 Subject: [PATCH 01/10] Add new route to patch session token from app bridge next --- .../shopify_app/sessions_controller.rb | 6 +++++- .../shopify_app/layouts/app_bridge.html.erb | 17 +++++++++++++++++ .../sessions/patch_session_token.html.erb | 0 config/routes.rb | 1 + 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 app/views/shopify_app/layouts/app_bridge.html.erb create mode 100644 app/views/shopify_app/sessions/patch_session_token.html.erb diff --git a/app/controllers/shopify_app/sessions_controller.rb b/app/controllers/shopify_app/sessions_controller.rb index 080abdb9a..6a6292713 100644 --- a/app/controllers/shopify_app/sessions_controller.rb +++ b/app/controllers/shopify_app/sessions_controller.rb @@ -7,7 +7,7 @@ class SessionsController < ActionController::Base layout false, only: :new - after_action only: [:new, :create] do |controller| + after_action only: [:new, :create, :patch_session_token] do |controller| controller.response.headers.except!("X-Frame-Options") end @@ -19,6 +19,10 @@ def create authenticate end + def patch_session_token + render(layout: "shopify_app/layouts/app_bridge") + end + def top_level_interaction @url = login_url_with_optional_shop(top_level: true) validate_shop_presence diff --git a/app/views/shopify_app/layouts/app_bridge.html.erb b/app/views/shopify_app/layouts/app_bridge.html.erb new file mode 100644 index 000000000..d9c681735 --- /dev/null +++ b/app/views/shopify_app/layouts/app_bridge.html.erb @@ -0,0 +1,17 @@ + + + + + <%= ShopifyApp.configuration.application_name %> + <%= yield :head %> + + <%= csrf_meta_tags %> + + + + <%= yield %> + + diff --git a/app/views/shopify_app/sessions/patch_session_token.html.erb b/app/views/shopify_app/sessions/patch_session_token.html.erb new file mode 100644 index 000000000..e69de29bb diff --git a/config/routes.rb b/config/routes.rb index 2f22c152d..9ffd04dd9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,6 +8,7 @@ get login_url => :new, :as => :login post login_url => :create, :as => :authenticate get "logout" => :destroy, :as => :logout + get "patch_session_token" => :patch_session_token # Kept to prevent apps relying on these routes from breaking if login_url.gsub(%r{^/}, "") != "login" From ed222c921af5fa3f154591f17ff4f867d0603fed Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Wed, 3 Apr 2024 15:07:42 -0600 Subject: [PATCH 02/10] Introduce token exchange to ensureInstalled concern --- app/controllers/concerns/shopify_app/ensure_installed.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/concerns/shopify_app/ensure_installed.rb b/app/controllers/concerns/shopify_app/ensure_installed.rb index b23ec502f..65c8eca6a 100644 --- a/app/controllers/concerns/shopify_app/ensure_installed.rb +++ b/app/controllers/concerns/shopify_app/ensure_installed.rb @@ -17,7 +17,11 @@ module EnsureInstalled before_action :check_shop_domain - unless ShopifyApp.configuration.use_new_embedded_auth_strategy? + if ShopifyApp.configuration.use_new_embedded_auth_strategy? + include ShopifyApp::TokenExchange + include ShopifyApp::EmbeddedApp + around_action :activate_shopify_session + else # TODO: Add support to use new embedded auth strategy here when invalid # session token can be handled by AppBridge app reload before_action :check_shop_known From ce9e370988349875e3795c463e1c63e172426214 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Tue, 16 Apr 2024 13:57:30 -0600 Subject: [PATCH 03/10] Rescuing invalidJWT token errors and redirecting to bounce page --- .../controller_concerns/token_exchange.rb | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/lib/shopify_app/controller_concerns/token_exchange.rb b/lib/shopify_app/controller_concerns/token_exchange.rb index a3f45938b..99b864007 100644 --- a/lib/shopify_app/controller_concerns/token_exchange.rb +++ b/lib/shopify_app/controller_concerns/token_exchange.rb @@ -5,8 +5,17 @@ module TokenExchange extend ActiveSupport::Concern include ShopifyApp::AdminAPI::WithTokenRefetch + INVALID_SHOPIFY_ID_TOKEN_ERRORS = [ + ShopifyAPI::Errors::CookieNotFoundError, + ShopifyAPI::Errors::InvalidJwtTokenError, + ].freeze + def activate_shopify_session(&block) - retrieve_session_from_token_exchange if current_shopify_session.blank? || should_exchange_expired_token? + begin + retrieve_session_from_token_exchange if current_shopify_session.blank? || should_exchange_expired_token? + rescue *INVALID_SHOPIFY_ID_TOKEN_ERRORS + return respond_to_invalid_shopify_id_token + end begin ShopifyApp::Logger.debug("Activating Shopify session") @@ -47,9 +56,6 @@ def current_shopify_domain def retrieve_session_from_token_exchange @current_shopify_session = nil ShopifyApp::Auth::TokenExchange.perform(shopify_id_token) - # TODO: Rescue JWT validation errors when bounce page is ready - # rescue ShopifyAPI::Errors::InvalidJwtTokenError - # respond_to_invalid_shopify_id_token end def shopify_id_token @@ -61,23 +67,26 @@ def id_token_header end def respond_to_invalid_shopify_id_token - # TODO: Implement this method to handle invalid session tokens + return redirect_to_bounce_page if request.headers["HTTP_AUTHORIZATION"].blank? - # if request.xhr? - # response.set_header("X-Shopify-Retry-Invalid-Session-Request", 1) - # unauthorized_response = { message: :unauthorized } - # render(json: { errors: [unauthorized_response] }, status: :unauthorized) - # else - # patch_session_token_url = "#{ShopifyAPI::Context.host}/patch_session_token" - # patch_session_token_params = request.query_parameters.except(:id_token) + response.set_header("X-Shopify-Retry-Invalid-Session-Request", 1) + unauthorized_response = { message: :unauthorized } + render(json: { errors: [unauthorized_response] }, status: :unauthorized) + end + + def redirect_to_bounce_page + patch_session_token_url = "#{ShopifyApp.configuration.root_url}/patch_session_token" + patch_session_token_params = request.query_parameters.except(:id_token) - # bounce_url = "#{ShopifyAPI::Context.host}#{request.path}?#{patch_session_token_params.to_query}" + bounce_url = "#{request.path}?#{patch_session_token_params.to_query}" - # # App Bridge will trigger a fetch to the URL in shopify-reload, with a new session token in headers - # patch_session_token_params["shopify-reload"] = bounce_url + # App Bridge will trigger a fetch to the URL in shopify-reload, with a new session token in headers + patch_session_token_params["shopify-reload"] = bounce_url - # redirect_to("#{patch_session_token_url}?#{patch_session_token_params.to_query}", allow_other_host: true) - # end + redirect_to( + "#{patch_session_token_url}?#{patch_session_token_params.to_query}", + allow_other_host: true, + ) end def online_token_configured? From 231b1a7af71d5db959d35c9e24c9860f19fbf908 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Tue, 16 Apr 2024 14:01:43 -0600 Subject: [PATCH 04/10] Rename session token to shopify_id token --- app/controllers/shopify_app/sessions_controller.rb | 4 ++-- ..._token.html.erb => patch_shopify_id_token.html.erb} | 0 config/routes.rb | 2 +- lib/shopify_app/controller_concerns/token_exchange.rb | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) rename app/views/shopify_app/sessions/{patch_session_token.html.erb => patch_shopify_id_token.html.erb} (100%) diff --git a/app/controllers/shopify_app/sessions_controller.rb b/app/controllers/shopify_app/sessions_controller.rb index 6a6292713..2e5c977f6 100644 --- a/app/controllers/shopify_app/sessions_controller.rb +++ b/app/controllers/shopify_app/sessions_controller.rb @@ -7,7 +7,7 @@ class SessionsController < ActionController::Base layout false, only: :new - after_action only: [:new, :create, :patch_session_token] do |controller| + after_action only: [:new, :create, :patch_shopify_id_token] do |controller| controller.response.headers.except!("X-Frame-Options") end @@ -19,7 +19,7 @@ def create authenticate end - def patch_session_token + def patch_shopify_id_token render(layout: "shopify_app/layouts/app_bridge") end diff --git a/app/views/shopify_app/sessions/patch_session_token.html.erb b/app/views/shopify_app/sessions/patch_shopify_id_token.html.erb similarity index 100% rename from app/views/shopify_app/sessions/patch_session_token.html.erb rename to app/views/shopify_app/sessions/patch_shopify_id_token.html.erb diff --git a/config/routes.rb b/config/routes.rb index 9ffd04dd9..d3e755b36 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,7 +8,7 @@ get login_url => :new, :as => :login post login_url => :create, :as => :authenticate get "logout" => :destroy, :as => :logout - get "patch_session_token" => :patch_session_token + get "patch_shopify_id_token" => :patch_shopify_id_token # Kept to prevent apps relying on these routes from breaking if login_url.gsub(%r{^/}, "") != "login" diff --git a/lib/shopify_app/controller_concerns/token_exchange.rb b/lib/shopify_app/controller_concerns/token_exchange.rb index 99b864007..02f0b430b 100644 --- a/lib/shopify_app/controller_concerns/token_exchange.rb +++ b/lib/shopify_app/controller_concerns/token_exchange.rb @@ -75,16 +75,16 @@ def respond_to_invalid_shopify_id_token end def redirect_to_bounce_page - patch_session_token_url = "#{ShopifyApp.configuration.root_url}/patch_session_token" - patch_session_token_params = request.query_parameters.except(:id_token) + patch_shopify_id_token_url = "#{ShopifyApp.configuration.root_url}/patch_shopify_id_token" + patch_shopify_id_token_params = request.query_parameters.except(:id_token) - bounce_url = "#{request.path}?#{patch_session_token_params.to_query}" + bounce_url = "#{request.path}?#{patch_shopify_id_token_params.to_query}" # App Bridge will trigger a fetch to the URL in shopify-reload, with a new session token in headers - patch_session_token_params["shopify-reload"] = bounce_url + patch_shopify_id_token_params["shopify-reload"] = bounce_url redirect_to( - "#{patch_session_token_url}?#{patch_session_token_params.to_query}", + "#{patch_shopify_id_token_url}?#{patch_shopify_id_token_params.to_query}", allow_other_host: true, ) end From 3bcaff09c889e0874f6480dc6d46da37af32b41a Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Tue, 16 Apr 2024 16:52:47 -0600 Subject: [PATCH 05/10] Add tests in token exchange concern for responding to invalid shopify id --- .../controller_concerns/token_exchange.rb | 5 ++- .../token_exchange_test.rb | 40 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/shopify_app/controller_concerns/token_exchange.rb b/lib/shopify_app/controller_concerns/token_exchange.rb index 02f0b430b..f611864fd 100644 --- a/lib/shopify_app/controller_concerns/token_exchange.rb +++ b/lib/shopify_app/controller_concerns/token_exchange.rb @@ -13,7 +13,8 @@ module TokenExchange def activate_shopify_session(&block) begin retrieve_session_from_token_exchange if current_shopify_session.blank? || should_exchange_expired_token? - rescue *INVALID_SHOPIFY_ID_TOKEN_ERRORS + rescue *INVALID_SHOPIFY_ID_TOKEN_ERRORS => e + ShopifyApp::Logger.debug("Responding to invalid Shopify ID token: #{e.message}") return respond_to_invalid_shopify_id_token end @@ -69,12 +70,14 @@ def id_token_header def respond_to_invalid_shopify_id_token return redirect_to_bounce_page if request.headers["HTTP_AUTHORIZATION"].blank? + ShopifyApp::Logger.debug("Responding to invalid Shopify ID token with unauthorized response") response.set_header("X-Shopify-Retry-Invalid-Session-Request", 1) unauthorized_response = { message: :unauthorized } render(json: { errors: [unauthorized_response] }, status: :unauthorized) end def redirect_to_bounce_page + ShopifyApp::Logger.debug("Redirecting to bounce page for patching Shopify ID token") patch_shopify_id_token_url = "#{ShopifyApp.configuration.root_url}/patch_shopify_id_token" patch_shopify_id_token_params = request.query_parameters.except(:id_token) diff --git a/test/shopify_app/controller_concerns/token_exchange_test.rb b/test/shopify_app/controller_concerns/token_exchange_test.rb index 77510d000..1b8be29dc 100644 --- a/test/shopify_app/controller_concerns/token_exchange_test.rb +++ b/test/shopify_app/controller_concerns/token_exchange_test.rb @@ -3,6 +3,7 @@ require "test_helper" require "action_controller" require "action_controller/base" +require "json" class ApiClass def self.perform; end @@ -17,6 +18,10 @@ def index render(plain: "OK") end + def reloaded_path + render(plain: "OK") + end + def make_api_call ApiClass.perform render(plain: "OK") @@ -178,6 +183,40 @@ class TokenExchangeControllerTest < ActionController::TestCase end end + [ + ShopifyAPI::Errors::InvalidJwtTokenError, + ShopifyAPI::Errors::CookieNotFoundError, + ].each do |invalid_shopify_id_token_error| + test "Redirects to bounce page if Shopify ID token is invalid with #{invalid_shopify_id_token_error}" do + ShopifyApp.configuration.root_url = "/my-root" + ShopifyAPI::Utils::SessionUtils.stubs(:current_session_id).raises(invalid_shopify_id_token_error) + request.headers["HTTP_AUTHORIZATION"] = nil + + params = { shop: @shop, my_param: "for-keeps", id_token: "dont-include-this-id-token" } + expected_redirect_url = "/my-root/patch_shopify_id_token?my_param=for-keeps&shop=my-shop.myshopify.com" + expected_redirect_url += "&shopify-reload=%2Freloaded_path%3Fmy_param%3Dfor-keeps%26shop%3Dmy-shop.myshopify.com" + + with_application_test_routes do + get :reloaded_path, params: params + assert_redirected_to expected_redirect_url + end + end + + test "Responds with unauthorized if Shopify Id token is invalid with #{invalid_shopify_id_token_error} and authorization header exists" do + ShopifyAPI::Utils::SessionUtils.stubs(:current_session_id).raises(invalid_shopify_id_token_error) + request.headers["HTTP_AUTHORIZATION"] = @id_token_in_header + expected_response = { errors: [{ message: :unauthorized }] } + + with_application_test_routes do + get :make_api_call, params: { shop: @shop } + + assert_response :unauthorized + assert_equal expected_response.to_json, response.body + assert_equal 1, response.headers["X-Shopify-Retry-Invalid-Session-Request"] + end + end + end + private def with_application_test_routes @@ -185,6 +224,7 @@ def with_application_test_routes set.draw do get "/" => "token_exchange#index" get "/make_api_call" => "token_exchange#make_api_call" + get "/reloaded_path" => "token_exchange#reloaded_path" end yield end From a2339741fda6d7749bfb5c9d855ffdfc14b794e3 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Tue, 16 Apr 2024 17:08:32 -0600 Subject: [PATCH 06/10] Add test for patching shopify id token bounce page --- test/controllers/sessions_controller_test.rb | 5 +++++ test/routes/sessions_routes_test.rb | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index 84dc518ff..649a15885 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb @@ -402,6 +402,11 @@ class SessionsControllerTest < ActionController::TestCase end end + test "#patch_shopify_id_token renders the app bridge layout" do + get :patch_shopify_id_token, params: { shop: "my-shop" } + assert_template "shopify_app/layouts/app_bridge" + end + private def assert_redirected_to_top_level(shop_domain, expected_url = nil) diff --git a/test/routes/sessions_routes_test.rb b/test/routes/sessions_routes_test.rb index 414535b67..f2db9093b 100644 --- a/test/routes/sessions_routes_test.rb +++ b/test/routes/sessions_routes_test.rb @@ -25,6 +25,10 @@ class SessionsRoutesTest < ActionController::TestCase assert_routing "/logout", { controller: "shopify_app/sessions", action: "destroy" } end + test "patch_shopify_id_token routes to sessions#patch_shopify_id_token" do + assert_routing "/patch_shopify_id_token", { controller: "shopify_app/sessions", action: "patch_shopify_id_token" } + end + test "login route doesn't change with custom root URL because it is in an engine" do ShopifyApp.configuration.root_url = "/new-root" Rails.application.reload_routes! From b65aac43ae239f7532bea6be4da7510c72a1b930 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Tue, 16 Apr 2024 17:25:44 -0600 Subject: [PATCH 07/10] Fix fragile tests? --- test/dummy/config/initializers/shopify_app.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/dummy/config/initializers/shopify_app.rb b/test/dummy/config/initializers/shopify_app.rb index e78ca8437..e935e2568 100644 --- a/test/dummy/config/initializers/shopify_app.rb +++ b/test/dummy/config/initializers/shopify_app.rb @@ -17,8 +17,13 @@ def self.call config.embedded_redirect_url = nil config.shop_session_repository = ShopifyApp::InMemorySessionStore + config.user_session_repository = nil config.after_authenticate_job = false config.reauth_on_access_scope_changes = true + config.root_url = "/" + config.wip_new_embedded_auth_strategy = false + config.check_session_expiry_date = false + config.custom_post_authenticate_tasks = nil end setup_context From f804df3cbc373da4435a52e29c697a9d6d582535 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Tue, 16 Apr 2024 17:39:49 -0600 Subject: [PATCH 08/10] Remove todo --- app/controllers/concerns/shopify_app/ensure_installed.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controllers/concerns/shopify_app/ensure_installed.rb b/app/controllers/concerns/shopify_app/ensure_installed.rb index 65c8eca6a..ba06427bf 100644 --- a/app/controllers/concerns/shopify_app/ensure_installed.rb +++ b/app/controllers/concerns/shopify_app/ensure_installed.rb @@ -22,8 +22,6 @@ module EnsureInstalled include ShopifyApp::EmbeddedApp around_action :activate_shopify_session else - # TODO: Add support to use new embedded auth strategy here when invalid - # session token can be handled by AppBridge app reload before_action :check_shop_known before_action :validate_non_embedded_session end From d925d9090590ff28c88a7475c118100efea4563d Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Thu, 18 Apr 2024 10:00:24 -0600 Subject: [PATCH 09/10] Format URL string in token exchange test --- test/shopify_app/controller_concerns/token_exchange_test.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/shopify_app/controller_concerns/token_exchange_test.rb b/test/shopify_app/controller_concerns/token_exchange_test.rb index 1b8be29dc..e8b40d18d 100644 --- a/test/shopify_app/controller_concerns/token_exchange_test.rb +++ b/test/shopify_app/controller_concerns/token_exchange_test.rb @@ -193,8 +193,10 @@ class TokenExchangeControllerTest < ActionController::TestCase request.headers["HTTP_AUTHORIZATION"] = nil params = { shop: @shop, my_param: "for-keeps", id_token: "dont-include-this-id-token" } - expected_redirect_url = "/my-root/patch_shopify_id_token?my_param=for-keeps&shop=my-shop.myshopify.com" - expected_redirect_url += "&shopify-reload=%2Freloaded_path%3Fmy_param%3Dfor-keeps%26shop%3Dmy-shop.myshopify.com" + reload_url = CGI.escape("/reloaded_path?my_param=for-keeps&shop=#{@shop}") + expected_redirect_url = "/my-root/patch_shopify_id_token"\ + "?my_param=for-keeps&shop=#{@shop}"\ + "&shopify-reload=#{reload_url}" with_application_test_routes do get :reloaded_path, params: params From 97122723a596216b086f517f33a790ccfe223b24 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Fri, 19 Apr 2024 12:05:33 -0600 Subject: [PATCH 10/10] handles invalid session token error in all of TokenExchange::activate_shopify_session --- .../controller_concerns/token_exchange.rb | 26 ++--- .../token_exchange_test.rb | 102 ++++++++++++++++++ 2 files changed, 113 insertions(+), 15 deletions(-) diff --git a/lib/shopify_app/controller_concerns/token_exchange.rb b/lib/shopify_app/controller_concerns/token_exchange.rb index f611864fd..cf6b28fb3 100644 --- a/lib/shopify_app/controller_concerns/token_exchange.rb +++ b/lib/shopify_app/controller_concerns/token_exchange.rb @@ -11,21 +11,17 @@ module TokenExchange ].freeze def activate_shopify_session(&block) - begin - retrieve_session_from_token_exchange if current_shopify_session.blank? || should_exchange_expired_token? - rescue *INVALID_SHOPIFY_ID_TOKEN_ERRORS => e - ShopifyApp::Logger.debug("Responding to invalid Shopify ID token: #{e.message}") - return respond_to_invalid_shopify_id_token - end - - begin - ShopifyApp::Logger.debug("Activating Shopify session") - ShopifyAPI::Context.activate_session(current_shopify_session) - with_token_refetch(current_shopify_session, shopify_id_token, &block) - ensure - ShopifyApp::Logger.debug("Deactivating session") - ShopifyAPI::Context.deactivate_session - end + retrieve_session_from_token_exchange if current_shopify_session.blank? || should_exchange_expired_token? + + ShopifyApp::Logger.debug("Activating Shopify session") + ShopifyAPI::Context.activate_session(current_shopify_session) + with_token_refetch(current_shopify_session, shopify_id_token, &block) + rescue *INVALID_SHOPIFY_ID_TOKEN_ERRORS => e + ShopifyApp::Logger.debug("Responding to invalid Shopify ID token: #{e.message}") + respond_to_invalid_shopify_id_token unless performed? + ensure + ShopifyApp::Logger.debug("Deactivating session") + ShopifyAPI::Context.deactivate_session end def should_exchange_expired_token? diff --git a/test/shopify_app/controller_concerns/token_exchange_test.rb b/test/shopify_app/controller_concerns/token_exchange_test.rb index e8b40d18d..a603bd7f5 100644 --- a/test/shopify_app/controller_concerns/token_exchange_test.rb +++ b/test/shopify_app/controller_concerns/token_exchange_test.rb @@ -26,6 +26,11 @@ def make_api_call ApiClass.perform render(plain: "OK") end + + def ensure_render + ensure + render(plain: "OK") + end end class MockPostAuthenticateTasks @@ -217,6 +222,102 @@ class TokenExchangeControllerTest < ActionController::TestCase assert_equal 1, response.headers["X-Shopify-Retry-Invalid-Session-Request"] end end + + test "Redirects to bounce page from Token Exchange if Shopify ID token is invalid with #{invalid_shopify_id_token_error}" do + ShopifyApp.configuration.root_url = "/my-root" + ShopifyAPI::Utils::SessionUtils.stubs(:current_session_id).returns(nil, @offline_session_id) + ShopifyApp::Auth::TokenExchange.expects(:perform).raises(invalid_shopify_id_token_error) + request.headers["HTTP_AUTHORIZATION"] = nil + + params = { shop: @shop, my_param: "for-keeps", id_token: "dont-include-this-id-token" } + reload_url = CGI.escape("/reloaded_path?my_param=for-keeps&shop=#{@shop}") + expected_redirect_url = "/my-root/patch_shopify_id_token"\ + "?my_param=for-keeps&shop=#{@shop}"\ + "&shopify-reload=#{reload_url}" + + with_application_test_routes do + get :reloaded_path, params: params + assert_redirected_to expected_redirect_url + end + end + + test "Responds with unauthorized from Token Exchange if Shopify Id token is invalid with #{invalid_shopify_id_token_error} and authorization header exists" do + ShopifyAPI::Utils::SessionUtils.stubs(:current_session_id).returns(nil, @offline_session_id) + ShopifyApp::Auth::TokenExchange.expects(:perform).raises(invalid_shopify_id_token_error) + + expected_response = { errors: [{ message: :unauthorized }] } + + with_application_test_routes do + get :make_api_call, params: { shop: @shop } + + assert_response :unauthorized + assert_equal expected_response.to_json, response.body + assert_equal 1, response.headers["X-Shopify-Retry-Invalid-Session-Request"] + end + end + + test "Redirects to bounce page from with_token_refetch if Shopify ID token is invalid with #{invalid_shopify_id_token_error}" do + ShopifyApp.configuration.root_url = "/my-root" + ShopifyAPI::Utils::SessionUtils.stubs(:current_session_id).returns(@offline_session_id) + ShopifyApp::Auth::TokenExchange.stubs(:perform) + request.headers["HTTP_AUTHORIZATION"] = nil + + @controller.expects(:with_token_refetch).raises(invalid_shopify_id_token_error) + + params = { shop: @shop, my_param: "for-keeps", id_token: "dont-include-this-id-token" } + reload_url = CGI.escape("/reloaded_path?my_param=for-keeps&shop=#{@shop}") + expected_redirect_url = "/my-root/patch_shopify_id_token"\ + "?my_param=for-keeps&shop=#{@shop}"\ + "&shopify-reload=#{reload_url}" + + with_application_test_routes do + get :reloaded_path, params: params + assert_redirected_to expected_redirect_url + end + end + + test "Responds with unauthorized from with_token_refetch if Shopify Id token is invalid with #{invalid_shopify_id_token_error} and authorization header exists" do + ShopifyAPI::Utils::SessionUtils.stubs(:current_session_id).returns(@offline_session_id) + ShopifyApp::Auth::TokenExchange.stubs(:perform) + expected_response = { errors: [{ message: :unauthorized }] } + + @controller.expects(:with_token_refetch).raises(invalid_shopify_id_token_error) + + with_application_test_routes do + get :make_api_call, params: { shop: @shop } + + assert_response :unauthorized + assert_equal expected_response.to_json, response.body + assert_equal 1, response.headers["X-Shopify-Retry-Invalid-Session-Request"] + end + end + + test "Does not redirect to bounce page if redirect/response has been performed already - #{invalid_shopify_id_token_error}" do + ShopifyAPI::Utils::SessionUtils.stubs(:current_session_id).returns(@offline_session_id) + ShopifyApp::Auth::TokenExchange.stubs(:perform) + request.headers["HTTP_AUTHORIZATION"] = nil + + @controller.expects(:with_token_refetch).raises(invalid_shopify_id_token_error) + @controller.stubs(:performed?).returns(true) + + with_application_test_routes do + get :ensure_render, params: { shop: @shop } + assert_response :ok + end + end + + test "Does not respond 401 if redirect/response has been performed already - #{invalid_shopify_id_token_error}" do + ShopifyAPI::Utils::SessionUtils.stubs(:current_session_id).returns(@offline_session_id) + ShopifyApp::Auth::TokenExchange.stubs(:perform) + + @controller.expects(:with_token_refetch).raises(invalid_shopify_id_token_error) + @controller.stubs(:performed?).returns(true) + + with_application_test_routes do + get :ensure_render, params: { shop: @shop } + assert_response :ok + end + end end private @@ -227,6 +328,7 @@ def with_application_test_routes get "/" => "token_exchange#index" get "/make_api_call" => "token_exchange#make_api_call" get "/reloaded_path" => "token_exchange#reloaded_path" + get "/ensure_render" => "token_exchange#ensure_render" end yield end