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,16 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class InMemoryShopSessionStore < InMemorySessionStore
4
+ class << self
5
+ def store(session, *args)
6
+ id = super
7
+ repo[session.domain] = session
8
+ id
9
+ end
10
+
11
+ def retrieve_by_shopify_domain(shopify_domain)
12
+ repo[shopify_domain]
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class InMemoryUserSessionStore < InMemorySessionStore
4
+ class << self
5
+ def store(session, user)
6
+ id = super
7
+ repo[user.shopify_user_id] = session
8
+ id
9
+ end
10
+
11
+ def retrieve_by_shopify_user_id(user_id)
12
+ repo[user_id]
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class JWT
4
+ class InvalidDestinationError < StandardError; end
5
+
6
+ class MismatchedHostsError < StandardError; end
7
+
8
+ class InvalidAudienceError < StandardError; end
9
+
10
+ WARN_EXCEPTIONS = [
11
+ ::JWT::DecodeError,
12
+ ::JWT::ExpiredSignature,
13
+ ::JWT::ImmatureSignature,
14
+ ::JWT::VerificationError,
15
+ InvalidAudienceError,
16
+ InvalidDestinationError,
17
+ MismatchedHostsError,
18
+ ]
19
+
20
+ def initialize(token)
21
+ @token = token
22
+ set_payload
23
+ end
24
+
25
+ def shopify_domain
26
+ @payload && ShopifyApp::Utils.sanitize_shop_domain(@payload['dest'])
27
+ end
28
+
29
+ def shopify_user_id
30
+ @payload['sub'].to_i if @payload && @payload['sub']
31
+ end
32
+
33
+ def expire_at
34
+ @payload['exp'].to_i if @payload && @payload['exp']
35
+ end
36
+
37
+ private
38
+
39
+ def set_payload
40
+ payload, _ = parse_token_data(ShopifyApp.configuration&.secret, ShopifyApp.configuration&.old_secret)
41
+ @payload = validate_payload(payload)
42
+ rescue *WARN_EXCEPTIONS => error
43
+ Rails.logger.warn("[ShopifyApp::JWT] Failed to validate JWT: [#{error.class}] #{error}")
44
+ nil
45
+ end
46
+
47
+ def parse_token_data(secret, old_secret)
48
+ ::JWT.decode(@token, secret, true, { algorithm: 'HS256' })
49
+ rescue ::JWT::VerificationError
50
+ raise unless old_secret
51
+
52
+ ::JWT.decode(@token, old_secret, true, { algorithm: 'HS256' })
53
+ end
54
+
55
+ def validate_payload(payload)
56
+ dest_host = ShopifyApp::Utils.sanitize_shop_domain(payload['dest'])
57
+ iss_host = ShopifyApp::Utils.sanitize_shop_domain(payload['iss'])
58
+ api_key = ShopifyApp.configuration.api_key
59
+
60
+ raise InvalidAudienceError, "'aud' claim does not match api_key" unless payload['aud'] == api_key
61
+ raise InvalidDestinationError, "'dest' claim host not a valid shopify host" unless dest_host
62
+ raise MismatchedHostsError, "'dest' claim host does not match 'iss' claim host" unless dest_host == iss_host
63
+
64
+ payload
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class NullUserSessionStore
4
+ class << self
5
+ def retrieve(_)
6
+ nil
7
+ end
8
+
9
+ def store(_, _)
10
+ raise SessionRepository::ConfigurationError, 'user_storage is not configured'
11
+ end
12
+
13
+ def retrieve_by_shopify_user_id(_)
14
+ nil
15
+ end
16
+
17
+ def blank?
18
+ true
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class SessionRepository
4
+ class ConfigurationError < StandardError; end
5
+
6
+ class << self
7
+ attr_writer :shop_storage
8
+
9
+ attr_writer :user_storage
10
+
11
+ def retrieve_shop_session(id)
12
+ shop_storage.retrieve(id)
13
+ end
14
+
15
+ def retrieve_user_session(id)
16
+ user_storage.retrieve(id)
17
+ end
18
+
19
+ def retrieve_shop_session_by_shopify_domain(shopify_domain)
20
+ shop_storage.retrieve_by_shopify_domain(shopify_domain)
21
+ end
22
+
23
+ def retrieve_user_session_by_shopify_user_id(user_id)
24
+ user_storage.retrieve_by_shopify_user_id(user_id)
25
+ end
26
+
27
+ def store_shop_session(session)
28
+ shop_storage.store(session)
29
+ end
30
+
31
+ def store_user_session(session, user)
32
+ user_storage.store(session, user)
33
+ end
34
+
35
+ def shop_storage
36
+ load_shop_storage || raise(ConfigurationError, "ShopifySessionRepository.shop_storage is not configured!")
37
+ end
38
+
39
+ def user_storage
40
+ load_user_storage
41
+ end
42
+
43
+ private
44
+
45
+ def load_shop_storage
46
+ return unless @shop_storage
47
+ @shop_storage.respond_to?(:safe_constantize) ? @shop_storage.safe_constantize : @shop_storage
48
+ end
49
+
50
+ def load_user_storage
51
+ return NullUserSessionStore unless @user_storage
52
+ @user_storage.respond_to?(:safe_constantize) ? @user_storage.safe_constantize : @user_storage
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ module SessionStorage
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ validates :shopify_token, presence: true
8
+ validates :api_version, presence: true
9
+ end
10
+
11
+ def with_shopify_session(&block)
12
+ ShopifyAPI::Session.temp(
13
+ domain: shopify_domain,
14
+ token: shopify_token,
15
+ api_version: api_version,
16
+ &block
17
+ )
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ module ShopSessionStorage
4
+ extend ActiveSupport::Concern
5
+ include ::ShopifyApp::SessionStorage
6
+
7
+ included do
8
+ validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false }
9
+ end
10
+
11
+ class_methods do
12
+ def store(auth_session, *_args)
13
+ shop = find_or_initialize_by(shopify_domain: auth_session.domain)
14
+ shop.shopify_token = auth_session.token
15
+ shop.save!
16
+ shop.id
17
+ end
18
+
19
+ def retrieve(id)
20
+ shop = find_by(id: id)
21
+ construct_session(shop)
22
+ end
23
+
24
+ def retrieve_by_shopify_domain(domain)
25
+ shop = find_by(shopify_domain: domain)
26
+ construct_session(shop)
27
+ end
28
+
29
+ private
30
+
31
+ def construct_session(shop)
32
+ return unless shop
33
+
34
+ ShopifyAPI::Session.new(
35
+ domain: shop.shopify_domain,
36
+ token: shop.shopify_token,
37
+ api_version: shop.api_version,
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyApp
4
+ module ShopSessionStorageWithScopes
5
+ extend ActiveSupport::Concern
6
+ include ::ShopifyApp::SessionStorage
7
+
8
+ included do
9
+ validates :shopify_domain, presence: true, uniqueness: { case_sensitive: false }
10
+ end
11
+
12
+ class_methods do
13
+ def store(auth_session, *_args)
14
+ shop = find_or_initialize_by(shopify_domain: auth_session.domain)
15
+ shop.shopify_token = auth_session.token
16
+ shop.access_scopes = auth_session.access_scopes
17
+
18
+ shop.save!
19
+ shop.id
20
+ end
21
+
22
+ def retrieve(id)
23
+ shop = find_by(id: id)
24
+ construct_session(shop)
25
+ end
26
+
27
+ def retrieve_by_shopify_domain(domain)
28
+ shop = find_by(shopify_domain: domain)
29
+ construct_session(shop)
30
+ end
31
+
32
+ private
33
+
34
+ def construct_session(shop)
35
+ return unless shop
36
+
37
+ ShopifyAPI::Session.new(
38
+ domain: shop.shopify_domain,
39
+ token: shop.shopify_token,
40
+ api_version: shop.api_version,
41
+ access_scopes: shop.access_scopes
42
+ )
43
+ end
44
+ end
45
+
46
+ def access_scopes=(scopes)
47
+ super(scopes)
48
+ rescue NotImplementedError, NoMethodError
49
+ raise NotImplementedError, "#access_scopes= must be defined to handle storing access scopes: #{scopes}"
50
+ end
51
+
52
+ def access_scopes
53
+ super
54
+ rescue NotImplementedError, NoMethodError
55
+ raise NotImplementedError, "#access_scopes= must be defined to hook into stored access scopes"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ module UserSessionStorage
4
+ extend ActiveSupport::Concern
5
+ include ::ShopifyApp::SessionStorage
6
+
7
+ included do
8
+ validates :shopify_domain, presence: true
9
+ end
10
+
11
+ class_methods do
12
+ def store(auth_session, user)
13
+ user = find_or_initialize_by(shopify_user_id: user[:id])
14
+ user.shopify_token = auth_session.token
15
+ user.shopify_domain = auth_session.domain
16
+ user.save!
17
+ user.id
18
+ end
19
+
20
+ def retrieve(id)
21
+ user = find_by(id: id)
22
+ construct_session(user)
23
+ end
24
+
25
+ def retrieve_by_shopify_user_id(user_id)
26
+ user = find_by(shopify_user_id: user_id)
27
+ construct_session(user)
28
+ end
29
+
30
+ private
31
+
32
+ def construct_session(user)
33
+ return unless user
34
+ ShopifyAPI::Session.new(
35
+ domain: user.shopify_domain,
36
+ token: user.shopify_token,
37
+ api_version: user.api_version,
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ module UserSessionStorageWithScopes
4
+ extend ActiveSupport::Concern
5
+ include ::ShopifyApp::SessionStorage
6
+
7
+ included do
8
+ validates :shopify_domain, presence: true
9
+ end
10
+
11
+ class_methods do
12
+ def store(auth_session, user)
13
+ user = find_or_initialize_by(shopify_user_id: user[:id])
14
+ user.shopify_token = auth_session.token
15
+ user.shopify_domain = auth_session.domain
16
+ user.access_scopes = auth_session.access_scopes
17
+
18
+ user.save!
19
+ user.id
20
+ end
21
+
22
+ def retrieve(id)
23
+ user = find_by(id: id)
24
+ construct_session(user)
25
+ end
26
+
27
+ def retrieve_by_shopify_user_id(user_id)
28
+ user = find_by(shopify_user_id: user_id)
29
+ construct_session(user)
30
+ end
31
+
32
+ private
33
+
34
+ def construct_session(user)
35
+ return unless user
36
+
37
+ ShopifyAPI::Session.new(
38
+ domain: user.shopify_domain,
39
+ token: user.shopify_token,
40
+ api_version: user.api_version,
41
+ access_scopes: user.access_scopes
42
+ )
43
+ end
44
+ end
45
+
46
+ def access_scopes=(scopes)
47
+ super(scopes)
48
+ rescue NotImplementedError, NoMethodError
49
+ raise NotImplementedError, "#access_scopes= must be defined to handle storing access scopes: #{scopes}"
50
+ end
51
+
52
+ def access_scopes
53
+ super
54
+ rescue NotImplementedError, NoMethodError
55
+ raise NotImplementedError, "#access_scopes= must be defined to hook into stored access scopes"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ require 'shopify_app/test_helpers/webhook_verification_helper'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ module TestHelpers
4
+ module WebhookVerificationHelper
5
+ def authorized_webhook_verification_headers!(params = {})
6
+ digest = OpenSSL::Digest.new('sha256')
7
+ secret = ShopifyApp.configuration.secret
8
+ valid_hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, secret, params.to_query)).strip
9
+ @request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] = valid_hmac
10
+ end
11
+
12
+ def unauthorized_webhook_verification_headers!
13
+ @request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] = "invalid_hmac"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ module Utils
4
+ def self.sanitize_shop_domain(shop_domain)
5
+ myshopify_domain = ShopifyApp.configuration.myshopify_domain
6
+ name = shop_domain.to_s.downcase.strip
7
+ name += ".#{myshopify_domain}" if !name.include?(myshopify_domain.to_s) && !name.include?(".")
8
+ name.sub!(%r|https?://|, '')
9
+
10
+ u = URI("http://#{name}")
11
+ u.host if u.host&.match(/^[a-z0-9][a-z0-9\-]*[a-z0-9]\.#{Regexp.escape(myshopify_domain)}$/)
12
+ rescue URI::InvalidURIError
13
+ nil
14
+ end
15
+
16
+ def self.fetch_known_api_versions
17
+ Rails.logger.info("[ShopifyAPI::ApiVersion] Fetching known Admin API Versions from Shopify...")
18
+ ShopifyAPI::ApiVersion.fetch_known_versions
19
+ Rails.logger.info("[ShopifyAPI::ApiVersion] Known API Versions: #{ShopifyAPI::ApiVersion.versions.keys}")
20
+ rescue ActiveResource::ConnectionError
21
+ logger.error("[ShopifyAPI::ApiVersion] Unable to fetch api_versions from Shopify")
22
+ end
23
+
24
+ def self.shop_login_url(shop:, host:, return_to:)
25
+ return ShopifyApp.configuration.login_url unless shop
26
+ url = URI(ShopifyApp.configuration.login_url)
27
+
28
+ url.query = URI.encode_www_form(
29
+ shop: shop,
30
+ host: host,
31
+ return_to: return_to,
32
+ )
33
+
34
+ url.to_s
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ VERSION = '1.0.0'
4
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+ require 'shopify_app/version'
3
+
4
+ # deps
5
+ require 'ruby_shopify_api'
6
+ require 'omniauth/rails_csrf_protection'
7
+ require 'omniauth-shopify-oauth2'
8
+ require 'redirect_safely'
9
+
10
+ module ShopifyApp
11
+ def self.rails6?
12
+ Rails::VERSION::MAJOR >= 6
13
+ end
14
+
15
+ def self.rails7?
16
+ Rails::VERSION::MAJOR >= 7
17
+ end
18
+
19
+ def self.use_importmap?
20
+ rails7? && File.exist?("config/importmap.rb")
21
+ end
22
+
23
+ def self.use_webpacker?
24
+ rails6? &&
25
+ defined?(Webpacker) == 'constant' &&
26
+ !configuration.disable_webpacker
27
+ end
28
+
29
+ # config
30
+ require 'shopify_app/configuration'
31
+
32
+ # engine
33
+ require 'shopify_app/engine'
34
+
35
+ # utils
36
+ require 'shopify_app/utils'
37
+
38
+ # controller concerns
39
+ require 'shopify_app/controller_concerns/csrf_protection'
40
+ require 'shopify_app/controller_concerns/localization'
41
+ require 'shopify_app/controller_concerns/itp'
42
+ require 'shopify_app/controller_concerns/login_protection'
43
+ require 'shopify_app/controller_concerns/embedded_app'
44
+ require 'shopify_app/controller_concerns/payload_verification'
45
+ require 'shopify_app/controller_concerns/app_proxy_verification'
46
+ require 'shopify_app/controller_concerns/webhook_verification'
47
+
48
+ # jobs
49
+ require 'shopify_app/jobs/webhooks_manager_job'
50
+ require 'shopify_app/jobs/scripttags_manager_job'
51
+
52
+ # managers
53
+ require 'shopify_app/managers/webhooks_manager'
54
+ require 'shopify_app/managers/scripttags_manager'
55
+
56
+ # middleware
57
+ require 'shopify_app/middleware/jwt_middleware'
58
+ require 'shopify_app/middleware/same_site_cookie_middleware'
59
+
60
+ # session
61
+ require 'shopify_app/session/in_memory_session_store'
62
+ require 'shopify_app/session/in_memory_shop_session_store'
63
+ require 'shopify_app/session/in_memory_user_session_store'
64
+ require 'shopify_app/session/jwt'
65
+ require 'shopify_app/session/null_user_session_store'
66
+ require 'shopify_app/session/session_repository'
67
+ require 'shopify_app/session/session_storage'
68
+ require 'shopify_app/session/shop_session_storage'
69
+ require 'shopify_app/session/shop_session_storage_with_scopes'
70
+ require 'shopify_app/session/user_session_storage'
71
+ require 'shopify_app/session/user_session_storage_with_scopes'
72
+
73
+ # access scopes strategies
74
+ require 'shopify_app/access_scopes/shop_strategy'
75
+ require 'shopify_app/access_scopes/user_strategy'
76
+ require 'shopify_app/access_scopes/noop_strategy'
77
+
78
+ # omniauth_configuration
79
+ require 'shopify_app/omniauth/omniauth_configuration'
80
+ end
data/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "shopify_app",
3
+ "version": "18.1.2",
4
+ "repository": "git@github.com:Shopify/shopify_app.git",
5
+ "author": "Shopify",
6
+ "license": "MIT",
7
+ "dependencies": {},
8
+ "devDependencies": {
9
+ "babel-loader": "^8.0.6",
10
+ "babel-preset-shopify": "^21.0.0",
11
+ "chai": "^4.1.2",
12
+ "karma": "^5.2.1",
13
+ "karma-chai-sinon": "^0.1.5",
14
+ "karma-chrome-launcher": "^3.1.0",
15
+ "karma-cli": "^2.0.0",
16
+ "karma-mocha": "^2.0.1",
17
+ "karma-mocha-clean-reporter": "^0.0.1",
18
+ "karma-webpack": "^4.0.2",
19
+ "mocha": "^8.1.3",
20
+ "sinon": "^9.0.3",
21
+ "sinon-chai": "^3.2.0",
22
+ "webpack": "^4.44.1"
23
+ },
24
+ "scripts": {
25
+ "test": "./node_modules/.bin/karma start --browsers ChromeHeadless --single-run"
26
+ }
27
+ }
data/service.yml ADDED
@@ -0,0 +1,4 @@
1
+ audience: partner
2
+ classification: library
3
+ slack_channels:
4
+ - shopify_app_gem
@@ -0,0 +1,4 @@
1
+ # using the default shipit config
2
+ ci:
3
+ hide:
4
+ - code-review/policial
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ $LOAD_PATH.push(File.expand_path('../lib', __FILE__))
3
+ require "shopify_app/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "ruby_shopify_app"
7
+ s.version = ShopifyApp::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Hopper Gee"]
10
+ s.email = ["hopper.gee@hey.com"]
11
+ s.summary = 'This gem is used to get quickly started with the Shopify API'
12
+
13
+ s.required_ruby_version = ">= 2.6"
14
+
15
+ s.metadata['allowed_push_host'] = 'https://rubygems.org'
16
+
17
+ s.add_runtime_dependency('browser_sniffer', '~> 1.4.0')
18
+ s.add_runtime_dependency('omniauth-rails_csrf_protection')
19
+ s.add_runtime_dependency('rails', '> 5.2.1')
20
+ s.add_runtime_dependency('ruby_shopify_api', '~> 1.0')
21
+ s.add_runtime_dependency('omniauth-shopify-oauth2', '~> 2.3')
22
+ s.add_runtime_dependency('jwt', '>= 2.2.3')
23
+ s.add_runtime_dependency('redirect_safely', '~> 1.0')
24
+
25
+ s.add_development_dependency('rake')
26
+ s.add_development_dependency('byebug')
27
+ s.add_development_dependency('pry')
28
+ s.add_development_dependency('pry-nav')
29
+ s.add_development_dependency('pry-stack_explorer')
30
+ s.add_development_dependency('rb-readline')
31
+ s.add_development_dependency('sqlite3', '~> 1.4')
32
+ s.add_development_dependency('minitest')
33
+ s.add_development_dependency('mocha')
34
+ s.add_development_dependency('webmock')
35
+
36
+ s.files = %x(git ls-files).split("\n").reject { |f| f.match(%r{^(test|example)/}) }
37
+ s.test_files = %x(git ls-files -- {test}/*).split("\n")
38
+ s.require_paths = ["lib"]
39
+ end
data/translation.yml ADDED
@@ -0,0 +1,7 @@
1
+ source_language: en
2
+ target_languages: [cs, da, de, es, fi, fr, it, ja, ko, nb, nl, pl, pt-BR, pt-PT, sv, th, tr, vi, zh-CN, zh-TW]
3
+ components:
4
+ - name: 'merchant'
5
+ paths:
6
+ - config/locales/{{language}}.yml
7
+ - config/locales/**/{{language}}.yml
data/webpack.config.js ADDED
@@ -0,0 +1,24 @@
1
+
2
+ var path = require('path');
3
+ var webpack = require('webpack');
4
+
5
+ module.exports = {
6
+ mode: 'development',
7
+ entry: 'test/javascripts/test.js',
8
+ output: {
9
+ path: path.resolve(__dirname, 'build'),
10
+ filename: 'test.bundle.js'
11
+ },
12
+ module: {
13
+ loaders: [
14
+ {
15
+ test: /\.js$/,
16
+ loader: 'babel-loader',
17
+ }
18
+ ]
19
+ },
20
+ stats: {
21
+ colors: true
22
+ },
23
+ devtool: 'source-map'
24
+ };