shopify_app 22.1.0 → 22.2.1

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.
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyApp
4
+ module Auth
5
+ class TokenExchange
6
+ attr_reader :id_token
7
+
8
+ def self.perform(id_token)
9
+ new(id_token).perform
10
+ end
11
+
12
+ def initialize(id_token)
13
+ @id_token = id_token
14
+ end
15
+
16
+ def perform
17
+ domain = ShopifyAPI::Auth::JwtPayload.new(id_token).shopify_domain
18
+
19
+ Logger.info("Performing Token Exchange for [#{domain}] - (Offline)")
20
+ session = exchange_token(
21
+ shop: domain,
22
+ id_token: id_token,
23
+ requested_token_type: ShopifyAPI::Auth::TokenExchange::RequestedTokenType::OFFLINE_ACCESS_TOKEN,
24
+ )
25
+
26
+ if online_token_configured?
27
+ Logger.info("Performing Token Exchange for [#{domain}] - (Online)")
28
+ session = exchange_token(
29
+ shop: domain,
30
+ id_token: id_token,
31
+ requested_token_type: ShopifyAPI::Auth::TokenExchange::RequestedTokenType::ONLINE_ACCESS_TOKEN,
32
+ )
33
+ end
34
+
35
+ ShopifyApp.configuration.post_authenticate_tasks.perform(session)
36
+
37
+ session
38
+ end
39
+
40
+ private
41
+
42
+ def exchange_token(shop:, id_token:, requested_token_type:)
43
+ session = ShopifyAPI::Auth::TokenExchange.exchange_token(
44
+ shop: shop,
45
+ session_token: id_token,
46
+ requested_token_type: requested_token_type,
47
+ )
48
+
49
+ SessionRepository.store_session(session)
50
+
51
+ session
52
+ rescue ShopifyAPI::Errors::InvalidJwtTokenError
53
+ Logger.error("Invalid id token '#{id_token}' during token exchange")
54
+ raise
55
+ rescue ShopifyAPI::Errors::HttpResponseError => error
56
+ Logger.error(
57
+ "A #{error.code} error (#{error.class}) occurred during the token exchange. Response: #{error.response.body}",
58
+ )
59
+ raise
60
+ rescue ActiveRecord::RecordNotUnique
61
+ Logger.debug("Session not stored due to concurrent token exchange calls")
62
+ session
63
+ rescue => error
64
+ Logger.error("An error occurred during the token exchange: [#{error.class}] #{error.message}")
65
+ raise
66
+ end
67
+
68
+ def online_token_configured?
69
+ ShopifyApp.configuration.online_token_configured?
70
+ end
71
+ end
72
+ end
73
+ end
@@ -48,8 +48,8 @@ module ShopifyApp
48
48
  # takes a ShopifyApp::BillingConfiguration object
49
49
  attr_accessor :billing
50
50
 
51
- # Work in Progress: enables token exchange authentication flow
52
- attr_accessor :wip_new_embedded_auth_strategy
51
+ # Enables new authorization flow using token exchange
52
+ attr_accessor :new_embedded_auth_strategy
53
53
 
54
54
  def initialize
55
55
  @root_url = "/"
@@ -129,7 +129,7 @@ module ShopifyApp
129
129
  end
130
130
 
131
131
  def use_new_embedded_auth_strategy?
132
- wip_new_embedded_auth_strategy && embedded_app?
132
+ new_embedded_auth_strategy && embedded_app?
133
133
  end
134
134
 
135
135
  def online_token_configured?
@@ -5,6 +5,7 @@ module ShopifyApp
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  include ShopifyApp::FrameAncestors
8
+ include ShopifyApp::SanitizedParams
8
9
 
9
10
  included do
10
11
  layout :embedded_app_layout
@@ -13,6 +14,22 @@ module ShopifyApp
13
14
 
14
15
  protected
15
16
 
17
+ def redirect_to_embed_app_in_admin
18
+ ShopifyApp::Logger.debug("Redirecting to embed app in admin")
19
+
20
+ host = if params[:host]
21
+ params[:host]
22
+ elsif params[:shop]
23
+ Base64.encode64("#{sanitized_shop_name}/admin")
24
+ else
25
+ return redirect_to(ShopifyApp.configuration.login_url)
26
+ end
27
+
28
+ redirect_path = ShopifyAPI::Auth.embedded_app_url(host)
29
+ redirect_path = ShopifyApp.configuration.root_url if deduced_phishing_attack?(redirect_path)
30
+ redirect_to(redirect_path, allow_other_host: true)
31
+ end
32
+
16
33
  def use_embedded_app_layout?
17
34
  ShopifyApp.configuration.embedded_app?
18
35
  end
@@ -27,5 +44,15 @@ module ShopifyApp
27
44
  response.set_header("P3P", 'CP="Not used"')
28
45
  response.headers.except!("X-Frame-Options")
29
46
  end
47
+
48
+ def deduced_phishing_attack?(decoded_host)
49
+ sanitized_host = ShopifyApp::Utils.sanitize_shop_domain(decoded_host)
50
+ if sanitized_host.nil?
51
+ message = "Host param for redirect to embed app in admin is not from a trusted domain, " \
52
+ "redirecting to root as this is likely a phishing attack."
53
+ ShopifyApp::Logger.info(message)
54
+ end
55
+ sanitized_host.nil?
56
+ end
30
57
  end
31
58
  end
@@ -27,7 +27,7 @@ module ShopifyApp
27
27
 
28
28
  unless has_payment
29
29
  if request.xhr?
30
- add_top_level_redirection_headers(url: confirmation_url, ignore_response_code: true)
30
+ RedirectForEmbedded.add_app_bridge_redirect_url_header(confirmation_url, response)
31
31
  ShopifyApp::Logger.debug("Responding with 401 unauthorized")
32
32
  head(:unauthorized)
33
33
  elsif ShopifyApp.configuration.embedded_app?
@@ -45,8 +45,16 @@ module ShopifyApp
45
45
  end
46
46
 
47
47
  def handle_billing_error(error)
48
- logger.info("#{error.message}: #{error.errors}")
49
- redirect_to_login
48
+ ShopifyApp::Logger.warn("Encountered billing error - #{error.message}: #{error.errors}\n" \
49
+ "Redirecting to login page")
50
+
51
+ login_url = ShopifyApp.configuration.login_url
52
+ if request.xhr?
53
+ RedirectForEmbedded.add_app_bridge_redirect_url_header(login_url, response)
54
+ head(:unauthorized)
55
+ else
56
+ fullpage_redirect_to(login_url)
57
+ end
50
58
  end
51
59
 
52
60
  def has_active_payment?(session)
@@ -16,6 +16,7 @@ module ShopifyApp
16
16
  end
17
17
 
18
18
  rescue_from ShopifyAPI::Errors::HttpResponseError, with: :handle_http_error
19
+ include ShopifyApp::WithShopifyIdToken
19
20
  end
20
21
 
21
22
  ACCESS_TOKEN_REQUIRED_HEADER = "X-Shopify-API-Request-Failure-Unauthorized"
@@ -53,7 +54,7 @@ module ShopifyApp
53
54
  @current_shopify_session ||= begin
54
55
  cookie_name = ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME
55
56
  load_current_session(
56
- auth_header: request.headers["HTTP_AUTHORIZATION"],
57
+ shopify_id_token: shopify_id_token,
57
58
  cookies: { cookie_name => cookies.encrypted[cookie_name] },
58
59
  is_online: online_token_configured?,
59
60
  )
@@ -78,13 +79,6 @@ module ShopifyApp
78
79
  response.set_header(ACCESS_TOKEN_REQUIRED_HEADER, "true")
79
80
  end
80
81
 
81
- def jwt_expire_at
82
- expire_at = request.env["jwt.expire_at"]
83
- return unless expire_at
84
-
85
- expire_at - 5.seconds # 5s gap to start fetching new token in advance
86
- end
87
-
88
82
  def add_top_level_redirection_headers(url: nil, ignore_response_code: false)
89
83
  if request.xhr? && (ignore_response_code || response.code.to_i == 401)
90
84
  ShopifyApp::Logger.debug("Adding top level redirection headers")
@@ -94,8 +88,8 @@ module ShopifyApp
94
88
  params[:shop] = if current_shopify_session
95
89
  current_shopify_session.shop
96
90
 
97
- elsif (matches = request.headers["HTTP_AUTHORIZATION"]&.match(/^Bearer (.+)$/))
98
- jwt_payload = ShopifyAPI::Auth::JwtPayload.new(T.must(matches[1]))
91
+ elsif shopify_id_token
92
+ jwt_payload = ShopifyAPI::Auth::JwtPayload.new(shopify_id_token)
99
93
  jwt_payload.shop
100
94
  end
101
95
  end
@@ -103,21 +97,12 @@ module ShopifyApp
103
97
  url ||= login_url_with_optional_shop
104
98
 
105
99
  ShopifyApp::Logger.debug("Setting Reauthorize-Url to #{url}")
106
- response.set_header("X-Shopify-API-Request-Failure-Reauthorize", "1")
107
- response.set_header("X-Shopify-API-Request-Failure-Reauthorize-Url", url)
100
+ RedirectForEmbedded.add_app_bridge_redirect_url_header(url, response)
108
101
  end
109
102
  end
110
103
 
111
104
  protected
112
105
 
113
- def jwt_shopify_domain
114
- request.env["jwt.shopify_domain"]
115
- end
116
-
117
- def jwt_shopify_user_id
118
- request.env["jwt.shopify_user_id"]
119
- end
120
-
121
106
  def host
122
107
  params[:host]
123
108
  end
@@ -273,10 +258,10 @@ module ShopifyApp
273
258
  online_token_configured?
274
259
  end
275
260
 
276
- def load_current_session(auth_header: nil, cookies: nil, is_online: false)
261
+ def load_current_session(shopify_id_token: nil, cookies: nil, is_online: false)
277
262
  return ShopifyAPI::Context.load_private_session if ShopifyAPI::Context.private?
278
263
 
279
- session_id = ShopifyAPI::Utils::SessionUtils.current_session_id(auth_header, cookies, is_online)
264
+ session_id = ShopifyAPI::Utils::SessionUtils.current_session_id(shopify_id_token, cookies, is_online)
280
265
  return nil unless session_id
281
266
 
282
267
  ShopifyApp::SessionRepository.load_session(session_id)
@@ -4,6 +4,11 @@ module ShopifyApp
4
4
  module RedirectForEmbedded
5
5
  include ShopifyApp::SanitizedParams
6
6
 
7
+ def self.add_app_bridge_redirect_url_header(url, response)
8
+ response.set_header("X-Shopify-API-Request-Failure-Reauthorize", "1")
9
+ response.set_header("X-Shopify-API-Request-Failure-Reauthorize-Url", url)
10
+ end
11
+
7
12
  private
8
13
 
9
14
  def embedded_redirect_url?
@@ -3,139 +3,109 @@
3
3
  module ShopifyApp
4
4
  module TokenExchange
5
5
  extend ActiveSupport::Concern
6
+ include ShopifyApp::AdminAPI::WithTokenRefetch
7
+ include ShopifyApp::SanitizedParams
8
+ include ShopifyApp::EmbeddedApp
6
9
 
7
- def activate_shopify_session
8
- if current_shopify_session.blank?
9
- retrieve_session_from_token_exchange
10
- end
10
+ included do
11
+ include ShopifyApp::WithShopifyIdToken
12
+ end
11
13
 
12
- if ShopifyApp.configuration.check_session_expiry_date && current_shopify_session.expired?
13
- @current_shopify_session = nil
14
- retrieve_session_from_token_exchange
15
- end
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
16
32
 
17
- begin
18
- ShopifyApp::Logger.debug("Activating Shopify session")
19
- ShopifyAPI::Context.activate_session(current_shopify_session)
20
- yield
21
- ensure
22
- ShopifyApp::Logger.debug("Deactivating session")
23
- ShopifyAPI::Context.deactivate_session
24
- end
33
+ def should_exchange_expired_token?
34
+ ShopifyApp.configuration.check_session_expiry_date && current_shopify_session.expired?
25
35
  end
26
36
 
27
37
  def current_shopify_session
28
- @current_shopify_session ||= begin
29
- session_id = ShopifyAPI::Utils::SessionUtils.current_session_id(
30
- request.headers["HTTP_AUTHORIZATION"],
31
- nil,
32
- online_token_configured?,
33
- )
34
- return nil unless session_id
35
-
36
- ShopifyApp::SessionRepository.load_session(session_id)
37
- end
38
+ return unless current_shopify_session_id
39
+
40
+ @current_shopify_session ||= ShopifyApp::SessionRepository.load_session(current_shopify_session_id)
38
41
  end
39
42
 
40
- def current_shopify_domain
41
- return if params[:shop].blank?
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
42
49
 
43
- ShopifyApp::Utils.sanitize_shop_domain(params[:shop])
50
+ def current_shopify_domain
51
+ sanitized_shop_name || current_shopify_session&.shop
44
52
  end
45
53
 
46
54
  private
47
55
 
48
56
  def retrieve_session_from_token_exchange
49
- # TODO: Right now JWT Middleware only updates env['jwt.shopify_domain'] from request headers tokens,
50
- # which won't work for new installs.
51
- # we need to update the middleware to also update the env['jwt.shopify_domain'] from the query params
52
- domain = ShopifyApp::JWT.new(session_token).shopify_domain
53
-
54
- ShopifyApp::Logger.info("Performing Token Exchange for [#{domain}] - (Offline)")
55
- session = exchange_token(
56
- shop: domain, # TODO: use jwt_shopify_domain ?
57
- session_token: session_token,
58
- requested_token_type: ShopifyAPI::Auth::TokenExchange::RequestedTokenType::OFFLINE_ACCESS_TOKEN,
59
- )
60
-
61
- if session && online_token_configured?
62
- ShopifyApp::Logger.info("Performing Token Exchange for [#{domain}] - (Online)")
63
- session = exchange_token(
64
- shop: domain, # TODO: use jwt_shopify_domain ?
65
- session_token: session_token,
66
- requested_token_type: ShopifyAPI::Auth::TokenExchange::RequestedTokenType::ONLINE_ACCESS_TOKEN,
67
- )
68
- end
69
-
70
- ShopifyApp.configuration.post_authenticate_tasks.perform(session)
57
+ @current_shopify_session = nil
58
+ ShopifyApp::Auth::TokenExchange.perform(shopify_id_token)
71
59
  end
72
60
 
73
- def exchange_token(shop:, session_token:, requested_token_type:)
74
- if session_token.blank?
75
- # respond_to_invalid_session_token
76
- return
77
- end
78
-
79
- begin
80
- session = ShopifyAPI::Auth::TokenExchange.exchange_token(
81
- shop: shop,
82
- session_token: session_token,
83
- requested_token_type: requested_token_type,
84
- )
85
- rescue ShopifyAPI::Errors::InvalidJwtTokenError
86
- # respond_to_invalid_session_token
87
- return
88
- rescue ShopifyAPI::Errors::HttpResponseError => error
89
- ShopifyApp::Logger.error(
90
- "A #{error.code} error (#{error.class}) occurred during the token exchange. Response: #{error.response.body}",
91
- )
92
- raise
93
- rescue => error
94
- ShopifyApp::Logger.error("An error occurred during the token exchange: #{error.message}")
95
- raise
96
- end
97
-
98
- if session
99
- begin
100
- ShopifyApp::SessionRepository.store_session(session)
101
- rescue ActiveRecord::RecordNotUnique
102
- ShopifyApp::Logger.debug("Session not stored due to concurrent token exchange calls")
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
103
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)
104
73
  end
105
-
106
- session
107
- end
108
-
109
- def session_token
110
- @session_token ||= id_token_header
111
74
  end
112
75
 
113
- def id_token_header
114
- request.headers["HTTP_AUTHORIZATION"]&.match(/^Bearer (.+)$/)&.[](1)
115
- end
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)
116
81
 
117
- def respond_to_invalid_session_token
118
- # TODO: Implement this method to handle invalid session tokens
82
+ bounce_url = "#{request.path}?#{patch_shopify_id_token_params.to_query}"
119
83
 
120
- # if request.xhr?
121
- # response.set_header("X-Shopify-Retry-Invalid-Session-Request", 1)
122
- # unauthorized_response = { message: :unauthorized }
123
- # render(json: { errors: [unauthorized_response] }, status: :unauthorized)
124
- # else
125
- # patch_session_token_url = "#{ShopifyAPI::Context.host}/patch_session_token"
126
- # patch_session_token_params = request.query_parameters.except(:id_token)
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
127
86
 
128
- # bounce_url = "#{ShopifyAPI::Context.host}#{request.path}?#{patch_session_token_params.to_query}"
129
-
130
- # # App Bridge will trigger a fetch to the URL in shopify-reload, with a new session token in headers
131
- # patch_session_token_params["shopify-reload"] = bounce_url
87
+ redirect_to(
88
+ "#{patch_shopify_id_token_url}?#{patch_shopify_id_token_params.to_query}",
89
+ allow_other_host: true,
90
+ )
91
+ end
132
92
 
133
- # redirect_to("#{patch_session_token_url}?#{patch_session_token_params.to_query}", allow_other_host: true)
134
- # end
93
+ def missing_embedded_param?
94
+ !params[:embedded].present? || params[:embedded] != "1"
135
95
  end
136
96
 
137
97
  def online_token_configured?
138
98
  ShopifyApp.configuration.online_token_configured?
139
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
140
110
  end
141
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\s+(.*?)$/
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 authorization_header(env)
13
+ return call_next(env) unless ShopifyApp.configuration.embedded_app?
13
14
 
14
- token = extract_token(env)
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 authorization_header(env)
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 extract_token(env)
32
- match = authorization_header(env).match(TOKEN_REGEX)
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 = ShopifyApp::JWT.new(token)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShopifyApp
4
- VERSION = "22.1.0"
4
+ VERSION = "22.2.1"
5
5
  end
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"
@@ -53,9 +56,11 @@ module ShopifyApp
53
56
  require "shopify_app/controller_concerns/app_proxy_verification"
54
57
  require "shopify_app/controller_concerns/webhook_verification"
55
58
  require "shopify_app/controller_concerns/token_exchange"
59
+ require "shopify_app/controller_concerns/with_shopify_id_token"
56
60
 
57
61
  # Auth helpers
58
62
  require "shopify_app/auth/post_authenticate_tasks"
63
+ require "shopify_app/auth/token_exchange"
59
64
 
60
65
  # jobs
61
66
  require "shopify_app/jobs/webhooks_manager_job"
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shopify_app",
3
- "version": "22.1.0",
3
+ "version": "22.2.1",
4
4
  "repository": "git@github.com:Shopify/shopify_app.git",
5
5
  "author": "Shopify",
6
6
  "license": "MIT",
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.1.0", "< 15.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")