shopify_app 21.0.0 → 22.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/ISSUE_TEMPLATE/ENHANCEMENT.md +9 -0
  4. data/.github/ISSUE_TEMPLATE/bug-report.md +30 -47
  5. data/.github/ISSUE_TEMPLATE/feature-request.md +5 -29
  6. data/.github/workflows/build.yml +11 -12
  7. data/.github/workflows/release.yml +2 -2
  8. data/.github/workflows/remove-labels-on-activity.yml +1 -1
  9. data/.github/workflows/rubocop.yml +2 -3
  10. data/.nvmrc +1 -1
  11. data/.rubocop.yml +2 -1
  12. data/.ruby-version +1 -1
  13. data/.spin/rails/prepare-application +8 -0
  14. data/CHANGELOG.md +173 -7
  15. data/CODE_OF_CONDUCT.md +46 -0
  16. data/CONTRIBUTING.md +16 -6
  17. data/Gemfile +1 -0
  18. data/Gemfile.lock +160 -121
  19. data/README.md +67 -19
  20. data/SECURITY.md +1 -1
  21. data/app/assets/javascripts/shopify_app/redirect.js +3 -10
  22. data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +9 -4
  23. data/app/controllers/concerns/shopify_app/ensure_has_session.rb +25 -0
  24. data/app/controllers/concerns/shopify_app/ensure_installed.rb +84 -0
  25. data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +5 -1
  26. data/app/controllers/shopify_app/authenticated_controller.rb +1 -1
  27. data/app/controllers/shopify_app/callback_controller.rb +101 -39
  28. data/app/controllers/shopify_app/extension_verification_controller.rb +4 -1
  29. data/app/controllers/shopify_app/sessions_controller.rb +37 -7
  30. data/app/controllers/shopify_app/webhooks_controller.rb +1 -1
  31. data/app/views/shopify_app/layouts/app_bridge.html.erb +17 -0
  32. data/app/views/shopify_app/sessions/patch_shopify_id_token.html.erb +0 -0
  33. data/app/views/shopify_app/shared/redirect.html.erb +10 -1
  34. data/config/locales/cs.yml +0 -18
  35. data/config/locales/da.yml +0 -15
  36. data/config/locales/de.yml +0 -17
  37. data/config/locales/en.yml +0 -11
  38. data/config/locales/es.yml +0 -17
  39. data/config/locales/fi.yml +0 -15
  40. data/config/locales/fr.yml +0 -18
  41. data/config/locales/it.yml +0 -16
  42. data/config/locales/ja.yml +0 -12
  43. data/config/locales/ko.yml +0 -14
  44. data/config/locales/nb.yml +0 -16
  45. data/config/locales/nl.yml +0 -16
  46. data/config/locales/pl.yml +0 -16
  47. data/config/locales/pt-BR.yml +0 -16
  48. data/config/locales/pt-PT.yml +0 -17
  49. data/config/locales/sv.yml +0 -16
  50. data/config/locales/th.yml +0 -15
  51. data/config/locales/tr.yml +0 -17
  52. data/config/locales/vi.yml +0 -17
  53. data/config/locales/zh-CN.yml +0 -11
  54. data/config/locales/zh-TW.yml +0 -11
  55. data/config/routes.rb +2 -1
  56. data/docs/Quickstart.md +14 -5
  57. data/docs/Troubleshooting.md +38 -25
  58. data/docs/Upgrading.md +103 -32
  59. data/docs/shopify_app/authentication.md +179 -58
  60. data/docs/shopify_app/controller-concerns.md +89 -0
  61. data/docs/shopify_app/engine.md +2 -11
  62. data/docs/shopify_app/generators.md +2 -2
  63. data/docs/shopify_app/logging.md +21 -0
  64. data/docs/shopify_app/sessions.md +358 -0
  65. data/docs/shopify_app/testing.md +32 -10
  66. data/docs/shopify_app/webhooks.md +97 -7
  67. data/karma.conf.js +6 -4
  68. data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +6 -3
  69. data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +1 -1
  70. data/lib/generators/shopify_app/add_app_uninstalled_job/add_app_uninstalled_job_generator.rb +15 -0
  71. data/lib/generators/shopify_app/add_app_uninstalled_job/templates/app_uninstalled_job.rb.tt +22 -0
  72. data/lib/generators/shopify_app/add_declarative_webhook/add_declarative_webhook_generator.rb +53 -0
  73. data/lib/generators/shopify_app/add_declarative_webhook/templates/webhook_controller.rb.tt +13 -0
  74. data/lib/generators/shopify_app/add_declarative_webhook/templates/webhook_job.rb.tt +15 -0
  75. data/lib/generators/shopify_app/add_privacy_jobs/add_privacy_jobs_generator.rb +23 -0
  76. data/lib/generators/shopify_app/add_privacy_jobs/templates/customers_data_request_job.rb.tt +22 -0
  77. data/lib/generators/shopify_app/add_privacy_jobs/templates/customers_redact_job.rb.tt +22 -0
  78. data/lib/generators/shopify_app/add_privacy_jobs/templates/shop_redact_job.rb.tt +22 -0
  79. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +8 -3
  80. data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +4 -2
  81. data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +1 -1
  82. data/lib/generators/shopify_app/authenticated_controller/templates/authenticated_controller.rb +1 -1
  83. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +1 -1
  84. data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +1 -1
  85. data/lib/generators/shopify_app/install/install_generator.rb +4 -4
  86. data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +13 -3
  87. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +1 -1
  88. data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +1 -1
  89. data/lib/generators/shopify_app/routes/routes_generator.rb +1 -1
  90. data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +1 -1
  91. data/lib/generators/shopify_app/shop_model/templates/db/migrate/add_shop_access_scopes_column.erb +1 -1
  92. data/lib/generators/shopify_app/shopify_app_generator.rb +2 -0
  93. data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_access_scopes_column.erb +1 -1
  94. data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_expires_at_column.erb +5 -0
  95. data/lib/generators/shopify_app/user_model/user_model_generator.rb +21 -1
  96. data/lib/shopify_app/access_scopes/noop_strategy.rb +4 -0
  97. data/lib/shopify_app/access_scopes/user_strategy.rb +9 -2
  98. data/lib/shopify_app/admin_api/with_token_refetch.rb +27 -0
  99. data/lib/shopify_app/auth/post_authenticate_tasks.rb +48 -0
  100. data/lib/shopify_app/auth/token_exchange.rb +73 -0
  101. data/lib/shopify_app/configuration.rb +82 -1
  102. data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +3 -3
  103. data/lib/shopify_app/controller_concerns/csrf_protection.rb +2 -1
  104. data/lib/shopify_app/controller_concerns/embedded_app.rb +42 -3
  105. data/lib/shopify_app/controller_concerns/ensure_billing.rb +28 -12
  106. data/lib/shopify_app/controller_concerns/frame_ancestors.rb +1 -1
  107. data/lib/shopify_app/controller_concerns/localization.rb +11 -8
  108. data/lib/shopify_app/controller_concerns/login_protection.rb +83 -38
  109. data/lib/shopify_app/controller_concerns/payload_verification.rb +1 -1
  110. data/lib/shopify_app/controller_concerns/redirect_for_embedded.rb +15 -3
  111. data/lib/shopify_app/controller_concerns/sanitized_params.rb +5 -0
  112. data/lib/shopify_app/controller_concerns/token_exchange.rb +111 -0
  113. data/lib/shopify_app/controller_concerns/webhook_verification.rb +4 -1
  114. data/lib/shopify_app/controller_concerns/with_shopify_id_token.rb +48 -0
  115. data/lib/shopify_app/engine.rb +7 -8
  116. data/lib/shopify_app/logger.rb +28 -0
  117. data/lib/shopify_app/managers/webhooks_manager.rb +20 -10
  118. data/lib/shopify_app/middleware/jwt_middleware.rb +13 -9
  119. data/lib/shopify_app/session/in_memory_user_session_store.rb +1 -1
  120. data/lib/shopify_app/session/jwt.rb +11 -2
  121. data/lib/shopify_app/session/session_repository.rb +66 -14
  122. data/lib/shopify_app/session/session_storage.rb +2 -2
  123. data/lib/shopify_app/session/shop_session_storage.rb +5 -1
  124. data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +5 -1
  125. data/lib/shopify_app/session/user_session_storage.rb +6 -2
  126. data/lib/shopify_app/session/user_session_storage_with_scopes.rb +27 -2
  127. data/lib/shopify_app/test_helpers/all.rb +1 -0
  128. data/lib/shopify_app/test_helpers/shopify_session_helper.rb +16 -0
  129. data/lib/shopify_app/utils.rb +82 -20
  130. data/lib/shopify_app/version.rb +1 -1
  131. data/lib/shopify_app.rb +12 -3
  132. data/package.json +5 -6
  133. data/service.yml +0 -2
  134. data/shopify_app.gemspec +6 -5
  135. data/translation.yml +1 -0
  136. data/yarn.lock +2139 -3910
  137. metadata +78 -58
  138. data/.github/workflows/stale.yml +0 -31
  139. data/app/assets/images/storage_access.svg +0 -1
  140. data/app/assets/javascripts/shopify_app/app_bridge_3.1.1.js +0 -10
  141. data/app/assets/javascripts/shopify_app/app_bridge_redirect.js +0 -22
  142. data/app/assets/javascripts/shopify_app/app_bridge_utils_3.1.1.js +0 -1
  143. data/app/assets/javascripts/shopify_app/enable_cookies.js +0 -3
  144. data/app/assets/javascripts/shopify_app/itp_helper.js +0 -40
  145. data/app/assets/javascripts/shopify_app/partition_cookies.js +0 -8
  146. data/app/assets/javascripts/shopify_app/post_redirect.js +0 -9
  147. data/app/assets/javascripts/shopify_app/request_storage_access.js +0 -3
  148. data/app/assets/javascripts/shopify_app/storage_access.js +0 -148
  149. data/app/assets/javascripts/shopify_app/storage_access_redirect.js +0 -17
  150. data/app/assets/javascripts/shopify_app/top_level.js +0 -2
  151. data/app/assets/javascripts/shopify_app/top_level_interaction.js +0 -11
  152. data/app/controllers/concerns/shopify_app/authenticated.rb +0 -19
  153. data/app/controllers/concerns/shopify_app/require_known_shop.rb +0 -48
  154. data/app/views/shopify_app/sessions/enable_cookies.html.erb +0 -70
  155. data/app/views/shopify_app/sessions/request_storage_access.html.erb +0 -68
  156. data/app/views/shopify_app/sessions/top_level_interaction.html.erb +0 -63
  157. data/app/views/shopify_app/shared/post_redirect_to_auth_shopify.html.erb +0 -13
  158. data/docs/shopify_app/script-tags.md +0 -28
  159. data/docs/shopify_app/session-repository.md +0 -88
  160. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +0 -41
  161. data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +0 -62
  162. data/lib/shopify_app/controller_concerns/itp.rb +0 -45
  163. data/lib/shopify_app/jobs/scripttags_manager_job.rb +0 -16
  164. data/lib/shopify_app/managers/scripttags_manager.rb +0 -84
@@ -1,148 +0,0 @@
1
- //= require ./app_bridge_redirect.js
2
-
3
- (function() {
4
- var ACCESS_GRANTED_STATUS = 'storage_access_granted';
5
- var ACCESS_DENIED_STATUS = 'storage_access_denied';
6
-
7
- function StorageAccessHelper(redirectData) {
8
- this.redirectData = redirectData;
9
- }
10
-
11
- StorageAccessHelper.prototype.setNormalizedLink = function(storageAccessStatus) {
12
- return storageAccessStatus === ACCESS_GRANTED_STATUS ? this.redirectData.hasStorageAccessUrl : this.redirectData.doesNotHaveStorageAccessUrl;
13
- }
14
-
15
- StorageAccessHelper.prototype.redirectToAppTLD = function(storageAccessStatus) {
16
- var normalizedLink = document.createElement('a');
17
-
18
- window.appBridgeRedirect(this.setNormalizedLink(storageAccessStatus));
19
- }
20
-
21
- StorageAccessHelper.prototype.redirectToAppsIndex = function() {
22
- window.parent.location.href = this.redirectData.myshopifyUrl + '/admin/apps';
23
- }
24
-
25
- StorageAccessHelper.prototype.redirectToAppTargetUrl = function() {
26
- window.location.href = this.redirectData.appTargetUrl;
27
- }
28
-
29
- StorageAccessHelper.prototype.sameSiteNoneIncompatible = function(ua) {
30
- return ua.includes("iPhone OS 12_") || ua.includes("iPad; CPU OS 12_") || //iOS 12
31
- (ua.includes("UCBrowser/")
32
- ? this.isOlderUcBrowser(ua) //UC Browser < 12.13.2
33
- : (ua.includes("Chrome/5") || ua.includes("Chrome/6"))) ||
34
- ua.includes("Chromium/5") || ua.includes("Chromium/6") ||
35
- (ua.includes(" OS X 10_14_") &&
36
- ((ua.includes("Version/") && ua.includes("Safari")) || //Safari on MacOS 10.14
37
- ua.endsWith("(KHTML, like Gecko)"))); //Web view on MacOS 10.14
38
- }
39
-
40
- StorageAccessHelper.prototype.isOlderUcBrowser = function(ua) {
41
- var match = ua.match(/UCBrowser\/(\d+)\.(\d+)\.(\d+)\./);
42
- if (!match) return false;
43
- var major = parseInt(match[1]);
44
- var minor = parseInt(match[2]);
45
- var build = parseInt(match[3]);
46
- if (major != 12) return major < 12;
47
- if (minor != 13) return minor < 13;
48
- return build < 2;
49
- }
50
-
51
- StorageAccessHelper.prototype.setCookie = function(value) {
52
- if(!this.sameSiteNoneIncompatible(navigator.userAgent)) {
53
- value += '; secure; SameSite=None'
54
- }
55
- document.cookie = value;
56
- }
57
-
58
- StorageAccessHelper.prototype.grantedStorageAccess = function() {
59
- try {
60
- sessionStorage.setItem('shopify.granted_storage_access', true);
61
- this.setCookie('shopify.granted_storage_access=true');
62
- if (!document.cookie) {
63
- throw 'Cannot set third-party cookie.'
64
- }
65
- this.redirectToAppTargetUrl();
66
- } catch (error) {
67
- console.warn('Third party cookies may be blocked.', error);
68
- this.redirectToAppTLD(ACCESS_DENIED_STATUS);
69
- }
70
- }
71
-
72
- StorageAccessHelper.prototype.handleRequestStorageAccess = function() {
73
- return document.requestStorageAccess().then(this.grantedStorageAccess.bind(this), this.redirectToAppsIndex.bind(this, ACCESS_DENIED_STATUS));
74
- }
75
-
76
- StorageAccessHelper.prototype.setupRequestStorageAccess = function() {
77
- var requestContent = document.getElementById('RequestStorageAccess');
78
- var requestButton = document.getElementById('TriggerAllowCookiesPrompt');
79
-
80
- requestButton.addEventListener('click', this.handleRequestStorageAccess.bind(this));
81
- requestContent.style.display = 'block';
82
- }
83
-
84
- StorageAccessHelper.prototype.handleHasStorageAccess = function() {
85
- if (sessionStorage.getItem('shopify.granted_storage_access')) {
86
- // If app was classified by ITP and used Storage Access API to acquire access
87
- this.redirectToAppTargetUrl();
88
- } else {
89
- // If app has not been classified by ITP and still has storage access
90
- this.redirectToAppTLD(ACCESS_GRANTED_STATUS);
91
- }
92
- }
93
-
94
- StorageAccessHelper.prototype.handleGetStorageAccess = function() {
95
- if (sessionStorage.getItem('shopify.top_level_interaction')) {
96
- // If merchant has been redirected to interact with TLD (requirement for prompting request to gain storage access)
97
- this.setupRequestStorageAccess();
98
- } else {
99
- // If merchant has not been redirected to interact with TLD (requirement for prompting request to gain storage access)
100
- this.redirectToAppTLD(ACCESS_DENIED_STATUS);
101
- }
102
- }
103
-
104
- StorageAccessHelper.prototype.manageStorageAccess = function() {
105
- return document.hasStorageAccess().then(function(hasAccess) {
106
- if (hasAccess) {
107
- this.handleHasStorageAccess();
108
- } else {
109
- this.handleGetStorageAccess();
110
- }
111
- }.bind(this));
112
- }
113
-
114
- StorageAccessHelper.prototype.execute = function() {
115
- if (ITPHelper.prototype.canPartitionCookies()) {
116
- this.setUpCookiePartitioning();
117
- return;
118
- }
119
-
120
- if (ITPHelper.prototype.userAgentIsAffected()) {
121
- this.manageStorageAccess();
122
- } else {
123
- this.grantedStorageAccess();
124
- }
125
- }
126
-
127
- /* ITP 2.0 solution: handles cookie partitioning */
128
- StorageAccessHelper.prototype.setUpHelper = function() {
129
- var shopifyData = document.body.dataset;
130
- return new ITPHelper({redirectUrl: "https://" + shopifyData.shopOrigin + "/admin/apps/" + shopifyData.apiKey + shopifyData.returnTo});
131
- }
132
-
133
- StorageAccessHelper.prototype.setCookieAndRedirect = function() {
134
- this.setCookie('shopify.cookies_persist=true');
135
- var helper = this.setUpHelper();
136
- helper.redirect();
137
- }
138
-
139
- StorageAccessHelper.prototype.setUpCookiePartitioning = function() {
140
- var itpContent = document.getElementById('CookiePartitionPrompt');
141
- itpContent.style.display = 'block';
142
-
143
- var button = document.getElementById('AcceptCookies');
144
- button.addEventListener('click', this.setCookieAndRedirect.bind(this));
145
- }
146
-
147
- this.StorageAccessHelper = StorageAccessHelper;
148
- })(window);
@@ -1,17 +0,0 @@
1
- (function() {
2
- function redirect() {
3
- var redirectTargetElement = document.getElementById("redirection-target");
4
-
5
- var targetInfo = JSON.parse(redirectTargetElement.dataset.target)
6
-
7
- if (window.top == window.self) {
8
- // If the current window is the 'parent', change the URL by setting location.href
9
- window.top.location.href = targetInfo.hasStorageAccessUrl;
10
- } else {
11
- var storageAccessHelper = new StorageAccessHelper(targetInfo);
12
- storageAccessHelper.execute();
13
- }
14
- }
15
-
16
- document.addEventListener("DOMContentLoaded", redirect);
17
- })();
@@ -1,2 +0,0 @@
1
- //= require ./itp_helper.js
2
- //= require ./top_level_interaction.js
@@ -1,11 +0,0 @@
1
- (function() {
2
- function setUpTopLevelInteraction() {
3
- var TopLevelInteraction = new ITPHelper({
4
- redirectUrl: document.body.dataset.redirectUrl,
5
- });
6
-
7
- TopLevelInteraction.execute();
8
- }
9
-
10
- document.addEventListener("DOMContentLoaded", setUpTopLevelInteraction);
11
- })();
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ShopifyApp
4
- module Authenticated
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
- include ShopifyApp::Localization
9
- include ShopifyApp::LoginProtection
10
- include ShopifyApp::CsrfProtection
11
- include ShopifyApp::EmbeddedApp
12
- include ShopifyApp::EnsureBilling
13
-
14
- before_action :login_again_if_different_user_or_shop
15
- around_action :activate_shopify_session
16
- after_action :add_top_level_redirection_headers
17
- end
18
- end
19
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ShopifyApp
4
- module RequireKnownShop
5
- extend ActiveSupport::Concern
6
- include ShopifyApp::RedirectForEmbedded
7
-
8
- included do
9
- before_action :check_shop_domain
10
- before_action :check_shop_known
11
- end
12
-
13
- def current_shopify_domain
14
- return if params[:shop].blank?
15
-
16
- @shopify_domain ||= ShopifyApp::Utils.sanitize_shop_domain(params[:shop])
17
- end
18
-
19
- private
20
-
21
- def check_shop_domain
22
- redirect_to(ShopifyApp.configuration.login_url) unless current_shopify_domain
23
- end
24
-
25
- def check_shop_known
26
- @shop = SessionRepository.retrieve_shop_session_by_shopify_domain(current_shopify_domain)
27
- unless @shop
28
- if embedded_param?
29
- redirect_for_embedded
30
- else
31
- redirect_to(shop_login)
32
- end
33
- end
34
- end
35
-
36
- def shop_login
37
- url = URI(ShopifyApp.configuration.login_url)
38
-
39
- url.query = URI.encode_www_form(
40
- shop: params[:shop],
41
- host: params[:host],
42
- return_to: request.fullpath,
43
- )
44
-
45
- url.to_s
46
- end
47
- end
48
- end
@@ -1,70 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="<%= I18n.locale %>">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <base target="_top">
7
- <title>Redirecting…</title>
8
- <%= render 'shopify_app/partials/layout_styles' %>
9
- <%= render 'shopify_app/partials/typography_styles' %>
10
- <%= render 'shopify_app/partials/card_styles' %>
11
- <%= render 'shopify_app/partials/button_styles' %>
12
- <style>
13
- #CookiePartitionPrompt {
14
- display: none;
15
- }
16
- </style>
17
-
18
- <%= javascript_include_tag('shopify_app/enable_cookies', crossorigin: 'anonymous', integrity: true) %>
19
- </head>
20
- <body data-api-key="<%= ShopifyApp.configuration.api_key %>" data-shop-origin="<%= @shop %>" data-redirect-url="<%= @url %>" data-host="<%= params[:host] %>" data-return-to="<%= params[:return_to] %>">
21
- <%=
22
- content_tag(
23
- :div, nil,
24
- id: 'redirection-target',
25
- data: {
26
- target: {
27
- myshopifyUrl: "https://#{current_shopify_domain}",
28
- hasStorageAccessUrl: "#{has_storage_access_url}",
29
- doesNotHaveStorageAccessUrl: "#{does_not_have_storage_access_url}",
30
- appTargetUrl: "#{app_target_url}"
31
- },
32
- },
33
- )
34
- %>
35
- <main id="CookiePartitionPrompt">
36
- <div class="Polaris-Page">
37
- <div class="Polaris-Page__Content">
38
- <div class="Polaris-Layout">
39
- <div class="Polaris-Layout__Section">
40
- <div class="Polaris-Stack Polaris-Stack--vertical">
41
- <div class="Polaris-Stack__Item">
42
- <div class="Polaris-Card">
43
- <div class="Polaris-Card__Header">
44
- <h1 class="Polaris-Heading"><%= I18n.t('enable_cookies_heading', app: ShopifyApp.configuration.application_name) %></h1>
45
- </div>
46
- <div class="Polaris-Card__Section">
47
- <p><%= I18n.t('enable_cookies_body', app: ShopifyApp.configuration.application_name) %></p>
48
- </div>
49
- <div class="Polaris-Card__Section Polaris-Card__Section--subdued">
50
- <p><%= I18n.t('enable_cookies_footer') %></p>
51
- </div>
52
- </div>
53
- </div>
54
- <div class="Polaris-Stack__Item">
55
- <div class="Polaris-Stack Polaris-Stack--distributionTrailing Polaris-Stack--distributionTrailingCustomSpacing">
56
- <div class="Polaris-Stack__Item">
57
- <button type="button" class="Polaris-Button Polaris-Button--primary" id="AcceptCookies">
58
- <span class="Polaris-Button__Content"><span><%= I18n.t('enable_cookies_action') %></span></span>
59
- </button>
60
- </div>
61
- </div>
62
- </div>
63
- </div>
64
- </div>
65
- </div>
66
- </div>
67
- </div>
68
- </main>
69
- </body>
70
- </html>
@@ -1,68 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="<%= I18n.locale %>">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <base target="_top">
7
- <title>Redirecting…</title>
8
- <%= render 'shopify_app/partials/layout_styles' %>
9
- <%= render 'shopify_app/partials/typography_styles' %>
10
- <%= render 'shopify_app/partials/card_styles' %>
11
- <%= render 'shopify_app/partials/button_styles' %>
12
- <style>
13
- #RequestStorageAccess {
14
- display: none;
15
- }
16
- </style>
17
- </head>
18
- <body data-api-key="<%= ShopifyApp.configuration.api_key %>" data-shop-origin="<%= current_shopify_domain %>" data-host="<%= params[:host] %>" data-return-to="<%= params[:return_to] %>">
19
- <%=
20
- content_tag(:div, nil,
21
- id: 'redirection-target',
22
- data: {
23
- target: {
24
- myshopifyUrl: "https://#{current_shopify_domain}",
25
- hasStorageAccessUrl: "#{has_storage_access_url}",
26
- doesNotHaveStorageAccessUrl: "#{does_not_have_storage_access_url}",
27
- appTargetUrl: "#{app_target_url}"
28
- },
29
- },
30
- )
31
- %>
32
- <main id="RequestStorageAccess">
33
- <div class="Polaris-Page">
34
- <div class="Polaris-Page__Content">
35
- <div class="Polaris-Layout">
36
- <div class="Polaris-Layout__Section">
37
- <div class="Polaris-Stack Polaris-Stack--vertical">
38
- <div class="Polaris-Stack__Item">
39
- <div class="Polaris-Card">
40
- <div class="Polaris-Card__Header">
41
- <h1 class="Polaris-Heading"><%= I18n.t('request_storage_access_heading', app: ShopifyApp.configuration.application_name) %></h1>
42
- </div>
43
- <div class="Polaris-Card__Section">
44
- <p><%= I18n.t('request_storage_access_body', app: ShopifyApp.configuration.application_name) %></p>
45
- </div>
46
- <div class="Polaris-Card__Section Polaris-Card__Section--subdued">
47
- <p><%= I18n.t('request_storage_access_footer') %></p>
48
- </div>
49
- </div>
50
- </div>
51
- <div class="Polaris-Stack__Item">
52
- <div class="Polaris-Stack Polaris-Stack--distributionTrailing Polaris-Stack--distributionTrailingCustomSpacing">
53
- <div class="Polaris-Stack__Item">
54
- <button type="button" class="Polaris-Button Polaris-Button--primary" id="TriggerAllowCookiesPrompt">
55
- <span class="Polaris-Button__Content"><span><%= I18n.t('request_storage_access_action') %></span></span>
56
- </button>
57
- </div>
58
- </div>
59
- </div>
60
- </div>
61
- </div>
62
- </div>
63
- </div>
64
- </div>
65
- </main>
66
- <%= javascript_include_tag('shopify_app/request_storage_access', crossorigin: 'anonymous', integrity: true) %>
67
- </body>
68
- </html>
@@ -1,63 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="<%= I18n.locale %>">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <base target="_top">
7
- <title>Redirecting…</title>
8
- <%= render 'shopify_app/partials/card_styles' %>
9
- <%= render 'shopify_app/partials/layout_styles' %>
10
- <%= render 'shopify_app/partials/typography_styles' %>
11
- <%= render 'shopify_app/partials/button_styles' %>
12
- <%= render 'shopify_app/partials/empty_state_styles' %>
13
- <style>
14
- #TopLevelInteractionContent {
15
- display: none;
16
- }
17
- </style>
18
-
19
- <%= javascript_include_tag('shopify_app/top_level', crossorigin: 'anonymous', integrity: true) %>
20
- </head>
21
- <body data-api-key="<%= ShopifyApp.configuration.api_key %>" data-shop-origin="https://<%= @shop %>" data-host="<%= @host %>" data-redirect-url="<%= @url %>">
22
- <main id="TopLevelInteractionContent">
23
- <div class="Polaris-Page">
24
- <div class="Polaris-Page__Content">
25
- <div class="Polaris-Layout">
26
- <div class="Polaris-Layout__Section">
27
- <div class="Polaris-Stack Polaris-Stack--vertical">
28
- <div class="Polaris-Stack__Item">
29
- <div class="Polaris-Card">
30
- <div class="Polaris-Card__Section">
31
- <div class="Polaris-EmptyState">
32
- <div class="Polaris-EmptyState__Section">
33
- <div class="Polaris-EmptyState__DetailsContainer">
34
- <div class="Polaris-EmptyState__Details">
35
- <div class="Polaris-TextContainer">
36
- <h1 class="Polaris-DisplayText Polaris-DisplayText--sizeSmall"><%= I18n.t('top_level_interaction_heading', app: ShopifyApp.configuration.application_name) %></h1>
37
- <div class="Polaris-EmptyState__Content">
38
- <p><%= I18n.t('top_level_interaction_body', app: ShopifyApp.configuration.application_name) %></p>
39
- </div>
40
- </div>
41
- <div class="Polaris-EmptyState__Actions">
42
- <div class="Polaris-Stack Polaris-Stack--alignmentCenter">
43
- <div class="Polaris-Stack__Item"><button type="button" id="TopLevelInteractionButton" class="Polaris-Button Polaris-Button--primary Polaris-Button--sizeLarge"><span class="Polaris-Button__Content"><span class="Polaris-Button__Icon"></span><span><%= I18n.t('top_level_interaction_action') %></span></span></button></div>
44
- </div>
45
- </div>
46
- </div>
47
- </div>
48
- <div class="Polaris-EmptyState__ImageContainer">
49
- <%= image_tag 'storage_access.svg', role: "presentation", alt: "", class: "Polaris-EmptyState__Image" %>
50
- </div>
51
- </div>
52
- </div>
53
- </div>
54
- </div>
55
- </div>
56
- </div>
57
- </div>
58
- </div>
59
- </div>
60
- </div>
61
- </main>
62
- </body>
63
- </html>
@@ -1,13 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <base target="_top">
7
- <title>Redirecting…</title>
8
- <%= javascript_include_tag('shopify_app/post_redirect', crossorigin: 'anonymous', integrity: true) %>
9
- </head>
10
- <body>
11
- <%= form_tag '/auth/shopify', id: 'redirect-form' %>
12
- </body>
13
- </html>
@@ -1,28 +0,0 @@
1
- # ScriptTags
2
-
3
- #### Table of contents
4
-
5
- [Manage ScriptTags using the Shopify App initializer](#manage-scripttags-using-the-shopify-app-initializer)
6
-
7
- ## Manage ScriptTags using the Shopify App initializer
8
-
9
- As with webhooks, ShopifyApp can manage your app's [ScriptTags](https://shopify-dev-staging.shopifycloud.com/docs/admin-api/graphql/reference/online-store/scripttag) for you by setting which scripttags you require in the initializer:
10
-
11
- ```ruby
12
- ShopifyApp.configure do |config|
13
- config.scripttags = [
14
- {event:'onload', src: 'https://example.com/fancy.js'},
15
- {event:'onload', src: ->(domain) { dynamic_tag_url(domain) } }
16
- ]
17
- end
18
- ```
19
-
20
- You also need to have write_script_tags permission in the config scope in order to add script tags automatically:
21
-
22
- ```ruby
23
- config.scope = '... , write_script_tags'
24
- ```
25
-
26
- Scripttags are created in the same way as the [Webhooks](/docs/shopify_app/webhooks.md), with a background job which will create the required scripttags.
27
-
28
- If `src` responds to `call` its return value will be used as the scripttag's source. It will be called on scripttag creation and deletion.
@@ -1,88 +0,0 @@
1
- # Session repository
2
-
3
- #### Table of contents
4
-
5
- [`ShopifyApp::SessionRepository`](#shopifyappsessionrepository)
6
- * [Shop-based token storage](#shop-based-token-storage)
7
- * [User-based token storage](#user-based-token-storage)
8
-
9
- [Access scopes](#access-scopes)
10
- * [`ShopifyApp::ShopSessionStorageWithScopes`](#shopifyappshopsessionstoragewithscopes)
11
- * [``ShopifyApp::UserSessionStorageWithScopes``](#shopifyappusersessionstoragewithscopes)
12
-
13
- [Migrating from shop-based to user-based token strategy](#migrating-from-shop-based-to-user-based-token-strategy)
14
-
15
- ## ShopifyApp::SessionRepository
16
-
17
- `ShopifyApp::SessionRepository` allows you as a developer to define how your sessions are stored and retrieved for shops. The `SessionRepository` is configured in the `config/initializers/shopify_app.rb` file and can be set to any object that implements `self.store(auth_session, *args)` which stores the session and returns a unique identifier and `self.retrieve(id)` which returns a `ShopifyAPI::Session` for the passed id. These methods are already implemented as part of the `ShopifyApp::SessionStorage` concern but can be overridden for custom implementation.
18
-
19
- ### Shop-based token storage
20
-
21
- Storing tokens on the store model means that any user login associated with the store will have equal access levels to whatever the original user granted the app.
22
- ```sh
23
- rails generate shopify_app:shop_model
24
- ```
25
- This will generate a shop model which will be the storage for the tokens necessary for authentication.
26
-
27
- ### User-based token storage
28
-
29
- A more granular control over the level of access per user on an app might be necessary, to which the shop-based token strategy is not sufficient. Shopify supports a user-based token storage strategy where a unique token to each user can be managed. Shop tokens must still be maintained if you are running background jobs so that you can make use of them when necessary.
30
- ```sh
31
- rails generate shopify_app:shop_model
32
- rails generate shopify_app:user_model
33
- ```
34
- This will generate a shop model and user model, which will be the storage for the tokens necessary for authentication.
35
-
36
- The current Shopify user will be stored in the rails session at `session[:shopify_user]`
37
-
38
- Read more about Online vs. Offline access [here](https://help.shopify.com/api/getting-started/authentication/oauth).
39
-
40
- ## Access scopes
41
-
42
- If you want to customize how access scopes are stored for shops and users, you can implement the `access_scopes` getters and setters in the models that include `ShopifyApp::ShopSessionStorageWithScopes` and `ShopifyApp::UserSessionStorageWithScopes` as shown:
43
-
44
- ### `ShopifyApp::ShopSessionStorageWithScopes`
45
- ```ruby
46
- class Shop < ActiveRecord::Base
47
- include ShopifyApp::ShopSessionStorageWithScopes
48
-
49
- def access_scopes=(scopes)
50
- # Store access scopes
51
- end
52
- def access_scopes
53
- # Find access scopes
54
- end
55
- end
56
- ```
57
-
58
- ### `ShopifyApp::UserSessionStorageWithScopes`
59
- ```ruby
60
- class User < ActiveRecord::Base
61
- include ShopifyApp::UserSessionStorageWithScopes
62
-
63
- def access_scopes=(scopes)
64
- # Store access scopes
65
- end
66
- def access_scopes
67
- # Find access scopes
68
- end
69
- end
70
- ```
71
- ## Migrating from shop-based to user-based token strategy
72
-
73
- 1. Run the `user_model` generator as mentioned above.
74
- 2. Ensure that both your `Shop` model and `User` model includes the necessary concerns `ShopifyApp::ShopSessionStorage` and `ShopifyApp::UserSessionStorage`.
75
- 3. Make changes to 2 initializer files as shown below:
76
- ```ruby
77
- # In the `omniauth.rb` initializer:
78
- provider :shopify,
79
- ...
80
- setup: lambda { |env|
81
- configuration = ShopifyApp::OmniAuthConfiguration.new(env['omniauth.strategy'], Rack::Request.new(env))
82
- configuration.build_options
83
- }
84
-
85
- # In the `shopify_app.rb` initializer:
86
- config.shop_session_repository = {YOUR_SHOP_MODEL_CLASS}
87
- config.user_session_repository = {YOUR_USER_MODEL_CLASS}
88
- ```
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rails/generators/base"
4
-
5
- module ShopifyApp
6
- module Generators
7
- class AddMarketingActivityExtensionGenerator < Rails::Generators::Base
8
- source_root File.expand_path("../templates", __FILE__)
9
-
10
- def generate_app_extension
11
- template("marketing_activities_controller.rb", "app/controllers/marketing_activities_controller.rb")
12
- generate_routes
13
- end
14
-
15
- private
16
-
17
- def generate_routes
18
- inject_into_file(
19
- "config/routes.rb",
20
- optimize_indentation(routes, 2),
21
- after: "root :to => 'home#index'\n"
22
- )
23
- end
24
-
25
- def routes
26
- <<~EOS
27
-
28
- resource :marketing_activities, only: [:create, :update] do
29
- patch :resume
30
- patch :pause
31
- patch :delete
32
- post :republish
33
- post :preload_form_data
34
- post :preview
35
- post :errors
36
- end
37
- EOS
38
- end
39
- end
40
- end
41
- end