shopify_app 12.0.0 → 13.0.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/README.md +67 -20
  4. data/app/assets/javascripts/shopify_app/itp_helper.js +6 -6
  5. data/app/assets/javascripts/shopify_app/storage_access.js +35 -6
  6. data/app/controllers/concerns/shopify_app/authenticated.rb +1 -1
  7. data/app/controllers/shopify_app/callback_controller.rb +14 -10
  8. data/app/controllers/shopify_app/sessions_controller.rb +49 -13
  9. data/app/views/shopify_app/sessions/enable_cookies.html.erb +1 -1
  10. data/app/views/shopify_app/sessions/request_storage_access.html.erb +1 -1
  11. data/config/locales/pt-BR.yml +1 -1
  12. data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +1 -5
  13. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +1 -1
  14. data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +1 -1
  15. data/lib/generators/shopify_app/install/templates/shopify_app.rb +1 -1
  16. data/lib/generators/shopify_app/install/templates/shopify_provider.rb +1 -1
  17. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +1 -1
  18. data/lib/generators/shopify_app/shop_model/templates/shop.rb +1 -1
  19. data/lib/generators/shopify_app/user_model/templates/user.rb +1 -1
  20. data/lib/generators/shopify_app/user_model/user_model_generator.rb +1 -1
  21. data/lib/shopify_app/configuration.rb +12 -9
  22. data/lib/shopify_app/controller_concerns/login_protection.rb +39 -22
  23. data/lib/shopify_app/engine.rb +1 -1
  24. data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +18 -45
  25. data/lib/shopify_app/session/in_memory_shop_session_store.rb +4 -0
  26. data/lib/shopify_app/session/in_memory_user_session_store.rb +4 -0
  27. data/lib/shopify_app/session/session_repository.rb +38 -13
  28. data/lib/shopify_app/session/session_storage.rb +0 -10
  29. data/lib/shopify_app/session/{storage_strategies/shop_storage_strategy.rb → shop_session_storage.rb} +9 -2
  30. data/lib/shopify_app/session/{storage_strategies/user_storage_strategy.rb → user_session_storage.rb} +10 -3
  31. data/lib/shopify_app/version.rb +1 -1
  32. data/lib/shopify_app.rb +4 -2
  33. data/package-lock.json +1228 -1207
  34. data/package.json +2 -2
  35. data/shopify_app.gemspec +4 -4
  36. metadata +12 -10
@@ -32,7 +32,7 @@
32
32
  myshopifyUrl: "https://#{current_shopify_domain}",
33
33
  hasStorageAccessUrl: "#{has_storage_access_url}",
34
34
  doesNotHaveStorageAccessUrl: "#{does_not_have_storage_access_url}",
35
- appHomeUrl: "#{app_home_url}"
35
+ appTargetUrl: "#{app_target_url}"
36
36
  },
37
37
  },
38
38
  )
@@ -24,7 +24,7 @@
24
24
  myshopifyUrl: "https://#{current_shopify_domain}",
25
25
  hasStorageAccessUrl: "#{has_storage_access_url}",
26
26
  doesNotHaveStorageAccessUrl: "#{does_not_have_storage_access_url}",
27
- appHomeUrl: "#{app_home_url}"
27
+ appTargetUrl: "#{app_target_url}"
28
28
  },
29
29
  },
30
30
  )
@@ -4,7 +4,7 @@ pt-BR:
4
4
  could_not_log_in: Não foi possível fazer login na Shopify store
5
5
  invalid_shop_url: Domínio de loja inválido
6
6
  enable_cookies_heading: Habilitar cookies de %{app}
7
- enable_cookies_body: Você deve habilitar manualmente os cookies neste navegador
7
+ enable_cookies_body: Você precisa habilitar manualmente os cookies neste navegador
8
8
  para usar %{app} dentro da Shopify.
9
9
  enable_cookies_footer: Os cookies permitem que o app o autentique armazenando temporariamente
10
10
  suas preferências e dados pessoais. Eles expiram depois de 30 dias.
@@ -3,11 +3,7 @@
3
3
  class MarketingActivitiesController < ShopifyApp::ExtensionVerificationController
4
4
  def preload_form_data
5
5
  preload_data = {
6
- "form_data": {
7
- "budget": {
8
- "currency": "USD",
9
- }
10
- }
6
+ "form_data": {}
11
7
  }
12
8
  render(json: preload_data, status: :ok)
13
9
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  <ul>
4
4
  <% @products.each do |product| %>
5
- <li><%= link_to product.title, "https://#{@shop_session.domain}/admin/products/#{product.id}", target: "_top" %></li>
5
+ <li><%= link_to product.title, "https://#{@current_shopify_session.domain}/admin/products/#{product.id}", target: "_top" %></li>
6
6
  <% end %>
7
7
  </ul>
8
8
 
@@ -28,7 +28,7 @@
28
28
 
29
29
  <%= content_tag(:div, nil, id: 'shopify-app-init', data: {
30
30
  api_key: ShopifyApp.configuration.api_key,
31
- shop_origin: (@shop_session.domain if @shop_session),
31
+ shop_origin: (@current_shopify_session.domain if @current_shopify_session),
32
32
  debug: Rails.env.development?
33
33
  } ) %>
34
34
 
@@ -8,7 +8,7 @@ ShopifyApp.configure do |config|
8
8
  config.embedded_app = <%= embedded_app? %>
9
9
  config.after_authenticate_job = false
10
10
  config.api_version = "<%= @api_version %>"
11
- config.session_repository = 'ShopifyApp::InMemorySessionStore'
11
+ config.shop_session_repository = 'ShopifyApp::InMemoryShopSessionStore'
12
12
  end
13
13
 
14
14
  # ShopifyApp::Utils.fetch_known_api_versions # Uncomment to fetch known api versions from shopify servers on boot
@@ -4,7 +4,6 @@ provider :shopify,
4
4
  ShopifyApp.configuration.api_key,
5
5
  ShopifyApp.configuration.secret,
6
6
  scope: ShopifyApp.configuration.scope,
7
- per_user_permissions: ShopifyApp.configuration.per_user_tokens,
8
7
  setup: lambda { |env|
9
8
  strategy = env['omniauth.strategy']
10
9
 
@@ -17,4 +16,5 @@ provider :shopify,
17
16
 
18
17
  strategy.options[:client_options][:site] = shop
19
18
  strategy.options[:old_client_secret] = ShopifyApp.configuration.old_secret
19
+ strategy.options[:per_user_permissions] = strategy.session[:user_tokens]
20
20
  }
@@ -16,7 +16,7 @@ module ShopifyApp
16
16
  end
17
17
 
18
18
  def update_shopify_app_initializer
19
- gsub_file 'config/initializers/shopify_app.rb', 'ShopifyApp::InMemorySessionStore', 'Shop'
19
+ gsub_file 'config/initializers/shopify_app.rb', 'ShopifyApp::InMemoryShopSessionStore', 'Shop'
20
20
  end
21
21
 
22
22
  def create_shop_fixtures
@@ -1,5 +1,5 @@
1
1
  class Shop < ActiveRecord::Base
2
- include ShopifyApp::SessionStorage
2
+ include ShopifyApp::ShopSessionStorage
3
3
 
4
4
  def api_version
5
5
  ShopifyApp.configuration.api_version
@@ -1,5 +1,5 @@
1
1
  class User < ActiveRecord::Base
2
- include ShopifyApp::SessionStorage
2
+ include ShopifyApp::UserSessionStorage
3
3
 
4
4
  def api_version
5
5
  ShopifyApp.configuration.api_version
@@ -16,7 +16,7 @@ module ShopifyApp
16
16
  end
17
17
 
18
18
  def update_shopify_app_initializer
19
- gsub_file 'config/initializers/shopify_app.rb', 'ShopifyApp::InMemorySessionStore', 'User'
19
+ gsub_file 'config/initializers/shopify_app.rb', 'ShopifyApp::InMemoryUserSessionStore', 'User'
20
20
  end
21
21
 
22
22
  def create_user_fixtures
@@ -5,7 +5,7 @@ module ShopifyApp
5
5
  # for the app in your Shopify Partners page. Change your settings in
6
6
  # `config/initializers/shopify_app.rb`
7
7
  attr_accessor :application_name
8
- attr_accessor :api_key
8
+ attr_accessor :api_key
9
9
  attr_accessor :secret
10
10
  attr_accessor :old_secret
11
11
  attr_accessor :scope
@@ -14,9 +14,8 @@ module ShopifyApp
14
14
  attr_accessor :webhooks
15
15
  attr_accessor :scripttags
16
16
  attr_accessor :after_authenticate_job
17
- attr_reader :session_repository
18
- attr_accessor :per_user_tokens
19
- alias_method :per_user_tokens?, :per_user_tokens
17
+ attr_reader :shop_session_repository
18
+ attr_reader :user_session_repository
20
19
  attr_accessor :api_version
21
20
 
22
21
  # customise urls
@@ -44,7 +43,6 @@ module ShopifyApp
44
43
  @myshopify_domain = 'myshopify.com'
45
44
  @scripttags_manager_queue_name = Rails.application.config.active_job.queue_name
46
45
  @webhooks_manager_queue_name = Rails.application.config.active_job.queue_name
47
- @per_user_tokens = false
48
46
  @disable_webpacker = ENV['SHOPIFY_APP_DISABLE_WEBPACKER'].present?
49
47
  end
50
48
 
@@ -52,9 +50,14 @@ module ShopifyApp
52
50
  @login_url || File.join(@root_url, 'login')
53
51
  end
54
52
 
55
- def session_repository=(klass)
56
- @session_repository = klass
57
- ShopifyApp::SessionRepository.storage = klass
53
+ def user_session_repository=(klass)
54
+ @user_session_repository = klass
55
+ ShopifyApp::SessionRepository.user_storage = klass
56
+ end
57
+
58
+ def shop_session_repository=(klass)
59
+ @shop_session_repository = klass
60
+ ShopifyApp::SessionRepository.shop_storage = klass
58
61
  end
59
62
 
60
63
  def has_webhooks?
@@ -66,7 +69,7 @@ module ShopifyApp
66
69
  end
67
70
 
68
71
  def enable_same_site_none
69
- @enable_same_site_none.nil? ? embedded_app? : @enable_same_site_none
72
+ !Rails.env.test? && (@enable_same_site_none.nil? ? embedded_app? : @enable_same_site_none)
70
73
  end
71
74
  end
72
75
 
@@ -14,44 +14,48 @@ module ShopifyApp
14
14
  rescue_from ActiveResource::UnauthorizedAccess, :with => :close_session
15
15
  end
16
16
 
17
- def shopify_session
18
- return redirect_to_login unless shop_session
17
+ def activate_shopify_session
18
+ return redirect_to_login if current_shopify_session.blank?
19
19
  clear_top_level_oauth_cookie
20
20
 
21
21
  begin
22
- ShopifyAPI::Base.activate_session(shop_session)
22
+ ShopifyAPI::Base.activate_session(current_shopify_session)
23
23
  yield
24
24
  ensure
25
25
  ShopifyAPI::Base.clear_session
26
26
  end
27
27
  end
28
28
 
29
- def shop_session
30
- if ShopifyApp.configuration.per_user_tokens?
31
- return unless session[:shopify_user]
32
- @shop_session ||= ShopifyApp::SessionRepository.retrieve(session[:shopify_user]['id'])
29
+ def current_shopify_session
30
+ if session[:user_id].present?
31
+ @current_shopify_session ||= user_session
33
32
  else
34
- return unless session[:shopify]
35
- @shop_session ||= ShopifyApp::SessionRepository.retrieve(session[:shopify])
33
+ @current_shopify_session ||= shop_session
36
34
  end
37
35
  end
38
36
 
37
+ def user_session
38
+ return if session[:user_id].blank?
39
+ ShopifyApp::SessionRepository.retrieve_user_session(session[:user_id])
40
+ end
41
+
42
+ def shop_session
43
+ return if session[:shop_id].blank?
44
+ ShopifyApp::SessionRepository.retrieve_shop_session(session[:shop_id])
45
+ end
46
+
39
47
  def login_again_if_different_user_or_shop
40
- if ShopifyApp.configuration.per_user_tokens?
41
- valid_session_data = session[:user_session].present? && params[:session].present? # session data was sent/stored correctly
42
- sessions_do_not_match = session[:user_session] != params[:session] # current user is different from stored user
48
+ if session[:user_session].present? && params[:session].present? # session data was sent/stored correctly
49
+ clear_session = session[:user_session] != params[:session] # current user is different from stored user
43
50
 
44
- if valid_session_data && sessions_do_not_match
45
- clear_session = true
46
- end
47
51
  end
48
52
 
49
- if shop_session && params[:shop] && params[:shop].is_a?(String) && (shop_session.domain != params[:shop])
53
+ if current_shopify_session && params[:shop] && params[:shop].is_a?(String) && (current_shopify_session.domain != params[:shop])
50
54
  clear_session = true
51
55
  end
52
56
 
53
57
  if clear_session
54
- clear_shop_session
58
+ clear_shopify_session
55
59
  redirect_to_login
56
60
  end
57
61
  end
@@ -76,12 +80,13 @@ module ShopifyApp
76
80
  end
77
81
 
78
82
  def close_session
79
- clear_shop_session
83
+ clear_shopify_session
80
84
  redirect_to(login_url_with_optional_shop)
81
85
  end
82
86
 
83
- def clear_shop_session
84
- session[:shopify] = nil
87
+ def clear_shopify_session
88
+ session[:shop_id] = nil
89
+ session[:user_id] = nil
85
90
  session[:shopify_domain] = nil
86
91
  session[:shopify_user] = nil
87
92
  session[:user_session] = nil
@@ -100,8 +105,10 @@ module ShopifyApp
100
105
  query_params = {}
101
106
  query_params[:shop] = sanitized_params[:shop] if params[:shop].present?
102
107
 
103
- if session[:return_to] && return_to_param_required?
104
- query_params[:return_to] = session[:return_to]
108
+ return_to = session[:return_to] || params[:return_to]
109
+
110
+ if return_to.present? && return_to_param_required?
111
+ query_params[:return_to] = return_to
105
112
  end
106
113
 
107
114
  has_referer_shop_name = referer_sanitized_shop_name.present?
@@ -165,5 +172,15 @@ module ShopifyApp
165
172
  def return_address
166
173
  session.delete(:return_to) || ShopifyApp.configuration.root_url
167
174
  end
175
+
176
+ def return_address_with_params(params)
177
+ uri = URI(return_address)
178
+ uri.query = CGI.parse(uri.query.to_s)
179
+ .symbolize_keys
180
+ .transform_values { |v| v.one? ? v.first : v }
181
+ .merge(params)
182
+ .to_query
183
+ uri.to_s
184
+ end
168
185
  end
169
186
  end
@@ -14,7 +14,7 @@ module ShopifyApp
14
14
  end
15
15
 
16
16
  initializer "shopify_app.middleware" do |app|
17
- app.config.middleware.insert_before(ActionDispatch::Cookies, ShopifyApp::SameSiteCookieMiddleware)
17
+ app.config.middleware.insert_after(::Rack::Runtime, ShopifyApp::SameSiteCookieMiddleware)
18
18
  end
19
19
  end
20
20
  end
@@ -1,60 +1,33 @@
1
1
  module ShopifyApp
2
2
  class SameSiteCookieMiddleware
3
+ COOKIE_SEPARATOR = "\n"
4
+
3
5
  def initialize(app)
4
6
  @app = app
5
7
  end
6
8
 
7
9
  def call(env)
8
- _status, headers, _body = @app.call(env)
9
- ensure
10
+ status, headers, body = @app.call(env)
10
11
  user_agent = env['HTTP_USER_AGENT']
11
12
 
12
- if headers && headers['Set-Cookie'] && !SameSiteCookieMiddleware.same_site_none_incompatible?(user_agent) &&
13
- ShopifyApp.configuration.enable_same_site_none
14
-
15
- cookies = headers['Set-Cookie'].split("\n").compact
16
-
17
- cookies.each do |cookie|
18
- unless cookie.include?("; SameSite")
19
- headers['Set-Cookie'] = headers['Set-Cookie'].gsub("#{cookie}", "#{cookie}; secure; SameSite=None")
13
+ if headers && headers['Set-Cookie'] &&
14
+ BrowserSniffer.new(user_agent).same_site_none_compatible? &&
15
+ ShopifyApp.configuration.enable_same_site_none &&
16
+ Rack::Request.new(env).ssl?
17
+
18
+ set_cookies = headers['Set-Cookie']
19
+ .split(COOKIE_SEPARATOR)
20
+ .compact
21
+ .map do |cookie|
22
+ cookie << '; Secure' if not cookie =~ /;\s*secure/i
23
+ cookie << '; SameSite=None' unless cookie =~ /;\s*samesite=/i
24
+ cookie
20
25
  end
21
- end
22
- end
23
- end
24
26
 
25
- def self.same_site_none_incompatible?(user_agent)
26
- sniffer = BrowserSniffer.new(user_agent)
27
-
28
- webkit_same_site_bug?(sniffer) || drops_unrecognized_same_site_cookies?(sniffer)
29
- rescue
30
- true
31
- end
32
-
33
- def self.webkit_same_site_bug?(sniffer)
34
- (sniffer.os == :ios && sniffer.os_version.match?(/^([0-9]|1[12])[\.\_]/)) ||
35
- (sniffer.os == :mac && sniffer.browser == :safari && sniffer.os_version.match?(/^10[\.\_]14/))
36
- end
37
-
38
- def self.drops_unrecognized_same_site_cookies?(sniffer)
39
- (chromium_based?(sniffer) && sniffer.major_browser_version >= 51 && sniffer.major_browser_version <= 66) ||
40
- (uc_browser?(sniffer) && !uc_browser_version_at_least?(sniffer: sniffer, major: 12, minor: 13, build: 2))
41
- end
42
-
43
- def self.chromium_based?(sniffer)
44
- sniffer.browser_name.downcase.match?(/chrom(e|ium)/)
45
- end
46
-
47
- def self.uc_browser?(sniffer)
48
- sniffer.user_agent.downcase.match?(/uc\s?browser/)
49
- end
50
-
51
- def self.uc_browser_version_at_least?(sniffer:, major:, minor:, build:)
52
- digits = sniffer.browser_version.split('.').map(&:to_i)
53
- return false unless digits.count >= 3
27
+ headers['Set-Cookie'] = set_cookies.join(COOKIE_SEPARATOR)
28
+ end
54
29
 
55
- return digits[0] > major if digits[0] != major
56
- return digits[1] > minor if digits[1] != minor
57
- digits[2] >= build
30
+ [status, headers, body]
58
31
  end
59
32
  end
60
33
  end
@@ -0,0 +1,4 @@
1
+ module ShopifyApp
2
+ class InMemoryShopSessionStore < InMemorySessionStore
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ShopifyApp
2
+ class InMemoryUserSessionStore < InMemorySessionStore
3
+ end
4
+ end
@@ -3,31 +3,56 @@ module ShopifyApp
3
3
  class ConfigurationError < StandardError; end
4
4
 
5
5
  class << self
6
- def storage=(storage)
7
- @storage = storage
6
+ def shop_storage=(storage)
7
+ @shop_storage = storage
8
8
 
9
- unless storage.nil? || self.storage.respond_to?(:store) && self.storage.respond_to?(:retrieve)
10
- raise ArgumentError, "storage must respond to :store and :retrieve"
9
+ unless storage.nil? || self.shop_storage.respond_to?(:store) && self.shop_storage.respond_to?(:retrieve)
10
+ raise ArgumentError, "shop storage must respond to :store and :retrieve"
11
11
  end
12
12
  end
13
13
 
14
- def retrieve(id)
15
- storage.retrieve(id)
14
+ def user_storage=(storage)
15
+ @user_storage = storage
16
+
17
+ unless storage.nil? || self.user_storage.respond_to?(:store) && self.user_storage.respond_to?(:retrieve)
18
+ raise ArgumentError, "user storage must respond to :store and :retrieve"
19
+ end
20
+ end
21
+
22
+ def retrieve_shop_session(id)
23
+ shop_storage.retrieve(id)
24
+ end
25
+
26
+ def retrieve_user_session(id)
27
+ user_storage.retrieve(id)
16
28
  end
17
29
 
18
- def store(session, *args)
19
- storage.store(session, *args)
30
+ def store_shop_session(session)
31
+ shop_storage.store(session)
20
32
  end
21
33
 
22
- def storage
23
- load_storage || raise(ConfigurationError.new("ShopifySessionRepository.storage is not configured!"))
34
+ def store_user_session(session, user)
35
+ user_storage.store(session, user)
36
+ end
37
+
38
+ def shop_storage
39
+ load_shop_storage || raise(ConfigurationError.new("ShopifySessionRepository.shop_storage is not configured!"))
40
+ end
41
+
42
+ def user_storage
43
+ load_user_storage
24
44
  end
25
45
 
26
46
  private
27
47
 
28
- def load_storage
29
- return unless @storage
30
- @storage.respond_to?(:safe_constantize) ? @storage.safe_constantize : @storage
48
+ def load_shop_storage
49
+ return unless @shop_storage
50
+ @shop_storage.respond_to?(:safe_constantize) ? @shop_storage.safe_constantize : @shop_storage
51
+ end
52
+
53
+ def load_user_storage
54
+ return unless @user_storage
55
+ @user_storage.respond_to?(:safe_constantize) ? @user_storage.safe_constantize : @user_storage
31
56
  end
32
57
  end
33
58
  end