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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/.github/ISSUE_TEMPLATE/ENHANCEMENT.md +9 -0
- data/.github/ISSUE_TEMPLATE/bug-report.md +30 -47
- data/.github/ISSUE_TEMPLATE/feature-request.md +5 -29
- data/.github/workflows/build.yml +11 -12
- data/.github/workflows/release.yml +2 -2
- data/.github/workflows/remove-labels-on-activity.yml +1 -1
- data/.github/workflows/rubocop.yml +2 -3
- data/.nvmrc +1 -1
- data/.rubocop.yml +2 -1
- data/.ruby-version +1 -1
- data/.spin/rails/prepare-application +8 -0
- data/CHANGELOG.md +173 -7
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +16 -6
- data/Gemfile +1 -0
- data/Gemfile.lock +160 -121
- data/README.md +67 -19
- data/SECURITY.md +1 -1
- data/app/assets/javascripts/shopify_app/redirect.js +3 -10
- data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +9 -4
- data/app/controllers/concerns/shopify_app/ensure_has_session.rb +25 -0
- data/app/controllers/concerns/shopify_app/ensure_installed.rb +84 -0
- data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +5 -1
- data/app/controllers/shopify_app/authenticated_controller.rb +1 -1
- data/app/controllers/shopify_app/callback_controller.rb +101 -39
- data/app/controllers/shopify_app/extension_verification_controller.rb +4 -1
- data/app/controllers/shopify_app/sessions_controller.rb +37 -7
- data/app/controllers/shopify_app/webhooks_controller.rb +1 -1
- data/app/views/shopify_app/layouts/app_bridge.html.erb +17 -0
- data/app/views/shopify_app/sessions/patch_shopify_id_token.html.erb +0 -0
- data/app/views/shopify_app/shared/redirect.html.erb +10 -1
- data/config/locales/cs.yml +0 -18
- data/config/locales/da.yml +0 -15
- data/config/locales/de.yml +0 -17
- data/config/locales/en.yml +0 -11
- data/config/locales/es.yml +0 -17
- data/config/locales/fi.yml +0 -15
- data/config/locales/fr.yml +0 -18
- data/config/locales/it.yml +0 -16
- data/config/locales/ja.yml +0 -12
- data/config/locales/ko.yml +0 -14
- data/config/locales/nb.yml +0 -16
- data/config/locales/nl.yml +0 -16
- data/config/locales/pl.yml +0 -16
- data/config/locales/pt-BR.yml +0 -16
- data/config/locales/pt-PT.yml +0 -17
- data/config/locales/sv.yml +0 -16
- data/config/locales/th.yml +0 -15
- data/config/locales/tr.yml +0 -17
- data/config/locales/vi.yml +0 -17
- data/config/locales/zh-CN.yml +0 -11
- data/config/locales/zh-TW.yml +0 -11
- data/config/routes.rb +2 -1
- data/docs/Quickstart.md +14 -5
- data/docs/Troubleshooting.md +38 -25
- data/docs/Upgrading.md +103 -32
- data/docs/shopify_app/authentication.md +179 -58
- data/docs/shopify_app/controller-concerns.md +89 -0
- data/docs/shopify_app/engine.md +2 -11
- data/docs/shopify_app/generators.md +2 -2
- data/docs/shopify_app/logging.md +21 -0
- data/docs/shopify_app/sessions.md +358 -0
- data/docs/shopify_app/testing.md +32 -10
- data/docs/shopify_app/webhooks.md +97 -7
- data/karma.conf.js +6 -4
- data/lib/generators/shopify_app/add_after_authenticate_job/add_after_authenticate_job_generator.rb +6 -3
- data/lib/generators/shopify_app/add_after_authenticate_job/templates/after_authenticate_job.rb +1 -1
- data/lib/generators/shopify_app/add_app_uninstalled_job/add_app_uninstalled_job_generator.rb +15 -0
- data/lib/generators/shopify_app/add_app_uninstalled_job/templates/app_uninstalled_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_declarative_webhook/add_declarative_webhook_generator.rb +53 -0
- data/lib/generators/shopify_app/add_declarative_webhook/templates/webhook_controller.rb.tt +13 -0
- data/lib/generators/shopify_app/add_declarative_webhook/templates/webhook_job.rb.tt +15 -0
- data/lib/generators/shopify_app/add_privacy_jobs/add_privacy_jobs_generator.rb +23 -0
- data/lib/generators/shopify_app/add_privacy_jobs/templates/customers_data_request_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_privacy_jobs/templates/customers_redact_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_privacy_jobs/templates/shop_redact_job.rb.tt +22 -0
- data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +8 -3
- data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +4 -2
- data/lib/generators/shopify_app/app_proxy_controller/app_proxy_controller_generator.rb +1 -1
- data/lib/generators/shopify_app/authenticated_controller/templates/authenticated_controller.rb +1 -1
- data/lib/generators/shopify_app/home_controller/templates/index.html.erb +1 -1
- data/lib/generators/shopify_app/home_controller/templates/unauthenticated_home_controller.rb +1 -1
- data/lib/generators/shopify_app/install/install_generator.rb +4 -4
- data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +13 -3
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token.rake +1 -1
- data/lib/generators/shopify_app/rotate_shopify_token_job/templates/rotate_shopify_token_job.rb +1 -1
- data/lib/generators/shopify_app/routes/routes_generator.rb +1 -1
- data/lib/generators/shopify_app/shop_model/shop_model_generator.rb +1 -1
- data/lib/generators/shopify_app/shop_model/templates/db/migrate/add_shop_access_scopes_column.erb +1 -1
- data/lib/generators/shopify_app/shopify_app_generator.rb +2 -0
- data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_access_scopes_column.erb +1 -1
- data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_expires_at_column.erb +5 -0
- data/lib/generators/shopify_app/user_model/user_model_generator.rb +21 -1
- data/lib/shopify_app/access_scopes/noop_strategy.rb +4 -0
- data/lib/shopify_app/access_scopes/user_strategy.rb +9 -2
- data/lib/shopify_app/admin_api/with_token_refetch.rb +27 -0
- data/lib/shopify_app/auth/post_authenticate_tasks.rb +48 -0
- data/lib/shopify_app/auth/token_exchange.rb +73 -0
- data/lib/shopify_app/configuration.rb +82 -1
- data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +3 -3
- data/lib/shopify_app/controller_concerns/csrf_protection.rb +2 -1
- data/lib/shopify_app/controller_concerns/embedded_app.rb +42 -3
- data/lib/shopify_app/controller_concerns/ensure_billing.rb +28 -12
- data/lib/shopify_app/controller_concerns/frame_ancestors.rb +1 -1
- data/lib/shopify_app/controller_concerns/localization.rb +11 -8
- data/lib/shopify_app/controller_concerns/login_protection.rb +83 -38
- data/lib/shopify_app/controller_concerns/payload_verification.rb +1 -1
- data/lib/shopify_app/controller_concerns/redirect_for_embedded.rb +15 -3
- data/lib/shopify_app/controller_concerns/sanitized_params.rb +5 -0
- data/lib/shopify_app/controller_concerns/token_exchange.rb +111 -0
- data/lib/shopify_app/controller_concerns/webhook_verification.rb +4 -1
- data/lib/shopify_app/controller_concerns/with_shopify_id_token.rb +48 -0
- data/lib/shopify_app/engine.rb +7 -8
- data/lib/shopify_app/logger.rb +28 -0
- data/lib/shopify_app/managers/webhooks_manager.rb +20 -10
- data/lib/shopify_app/middleware/jwt_middleware.rb +13 -9
- data/lib/shopify_app/session/in_memory_user_session_store.rb +1 -1
- data/lib/shopify_app/session/jwt.rb +11 -2
- data/lib/shopify_app/session/session_repository.rb +66 -14
- data/lib/shopify_app/session/session_storage.rb +2 -2
- data/lib/shopify_app/session/shop_session_storage.rb +5 -1
- data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +5 -1
- data/lib/shopify_app/session/user_session_storage.rb +6 -2
- data/lib/shopify_app/session/user_session_storage_with_scopes.rb +27 -2
- data/lib/shopify_app/test_helpers/all.rb +1 -0
- data/lib/shopify_app/test_helpers/shopify_session_helper.rb +16 -0
- data/lib/shopify_app/utils.rb +82 -20
- data/lib/shopify_app/version.rb +1 -1
- data/lib/shopify_app.rb +12 -3
- data/package.json +5 -6
- data/service.yml +0 -2
- data/shopify_app.gemspec +6 -5
- data/translation.yml +1 -0
- data/yarn.lock +2139 -3910
- metadata +78 -58
- data/.github/workflows/stale.yml +0 -31
- data/app/assets/images/storage_access.svg +0 -1
- data/app/assets/javascripts/shopify_app/app_bridge_3.1.1.js +0 -10
- data/app/assets/javascripts/shopify_app/app_bridge_redirect.js +0 -22
- data/app/assets/javascripts/shopify_app/app_bridge_utils_3.1.1.js +0 -1
- data/app/assets/javascripts/shopify_app/enable_cookies.js +0 -3
- data/app/assets/javascripts/shopify_app/itp_helper.js +0 -40
- data/app/assets/javascripts/shopify_app/partition_cookies.js +0 -8
- data/app/assets/javascripts/shopify_app/post_redirect.js +0 -9
- data/app/assets/javascripts/shopify_app/request_storage_access.js +0 -3
- data/app/assets/javascripts/shopify_app/storage_access.js +0 -148
- data/app/assets/javascripts/shopify_app/storage_access_redirect.js +0 -17
- data/app/assets/javascripts/shopify_app/top_level.js +0 -2
- data/app/assets/javascripts/shopify_app/top_level_interaction.js +0 -11
- data/app/controllers/concerns/shopify_app/authenticated.rb +0 -19
- data/app/controllers/concerns/shopify_app/require_known_shop.rb +0 -48
- data/app/views/shopify_app/sessions/enable_cookies.html.erb +0 -70
- data/app/views/shopify_app/sessions/request_storage_access.html.erb +0 -68
- data/app/views/shopify_app/sessions/top_level_interaction.html.erb +0 -63
- data/app/views/shopify_app/shared/post_redirect_to_auth_shopify.html.erb +0 -13
- data/docs/shopify_app/script-tags.md +0 -28
- data/docs/shopify_app/session-repository.md +0 -88
- data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +0 -41
- data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +0 -62
- data/lib/shopify_app/controller_concerns/itp.rb +0 -45
- data/lib/shopify_app/jobs/scripttags_manager_job.rb +0 -16
- 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,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
|