shopify_app 18.1.0 → 19.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +2 -2
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +13 -0
  5. data/Gemfile +3 -2
  6. data/Gemfile.lock +124 -158
  7. data/README.md +1 -0
  8. data/Rakefile +4 -3
  9. data/app/assets/javascripts/shopify_app/app_bridge_2.0.12.js +10 -0
  10. data/app/assets/javascripts/shopify_app/app_bridge_redirect.js +3 -4
  11. data/app/assets/javascripts/shopify_app/storage_access.js +1 -1
  12. data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +1 -1
  13. data/app/controllers/concerns/shopify_app/require_known_shop.rb +1 -0
  14. data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +1 -1
  15. data/app/controllers/shopify_app/authenticated_controller.rb +1 -0
  16. data/app/controllers/shopify_app/callback_controller.rb +35 -147
  17. data/app/controllers/shopify_app/sessions_controller.rb +25 -132
  18. data/app/controllers/shopify_app/webhooks_controller.rb +5 -23
  19. data/app/views/shopify_app/sessions/enable_cookies.html.erb +1 -8
  20. data/app/views/shopify_app/sessions/request_storage_access.html.erb +1 -8
  21. data/app/views/shopify_app/sessions/top_level_interaction.html.erb +1 -1
  22. data/app/views/shopify_app/shared/redirect.html.erb +1 -7
  23. data/config/routes.rb +6 -12
  24. data/docs/Troubleshooting.md +0 -3
  25. data/docs/Upgrading.md +92 -3
  26. data/docs/shopify_app/webhooks.md +1 -1
  27. data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +10 -9
  28. data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +1 -0
  29. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +4 -3
  30. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +13 -12
  31. data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +9 -1
  32. data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +7 -6
  33. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +2 -1
  34. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +1 -1
  35. data/lib/generators/shopify_app/authenticated_controller/authenticated_controller_generator.rb +3 -3
  36. data/lib/generators/shopify_app/controllers/controllers_generator.rb +4 -3
  37. data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +11 -15
  38. data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +2 -2
  39. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +3 -3
  40. data/lib/generators/shopify_app/install/install_generator.rb +25 -74
  41. data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +1 -1
  42. data/lib/generators/shopify_app/install/templates/session_store.rb +2 -1
  43. data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +20 -5
  44. data/lib/generators/shopify_app/products_controller/products_controller_generator.rb +3 -3
  45. data/lib/generators/shopify_app/products_controller/templates/products_controller.rb +1 -1
  46. data/lib/generators/shopify_app/rotate_shopify_token_job/rotate_shopify_token_job_generator.rb +4 -4
  47. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +1 -0
  48. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +1 -1
  49. data/lib/generators/shopify_app/routes/routes_generator.rb +6 -5
  50. data/lib/generators/shopify_app/routes/templates/routes.rb +5 -5
  51. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +11 -10
  52. data/lib/generators/shopify_app/shop_model/templates/shop.rb +1 -0
  53. data/lib/generators/shopify_app/shopify_app_generator.rb +4 -3
  54. data/lib/generators/shopify_app/user_model/templates/user.rb +1 -0
  55. data/lib/generators/shopify_app/user_model/user_model_generator.rb +11 -10
  56. data/lib/generators/shopify_app/views/views_generator.rb +4 -3
  57. data/lib/shopify_app/access_scopes/shop_strategy.rb +2 -2
  58. data/lib/shopify_app/access_scopes/user_strategy.rb +4 -4
  59. data/lib/shopify_app/configuration.rb +5 -17
  60. data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +4 -3
  61. data/lib/shopify_app/controller_concerns/csrf_protection.rb +2 -1
  62. data/lib/shopify_app/controller_concerns/embedded_app.rb +4 -3
  63. data/lib/shopify_app/controller_concerns/itp.rb +3 -3
  64. data/lib/shopify_app/controller_concerns/localization.rb +1 -0
  65. data/lib/shopify_app/controller_concerns/login_protection.rb +55 -73
  66. data/lib/shopify_app/controller_concerns/payload_verification.rb +3 -2
  67. data/lib/shopify_app/controller_concerns/webhook_verification.rb +2 -1
  68. data/lib/shopify_app/engine.rb +7 -15
  69. data/lib/shopify_app/jobs/scripttags_manager_job.rb +2 -2
  70. data/lib/shopify_app/jobs/webhooks_manager_job.rb +4 -5
  71. data/lib/shopify_app/managers/scripttags_manager.rb +11 -4
  72. data/lib/shopify_app/managers/webhooks_manager.rb +42 -44
  73. data/lib/shopify_app/middleware/jwt_middleware.rb +5 -4
  74. data/lib/shopify_app/session/in_memory_session_store.rb +1 -0
  75. data/lib/shopify_app/session/in_memory_shop_session_store.rb +2 -1
  76. data/lib/shopify_app/session/in_memory_user_session_store.rb +1 -0
  77. data/lib/shopify_app/session/jwt.rb +9 -8
  78. data/lib/shopify_app/session/null_user_session_store.rb +2 -1
  79. data/lib/shopify_app/session/session_repository.rb +37 -0
  80. data/lib/shopify_app/session/session_storage.rb +4 -6
  81. data/lib/shopify_app/session/shop_session_storage.rb +6 -6
  82. data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +7 -8
  83. data/lib/shopify_app/session/user_session_storage.rb +19 -6
  84. data/lib/shopify_app/session/user_session_storage_with_scopes.rb +21 -8
  85. data/lib/shopify_app/test_helpers/all.rb +2 -1
  86. data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +4 -3
  87. data/lib/shopify_app/utils.rb +4 -10
  88. data/lib/shopify_app/version.rb +2 -1
  89. data/lib/shopify_app.rb +35 -40
  90. data/package.json +1 -1
  91. data/shopify_app.gemspec +21 -20
  92. data/yarn.lock +6 -6
  93. metadata +44 -49
  94. data/app/assets/javascripts/shopify_app/app_bridge_1.30.0.js +0 -1
  95. data/lib/generators/shopify_app/install/templates/omniauth.rb +0 -4
  96. data/lib/generators/shopify_app/install/templates/shopify_provider.rb.tt +0 -8
  97. data/lib/generators/shopify_app/install/templates/user_agent.rb +0 -6
  98. data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +0 -34
  99. data/lib/shopify_app/omniauth/omniauth_configuration.rb +0 -64
@@ -1,21 +1,22 @@
1
1
  # frozen_string_literal: true
2
- require 'rails/generators/base'
3
- require 'rails/generators/active_record'
2
+
3
+ require "rails/generators/base"
4
+ require "rails/generators/active_record"
4
5
 
5
6
  module ShopifyApp
6
7
  module Generators
7
8
  class UserModelGenerator < Rails::Generators::Base
8
9
  include Rails::Generators::Migration
9
- source_root File.expand_path('../templates', __FILE__)
10
+ source_root File.expand_path("../templates", __FILE__)
10
11
 
11
12
  class_option :new_shopify_cli_app, type: :boolean, default: false
12
13
 
13
14
  def create_user_model
14
- copy_file('user.rb', 'app/models/user.rb')
15
+ copy_file("user.rb", "app/models/user.rb")
15
16
  end
16
17
 
17
18
  def create_user_migration
18
- migration_template('db/migrate/create_users.erb', 'db/migrate/create_users.rb')
19
+ migration_template("db/migrate/create_users.erb", "db/migrate/create_users.rb")
19
20
  end
20
21
 
21
22
  def create_scopes_storage_in_user_model
@@ -33,24 +34,24 @@ module ShopifyApp
33
34
 
34
35
  if new_shopify_cli_app? || Rails.env.test? || yes?(scopes_column_prompt)
35
36
  migration_template(
36
- 'db/migrate/add_user_access_scopes_column.erb',
37
- 'db/migrate/add_user_access_scopes_column.rb'
37
+ "db/migrate/add_user_access_scopes_column.erb",
38
+ "db/migrate/add_user_access_scopes_column.rb"
38
39
  )
39
40
  end
40
41
  end
41
42
 
42
43
  def update_shopify_app_initializer
43
- gsub_file('config/initializers/shopify_app.rb', 'ShopifyApp::InMemoryUserSessionStore', 'User')
44
+ gsub_file("config/initializers/shopify_app.rb", "ShopifyApp::InMemoryUserSessionStore", "User")
44
45
  end
45
46
 
46
47
  def create_user_fixtures
47
- copy_file('users.yml', 'test/fixtures/users.yml')
48
+ copy_file("users.yml", "test/fixtures/users.yml")
48
49
  end
49
50
 
50
51
  private
51
52
 
52
53
  def new_shopify_cli_app?
53
- options['new_shopify_cli_app']
54
+ options["new_shopify_cli_app"]
54
55
  end
55
56
 
56
57
  def rails_migration_version
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require 'rails/generators/base'
2
+
3
+ require "rails/generators/base"
3
4
 
4
5
  module ShopifyApp
5
6
  module Generators
@@ -15,14 +16,14 @@ module ShopifyApp
15
16
  private
16
17
 
17
18
  def views
18
- files_within_root('.', 'app/views/**/*.*')
19
+ files_within_root(".", "app/views/**/*.*")
19
20
  end
20
21
 
21
22
  def files_within_root(prefix, glob)
22
23
  root = "#{self.class.source_root}/#{prefix}"
23
24
 
24
25
  Dir["#{root}/#{glob}"].sort.map do |full_path|
25
- full_path.sub(root, '.').gsub('/./', '/')
26
+ full_path.sub(root, ".").gsub("/./", "/")
26
27
  end
27
28
  end
28
29
  end
@@ -12,11 +12,11 @@ module ShopifyApp
12
12
  private
13
13
 
14
14
  def shop_access_scopes(shop_domain)
15
- ShopifyApp::SessionRepository.retrieve_shop_session_by_shopify_domain(shop_domain)&.access_scopes
15
+ ShopifyApp::SessionRepository.retrieve_shop_session_by_shopify_domain(shop_domain)&.scope
16
16
  end
17
17
 
18
18
  def configuration_access_scopes
19
- ShopifyAPI::ApiAccess.new(ShopifyApp.configuration.shop_access_scopes)
19
+ ShopifyAPI::Auth::AuthScopes.new(ShopifyApp.configuration.shop_access_scopes)
20
20
  end
21
21
  end
22
22
  end
@@ -9,7 +9,7 @@ module ShopifyApp
9
9
  def update_access_scopes?(user_id: nil, shopify_user_id: nil)
10
10
  return update_access_scopes_for_user_id?(user_id) if user_id
11
11
  return update_access_scopes_for_shopify_user_id?(shopify_user_id) if shopify_user_id
12
- raise(InvalidInput, '#update_access_scopes? requires user_id or shopify_user_id parameter inputs')
12
+ raise(InvalidInput, "#update_access_scopes? requires user_id or shopify_user_id parameter inputs")
13
13
  end
14
14
 
15
15
  private
@@ -25,15 +25,15 @@ module ShopifyApp
25
25
  end
26
26
 
27
27
  def user_access_scopes_by_user_id(user_id)
28
- ShopifyApp::SessionRepository.retrieve_user_session(user_id)&.access_scopes
28
+ ShopifyApp::SessionRepository.retrieve_user_session(user_id)&.scope
29
29
  end
30
30
 
31
31
  def user_access_scopes_by_shopify_user_id(shopify_user_id)
32
- ShopifyApp::SessionRepository.retrieve_user_session_by_shopify_user_id(shopify_user_id)&.access_scopes
32
+ ShopifyApp::SessionRepository.retrieve_user_session_by_shopify_user_id(shopify_user_id)&.scope
33
33
  end
34
34
 
35
35
  def configuration_access_scopes
36
- ShopifyAPI::ApiAccess.new(ShopifyApp.configuration.user_access_scopes)
36
+ ShopifyAPI::Auth::AuthScopes.new(ShopifyApp.configuration.user_access_scopes)
37
37
  end
38
38
  end
39
39
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class Configuration
4
5
  # Shopify App settings. These values should match the configuration
@@ -37,25 +38,16 @@ module ShopifyApp
37
38
  # allow namespacing webhook jobs
38
39
  attr_accessor :webhook_jobs_namespace
39
40
 
40
- # allow enabling of same site none on cookies
41
- attr_writer :enable_same_site_none
42
-
43
- # allow enabling jwt headers for authentication
44
- attr_accessor :allow_jwt_authentication
45
-
46
- attr_accessor :allow_cookie_authentication
47
-
48
41
  def initialize
49
- @root_url = '/'
50
- @myshopify_domain = 'myshopify.com'
42
+ @root_url = "/"
43
+ @myshopify_domain = "myshopify.com"
51
44
  @scripttags_manager_queue_name = Rails.application.config.active_job.queue_name
52
45
  @webhooks_manager_queue_name = Rails.application.config.active_job.queue_name
53
- @disable_webpacker = ENV['SHOPIFY_APP_DISABLE_WEBPACKER'].present?
54
- @allow_cookie_authentication = true
46
+ @disable_webpacker = ENV["SHOPIFY_APP_DISABLE_WEBPACKER"].present?
55
47
  end
56
48
 
57
49
  def login_url
58
- @login_url || File.join(@root_url, 'login')
50
+ @login_url || File.join(@root_url, "login")
59
51
  end
60
52
 
61
53
  def user_session_repository=(klass)
@@ -92,10 +84,6 @@ module ShopifyApp
92
84
  scripttags.present?
93
85
  end
94
86
 
95
- def enable_same_site_none
96
- !Rails.env.test? && (@enable_same_site_none.nil? ? embedded_app? : @enable_same_site_none)
97
- end
98
-
99
87
  def shop_access_scopes
100
88
  @shop_access_scopes || scope
101
89
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module AppProxyVerification
4
5
  extend ActiveSupport::Concern
@@ -16,7 +17,7 @@ module ShopifyApp
16
17
  def query_string_valid?(query_string)
17
18
  query_hash = Rack::Utils.parse_query(query_string)
18
19
 
19
- signature = query_hash.delete('signature')
20
+ signature = query_hash.delete("signature")
20
21
  return false if signature.nil?
21
22
 
22
23
  ActiveSupport::SecurityUtils.secure_compare(
@@ -26,10 +27,10 @@ module ShopifyApp
26
27
  end
27
28
 
28
29
  def calculated_signature(query_hash_without_signature)
29
- sorted_params = query_hash_without_signature.collect { |k, v| "#{k}=#{Array(v).join(',')}" }.sort.join
30
+ sorted_params = query_hash_without_signature.collect { |k, v| "#{k}=#{Array(v).join(",")}" }.sort.join
30
31
 
31
32
  OpenSSL::HMAC.hexdigest(
32
- OpenSSL::Digest.new('sha256'),
33
+ OpenSSL::Digest.new("sha256"),
33
34
  ShopifyApp.configuration.secret,
34
35
  sorted_params
35
36
  )
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module CsrfProtection
4
5
  extend ActiveSupport::Concern
@@ -9,7 +10,7 @@ module ShopifyApp
9
10
  private
10
11
 
11
12
  def valid_session_token?
12
- request.env['jwt.shopify_domain']
13
+ request.env["jwt.shopify_domain"]
13
14
  end
14
15
  end
15
16
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module EmbeddedApp
4
5
  extend ActiveSupport::Concern
@@ -6,15 +7,15 @@ module ShopifyApp
6
7
  included do
7
8
  if ShopifyApp.configuration.embedded_app?
8
9
  after_action(:set_esdk_headers)
9
- layout('embedded_app')
10
+ layout("embedded_app")
10
11
  end
11
12
  end
12
13
 
13
14
  private
14
15
 
15
16
  def set_esdk_headers
16
- response.set_header('P3P', 'CP="Not used"')
17
- response.headers.except!('X-Frame-Options')
17
+ response.set_header("P3P", 'CP="Not used"')
18
+ response.headers.except!("X-Frame-Options")
18
19
  end
19
20
  end
20
21
  end
@@ -9,15 +9,15 @@ module ShopifyApp
9
9
  return unless ShopifyApp.configuration.embedded_app?
10
10
  return unless user_agent_can_partition_cookies
11
11
 
12
- session['shopify.cookies_persist'] = true
12
+ session["shopify.cookies_persist"] = true
13
13
  end
14
14
 
15
15
  def set_top_level_oauth_cookie
16
- session['shopify.top_level_oauth'] = true
16
+ session["shopify.top_level_oauth"] = true
17
17
  end
18
18
 
19
19
  def clear_top_level_oauth_cookie
20
- session.delete('shopify.top_level_oauth')
20
+ session.delete("shopify.top_level_oauth")
21
21
  end
22
22
 
23
23
  def user_agent_is_mobile
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module Localization
4
5
  extend ActiveSupport::Concern
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'browser_sniffer'
3
+ require "browser_sniffer"
4
4
 
5
5
  module ShopifyApp
6
6
  module LoginProtection
@@ -13,82 +13,51 @@ module ShopifyApp
13
13
 
14
14
  included do
15
15
  after_action :set_test_cookie
16
- rescue_from ActiveResource::UnauthorizedAccess, with: :close_session
16
+ rescue_from ShopifyAPI::Errors::HttpResponseError, with: :handle_http_error
17
17
  end
18
18
 
19
- ACCESS_TOKEN_REQUIRED_HEADER = 'X-Shopify-API-Request-Failure-Unauthorized'
19
+ ACCESS_TOKEN_REQUIRED_HEADER = "X-Shopify-API-Request-Failure-Unauthorized"
20
20
 
21
21
  def activate_shopify_session
22
- if user_session_expected? && user_session.blank?
22
+ if current_shopify_session.blank?
23
23
  signal_access_token_required
24
24
  return redirect_to_login
25
25
  end
26
26
 
27
- return redirect_to_login if current_shopify_session.blank?
27
+ unless current_shopify_session.scope.to_a.empty? ||
28
+ current_shopify_session.scope.covers?(ShopifyAPI::Context.scope)
28
29
 
29
- clear_top_level_oauth_cookie
30
+ clear_shopify_session
31
+ return redirect_to_login
32
+ end
30
33
 
31
34
  begin
32
- ShopifyAPI::Base.activate_session(current_shopify_session)
35
+ ShopifyAPI::Context.activate_session(current_shopify_session)
33
36
  yield
34
37
  ensure
35
- ShopifyAPI::Base.clear_session
38
+ ShopifyAPI::Context.deactivate_session
36
39
  end
37
40
  end
38
41
 
39
42
  def current_shopify_session
40
43
  @current_shopify_session ||= begin
41
- user_session || shop_session
44
+ cookie_name = ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME
45
+ ShopifyAPI::Utils::SessionUtils.load_current_session(
46
+ auth_header: request.headers["HTTP_AUTHORIZATION"],
47
+ cookies: { cookie_name => cookies.encrypted[cookie_name] },
48
+ is_online: user_session_expected?
49
+ )
50
+ rescue ShopifyAPI::Errors::CookieNotFoundError
51
+ nil
52
+ rescue ShopifyAPI::Errors::InvalidJwtTokenError
53
+ nil
42
54
  end
43
55
  end
44
56
 
45
- def user_session
46
- user_session_by_jwt || user_session_by_cookie
47
- end
48
-
49
- def user_session_by_jwt
50
- return unless ShopifyApp.configuration.allow_jwt_authentication
51
- return unless jwt_shopify_user_id
52
- ShopifyApp::SessionRepository.retrieve_user_session_by_shopify_user_id(jwt_shopify_user_id)
53
- end
54
-
55
- def user_session_by_cookie
56
- return unless ShopifyApp.configuration.allow_cookie_authentication
57
- return unless session[:user_id].present?
58
- ShopifyApp::SessionRepository.retrieve_user_session(session[:user_id])
59
- end
60
-
61
- def shop_session
62
- shop_session_by_jwt || shop_session_by_cookie
63
- end
64
-
65
- def shop_session_by_jwt
66
- return unless ShopifyApp.configuration.allow_jwt_authentication
67
- return unless jwt_shopify_domain
68
- ShopifyApp::SessionRepository.retrieve_shop_session_by_shopify_domain(jwt_shopify_domain)
69
- end
70
-
71
- def shop_session_by_cookie
72
- return unless ShopifyApp.configuration.allow_cookie_authentication
73
- return unless session[:shop_id].present?
74
- ShopifyApp::SessionRepository.retrieve_shop_session(session[:shop_id])
75
- end
76
-
77
57
  def login_again_if_different_user_or_shop
78
- if session[:user_session].present? && params[:session].present? # session data was sent/stored correctly
79
- clear_session = session[:user_session] != params[:session] # current user is different from stored user
80
- end
81
-
82
- if current_shopify_session &&
83
- params[:shop] && params[:shop].is_a?(String) &&
84
- (current_shopify_session.domain != params[:shop])
85
- clear_session = true
86
- end
87
-
88
- if clear_session
89
- clear_shopify_session
90
- redirect_to_login
91
- end
58
+ return unless session_id_conflicts_with_params || session_shop_conflicts_with_params
59
+ clear_shopify_session
60
+ redirect_to_login
92
61
  end
93
62
 
94
63
  def signal_access_token_required
@@ -96,7 +65,7 @@ module ShopifyApp
96
65
  end
97
66
 
98
67
  def jwt_expire_at
99
- expire_at = request.env['jwt.expire_at']
68
+ expire_at = request.env["jwt.expire_at"]
100
69
  return unless expire_at
101
70
  expire_at - 5.seconds # 5s gap to start fetching new token in advance
102
71
  end
@@ -104,17 +73,15 @@ module ShopifyApp
104
73
  protected
105
74
 
106
75
  def jwt_shopify_domain
107
- request.env['jwt.shopify_domain']
76
+ request.env["jwt.shopify_domain"]
108
77
  end
109
78
 
110
79
  def jwt_shopify_user_id
111
- request.env['jwt.shopify_user_id']
80
+ request.env["jwt.shopify_user_id"]
112
81
  end
113
82
 
114
83
  def host
115
- return params[:host] if params[:host].present?
116
-
117
- raise ShopifyHostNotFound
84
+ params[:host]
118
85
  end
119
86
 
120
87
  def redirect_to_login
@@ -139,12 +106,16 @@ module ShopifyApp
139
106
  redirect_to(login_url_with_optional_shop)
140
107
  end
141
108
 
109
+ def handle_http_error(error)
110
+ if error.code == 401
111
+ close_session
112
+ else
113
+ raise error
114
+ end
115
+ end
116
+
142
117
  def clear_shopify_session
143
- session[:shop_id] = nil
144
- session[:user_id] = nil
145
- session[:shopify_domain] = nil
146
- session[:shopify_user] = nil
147
- session[:user_session] = nil
118
+ cookies.encrypted[ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME] = nil
148
119
  end
149
120
 
150
121
  def login_url_with_optional_shop(top_level: false)
@@ -172,28 +143,30 @@ module ShopifyApp
172
143
  query_params[:shop] ||= referer_sanitized_shop_name
173
144
  end
174
145
 
146
+ if params[:host].present?
147
+ query_params[:host] ||= host
148
+ end
149
+
175
150
  query_params[:top_level] = true if top_level
176
151
  query_params
177
152
  end
178
153
 
179
154
  def return_to_param_required?
180
- native_params = %i[shop hmac timestamp locale protocol return_to]
181
- request.path != '/' || sanitized_params.except(*native_params).any?
155
+ native_params = [:shop, :hmac, :timestamp, :locale, :protocol, :return_to]
156
+ request.path != "/" || sanitized_params.except(*native_params).any?
182
157
  end
183
158
 
184
159
  def fullpage_redirect_to(url)
185
160
  if ShopifyApp.configuration.embedded_app?
186
- render('shopify_app/shared/redirect', layout: false,
187
- locals: { url: url, current_shopify_domain: current_shopify_domain })
161
+ render("shopify_app/shared/redirect", layout: false,
162
+ locals: { url: url, current_shopify_domain: current_shopify_domain })
188
163
  else
189
164
  redirect_to(url)
190
165
  end
191
166
  end
192
167
 
193
168
  def current_shopify_domain
194
- shopify_domain = sanitized_shop_name ||
195
- jwt_shopify_domain ||
196
- session[:shopify_domain]
169
+ shopify_domain = sanitized_shop_name || current_shopify_session&.shop
197
170
 
198
171
  return shopify_domain if shopify_domain.present?
199
172
 
@@ -250,6 +223,15 @@ module ShopifyApp
250
223
 
251
224
  private
252
225
 
226
+ def session_id_conflicts_with_params
227
+ shopify_session_id = current_shopify_session&.shopify_session_id
228
+ params[:session].present? && shopify_session_id.present? && params[:session] != shopify_session_id
229
+ end
230
+
231
+ def session_shop_conflicts_with_params
232
+ current_shopify_session && params[:shop].is_a?(String) && current_shopify_session.shop != params[:shop]
233
+ end
234
+
253
235
  def user_session_expected?
254
236
  !ShopifyApp.configuration.user_session_repository.blank? && ShopifyApp::SessionRepository.user_storage.present?
255
237
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module PayloadVerification
4
5
  extend ActiveSupport::Concern
@@ -6,14 +7,14 @@ module ShopifyApp
6
7
  private
7
8
 
8
9
  def shopify_hmac
9
- request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']
10
+ request.headers["HTTP_X_SHOPIFY_HMAC_SHA256"]
10
11
  end
11
12
 
12
13
  def hmac_valid?(data)
13
14
  secrets = [ShopifyApp.configuration.secret, ShopifyApp.configuration.old_secret].reject(&:blank?)
14
15
 
15
16
  secrets.any? do |secret|
16
- digest = OpenSSL::Digest.new('sha256')
17
+ digest = OpenSSL::Digest.new("sha256")
17
18
  ActiveSupport::SecurityUtils.secure_compare(
18
19
  shopify_hmac,
19
20
  Base64.strict_encode64(OpenSSL::HMAC.digest(digest, secret, data))
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module WebhookVerification
4
5
  extend ActiveSupport::Concern
@@ -17,7 +18,7 @@ module ShopifyApp
17
18
  end
18
19
 
19
20
  def shop_domain
20
- request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN']
21
+ request.headers["HTTP_X_SHOPIFY_SHOP_DOMAIN"]
21
22
  end
22
23
  end
23
24
  end
@@ -1,36 +1,28 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module RedactJobParams
4
5
  private
5
6
 
6
7
  def args_info(job)
7
- log_disabled_classes = %w(ShopifyApp::ScripttagsManagerJob ShopifyApp::WebhooksManagerJob)
8
+ log_disabled_classes = ["ShopifyApp::ScripttagsManagerJob", "ShopifyApp::WebhooksManagerJob"]
8
9
  return "" if log_disabled_classes.include?(job.class.name)
9
10
  super
10
11
  end
11
12
  end
12
13
 
13
14
  class Engine < Rails::Engine
14
- engine_name 'shopify_app'
15
+ engine_name "shopify_app"
15
16
  isolate_namespace ShopifyApp
16
17
 
17
18
  initializer "shopify_app.assets.precompile" do |app|
18
- app.config.assets.precompile += %w[
19
- shopify_app/redirect.js
20
- shopify_app/post_redirect.js
21
- shopify_app/top_level.js
22
- shopify_app/enable_cookies.js
23
- shopify_app/request_storage_access.js
24
- storage_access.svg
25
- ]
19
+ app.config.assets.precompile += ["shopify_app/redirect.js", "shopify_app/post_redirect.js",
20
+ "shopify_app/top_level.js", "shopify_app/enable_cookies.js",
21
+ "shopify_app/request_storage_access.js", "storage_access.svg",]
26
22
  end
27
23
 
28
24
  initializer "shopify_app.middleware" do |app|
29
- app.config.middleware.insert_after(::Rack::Runtime, ShopifyApp::SameSiteCookieMiddleware)
30
-
31
- if ShopifyApp.configuration.allow_jwt_authentication
32
- app.config.middleware.insert_after(ShopifyApp::SameSiteCookieMiddleware, ShopifyApp::JWTMiddleware)
33
- end
25
+ app.config.middleware.insert_after(::Rack::Runtime, ShopifyApp::JWTMiddleware)
34
26
  end
35
27
 
36
28
  initializer "shopify_app.redact_job_params" do
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class ScripttagsManagerJob < ActiveJob::Base
4
5
  queue_as do
@@ -6,8 +7,7 @@ module ShopifyApp
6
7
  end
7
8
 
8
9
  def perform(shop_domain:, shop_token:, scripttags:)
9
- api_version = ShopifyApp.configuration.api_version
10
- ShopifyAPI::Session.temp(domain: shop_domain, token: shop_token, api_version: api_version) do
10
+ ShopifyAPI::Auth::Session.temp(shop: shop_domain, access_token: shop_token) do
11
11
  manager = ScripttagsManager.new(scripttags, shop_domain)
12
12
  manager.create_scripttags
13
13
  end
@@ -1,15 +1,14 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class WebhooksManagerJob < ActiveJob::Base
4
5
  queue_as do
5
6
  ShopifyApp.configuration.webhooks_manager_queue_name
6
7
  end
7
8
 
8
- def perform(shop_domain:, shop_token:, webhooks:)
9
- api_version = ShopifyApp.configuration.api_version
10
- ShopifyAPI::Session.temp(domain: shop_domain, token: shop_token, api_version: api_version) do
11
- manager = WebhooksManager.new(webhooks)
12
- manager.create_webhooks
9
+ def perform(shop_domain:, shop_token:)
10
+ ShopifyAPI::Auth::Session.temp(shop: shop_domain, access_token: shop_token) do |session|
11
+ WebhooksManager.create_webhooks(session: session)
13
12
  end
14
13
  end
15
14
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  class ScripttagsManager
4
5
  class CreationFailed < StandardError; end
@@ -44,7 +45,7 @@ module ShopifyApp
44
45
  def destroy_scripttags
45
46
  scripttags = expanded_scripttags
46
47
  ShopifyAPI::ScriptTag.all.each do |tag|
47
- ShopifyAPI::ScriptTag.delete(tag.id) if required_scripttag?(scripttags, tag)
48
+ tag.delete if required_scripttag?(scripttags, tag)
48
49
  end
49
50
 
50
51
  @current_scripttags = nil
@@ -61,9 +62,15 @@ module ShopifyApp
61
62
  end
62
63
 
63
64
  def create_scripttag(attributes)
64
- attributes.reverse_merge!(format: 'json')
65
- scripttag = ShopifyAPI::ScriptTag.create(attributes)
66
- raise CreationFailed, scripttag.errors.full_messages.to_sentence unless scripttag.persisted?
65
+ scripttag = ShopifyAPI::ScriptTag.new
66
+ attributes.each { |key, value| scripttag.public_send("#{key}=", value) }
67
+
68
+ begin
69
+ scripttag.save!
70
+ rescue ShopifyAPI::Errors::HttpResponseError => e
71
+ raise CreationFailed, e.message
72
+ end
73
+
67
74
  scripttag
68
75
  end
69
76