shopify_app 21.1.1 → 21.3.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.
- checksums.yaml +4 -4
- data/.github/workflows/build.yml +7 -8
- data/.github/workflows/stale.yml +1 -0
- data/.spin/rails/prepare-application +8 -0
- data/CHANGELOG.md +19 -8
- data/Gemfile +1 -0
- data/Gemfile.lock +106 -91
- data/README.md +19 -15
- data/SECURITY.md +1 -1
- data/app/controllers/concerns/shopify_app/authenticated.rb +4 -9
- data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +3 -2
- data/app/controllers/concerns/shopify_app/ensure_has_session.rb +19 -0
- data/app/controllers/concerns/shopify_app/ensure_installed.rb +62 -0
- data/app/controllers/concerns/shopify_app/require_known_shop.rb +3 -38
- data/app/controllers/shopify_app/authenticated_controller.rb +1 -1
- data/app/controllers/shopify_app/callback_controller.rb +64 -27
- data/app/controllers/shopify_app/extension_verification_controller.rb +4 -1
- data/app/controllers/shopify_app/sessions_controller.rb +11 -2
- data/config/locales/ja.yml +1 -1
- data/docs/Troubleshooting.md +38 -2
- data/docs/Upgrading.md +40 -32
- data/docs/shopify_app/controller-concerns.md +48 -0
- data/docs/shopify_app/logging.md +21 -0
- data/docs/shopify_app/webhooks.md +13 -0
- data/lib/generators/shopify_app/add_app_uninstalled_job/add_app_uninstalled_job_generator.rb +15 -0
- data/lib/generators/shopify_app/add_app_uninstalled_job/templates/app_uninstalled_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_gdpr_jobs/add_gdpr_jobs_generator.rb +23 -0
- data/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_data_request_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_gdpr_jobs/templates/customers_redact_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_gdpr_jobs/templates/shop_redact_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +1 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +2 -1
- data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +2 -1
- data/lib/generators/shopify_app/authenticated_controller/templates/authenticated_controller.rb +1 -1
- data/lib/generators/shopify_app/home_controller/templates/index.html.erb +1 -1
- data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +1 -1
- data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +8 -2
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +1 -1
- data/lib/generators/shopify_app/shopify_app_generator.rb +2 -0
- data/lib/shopify_app/access_scopes/noop_strategy.rb +4 -0
- data/lib/shopify_app/access_scopes/user_strategy.rb +5 -0
- data/lib/shopify_app/configuration.rb +11 -0
- data/lib/shopify_app/controller_concerns/ensure_billing.rb +3 -0
- data/lib/shopify_app/controller_concerns/itp.rb +5 -0
- data/lib/shopify_app/controller_concerns/login_protection.rb +56 -13
- data/lib/shopify_app/controller_concerns/redirect_for_embedded.rb +4 -1
- data/lib/shopify_app/controller_concerns/webhook_verification.rb +4 -1
- data/lib/shopify_app/logger.rb +28 -0
- data/lib/shopify_app/managers/scripttags_manager.rb +1 -0
- data/lib/shopify_app/managers/webhooks_manager.rb +6 -0
- data/lib/shopify_app/session/jwt.rb +1 -1
- data/lib/shopify_app/session/session_repository.rb +15 -4
- data/lib/shopify_app/version.rb +1 -1
- data/lib/shopify_app.rb +2 -0
- data/shopify_app.gemspec +2 -1
- data/yarn.lock +5 -5
- 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
|
-
|
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
|
-
|
15
|
+
|
16
|
+
raise ActiveRecord::RecordNotFound, "Shop Not Found"
|
16
17
|
end
|
17
18
|
|
18
19
|
shop.with_shopify_session do
|
@@ -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 => {
|
@@ -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
|
-
|
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}"
|
@@ -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")
|
@@ -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
|
-
|
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
|
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
|
-
|
49
|
+
load_current_session(
|
43
50
|
auth_header: request.headers["HTTP_AUTHORIZATION"],
|
44
51
|
cookies: { cookie_name => cookies.encrypted[cookie_name] },
|
45
|
-
is_online:
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/shopify_app/version.rb
CHANGED
data/lib/shopify_app.rb
CHANGED
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.
|
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
|
|