shopify_app 12.0.5 → 13.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -6
  3. data/.travis.yml +4 -3
  4. data/CHANGELOG.md +33 -1
  5. data/Gemfile +3 -0
  6. data/README.md +109 -41
  7. data/Rakefile +1 -0
  8. data/app/controllers/concerns/shopify_app/authenticated.rb +1 -1
  9. data/app/controllers/shopify_app/authenticated_controller.rb +1 -0
  10. data/app/controllers/shopify_app/callback_controller.rb +15 -11
  11. data/app/controllers/shopify_app/sessions_controller.rb +35 -9
  12. data/app/controllers/shopify_app/webhooks_controller.rb +6 -5
  13. data/config/locales/fi.yml +1 -1
  14. data/config/locales/nl.yml +7 -7
  15. data/config/routes.rb +1 -0
  16. data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +5 -3
  17. data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +1 -0
  18. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +2 -1
  19. data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +4 -4
  20. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +5 -4
  21. data/lib/generators/shopify_app/add_webhook/templates/{webhook_job.rb → webhook_job.rb.tt} +5 -0
  22. data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +4 -3
  23. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +3 -3
  24. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +10 -9
  25. data/lib/generators/shopify_app/controllers/controllers_generator.rb +1 -0
  26. data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +4 -3
  27. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +1 -1
  28. data/lib/generators/shopify_app/install/install_generator.rb +10 -9
  29. data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +1 -1
  30. data/lib/generators/shopify_app/install/templates/omniauth.rb +2 -1
  31. data/lib/generators/shopify_app/install/templates/{shopify_app.rb → shopify_app.rb.tt} +1 -1
  32. data/lib/generators/shopify_app/install/templates/shopify_provider.rb +1 -1
  33. data/lib/generators/shopify_app/install/templates/user_agent.rb +2 -1
  34. data/lib/generators/shopify_app/routes/routes_generator.rb +1 -0
  35. data/lib/generators/shopify_app/routes/templates/routes.rb +10 -9
  36. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +12 -7
  37. data/lib/generators/shopify_app/shop_model/templates/shop.rb +2 -1
  38. data/lib/generators/shopify_app/shopify_app_generator.rb +4 -3
  39. data/lib/generators/shopify_app/user_model/templates/user.rb +2 -1
  40. data/lib/generators/shopify_app/user_model/user_model_generator.rb +12 -7
  41. data/lib/generators/shopify_app/views/views_generator.rb +1 -0
  42. data/lib/shopify_app.rb +9 -4
  43. data/lib/shopify_app/configuration.rb +21 -17
  44. data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +3 -2
  45. data/lib/shopify_app/controller_concerns/embedded_app.rb +3 -2
  46. data/lib/shopify_app/controller_concerns/localization.rb +1 -0
  47. data/lib/shopify_app/controller_concerns/login_protection.rb +72 -27
  48. data/lib/shopify_app/controller_concerns/webhook_verification.rb +2 -1
  49. data/lib/shopify_app/engine.rb +1 -0
  50. data/lib/shopify_app/jobs/scripttags_manager_job.rb +1 -1
  51. data/lib/shopify_app/jobs/webhooks_manager_job.rb +1 -1
  52. data/lib/shopify_app/managers/scripttags_manager.rb +4 -3
  53. data/lib/shopify_app/managers/webhooks_manager.rb +4 -3
  54. data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +2 -1
  55. data/lib/shopify_app/session/in_memory_session_store.rb +7 -3
  56. data/lib/shopify_app/session/in_memory_shop_session_store.rb +14 -0
  57. data/lib/shopify_app/session/in_memory_user_session_store.rb +14 -0
  58. data/lib/shopify_app/session/jwt.rb +61 -0
  59. data/lib/shopify_app/session/null_user_session_store.rb +22 -0
  60. data/lib/shopify_app/session/session_repository.rb +36 -14
  61. data/lib/shopify_app/session/session_storage.rb +1 -10
  62. data/lib/shopify_app/session/shop_session_storage.rb +42 -0
  63. data/lib/shopify_app/session/user_session_storage.rb +42 -0
  64. data/lib/shopify_app/test_helpers/all.rb +2 -0
  65. data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +17 -0
  66. data/lib/shopify_app/utils.rb +6 -5
  67. data/lib/shopify_app/version.rb +2 -1
  68. data/package-lock.json +1231 -1210
  69. data/package.json +1 -1
  70. data/shopify_app.gemspec +11 -7
  71. data/yarn.lock +3 -3
  72. metadata +33 -12
  73. data/lib/shopify_app/session/storage_strategies/shop_storage_strategy.rb +0 -23
  74. data/lib/shopify_app/session/storage_strategies/user_storage_strategy.rb +0 -24
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyApp
2
3
  class AuthenticatedController < ActionController::Base
3
4
  include ShopifyApp::Authenticated
@@ -8,11 +8,16 @@ module ShopifyApp
8
8
  def callback
9
9
  if auth_hash
10
10
  login_shop
11
+
12
+ if ShopifyApp::SessionRepository.user_storage.present? && user_session.blank?
13
+ return redirect_to(login_url_with_optional_shop)
14
+ end
15
+
11
16
  install_webhooks
12
17
  install_scripttags
13
18
  perform_after_authenticate_job
14
19
 
15
- redirect_to return_address
20
+ redirect_to(return_address)
16
21
  else
17
22
  flash[:error] = I18n.t('could_not_log_in')
18
23
  redirect_to(login_url_with_optional_shop)
@@ -55,16 +60,15 @@ module ShopifyApp
55
60
  token: token,
56
61
  api_version: ShopifyApp.configuration.api_version
57
62
  )
58
- session[:shopify] = ShopifyApp::SessionRepository.store(session_store, user: associated_user)
59
- session[:shopify_domain] = shop_name
60
- session[:shopify_user] = associated_user
61
63
 
62
- if ShopifyApp.configuration.per_user_tokens?
63
- # Adds the user_session to the session to determine if the logged in user has changed
64
- user_session = auth_hash&.extra&.session
65
- raise IndexError, "Missing user session signature" if user_session.nil?
66
- session[:user_session] = user_session
64
+ session[:shopify_user] = associated_user
65
+ if session[:shopify_user].present?
66
+ session[:user_id] = ShopifyApp::SessionRepository.store_user_session(session_store, associated_user)
67
+ else
68
+ session[:shop_id] = ShopifyApp::SessionRepository.store_shop_session(session_store)
67
69
  end
70
+ session[:shopify_domain] = shop_name
71
+ session[:user_session] = auth_hash&.extra&.session
68
72
  end
69
73
 
70
74
  def install_webhooks
@@ -72,7 +76,7 @@ module ShopifyApp
72
76
 
73
77
  WebhooksManager.queue(
74
78
  shop_name,
75
- token,
79
+ shop_session&.token || user_session.token,
76
80
  ShopifyApp.configuration.webhooks
77
81
  )
78
82
  end
@@ -82,7 +86,7 @@ module ShopifyApp
82
86
 
83
87
  ScripttagsManager.queue(
84
88
  shop_name,
85
- token,
89
+ shop_session&.token || user_session.token,
86
90
  ShopifyApp.configuration.scripttags
87
91
  )
88
92
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyApp
2
- class SessionsController < ActionController::Base # rubocop:disable Metrics/ClassLength
3
+ class SessionsController < ActionController::Base
3
4
  include ShopifyApp::LoginProtection
4
5
 
5
6
  layout false, only: :new
7
+
6
8
  after_action only: [:new, :create] do |controller|
7
9
  controller.response.headers.except!('X-Frame-Options')
8
10
  end
@@ -16,7 +18,7 @@ module ShopifyApp
16
18
  end
17
19
 
18
20
  def enable_cookies
19
- return unless validate_shop
21
+ return unless validate_shop_presence
20
22
 
21
23
  render(:enable_cookies, layout: false, locals: {
22
24
  does_not_have_storage_access_url: top_level_interaction_path(
@@ -28,17 +30,17 @@ module ShopifyApp
28
30
  shop: sanitized_shop_name,
29
31
  return_to: params[:return_to]
30
32
  ),
31
- current_shopify_domain: current_shopify_domain
33
+ current_shopify_domain: current_shopify_domain,
32
34
  })
33
35
  end
34
36
 
35
37
  def top_level_interaction
36
38
  @url = login_url_with_optional_shop(top_level: true)
37
- validate_shop
39
+ validate_shop_presence
38
40
  end
39
41
 
40
42
  def granted_storage_access
41
- return unless validate_shop
43
+ return unless validate_shop_presence
42
44
 
43
45
  session['shopify.granted_storage_access'] = true
44
46
 
@@ -61,6 +63,8 @@ module ShopifyApp
61
63
 
62
64
  copy_return_to_param_to_session
63
65
 
66
+ set_user_tokens_option
67
+
64
68
  if user_agent_can_partition_cookies
65
69
  authenticate_with_partitioning
66
70
  else
@@ -88,7 +92,29 @@ module ShopifyApp
88
92
  end
89
93
  end
90
94
 
91
- def validate_shop
95
+ # rubocop:disable Lint/SuppressedException
96
+ def set_user_tokens_option
97
+ if shop_session.blank?
98
+ session[:user_tokens] = false
99
+ return
100
+ end
101
+
102
+ session[:user_tokens] = ShopifyApp::SessionRepository.user_storage.present?
103
+
104
+ ShopifyAPI::Session.temp(
105
+ domain: shop_session.domain,
106
+ token: shop_session.token,
107
+ api_version: shop_session.api_version
108
+ ) do
109
+ ShopifyAPI::Metafield.find(:token_validity_bogus_check)
110
+ end
111
+ rescue ActiveResource::UnauthorizedAccess
112
+ session[:user_tokens] = false
113
+ rescue StandardError
114
+ end
115
+ # rubocop:enable Lint/SuppressedException
116
+
117
+ def validate_shop_presence
92
118
  @shop = sanitized_shop_name
93
119
  unless @shop
94
120
  render_invalid_shop_error
@@ -104,7 +130,7 @@ module ShopifyApp
104
130
 
105
131
  def render_invalid_shop_error
106
132
  flash[:error] = I18n.t('invalid_shop_url')
107
- redirect_to return_address
133
+ redirect_to(return_address)
108
134
  end
109
135
 
110
136
  def enable_cookie_access
@@ -115,7 +141,7 @@ module ShopifyApp
115
141
  end
116
142
 
117
143
  def authenticate_in_context
118
- redirect_to "#{main_app.root_path}auth/shopify"
144
+ redirect_to("#{main_app.root_path}auth/shopify")
119
145
  end
120
146
 
121
147
  def authenticate_at_top_level
@@ -150,7 +176,7 @@ module ShopifyApp
150
176
  shop: sanitized_shop_name,
151
177
  return_to: session[:return_to]
152
178
  ),
153
- current_shopify_domain: current_shopify_domain
179
+ current_shopify_domain: current_shopify_domain,
154
180
  }
155
181
  )
156
182
  end
@@ -1,14 +1,15 @@
1
+ # frozen_string_literal: true
1
2
  module ShopifyApp
3
+ class MissingWebhookJobError < StandardError; end
4
+
2
5
  class WebhooksController < ActionController::Base
3
6
  include ShopifyApp::WebhookVerification
4
7
 
5
- class ShopifyApp::MissingWebhookJobError < StandardError; end
6
-
7
8
  def receive
8
9
  params.permit!
9
- job_args = {shop_domain: shop_domain, webhook: webhook_params.to_h}
10
+ job_args = { shop_domain: shop_domain, webhook: webhook_params.to_h }
10
11
  webhook_job_klass.perform_later(job_args)
11
- head :no_content
12
+ head(:no_content)
12
13
  end
13
14
 
14
15
  private
@@ -18,7 +19,7 @@ module ShopifyApp
18
19
  end
19
20
 
20
21
  def webhook_job_klass
21
- webhook_job_klass_name.safe_constantize or raise ShopifyApp::MissingWebhookJobError
22
+ webhook_job_klass_name.safe_constantize || raise(ShopifyApp::MissingWebhookJobError)
22
23
  end
23
24
 
24
25
  def webhook_job_klass_name(type = webhook_type)
@@ -15,6 +15,6 @@ fi:
15
15
  top_level_interaction_action: Jatka
16
16
  request_storage_access_heading: "%{app} edellyttää evästeiden käyttöä"
17
17
  request_storage_access_body: Näin sovellus voi todentaa sinut tallentamalla henkilötietosi
18
- tilapäisesti. Napsauta Jatka ja salli evästeet sovelluksen käyttämiseksi.
18
+ tilapäisesti. Klikkaa Jatka ja salli evästeet sovelluksen käyttämiseksi.
19
19
  request_storage_access_footer: Evästeet vanhenevat 30 päivän kuluttua.
20
20
  request_storage_access_action: Jatka
@@ -1,20 +1,20 @@
1
1
  ---
2
2
  nl:
3
- logged_out: u bent afgemeld
3
+ logged_out: je bentafgemeld
4
4
  could_not_log_in: Kon niet aanmelden bij Shopify-winkel
5
5
  invalid_shop_url: Ongeldig winkeldomein
6
6
  enable_cookies_heading: Schakel cookies in van %{app}
7
- enable_cookies_body: U moet cookies in deze browser handmatig inschakelen om %{app}
7
+ enable_cookies_body: Je moet cookies in deze browser handmatig inschakelen om %{app}
8
8
  binnen Shopify te gebruiken.
9
- enable_cookies_footer: Met cookies kan de app u verifiëren door uw voorkeuren en
9
+ enable_cookies_footer: Met cookies kan de app je verifiëren door je voorkeuren en
10
10
  persoonlijke informatie tijdelijk op te slaan. Ze vervallen na 30 dagen.
11
11
  enable_cookies_action: Schakel cookies in
12
- top_level_interaction_heading: Uw browser moet %{app} verifiëren
13
- top_level_interaction_body: Uw browser heeft apps nodig zoals %{app} om u toegang
14
- te vragen tot cookies voordat Shopify het voor u kan openen.
12
+ top_level_interaction_heading: Je browser moet %{app} verifiëren
13
+ top_level_interaction_body: Je browser heeft apps nodig zoals %{app} om je toegang
14
+ te vragen tot cookies voordat Shopify het voor je kan openen.
15
15
  top_level_interaction_action: Doorgaan
16
16
  request_storage_access_heading: "%{app} heeft toegang tot cookies nodig"
17
- request_storage_access_body: Hiermee kan de app u verifiëren door uw persoonlijke
17
+ request_storage_access_body: Hiermee kan de app je verifiëren door je persoonlijke
18
18
  gegevens tijdelijk op te slaan. Klik op Doorgaan en sta cookies toe om de app
19
19
  te gebruiken.
20
20
  request_storage_access_footer: Cookies verlopen na 30 dagen.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  ShopifyApp::Engine.routes.draw do
2
3
  controller :sessions do
3
4
  get 'login' => :new, :as => :login
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rails/generators/base'
2
3
 
3
4
  module ShopifyApp
@@ -6,7 +7,7 @@ module ShopifyApp
6
7
  source_root File.expand_path('../templates', __FILE__)
7
8
 
8
9
  hook_for :test_framework, as: :job, in: :rails do |instance, generator|
9
- instance.invoke generator, [ instance.send(:job_file_name) ]
10
+ instance.invoke(generator, [instance.send(:job_file_name)])
10
11
  end
11
12
 
12
13
  def init_after_authenticate_config
@@ -23,12 +24,13 @@ module ShopifyApp
23
24
  )
24
25
 
25
26
  unless initializer.include?(after_authenticate_job_config)
26
- shell.say("Error adding after_authenticate_job to config. Add this line manually: #{after_authenticate_job_config}", :red)
27
+ shell.say("Error adding after_authenticate_job to config. Add this line manually: "\
28
+ "#{after_authenticate_job_config}", :red)
27
29
  end
28
30
  end
29
31
 
30
32
  def add_after_authenticate_job
31
- template 'after_authenticate_job.rb', "app/jobs/#{job_file_name}_job.rb"
33
+ template('after_authenticate_job.rb', "app/jobs/#{job_file_name}_job.rb")
32
34
  end
33
35
 
34
36
  private
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Shopify
2
3
  class AfterAuthenticateJob < ActiveJob::Base
3
4
  def perform(shop_domain:)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rails/generators/base'
2
3
 
3
4
  module ShopifyApp
@@ -6,7 +7,7 @@ module ShopifyApp
6
7
  source_root File.expand_path('../templates', __FILE__)
7
8
 
8
9
  def generate_app_extension
9
- template "marketing_activities_controller.rb", "app/controllers/marketing_activities_controller.rb"
10
+ template("marketing_activities_controller.rb", "app/controllers/marketing_activities_controller.rb")
10
11
  generate_routes
11
12
  end
12
13
 
@@ -3,7 +3,7 @@
3
3
  class MarketingActivitiesController < ShopifyApp::ExtensionVerificationController
4
4
  def preload_form_data
5
5
  preload_data = {
6
- "form_data": {}
6
+ "form_data": {},
7
7
  }
8
8
  render(json: preload_data, status: :ok)
9
9
  end
@@ -31,14 +31,14 @@ class MarketingActivitiesController < ShopifyApp::ExtensionVerificationControlle
31
31
  "preview_url": placeholder_img,
32
32
  "content_type": "text/html",
33
33
  "width": 360,
34
- "height": 200
34
+ "height": 200,
35
35
  },
36
36
  "mobile": {
37
37
  "preview_url": placeholder_img,
38
38
  "content_type": "text/html",
39
39
  "width": 360,
40
- "height": 200
41
- }
40
+ "height": 200,
41
+ },
42
42
  }
43
43
  render(json: preview_response, status: :ok)
44
44
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rails/generators/base'
2
3
 
3
4
  module ShopifyApp
@@ -8,7 +9,7 @@ module ShopifyApp
8
9
  class_option :address, type: :string, aliases: "-a", required: true
9
10
 
10
11
  hook_for :test_framework, as: :job, in: :rails do |instance, generator|
11
- instance.invoke generator, [ instance.send(:job_file_name) ]
12
+ instance.invoke(generator, [instance.send(:job_file_name)])
12
13
  end
13
14
 
14
15
  def init_webhook_config
@@ -32,14 +33,14 @@ module ShopifyApp
32
33
  initializer = load_initializer
33
34
 
34
35
  unless initializer.include?(webhook_config)
35
- shell.say "Error adding webhook to config. Add this line manually: #{webhook_config}", :red
36
+ shell.say("Error adding webhook to config. Add this line manually: #{webhook_config}", :red)
36
37
  end
37
38
  end
38
39
 
39
40
  def add_webhook_job
40
41
  @job_file_name = job_file_name + '_job'
41
- @job_class_name = @job_file_name.classify
42
- template 'webhook_job.rb', "app/jobs/#{@job_file_name}.rb"
42
+ @job_class_name = @job_file_name.classify
43
+ template('webhook_job.rb', "app/jobs/#{@job_file_name}.rb")
43
44
  end
44
45
 
45
46
  private
@@ -2,6 +2,11 @@ class <%= @job_class_name %> < ActiveJob::Base
2
2
  def perform(shop_domain:, webhook:)
3
3
  shop = Shop.find_by(shopify_domain: shop_domain)
4
4
 
5
+ if shop.nil?
6
+ logger.error("#{self.class} failed: cannot find shop with domain '#{shop_domain}'")
7
+ return
8
+ end
9
+
5
10
  shop.with_shopify_session do
6
11
  end
7
12
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rails/generators/base'
2
3
 
3
4
  module ShopifyApp
@@ -6,11 +7,11 @@ module ShopifyApp
6
7
  source_root File.expand_path('../templates', __FILE__)
7
8
 
8
9
  def create_app_proxy_controller
9
- template 'app_proxy_controller.rb', 'app/controllers/app_proxy_controller.rb'
10
+ template('app_proxy_controller.rb', 'app/controllers/app_proxy_controller.rb')
10
11
  end
11
12
 
12
13
  def create_app_proxy_index_view
13
- copy_file 'index.html.erb', 'app/views/app_proxy/index.html.erb'
14
+ copy_file('index.html.erb', 'app/views/app_proxy/index.html.erb')
14
15
  end
15
16
 
16
17
  def add_app_proxy_route
@@ -18,7 +19,7 @@ module ShopifyApp
18
19
  'config/routes.rb',
19
20
  File.read(File.expand_path(find_in_source_paths('app_proxy_route.rb'))),
20
21
  after: "mount ShopifyApp::Engine, at: '/'\n"
21
- )
22
+ )
22
23
  end
23
24
  end
24
25
  end
@@ -1,8 +1,8 @@
1
+ # frozen_string_literal: true
1
2
  class AppProxyController < ApplicationController
2
- include ShopifyApp::AppProxyVerification
3
+ include ShopifyApp::AppProxyVerification
3
4
 
4
5
  def index
5
- render layout: false, content_type: 'application/liquid'
6
+ render(layout: false, content_type: 'application/liquid')
6
7
  end
7
-
8
8
  end
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
1
2
 
2
- namespace :app_proxy do
3
- root action: 'index'
4
- # simple routes without a specified controller will go to AppProxyController
5
-
6
- # more complex routes will go to controllers in the AppProxy namespace
7
- # resources :reviews
8
- # GET /app_proxy/reviews will now be routed to
9
- # AppProxy::ReviewsController#index, for example
10
- end
3
+ namespace :app_proxy do
4
+ root action: 'index'
5
+ # simple routes without a specified controller will go to AppProxyController
6
+
7
+ # more complex routes will go to controllers in the AppProxy namespace
8
+ # resources :reviews
9
+ # GET /app_proxy/reviews will now be routed to
10
+ # AppProxy::ReviewsController#index, for example
11
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rails/generators/base'
2
3
 
3
4
  module ShopifyApp
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'rails/generators/base'
2
3
 
3
4
  module ShopifyApp
@@ -6,15 +7,15 @@ module ShopifyApp
6
7
  source_root File.expand_path('../templates', __FILE__)
7
8
 
8
9
  def create_home_controller
9
- template 'home_controller.rb', 'app/controllers/home_controller.rb'
10
+ template('home_controller.rb', 'app/controllers/home_controller.rb')
10
11
  end
11
12
 
12
13
  def create_home_index_view
13
- copy_file 'index.html.erb', 'app/views/home/index.html.erb'
14
+ copy_file('index.html.erb', 'app/views/home/index.html.erb')
14
15
  end
15
16
 
16
17
  def add_home_index_route
17
- route "root :to => 'home#index'"
18
+ route("root :to => 'home#index'")
18
19
  end
19
20
 
20
21
  def embedded_app?