shopify_app 18.1.3 → 19.0.0

Sign up to get free protection for your applications and to get access to all the features.
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.