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
@@ -1,10 +1,11 @@
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
6
7
  class RoutesGenerator < Rails::Generators::Base
7
- source_root File.expand_path('../templates', __FILE__)
8
+ source_root File.expand_path("../templates", __FILE__)
8
9
 
9
10
  def inject_shopify_app_routes_into_application_routes
10
11
  route(session_routes)
@@ -12,9 +13,9 @@ module ShopifyApp
12
13
 
13
14
  def disable_engine_routes
14
15
  gsub_file(
15
- 'config/routes.rb',
16
+ "config/routes.rb",
16
17
  "mount ShopifyApp::Engine, at: '/'",
17
- ''
18
+ ""
18
19
  )
19
20
  end
20
21
 
@@ -25,7 +26,7 @@ module ShopifyApp
25
26
  end
26
27
 
27
28
  def routes_file_path
28
- File.expand_path(find_in_source_paths('routes.rb'))
29
+ File.expand_path(find_in_source_paths("routes.rb"))
29
30
  end
30
31
  end
31
32
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  controller :sessions do
4
- get 'login' => :new, :as => :login
5
- post 'login' => :create, :as => :authenticate
6
- get 'auth/shopify/callback' => :callback
7
- get 'logout' => :destroy, :as => :logout
4
+ get "login" => :new, :as => :login
5
+ post "login" => :create, :as => :authenticate
6
+ get "auth/shopify/callback" => :callback
7
+ get "logout" => :destroy, :as => :logout
8
8
  end
9
9
 
10
10
  namespace :webhooks do
11
- post ':type' => :receive
11
+ post ":type" => :receive
12
12
  end
@@ -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 ShopModelGenerator < 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_shop_model
14
- copy_file('shop.rb', 'app/models/shop.rb')
15
+ copy_file("shop.rb", "app/models/shop.rb")
15
16
  end
16
17
 
17
18
  def create_shop_migration
18
- migration_template('db/migrate/create_shops.erb', 'db/migrate/create_shops.rb')
19
+ migration_template("db/migrate/create_shops.erb", "db/migrate/create_shops.rb")
19
20
  end
20
21
 
21
22
  def create_shop_with_access_scopes_migration
@@ -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_shop_access_scopes_column.erb',
37
- 'db/migrate/add_shop_access_scopes_column.rb'
37
+ "db/migrate/add_shop_access_scopes_column.erb",
38
+ "db/migrate/add_shop_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::InMemoryShopSessionStore', 'Shop')
44
+ gsub_file("config/initializers/shopify_app.rb", "ShopifyApp::InMemoryShopSessionStore", "Shop")
44
45
  end
45
46
 
46
47
  def create_shop_fixtures
47
- copy_file('shops.yml', 'test/fixtures/shops.yml')
48
+ copy_file("shops.yml", "test/fixtures/shops.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,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Shop < ActiveRecord::Base
3
4
  include ShopifyApp::ShopSessionStorageWithScopes
4
5
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ShopifyApp
3
4
  module Generators
4
5
  class ShopifyAppGenerator < Rails::Generators::Base
@@ -8,10 +9,10 @@ module ShopifyApp
8
9
  end
9
10
 
10
11
  def run_all_generators
11
- generate("shopify_app:install #{@opts.join(' ')}")
12
- generate("shopify_app:shop_model #{@opts.join(' ')}")
12
+ generate("shopify_app:install #{@opts.join(" ")}")
13
+ generate("shopify_app:shop_model #{@opts.join(" ")}")
13
14
  generate("shopify_app:authenticated_controller")
14
- generate("shopify_app:home_controller #{@opts.join(' ')}")
15
+ generate("shopify_app:home_controller #{@opts.join(" ")}")
15
16
  end
16
17
  end
17
18
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class User < ActiveRecord::Base
3
4
  include ShopifyApp::UserSessionStorageWithScopes
4
5
 
@@ -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,11 +73,11 @@ 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
@@ -137,12 +106,16 @@ module ShopifyApp
137
106
  redirect_to(login_url_with_optional_shop)
138
107
  end
139
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
+
140
117
  def clear_shopify_session
141
- session[:shop_id] = nil
142
- session[:user_id] = nil
143
- session[:shopify_domain] = nil
144
- session[:shopify_user] = nil
145
- session[:user_session] = nil
118
+ cookies.encrypted[ShopifyAPI::Auth::Oauth::SessionCookie::SESSION_COOKIE_NAME] = nil
146
119
  end
147
120
 
148
121
  def login_url_with_optional_shop(top_level: false)
@@ -179,23 +152,21 @@ module ShopifyApp
179
152
  end
180
153
 
181
154
  def return_to_param_required?
182
- native_params = %i[shop hmac timestamp locale protocol return_to]
183
- 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?
184
157
  end
185
158
 
186
159
  def fullpage_redirect_to(url)
187
160
  if ShopifyApp.configuration.embedded_app?
188
- render('shopify_app/shared/redirect', layout: false,
189
- 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 })
190
163
  else
191
164
  redirect_to(url)
192
165
  end
193
166
  end
194
167
 
195
168
  def current_shopify_domain
196
- shopify_domain = sanitized_shop_name ||
197
- jwt_shopify_domain ||
198
- session[:shopify_domain]
169
+ shopify_domain = sanitized_shop_name || current_shopify_session&.shop
199
170
 
200
171
  return shopify_domain if shopify_domain.present?
201
172
 
@@ -252,7 +223,22 @@ module ShopifyApp
252
223
 
253
224
  private
254
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
+
235
+ def shop_session
236
+ ShopifyApp::SessionRepository.retrieve_shop_session_by_shopify_domain(sanitize_shop_param(params))
237
+ end
238
+
255
239
  def user_session_expected?
240
+ return false if shop_session.nil?
241
+ return false if ShopifyApp.configuration.shop_access_scopes_strategy.update_access_scopes?(shop_session.shop)
256
242
  !ShopifyApp.configuration.user_session_repository.blank? && ShopifyApp::SessionRepository.user_storage.present?
257
243
  end
258
244
  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