shopify_app 18.1.2 → 19.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +2 -2
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +18 -0
  5. data/Gemfile +3 -2
  6. data/Gemfile.lock +120 -134
  7. data/Rakefile +4 -3
  8. data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +1 -1
  9. data/app/controllers/shopify_app/authenticated_controller.rb +1 -0
  10. data/app/controllers/shopify_app/callback_controller.rb +46 -133
  11. data/app/controllers/shopify_app/sessions_controller.rb +25 -137
  12. data/app/controllers/shopify_app/webhooks_controller.rb +5 -23
  13. data/config/routes.rb +6 -12
  14. data/docs/Troubleshooting.md +0 -3
  15. data/docs/Upgrading.md +87 -5
  16. data/docs/shopify_app/webhooks.md +1 -1
  17. data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +10 -9
  18. data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +1 -0
  19. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +4 -3
  20. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +13 -12
  21. data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +9 -1
  22. data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +7 -6
  23. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +2 -1
  24. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +1 -1
  25. data/lib/generators/shopify_app/authenticated_controller/authenticated_controller_generator.rb +3 -3
  26. data/lib/generators/shopify_app/controllers/controllers_generator.rb +4 -3
  27. data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +11 -15
  28. data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +2 -2
  29. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +3 -3
  30. data/lib/generators/shopify_app/install/install_generator.rb +25 -74
  31. data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +1 -1
  32. data/lib/generators/shopify_app/install/templates/session_store.rb +2 -1
  33. data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +20 -5
  34. data/lib/generators/shopify_app/products_controller/products_controller_generator.rb +3 -3
  35. data/lib/generators/shopify_app/products_controller/templates/products_controller.rb +1 -1
  36. data/lib/generators/shopify_app/rotate_shopify_token_job/rotate_shopify_token_job_generator.rb +4 -4
  37. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +1 -0
  38. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +1 -1
  39. data/lib/generators/shopify_app/routes/routes_generator.rb +6 -5
  40. data/lib/generators/shopify_app/routes/templates/routes.rb +5 -5
  41. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +11 -10
  42. data/lib/generators/shopify_app/shop_model/templates/shop.rb +1 -0
  43. data/lib/generators/shopify_app/shopify_app_generator.rb +4 -3
  44. data/lib/generators/shopify_app/user_model/templates/user.rb +1 -0
  45. data/lib/generators/shopify_app/user_model/user_model_generator.rb +11 -10
  46. data/lib/generators/shopify_app/views/views_generator.rb +4 -3
  47. data/lib/shopify_app/access_scopes/shop_strategy.rb +2 -2
  48. data/lib/shopify_app/access_scopes/user_strategy.rb +4 -4
  49. data/lib/shopify_app/configuration.rb +5 -17
  50. data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +4 -3
  51. data/lib/shopify_app/controller_concerns/csrf_protection.rb +2 -1
  52. data/lib/shopify_app/controller_concerns/embedded_app.rb +4 -3
  53. data/lib/shopify_app/controller_concerns/itp.rb +3 -3
  54. data/lib/shopify_app/controller_concerns/localization.rb +1 -0
  55. data/lib/shopify_app/controller_concerns/login_protection.rb +56 -70
  56. data/lib/shopify_app/controller_concerns/payload_verification.rb +3 -2
  57. data/lib/shopify_app/controller_concerns/webhook_verification.rb +2 -1
  58. data/lib/shopify_app/engine.rb +7 -15
  59. data/lib/shopify_app/jobs/scripttags_manager_job.rb +2 -2
  60. data/lib/shopify_app/jobs/webhooks_manager_job.rb +4 -5
  61. data/lib/shopify_app/managers/scripttags_manager.rb +11 -4
  62. data/lib/shopify_app/managers/webhooks_manager.rb +42 -44
  63. data/lib/shopify_app/middleware/jwt_middleware.rb +5 -4
  64. data/lib/shopify_app/session/in_memory_session_store.rb +1 -0
  65. data/lib/shopify_app/session/in_memory_shop_session_store.rb +2 -1
  66. data/lib/shopify_app/session/in_memory_user_session_store.rb +1 -0
  67. data/lib/shopify_app/session/jwt.rb +9 -8
  68. data/lib/shopify_app/session/null_user_session_store.rb +2 -1
  69. data/lib/shopify_app/session/session_repository.rb +37 -0
  70. data/lib/shopify_app/session/session_storage.rb +4 -6
  71. data/lib/shopify_app/session/shop_session_storage.rb +6 -6
  72. data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +7 -8
  73. data/lib/shopify_app/session/user_session_storage.rb +19 -6
  74. data/lib/shopify_app/session/user_session_storage_with_scopes.rb +22 -9
  75. data/lib/shopify_app/test_helpers/all.rb +2 -1
  76. data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +4 -3
  77. data/lib/shopify_app/utils.rb +2 -9
  78. data/lib/shopify_app/version.rb +2 -1
  79. data/lib/shopify_app.rb +35 -40
  80. data/package.json +1 -1
  81. data/shopify_app.gemspec +21 -20
  82. data/yarn.lock +9 -9
  83. metadata +43 -48
  84. data/lib/generators/shopify_app/install/templates/omniauth.rb +0 -4
  85. data/lib/generators/shopify_app/install/templates/shopify_provider.rb.tt +0 -8
  86. data/lib/generators/shopify_app/install/templates/user_agent.rb +0 -6
  87. data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +0 -34
  88. data/lib/shopify_app/omniauth/omniauth_configuration.rb +0 -64
@@ -6,15 +6,34 @@ module ShopifyApp
6
6
  include ShopifyApp::LoginProtection
7
7
 
8
8
  def callback
9
- return respond_with_error if invalid_request?
9
+ begin
10
+ filtered_params = request.parameters.symbolize_keys.slice(:code, :shop, :timestamp, :state, :host, :hmac)
11
+
12
+ auth_result = ShopifyAPI::Auth::Oauth.validate_auth_callback(
13
+ cookies: {
14
+ ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME =>
15
+ cookies.encrypted[ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME],
16
+ },
17
+ auth_query: ShopifyAPI::Auth::Oauth::AuthQuery.new(**filtered_params)
18
+ )
19
+ rescue
20
+ return respond_with_error
21
+ end
22
+
23
+ cookies.encrypted[auth_result[:cookie].name] = {
24
+ expires: auth_result[:cookie].expires,
25
+ secure: true,
26
+ http_only: true,
27
+ value: auth_result[:cookie].value,
28
+ }
10
29
 
11
- store_access_token_and_build_session
30
+ session[:shopify_user_id] = auth_result[:session].associated_user.id if auth_result[:session].online?
12
31
 
13
- if start_user_token_flow?
32
+ if start_user_token_flow?(auth_result[:session])
14
33
  return respond_with_user_token_flow
15
34
  end
16
35
 
17
- perform_post_authenticate_jobs
36
+ perform_post_authenticate_jobs(auth_result[:session])
18
37
 
19
38
  respond_successfully
20
39
  end
@@ -22,162 +41,56 @@ module ShopifyApp
22
41
  private
23
42
 
24
43
  def respond_successfully
25
- if jwt_request?
26
- head(:ok)
27
- else
28
- redirect_to(return_address)
29
- end
30
- end
31
-
32
- def respond_with_user_token_flow
33
- redirect_to(login_url_with_optional_shop)
34
- end
35
-
36
- def store_access_token_and_build_session
37
- if native_browser_request?
38
- reset_session_options
39
- end
40
- set_shopify_session
41
- end
42
-
43
- def invalid_request?
44
- return true unless auth_hash
45
-
46
- jwt_request? && !valid_jwt_auth?
47
- end
48
-
49
- def native_browser_request?
50
- !jwt_request?
51
- end
52
-
53
- def perform_post_authenticate_jobs
54
- install_webhooks
55
- install_scripttags
56
- perform_after_authenticate_job
44
+ redirect_to(return_address)
57
45
  end
58
46
 
59
47
  def respond_with_error
60
- if jwt_request?
61
- head(:unauthorized)
62
- else
63
- flash[:error] = I18n.t('could_not_log_in')
64
- redirect_to(login_url_with_optional_shop)
65
- end
48
+ flash[:error] = I18n.t("could_not_log_in")
49
+ redirect_to(login_url_with_optional_shop)
66
50
  end
67
51
 
68
- # Override user_session_by_cookie from LoginProtection to bypass allow_cookie_authentication
69
- # setting check because session cookies are justified at top level
70
- def user_session_by_cookie
71
- return unless session[:user_id].present?
72
- ShopifyApp::SessionRepository.retrieve_user_session(session[:user_id])
52
+ def respond_with_user_token_flow
53
+ redirect_to(login_url_with_optional_shop)
73
54
  end
74
55
 
75
- def start_user_token_flow?
76
- if jwt_request?
77
- false
78
- else
79
- return false unless ShopifyApp::SessionRepository.user_storage.present?
80
- update_user_access_scopes?
81
- end
56
+ def start_user_token_flow?(shopify_session)
57
+ return false unless ShopifyApp::SessionRepository.user_storage.present?
58
+ return false if shopify_session.online?
59
+ update_user_access_scopes?
82
60
  end
83
61
 
84
62
  def update_user_access_scopes?
85
- return true if user_session.blank?
86
- user_access_scopes_strategy.update_access_scopes?(user_id: session[:user_id])
63
+ return true if session[:shopify_user_id].nil?
64
+ user_access_scopes_strategy.update_access_scopes?(shopify_user_id: session[:shopify_user_id])
87
65
  end
88
66
 
89
67
  def user_access_scopes_strategy
90
68
  ShopifyApp.configuration.user_access_scopes_strategy
91
69
  end
92
70
 
93
- def jwt_request?
94
- jwt_shopify_domain || jwt_shopify_user_id
95
- end
96
-
97
- def valid_jwt_auth?
98
- auth_hash && jwt_shopify_domain == shop_name && jwt_shopify_user_id == associated_user_id
71
+ def perform_post_authenticate_jobs(session)
72
+ install_webhooks(session)
73
+ install_scripttags(session)
74
+ perform_after_authenticate_job(session)
99
75
  end
100
76
 
101
- def auth_hash
102
- request.env['omniauth.auth']
103
- end
104
-
105
- def shop_name
106
- auth_hash.uid
107
- end
108
-
109
- def offline_access_token
110
- ShopifyApp::SessionRepository.retrieve_shop_session_by_shopify_domain(shop_name)&.token
111
- end
112
-
113
- def online_access_token
114
- ShopifyApp::SessionRepository.retrieve_user_session_by_shopify_user_id(associated_user_id)&.token
115
- end
116
-
117
- def associated_user
118
- return unless auth_hash.dig('extra', 'associated_user').present?
119
-
120
- auth_hash['extra']['associated_user'].merge('scope' => auth_hash['extra']['associated_user_scope'])
121
- end
122
-
123
- def associated_user_id
124
- associated_user && associated_user['id']
125
- end
126
-
127
- def token
128
- auth_hash['credentials']['token']
129
- end
130
-
131
- def access_scopes
132
- auth_hash.dig('extra', 'scope')
133
- end
134
-
135
- def reset_session_options
136
- request.session_options[:renew] = true
137
- session.delete(:_csrf_token)
138
- end
139
-
140
- def set_shopify_session
141
- session_store = ShopifyAPI::Session.new(
142
- domain: shop_name,
143
- token: token,
144
- api_version: ShopifyApp.configuration.api_version,
145
- access_scopes: access_scopes
146
- )
147
-
148
- session[:shopify_user] = associated_user
149
- if session[:shopify_user].present?
150
- session[:shop_id] = nil if shop_session && shop_session.domain != shop_name
151
- session[:user_id] = ShopifyApp::SessionRepository.store_user_session(session_store, associated_user)
152
- else
153
- session[:shop_id] = ShopifyApp::SessionRepository.store_shop_session(session_store)
154
- session[:user_id] = nil if user_session && user_session.domain != shop_name
155
- end
156
- session[:shopify_domain] = shop_name
157
- session[:user_session] = auth_hash&.extra&.session
158
- end
159
-
160
- def install_webhooks
77
+ def install_webhooks(session)
161
78
  return unless ShopifyApp.configuration.has_webhooks?
162
79
 
163
- WebhooksManager.queue(
164
- shop_name,
165
- offline_access_token || online_access_token,
166
- ShopifyApp.configuration.webhooks
167
- )
80
+ WebhooksManager.queue(session.shop, session.access_token)
168
81
  end
169
82
 
170
- def install_scripttags
83
+ def install_scripttags(session)
171
84
  return unless ShopifyApp.configuration.has_scripttags?
172
85
 
173
86
  ScripttagsManager.queue(
174
- shop_name,
175
- offline_access_token || online_access_token,
87
+ session.shop,
88
+ session.access_token,
176
89
  ShopifyApp.configuration.scripttags
177
90
  )
178
91
  end
179
92
 
180
- def perform_after_authenticate_job
93
+ def perform_after_authenticate_job(session)
181
94
  config = ShopifyApp.configuration.after_authenticate_job
182
95
 
183
96
  return unless config && config[:job].present?
@@ -186,9 +99,9 @@ module ShopifyApp
186
99
  job = job.constantize if job.is_a?(String)
187
100
 
188
101
  if config[:inline] == true
189
- job.perform_now(shop_domain: session[:shopify_domain])
102
+ job.perform_now(shop_domain: session.shop)
190
103
  else
191
- job.perform_later(shop_domain: session[:shopify_domain])
104
+ job.perform_later(shop_domain: session.shop)
192
105
  end
193
106
  end
194
107
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class SessionsController < ActionController::Base
4
5
  include ShopifyApp::LoginProtection
@@ -6,7 +7,7 @@ module ShopifyApp
6
7
  layout false, only: :new
7
8
 
8
9
  after_action only: [:new, :create] do |controller|
9
- controller.response.headers.except!('X-Frame-Options')
10
+ controller.response.headers.except!("X-Frame-Options")
10
11
  end
11
12
 
12
13
  def new
@@ -17,43 +18,14 @@ module ShopifyApp
17
18
  authenticate
18
19
  end
19
20
 
20
- def enable_cookies
21
- return unless validate_shop_presence
22
-
23
- render(:enable_cookies, layout: false, locals: {
24
- does_not_have_storage_access_url: top_level_interaction_path(
25
- shop: sanitized_shop_name,
26
- host: host,
27
- return_to: params[:return_to]
28
- ),
29
- has_storage_access_url: login_url_with_optional_shop(top_level: true),
30
- app_target_url: granted_storage_access_path(
31
- shop: sanitized_shop_name,
32
- host: host,
33
- return_to: params[:return_to]
34
- ),
35
- current_shopify_domain: current_shopify_domain,
36
- })
37
- end
38
-
39
21
  def top_level_interaction
40
22
  @url = login_url_with_optional_shop(top_level: true)
41
23
  validate_shop_presence
42
24
  end
43
25
 
44
- def granted_storage_access
45
- return unless validate_shop_presence
46
-
47
- session['shopify.granted_storage_access'] = true
48
-
49
- copy_return_to_param_to_session
50
-
51
- redirect_to(return_address_with_params({ shop: @shop }))
52
- end
53
-
54
26
  def destroy
55
27
  reset_session
56
- flash[:notice] = I18n.t('.logged_out')
28
+ flash[:notice] = I18n.t(".logged_out")
57
29
  redirect_to(login_url_with_optional_shop)
58
30
  end
59
31
 
@@ -61,69 +33,31 @@ module ShopifyApp
61
33
 
62
34
  def authenticate
63
35
  return render_invalid_shop_error unless sanitized_shop_name.present?
64
- session['shopify.omniauth_params'] = { shop: sanitized_shop_name }
65
36
 
66
37
  copy_return_to_param_to_session
67
38
 
68
- set_user_tokens_option
69
-
70
- if user_agent_can_partition_cookies
71
- authenticate_with_partitioning
39
+ if top_level?
40
+ start_oauth
72
41
  else
73
- authenticate_normally
42
+ redirect_auth_to_top_level
74
43
  end
75
44
  end
76
45
 
77
- def authenticate_normally
78
- if request_storage_access?
79
- redirect_to_request_storage_access
80
- elsif authenticate_in_context?
81
- authenticate_in_context
82
- else
83
- authenticate_at_top_level
84
- end
85
- end
86
-
87
- def authenticate_with_partitioning
88
- if session['shopify.cookies_persist']
89
- clear_top_level_oauth_cookie
90
- authenticate_in_context
91
- else
92
- set_top_level_oauth_cookie
93
- enable_cookie_access
94
- end
95
- end
96
-
97
- # Override shop_session_by_cookie from LoginProtection to bypass allow_cookie_authentication
98
- # setting check because session cookies are justified at top level
99
- def shop_session_by_cookie
100
- return unless session[:shop_id].present?
101
- ShopifyApp::SessionRepository.retrieve_shop_session(session[:shop_id])
102
- end
103
-
104
- # rubocop:disable Lint/SuppressedException
105
- def set_user_tokens_option
106
- current_shop_session = shop_session
107
-
108
- if current_shop_session.blank?
109
- session[:user_tokens] = false
110
- return
111
- end
112
-
113
- session[:user_tokens] = ShopifyApp::SessionRepository.user_storage.present?
46
+ def start_oauth
47
+ auth_attributes = ShopifyAPI::Auth::Oauth.begin_auth(
48
+ shop: sanitized_shop_name,
49
+ redirect_path: "/auth/shopify/callback",
50
+ is_online: user_session_expected?
51
+ )
52
+ cookies.encrypted[auth_attributes[:cookie].name] = {
53
+ expires: auth_attributes[:cookie].expires,
54
+ secure: true,
55
+ http_only: true,
56
+ value: auth_attributes[:cookie].value,
57
+ }
114
58
 
115
- ShopifyAPI::Session.temp(
116
- domain: current_shop_session.domain,
117
- token: current_shop_session.token,
118
- api_version: current_shop_session.api_version
119
- ) do
120
- ShopifyAPI::Metafield.find(:token_validity_bogus_check)
121
- end
122
- rescue ActiveResource::UnauthorizedAccess
123
- session[:user_tokens] = false
124
- rescue StandardError
59
+ redirect_to(auth_attributes[:auth_route], allow_other_host: true)
125
60
  end
126
- # rubocop:enable Lint/SuppressedException
127
61
 
128
62
  def validate_shop_presence
129
63
  @shop = sanitized_shop_name
@@ -136,67 +70,21 @@ module ShopifyApp
136
70
  end
137
71
 
138
72
  def copy_return_to_param_to_session
139
- session[:return_to] = RedirectSafely.make_safe(params[:return_to], '/') if params[:return_to]
73
+ session[:return_to] = RedirectSafely.make_safe(params[:return_to], "/") if params[:return_to]
140
74
  end
141
75
 
142
76
  def render_invalid_shop_error
143
- flash[:error] = I18n.t('invalid_shop_url')
77
+ flash[:error] = I18n.t("invalid_shop_url")
144
78
  redirect_to(return_address)
145
79
  end
146
80
 
147
- def enable_cookie_access
148
- fullpage_redirect_to(enable_cookies_path(
149
- shop: sanitized_shop_name,
150
- host: host,
151
- return_to: session[:return_to]
152
- ))
153
- end
154
-
155
- def authenticate_in_context
156
- post_redirect_to_auth_shopify
157
- end
158
-
159
- def post_redirect_to_auth_shopify
160
- render('shopify_app/shared/post_redirect_to_auth_shopify', layout: false)
161
- end
162
-
163
- def authenticate_at_top_level
164
- fullpage_redirect_to(login_url_with_optional_shop(top_level: true))
165
- end
166
-
167
- def authenticate_in_context?
81
+ def top_level?
168
82
  return true unless ShopifyApp.configuration.embedded_app?
169
- params[:top_level]
83
+ !params[:top_level].nil?
170
84
  end
171
85
 
172
- def request_storage_access?
173
- return false unless ShopifyApp.configuration.embedded_app?
174
- return false if params[:top_level]
175
- return false if user_agent_is_mobile
176
- return false if user_agent_is_pos
177
-
178
- !session['shopify.granted_storage_access']
179
- end
180
-
181
- def redirect_to_request_storage_access
182
- render(
183
- :request_storage_access,
184
- layout: false,
185
- locals: {
186
- does_not_have_storage_access_url: top_level_interaction_path(
187
- shop: sanitized_shop_name,
188
- host: host,
189
- return_to: session[:return_to]
190
- ),
191
- has_storage_access_url: login_url_with_optional_shop(top_level: true),
192
- app_target_url: granted_storage_access_path(
193
- shop: sanitized_shop_name,
194
- host: host,
195
- return_to: session[:return_to]
196
- ),
197
- current_shopify_domain: current_shopify_domain,
198
- }
199
- )
86
+ def redirect_auth_to_top_level
87
+ fullpage_redirect_to(login_url_with_optional_shop(top_level: true))
200
88
  end
201
89
  end
202
90
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class MissingWebhookJobError < StandardError; end
4
5
 
@@ -7,30 +8,11 @@ module ShopifyApp
7
8
 
8
9
  def receive
9
10
  params.permit!
10
- webhook_job_klass.perform_later(shop_domain: shop_domain, webhook: webhook_params.to_h)
11
- head(:ok)
12
- end
13
-
14
- private
15
11
 
16
- def webhook_params
17
- params.except(:controller, :action, :type)
18
- end
19
-
20
- def webhook_job_klass
21
- webhook_job_klass_name.safe_constantize || raise(ShopifyApp::MissingWebhookJobError)
22
- end
23
-
24
- def webhook_job_klass_name(type = webhook_type)
25
- [webhook_namespace, "#{type}_job"].compact.join('/').classify
26
- end
27
-
28
- def webhook_type
29
- params[:type]
30
- end
31
-
32
- def webhook_namespace
33
- ShopifyApp.configuration.webhook_jobs_namespace
12
+ ShopifyAPI::Webhooks::Registry.process(
13
+ ShopifyAPI::Webhooks::Request.new(raw_body: request.raw_post, headers: request.headers.to_h)
14
+ )
15
+ head(:ok)
34
16
  end
35
17
  end
36
18
  end
data/config/routes.rb CHANGED
@@ -1,23 +1,17 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  ShopifyApp::Engine.routes.draw do
3
4
  controller :sessions do
4
- get 'login' => :new, :as => :login
5
- post 'login' => :create, :as => :authenticate
6
- get 'enable_cookies' => :enable_cookies, :as => :enable_cookies
7
- get 'top_level_interaction' =>
8
- :top_level_interaction,
9
- :as => :top_level_interaction
10
- get 'granted_storage_access' =>
11
- :granted_storage_access,
12
- :as => :granted_storage_access
13
- get 'logout' => :destroy, :as => :logout
5
+ get "login" => :new, :as => :login
6
+ post "login" => :create, :as => :authenticate
7
+ get "logout" => :destroy, :as => :logout
14
8
  end
15
9
 
16
10
  controller :callback do
17
- get 'auth/shopify/callback' => :callback
11
+ get "auth/shopify/callback" => :callback
18
12
  end
19
13
 
20
14
  namespace :webhooks do
21
- post ':type' => :receive
15
+ post ":type" => :receive
22
16
  end
23
17
  end
@@ -87,9 +87,6 @@ Edit `config/initializer/shopify_app.rb` and ensure the following configurations
87
87
  ```diff
88
88
  + config.embedded_app = true
89
89
 
90
- + config.allow_jwt_authentication = true
91
- + config.allow_cookie_authentication = false
92
-
93
90
  # This line should already exist if you're using shopify_app gem 13.x.x+
94
91
  + config.shop_session_repository = 'Shop'
95
92
  ```
data/docs/Upgrading.md CHANGED
@@ -1,10 +1,12 @@
1
- # Upgrading
1
+ # Upgrading
2
2
 
3
3
  This file documents important changes needed to upgrade your app's Shopify App version to a new major version.
4
4
 
5
5
  #### Table of contents
6
6
 
7
- [Upgrading to `v18.1.1`](#upgrading-to-v1811)
7
+ [Upgrading to `v19.0.0`](#upgrading-to-v1900)
8
+
9
+ [Upgrading to `v18.1.2`](#upgrading-to-v1812)
8
10
 
9
11
  [Upgrading to `v17.2.0`](#upgrading-to-v1720)
10
12
 
@@ -14,10 +16,90 @@ This file documents important changes needed to upgrade your app's Shopify App v
14
16
 
15
17
  [Upgrading from `v8.6` to `v9.0.0`](#upgrading-from-v86-to-v900)
16
18
 
19
+ ## Upgrading to `v19.0.0`
20
+
21
+ This update moves API authentication logic from this gem to the [`shopify_api`](https://github.com/Shopify/shopify_api)
22
+ gem.
23
+
24
+ ### High-level process
25
+
26
+ * Delete `config/initializers/omniauth.rb` as apps no longer need to initialize `OmniAuth` directly.
27
+ * Delete `config/initializers/user_agent.rb` as `shopify_app` will set the right `User-Agent` header for interacting
28
+ with the Shopify API. If the app requires further information in the `User-Agent` header beyond what Shopify API
29
+ requires, specify this in the `ShopifyAPI::Context.user_agent_prefix` setting.
30
+ * Remove `allow_jwt_authentication=` and `allow_cookie_authentication=` invocations from
31
+ `config/initializers/shopify_app.rb` as the decision logic for which authentication method to use is now handled
32
+ internally by the `shopify_api` gem, using the `ShopifyAPI::Context.embedded_app` setting.
33
+ * `v19.0.0` updates the `shopify_api` dependency to `10.0.0`. This version of `shopify_api` has breaking changes. See
34
+ the documentation for addressing these breaking changes on GitHub [here](https://github.com/Shopify/shopify_api#breaking-change-notice-for-version-1000).
35
+
36
+ ### Specific cases
37
+
38
+ #### Webhook Jobs
39
+
40
+ Add a new `handle` method to existing webhook jobs to go through the updated `shopify_api` gem.
41
+
42
+ ```ruby
43
+ class MyWebhookJob < ActiveJob::Base
44
+ extend ShopifyAPI::Webhooks::Handler
45
+
46
+ class << self
47
+ # new handle function
48
+ def handle(topic:, shop:, body:)
49
+ # delegate to pre-existing perform_later function
50
+ perform_later(topic: topic, shop_domain: shop, webhook: body)
51
+ end
52
+ end
53
+
54
+ # original perform function
55
+ def perform(topic:, shop_domain:, webhook:)
56
+ # ...
57
+ ```
58
+
59
+ #### Temporary sessions
17
60
 
18
- ## Upgrading to `v18.1.1`
61
+ The new `shopify_api` gem offers a utility to temporarily create sessions for interacting with the API within a block.
62
+ This is useful for interacting with the Shopify API outside of the context of a subclass of `AuthenticatedController`.
63
+
64
+ ```ruby
65
+ ShopifyAPI::Auth::Session.temp(shop: shop_domain, access_token: shop_token) do |session|
66
+ # make invocations to the API
67
+ end
68
+ ```
69
+
70
+ Within a subclass of `AuthenticatedController`, the `current_shopify_session` function will return the current active
71
+ Shopify API session, or `nil` if no such session is available.
72
+
73
+ #### Setting up `ShopifyAPI::Context`
74
+
75
+ The `shopify_app` initializer must configure the `ShopifyAPI::Context`. The Rails generator will
76
+ generate a block in the `shopify_app` initializer. To do so manually, ensure the following is
77
+ part of the `after_initialize` block in `shopify_app.rb`.
78
+
79
+ ```ruby
80
+ Rails.application.config.after_initialize do
81
+ if ShopifyApp.configuration.api_key.present? && ShopifyApp.configuration.secret.present?
82
+ ShopifyAPI::Context.setup(
83
+ api_key: ShopifyApp.configuration.api_key,
84
+ api_secret_key: ShopifyApp.configuration.secret,
85
+ api_version: ShopifyApp.configuration.api_version,
86
+ host_name: URI(ENV.fetch('HOST', '')).host || '',
87
+ scope: ShopifyApp.configuration.scope,
88
+ is_private: !ENV.fetch('SHOPIFY_APP_PRIVATE_SHOP', '').empty?,
89
+ is_embedded: ShopifyApp.configuration.embedded_app,
90
+ session_storage: ShopifyApp::SessionRepository,
91
+ logger: Rails.logger,
92
+ private_shop: ENV.fetch('SHOPIFY_APP_PRIVATE_SHOP', nil),
93
+ user_agent_prefix: "ShopifyApp/#{ShopifyApp::VERSION}"
94
+ )
95
+
96
+ ShopifyApp::WebhooksManager.add_registrations
97
+ end
98
+ end
99
+ ```
100
+ ## Upgrading to `v18.1.2`
19
101
 
20
- Version 18.1.1 replaces the deprecated EASDK redirect with an App Bridge redirect when attempting to break out of an iframe. This happens when an app is installed, requires new access scopes, or re-authentication because the login session is expired.
102
+ Version 18.1.2 replaces the deprecated EASDK redirect with an App Bridge 2 redirect when attempting to break out of an iframe. This happens when an app is installed, requires new access scopes, or re-authentication because the login session is expired.
21
103
 
22
104
  ## Upgrading to `v17.2.0`
23
105
 
@@ -127,7 +209,7 @@ is changed to
127
209
 
128
210
  ### ShopifyAPI changes
129
211
 
130
- You will need to also follow the ShopifyAPI [upgrade guide](https://github.com/Shopify/shopify_api/blob/master/README.md#-breaking-change-notice-for-version-700-) to ensure your app is ready to work with API versioning.
212
+ You will need to also follow the ShopifyAPI [upgrade guide](https://github.com/Shopify/shopify_api/blob/master/README.md#-breaking-change-notice-for-version-700-) to ensure your app is ready to work with API versioning.
131
213
 
132
214
  [dashboard]:https://partners.shopify.com
133
215
  [app-bridge]:https://shopify.dev/apps/tools/app-bridge
@@ -66,7 +66,7 @@ The WebhooksManager uses ActiveJob. If ActiveJob is not configured then by defau
66
66
  ShopifyApp can create webhooks for you using the `add_webhook` generator. This will add the new webhook to your config and create the required job class for you.
67
67
 
68
68
  ```
69
- rails g shopify_app:add_webhook -t carts/update -a https://example.com/webhooks/carts_update
69
+ rails g shopify_app:add_webhook -t carts/update -a /webhooks/carts_update
70
70
  ```
71
71
 
72
72
  Where `-t` is the topic and `-a` is the address the webhook should be sent to.