shopify_app 21.6.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/bug-report.md +23 -18
- data/.github/workflows/build.yml +2 -2
- data/.github/workflows/release.yml +1 -1
- data/.github/workflows/rubocop.yml +1 -2
- data/.nvmrc +1 -1
- data/.rubocop.yml +0 -1
- data/CHANGELOG.md +115 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +1 -6
- data/Gemfile.lock +99 -96
- data/README.md +47 -2
- data/app/assets/javascripts/shopify_app/redirect.js +3 -10
- data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +5 -1
- data/app/controllers/concerns/shopify_app/ensure_has_session.rb +11 -5
- data/app/controllers/concerns/shopify_app/ensure_installed.rb +10 -4
- data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +5 -1
- data/app/controllers/shopify_app/callback_controller.rb +39 -18
- data/app/controllers/shopify_app/sessions_controller.rb +25 -4
- 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 +9 -2
- data/docs/Troubleshooting.md +0 -23
- data/docs/Upgrading.md +64 -1
- data/docs/shopify_app/authentication.md +179 -58
- data/docs/shopify_app/controller-concerns.md +53 -12
- data/docs/shopify_app/generators.md +2 -2
- data/docs/shopify_app/sessions.md +358 -0
- data/docs/shopify_app/webhooks.md +88 -11
- data/karma.conf.js +6 -4
- 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_gdpr_jobs/add_gdpr_jobs_generator.rb → add_privacy_jobs/add_privacy_jobs_generator.rb} +1 -1
- data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +6 -1
- data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +1 -0
- data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +5 -2
- data/lib/generators/shopify_app/shopify_app_generator.rb +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 +20 -0
- 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 +69 -1
- data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +1 -1
- 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 +14 -3
- 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 +34 -38
- data/lib/shopify_app/controller_concerns/redirect_for_embedded.rb +5 -0
- data/lib/shopify_app/controller_concerns/sanitized_params.rb +4 -0
- data/lib/shopify_app/controller_concerns/token_exchange.rb +111 -0
- data/lib/shopify_app/controller_concerns/with_shopify_id_token.rb +48 -0
- data/lib/shopify_app/engine.rb +5 -11
- data/lib/shopify_app/managers/webhooks_manager.rb +6 -2
- 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 +9 -0
- data/lib/shopify_app/session/session_repository.rb +49 -8
- data/lib/shopify_app/session/shop_session_storage.rb +4 -0
- data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +4 -0
- data/lib/shopify_app/session/user_session_storage.rb +4 -0
- data/lib/shopify_app/session/user_session_storage_with_scopes.rb +25 -0
- data/lib/shopify_app/test_helpers/shopify_session_helper.rb +1 -0
- data/lib/shopify_app/utils.rb +14 -1
- data/lib/shopify_app/version.rb +1 -1
- data/lib/shopify_app.rb +9 -3
- data/package.json +5 -6
- data/shopify_app.gemspec +4 -4
- data/yarn.lock +2134 -3905
- metadata +51 -60
- data/.github/workflows/stale.yml +0 -43
- 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/controllers/concerns/shopify_app/authenticated.rb +0 -17
- data/app/controllers/concerns/shopify_app/require_known_shop.rb +0 -16
- data/docs/shopify_app/script-tags.md +0 -28
- data/docs/shopify_app/session-repository.md +0 -79
- data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +0 -42
- data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +0 -63
- data/lib/shopify_app/controller_concerns/itp.rb +0 -50
- data/lib/shopify_app/jobs/scripttags_manager_job.rb +0 -16
- data/lib/shopify_app/managers/scripttags_manager.rb +0 -85
- /data/lib/generators/shopify_app/{add_gdpr_jobs → add_privacy_jobs}/templates/customers_data_request_job.rb.tt +0 -0
- /data/lib/generators/shopify_app/{add_gdpr_jobs → add_privacy_jobs}/templates/customers_redact_job.rb.tt +0 -0
- /data/lib/generators/shopify_app/{add_gdpr_jobs → add_privacy_jobs}/templates/shop_redact_job.rb.tt +0 -0
@@ -1,79 +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://shopify.dev/apps/auth/oauth/access-modes).
|
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 the `shopify_app.rb` initializer file as shown below:
|
76
|
-
```ruby
|
77
|
-
config.shop_session_repository = {YOUR_SHOP_MODEL_CLASS}
|
78
|
-
config.user_session_repository = {YOUR_USER_MODEL_CLASS}
|
79
|
-
```
|
@@ -1,42 +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
|
-
ShopifyApp::Logger.deprecated("MarketingActivitiesController will be removed in an upcoming version", "22.0.0")
|
12
|
-
template("marketing_activities_controller.rb", "app/controllers/marketing_activities_controller.rb")
|
13
|
-
generate_routes
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def generate_routes
|
19
|
-
inject_into_file(
|
20
|
-
"config/routes.rb",
|
21
|
-
optimize_indentation(routes, 2),
|
22
|
-
after: "root :to => 'home#index'\n",
|
23
|
-
)
|
24
|
-
end
|
25
|
-
|
26
|
-
def routes
|
27
|
-
<<~EOS
|
28
|
-
|
29
|
-
resource :marketing_activities, only: [:create, :update] do
|
30
|
-
patch :resume
|
31
|
-
patch :pause
|
32
|
-
patch :delete
|
33
|
-
post :republish
|
34
|
-
post :preload_form_data
|
35
|
-
post :preview
|
36
|
-
post :errors
|
37
|
-
end
|
38
|
-
EOS
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,63 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class MarketingActivitiesController < ShopifyApp::ExtensionVerificationController
|
4
|
-
def preload_form_data
|
5
|
-
preload_data = {
|
6
|
-
"form_data": {},
|
7
|
-
}
|
8
|
-
render(json: preload_data, status: :ok)
|
9
|
-
end
|
10
|
-
|
11
|
-
def update
|
12
|
-
render(json: {}, status: :accepted)
|
13
|
-
end
|
14
|
-
|
15
|
-
def pause
|
16
|
-
render(json: {}, status: :accepted)
|
17
|
-
end
|
18
|
-
|
19
|
-
def resume
|
20
|
-
render(json: {}, status: :accepted)
|
21
|
-
end
|
22
|
-
|
23
|
-
def delete
|
24
|
-
render(json: {}, status: :accepted)
|
25
|
-
end
|
26
|
-
|
27
|
-
def preview
|
28
|
-
placeholder_img = "https://cdn.shopify.com/s/files/1/0533/2089/files/placeholder-images-image_small.png"
|
29
|
-
preview_response = {
|
30
|
-
"desktop": {
|
31
|
-
"preview_url": placeholder_img,
|
32
|
-
"content_type": "text/html",
|
33
|
-
"width": 360,
|
34
|
-
"height": 200,
|
35
|
-
},
|
36
|
-
"mobile": {
|
37
|
-
"preview_url": placeholder_img,
|
38
|
-
"content_type": "text/html",
|
39
|
-
"width": 360,
|
40
|
-
"height": 200,
|
41
|
-
},
|
42
|
-
}
|
43
|
-
render(json: preview_response, status: :ok)
|
44
|
-
end
|
45
|
-
|
46
|
-
def create
|
47
|
-
render(json: {}, status: :ok)
|
48
|
-
end
|
49
|
-
|
50
|
-
def republish
|
51
|
-
render(json: {}, status: :accepted)
|
52
|
-
end
|
53
|
-
|
54
|
-
def errors
|
55
|
-
request_id = params[:request_id]
|
56
|
-
message = params[:message]
|
57
|
-
|
58
|
-
ShopifyApp::Logger.info("[Marketing Activity App Error Feedback]"\
|
59
|
-
"Request id: #{request_id}, message: #{message}")
|
60
|
-
|
61
|
-
render(json: {}, status: :ok)
|
62
|
-
end
|
63
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ShopifyApp
|
4
|
-
# Cookie management helpers required for ITP implementation
|
5
|
-
module Itp
|
6
|
-
extend ActiveSupport::Concern
|
7
|
-
included do
|
8
|
-
ShopifyApp::Logger.deprecated("Itp will be removed in an upcoming version", "22.0.0")
|
9
|
-
end
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def set_test_cookie
|
14
|
-
return unless ShopifyApp.configuration.embedded_app?
|
15
|
-
return unless user_agent_can_partition_cookies
|
16
|
-
|
17
|
-
session["shopify.cookies_persist"] = true
|
18
|
-
end
|
19
|
-
|
20
|
-
def set_top_level_oauth_cookie
|
21
|
-
session["shopify.top_level_oauth"] = true
|
22
|
-
end
|
23
|
-
|
24
|
-
def clear_top_level_oauth_cookie
|
25
|
-
session.delete("shopify.top_level_oauth")
|
26
|
-
end
|
27
|
-
|
28
|
-
def user_agent_is_mobile
|
29
|
-
user_agent = BrowserSniffer.new(request.user_agent).browser_info
|
30
|
-
|
31
|
-
user_agent[:name].to_s.match(/Shopify\sMobile/)
|
32
|
-
end
|
33
|
-
|
34
|
-
def user_agent_is_pos
|
35
|
-
user_agent = BrowserSniffer.new(request.user_agent).browser_info
|
36
|
-
|
37
|
-
user_agent[:name].to_s.match(/Shopify\sPOS/)
|
38
|
-
end
|
39
|
-
|
40
|
-
def user_agent_can_partition_cookies
|
41
|
-
user_agent = BrowserSniffer.new(request.user_agent).browser_info
|
42
|
-
|
43
|
-
is_safari = user_agent[:name].to_s.match(/Safari/)
|
44
|
-
|
45
|
-
return false unless is_safari
|
46
|
-
|
47
|
-
user_agent[:version].to_s.match(/12\.0/)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ShopifyApp
|
4
|
-
class ScripttagsManagerJob < ActiveJob::Base
|
5
|
-
queue_as do
|
6
|
-
ShopifyApp.configuration.scripttags_manager_queue_name
|
7
|
-
end
|
8
|
-
|
9
|
-
def perform(shop_domain:, shop_token:, scripttags:)
|
10
|
-
ShopifyAPI::Auth::Session.temp(shop: shop_domain, access_token: shop_token) do
|
11
|
-
manager = ScripttagsManager.new(scripttags, shop_domain)
|
12
|
-
manager.create_scripttags
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,85 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ShopifyApp
|
4
|
-
class ScripttagsManager
|
5
|
-
def self.queue(shop_domain, shop_token, scripttags)
|
6
|
-
ShopifyApp::ScripttagsManagerJob.perform_later(
|
7
|
-
shop_domain: shop_domain,
|
8
|
-
shop_token: shop_token,
|
9
|
-
# Procs cannot be serialized so we interpolate now, if necessary
|
10
|
-
scripttags: build_src(scripttags, shop_domain),
|
11
|
-
)
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.build_src(scripttags, domain)
|
15
|
-
scripttags.map do |tag|
|
16
|
-
next tag unless tag[:src].respond_to?(:call)
|
17
|
-
|
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
|
-
ShopifyApp::Logger.deprecated("The ScripttagsManager will become deprecated in an upcoming version", "22.0.0")
|
28
|
-
@required_scripttags = scripttags
|
29
|
-
@shop_domain = shop_domain
|
30
|
-
end
|
31
|
-
|
32
|
-
def recreate_scripttags!
|
33
|
-
destroy_scripttags
|
34
|
-
create_scripttags
|
35
|
-
end
|
36
|
-
|
37
|
-
def create_scripttags
|
38
|
-
return unless required_scripttags.present?
|
39
|
-
|
40
|
-
expanded_scripttags.each do |scripttag|
|
41
|
-
create_scripttag(scripttag) unless scripttag_exists?(scripttag[:src])
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def destroy_scripttags
|
46
|
-
scripttags = expanded_scripttags
|
47
|
-
ShopifyAPI::ScriptTag.all.each do |tag|
|
48
|
-
tag.delete if required_scripttag?(scripttags, tag)
|
49
|
-
end
|
50
|
-
|
51
|
-
@current_scripttags = nil
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def expanded_scripttags
|
57
|
-
self.class.build_src(required_scripttags, shop_domain)
|
58
|
-
end
|
59
|
-
|
60
|
-
def required_scripttag?(scripttags, tag)
|
61
|
-
scripttags.map { |w| w[:src] }.include?(tag.src)
|
62
|
-
end
|
63
|
-
|
64
|
-
def create_scripttag(attributes)
|
65
|
-
scripttag = ShopifyAPI::ScriptTag.new
|
66
|
-
attributes.each { |key, value| scripttag.public_send("#{key}=", value) }
|
67
|
-
|
68
|
-
begin
|
69
|
-
scripttag.save!
|
70
|
-
rescue ShopifyAPI::Errors::HttpResponseError => e
|
71
|
-
raise ::ShopifyApp::CreationFailed, e.message
|
72
|
-
end
|
73
|
-
|
74
|
-
scripttag
|
75
|
-
end
|
76
|
-
|
77
|
-
def scripttag_exists?(src)
|
78
|
-
current_scripttags[src]
|
79
|
-
end
|
80
|
-
|
81
|
-
def current_scripttags
|
82
|
-
@current_scripttags ||= ShopifyAPI::ScriptTag.all.index_by(&:src)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
File without changes
|
File without changes
|
/data/lib/generators/shopify_app/{add_gdpr_jobs → add_privacy_jobs}/templates/shop_redact_job.rb.tt
RENAMED
File without changes
|