shopify_app 18.1.3 → 19.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +2 -2
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +3 -2
  5. data/Gemfile +3 -2
  6. data/Gemfile.lock +122 -136
  7. data/Rakefile +4 -3
  8. data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +1 -1
  9. data/app/controllers/shopify_app/authenticated_controller.rb +1 -0
  10. data/app/controllers/shopify_app/callback_controller.rb +35 -147
  11. data/app/controllers/shopify_app/sessions_controller.rb +25 -137
  12. data/app/controllers/shopify_app/webhooks_controller.rb +5 -23
  13. data/config/routes.rb +6 -12
  14. data/docs/Troubleshooting.md +0 -3
  15. data/docs/Upgrading.md +85 -2
  16. data/docs/shopify_app/webhooks.md +1 -1
  17. data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +10 -9
  18. data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +1 -0
  19. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +4 -3
  20. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +13 -12
  21. data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +9 -1
  22. data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +7 -6
  23. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +2 -1
  24. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +1 -1
  25. data/lib/generators/shopify_app/authenticated_controller/authenticated_controller_generator.rb +3 -3
  26. data/lib/generators/shopify_app/controllers/controllers_generator.rb +4 -3
  27. data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +11 -15
  28. data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +2 -2
  29. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +3 -3
  30. data/lib/generators/shopify_app/install/install_generator.rb +25 -74
  31. data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +1 -1
  32. data/lib/generators/shopify_app/install/templates/session_store.rb +2 -1
  33. data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +20 -5
  34. data/lib/generators/shopify_app/products_controller/products_controller_generator.rb +3 -3
  35. data/lib/generators/shopify_app/products_controller/templates/products_controller.rb +1 -1
  36. data/lib/generators/shopify_app/rotate_shopify_token_job/rotate_shopify_token_job_generator.rb +4 -4
  37. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +1 -0
  38. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +1 -1
  39. data/lib/generators/shopify_app/routes/routes_generator.rb +6 -5
  40. data/lib/generators/shopify_app/routes/templates/routes.rb +5 -5
  41. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +11 -10
  42. data/lib/generators/shopify_app/shop_model/templates/shop.rb +1 -0
  43. data/lib/generators/shopify_app/shopify_app_generator.rb +4 -3
  44. data/lib/generators/shopify_app/user_model/templates/user.rb +1 -0
  45. data/lib/generators/shopify_app/user_model/user_model_generator.rb +11 -10
  46. data/lib/generators/shopify_app/views/views_generator.rb +4 -3
  47. data/lib/shopify_app/access_scopes/shop_strategy.rb +2 -2
  48. data/lib/shopify_app/access_scopes/user_strategy.rb +4 -4
  49. data/lib/shopify_app/configuration.rb +5 -17
  50. data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +4 -3
  51. data/lib/shopify_app/controller_concerns/csrf_protection.rb +2 -1
  52. data/lib/shopify_app/controller_concerns/embedded_app.rb +4 -3
  53. data/lib/shopify_app/controller_concerns/itp.rb +3 -3
  54. data/lib/shopify_app/controller_concerns/localization.rb +1 -0
  55. data/lib/shopify_app/controller_concerns/login_protection.rb +50 -70
  56. data/lib/shopify_app/controller_concerns/payload_verification.rb +3 -2
  57. data/lib/shopify_app/controller_concerns/webhook_verification.rb +2 -1
  58. data/lib/shopify_app/engine.rb +7 -15
  59. data/lib/shopify_app/jobs/scripttags_manager_job.rb +2 -2
  60. data/lib/shopify_app/jobs/webhooks_manager_job.rb +4 -5
  61. data/lib/shopify_app/managers/scripttags_manager.rb +11 -4
  62. data/lib/shopify_app/managers/webhooks_manager.rb +42 -44
  63. data/lib/shopify_app/middleware/jwt_middleware.rb +5 -4
  64. data/lib/shopify_app/session/in_memory_session_store.rb +1 -0
  65. data/lib/shopify_app/session/in_memory_shop_session_store.rb +2 -1
  66. data/lib/shopify_app/session/in_memory_user_session_store.rb +1 -0
  67. data/lib/shopify_app/session/jwt.rb +9 -8
  68. data/lib/shopify_app/session/null_user_session_store.rb +2 -1
  69. data/lib/shopify_app/session/session_repository.rb +37 -0
  70. data/lib/shopify_app/session/session_storage.rb +4 -6
  71. data/lib/shopify_app/session/shop_session_storage.rb +6 -6
  72. data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +7 -8
  73. data/lib/shopify_app/session/user_session_storage.rb +19 -6
  74. data/lib/shopify_app/session/user_session_storage_with_scopes.rb +21 -8
  75. data/lib/shopify_app/test_helpers/all.rb +2 -1
  76. data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +4 -3
  77. data/lib/shopify_app/utils.rb +2 -9
  78. data/lib/shopify_app/version.rb +2 -1
  79. data/lib/shopify_app.rb +35 -40
  80. data/package.json +1 -1
  81. data/shopify_app.gemspec +21 -20
  82. data/yarn.lock +6 -6
  83. metadata +45 -50
  84. data/lib/generators/shopify_app/install/templates/omniauth.rb +0 -4
  85. data/lib/generators/shopify_app/install/templates/shopify_provider.rb.tt +0 -8
  86. data/lib/generators/shopify_app/install/templates/user_agent.rb +0 -6
  87. data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +0 -34
  88. data/lib/shopify_app/omniauth/omniauth_configuration.rb +0 -64
@@ -9,7 +9,7 @@ module ShopifyApp
9
9
  def update_access_scopes?(user_id: nil, shopify_user_id: nil)
10
10
  return update_access_scopes_for_user_id?(user_id) if user_id
11
11
  return update_access_scopes_for_shopify_user_id?(shopify_user_id) if shopify_user_id
12
- raise(InvalidInput, '#update_access_scopes? requires user_id or shopify_user_id parameter inputs')
12
+ raise(InvalidInput, "#update_access_scopes? requires user_id or shopify_user_id parameter inputs")
13
13
  end
14
14
 
15
15
  private
@@ -25,15 +25,15 @@ module ShopifyApp
25
25
  end
26
26
 
27
27
  def user_access_scopes_by_user_id(user_id)
28
- ShopifyApp::SessionRepository.retrieve_user_session(user_id)&.access_scopes
28
+ ShopifyApp::SessionRepository.retrieve_user_session(user_id)&.scope
29
29
  end
30
30
 
31
31
  def user_access_scopes_by_shopify_user_id(shopify_user_id)
32
- ShopifyApp::SessionRepository.retrieve_user_session_by_shopify_user_id(shopify_user_id)&.access_scopes
32
+ ShopifyApp::SessionRepository.retrieve_user_session_by_shopify_user_id(shopify_user_id)&.scope
33
33
  end
34
34
 
35
35
  def configuration_access_scopes
36
- ShopifyAPI::ApiAccess.new(ShopifyApp.configuration.user_access_scopes)
36
+ ShopifyAPI::Auth::AuthScopes.new(ShopifyApp.configuration.user_access_scopes)
37
37
  end
38
38
  end
39
39
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class Configuration
4
5
  # Shopify App settings. These values should match the configuration
@@ -37,25 +38,16 @@ module ShopifyApp
37
38
  # allow namespacing webhook jobs
38
39
  attr_accessor :webhook_jobs_namespace
39
40
 
40
- # allow enabling of same site none on cookies
41
- attr_writer :enable_same_site_none
42
-
43
- # allow enabling jwt headers for authentication
44
- attr_accessor :allow_jwt_authentication
45
-
46
- attr_accessor :allow_cookie_authentication
47
-
48
41
  def initialize
49
- @root_url = '/'
50
- @myshopify_domain = 'myshopify.com'
42
+ @root_url = "/"
43
+ @myshopify_domain = "myshopify.com"
51
44
  @scripttags_manager_queue_name = Rails.application.config.active_job.queue_name
52
45
  @webhooks_manager_queue_name = Rails.application.config.active_job.queue_name
53
- @disable_webpacker = ENV['SHOPIFY_APP_DISABLE_WEBPACKER'].present?
54
- @allow_cookie_authentication = true
46
+ @disable_webpacker = ENV["SHOPIFY_APP_DISABLE_WEBPACKER"].present?
55
47
  end
56
48
 
57
49
  def login_url
58
- @login_url || File.join(@root_url, 'login')
50
+ @login_url || File.join(@root_url, "login")
59
51
  end
60
52
 
61
53
  def user_session_repository=(klass)
@@ -92,10 +84,6 @@ module ShopifyApp
92
84
  scripttags.present?
93
85
  end
94
86
 
95
- def enable_same_site_none
96
- !Rails.env.test? && (@enable_same_site_none.nil? ? embedded_app? : @enable_same_site_none)
97
- end
98
-
99
87
  def shop_access_scopes
100
88
  @shop_access_scopes || scope
101
89
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module AppProxyVerification
4
5
  extend ActiveSupport::Concern
@@ -16,7 +17,7 @@ module ShopifyApp
16
17
  def query_string_valid?(query_string)
17
18
  query_hash = Rack::Utils.parse_query(query_string)
18
19
 
19
- signature = query_hash.delete('signature')
20
+ signature = query_hash.delete("signature")
20
21
  return false if signature.nil?
21
22
 
22
23
  ActiveSupport::SecurityUtils.secure_compare(
@@ -26,10 +27,10 @@ module ShopifyApp
26
27
  end
27
28
 
28
29
  def calculated_signature(query_hash_without_signature)
29
- sorted_params = query_hash_without_signature.collect { |k, v| "#{k}=#{Array(v).join(',')}" }.sort.join
30
+ sorted_params = query_hash_without_signature.collect { |k, v| "#{k}=#{Array(v).join(",")}" }.sort.join
30
31
 
31
32
  OpenSSL::HMAC.hexdigest(
32
- OpenSSL::Digest.new('sha256'),
33
+ OpenSSL::Digest.new("sha256"),
33
34
  ShopifyApp.configuration.secret,
34
35
  sorted_params
35
36
  )
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module CsrfProtection
4
5
  extend ActiveSupport::Concern
@@ -9,7 +10,7 @@ module ShopifyApp
9
10
  private
10
11
 
11
12
  def valid_session_token?
12
- request.env['jwt.shopify_domain']
13
+ request.env["jwt.shopify_domain"]
13
14
  end
14
15
  end
15
16
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module EmbeddedApp
4
5
  extend ActiveSupport::Concern
@@ -6,15 +7,15 @@ module ShopifyApp
6
7
  included do
7
8
  if ShopifyApp.configuration.embedded_app?
8
9
  after_action(:set_esdk_headers)
9
- layout('embedded_app')
10
+ layout("embedded_app")
10
11
  end
11
12
  end
12
13
 
13
14
  private
14
15
 
15
16
  def set_esdk_headers
16
- response.set_header('P3P', 'CP="Not used"')
17
- response.headers.except!('X-Frame-Options')
17
+ response.set_header("P3P", 'CP="Not used"')
18
+ response.headers.except!("X-Frame-Options")
18
19
  end
19
20
  end
20
21
  end
@@ -9,15 +9,15 @@ module ShopifyApp
9
9
  return unless ShopifyApp.configuration.embedded_app?
10
10
  return unless user_agent_can_partition_cookies
11
11
 
12
- session['shopify.cookies_persist'] = true
12
+ session["shopify.cookies_persist"] = true
13
13
  end
14
14
 
15
15
  def set_top_level_oauth_cookie
16
- session['shopify.top_level_oauth'] = true
16
+ session["shopify.top_level_oauth"] = true
17
17
  end
18
18
 
19
19
  def clear_top_level_oauth_cookie
20
- session.delete('shopify.top_level_oauth')
20
+ session.delete("shopify.top_level_oauth")
21
21
  end
22
22
 
23
23
  def user_agent_is_mobile
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module Localization
4
5
  extend ActiveSupport::Concern
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'browser_sniffer'
3
+ require "browser_sniffer"
4
4
 
5
5
  module ShopifyApp
6
6
  module LoginProtection
@@ -13,82 +13,51 @@ module ShopifyApp
13
13
 
14
14
  included do
15
15
  after_action :set_test_cookie
16
- rescue_from ActiveResource::UnauthorizedAccess, with: :close_session
16
+ rescue_from ShopifyAPI::Errors::HttpResponseError, with: :handle_http_error
17
17
  end
18
18
 
19
- ACCESS_TOKEN_REQUIRED_HEADER = 'X-Shopify-API-Request-Failure-Unauthorized'
19
+ ACCESS_TOKEN_REQUIRED_HEADER = "X-Shopify-API-Request-Failure-Unauthorized"
20
20
 
21
21
  def activate_shopify_session
22
- if user_session_expected? && user_session.blank?
22
+ if current_shopify_session.blank?
23
23
  signal_access_token_required
24
24
  return redirect_to_login
25
25
  end
26
26
 
27
- return redirect_to_login if current_shopify_session.blank?
27
+ unless current_shopify_session.scope.to_a.empty? ||
28
+ current_shopify_session.scope.covers?(ShopifyAPI::Context.scope)
28
29
 
29
- clear_top_level_oauth_cookie
30
+ clear_shopify_session
31
+ return redirect_to_login
32
+ end
30
33
 
31
34
  begin
32
- ShopifyAPI::Base.activate_session(current_shopify_session)
35
+ ShopifyAPI::Context.activate_session(current_shopify_session)
33
36
  yield
34
37
  ensure
35
- ShopifyAPI::Base.clear_session
38
+ ShopifyAPI::Context.deactivate_session
36
39
  end
37
40
  end
38
41
 
39
42
  def current_shopify_session
40
43
  @current_shopify_session ||= begin
41
- user_session || shop_session
44
+ cookie_name = ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME
45
+ ShopifyAPI::Utils::SessionUtils.load_current_session(
46
+ auth_header: request.headers["HTTP_AUTHORIZATION"],
47
+ cookies: { cookie_name => cookies.encrypted[cookie_name] },
48
+ is_online: user_session_expected?
49
+ )
50
+ rescue ShopifyAPI::Errors::CookieNotFoundError
51
+ nil
52
+ rescue ShopifyAPI::Errors::InvalidJwtTokenError
53
+ nil
42
54
  end
43
55
  end
44
56
 
45
- def user_session
46
- user_session_by_jwt || user_session_by_cookie
47
- end
48
-
49
- def user_session_by_jwt
50
- return unless ShopifyApp.configuration.allow_jwt_authentication
51
- return unless jwt_shopify_user_id
52
- ShopifyApp::SessionRepository.retrieve_user_session_by_shopify_user_id(jwt_shopify_user_id)
53
- end
54
-
55
- def user_session_by_cookie
56
- return unless ShopifyApp.configuration.allow_cookie_authentication
57
- return unless session[:user_id].present?
58
- ShopifyApp::SessionRepository.retrieve_user_session(session[:user_id])
59
- end
60
-
61
- def shop_session
62
- shop_session_by_jwt || shop_session_by_cookie
63
- end
64
-
65
- def shop_session_by_jwt
66
- return unless ShopifyApp.configuration.allow_jwt_authentication
67
- return unless jwt_shopify_domain
68
- ShopifyApp::SessionRepository.retrieve_shop_session_by_shopify_domain(jwt_shopify_domain)
69
- end
70
-
71
- def shop_session_by_cookie
72
- return unless ShopifyApp.configuration.allow_cookie_authentication
73
- return unless session[:shop_id].present?
74
- ShopifyApp::SessionRepository.retrieve_shop_session(session[:shop_id])
75
- end
76
-
77
57
  def login_again_if_different_user_or_shop
78
- if session[:user_session].present? && params[:session].present? # session data was sent/stored correctly
79
- clear_session = session[:user_session] != params[:session] # current user is different from stored user
80
- end
81
-
82
- if current_shopify_session &&
83
- params[:shop] && params[:shop].is_a?(String) &&
84
- (current_shopify_session.domain != params[:shop])
85
- clear_session = true
86
- end
87
-
88
- if clear_session
89
- clear_shopify_session
90
- redirect_to_login
91
- end
58
+ return unless session_id_conflicts_with_params || session_shop_conflicts_with_params
59
+ clear_shopify_session
60
+ redirect_to_login
92
61
  end
93
62
 
94
63
  def signal_access_token_required
@@ -96,7 +65,7 @@ module ShopifyApp
96
65
  end
97
66
 
98
67
  def jwt_expire_at
99
- expire_at = request.env['jwt.expire_at']
68
+ expire_at = request.env["jwt.expire_at"]
100
69
  return unless expire_at
101
70
  expire_at - 5.seconds # 5s gap to start fetching new token in advance
102
71
  end
@@ -104,11 +73,11 @@ module ShopifyApp
104
73
  protected
105
74
 
106
75
  def jwt_shopify_domain
107
- request.env['jwt.shopify_domain']
76
+ request.env["jwt.shopify_domain"]
108
77
  end
109
78
 
110
79
  def jwt_shopify_user_id
111
- request.env['jwt.shopify_user_id']
80
+ request.env["jwt.shopify_user_id"]
112
81
  end
113
82
 
114
83
  def host
@@ -137,12 +106,16 @@ module ShopifyApp
137
106
  redirect_to(login_url_with_optional_shop)
138
107
  end
139
108
 
109
+ def handle_http_error(error)
110
+ if error.code == 401
111
+ close_session
112
+ else
113
+ raise error
114
+ end
115
+ end
116
+
140
117
  def clear_shopify_session
141
- session[:shop_id] = nil
142
- session[:user_id] = nil
143
- session[:shopify_domain] = nil
144
- session[:shopify_user] = nil
145
- session[:user_session] = nil
118
+ cookies.encrypted[ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME] = nil
146
119
  end
147
120
 
148
121
  def login_url_with_optional_shop(top_level: false)
@@ -179,23 +152,21 @@ module ShopifyApp
179
152
  end
180
153
 
181
154
  def return_to_param_required?
182
- native_params = %i[shop hmac timestamp locale protocol return_to]
183
- request.path != '/' || sanitized_params.except(*native_params).any?
155
+ native_params = [:shop, :hmac, :timestamp, :locale, :protocol, :return_to]
156
+ request.path != "/" || sanitized_params.except(*native_params).any?
184
157
  end
185
158
 
186
159
  def fullpage_redirect_to(url)
187
160
  if ShopifyApp.configuration.embedded_app?
188
- render('shopify_app/shared/redirect', layout: false,
189
- locals: { url: url, current_shopify_domain: current_shopify_domain })
161
+ render("shopify_app/shared/redirect", layout: false,
162
+ locals: { url: url, current_shopify_domain: current_shopify_domain })
190
163
  else
191
164
  redirect_to(url)
192
165
  end
193
166
  end
194
167
 
195
168
  def current_shopify_domain
196
- shopify_domain = sanitized_shop_name ||
197
- jwt_shopify_domain ||
198
- session[:shopify_domain]
169
+ shopify_domain = sanitized_shop_name || current_shopify_session&.shop
199
170
 
200
171
  return shopify_domain if shopify_domain.present?
201
172
 
@@ -252,6 +223,15 @@ module ShopifyApp
252
223
 
253
224
  private
254
225
 
226
+ def session_id_conflicts_with_params
227
+ shopify_session_id = current_shopify_session&.shopify_session_id
228
+ params[:session].present? && shopify_session_id.present? && params[:session] != shopify_session_id
229
+ end
230
+
231
+ def session_shop_conflicts_with_params
232
+ current_shopify_session && params[:shop].is_a?(String) && current_shopify_session.shop != params[:shop]
233
+ end
234
+
255
235
  def user_session_expected?
256
236
  !ShopifyApp.configuration.user_session_repository.blank? && ShopifyApp::SessionRepository.user_storage.present?
257
237
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module PayloadVerification
4
5
  extend ActiveSupport::Concern
@@ -6,14 +7,14 @@ module ShopifyApp
6
7
  private
7
8
 
8
9
  def shopify_hmac
9
- request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']
10
+ request.headers["HTTP_X_SHOPIFY_HMAC_SHA256"]
10
11
  end
11
12
 
12
13
  def hmac_valid?(data)
13
14
  secrets = [ShopifyApp.configuration.secret, ShopifyApp.configuration.old_secret].reject(&:blank?)
14
15
 
15
16
  secrets.any? do |secret|
16
- digest = OpenSSL::Digest.new('sha256')
17
+ digest = OpenSSL::Digest.new("sha256")
17
18
  ActiveSupport::SecurityUtils.secure_compare(
18
19
  shopify_hmac,
19
20
  Base64.strict_encode64(OpenSSL::HMAC.digest(digest, secret, data))
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module WebhookVerification
4
5
  extend ActiveSupport::Concern
@@ -17,7 +18,7 @@ module ShopifyApp
17
18
  end
18
19
 
19
20
  def shop_domain
20
- request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN']
21
+ request.headers["HTTP_X_SHOPIFY_SHOP_DOMAIN"]
21
22
  end
22
23
  end
23
24
  end
@@ -1,36 +1,28 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module RedactJobParams
4
5
  private
5
6
 
6
7
  def args_info(job)
7
- log_disabled_classes = %w(ShopifyApp::ScripttagsManagerJob ShopifyApp::WebhooksManagerJob)
8
+ log_disabled_classes = ["ShopifyApp::ScripttagsManagerJob", "ShopifyApp::WebhooksManagerJob"]
8
9
  return "" if log_disabled_classes.include?(job.class.name)
9
10
  super
10
11
  end
11
12
  end
12
13
 
13
14
  class Engine < Rails::Engine
14
- engine_name 'shopify_app'
15
+ engine_name "shopify_app"
15
16
  isolate_namespace ShopifyApp
16
17
 
17
18
  initializer "shopify_app.assets.precompile" do |app|
18
- app.config.assets.precompile += %w[
19
- shopify_app/redirect.js
20
- shopify_app/post_redirect.js
21
- shopify_app/top_level.js
22
- shopify_app/enable_cookies.js
23
- shopify_app/request_storage_access.js
24
- storage_access.svg
25
- ]
19
+ app.config.assets.precompile += ["shopify_app/redirect.js", "shopify_app/post_redirect.js",
20
+ "shopify_app/top_level.js", "shopify_app/enable_cookies.js",
21
+ "shopify_app/request_storage_access.js", "storage_access.svg",]
26
22
  end
27
23
 
28
24
  initializer "shopify_app.middleware" do |app|
29
- app.config.middleware.insert_after(::Rack::Runtime, ShopifyApp::SameSiteCookieMiddleware)
30
-
31
- if ShopifyApp.configuration.allow_jwt_authentication
32
- app.config.middleware.insert_after(ShopifyApp::SameSiteCookieMiddleware, ShopifyApp::JWTMiddleware)
33
- end
25
+ app.config.middleware.insert_after(::Rack::Runtime, ShopifyApp::JWTMiddleware)
34
26
  end
35
27
 
36
28
  initializer "shopify_app.redact_job_params" do
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class ScripttagsManagerJob < ActiveJob::Base
4
5
  queue_as do
@@ -6,8 +7,7 @@ module ShopifyApp
6
7
  end
7
8
 
8
9
  def perform(shop_domain:, shop_token:, scripttags:)
9
- api_version = ShopifyApp.configuration.api_version
10
- ShopifyAPI::Session.temp(domain: shop_domain, token: shop_token, api_version: api_version) do
10
+ ShopifyAPI::Auth::Session.temp(shop: shop_domain, access_token: shop_token) do
11
11
  manager = ScripttagsManager.new(scripttags, shop_domain)
12
12
  manager.create_scripttags
13
13
  end
@@ -1,15 +1,14 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class WebhooksManagerJob < ActiveJob::Base
4
5
  queue_as do
5
6
  ShopifyApp.configuration.webhooks_manager_queue_name
6
7
  end
7
8
 
8
- def perform(shop_domain:, shop_token:, webhooks:)
9
- api_version = ShopifyApp.configuration.api_version
10
- ShopifyAPI::Session.temp(domain: shop_domain, token: shop_token, api_version: api_version) do
11
- manager = WebhooksManager.new(webhooks)
12
- manager.create_webhooks
9
+ def perform(shop_domain:, shop_token:)
10
+ ShopifyAPI::Auth::Session.temp(shop: shop_domain, access_token: shop_token) do |session|
11
+ WebhooksManager.create_webhooks(session: session)
13
12
  end
14
13
  end
15
14
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class ScripttagsManager
4
5
  class CreationFailed < StandardError; end
@@ -44,7 +45,7 @@ module ShopifyApp
44
45
  def destroy_scripttags
45
46
  scripttags = expanded_scripttags
46
47
  ShopifyAPI::ScriptTag.all.each do |tag|
47
- ShopifyAPI::ScriptTag.delete(tag.id) if required_scripttag?(scripttags, tag)
48
+ tag.delete if required_scripttag?(scripttags, tag)
48
49
  end
49
50
 
50
51
  @current_scripttags = nil
@@ -61,9 +62,15 @@ module ShopifyApp
61
62
  end
62
63
 
63
64
  def create_scripttag(attributes)
64
- attributes.reverse_merge!(format: 'json')
65
- scripttag = ShopifyAPI::ScriptTag.create(attributes)
66
- raise CreationFailed, scripttag.errors.full_messages.to_sentence unless scripttag.persisted?
65
+ scripttag = ShopifyAPI::ScriptTag.new
66
+ attributes.each { |key, value| scripttag.public_send("#{key}=", value) }
67
+
68
+ begin
69
+ scripttag.save!
70
+ rescue ShopifyAPI::Errors::HttpResponseError => e
71
+ raise CreationFailed, e.message
72
+ end
73
+
67
74
  scripttag
68
75
  end
69
76
 
@@ -1,62 +1,60 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class WebhooksManager
4
5
  class CreationFailed < StandardError; end
5
6
 
6
- def self.queue(shop_domain, shop_token, webhooks)
7
- ShopifyApp::WebhooksManagerJob.perform_later(
8
- shop_domain: shop_domain,
9
- shop_token: shop_token,
10
- webhooks: webhooks
11
- )
12
- end
13
-
14
- attr_reader :required_webhooks
15
-
16
- def initialize(webhooks)
17
- @required_webhooks = webhooks
18
- end
19
-
20
- def recreate_webhooks!
21
- destroy_webhooks
22
- create_webhooks
23
- end
24
-
25
- def create_webhooks
26
- return unless required_webhooks.present?
7
+ class << self
8
+ def queue(shop_domain, shop_token)
9
+ ShopifyApp::WebhooksManagerJob.perform_later(
10
+ shop_domain: shop_domain,
11
+ shop_token: shop_token
12
+ )
13
+ end
27
14
 
28
- required_webhooks.each do |webhook|
29
- create_webhook(webhook) unless webhook_exists?(webhook[:topic])
15
+ def create_webhooks(session:)
16
+ return unless ShopifyApp.configuration.has_webhooks?
17
+ ShopifyAPI::Webhooks::Registry.register_all(session: session)
30
18
  end
31
- end
32
19
 
33
- def destroy_webhooks
34
- ShopifyAPI::Webhook.all.to_a.each do |webhook|
35
- ShopifyAPI::Webhook.delete(webhook.id) if required_webhook?(webhook)
20
+ def recreate_webhooks!(session:)
21
+ destroy_webhooks
22
+ return unless ShopifyApp.configuration.has_webhooks?
23
+ add_registrations
24
+ ShopifyAPI::Webhooks::Registry.register_all(session: session)
36
25
  end
37
26
 
38
- @current_webhooks = nil
39
- end
27
+ def destroy_webhooks
28
+ return unless ShopifyApp.configuration.has_webhooks?
40
29
 
41
- private
30
+ ShopifyApp.configuration.webhooks.each do |attributes|
31
+ ShopifyAPI::Webhooks::Registry.unregister(topic: attributes[:topic])
32
+ end
33
+ end
42
34
 
43
- def required_webhook?(webhook)
44
- required_webhooks.map { |w| w[:address] }.include?(webhook.address)
45
- end
35
+ def add_registrations
36
+ return unless ShopifyApp.configuration.has_webhooks?
37
+
38
+ ShopifyApp.configuration.webhooks.each do |attributes|
39
+ ShopifyAPI::Webhooks::Registry.add_registration(
40
+ topic: attributes[:topic],
41
+ delivery_method: attributes[:delivery_method] || :http,
42
+ path: attributes[:address],
43
+ handler: webhook_job_klass(attributes[:topic]),
44
+ fields: attributes[:fields]
45
+ )
46
+ end
47
+ end
46
48
 
47
- def create_webhook(attributes)
48
- attributes.reverse_merge!(format: 'json')
49
- webhook = ShopifyAPI::Webhook.create(attributes)
50
- raise CreationFailed, webhook.errors.full_messages.to_sentence unless webhook.persisted?
51
- webhook
52
- end
49
+ private
53
50
 
54
- def webhook_exists?(topic)
55
- current_webhooks[topic]
56
- end
51
+ def webhook_job_klass(topic)
52
+ webhook_job_klass_name(topic).safe_constantize || raise(ShopifyApp::MissingWebhookJobError)
53
+ end
57
54
 
58
- def current_webhooks
59
- @current_webhooks ||= ShopifyAPI::Webhook.all.to_a.index_by(&:topic)
55
+ def webhook_job_klass_name(topic)
56
+ [ShopifyApp.configuration.webhook_jobs_namespace, "#{topic.gsub("/", "_")}_job"].compact.join("/").classify
57
+ end
60
58
  end
61
59
  end
62
60
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class JWTMiddleware
4
5
  TOKEN_REGEX = /^Bearer\s+(.*?)$/
@@ -24,7 +25,7 @@ module ShopifyApp
24
25
  end
25
26
 
26
27
  def authorization_header(env)
27
- env['HTTP_AUTHORIZATION']
28
+ env["HTTP_AUTHORIZATION"]
28
29
  end
29
30
 
30
31
  def extract_token(env)
@@ -35,9 +36,9 @@ module ShopifyApp
35
36
  def set_env_variables(token, env)
36
37
  jwt = ShopifyApp::JWT.new(token)
37
38
 
38
- env['jwt.shopify_domain'] = jwt.shopify_domain
39
- env['jwt.shopify_user_id'] = jwt.shopify_user_id
40
- env['jwt.expire_at'] = jwt.expire_at
39
+ env["jwt.shopify_domain"] = jwt.shopify_domain
40
+ env["jwt.shopify_user_id"] = jwt.shopify_user_id
41
+ env["jwt.expire_at"] = jwt.expire_at
41
42
  end
42
43
  end
43
44
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  # rubocop:disable Style/ClassVars
4
5
  # Class var repo is needed here in order to share data between the 2 child classes.