shopify_app 13.0.1 → 13.4.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/rubocop.yml +28 -0
- data/.rubocop.yml +2 -2
- data/.travis.yml +4 -3
- data/CHANGELOG.md +27 -0
- data/Gemfile +3 -1
- data/README.md +39 -42
- data/SECURITY.md +59 -0
- data/app/controllers/concerns/shopify_app/authenticated.rb +1 -0
- data/app/controllers/concerns/shopify_app/require_known_shop.rb +39 -0
- data/app/controllers/shopify_app/callback_controller.rb +38 -7
- data/app/controllers/shopify_app/extension_verification_controller.rb +2 -7
- data/app/controllers/shopify_app/sessions_controller.rb +1 -1
- data/app/controllers/shopify_app/webhooks_controller.rb +1 -1
- data/config/locales/nl.yml +7 -7
- data/docs/Quickstart.md +7 -17
- data/docs/Releasing.md +1 -0
- data/lib/generators/shopify_app/add_webhook/templates/{webhook_job.rb → webhook_job.rb.tt} +0 -0
- data/lib/generators/shopify_app/install/install_generator.rb +1 -1
- data/lib/generators/shopify_app/install/templates/flash_messages.js +0 -2
- data/lib/generators/shopify_app/install/templates/{shopify_app.rb → shopify_app.rb.tt} +1 -1
- data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +7 -3
- data/lib/generators/shopify_app/user_model/user_model_generator.rb +7 -3
- data/lib/shopify_app.rb +5 -1
- data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +0 -1
- data/lib/shopify_app/controller_concerns/csrf_protection.rb +15 -0
- data/lib/shopify_app/controller_concerns/login_protection.rb +14 -12
- data/lib/shopify_app/controller_concerns/payload_verification.rb +24 -0
- data/lib/shopify_app/controller_concerns/webhook_verification.rb +1 -17
- data/lib/shopify_app/engine.rb +4 -0
- data/lib/shopify_app/middleware/jwt_middleware.rb +42 -0
- data/lib/shopify_app/session/jwt.rb +35 -22
- data/lib/shopify_app/test_helpers/all.rb +1 -0
- data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +2 -1
- data/lib/shopify_app/version.rb +1 -1
- data/package.json +1 -1
- data/shopify_app.gemspec +4 -3
- metadata +29 -9
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ShopifyApp
|
3
|
+
module PayloadVerification
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def shopify_hmac
|
9
|
+
request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']
|
10
|
+
end
|
11
|
+
|
12
|
+
def hmac_valid?(data)
|
13
|
+
secrets = [ShopifyApp.configuration.secret, ShopifyApp.configuration.old_secret].reject(&:blank?)
|
14
|
+
|
15
|
+
secrets.any? do |secret|
|
16
|
+
digest = OpenSSL::Digest.new('sha256')
|
17
|
+
ActiveSupport::SecurityUtils.secure_compare(
|
18
|
+
shopify_hmac,
|
19
|
+
Base64.strict_encode64(OpenSSL::HMAC.digest(digest, secret, data))
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
module ShopifyApp
|
3
3
|
module WebhookVerification
|
4
4
|
extend ActiveSupport::Concern
|
5
|
+
include ShopifyApp::PayloadVerification
|
5
6
|
|
6
7
|
included do
|
7
8
|
skip_before_action :verify_authenticity_token, raise: false
|
@@ -15,25 +16,8 @@ module ShopifyApp
|
|
15
16
|
return head(:unauthorized) unless hmac_valid?(data)
|
16
17
|
end
|
17
18
|
|
18
|
-
def hmac_valid?(data)
|
19
|
-
secrets = [ShopifyApp.configuration.secret, ShopifyApp.configuration.old_secret].reject(&:blank?)
|
20
|
-
|
21
|
-
secrets.any? do |secret|
|
22
|
-
digest = OpenSSL::Digest.new('sha256')
|
23
|
-
|
24
|
-
ActiveSupport::SecurityUtils.secure_compare(
|
25
|
-
shopify_hmac,
|
26
|
-
Base64.strict_encode64(OpenSSL::HMAC.digest(digest, secret, data))
|
27
|
-
)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
19
|
def shop_domain
|
32
20
|
request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN']
|
33
21
|
end
|
34
|
-
|
35
|
-
def shopify_hmac
|
36
|
-
request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']
|
37
|
-
end
|
38
22
|
end
|
39
23
|
end
|
data/lib/shopify_app/engine.rb
CHANGED
@@ -16,6 +16,10 @@ module ShopifyApp
|
|
16
16
|
|
17
17
|
initializer "shopify_app.middleware" do |app|
|
18
18
|
app.config.middleware.insert_after(::Rack::Runtime, ShopifyApp::SameSiteCookieMiddleware)
|
19
|
+
|
20
|
+
if ShopifyApp.configuration.allow_jwt_authentication
|
21
|
+
app.config.middleware.insert_after(ShopifyApp::SameSiteCookieMiddleware, ShopifyApp::JWTMiddleware)
|
22
|
+
end
|
19
23
|
end
|
20
24
|
end
|
21
25
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ShopifyApp
|
3
|
+
class JWTMiddleware
|
4
|
+
TOKEN_REGEX = /^Bearer\s+(.*?)$/
|
5
|
+
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
return call_next(env) unless authorization_header(env)
|
12
|
+
|
13
|
+
token = extract_token(env)
|
14
|
+
return call_next(env) unless token
|
15
|
+
|
16
|
+
set_env_variables(token, env)
|
17
|
+
call_next(env)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def call_next(env)
|
23
|
+
@app.call(env)
|
24
|
+
end
|
25
|
+
|
26
|
+
def authorization_header(env)
|
27
|
+
env['HTTP_AUTHORIZATION']
|
28
|
+
end
|
29
|
+
|
30
|
+
def extract_token(env)
|
31
|
+
match = authorization_header(env).match(TOKEN_REGEX)
|
32
|
+
match && match[1]
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_env_variables(token, env)
|
36
|
+
jwt = ShopifyApp::JWT.new(token)
|
37
|
+
|
38
|
+
env['jwt.shopify_domain'] = jwt.shopify_domain
|
39
|
+
env['jwt.shopify_user_id'] = jwt.shopify_user_id
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,48 +1,61 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module ShopifyApp
|
3
3
|
class JWT
|
4
|
+
class InvalidDestinationError < StandardError; end
|
5
|
+
class MismatchedHostsError < StandardError; end
|
6
|
+
class InvalidAudienceError < StandardError; end
|
7
|
+
|
8
|
+
WARN_EXCEPTIONS = [
|
9
|
+
::JWT::DecodeError,
|
10
|
+
::JWT::ExpiredSignature,
|
11
|
+
::JWT::ImmatureSignature,
|
12
|
+
::JWT::VerificationError,
|
13
|
+
InvalidAudienceError,
|
14
|
+
InvalidDestinationError,
|
15
|
+
MismatchedHostsError,
|
16
|
+
]
|
17
|
+
|
4
18
|
def initialize(token)
|
5
19
|
@token = token
|
6
20
|
set_payload
|
7
21
|
end
|
8
22
|
|
9
23
|
def shopify_domain
|
10
|
-
payload &&
|
24
|
+
@payload && ShopifyApp::Utils.sanitize_shop_domain(@payload['dest'])
|
11
25
|
end
|
12
26
|
|
13
27
|
def shopify_user_id
|
14
|
-
payload && payload['sub']
|
28
|
+
@payload && @payload['sub']
|
15
29
|
end
|
16
30
|
|
17
31
|
private
|
18
32
|
|
19
|
-
def payload
|
20
|
-
return unless @payload
|
21
|
-
return unless dest_host
|
22
|
-
return unless dest_host == iss_host
|
23
|
-
return unless @payload['aud'] == ShopifyApp.configuration.api_key
|
24
|
-
|
25
|
-
@payload
|
26
|
-
end
|
27
|
-
|
28
33
|
def set_payload
|
29
|
-
|
30
|
-
|
31
|
-
|
34
|
+
payload, _ = parse_token_data(ShopifyApp.configuration&.secret, ShopifyApp.configuration&.old_secret)
|
35
|
+
@payload = validate_payload(payload)
|
36
|
+
rescue *WARN_EXCEPTIONS => error
|
37
|
+
Rails.logger.warn("[ShopifyApp::JWT] Failed to validate JWT: [#{error.class}] #{error}")
|
38
|
+
nil
|
32
39
|
end
|
33
40
|
|
34
|
-
def parse_token_data(secret)
|
41
|
+
def parse_token_data(secret, old_secret)
|
35
42
|
::JWT.decode(@token, secret, true, { algorithm: 'HS256' })
|
36
|
-
rescue ::JWT::
|
37
|
-
|
38
|
-
end
|
43
|
+
rescue ::JWT::VerificationError
|
44
|
+
raise unless old_secret
|
39
45
|
|
40
|
-
|
41
|
-
@payload && ShopifyApp::Utils.sanitize_shop_domain(@payload['dest'])
|
46
|
+
::JWT.decode(@token, old_secret, true, { algorithm: 'HS256' })
|
42
47
|
end
|
43
48
|
|
44
|
-
def
|
45
|
-
|
49
|
+
def validate_payload(payload)
|
50
|
+
dest_host = ShopifyApp::Utils.sanitize_shop_domain(payload['dest'])
|
51
|
+
iss_host = ShopifyApp::Utils.sanitize_shop_domain(payload['iss'])
|
52
|
+
api_key = ShopifyApp.configuration.api_key
|
53
|
+
|
54
|
+
raise InvalidAudienceError, "'aud' claim does not match api_key" unless payload['aud'] == api_key
|
55
|
+
raise InvalidDestinationError, "'dest' claim host not a valid shopify host" unless dest_host
|
56
|
+
raise MismatchedHostsError, "'dest' claim host does not match 'iss' claim host" unless dest_host == iss_host
|
57
|
+
|
58
|
+
payload
|
46
59
|
end
|
47
60
|
end
|
48
61
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module ShopifyApp
|
2
3
|
module TestHelpers
|
3
4
|
module WebhookVerificationHelper
|
@@ -8,7 +9,7 @@ module ShopifyApp
|
|
8
9
|
@request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] = valid_hmac
|
9
10
|
end
|
10
11
|
|
11
|
-
def unauthorized_webhook_verification_headers!
|
12
|
+
def unauthorized_webhook_verification_headers!
|
12
13
|
@request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] = "invalid_hmac"
|
13
14
|
end
|
14
15
|
end
|
data/lib/shopify_app/version.rb
CHANGED
data/package.json
CHANGED
data/shopify_app.gemspec
CHANGED
@@ -9,15 +9,16 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.author = "Shopify"
|
10
10
|
s.summary = 'This gem is used to get quickly started with the Shopify API'
|
11
11
|
|
12
|
-
s.required_ruby_version = ">= 2.
|
12
|
+
s.required_ruby_version = ">= 2.4"
|
13
13
|
|
14
14
|
s.metadata['allowed_push_host'] = 'https://rubygems.org'
|
15
15
|
|
16
|
-
s.add_runtime_dependency('browser_sniffer', '~> 1.2.
|
16
|
+
s.add_runtime_dependency('browser_sniffer', '~> 1.2.2')
|
17
17
|
s.add_runtime_dependency('rails', '> 5.2.1')
|
18
|
-
s.add_runtime_dependency('shopify_api', '~> 9.0
|
18
|
+
s.add_runtime_dependency('shopify_api', '~> 9.1.0')
|
19
19
|
s.add_runtime_dependency('omniauth-shopify-oauth2', '~> 2.2.2')
|
20
20
|
s.add_runtime_dependency('jwt', '~> 2.2.1')
|
21
|
+
s.add_runtime_dependency('redirect_safely', '~> 1.0')
|
21
22
|
|
22
23
|
s.add_development_dependency('rake')
|
23
24
|
s.add_development_dependency('byebug')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shopify_app
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 13.0
|
4
|
+
version: 13.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: browser_sniffer
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.2.
|
19
|
+
version: 1.2.2
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.2.
|
26
|
+
version: 1.2.2
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rails
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 9.0
|
47
|
+
version: 9.1.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 9.0
|
54
|
+
version: 9.1.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: omniauth-shopify-oauth2
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 2.2.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: redirect_safely
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: rake
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -230,6 +244,7 @@ files:
|
|
230
244
|
- ".github/CODEOWNERS"
|
231
245
|
- ".github/ISSUE_TEMPLATE.md"
|
232
246
|
- ".github/probots.yml"
|
247
|
+
- ".github/workflows/rubocop.yml"
|
233
248
|
- ".gitignore"
|
234
249
|
- ".nvmrc"
|
235
250
|
- ".rubocop.yml"
|
@@ -240,6 +255,7 @@ files:
|
|
240
255
|
- LICENSE
|
241
256
|
- README.md
|
242
257
|
- Rakefile
|
258
|
+
- SECURITY.md
|
243
259
|
- app/assets/images/storage_access.svg
|
244
260
|
- app/assets/javascripts/shopify_app/enable_cookies.js
|
245
261
|
- app/assets/javascripts/shopify_app/itp_helper.js
|
@@ -251,6 +267,7 @@ files:
|
|
251
267
|
- app/assets/javascripts/shopify_app/top_level.js
|
252
268
|
- app/assets/javascripts/shopify_app/top_level_interaction.js
|
253
269
|
- app/controllers/concerns/shopify_app/authenticated.rb
|
270
|
+
- app/controllers/concerns/shopify_app/require_known_shop.rb
|
254
271
|
- app/controllers/shopify_app/authenticated_controller.rb
|
255
272
|
- app/controllers/shopify_app/callback_controller.rb
|
256
273
|
- app/controllers/shopify_app/extension_verification_controller.rb
|
@@ -301,7 +318,7 @@ files:
|
|
301
318
|
- lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb
|
302
319
|
- lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb
|
303
320
|
- lib/generators/shopify_app/add_webhook/add_webhook_generator.rb
|
304
|
-
- lib/generators/shopify_app/add_webhook/templates/webhook_job.rb
|
321
|
+
- lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt
|
305
322
|
- lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb
|
306
323
|
- lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb
|
307
324
|
- lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb
|
@@ -319,7 +336,7 @@ files:
|
|
319
336
|
- lib/generators/shopify_app/install/templates/omniauth.rb
|
320
337
|
- lib/generators/shopify_app/install/templates/session_store.rb
|
321
338
|
- lib/generators/shopify_app/install/templates/shopify_app.js
|
322
|
-
- lib/generators/shopify_app/install/templates/shopify_app.rb
|
339
|
+
- lib/generators/shopify_app/install/templates/shopify_app.rb.tt
|
323
340
|
- lib/generators/shopify_app/install/templates/shopify_app_index.js
|
324
341
|
- lib/generators/shopify_app/install/templates/shopify_provider.rb
|
325
342
|
- lib/generators/shopify_app/install/templates/user_agent.rb
|
@@ -341,16 +358,19 @@ files:
|
|
341
358
|
- lib/shopify_app.rb
|
342
359
|
- lib/shopify_app/configuration.rb
|
343
360
|
- lib/shopify_app/controller_concerns/app_proxy_verification.rb
|
361
|
+
- lib/shopify_app/controller_concerns/csrf_protection.rb
|
344
362
|
- lib/shopify_app/controller_concerns/embedded_app.rb
|
345
363
|
- lib/shopify_app/controller_concerns/itp.rb
|
346
364
|
- lib/shopify_app/controller_concerns/localization.rb
|
347
365
|
- lib/shopify_app/controller_concerns/login_protection.rb
|
366
|
+
- lib/shopify_app/controller_concerns/payload_verification.rb
|
348
367
|
- lib/shopify_app/controller_concerns/webhook_verification.rb
|
349
368
|
- lib/shopify_app/engine.rb
|
350
369
|
- lib/shopify_app/jobs/scripttags_manager_job.rb
|
351
370
|
- lib/shopify_app/jobs/webhooks_manager_job.rb
|
352
371
|
- lib/shopify_app/managers/scripttags_manager.rb
|
353
372
|
- lib/shopify_app/managers/webhooks_manager.rb
|
373
|
+
- lib/shopify_app/middleware/jwt_middleware.rb
|
354
374
|
- lib/shopify_app/middleware/same_site_cookie_middleware.rb
|
355
375
|
- lib/shopify_app/session/in_memory_session_store.rb
|
356
376
|
- lib/shopify_app/session/in_memory_shop_session_store.rb
|
@@ -385,7 +405,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
385
405
|
requirements:
|
386
406
|
- - ">="
|
387
407
|
- !ruby/object:Gem::Version
|
388
|
-
version: 2.
|
408
|
+
version: '2.4'
|
389
409
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
390
410
|
requirements:
|
391
411
|
- - ">="
|