shopify_app 7.4.0 → 8.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/.babelrc +5 -0
  3. data/.github/CODEOWNERS +1 -0
  4. data/.github/probots.yml +2 -0
  5. data/.gitignore +5 -0
  6. data/.nvmrc +1 -0
  7. data/.rubocop.yml +10 -0
  8. data/.ruby-version +1 -0
  9. data/.travis.yml +26 -3
  10. data/CHANGELOG.md +95 -0
  11. data/Gemfile +2 -0
  12. data/README.md +113 -56
  13. data/app/assets/images/storage_access.svg +2 -0
  14. data/app/assets/javascripts/shopify_app/enable_cookies.js +3 -0
  15. data/app/assets/javascripts/shopify_app/itp_helper.js +40 -0
  16. data/app/assets/javascripts/shopify_app/partition_cookies.js +7 -0
  17. data/app/assets/javascripts/shopify_app/redirect.js +33 -0
  18. data/app/assets/javascripts/shopify_app/request_storage_access.js +3 -0
  19. data/app/assets/javascripts/shopify_app/storage_access.js +121 -0
  20. data/app/assets/javascripts/shopify_app/storage_access_redirect.js +17 -0
  21. data/app/assets/javascripts/shopify_app/top_level.js +2 -0
  22. data/app/assets/javascripts/shopify_app/top_level_interaction.js +11 -0
  23. data/app/controllers/shopify_app/authenticated_controller.rb +3 -4
  24. data/{lib/shopify_app/sessions_concern.rb → app/controllers/shopify_app/callback_controller.rb} +27 -38
  25. data/app/controllers/shopify_app/sessions_controller.rb +120 -2
  26. data/app/controllers/shopify_app/webhooks_controller.rb +11 -3
  27. data/app/views/shopify_app/partials/_button_styles.html.erb +104 -0
  28. data/app/views/shopify_app/partials/_card_styles.html.erb +33 -0
  29. data/app/views/shopify_app/partials/_empty_state_styles.html.erb +129 -0
  30. data/app/views/shopify_app/partials/_layout_styles.html.erb +167 -0
  31. data/app/views/shopify_app/partials/_typography_styles.html.erb +35 -0
  32. data/app/views/shopify_app/sessions/enable_cookies.html.erb +59 -0
  33. data/app/views/shopify_app/sessions/new.html.erb +88 -60
  34. data/app/views/shopify_app/sessions/request_storage_access.html.erb +67 -0
  35. data/app/views/shopify_app/sessions/top_level_interaction.html.erb +63 -0
  36. data/app/views/shopify_app/shared/redirect.html.erb +22 -0
  37. data/config/locales/de.yml +21 -2
  38. data/config/locales/en.yml +12 -0
  39. data/config/locales/es.yml +21 -2
  40. data/config/locales/fr.yml +22 -2
  41. data/config/locales/it.yml +22 -0
  42. data/config/locales/ja.yml +16 -2
  43. data/config/locales/nl.yml +21 -0
  44. data/config/locales/pt-BR.yml +22 -0
  45. data/config/locales/zh-CN.yml +16 -0
  46. data/config/locales/zh-TW.yml +17 -0
  47. data/config/routes.rb +11 -1
  48. data/docs/Quickstart.md +26 -23
  49. data/docs/Releasing.md +1 -0
  50. data/karma.conf.js +43 -0
  51. data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +3 -1
  52. data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +1 -0
  53. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +14 -0
  54. data/lib/generators/shopify_app/home_controller/templates/shopify_app_ready_script.html.erb +1 -5
  55. data/lib/generators/shopify_app/install/install_generator.rb +3 -13
  56. data/lib/generators/shopify_app/install/templates/_flash_messages.html.erb +13 -9
  57. data/lib/generators/shopify_app/install/templates/shopify_app.rb +4 -1
  58. data/lib/generators/shopify_app/install/templates/shopify_provider.rb +19 -4
  59. data/lib/generators/shopify_app/rotate_shopify_token_job/rotate_shopify_token_job_generator.rb +16 -0
  60. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +17 -0
  61. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +42 -0
  62. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +3 -3
  63. data/lib/generators/shopify_app/shop_model/templates/shop.rb +0 -1
  64. data/lib/shopify_app.rb +21 -17
  65. data/lib/shopify_app/configuration.rb +26 -8
  66. data/lib/shopify_app/{app_proxy_verification.rb → controller_concerns/app_proxy_verification.rb} +1 -1
  67. data/lib/shopify_app/controller_concerns/embedded_app.rb +19 -0
  68. data/lib/shopify_app/controller_concerns/itp.rb +45 -0
  69. data/lib/shopify_app/{localization.rb → controller_concerns/localization.rb} +6 -0
  70. data/lib/shopify_app/controller_concerns/login_protection.rb +135 -0
  71. data/lib/shopify_app/{webhook_verification.rb → controller_concerns/webhook_verification.rb} +10 -6
  72. data/lib/shopify_app/engine.rb +10 -0
  73. data/lib/shopify_app/{scripttags_manager_job.rb → jobs/scripttags_manager_job.rb} +0 -0
  74. data/lib/shopify_app/{webhooks_manager_job.rb → jobs/webhooks_manager_job.rb} +0 -0
  75. data/lib/shopify_app/{scripttags_manager.rb → managers/scripttags_manager.rb} +0 -0
  76. data/lib/shopify_app/{webhooks_manager.rb → managers/webhooks_manager.rb} +0 -0
  77. data/lib/shopify_app/session/in_memory_session_store.rb +27 -0
  78. data/lib/shopify_app/{shopify_session_repository.rb → session/session_repository.rb} +0 -0
  79. data/lib/shopify_app/{session_storage.rb → session/session_storage.rb} +9 -0
  80. data/lib/shopify_app/utils.rb +2 -2
  81. data/lib/shopify_app/version.rb +1 -1
  82. data/package-lock.json +23 -0
  83. data/package.json +28 -0
  84. data/service.yml +7 -0
  85. data/shipit.rubygems.yml +2 -0
  86. data/shopify_app.gemspec +5 -4
  87. data/translation.yml +7 -0
  88. data/webpack.config.js +24 -0
  89. data/yarn.lock +4594 -0
  90. metadata +80 -27
  91. data/lib/generators/shopify_app/install/templates/shopify_session_repository.rb +0 -23
  92. data/lib/generators/shopify_app/shop_model/templates/shopify_session_repository.rb +0 -9
  93. data/lib/shopify_app/in_memory_session_store.rb +0 -25
  94. data/lib/shopify_app/login_protection.rb +0 -119
  95. data/lib/shopify_app/shop.rb +0 -15
@@ -1,15 +1,19 @@
1
1
  <% content_for :javascript do %>
2
2
  <script type="text/javascript">
3
- var eventName = typeof(Turbolinks) !== 'undefined' ? 'page:change' : 'DOMContentLoaded';
3
+ var eventName = typeof(Turbolinks) !== 'undefined' ? 'turbolinks:load' : 'DOMContentLoaded';
4
4
 
5
- document.addEventListener(eventName, function() {
6
- <% if flash[:notice] %>
7
- ShopifyApp.flashNotice("<%= j flash[:notice].html_safe %>");
8
- <% end %>
5
+ if (!document.documentElement.hasAttribute("data-turbolinks-preview")) {
6
+ document.addEventListener(eventName, function flash() {
7
+ <% if flash[:notice] %>
8
+ ShopifyApp.flashNotice(<%== flash[:notice].to_json %>);
9
+ <% end %>
9
10
 
10
- <% if flash[:error] %>
11
- ShopifyApp.flashError("<%= j flash[:error].html_safe %>");
12
- <% end %>
13
- });
11
+ <% if flash[:error] %>
12
+ ShopifyApp.flashError(<%== flash[:error].to_json %>);
13
+ <% end %>
14
+
15
+ document.removeEventListener(eventName, flash)
16
+ });
17
+ }
14
18
  </script>
15
19
  <% end %>
@@ -2,7 +2,10 @@ ShopifyApp.configure do |config|
2
2
  config.application_name = "<%= @application_name %>"
3
3
  config.api_key = "<%= @api_key %>"
4
4
  config.secret = "<%= @secret %>"
5
- config.scope = "<%= @scope %>"
5
+ config.old_secret = "<%= @old_secret %>"
6
+ config.scope = "<%= @scope %>" # Consult this page for more scope options:
7
+ # https://help.shopify.com/en/api/getting-started/authentication/oauth/scopes
6
8
  config.embedded_app = <%= embedded_app? %>
7
9
  config.after_authenticate_job = false
10
+ config.session_repository = ShopifyApp::InMemorySessionStore
8
11
  end
@@ -1,4 +1,19 @@
1
- provider :shopify,
2
- ShopifyApp.configuration.api_key,
3
- ShopifyApp.configuration.secret,
4
- scope: ShopifyApp.configuration.scope
1
+ # frozen_string_literal: true
2
+
3
+ provider :shopify,
4
+ ShopifyApp.configuration.api_key,
5
+ ShopifyApp.configuration.secret,
6
+ scope: ShopifyApp.configuration.scope,
7
+ setup: lambda { |env|
8
+ strategy = env['omniauth.strategy']
9
+
10
+ shopify_auth_params = strategy.session['shopify.omniauth_params']&.with_indifferent_access
11
+ shop = if shopify_auth_params.present?
12
+ "https://#{shopify_auth_params[:shop]}"
13
+ else
14
+ ''
15
+ end
16
+
17
+ strategy.options[:client_options][:site] = shop
18
+ strategy.options[:old_client_secret] = ShopifyApp.configuration.old_secret
19
+ }
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/base'
4
+
5
+ module ShopifyApp
6
+ module Generators
7
+ class RotateShopifyTokenJobGenerator < Rails::Generators::Base
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ def add_rotate_shopify_token_job
11
+ copy_file('rotate_shopify_token_job.rb', "app/jobs/shopify/rotate_shopify_token_job.rb")
12
+ copy_file('rotate_shopify_token.rake', "lib/tasks/shopify/rotate_shopify_token.rake")
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ namespace :shopify do
3
+ desc "Rotate shopify tokens for all active shops"
4
+ task :rotate_shopify_tokens, [:refresh_token] => :environment do |_t, args|
5
+ all_active_shops.find_each do |shop|
6
+ Shopify::RotateShopifyTokenJob.perform_later(
7
+ shop_domain: shop.shopify_domain,
8
+ refresh_token: args[:refresh_token]
9
+ )
10
+ end
11
+ end
12
+
13
+ # Its implementation will depend on the app configuration. Change accordingly.
14
+ def all_active_shops
15
+ Shop.all
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shopify
4
+ class RotateShopifyTokenJob < ActiveJob::Base
5
+ def perform(params)
6
+ @shop = Shop.find_by(shopify_domain: params[:shop_domain])
7
+ return unless @shop
8
+
9
+ config = ShopifyApp.configuration
10
+ uri = URI("https://#{@shop.shopify_domain}/admin/oauth/access_token")
11
+ post_data = {
12
+ client_id: config.api_key,
13
+ client_secret: config.secret,
14
+ refresh_token: params[:refresh_token],
15
+ access_token: @shop.shopify_token,
16
+ }
17
+
18
+ @response = Net::HTTP.post_form(uri, post_data)
19
+ return log_error(response_expcetion_error_message) unless @response.is_a?(Net::HTTPSuccess)
20
+
21
+ access_token = JSON.parse(@response.body)['access_token']
22
+ return log_error(no_access_token_error_message) unless access_token
23
+
24
+ @shop.update(shopify_token: access_token)
25
+ end
26
+
27
+ private
28
+
29
+ def log_error(message)
30
+ Rails.logger.error(message)
31
+ end
32
+
33
+ def no_access_token_error_message
34
+ "RotateShopifyTokenJob response returned no access token for shop: #{@shop.shopify_domain}"
35
+ end
36
+
37
+ def response_exception_error_message
38
+ "RotateShopifyTokenJob failed for shop: #{@shop.shopify_domain}." \
39
+ "Response returned status: #{@response.code}. Error message: #{@response.message}. "
40
+ end
41
+ end
42
+ end
@@ -12,11 +12,11 @@ module ShopifyApp
12
12
  end
13
13
 
14
14
  def create_shop_migration
15
- migration_template "db/migrate/create_shops.erb", "db/migrate/create_shops.rb"
15
+ migration_template 'db/migrate/create_shops.erb', 'db/migrate/create_shops.rb'
16
16
  end
17
17
 
18
- def create_session_storage_initializer
19
- copy_file 'shopify_session_repository.rb', 'config/initializers/shopify_session_repository.rb', force: true
18
+ def update_shopify_app_initializer
19
+ gsub_file 'config/initializers/shopify_app.rb', 'ShopifyApp::InMemorySessionStore', 'Shop'
20
20
  end
21
21
 
22
22
  def create_shop_fixtures
@@ -1,4 +1,3 @@
1
1
  class Shop < ActiveRecord::Base
2
- include ShopifyApp::Shop
3
2
  include ShopifyApp::SessionStorage
4
3
  end
@@ -10,22 +10,26 @@ require 'shopify_app/configuration'
10
10
  # engine
11
11
  require 'shopify_app/engine'
12
12
 
13
- # jobs
14
- require 'shopify_app/webhooks_manager_job'
15
- require 'shopify_app/scripttags_manager_job'
16
-
17
- # helpers and concerns
18
- require 'shopify_app/shop'
19
- require 'shopify_app/session_storage'
20
- require 'shopify_app/sessions_concern'
21
- require 'shopify_app/localization'
22
- require 'shopify_app/login_protection'
23
- require 'shopify_app/webhooks_manager'
24
- require 'shopify_app/scripttags_manager'
25
- require 'shopify_app/webhook_verification'
26
- require 'shopify_app/app_proxy_verification'
13
+ # utils
27
14
  require 'shopify_app/utils'
28
15
 
29
- # session repository
30
- require 'shopify_app/shopify_session_repository'
31
- require 'shopify_app/in_memory_session_store'
16
+ # controller concerns
17
+ require 'shopify_app/controller_concerns/localization'
18
+ require 'shopify_app/controller_concerns/itp'
19
+ require 'shopify_app/controller_concerns/login_protection'
20
+ require 'shopify_app/controller_concerns/embedded_app'
21
+ require 'shopify_app/controller_concerns/webhook_verification'
22
+ require 'shopify_app/controller_concerns/app_proxy_verification'
23
+
24
+ # jobs
25
+ require 'shopify_app/jobs/webhooks_manager_job'
26
+ require 'shopify_app/jobs/scripttags_manager_job'
27
+
28
+ # managers
29
+ require 'shopify_app/managers/webhooks_manager'
30
+ require 'shopify_app/managers/scripttags_manager'
31
+
32
+ # session
33
+ require 'shopify_app/session/session_storage'
34
+ require 'shopify_app/session/session_repository'
35
+ require 'shopify_app/session/in_memory_session_store'
@@ -7,12 +7,18 @@ module ShopifyApp
7
7
  attr_accessor :application_name
8
8
  attr_accessor :api_key
9
9
  attr_accessor :secret
10
+ attr_accessor :old_secret
10
11
  attr_accessor :scope
11
12
  attr_accessor :embedded_app
12
13
  alias_method :embedded_app?, :embedded_app
13
14
  attr_accessor :webhooks
14
15
  attr_accessor :scripttags
15
16
  attr_accessor :after_authenticate_job
17
+ attr_accessor :session_repository
18
+
19
+ # customise urls
20
+ attr_accessor :root_url
21
+ attr_accessor :login_url
16
22
 
17
23
  # customise ActiveJob queue names
18
24
  attr_accessor :scripttags_manager_queue_name
@@ -21,24 +27,36 @@ module ShopifyApp
21
27
  # configure myshopify domain for local shopify development
22
28
  attr_accessor :myshopify_domain
23
29
 
30
+ # allow namespacing webhook jobs
31
+ attr_accessor :webhook_jobs_namespace
32
+
24
33
  def initialize
34
+ @root_url = '/'
25
35
  @myshopify_domain = 'myshopify.com'
36
+ @scripttags_manager_queue_name = Rails.application.config.active_job.queue_name
37
+ @webhooks_manager_queue_name = Rails.application.config.active_job.queue_name
26
38
  end
27
39
 
28
- def has_webhooks?
29
- webhooks.present?
40
+ def login_url
41
+ @login_url || File.join(@root_url, 'login')
30
42
  end
31
43
 
32
- def has_scripttags?
33
- scripttags.present?
44
+ def session_repository=(klass)
45
+ if Rails.configuration.cache_classes
46
+ ShopifyApp::SessionRepository.storage = klass
47
+ else
48
+ ActiveSupport::Reloader.to_prepare do
49
+ ShopifyApp::SessionRepository.storage = klass
50
+ end
51
+ end
34
52
  end
35
53
 
36
- def scripttags_manager_queue_name
37
- @scripttags_manager_queue_name ||= Rails.application.config.active_job.queue_name
54
+ def has_webhooks?
55
+ webhooks.present?
38
56
  end
39
57
 
40
- def webhooks_manager_queue_name
41
- @webhooks_manager_queue_name ||= Rails.application.config.active_job.queue_name
58
+ def has_scripttags?
59
+ scripttags.present?
42
60
  end
43
61
  end
44
62
 
@@ -8,7 +8,7 @@ module ShopifyApp
8
8
  end
9
9
 
10
10
  def verify_proxy_request
11
- return head :unauthorized unless query_string_valid?(request.query_string)
11
+ return head :forbidden unless query_string_valid?(request.query_string)
12
12
  end
13
13
 
14
14
  private
@@ -0,0 +1,19 @@
1
+ module ShopifyApp
2
+ module EmbeddedApp
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ if ShopifyApp.configuration.embedded_app?
7
+ after_action :set_esdk_headers
8
+ layout 'embedded_app'
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def set_esdk_headers
15
+ response.set_header('P3P', 'CP="Not used"')
16
+ response.headers.except!('X-Frame-Options')
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyApp
4
+ # Cookie management helpers required for ITP implementation
5
+ module Itp
6
+ private
7
+
8
+ def set_test_cookie
9
+ return unless ShopifyApp.configuration.embedded_app?
10
+ return unless user_agent_can_partition_cookies
11
+
12
+ session['shopify.cookies_persist'] = true
13
+ end
14
+
15
+ def set_top_level_oauth_cookie
16
+ session['shopify.top_level_oauth'] = true
17
+ end
18
+
19
+ def clear_top_level_oauth_cookie
20
+ session.delete('shopify.top_level_oauth')
21
+ end
22
+
23
+ def user_agent_is_mobile
24
+ user_agent = BrowserSniffer.new(request.user_agent).browser_info
25
+
26
+ user_agent[:name].to_s.match(/Shopify\sMobile/)
27
+ end
28
+
29
+ def user_agent_is_pos
30
+ user_agent = BrowserSniffer.new(request.user_agent).browser_info
31
+
32
+ user_agent[:name].to_s.match(/Shopify\sPOS/)
33
+ end
34
+
35
+ def user_agent_can_partition_cookies
36
+ user_agent = BrowserSniffer.new(request.user_agent).browser_info
37
+
38
+ is_safari = user_agent[:name].to_s.match(/Safari/)
39
+
40
+ return false unless is_safari
41
+
42
+ user_agent[:version].to_s.match(/12\.0/)
43
+ end
44
+ end
45
+ end
@@ -2,6 +2,12 @@ module ShopifyApp
2
2
  module Localization
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ included do
6
+ before_action :set_locale
7
+ end
8
+
9
+ private
10
+
5
11
  def set_locale
6
12
  if params[:locale]
7
13
  session[:locale] = params[:locale]
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'browser_sniffer'
4
+
5
+ module ShopifyApp
6
+ module LoginProtection
7
+ extend ActiveSupport::Concern
8
+ include ShopifyApp::Itp
9
+
10
+ class ShopifyDomainNotFound < StandardError; end
11
+
12
+ included do
13
+ after_action :set_test_cookie
14
+ rescue_from ActiveResource::UnauthorizedAccess, :with => :close_session
15
+ end
16
+
17
+ def shopify_session
18
+ return redirect_to_login unless shop_session
19
+ clear_top_level_oauth_cookie
20
+
21
+ begin
22
+ ShopifyAPI::Base.activate_session(shop_session)
23
+ yield
24
+ ensure
25
+ ShopifyAPI::Base.clear_session
26
+ end
27
+ end
28
+
29
+ def shop_session
30
+ return unless session[:shopify]
31
+ @shop_session ||= ShopifyApp::SessionRepository.retrieve(session[:shopify])
32
+ end
33
+
34
+ def login_again_if_different_shop
35
+ if shop_session && params[:shop] && params[:shop].is_a?(String) && (shop_session.url != params[:shop])
36
+ clear_shop_session
37
+ redirect_to_login
38
+ end
39
+ end
40
+
41
+ protected
42
+
43
+ def redirect_to_login
44
+ if request.xhr?
45
+ head :unauthorized
46
+ else
47
+ if request.get?
48
+ session[:return_to] = "#{request.path}?#{sanitized_params.to_query}"
49
+ end
50
+ redirect_to login_url
51
+ end
52
+ end
53
+
54
+ def close_session
55
+ clear_shop_session
56
+ redirect_to login_url
57
+ end
58
+
59
+ def clear_shop_session
60
+ session[:shopify] = nil
61
+ session[:shopify_domain] = nil
62
+ session[:shopify_user] = nil
63
+ end
64
+
65
+ def login_url(top_level: false)
66
+ url = ShopifyApp.configuration.login_url
67
+
68
+ query_params = login_url_params(top_level: top_level)
69
+
70
+ url = "#{url}?#{query_params.to_query}" if query_params.present?
71
+ url
72
+ end
73
+
74
+ def login_url_params(top_level:)
75
+ query_params = {}
76
+ query_params[:shop] = sanitized_params[:shop] if params[:shop].present?
77
+
78
+ has_referer_shop_name = referer_sanitized_shop_name.present?
79
+
80
+ if has_referer_shop_name
81
+ query_params[:shop] ||= referer_sanitized_shop_name
82
+ end
83
+
84
+ query_params[:top_level] = true if top_level
85
+ query_params
86
+ end
87
+
88
+ def fullpage_redirect_to(url)
89
+ if ShopifyApp.configuration.embedded_app?
90
+ render 'shopify_app/shared/redirect', layout: false, locals: { url: url, current_shopify_domain: current_shopify_domain }
91
+ else
92
+ redirect_to url
93
+ end
94
+ end
95
+
96
+ def current_shopify_domain
97
+ shopify_domain = sanitized_shop_name || session[:shopify_domain]
98
+ return shopify_domain if shopify_domain.present?
99
+
100
+ raise ShopifyDomainNotFound
101
+ end
102
+
103
+ def sanitized_shop_name
104
+ @sanitized_shop_name ||= sanitize_shop_param(params)
105
+ end
106
+
107
+ def referer_sanitized_shop_name
108
+ return unless request.referer.present?
109
+
110
+ @referer_sanitized_shop_name ||= begin
111
+ referer_uri = URI(request.referer)
112
+ query_params = Rack::Utils.parse_query(referer_uri.query)
113
+
114
+ sanitize_shop_param(query_params.with_indifferent_access)
115
+ end
116
+ end
117
+
118
+ def sanitize_shop_param(params)
119
+ return unless params[:shop].present?
120
+ ShopifyApp::Utils.sanitize_shop_domain(params[:shop])
121
+ end
122
+
123
+ def sanitized_params
124
+ request.query_parameters.clone.tap do |query_params|
125
+ if params[:shop].is_a?(String)
126
+ query_params[:shop] = sanitize_shop_param(params)
127
+ end
128
+ end
129
+ end
130
+
131
+ def return_address
132
+ session.delete(:return_to) || ShopifyApp.configuration.root_url
133
+ end
134
+ end
135
+ end