shopify_app 13.5.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 (156) hide show
  1. checksums.yaml +7 -0
  2. data/.babelrc +5 -0
  3. data/.github/CODEOWNERS +1 -0
  4. data/.github/ISSUE_TEMPLATE.md +14 -0
  5. data/.github/PULL_REQUEST_TEMPLATE.md +6 -0
  6. data/.github/probots.yml +2 -0
  7. data/.github/workflows/rubocop.yml +28 -0
  8. data/.gitignore +16 -0
  9. data/.nvmrc +1 -0
  10. data/.rubocop.yml +17 -0
  11. data/.ruby-version +1 -0
  12. data/.travis.yml +28 -0
  13. data/CHANGELOG.md +505 -0
  14. data/Gemfile +11 -0
  15. data/LICENSE +19 -0
  16. data/README.md +620 -0
  17. data/Rakefile +7 -0
  18. data/SECURITY.md +59 -0
  19. data/app/assets/images/storage_access.svg +2 -0
  20. data/app/assets/javascripts/shopify_app/enable_cookies.js +3 -0
  21. data/app/assets/javascripts/shopify_app/itp_helper.js +40 -0
  22. data/app/assets/javascripts/shopify_app/partition_cookies.js +8 -0
  23. data/app/assets/javascripts/shopify_app/redirect.js +33 -0
  24. data/app/assets/javascripts/shopify_app/request_storage_access.js +3 -0
  25. data/app/assets/javascripts/shopify_app/storage_access.js +153 -0
  26. data/app/assets/javascripts/shopify_app/storage_access_redirect.js +17 -0
  27. data/app/assets/javascripts/shopify_app/top_level.js +2 -0
  28. data/app/assets/javascripts/shopify_app/top_level_interaction.js +11 -0
  29. data/app/controllers/concerns/shopify_app/authenticated.rb +16 -0
  30. data/app/controllers/concerns/shopify_app/require_known_shop.rb +39 -0
  31. data/app/controllers/shopify_app/authenticated_controller.rb +8 -0
  32. data/app/controllers/shopify_app/callback_controller.rb +140 -0
  33. data/app/controllers/shopify_app/extension_verification_controller.rb +15 -0
  34. data/app/controllers/shopify_app/sessions_controller.rb +184 -0
  35. data/app/controllers/shopify_app/webhooks_controller.rb +37 -0
  36. data/app/views/shopify_app/partials/_button_styles.html.erb +104 -0
  37. data/app/views/shopify_app/partials/_card_styles.html.erb +33 -0
  38. data/app/views/shopify_app/partials/_empty_state_styles.html.erb +129 -0
  39. data/app/views/shopify_app/partials/_layout_styles.html.erb +167 -0
  40. data/app/views/shopify_app/partials/_typography_styles.html.erb +35 -0
  41. data/app/views/shopify_app/sessions/enable_cookies.html.erb +75 -0
  42. data/app/views/shopify_app/sessions/new.html.erb +123 -0
  43. data/app/views/shopify_app/sessions/request_storage_access.html.erb +68 -0
  44. data/app/views/shopify_app/sessions/top_level_interaction.html.erb +64 -0
  45. data/app/views/shopify_app/shared/redirect.html.erb +23 -0
  46. data/config/locales/cs.yml +23 -0
  47. data/config/locales/da.yml +20 -0
  48. data/config/locales/de.yml +22 -0
  49. data/config/locales/en.yml +15 -0
  50. data/config/locales/es.yml +22 -0
  51. data/config/locales/fi.yml +20 -0
  52. data/config/locales/fr.yml +23 -0
  53. data/config/locales/hi.yml +23 -0
  54. data/config/locales/it.yml +21 -0
  55. data/config/locales/ja.yml +17 -0
  56. data/config/locales/ko.yml +19 -0
  57. data/config/locales/ms.yml +22 -0
  58. data/config/locales/nb.yml +21 -0
  59. data/config/locales/nl.yml +21 -0
  60. data/config/locales/pl.yml +21 -0
  61. data/config/locales/pt-BR.yml +21 -0
  62. data/config/locales/pt-PT.yml +22 -0
  63. data/config/locales/sv.yml +21 -0
  64. data/config/locales/th.yml +20 -0
  65. data/config/locales/tr.yml +22 -0
  66. data/config/locales/zh-CN.yml +16 -0
  67. data/config/locales/zh-TW.yml +16 -0
  68. data/config/routes.rb +23 -0
  69. data/docs/Quickstart.md +93 -0
  70. data/docs/Releasing.md +18 -0
  71. data/docs/Troubleshooting.md +16 -0
  72. data/docs/install-on-dev-shop.png +0 -0
  73. data/docs/test-your-app.png +0 -0
  74. data/images/app-proxy-screenshot.png +0 -0
  75. data/karma.conf.js +44 -0
  76. data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +47 -0
  77. data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +11 -0
  78. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +40 -0
  79. data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +62 -0
  80. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +69 -0
  81. data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +13 -0
  82. data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +26 -0
  83. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb +8 -0
  84. data/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_route.rb +11 -0
  85. data/lib/generators/shopify_app/app_proxy_controller/templates/index.html.erb +19 -0
  86. data/lib/generators/shopify_app/authenticated_controller/authenticated_controller_generator.rb +15 -0
  87. data/lib/generators/shopify_app/authenticated_controller/templates/authenticated_controller.rb +5 -0
  88. data/lib/generators/shopify_app/controllers/controllers_generator.rb +30 -0
  89. data/lib/generators/shopify_app/home_controller/home_controller_generator.rb +26 -0
  90. data/lib/generators/shopify_app/home_controller/templates/home_controller.rb +8 -0
  91. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +21 -0
  92. data/lib/generators/shopify_app/install/install_generator.rb +83 -0
  93. data/lib/generators/shopify_app/install/templates/_flash_messages.html.erb +3 -0
  94. data/lib/generators/shopify_app/install/templates/embedded_app.html.erb +41 -0
  95. data/lib/generators/shopify_app/install/templates/flash_messages.js +24 -0
  96. data/lib/generators/shopify_app/install/templates/omniauth.rb +3 -0
  97. data/lib/generators/shopify_app/install/templates/session_store.rb +4 -0
  98. data/lib/generators/shopify_app/install/templates/shopify_app.js +15 -0
  99. data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +15 -0
  100. data/lib/generators/shopify_app/install/templates/shopify_app_index.js +2 -0
  101. data/lib/generators/shopify_app/install/templates/shopify_provider.rb +20 -0
  102. data/lib/generators/shopify_app/install/templates/user_agent.rb +6 -0
  103. data/lib/generators/shopify_app/rotate_shopify_token_job/rotate_shopify_token_job_generator.rb +16 -0
  104. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +17 -0
  105. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +42 -0
  106. data/lib/generators/shopify_app/routes/routes_generator.rb +32 -0
  107. data/lib/generators/shopify_app/routes/templates/routes.rb +12 -0
  108. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +43 -0
  109. data/lib/generators/shopify_app/shop_model/templates/db/migrate/create_shops.erb +15 -0
  110. data/lib/generators/shopify_app/shop_model/templates/shop.rb +8 -0
  111. data/lib/generators/shopify_app/shop_model/templates/shops.yml +3 -0
  112. data/lib/generators/shopify_app/shopify_app_generator.rb +18 -0
  113. data/lib/generators/shopify_app/user_model/templates/db/migrate/create_users.erb +16 -0
  114. data/lib/generators/shopify_app/user_model/templates/user.rb +8 -0
  115. data/lib/generators/shopify_app/user_model/templates/users.yml +4 -0
  116. data/lib/generators/shopify_app/user_model/user_model_generator.rb +43 -0
  117. data/lib/generators/shopify_app/views/views_generator.rb +30 -0
  118. data/lib/shopify_app.rb +61 -0
  119. data/lib/shopify_app/configuration.rb +94 -0
  120. data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +38 -0
  121. data/lib/shopify_app/controller_concerns/csrf_protection.rb +15 -0
  122. data/lib/shopify_app/controller_concerns/embedded_app.rb +20 -0
  123. data/lib/shopify_app/controller_concerns/itp.rb +45 -0
  124. data/lib/shopify_app/controller_concerns/localization.rb +23 -0
  125. data/lib/shopify_app/controller_concerns/login_protection.rb +231 -0
  126. data/lib/shopify_app/controller_concerns/payload_verification.rb +24 -0
  127. data/lib/shopify_app/controller_concerns/webhook_verification.rb +23 -0
  128. data/lib/shopify_app/engine.rb +25 -0
  129. data/lib/shopify_app/jobs/scripttags_manager_job.rb +16 -0
  130. data/lib/shopify_app/jobs/webhooks_manager_job.rb +16 -0
  131. data/lib/shopify_app/managers/scripttags_manager.rb +78 -0
  132. data/lib/shopify_app/managers/webhooks_manager.rb +62 -0
  133. data/lib/shopify_app/middleware/jwt_middleware.rb +42 -0
  134. data/lib/shopify_app/middleware/same_site_cookie_middleware.rb +34 -0
  135. data/lib/shopify_app/session/in_memory_session_store.rb +31 -0
  136. data/lib/shopify_app/session/in_memory_shop_session_store.rb +14 -0
  137. data/lib/shopify_app/session/in_memory_user_session_store.rb +14 -0
  138. data/lib/shopify_app/session/jwt.rb +61 -0
  139. data/lib/shopify_app/session/null_user_session_store.rb +22 -0
  140. data/lib/shopify_app/session/session_repository.rb +56 -0
  141. data/lib/shopify_app/session/session_storage.rb +20 -0
  142. data/lib/shopify_app/session/shop_session_storage.rb +42 -0
  143. data/lib/shopify_app/session/user_session_storage.rb +42 -0
  144. data/lib/shopify_app/test_helpers/all.rb +2 -0
  145. data/lib/shopify_app/test_helpers/webhook_verification_helper.rb +17 -0
  146. data/lib/shopify_app/utils.rb +24 -0
  147. data/lib/shopify_app/version.rb +4 -0
  148. data/package-lock.json +7177 -0
  149. data/package.json +28 -0
  150. data/service.yml +7 -0
  151. data/shipit.rubygems.yml +4 -0
  152. data/shopify_app.gemspec +37 -0
  153. data/translation.yml +7 -0
  154. data/webpack.config.js +24 -0
  155. data/yarn.lock +5263 -0
  156. metadata +420 -0
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class ScripttagsManagerJob < ActiveJob::Base
4
+ queue_as do
5
+ ShopifyApp.configuration.scripttags_manager_queue_name
6
+ end
7
+
8
+ 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
11
+ manager = ScripttagsManager.new(scripttags, shop_domain)
12
+ manager.create_scripttags
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class WebhooksManagerJob < ActiveJob::Base
4
+ queue_as do
5
+ ShopifyApp.configuration.webhooks_manager_queue_name
6
+ end
7
+
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
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class ScripttagsManager
4
+ class CreationFailed < StandardError; end
5
+
6
+ def self.queue(shop_domain, shop_token, scripttags)
7
+ ShopifyApp::ScripttagsManagerJob.perform_later(
8
+ shop_domain: shop_domain,
9
+ shop_token: shop_token,
10
+ # Procs cannot be serialized so we interpolate now, if necessary
11
+ scripttags: build_src(scripttags, shop_domain)
12
+ )
13
+ end
14
+
15
+ def self.build_src(scripttags, domain)
16
+ scripttags.map do |tag|
17
+ next tag unless tag[:src].respond_to?(:call)
18
+ tag = tag.dup
19
+ tag[:src] = tag[:src].call(domain)
20
+ tag
21
+ end
22
+ end
23
+
24
+ attr_reader :required_scripttags, :shop_domain
25
+
26
+ def initialize(scripttags, shop_domain)
27
+ @required_scripttags = scripttags
28
+ @shop_domain = shop_domain
29
+ end
30
+
31
+ def recreate_scripttags!
32
+ destroy_scripttags
33
+ create_scripttags
34
+ end
35
+
36
+ def create_scripttags
37
+ return unless required_scripttags.present?
38
+
39
+ expanded_scripttags.each do |scripttag|
40
+ create_scripttag(scripttag) unless scripttag_exists?(scripttag[:src])
41
+ end
42
+ end
43
+
44
+ def destroy_scripttags
45
+ scripttags = expanded_scripttags
46
+ ShopifyAPI::ScriptTag.all.each do |tag|
47
+ ShopifyAPI::ScriptTag.delete(tag.id) if required_scripttag?(scripttags, tag)
48
+ end
49
+
50
+ @current_scripttags = nil
51
+ end
52
+
53
+ private
54
+
55
+ def expanded_scripttags
56
+ self.class.build_src(required_scripttags, shop_domain)
57
+ end
58
+
59
+ def required_scripttag?(scripttags, tag)
60
+ scripttags.map { |w| w[:src] }.include?(tag.src)
61
+ end
62
+
63
+ 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?
67
+ scripttag
68
+ end
69
+
70
+ def scripttag_exists?(src)
71
+ current_scripttags[src]
72
+ end
73
+
74
+ def current_scripttags
75
+ @current_scripttags ||= ShopifyAPI::ScriptTag.all.index_by(&:src)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class WebhooksManager
4
+ class CreationFailed < StandardError; end
5
+
6
+ def self.queue(shop_domain, shop_token, webhooks)
7
+ ShopifyApp::WebhooksManagerJob.perform_later(
8
+ shop_domain: shop_domain,
9
+ shop_token: shop_token,
10
+ webhooks: webhooks
11
+ )
12
+ end
13
+
14
+ attr_reader :required_webhooks
15
+
16
+ def initialize(webhooks)
17
+ @required_webhooks = webhooks
18
+ end
19
+
20
+ def recreate_webhooks!
21
+ destroy_webhooks
22
+ create_webhooks
23
+ end
24
+
25
+ def create_webhooks
26
+ return unless required_webhooks.present?
27
+
28
+ required_webhooks.each do |webhook|
29
+ create_webhook(webhook) unless webhook_exists?(webhook[:topic])
30
+ end
31
+ end
32
+
33
+ def destroy_webhooks
34
+ ShopifyAPI::Webhook.all.to_a.each do |webhook|
35
+ ShopifyAPI::Webhook.delete(webhook.id) if required_webhook?(webhook)
36
+ end
37
+
38
+ @current_webhooks = nil
39
+ end
40
+
41
+ private
42
+
43
+ def required_webhook?(webhook)
44
+ required_webhooks.map { |w| w[:address] }.include?(webhook.address)
45
+ end
46
+
47
+ def create_webhook(attributes)
48
+ attributes.reverse_merge!(format: 'json')
49
+ webhook = ShopifyAPI::Webhook.create(attributes)
50
+ raise CreationFailed, webhook.errors.full_messages.to_sentence unless webhook.persisted?
51
+ webhook
52
+ end
53
+
54
+ def webhook_exists?(topic)
55
+ current_webhooks[topic]
56
+ end
57
+
58
+ def current_webhooks
59
+ @current_webhooks ||= ShopifyAPI::Webhook.all.to_a.index_by(&:topic)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class JWTMiddleware
4
+ TOKEN_REGEX = /^Bearer\s+(.*?)$/
5
+
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ return call_next(env) unless authorization_header(env)
12
+
13
+ token = extract_token(env)
14
+ return call_next(env) unless token
15
+
16
+ set_env_variables(token, env)
17
+ call_next(env)
18
+ end
19
+
20
+ private
21
+
22
+ def call_next(env)
23
+ @app.call(env)
24
+ end
25
+
26
+ def authorization_header(env)
27
+ env['HTTP_AUTHORIZATION']
28
+ end
29
+
30
+ def extract_token(env)
31
+ match = authorization_header(env).match(TOKEN_REGEX)
32
+ match && match[1]
33
+ end
34
+
35
+ def set_env_variables(token, env)
36
+ jwt = ShopifyApp::JWT.new(token)
37
+
38
+ env['jwt.shopify_domain'] = jwt.shopify_domain
39
+ env['jwt.shopify_user_id'] = jwt.shopify_user_id
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class SameSiteCookieMiddleware
4
+ COOKIE_SEPARATOR = "\n"
5
+
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ status, headers, body = @app.call(env)
12
+ user_agent = env['HTTP_USER_AGENT']
13
+
14
+ if headers && headers['Set-Cookie'] &&
15
+ BrowserSniffer.new(user_agent).same_site_none_compatible? &&
16
+ ShopifyApp.configuration.enable_same_site_none &&
17
+ Rack::Request.new(env).ssl?
18
+
19
+ set_cookies = headers['Set-Cookie']
20
+ .split(COOKIE_SEPARATOR)
21
+ .compact
22
+ .map do |cookie|
23
+ cookie << '; Secure' unless cookie =~ /;\s*secure/i
24
+ cookie << '; SameSite=None' unless cookie =~ /;\s*samesite=/i
25
+ cookie
26
+ end
27
+
28
+ headers['Set-Cookie'] = set_cookies.join(COOKIE_SEPARATOR)
29
+ end
30
+
31
+ [status, headers, body]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ # rubocop:disable Style/ClassVars
4
+ # Class var repo is needed here in order to share data between the 2 child classes.
5
+ class InMemorySessionStore
6
+ class EnvironmentError < StandardError; end
7
+
8
+ def self.retrieve(id)
9
+ repo[id]
10
+ end
11
+
12
+ def self.store(session, *_args)
13
+ id = SecureRandom.uuid
14
+ repo[id] = session
15
+ id
16
+ end
17
+
18
+ def self.clear
19
+ @@repo = nil
20
+ end
21
+
22
+ def self.repo
23
+ if Rails.env.production?
24
+ raise EnvironmentError, "Cannot use InMemorySessionStore in a Production environment. \
25
+ Please initialize ShopifyApp with a model that can store and retrieve sessions"
26
+ end
27
+ @@repo ||= {}
28
+ end
29
+ end
30
+ # rubocop:enable Style/ClassVars
31
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class InMemoryShopSessionStore < InMemorySessionStore
4
+ def self.store(session, *args)
5
+ id = super
6
+ repo[session.domain] = session
7
+ id
8
+ end
9
+
10
+ def self.retrieve_by_shopify_domain(shopify_domain)
11
+ repo[shopify_domain]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class InMemoryUserSessionStore < InMemorySessionStore
4
+ def self.store(session, user)
5
+ id = super
6
+ repo[user.shopify_user_id] = session
7
+ id
8
+ end
9
+
10
+ def self.retrieve_by_shopify_user_id(user_id)
11
+ repo[user_id]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ module ShopifyApp
3
+ class JWT
4
+ class InvalidDestinationError < StandardError; end
5
+ class MismatchedHostsError < StandardError; end
6
+ class InvalidAudienceError < StandardError; end
7
+
8
+ WARN_EXCEPTIONS = [
9
+ ::JWT::DecodeError,
10
+ ::JWT::ExpiredSignature,
11
+ ::JWT::ImmatureSignature,
12
+ ::JWT::VerificationError,
13
+ InvalidAudienceError,
14
+ InvalidDestinationError,
15
+ MismatchedHostsError,
16
+ ]
17
+
18
+ def initialize(token)
19
+ @token = token
20
+ set_payload
21
+ end
22
+
23
+ def shopify_domain
24
+ @payload && ShopifyApp::Utils.sanitize_shop_domain(@payload['dest'])
25
+ end
26
+
27
+ def shopify_user_id
28
+ @payload && @payload['sub']
29
+ end
30
+
31
+ private
32
+
33
+ def set_payload
34
+ payload, _ = parse_token_data(ShopifyApp.configuration&.secret, ShopifyApp.configuration&.old_secret)
35
+ @payload = validate_payload(payload)
36
+ rescue *WARN_EXCEPTIONS => error
37
+ Rails.logger.warn("[ShopifyApp::JWT] Failed to validate JWT: [#{error.class}] #{error}")
38
+ nil
39
+ end
40
+
41
+ def parse_token_data(secret, old_secret)
42
+ ::JWT.decode(@token, secret, true, { algorithm: 'HS256' })
43
+ rescue ::JWT::VerificationError
44
+ raise unless old_secret
45
+
46
+ ::JWT.decode(@token, old_secret, true, { algorithm: 'HS256' })
47
+ end
48
+
49
+ def validate_payload(payload)
50
+ dest_host = ShopifyApp::Utils.sanitize_shop_domain(payload['dest'])
51
+ iss_host = ShopifyApp::Utils.sanitize_shop_domain(payload['iss'])
52
+ api_key = ShopifyApp.configuration.api_key
53
+
54
+ raise InvalidAudienceError, "'aud' claim does not match api_key" unless payload['aud'] == api_key
55
+ raise InvalidDestinationError, "'dest' claim host not a valid shopify host" unless dest_host
56
+ raise MismatchedHostsError, "'dest' claim host does not match 'iss' claim host" unless dest_host == iss_host
57
+
58
+ payload
59
+ end
60
+ end
61
+ 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