shopify_app 22.0.1 → 22.2.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 +1 -2
- data/.rubocop.yml +0 -1
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +20 -17
- data/README.md +38 -0
- data/app/controllers/concerns/shopify_app/ensure_has_session.rb +11 -5
- data/app/controllers/concerns/shopify_app/ensure_installed.rb +8 -2
- data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +5 -1
- data/app/controllers/shopify_app/callback_controller.rb +10 -1
- data/app/controllers/shopify_app/sessions_controller.rb +24 -4
- data/app/views/shopify_app/layouts/app_bridge.html.erb +17 -0
- data/app/views/shopify_app/sessions/patch_shopify_id_token.html.erb +0 -0
- data/config/routes.rb +1 -0
- data/docs/Troubleshooting.md +0 -23
- data/docs/Upgrading.md +25 -0
- data/docs/shopify_app/authentication.md +105 -20
- data/docs/shopify_app/sessions.md +110 -14
- data/docs/shopify_app/webhooks.md +1 -1
- data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +2 -0
- data/lib/shopify_app/admin_api/with_token_refetch.rb +28 -0
- data/lib/shopify_app/auth/post_authenticate_tasks.rb +48 -0
- data/lib/shopify_app/auth/token_exchange.rb +73 -0
- data/lib/shopify_app/configuration.rb +54 -3
- data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +1 -1
- data/lib/shopify_app/controller_concerns/embedded_app.rb +27 -0
- data/lib/shopify_app/controller_concerns/ensure_billing.rb +11 -3
- data/lib/shopify_app/controller_concerns/login_protection.rb +8 -23
- data/lib/shopify_app/controller_concerns/redirect_for_embedded.rb +5 -0
- data/lib/shopify_app/controller_concerns/token_exchange.rb +111 -0
- data/lib/shopify_app/controller_concerns/with_shopify_id_token.rb +41 -0
- data/lib/shopify_app/middleware/jwt_middleware.rb +13 -9
- data/lib/shopify_app/session/jwt.rb +9 -0
- data/lib/shopify_app/version.rb +1 -1
- data/lib/shopify_app.rb +9 -0
- data/package.json +1 -1
- data/shopify_app.gemspec +1 -1
- data/yarn.lock +3 -3
- metadata +12 -5
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyApp
|
4
|
+
module TokenExchange
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include ShopifyApp::AdminAPI::WithTokenRefetch
|
7
|
+
include ShopifyApp::SanitizedParams
|
8
|
+
include ShopifyApp::EmbeddedApp
|
9
|
+
|
10
|
+
included do
|
11
|
+
include ShopifyApp::WithShopifyIdToken
|
12
|
+
end
|
13
|
+
|
14
|
+
INVALID_SHOPIFY_ID_TOKEN_ERRORS = [
|
15
|
+
ShopifyAPI::Errors::MissingJwtTokenError,
|
16
|
+
ShopifyAPI::Errors::InvalidJwtTokenError,
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
def activate_shopify_session(&block)
|
20
|
+
retrieve_session_from_token_exchange if current_shopify_session.blank? || should_exchange_expired_token?
|
21
|
+
|
22
|
+
ShopifyApp::Logger.debug("Activating Shopify session")
|
23
|
+
ShopifyAPI::Context.activate_session(current_shopify_session)
|
24
|
+
with_token_refetch(current_shopify_session, shopify_id_token, &block)
|
25
|
+
rescue *INVALID_SHOPIFY_ID_TOKEN_ERRORS => e
|
26
|
+
ShopifyApp::Logger.debug("Responding to invalid Shopify ID token: #{e.message}")
|
27
|
+
respond_to_invalid_shopify_id_token unless performed?
|
28
|
+
ensure
|
29
|
+
ShopifyApp::Logger.debug("Deactivating session")
|
30
|
+
ShopifyAPI::Context.deactivate_session
|
31
|
+
end
|
32
|
+
|
33
|
+
def should_exchange_expired_token?
|
34
|
+
ShopifyApp.configuration.check_session_expiry_date && current_shopify_session.expired?
|
35
|
+
end
|
36
|
+
|
37
|
+
def current_shopify_session
|
38
|
+
return unless current_shopify_session_id
|
39
|
+
|
40
|
+
@current_shopify_session ||= ShopifyApp::SessionRepository.load_session(current_shopify_session_id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def current_shopify_session_id
|
44
|
+
@current_shopify_session_id ||= ShopifyAPI::Utils::SessionUtils.session_id_from_shopify_id_token(
|
45
|
+
id_token: shopify_id_token,
|
46
|
+
online: online_token_configured?,
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def current_shopify_domain
|
51
|
+
sanitized_shop_name || current_shopify_session&.shop
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def retrieve_session_from_token_exchange
|
57
|
+
@current_shopify_session = nil
|
58
|
+
ShopifyApp::Auth::TokenExchange.perform(shopify_id_token)
|
59
|
+
end
|
60
|
+
|
61
|
+
def respond_to_invalid_shopify_id_token
|
62
|
+
if request.headers["HTTP_AUTHORIZATION"].blank?
|
63
|
+
if missing_embedded_param?
|
64
|
+
redirect_to_embed_app_in_admin
|
65
|
+
else
|
66
|
+
redirect_to_bounce_page
|
67
|
+
end
|
68
|
+
else
|
69
|
+
ShopifyApp::Logger.debug("Responding to invalid Shopify ID token with unauthorized response")
|
70
|
+
response.set_header("X-Shopify-Retry-Invalid-Session-Request", 1)
|
71
|
+
unauthorized_response = { message: :unauthorized }
|
72
|
+
render(json: { errors: [unauthorized_response] }, status: :unauthorized)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def redirect_to_bounce_page
|
77
|
+
ShopifyApp::Logger.debug("Redirecting to bounce page for patching Shopify ID token")
|
78
|
+
patch_shopify_id_token_url =
|
79
|
+
"#{ShopifyAPI::Context.host}#{ShopifyApp.configuration.root_url}/patch_shopify_id_token"
|
80
|
+
patch_shopify_id_token_params = request.query_parameters.except(:id_token)
|
81
|
+
|
82
|
+
bounce_url = "#{request.path}?#{patch_shopify_id_token_params.to_query}"
|
83
|
+
|
84
|
+
# App Bridge will trigger a fetch to the URL in shopify-reload, with a new session token in headers
|
85
|
+
patch_shopify_id_token_params["shopify-reload"] = bounce_url
|
86
|
+
|
87
|
+
redirect_to(
|
88
|
+
"#{patch_shopify_id_token_url}?#{patch_shopify_id_token_params.to_query}",
|
89
|
+
allow_other_host: true,
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
def missing_embedded_param?
|
94
|
+
!params[:embedded].present? || params[:embedded] != "1"
|
95
|
+
end
|
96
|
+
|
97
|
+
def online_token_configured?
|
98
|
+
ShopifyApp.configuration.online_token_configured?
|
99
|
+
end
|
100
|
+
|
101
|
+
def fullpage_redirect_to(url)
|
102
|
+
raise ShopifyApp::ShopifyDomainNotFound if current_shopify_domain.nil?
|
103
|
+
|
104
|
+
render(
|
105
|
+
"shopify_app/shared/redirect",
|
106
|
+
layout: false,
|
107
|
+
locals: { url: url, current_shopify_domain: current_shopify_domain },
|
108
|
+
)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyApp
|
4
|
+
module WithShopifyIdToken
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def shopify_id_token
|
8
|
+
@shopify_id_token ||= id_token_from_request_env || id_token_from_authorization_header || id_token_from_url_param
|
9
|
+
end
|
10
|
+
|
11
|
+
def jwt_shopify_domain
|
12
|
+
request.env["jwt.shopify_domain"]
|
13
|
+
end
|
14
|
+
|
15
|
+
def jwt_shopify_user_id
|
16
|
+
request.env["jwt.shopify_user_id"]
|
17
|
+
end
|
18
|
+
|
19
|
+
def jwt_expire_at
|
20
|
+
expire_at = request.env["jwt.expire_at"]
|
21
|
+
return unless expire_at
|
22
|
+
|
23
|
+
expire_at - 5.seconds # 5s gap to start fetching new token in advance
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def id_token_from_request_env
|
29
|
+
# This is set from ShopifyApp::JWTMiddleware
|
30
|
+
request.env["jwt.token"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def id_token_from_authorization_header
|
34
|
+
request.headers["HTTP_AUTHORIZATION"]&.match(/^Bearer (.+)$/)&.[](1)
|
35
|
+
end
|
36
|
+
|
37
|
+
def id_token_from_url_param
|
38
|
+
params["id_token"]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -2,16 +2,17 @@
|
|
2
2
|
|
3
3
|
module ShopifyApp
|
4
4
|
class JWTMiddleware
|
5
|
-
TOKEN_REGEX = /^Bearer
|
5
|
+
TOKEN_REGEX = /^Bearer (.+)$/
|
6
|
+
ID_TOKEN_QUERY_PARAM = "id_token"
|
6
7
|
|
7
8
|
def initialize(app)
|
8
9
|
@app = app
|
9
10
|
end
|
10
11
|
|
11
12
|
def call(env)
|
12
|
-
return call_next(env) unless
|
13
|
+
return call_next(env) unless ShopifyApp.configuration.embedded_app?
|
13
14
|
|
14
|
-
token =
|
15
|
+
token = token_from_authorization_header(env) || token_from_query_string(env)
|
15
16
|
return call_next(env) unless token
|
16
17
|
|
17
18
|
set_env_variables(token, env)
|
@@ -24,21 +25,24 @@ module ShopifyApp
|
|
24
25
|
@app.call(env)
|
25
26
|
end
|
26
27
|
|
27
|
-
def
|
28
|
-
env["HTTP_AUTHORIZATION"]
|
28
|
+
def token_from_authorization_header(env)
|
29
|
+
env["HTTP_AUTHORIZATION"]&.match(TOKEN_REGEX)&.[](1)
|
29
30
|
end
|
30
31
|
|
31
|
-
def
|
32
|
-
|
33
|
-
match && match[1]
|
32
|
+
def token_from_query_string(env)
|
33
|
+
Rack::Utils.parse_nested_query(env["QUERY_STRING"])[ID_TOKEN_QUERY_PARAM]
|
34
34
|
end
|
35
35
|
|
36
36
|
def set_env_variables(token, env)
|
37
|
-
jwt =
|
37
|
+
jwt = ShopifyAPI::Auth::JwtPayload.new(token)
|
38
38
|
|
39
|
+
env["jwt.token"] = token
|
39
40
|
env["jwt.shopify_domain"] = jwt.shopify_domain
|
40
41
|
env["jwt.shopify_user_id"] = jwt.shopify_user_id
|
41
42
|
env["jwt.expire_at"] = jwt.expire_at
|
43
|
+
rescue ShopifyAPI::Errors::InvalidJwtTokenError
|
44
|
+
# ShopifyApp::JWT did not raise any exceptions, ensuring behaviour does not change
|
45
|
+
nil
|
42
46
|
end
|
43
47
|
end
|
44
48
|
end
|
@@ -13,6 +13,7 @@ module ShopifyApp
|
|
13
13
|
]
|
14
14
|
|
15
15
|
def initialize(token)
|
16
|
+
warn_deprecation
|
16
17
|
@token = token
|
17
18
|
set_payload
|
18
19
|
end
|
@@ -60,5 +61,13 @@ module ShopifyApp
|
|
60
61
|
|
61
62
|
payload
|
62
63
|
end
|
64
|
+
|
65
|
+
def warn_deprecation
|
66
|
+
message = <<~EOS
|
67
|
+
"ShopifyApp::JWT will be deprecated, use ShopifyAPI::Auth::JwtPayload to parse JWT token instead."
|
68
|
+
EOS
|
69
|
+
|
70
|
+
ShopifyApp::Logger.deprecated(message, "23.0.0")
|
71
|
+
end
|
63
72
|
end
|
64
73
|
end
|
data/lib/shopify_app/version.rb
CHANGED
data/lib/shopify_app.rb
CHANGED
@@ -40,6 +40,9 @@ module ShopifyApp
|
|
40
40
|
|
41
41
|
require "shopify_app/logger"
|
42
42
|
|
43
|
+
# Admin API helpers
|
44
|
+
require "shopify_app/admin_api/with_token_refetch"
|
45
|
+
|
43
46
|
# controller concerns
|
44
47
|
require "shopify_app/controller_concerns/csrf_protection"
|
45
48
|
require "shopify_app/controller_concerns/localization"
|
@@ -52,6 +55,12 @@ module ShopifyApp
|
|
52
55
|
require "shopify_app/controller_concerns/payload_verification"
|
53
56
|
require "shopify_app/controller_concerns/app_proxy_verification"
|
54
57
|
require "shopify_app/controller_concerns/webhook_verification"
|
58
|
+
require "shopify_app/controller_concerns/token_exchange"
|
59
|
+
require "shopify_app/controller_concerns/with_shopify_id_token"
|
60
|
+
|
61
|
+
# Auth helpers
|
62
|
+
require "shopify_app/auth/post_authenticate_tasks"
|
63
|
+
require "shopify_app/auth/token_exchange"
|
55
64
|
|
56
65
|
# jobs
|
57
66
|
require "shopify_app/jobs/webhooks_manager_job"
|
data/package.json
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", ">= 14.0
|
22
|
+
s.add_runtime_dependency("shopify_api", ">= 14.3.0", "< 15.0")
|
23
23
|
s.add_runtime_dependency("sprockets-rails", ">= 2.0.0")
|
24
24
|
|
25
25
|
s.add_development_dependency("byebug")
|
data/yarn.lock
CHANGED
@@ -2040,9 +2040,9 @@ flatted@^3.2.7:
|
|
2040
2040
|
integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==
|
2041
2041
|
|
2042
2042
|
follow-redirects@^1.0.0:
|
2043
|
-
version "1.15.
|
2044
|
-
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.
|
2045
|
-
integrity sha512-
|
2043
|
+
version "1.15.6"
|
2044
|
+
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
|
2045
|
+
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
|
2046
2046
|
|
2047
2047
|
fs-extra@^8.1.0:
|
2048
2048
|
version "8.1.0"
|
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: 22.0
|
4
|
+
version: 22.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activeresource
|
@@ -86,7 +86,7 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 14.0
|
89
|
+
version: 14.3.0
|
90
90
|
- - "<"
|
91
91
|
- !ruby/object:Gem::Version
|
92
92
|
version: '15.0'
|
@@ -96,7 +96,7 @@ dependencies:
|
|
96
96
|
requirements:
|
97
97
|
- - ">="
|
98
98
|
- !ruby/object:Gem::Version
|
99
|
-
version: 14.0
|
99
|
+
version: 14.3.0
|
100
100
|
- - "<"
|
101
101
|
- !ruby/object:Gem::Version
|
102
102
|
version: '15.0'
|
@@ -314,6 +314,7 @@ files:
|
|
314
314
|
- app/controllers/shopify_app/extension_verification_controller.rb
|
315
315
|
- app/controllers/shopify_app/sessions_controller.rb
|
316
316
|
- app/controllers/shopify_app/webhooks_controller.rb
|
317
|
+
- app/views/shopify_app/layouts/app_bridge.html.erb
|
317
318
|
- app/views/shopify_app/partials/_button_styles.html.erb
|
318
319
|
- app/views/shopify_app/partials/_card_styles.html.erb
|
319
320
|
- app/views/shopify_app/partials/_empty_state_styles.html.erb
|
@@ -321,6 +322,7 @@ files:
|
|
321
322
|
- app/views/shopify_app/partials/_layout_styles.html.erb
|
322
323
|
- app/views/shopify_app/partials/_typography_styles.html.erb
|
323
324
|
- app/views/shopify_app/sessions/new.html.erb
|
325
|
+
- app/views/shopify_app/sessions/patch_shopify_id_token.html.erb
|
324
326
|
- app/views/shopify_app/shared/redirect.html.erb
|
325
327
|
- config/locales/cs.yml
|
326
328
|
- config/locales/da.yml
|
@@ -414,6 +416,9 @@ files:
|
|
414
416
|
- lib/shopify_app/access_scopes/noop_strategy.rb
|
415
417
|
- lib/shopify_app/access_scopes/shop_strategy.rb
|
416
418
|
- lib/shopify_app/access_scopes/user_strategy.rb
|
419
|
+
- lib/shopify_app/admin_api/with_token_refetch.rb
|
420
|
+
- lib/shopify_app/auth/post_authenticate_tasks.rb
|
421
|
+
- lib/shopify_app/auth/token_exchange.rb
|
417
422
|
- lib/shopify_app/configuration.rb
|
418
423
|
- lib/shopify_app/controller_concerns/app_proxy_verification.rb
|
419
424
|
- lib/shopify_app/controller_concerns/csrf_protection.rb
|
@@ -425,7 +430,9 @@ files:
|
|
425
430
|
- lib/shopify_app/controller_concerns/payload_verification.rb
|
426
431
|
- lib/shopify_app/controller_concerns/redirect_for_embedded.rb
|
427
432
|
- lib/shopify_app/controller_concerns/sanitized_params.rb
|
433
|
+
- lib/shopify_app/controller_concerns/token_exchange.rb
|
428
434
|
- lib/shopify_app/controller_concerns/webhook_verification.rb
|
435
|
+
- lib/shopify_app/controller_concerns/with_shopify_id_token.rb
|
429
436
|
- lib/shopify_app/engine.rb
|
430
437
|
- lib/shopify_app/errors.rb
|
431
438
|
- lib/shopify_app/jobs/webhooks_manager_job.rb
|
@@ -474,7 +481,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
474
481
|
- !ruby/object:Gem::Version
|
475
482
|
version: '0'
|
476
483
|
requirements: []
|
477
|
-
rubygems_version: 3.5.
|
484
|
+
rubygems_version: 3.5.9
|
478
485
|
signing_key:
|
479
486
|
specification_version: 4
|
480
487
|
summary: This gem is used to get quickly started with the Shopify API
|