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.
- checksums.yaml +4 -4
- data/.github/workflows/build.yml +2 -2
- data/.gitignore +1 -0
- data/CHANGELOG.md +3 -2
- data/Gemfile +3 -2
- data/Gemfile.lock +122 -136
- data/Rakefile +4 -3
- data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +1 -1
- data/app/controllers/shopify_app/authenticated_controller.rb +1 -0
- data/app/controllers/shopify_app/callback_controller.rb +35 -147
- data/app/controllers/shopify_app/sessions_controller.rb +25 -137
- data/app/controllers/shopify_app/webhooks_controller.rb +5 -23
- data/config/routes.rb +6 -12
- data/docs/Troubleshooting.md +0 -3
- data/docs/Upgrading.md +85 -2
- data/docs/shopify_app/webhooks.md +1 -1
- data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +10 -9
- data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +1 -0
- data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +4 -3
- data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +13 -12
- data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +9 -1
- data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +7 -6
- data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +2 -1
- data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +1 -1
- data/lib/generators/shopify_app/authenticated_controller/authenticated_controller_generator.rb +3 -3
- data/lib/generators/shopify_app/controllers/controllers_generator.rb +4 -3
- data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +11 -15
- data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +2 -2
- data/lib/generators/shopify_app/home_controller/templates/index.html.erb +3 -3
- data/lib/generators/shopify_app/install/install_generator.rb +25 -74
- data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +1 -1
- data/lib/generators/shopify_app/install/templates/session_store.rb +2 -1
- data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +20 -5
- data/lib/generators/shopify_app/products_controller/products_controller_generator.rb +3 -3
- data/lib/generators/shopify_app/products_controller/templates/products_controller.rb +1 -1
- data/lib/generators/shopify_app/rotate_shopify_token_job/rotate_shopify_token_job_generator.rb +4 -4
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +1 -0
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +1 -1
- data/lib/generators/shopify_app/routes/routes_generator.rb +6 -5
- data/lib/generators/shopify_app/routes/templates/routes.rb +5 -5
- data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +11 -10
- data/lib/generators/shopify_app/shop_model/templates/shop.rb +1 -0
- data/lib/generators/shopify_app/shopify_app_generator.rb +4 -3
- data/lib/generators/shopify_app/user_model/templates/user.rb +1 -0
- data/lib/generators/shopify_app/user_model/user_model_generator.rb +11 -10
- data/lib/generators/shopify_app/views/views_generator.rb +4 -3
- data/lib/shopify_app/access_scopes/shop_strategy.rb +2 -2
- data/lib/shopify_app/access_scopes/user_strategy.rb +4 -4
- data/lib/shopify_app/configuration.rb +5 -17
- data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +4 -3
- data/lib/shopify_app/controller_concerns/csrf_protection.rb +2 -1
- data/lib/shopify_app/controller_concerns/embedded_app.rb +4 -3
- data/lib/shopify_app/controller_concerns/itp.rb +3 -3
- data/lib/shopify_app/controller_concerns/localization.rb +1 -0
- data/lib/shopify_app/controller_concerns/login_protection.rb +50 -70
- data/lib/shopify_app/controller_concerns/payload_verification.rb +3 -2
- data/lib/shopify_app/controller_concerns/webhook_verification.rb +2 -1
- data/lib/shopify_app/engine.rb +7 -15
- data/lib/shopify_app/jobs/scripttags_manager_job.rb +2 -2
- data/lib/shopify_app/jobs/webhooks_manager_job.rb +4 -5
- data/lib/shopify_app/managers/scripttags_manager.rb +11 -4
- data/lib/shopify_app/managers/webhooks_manager.rb +42 -44
- data/lib/shopify_app/middleware/jwt_middleware.rb +5 -4
- data/lib/shopify_app/session/in_memory_session_store.rb +1 -0
- data/lib/shopify_app/session/in_memory_shop_session_store.rb +2 -1
- data/lib/shopify_app/session/in_memory_user_session_store.rb +1 -0
- data/lib/shopify_app/session/jwt.rb +9 -8
- data/lib/shopify_app/session/null_user_session_store.rb +2 -1
- data/lib/shopify_app/session/session_repository.rb +37 -0
- data/lib/shopify_app/session/session_storage.rb +4 -6
- data/lib/shopify_app/session/shop_session_storage.rb +6 -6
- data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +7 -8
- data/lib/shopify_app/session/user_session_storage.rb +19 -6
- data/lib/shopify_app/session/user_session_storage_with_scopes.rb +21 -8
- data/lib/shopify_app/test_helpers/all.rb +2 -1
- data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +4 -3
- data/lib/shopify_app/utils.rb +2 -9
- data/lib/shopify_app/version.rb +2 -1
- data/lib/shopify_app.rb +35 -40
- data/package.json +1 -1
- data/shopify_app.gemspec +21 -20
- data/yarn.lock +6 -6
- metadata +45 -50
- data/lib/generators/shopify_app/install/templates/omniauth.rb +0 -4
- data/lib/generators/shopify_app/install/templates/shopify_provider.rb.tt +0 -8
- data/lib/generators/shopify_app/install/templates/user_agent.rb +0 -6
- data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +0 -34
- 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,
|
|
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)&.
|
|
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)&.
|
|
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::
|
|
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 =
|
|
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[
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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[
|
|
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(
|
|
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(
|
|
17
|
-
response.headers.except!(
|
|
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[
|
|
12
|
+
session["shopify.cookies_persist"] = true
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def set_top_level_oauth_cookie
|
|
16
|
-
session[
|
|
16
|
+
session["shopify.top_level_oauth"] = true
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def clear_top_level_oauth_cookie
|
|
20
|
-
session.delete(
|
|
20
|
+
session.delete("shopify.top_level_oauth")
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def user_agent_is_mobile
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
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
|
|
16
|
+
rescue_from ShopifyAPI::Errors::HttpResponseError, with: :handle_http_error
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
ACCESS_TOKEN_REQUIRED_HEADER =
|
|
19
|
+
ACCESS_TOKEN_REQUIRED_HEADER = "X-Shopify-API-Request-Failure-Unauthorized"
|
|
20
20
|
|
|
21
21
|
def activate_shopify_session
|
|
22
|
-
if
|
|
22
|
+
if current_shopify_session.blank?
|
|
23
23
|
signal_access_token_required
|
|
24
24
|
return redirect_to_login
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
unless current_shopify_session.scope.to_a.empty? ||
|
|
28
|
+
current_shopify_session.scope.covers?(ShopifyAPI::Context.scope)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
clear_shopify_session
|
|
31
|
+
return redirect_to_login
|
|
32
|
+
end
|
|
30
33
|
|
|
31
34
|
begin
|
|
32
|
-
ShopifyAPI::
|
|
35
|
+
ShopifyAPI::Context.activate_session(current_shopify_session)
|
|
33
36
|
yield
|
|
34
37
|
ensure
|
|
35
|
-
ShopifyAPI::
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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[
|
|
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[
|
|
76
|
+
request.env["jwt.shopify_domain"]
|
|
108
77
|
end
|
|
109
78
|
|
|
110
79
|
def jwt_shopify_user_id
|
|
111
|
-
request.env[
|
|
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
|
-
|
|
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 =
|
|
183
|
-
request.path !=
|
|
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(
|
|
189
|
-
|
|
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[
|
|
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(
|
|
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[
|
|
21
|
+
request.headers["HTTP_X_SHOPIFY_SHOP_DOMAIN"]
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
24
|
end
|
data/lib/shopify_app/engine.rb
CHANGED
|
@@ -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 =
|
|
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
|
|
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 +=
|
|
19
|
-
|
|
20
|
-
|
|
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::
|
|
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
|
-
|
|
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
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
27
|
+
def destroy_webhooks
|
|
28
|
+
return unless ShopifyApp.configuration.has_webhooks?
|
|
40
29
|
|
|
41
|
-
|
|
30
|
+
ShopifyApp.configuration.webhooks.each do |attributes|
|
|
31
|
+
ShopifyAPI::Webhooks::Registry.unregister(topic: attributes[:topic])
|
|
32
|
+
end
|
|
33
|
+
end
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
def webhook_job_klass(topic)
|
|
52
|
+
webhook_job_klass_name(topic).safe_constantize || raise(ShopifyApp::MissingWebhookJobError)
|
|
53
|
+
end
|
|
57
54
|
|
|
58
|
-
|
|
59
|
-
|
|
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[
|
|
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[
|
|
39
|
-
env[
|
|
40
|
-
env[
|
|
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
|