ruby_shopify_app 1.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 (186) hide show
  1. checksums.yaml +7 -0
  2. data/.babelrc +5 -0
  3. data/.github/CODEOWNERS +2 -0
  4. data/.github/ISSUE_TEMPLATE/bug-report.md +63 -0
  5. data/.github/ISSUE_TEMPLATE/config.yml +1 -0
  6. data/.github/ISSUE_TEMPLATE/feature-request.md +33 -0
  7. data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
  8. data/.github/probots.yml +2 -0
  9. data/.github/workflows/build.yml +40 -0
  10. data/.github/workflows/release.yml +24 -0
  11. data/.github/workflows/rubocop.yml +22 -0
  12. data/.gitignore +14 -0
  13. data/.nvmrc +1 -0
  14. data/.rubocop.yml +18 -0
  15. data/.ruby-version +1 -0
  16. data/CHANGELOG-OLD.md +643 -0
  17. data/CHANGELOG.md +6 -0
  18. data/CONTRIBUTING.md +81 -0
  19. data/Gemfile +11 -0
  20. data/Gemfile.lock +280 -0
  21. data/LICENSE +19 -0
  22. data/README.md +132 -0
  23. data/Rakefile +7 -0
  24. data/SECURITY.md +59 -0
  25. data/app/assets/images/storage_access.svg +1 -0
  26. data/app/assets/javascripts/shopify_app/app_bridge_2.0.12.js +10 -0
  27. data/app/assets/javascripts/shopify_app/app_bridge_redirect.js +22 -0
  28. data/app/assets/javascripts/shopify_app/enable_cookies.js +3 -0
  29. data/app/assets/javascripts/shopify_app/itp_helper.js +40 -0
  30. data/app/assets/javascripts/shopify_app/partition_cookies.js +8 -0
  31. data/app/assets/javascripts/shopify_app/post_redirect.js +9 -0
  32. data/app/assets/javascripts/shopify_app/redirect.js +31 -0
  33. data/app/assets/javascripts/shopify_app/request_storage_access.js +3 -0
  34. data/app/assets/javascripts/shopify_app/storage_access.js +148 -0
  35. data/app/assets/javascripts/shopify_app/storage_access_redirect.js +17 -0
  36. data/app/assets/javascripts/shopify_app/top_level.js +2 -0
  37. data/app/assets/javascripts/shopify_app/top_level_interaction.js +11 -0
  38. data/app/controllers/concerns/shopify_app/authenticated.rb +16 -0
  39. data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +39 -0
  40. data/app/controllers/concerns/shopify_app/require_known_shop.rb +40 -0
  41. data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +32 -0
  42. data/app/controllers/shopify_app/authenticated_controller.rb +8 -0
  43. data/app/controllers/shopify_app/callback_controller.rb +195 -0
  44. data/app/controllers/shopify_app/extension_verification_controller.rb +15 -0
  45. data/app/controllers/shopify_app/sessions_controller.rb +202 -0
  46. data/app/controllers/shopify_app/webhooks_controller.rb +36 -0
  47. data/app/views/shopify_app/partials/_button_styles.html.erb +109 -0
  48. data/app/views/shopify_app/partials/_card_styles.html.erb +33 -0
  49. data/app/views/shopify_app/partials/_empty_state_styles.html.erb +98 -0
  50. data/app/views/shopify_app/partials/_form_styles.html.erb +56 -0
  51. data/app/views/shopify_app/partials/_layout_styles.html.erb +182 -0
  52. data/app/views/shopify_app/partials/_typography_styles.html.erb +35 -0
  53. data/app/views/shopify_app/sessions/enable_cookies.html.erb +70 -0
  54. data/app/views/shopify_app/sessions/new.html.erb +51 -0
  55. data/app/views/shopify_app/sessions/request_storage_access.html.erb +68 -0
  56. data/app/views/shopify_app/sessions/top_level_interaction.html.erb +63 -0
  57. data/app/views/shopify_app/shared/post_redirect_to_auth_shopify.html.erb +13 -0
  58. data/app/views/shopify_app/shared/redirect.html.erb +23 -0
  59. data/config/locales/cs.yml +23 -0
  60. data/config/locales/da.yml +20 -0
  61. data/config/locales/de.yml +22 -0
  62. data/config/locales/en.yml +15 -0
  63. data/config/locales/es.yml +22 -0
  64. data/config/locales/fi.yml +20 -0
  65. data/config/locales/fr.yml +23 -0
  66. data/config/locales/it.yml +21 -0
  67. data/config/locales/ja.yml +17 -0
  68. data/config/locales/ko.yml +19 -0
  69. data/config/locales/nb.yml +21 -0
  70. data/config/locales/nl.yml +21 -0
  71. data/config/locales/pl.yml +21 -0
  72. data/config/locales/pt-BR.yml +21 -0
  73. data/config/locales/pt-PT.yml +22 -0
  74. data/config/locales/sv.yml +21 -0
  75. data/config/locales/th.yml +20 -0
  76. data/config/locales/tr.yml +22 -0
  77. data/config/locales/vi.yml +22 -0
  78. data/config/locales/zh-CN.yml +16 -0
  79. data/config/locales/zh-TW.yml +16 -0
  80. data/config/routes.rb +23 -0
  81. data/docs/Quickstart.md +31 -0
  82. data/docs/Releasing.md +21 -0
  83. data/docs/Troubleshooting.md +159 -0
  84. data/docs/Upgrading.md +132 -0
  85. data/docs/shopify_app/authentication.md +124 -0
  86. data/docs/shopify_app/engine.md +82 -0
  87. data/docs/shopify_app/generators.md +127 -0
  88. data/docs/shopify_app/handling-access-scopes-changes.md +24 -0
  89. data/docs/shopify_app/script-tags.md +28 -0
  90. data/docs/shopify_app/session-repository.md +88 -0
  91. data/docs/shopify_app/testing.md +38 -0
  92. data/docs/shopify_app/webhooks.md +72 -0
  93. data/images/app-proxy-screenshot.png +0 -0
  94. data/karma.conf.js +44 -0
  95. data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +47 -0
  96. data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +11 -0
  97. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +40 -0
  98. data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +62 -0
  99. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +69 -0
  100. data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +13 -0
  101. data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +26 -0
  102. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +8 -0
  103. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +11 -0
  104. data/lib/generators/shopify_app/app_proxy_controller/templates/index.html.erb +19 -0
  105. data/lib/generators/shopify_app/authenticated_controller/authenticated_controller_generator.rb +15 -0
  106. data/lib/generators/shopify_app/authenticated_controller/templates/authenticated_controller.rb +5 -0
  107. data/lib/generators/shopify_app/controllers/controllers_generator.rb +30 -0
  108. data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +53 -0
  109. data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +18 -0
  110. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +75 -0
  111. data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +12 -0
  112. data/lib/generators/shopify_app/install/install_generator.rb +121 -0
  113. data/lib/generators/shopify_app/install/templates/_flash_messages.html.erb +3 -0
  114. data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +44 -0
  115. data/lib/generators/shopify_app/install/templates/flash_messages.js +24 -0
  116. data/lib/generators/shopify_app/install/templates/omniauth.rb +4 -0
  117. data/lib/generators/shopify_app/install/templates/session_store.rb +4 -0
  118. data/lib/generators/shopify_app/install/templates/shopify_app.js +15 -0
  119. data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +25 -0
  120. data/lib/generators/shopify_app/install/templates/shopify_app_importmap.js +13 -0
  121. data/lib/generators/shopify_app/install/templates/shopify_app_index.js +2 -0
  122. data/lib/generators/shopify_app/install/templates/shopify_provider.rb.tt +8 -0
  123. data/lib/generators/shopify_app/install/templates/user_agent.rb +6 -0
  124. data/lib/generators/shopify_app/products_controller/products_controller_generator.rb +19 -0
  125. data/lib/generators/shopify_app/products_controller/templates/products_controller.rb +8 -0
  126. data/lib/generators/shopify_app/rotate_shopify_token_job/rotate_shopify_token_job_generator.rb +16 -0
  127. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +17 -0
  128. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +42 -0
  129. data/lib/generators/shopify_app/routes/routes_generator.rb +32 -0
  130. data/lib/generators/shopify_app/routes/templates/routes.rb +12 -0
  131. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +70 -0
  132. data/lib/generators/shopify_app/shop_model/templates/db/migrate/add_shop_access_scopes_column.erb +5 -0
  133. data/lib/generators/shopify_app/shop_model/templates/db/migrate/create_shops.erb +15 -0
  134. data/lib/generators/shopify_app/shop_model/templates/shop.rb +8 -0
  135. data/lib/generators/shopify_app/shop_model/templates/shops.yml +3 -0
  136. data/lib/generators/shopify_app/shopify_app_generator.rb +18 -0
  137. data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_access_scopes_column.erb +5 -0
  138. data/lib/generators/shopify_app/user_model/templates/db/migrate/create_users.erb +16 -0
  139. data/lib/generators/shopify_app/user_model/templates/user.rb +8 -0
  140. data/lib/generators/shopify_app/user_model/templates/users.yml +4 -0
  141. data/lib/generators/shopify_app/user_model/user_model_generator.rb +70 -0
  142. data/lib/generators/shopify_app/views/views_generator.rb +30 -0
  143. data/lib/shopify_app/access_scopes/noop_strategy.rb +13 -0
  144. data/lib/shopify_app/access_scopes/shop_strategy.rb +24 -0
  145. data/lib/shopify_app/access_scopes/user_strategy.rb +41 -0
  146. data/lib/shopify_app/configuration.rb +119 -0
  147. data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +38 -0
  148. data/lib/shopify_app/controller_concerns/csrf_protection.rb +15 -0
  149. data/lib/shopify_app/controller_concerns/embedded_app.rb +20 -0
  150. data/lib/shopify_app/controller_concerns/itp.rb +45 -0
  151. data/lib/shopify_app/controller_concerns/localization.rb +23 -0
  152. data/lib/shopify_app/controller_concerns/login_protection.rb +259 -0
  153. data/lib/shopify_app/controller_concerns/payload_verification.rb +24 -0
  154. data/lib/shopify_app/controller_concerns/webhook_verification.rb +23 -0
  155. data/lib/shopify_app/engine.rb +47 -0
  156. data/lib/shopify_app/jobs/scripttags_manager_job.rb +16 -0
  157. data/lib/shopify_app/jobs/webhooks_manager_job.rb +16 -0
  158. data/lib/shopify_app/managers/scripttags_manager.rb +78 -0
  159. data/lib/shopify_app/managers/webhooks_manager.rb +62 -0
  160. data/lib/shopify_app/middleware/jwt_middleware.rb +43 -0
  161. data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +34 -0
  162. data/lib/shopify_app/omniauth/omniauth_configuration.rb +64 -0
  163. data/lib/shopify_app/session/in_memory_session_store.rb +31 -0
  164. data/lib/shopify_app/session/in_memory_shop_session_store.rb +16 -0
  165. data/lib/shopify_app/session/in_memory_user_session_store.rb +16 -0
  166. data/lib/shopify_app/session/jwt.rb +67 -0
  167. data/lib/shopify_app/session/null_user_session_store.rb +22 -0
  168. data/lib/shopify_app/session/session_repository.rb +56 -0
  169. data/lib/shopify_app/session/session_storage.rb +20 -0
  170. data/lib/shopify_app/session/shop_session_storage.rb +42 -0
  171. data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +58 -0
  172. data/lib/shopify_app/session/user_session_storage.rb +42 -0
  173. data/lib/shopify_app/session/user_session_storage_with_scopes.rb +58 -0
  174. data/lib/shopify_app/test_helpers/all.rb +2 -0
  175. data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +17 -0
  176. data/lib/shopify_app/utils.rb +37 -0
  177. data/lib/shopify_app/version.rb +4 -0
  178. data/lib/shopify_app.rb +80 -0
  179. data/package.json +27 -0
  180. data/service.yml +4 -0
  181. data/shipit.rubygems.yml +4 -0
  182. data/shopify_app.gemspec +39 -0
  183. data/translation.yml +7 -0
  184. data/webpack.config.js +24 -0
  185. data/yarn.lock +5230 -0
  186. metadata +465 -0
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ module Generators
4
+ class ShopifyAppGenerator < Rails::Generators::Base
5
+ def initialize(args, *options)
6
+ @opts = options.first
7
+ super(args, *options)
8
+ end
9
+
10
+ def run_all_generators
11
+ generate("shopify_app:install #{@opts.join(' ')}")
12
+ generate("shopify_app:shop_model #{@opts.join(' ')}")
13
+ generate("shopify_app:authenticated_controller")
14
+ generate("shopify_app:home_controller #{@opts.join(' ')}")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ class AddUserAccessScopesColumn < ActiveRecord::Migration[<%= rails_migration_version %>]
2
+ def change
3
+ add_column :users, :access_scopes, :string
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ class CreateUsers < ActiveRecord::Migration[<%= rails_migration_version %>]
2
+ def self.up
3
+ create_table :users do |t|
4
+ t.bigint :shopify_user_id, null: false
5
+ t.string :shopify_domain, null: false
6
+ t.string :shopify_token, null: false
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :users, :shopify_user_id, unique: true
11
+ end
12
+
13
+ def self.down
14
+ drop_table :users
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ class User < ActiveRecord::Base
3
+ include ShopifyApp::UserSessionStorageWithScopes
4
+
5
+ def api_version
6
+ ShopifyApp.configuration.api_version
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ regular_user:
2
+ shopify_domain: 'regular-shop.myshopify.com'
3
+ shopify_token: 'token'
4
+ shopify_user_id: 1
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+ require 'rails/generators/base'
3
+ require 'rails/generators/active_record'
4
+
5
+ module ShopifyApp
6
+ module Generators
7
+ class UserModelGenerator < Rails::Generators::Base
8
+ include Rails::Generators::Migration
9
+ source_root File.expand_path('../templates', __FILE__)
10
+
11
+ class_option :new_shopify_cli_app, type: :boolean, default: false
12
+
13
+ def create_user_model
14
+ copy_file('user.rb', 'app/models/user.rb')
15
+ end
16
+
17
+ def create_user_migration
18
+ migration_template('db/migrate/create_users.erb', 'db/migrate/create_users.rb')
19
+ end
20
+
21
+ def create_scopes_storage_in_user_model
22
+ scopes_column_prompt = <<~PROMPT
23
+ It is highly recommended that apps record the access scopes granted by \
24
+ merchants during app installation. See app/models/user.rb to modify how \
25
+ access scopes are stored and retrieved.
26
+
27
+ [WARNING] You will need to update the access_scopes accessors in the User model \
28
+ to allow shopify_app to store and retrieve scopes when going through OAuth.
29
+
30
+ The following migration will add an `access_scopes` column to the User model. \
31
+ Do you want to include this migration? [y/n]
32
+ PROMPT
33
+
34
+ if new_shopify_cli_app? || Rails.env.test? || yes?(scopes_column_prompt)
35
+ migration_template(
36
+ 'db/migrate/add_user_access_scopes_column.erb',
37
+ 'db/migrate/add_user_access_scopes_column.rb'
38
+ )
39
+ end
40
+ end
41
+
42
+ def update_shopify_app_initializer
43
+ gsub_file('config/initializers/shopify_app.rb', 'ShopifyApp::InMemoryUserSessionStore', 'User')
44
+ end
45
+
46
+ def create_user_fixtures
47
+ copy_file('users.yml', 'test/fixtures/users.yml')
48
+ end
49
+
50
+ private
51
+
52
+ def new_shopify_cli_app?
53
+ options['new_shopify_cli_app']
54
+ end
55
+
56
+ def rails_migration_version
57
+ Rails.version.match(/\d\.\d/)[0]
58
+ end
59
+
60
+ class << self
61
+ private :next_migration_number
62
+
63
+ # for generating a timestamp when using `create_migration`
64
+ def next_migration_number(dir)
65
+ ActiveRecord::Generators::Base.next_migration_number(dir)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ require 'rails/generators/base'
3
+
4
+ module ShopifyApp
5
+ module Generators
6
+ class ViewsGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("../../../../..", __FILE__)
8
+
9
+ def create_views
10
+ views.each do |view|
11
+ copy_file(view)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def views
18
+ files_within_root('.', 'app/views/**/*.*')
19
+ end
20
+
21
+ def files_within_root(prefix, glob)
22
+ root = "#{self.class.source_root}/#{prefix}"
23
+
24
+ Dir["#{root}/#{glob}"].sort.map do |full_path|
25
+ full_path.sub(root, '.').gsub('/./', '/')
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyApp
4
+ module AccessScopes
5
+ class NoopStrategy
6
+ class << self
7
+ def update_access_scopes?(*_args)
8
+ false
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyApp
4
+ module AccessScopes
5
+ class ShopStrategy
6
+ class << self
7
+ def update_access_scopes?(shop_domain)
8
+ shop_access_scopes = shop_access_scopes(shop_domain)
9
+ configuration_access_scopes != shop_access_scopes
10
+ end
11
+
12
+ private
13
+
14
+ def shop_access_scopes(shop_domain)
15
+ ShopifyApp::SessionRepository.retrieve_shop_session_by_shopify_domain(shop_domain)&.access_scopes
16
+ end
17
+
18
+ def configuration_access_scopes
19
+ ShopifyAPI::ApiAccess.new(ShopifyApp.configuration.shop_access_scopes)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyApp
4
+ module AccessScopes
5
+ class UserStrategy
6
+ class InvalidInput < StandardError; end
7
+
8
+ class << self
9
+ def update_access_scopes?(user_id: nil, shopify_user_id: nil)
10
+ return update_access_scopes_for_user_id?(user_id) if user_id
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')
13
+ end
14
+
15
+ private
16
+
17
+ def update_access_scopes_for_user_id?(user_id)
18
+ user_access_scopes = user_access_scopes_by_user_id(user_id)
19
+ configuration_access_scopes != user_access_scopes
20
+ end
21
+
22
+ def update_access_scopes_for_shopify_user_id?(shopify_user_id)
23
+ user_access_scopes = user_access_scopes_by_shopify_user_id(shopify_user_id)
24
+ configuration_access_scopes != user_access_scopes
25
+ end
26
+
27
+ def user_access_scopes_by_user_id(user_id)
28
+ ShopifyApp::SessionRepository.retrieve_user_session(user_id)&.access_scopes
29
+ end
30
+
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
33
+ end
34
+
35
+ def configuration_access_scopes
36
+ ShopifyAPI::ApiAccess.new(ShopifyApp.configuration.user_access_scopes)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class Configuration
4
+ # Shopify App settings. These values should match the configuration
5
+ # for the app in your Shopify Partners page. Change your settings in
6
+ # `config/initializers/shopify_app.rb`
7
+ attr_accessor :application_name
8
+ attr_accessor :api_key
9
+ attr_accessor :secret
10
+ attr_accessor :old_secret
11
+ attr_accessor :scope
12
+ attr_writer :shop_access_scopes
13
+ attr_writer :user_access_scopes
14
+ attr_accessor :embedded_app
15
+ alias_method :embedded_app?, :embedded_app
16
+ attr_accessor :webhooks
17
+ attr_accessor :scripttags
18
+ attr_accessor :after_authenticate_job
19
+ attr_accessor :api_version
20
+
21
+ attr_accessor :reauth_on_access_scope_changes
22
+
23
+ # customise urls
24
+ attr_accessor :root_url
25
+ attr_writer :login_url
26
+
27
+ # customise ActiveJob queue names
28
+ attr_accessor :scripttags_manager_queue_name
29
+ attr_accessor :webhooks_manager_queue_name
30
+
31
+ # configure myshopify domain for local shopify development
32
+ attr_accessor :myshopify_domain
33
+
34
+ # ability to have webpacker installed but not used in this gem and the generators
35
+ attr_accessor :disable_webpacker
36
+
37
+ # allow namespacing webhook jobs
38
+ attr_accessor :webhook_jobs_namespace
39
+
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
+ def initialize
49
+ @root_url = '/'
50
+ @myshopify_domain = 'myshopify.com'
51
+ @scripttags_manager_queue_name = Rails.application.config.active_job.queue_name
52
+ @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
55
+ end
56
+
57
+ def login_url
58
+ @login_url || File.join(@root_url, 'login')
59
+ end
60
+
61
+ def user_session_repository=(klass)
62
+ ShopifyApp::SessionRepository.user_storage = klass
63
+ end
64
+
65
+ def user_session_repository
66
+ ShopifyApp::SessionRepository.user_storage
67
+ end
68
+
69
+ def shop_session_repository=(klass)
70
+ ShopifyApp::SessionRepository.shop_storage = klass
71
+ end
72
+
73
+ def shop_session_repository
74
+ ShopifyApp::SessionRepository.shop_storage
75
+ end
76
+
77
+ def shop_access_scopes_strategy
78
+ return ShopifyApp::AccessScopes::NoopStrategy unless reauth_on_access_scope_changes
79
+ ShopifyApp::AccessScopes::ShopStrategy
80
+ end
81
+
82
+ def user_access_scopes_strategy
83
+ return ShopifyApp::AccessScopes::NoopStrategy unless reauth_on_access_scope_changes
84
+ ShopifyApp::AccessScopes::UserStrategy
85
+ end
86
+
87
+ def has_webhooks?
88
+ webhooks.present?
89
+ end
90
+
91
+ def has_scripttags?
92
+ scripttags.present?
93
+ end
94
+
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
+ def shop_access_scopes
100
+ @shop_access_scopes || scope
101
+ end
102
+
103
+ def user_access_scopes
104
+ @user_access_scopes || scope
105
+ end
106
+ end
107
+
108
+ def self.configuration
109
+ @configuration ||= Configuration.new
110
+ end
111
+
112
+ def self.configuration=(config)
113
+ @configuration = config
114
+ end
115
+
116
+ def self.configure
117
+ yield configuration
118
+ end
119
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ module AppProxyVerification
4
+ extend ActiveSupport::Concern
5
+ included do
6
+ skip_before_action :verify_authenticity_token, raise: false
7
+ before_action :verify_proxy_request
8
+ end
9
+
10
+ def verify_proxy_request
11
+ return head(:forbidden) unless query_string_valid?(request.query_string)
12
+ end
13
+
14
+ private
15
+
16
+ def query_string_valid?(query_string)
17
+ query_hash = Rack::Utils.parse_query(query_string)
18
+
19
+ signature = query_hash.delete('signature')
20
+ return false if signature.nil?
21
+
22
+ ActiveSupport::SecurityUtils.secure_compare(
23
+ calculated_signature(query_hash),
24
+ signature
25
+ )
26
+ end
27
+
28
+ def calculated_signature(query_hash_without_signature)
29
+ sorted_params = query_hash_without_signature.collect { |k, v| "#{k}=#{Array(v).join(',')}" }.sort.join
30
+
31
+ OpenSSL::HMAC.hexdigest(
32
+ OpenSSL::Digest.new('sha256'),
33
+ ShopifyApp.configuration.secret,
34
+ sorted_params
35
+ )
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ module CsrfProtection
4
+ extend ActiveSupport::Concern
5
+ included do
6
+ protect_from_forgery with: :exception, unless: :valid_session_token?
7
+ end
8
+
9
+ private
10
+
11
+ def valid_session_token?
12
+ request.env['jwt.shopify_domain']
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ module EmbeddedApp
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ if ShopifyApp.configuration.embedded_app?
8
+ after_action(:set_esdk_headers)
9
+ layout('embedded_app')
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def set_esdk_headers
16
+ response.set_header('P3P', 'CP="Not used"')
17
+ response.headers.except!('X-Frame-Options')
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyApp
4
+ # Cookie management helpers required for ITP implementation
5
+ module Itp
6
+ private
7
+
8
+ def set_test_cookie
9
+ return unless ShopifyApp.configuration.embedded_app?
10
+ return unless user_agent_can_partition_cookies
11
+
12
+ session['shopify.cookies_persist'] = true
13
+ end
14
+
15
+ def set_top_level_oauth_cookie
16
+ session['shopify.top_level_oauth'] = true
17
+ end
18
+
19
+ def clear_top_level_oauth_cookie
20
+ session.delete('shopify.top_level_oauth')
21
+ end
22
+
23
+ def user_agent_is_mobile
24
+ user_agent = BrowserSniffer.new(request.user_agent).browser_info
25
+
26
+ user_agent[:name].to_s.match(/Shopify\sMobile/)
27
+ end
28
+
29
+ def user_agent_is_pos
30
+ user_agent = BrowserSniffer.new(request.user_agent).browser_info
31
+
32
+ user_agent[:name].to_s.match(/Shopify\sPOS/)
33
+ end
34
+
35
+ def user_agent_can_partition_cookies
36
+ user_agent = BrowserSniffer.new(request.user_agent).browser_info
37
+
38
+ is_safari = user_agent[:name].to_s.match(/Safari/)
39
+
40
+ return false unless is_safari
41
+
42
+ user_agent[:version].to_s.match(/12\.0/)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ module Localization
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_action :set_locale
8
+ end
9
+
10
+ private
11
+
12
+ def set_locale
13
+ if params[:locale]
14
+ session[:locale] = params[:locale]
15
+ else
16
+ session[:locale] ||= I18n.default_locale
17
+ end
18
+ I18n.locale = session[:locale]
19
+ rescue I18n::InvalidLocale
20
+ I18n.locale = I18n.default_locale
21
+ end
22
+ end
23
+ end