shopify_app 21.1.1 → 21.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +7 -8
  3. data/.github/workflows/stale.yml +1 -0
  4. data/.spin/rails/prepare-application +8 -0
  5. data/CHANGELOG.md +19 -8
  6. data/Gemfile +1 -0
  7. data/Gemfile.lock +106 -91
  8. data/README.md +19 -15
  9. data/SECURITY.md +1 -1
  10. data/app/controllers/concerns/shopify_app/authenticated.rb +4 -9
  11. data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +3 -2
  12. data/app/controllers/concerns/shopify_app/ensure_has_session.rb +19 -0
  13. data/app/controllers/concerns/shopify_app/ensure_installed.rb +62 -0
  14. data/app/controllers/concerns/shopify_app/require_known_shop.rb +3 -38
  15. data/app/controllers/shopify_app/authenticated_controller.rb +1 -1
  16. data/app/controllers/shopify_app/callback_controller.rb +64 -27
  17. data/app/controllers/shopify_app/extension_verification_controller.rb +4 -1
  18. data/app/controllers/shopify_app/sessions_controller.rb +11 -2
  19. data/config/locales/ja.yml +1 -1
  20. data/docs/Troubleshooting.md +38 -2
  21. data/docs/Upgrading.md +40 -32
  22. data/docs/shopify_app/controller-concerns.md +48 -0
  23. data/docs/shopify_app/logging.md +21 -0
  24. data/docs/shopify_app/webhooks.md +13 -0
  25. data/lib/generators/shopify_app/add_app_uninstalled_job/add_app_uninstalled_job_generator.rb +15 -0
  26. data/lib/generators/shopify_app/add_app_uninstalled_job/templates/app_uninstalled_job.rb.tt +22 -0
  27. data/lib/generators/shopify_app/add_gdpr_jobs/add_gdpr_jobs_generator.rb +23 -0
  28. data/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_data_request_job.rb.tt +22 -0
  29. data/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_redact_job.rb.tt +22 -0
  30. data/lib/generators/shopify_app/add_gdpr_jobs/templates/shop_redact_job.rb.tt +22 -0
  31. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +1 -0
  32. data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +2 -1
  33. data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +2 -1
  34. data/lib/generators/shopify_app/authenticated_controller/templates/authenticated_controller.rb +1 -1
  35. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +1 -1
  36. data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +1 -1
  37. data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +8 -2
  38. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +1 -1
  39. data/lib/generators/shopify_app/shopify_app_generator.rb +2 -0
  40. data/lib/shopify_app/access_scopes/noop_strategy.rb +4 -0
  41. data/lib/shopify_app/access_scopes/user_strategy.rb +5 -0
  42. data/lib/shopify_app/configuration.rb +11 -0
  43. data/lib/shopify_app/controller_concerns/ensure_billing.rb +3 -0
  44. data/lib/shopify_app/controller_concerns/itp.rb +5 -0
  45. data/lib/shopify_app/controller_concerns/login_protection.rb +56 -13
  46. data/lib/shopify_app/controller_concerns/redirect_for_embedded.rb +4 -1
  47. data/lib/shopify_app/controller_concerns/webhook_verification.rb +4 -1
  48. data/lib/shopify_app/logger.rb +28 -0
  49. data/lib/shopify_app/managers/scripttags_manager.rb +1 -0
  50. data/lib/shopify_app/managers/webhooks_manager.rb +6 -0
  51. data/lib/shopify_app/session/jwt.rb +1 -1
  52. data/lib/shopify_app/session/session_repository.rb +15 -4
  53. data/lib/shopify_app/version.rb +1 -1
  54. data/lib/shopify_app.rb +2 -0
  55. data/shopify_app.gemspec +2 -1
  56. data/yarn.lock +5 -5
  57. metadata +30 -4
@@ -0,0 +1,22 @@
1
+ class CustomersDataRequestJob < ActiveJob::Base
2
+ extend ShopifyAPI::Webhooks::Handler
3
+
4
+ class << self
5
+ def handle(topic:, shop:, body:)
6
+ perform_later(topic: topic, shop_domain: shop, webhook: body)
7
+ end
8
+ end
9
+
10
+ def perform(topic:, shop_domain:, webhook:)
11
+ shop = Shop.find_by(shopify_domain: shop_domain)
12
+
13
+ if shop.nil?
14
+ logger.error("#{self.class} failed: cannot find shop with domain '#{shop_domain}'")
15
+
16
+ raise ActiveRecord::RecordNotFound, "Shop Not Found"
17
+ end
18
+
19
+ shop.with_shopify_session do
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ class CustomersRedactJob < ActiveJob::Base
2
+ extend ShopifyAPI::Webhooks::Handler
3
+
4
+ class << self
5
+ def handle(topic:, shop:, body:)
6
+ perform_later(topic: topic, shop_domain: shop, webhook: body)
7
+ end
8
+ end
9
+
10
+ def perform(topic:, shop_domain:, webhook:)
11
+ shop = Shop.find_by(shopify_domain: shop_domain)
12
+
13
+ if shop.nil?
14
+ logger.error("#{self.class} failed: cannot find shop with domain '#{shop_domain}'")
15
+
16
+ raise ActiveRecord::RecordNotFound, "Shop Not Found"
17
+ end
18
+
19
+ shop.with_shopify_session do
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ class ShopRedactJob < ActiveJob::Base
2
+ extend ShopifyAPI::Webhooks::Handler
3
+
4
+ class << self
5
+ def handle(topic:, shop:, body:)
6
+ perform_later(topic: topic, shop_domain: shop, webhook: body)
7
+ end
8
+ end
9
+
10
+ def perform(topic:, shop_domain:, webhook:)
11
+ shop = Shop.find_by(shopify_domain: shop_domain)
12
+
13
+ if shop.nil?
14
+ logger.error("#{self.class} failed: cannot find shop with domain '#{shop_domain}'")
15
+
16
+ raise ActiveRecord::RecordNotFound, "Shop Not Found"
17
+ end
18
+
19
+ shop.with_shopify_session do
20
+ end
21
+ end
22
+ end
@@ -8,6 +8,7 @@ module ShopifyApp
8
8
  source_root File.expand_path("../templates", __FILE__)
9
9
 
10
10
  def generate_app_extension
11
+ ShopifyApp::Logger.deprecated("MarketingActivitiesController will be removed in an upcoming version", "22.0.0")
11
12
  template("marketing_activities_controller.rb", "app/controllers/marketing_activities_controller.rb")
12
13
  generate_routes
13
14
  end
@@ -55,7 +55,8 @@ class MarketingActivitiesController < ShopifyApp::ExtensionVerificationControlle
55
55
  request_id = params[:request_id]
56
56
  message = params[:message]
57
57
 
58
- Rails.logger.info("[Marketing Activity App Error Feedback] Request id: #{request_id}, message: #{message}")
58
+ ShopifyApp::Logger.info("[Marketing Activity App Error Feedback]"\
59
+ "Request id: #{request_id}, message: #{message}")
59
60
 
60
61
  render(json: {}, status: :ok)
61
62
  end
@@ -12,7 +12,8 @@ class <%= @job_class_name %> < ActiveJob::Base
12
12
 
13
13
  if shop.nil?
14
14
  logger.error("#{self.class} failed: cannot find shop with domain '#{shop_domain}'")
15
- return
15
+
16
+ raise ActiveRecord::RecordNotFound, "Shop Not Found"
16
17
  end
17
18
 
18
19
  shop.with_shopify_session do
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class AuthenticatedController < ApplicationController
4
- include ShopifyApp::Authenticated
4
+ include ShopifyApp::EnsureHasSession
5
5
  end
@@ -24,7 +24,7 @@
24
24
  });
25
25
 
26
26
  var fetchProducts = function() {
27
- var headers = new Headers({ "Authorization": "Bearer " + window.sessionToken });
27
+ var headers = new Headers({ "Content-Type": "text/javascript", "Authorization": "Bearer " + window.sessionToken });
28
28
  return fetch("/products", { headers })
29
29
  .then(response => response.json())
30
30
  .then(data => {
@@ -2,7 +2,7 @@
2
2
 
3
3
  class HomeController < ApplicationController
4
4
  include ShopifyApp::EmbeddedApp
5
- include ShopifyApp::RequireKnownShop
5
+ include ShopifyApp::EnsureInstalled
6
6
  include ShopifyApp::ShopAccessScopesVerification
7
7
 
8
8
  def index
@@ -7,8 +7,14 @@ ShopifyApp.configure do |config|
7
7
  config.after_authenticate_job = false
8
8
  config.api_version = "<%= @api_version %>"
9
9
  config.shop_session_repository = 'Shop'
10
-
10
+ config.log_level = :info
11
11
  config.reauth_on_access_scope_changes = true
12
+ config.webhooks = [
13
+ { topic: "app/uninstalled", address: "webhooks/app_uninstalled"},
14
+ { topic: "customers/data_request", address: "webhooks/customers_data_request" },
15
+ { topic: "customer/redact", address: "webhooks/customers_redact"},
16
+ { topic: "shop/redact", address: "webhooks/shop_redact"}
17
+ ]
12
18
 
13
19
  config.api_key = ENV.fetch('SHOPIFY_API_KEY', '').presence
14
20
  config.secret = ENV.fetch('SHOPIFY_API_SECRET', '').presence
@@ -42,7 +48,7 @@ Rails.application.config.after_initialize do
42
48
  scope: ShopifyApp.configuration.scope,
43
49
  is_private: !ENV.fetch('SHOPIFY_APP_PRIVATE_SHOP', '').empty?,
44
50
  is_embedded: ShopifyApp.configuration.embedded_app,
45
- session_storage: ShopifyApp::SessionRepository,
51
+ log_level: :info,
46
52
  logger: Rails.logger,
47
53
  private_shop: ENV.fetch('SHOPIFY_APP_PRIVATE_SHOP', nil),
48
54
  user_agent_prefix: "ShopifyApp/#{ShopifyApp::VERSION}"
@@ -27,7 +27,7 @@ module Shopify
27
27
  private
28
28
 
29
29
  def log_error(message)
30
- Rails.logger.error(message)
30
+ ShopifyApp::Logger.error(message)
31
31
  end
32
32
 
33
33
  def no_access_token_error_message
@@ -9,6 +9,8 @@ module ShopifyApp
9
9
  end
10
10
 
11
11
  def run_all_generators
12
+ generate("shopify_app:add_app_uninstalled_job")
13
+ generate("shopify_app:add_gdpr_jobs")
12
14
  generate("shopify_app:install #{@opts.join(" ")}")
13
15
  generate("shopify_app:shop_model #{@opts.join(" ")}")
14
16
  generate("shopify_app:authenticated_controller")
@@ -7,6 +7,10 @@ module ShopifyApp
7
7
  def update_access_scopes?(*_args)
8
8
  false
9
9
  end
10
+
11
+ def covers_scopes?(*_args)
12
+ true
13
+ end
10
14
  end
11
15
  end
12
16
  end
@@ -12,6 +12,11 @@ module ShopifyApp
12
12
  "#update_access_scopes? requires user_id or shopify_user_id parameter inputs")
13
13
  end
14
14
 
15
+ def covers_scopes?(current_shopify_session)
16
+ # NOTE: this not Ruby's `covers?` method, it is defined in ShopifyAPI::Auth::AuthScopes
17
+ current_shopify_session.scope.to_a.empty? || current_shopify_session.scope.covers?(ShopifyAPI::Context.scope)
18
+ end
19
+
15
20
  private
16
21
 
17
22
  def update_access_scopes_for_user_id?(user_id)
@@ -20,6 +20,7 @@ module ShopifyApp
20
20
  attr_accessor :api_version
21
21
 
22
22
  attr_accessor :reauth_on_access_scope_changes
23
+ attr_accessor :log_level
23
24
 
24
25
  # customise urls
25
26
  attr_accessor :root_url
@@ -82,7 +83,17 @@ module ShopifyApp
82
83
  ShopifyApp::AccessScopes::ShopStrategy
83
84
  end
84
85
 
86
+ def user_access_scopes_strategy=(class_name)
87
+ unless class_name.is_a?(String)
88
+ raise ConfigurationError, "Invalid user access scopes strategy - expected a string"
89
+ end
90
+
91
+ @user_access_scopes_strategy = class_name.safe_constantize
92
+ end
93
+
85
94
  def user_access_scopes_strategy
95
+ return @user_access_scopes_strategy if @user_access_scopes_strategy
96
+
86
97
  return ShopifyApp::AccessScopes::NoopStrategy unless reauth_on_access_scope_changes
87
98
 
88
99
  ShopifyApp::AccessScopes::UserStrategy
@@ -28,6 +28,7 @@ module ShopifyApp
28
28
  unless has_payment
29
29
  if request.xhr?
30
30
  add_top_level_redirection_headers(url: confirmation_url, ignore_response_code: true)
31
+ ShopifyApp::Logger.debug("Responding with 401 unauthorized")
31
32
  head(:unauthorized)
32
33
  else
33
34
  redirect_to(confirmation_url, allow_other_host: true)
@@ -55,6 +56,7 @@ module ShopifyApp
55
56
  end
56
57
 
57
58
  def has_subscription?(session)
59
+ ShopifyApp::Logger.debug("Checking if shop has subscription")
58
60
  response = run_query(session: session, query: RECURRING_PURCHASES_QUERY)
59
61
  subscriptions = response.body["data"]["currentAppInstallation"]["activeSubscriptions"]
60
62
 
@@ -70,6 +72,7 @@ module ShopifyApp
70
72
  end
71
73
 
72
74
  def has_one_time_payment?(session)
75
+ ShopifyApp::Logger.debug("Checking if has one time payment")
73
76
  purchases = nil
74
77
  end_cursor = nil
75
78
 
@@ -3,6 +3,11 @@
3
3
  module ShopifyApp
4
4
  # Cookie management helpers required for ITP implementation
5
5
  module Itp
6
+ extend ActiveSupport::Concern
7
+ included do
8
+ ShopifyApp::Logger.deprecated("Itp will be removed in an upcoming version", "22.0.0")
9
+ end
10
+
6
11
  private
7
12
 
8
13
  def set_test_cookie
@@ -5,11 +5,17 @@ require "browser_sniffer"
5
5
  module ShopifyApp
6
6
  module LoginProtection
7
7
  extend ActiveSupport::Concern
8
- include ShopifyApp::Itp
9
8
  include ShopifyApp::SanitizedParams
10
9
 
11
10
  included do
12
- after_action :set_test_cookie
11
+ if ancestors.include?(ShopifyApp::RequireKnownShop || ShopifyApp::EnsureInstalled)
12
+ message = <<~EOS
13
+ We detected the use of incompatible concerns (RequireKnownShop/EnsureInstalled and LoginProtection) in #{name},
14
+ which may lead to unpredictable behavior. In a future release of this library this will raise an error.
15
+ EOS
16
+ ShopifyApp::Logger.deprecated(message, "22.0.0")
17
+ end
18
+
13
19
  rescue_from ShopifyAPI::Errors::HttpResponseError, with: :handle_http_error
14
20
  end
15
21
 
@@ -18,20 +24,21 @@ module ShopifyApp
18
24
  def activate_shopify_session
19
25
  if current_shopify_session.blank?
20
26
  signal_access_token_required
27
+ ShopifyApp::Logger.debug("No session found, redirecting to login")
21
28
  return redirect_to_login
22
29
  end
23
30
 
24
- unless current_shopify_session.scope.to_a.empty? ||
25
- current_shopify_session.scope.covers?(ShopifyAPI::Context.scope)
26
-
31
+ unless ShopifyApp.configuration.user_access_scopes_strategy.covers_scopes?(current_shopify_session)
27
32
  clear_shopify_session
28
33
  return redirect_to_login
29
34
  end
30
35
 
31
36
  begin
37
+ ShopifyApp::Logger.debug("Activating Shopify session")
32
38
  ShopifyAPI::Context.activate_session(current_shopify_session)
33
39
  yield
34
40
  ensure
41
+ ShopifyApp::Logger.debug("Deactivating session")
35
42
  ShopifyAPI::Context.deactivate_session
36
43
  end
37
44
  end
@@ -39,14 +46,16 @@ module ShopifyApp
39
46
  def current_shopify_session
40
47
  @current_shopify_session ||= begin
41
48
  cookie_name = ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME
42
- ShopifyAPI::Utils::SessionUtils.load_current_session(
49
+ load_current_session(
43
50
  auth_header: request.headers["HTTP_AUTHORIZATION"],
44
51
  cookies: { cookie_name => cookies.encrypted[cookie_name] },
45
- is_online: user_session_expected?,
52
+ is_online: online_token_configured?,
46
53
  )
47
54
  rescue ShopifyAPI::Errors::CookieNotFoundError
55
+ ShopifyApp::Logger.warn("No cookies have been found - cookie name: #{cookie_name}")
48
56
  nil
49
57
  rescue ShopifyAPI::Errors::InvalidJwtTokenError
58
+ ShopifyApp::Logger.warn("Invalid JWT token for current Shopify session")
50
59
  nil
51
60
  end
52
61
  end
@@ -54,6 +63,7 @@ module ShopifyApp
54
63
  def login_again_if_different_user_or_shop
55
64
  return unless session_id_conflicts_with_params || session_shop_conflicts_with_params
56
65
 
66
+ ShopifyApp::Logger.debug("Clearing session and redirecting to login")
57
67
  clear_shopify_session
58
68
  redirect_to_login
59
69
  end
@@ -71,10 +81,13 @@ module ShopifyApp
71
81
 
72
82
  def add_top_level_redirection_headers(url: nil, ignore_response_code: false)
73
83
  if request.xhr? && (ignore_response_code || response.code.to_i == 401)
84
+ ShopifyApp::Logger.debug("Adding top level redirection headers")
74
85
  # Make sure the shop is set in the redirection URL
75
86
  unless params[:shop]
87
+ ShopifyApp::Logger.debug("Setting current shop session")
76
88
  params[:shop] = if current_shopify_session
77
89
  current_shopify_session.shop
90
+
78
91
  elsif (matches = request.headers["HTTP_AUTHORIZATION"]&.match(/^Bearer (.+)$/))
79
92
  jwt_payload = ShopifyAPI::Auth::JwtPayload.new(T.must(matches[1]))
80
93
  jwt_payload.shop
@@ -83,6 +96,7 @@ module ShopifyApp
83
96
 
84
97
  url ||= login_url_with_optional_shop
85
98
 
99
+ ShopifyApp::Logger.debug("Setting Reauthorize-Url to #{url}")
86
100
  response.set_header("X-Shopify-API-Request-Failure-Reauthorize", "1")
87
101
  response.set_header("X-Shopify-API-Request-Failure-Reauthorize-Url", url)
88
102
  end
@@ -103,8 +117,9 @@ module ShopifyApp
103
117
  end
104
118
 
105
119
  def redirect_to_login
106
- if request.xhr?
120
+ if requested_by_javascript?
107
121
  add_top_level_redirection_headers(ignore_response_code: true)
122
+ ShopifyApp::Logger.debug("Login redirect request is a XHR")
108
123
  head(:unauthorized)
109
124
  else
110
125
  if request.get?
@@ -117,12 +132,15 @@ module ShopifyApp
117
132
  query = query.merge(sanitized_params).to_query
118
133
  end
119
134
  session[:return_to] = query.blank? ? path.to_s : "#{path}?#{query}"
135
+ ShopifyApp::Logger.debug("Redirecting to #{login_url_with_optional_shop}")
120
136
  redirect_to(login_url_with_optional_shop)
121
137
  end
122
138
  end
123
139
 
124
140
  def close_session
125
141
  clear_shopify_session
142
+ ShopifyApp::Logger.debug("Closing session")
143
+ ShopifyApp::Logger.debug("Redirecting to #{login_url_with_optional_shop}")
126
144
  redirect_to(login_url_with_optional_shop)
127
145
  end
128
146
 
@@ -167,6 +185,10 @@ module ShopifyApp
167
185
  query_params[:host] ||= host
168
186
  end
169
187
 
188
+ if params[:access_scopes].present?
189
+ query_params[:scope] = params[:access_scopes].join(",")
190
+ end
191
+
170
192
  query_params[:top_level] = true if top_level
171
193
  query_params
172
194
  end
@@ -178,6 +200,8 @@ module ShopifyApp
178
200
 
179
201
  def fullpage_redirect_to(url)
180
202
  if ShopifyApp.configuration.embedded_app?
203
+ raise ::ShopifyApp::ShopifyDomainNotFound if current_shopify_domain.nil?
204
+
181
205
  render("shopify_app/shared/redirect", layout: false,
182
206
  locals: { url: url, current_shopify_domain: current_shopify_domain })
183
207
  else
@@ -187,13 +211,13 @@ module ShopifyApp
187
211
 
188
212
  def current_shopify_domain
189
213
  shopify_domain = sanitized_shop_name || current_shopify_session&.shop
190
-
191
- return shopify_domain if shopify_domain.present?
192
-
193
- raise ::ShopifyApp::ShopifyDomainNotFound
214
+ ShopifyApp::Logger.info("Installed store - #{shopify_domain} deduced from user session")
215
+ shopify_domain
194
216
  end
195
217
 
196
218
  def return_address
219
+ return base_return_address if current_shopify_domain.nil?
220
+
197
221
  return_address_with_params(shop: current_shopify_domain, host: host)
198
222
  rescue ::ShopifyApp::ShopifyDomainNotFound, ::ShopifyApp::ShopifyHostNotFound
199
223
  base_return_address
@@ -228,11 +252,30 @@ module ShopifyApp
228
252
  ShopifyApp::SessionRepository.retrieve_shop_session_by_shopify_domain(sanitize_shop_param(params))
229
253
  end
230
254
 
255
+ def online_token_configured?
256
+ !ShopifyApp.configuration.user_session_repository.blank? && ShopifyApp::SessionRepository.user_storage.present?
257
+ end
258
+
231
259
  def user_session_expected?
232
260
  return false if shop_session.nil?
233
261
  return false if ShopifyApp.configuration.shop_access_scopes_strategy.update_access_scopes?(shop_session.shop)
234
262
 
235
- !ShopifyApp.configuration.user_session_repository.blank? && ShopifyApp::SessionRepository.user_storage.present?
263
+ online_token_configured?
264
+ end
265
+
266
+ def load_current_session(auth_header: nil, cookies: nil, is_online: false)
267
+ return ShopifyAPI::Context.load_private_session if ShopifyAPI::Context.private?
268
+
269
+ session_id = ShopifyAPI::Utils::SessionUtils.current_session_id(auth_header, cookies, is_online)
270
+ return nil unless session_id
271
+
272
+ ShopifyApp::SessionRepository.load_session(session_id)
273
+ end
274
+
275
+ def requested_by_javascript?
276
+ request.xhr? ||
277
+ request.content_type == "text/javascript" ||
278
+ request.content_type == "application/javascript"
236
279
  end
237
280
  end
238
281
  end
@@ -16,7 +16,10 @@ module ShopifyApp
16
16
 
17
17
  def redirect_for_embedded
18
18
  # Don't actually redirect if we're already in the redirect route - we want the request to reach the FE
19
- redirect_to(redirect_uri_for_embedded) unless request.path == ShopifyApp.configuration.embedded_redirect_url
19
+ unless request.path == ShopifyApp.configuration.embedded_redirect_url
20
+ ShopifyApp::Logger.debug("Redirecting to #{redirect_uri_for_embedded}")
21
+ redirect_to(redirect_uri_for_embedded)
22
+ end
20
23
  end
21
24
 
22
25
  def redirect_uri_for_embedded
@@ -14,7 +14,10 @@ module ShopifyApp
14
14
 
15
15
  def verify_request
16
16
  data = request.raw_post
17
- return head(:unauthorized) unless hmac_valid?(data)
17
+ unless hmac_valid?(data)
18
+ ShopifyApp::Logger.debug("Webhook verification failed - HMAC invalid")
19
+ head(:unauthorized)
20
+ end
18
21
  end
19
22
 
20
23
  def shop_domain
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyApp
4
+ class Logger < ShopifyAPI::Logger
5
+ class << self
6
+ def deprecated(message, version)
7
+ return unless enabled_for_log_level?(:warn)
8
+
9
+ raise ShopifyAPI::Errors::FeatureDeprecatedError unless valid_version(version)
10
+
11
+ ActiveSupport::Deprecation.warn("[#{version}] #{context(:warn)} #{message}")
12
+ end
13
+
14
+ private
15
+
16
+ def context(log_level)
17
+ current_shop = ShopifyAPI::Context.active_session&.shop || "Shop Not Found"
18
+ "[ ShopifyApp | #{log_level.to_s.upcase} | #{current_shop} ]"
19
+ end
20
+
21
+ def valid_version(version)
22
+ current_version = Gem::Version.create(ShopifyApp::VERSION)
23
+ deprecate_version = Gem::Version.create(version)
24
+ current_version < deprecate_version
25
+ end
26
+ end
27
+ end
28
+ end
@@ -24,6 +24,7 @@ module ShopifyApp
24
24
  attr_reader :required_scripttags, :shop_domain
25
25
 
26
26
  def initialize(scripttags, shop_domain)
27
+ ShopifyApp::Logger.deprecated("The ScripttagsManager will become deprecated in an upcoming version", "22.0.0")
27
28
  @required_scripttags = scripttags
28
29
  @shop_domain = shop_domain
29
30
  end
@@ -15,6 +15,8 @@ module ShopifyApp
15
15
  def create_webhooks(session:)
16
16
  return unless ShopifyApp.configuration.has_webhooks?
17
17
 
18
+ ShopifyApp::Logger.debug("Creating webhooks #{ShopifyApp.configuration.webhooks}")
19
+
18
20
  ShopifyAPI::Webhooks::Registry.register_all(session: session)
19
21
  end
20
22
 
@@ -23,12 +25,15 @@ module ShopifyApp
23
25
  return unless ShopifyApp.configuration.has_webhooks?
24
26
 
25
27
  add_registrations
28
+
29
+ ShopifyApp::Logger.debug("Recreating webhooks")
26
30
  ShopifyAPI::Webhooks::Registry.register_all(session: session)
27
31
  end
28
32
 
29
33
  def destroy_webhooks
30
34
  return unless ShopifyApp.configuration.has_webhooks?
31
35
 
36
+ ShopifyApp::Logger.debug("Destroying webhooks")
32
37
  ShopifyApp.configuration.webhooks.each do |attributes|
33
38
  ShopifyAPI::Webhooks::Registry.unregister(topic: attributes[:topic])
34
39
  end
@@ -37,6 +42,7 @@ module ShopifyApp
37
42
  def add_registrations
38
43
  return unless ShopifyApp.configuration.has_webhooks?
39
44
 
45
+ ShopifyApp::Logger.debug("Adding registrations to webhooks")
40
46
  ShopifyApp.configuration.webhooks.each do |attributes|
41
47
  webhook_path = path(attributes)
42
48
 
@@ -35,7 +35,7 @@ module ShopifyApp
35
35
  payload, _ = parse_token_data(ShopifyApp.configuration&.secret, ShopifyApp.configuration&.old_secret)
36
36
  @payload = validate_payload(payload)
37
37
  rescue *WARN_EXCEPTIONS => error
38
- Rails.logger.warn("[ShopifyApp::JWT] Failed to validate JWT: [#{error.class}] #{error}")
38
+ ShopifyApp::Logger.warn("Failed to validate JWT: [#{error.class}] #{error}")
39
39
  nil
40
40
  end
41
41
 
@@ -45,8 +45,11 @@ module ShopifyApp
45
45
  # ShopifyAPI::Auth::SessionStorage override
46
46
  def store_session(session)
47
47
  if session.online?
48
- user_storage.store(session, session.associated_user)
48
+ user = session.associated_user
49
+ ShopifyApp::Logger.debug("Storing online user session - session: #{session.id}")
50
+ user_storage.store(session, user)
49
51
  else
52
+ ShopifyApp::Logger.debug("Storing offline store session - session: #{session.id}")
50
53
  shop_storage.store(session)
51
54
  end
52
55
  end
@@ -55,9 +58,13 @@ module ShopifyApp
55
58
  def load_session(id)
56
59
  match = id.match(/^offline_(.*)/)
57
60
  if match
58
- retrieve_shop_session_by_shopify_domain(match[1])
61
+ domain = match[1]
62
+ ShopifyApp::Logger.debug("Loading session by domain - domain: #{domain}")
63
+ retrieve_shop_session_by_shopify_domain(domain)
59
64
  else
60
- retrieve_user_session_by_shopify_user_id(id.split("_").last)
65
+ user = id.split("_").last
66
+ ShopifyApp::Logger.debug("Loading session by user_id - user: #{user}")
67
+ retrieve_user_session_by_shopify_user_id(user)
61
68
  end
62
69
  end
63
70
 
@@ -66,9 +73,13 @@ module ShopifyApp
66
73
  match = id.match(/^offline_(.*)/)
67
74
 
68
75
  record = if match
76
+ domain = match[1]
77
+ ShopifyApp::Logger.debug("Destroying session by domain - domain: #{domain}")
69
78
  Shop.find_by(shopify_domain: match[1])
70
79
  else
71
- User.find_by(shopify_user_id: id.split("_").last)
80
+ shopify_user_id = id.split("_").last
81
+ ShopifyApp::Logger.debug("Destroying session by user - user_id: #{shopify_user_id}")
82
+ User.find_by(shopify_user_id: shopify_user_id)
72
83
  end
73
84
 
74
85
  record.destroy
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShopifyApp
4
- VERSION = "21.1.1"
4
+ VERSION = "21.3.0"
5
5
  end
data/lib/shopify_app.rb CHANGED
@@ -37,6 +37,8 @@ module ShopifyApp
37
37
  # errors
38
38
  require "shopify_app/errors"
39
39
 
40
+ require "shopify_app/logger"
41
+
40
42
  # controller concerns
41
43
  require "shopify_app/controller_concerns/csrf_protection"
42
44
  require "shopify_app/controller_concerns/localization"
data/shopify_app.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.add_runtime_dependency("jwt", ">= 2.2.3")
20
20
  s.add_runtime_dependency("rails", "> 5.2.1")
21
21
  s.add_runtime_dependency("redirect_safely", "~> 1.0")
22
- s.add_runtime_dependency("shopify_api", "~> 12.2")
22
+ s.add_runtime_dependency("shopify_api", "~> 12.3")
23
23
  s.add_runtime_dependency("sprockets-rails", ">= 2.0.0")
24
24
 
25
25
  s.add_development_dependency("byebug")
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
30
30
  s.add_development_dependency("pry-stack_explorer")
31
31
  s.add_development_dependency("rake")
32
32
  s.add_development_dependency("rb-readline")
33
+ s.add_development_dependency("ruby-lsp")
33
34
  s.add_development_dependency("sqlite3", "~> 1.4")
34
35
  s.add_development_dependency("webmock")
35
36