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.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/ISSUE_TEMPLATE/bug-report.md +23 -18
  4. data/.github/workflows/build.yml +2 -2
  5. data/.github/workflows/release.yml +1 -1
  6. data/.github/workflows/rubocop.yml +1 -2
  7. data/.nvmrc +1 -1
  8. data/.rubocop.yml +0 -1
  9. data/CHANGELOG.md +115 -0
  10. data/CODE_OF_CONDUCT.md +46 -0
  11. data/CONTRIBUTING.md +1 -6
  12. data/Gemfile.lock +99 -96
  13. data/README.md +47 -2
  14. data/app/assets/javascripts/shopify_app/redirect.js +3 -10
  15. data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +5 -1
  16. data/app/controllers/concerns/shopify_app/ensure_has_session.rb +11 -5
  17. data/app/controllers/concerns/shopify_app/ensure_installed.rb +10 -4
  18. data/app/controllers/concerns/shopify_app/shop_access_scopes_verification.rb +5 -1
  19. data/app/controllers/shopify_app/callback_controller.rb +39 -18
  20. data/app/controllers/shopify_app/sessions_controller.rb +25 -4
  21. data/app/views/shopify_app/layouts/app_bridge.html.erb +17 -0
  22. data/app/views/shopify_app/sessions/patch_shopify_id_token.html.erb +0 -0
  23. data/app/views/shopify_app/shared/redirect.html.erb +10 -1
  24. data/config/locales/cs.yml +0 -18
  25. data/config/locales/da.yml +0 -15
  26. data/config/locales/de.yml +0 -17
  27. data/config/locales/en.yml +0 -11
  28. data/config/locales/es.yml +0 -17
  29. data/config/locales/fi.yml +0 -15
  30. data/config/locales/fr.yml +0 -18
  31. data/config/locales/it.yml +0 -16
  32. data/config/locales/ja.yml +0 -12
  33. data/config/locales/ko.yml +0 -14
  34. data/config/locales/nb.yml +0 -16
  35. data/config/locales/nl.yml +0 -16
  36. data/config/locales/pl.yml +0 -16
  37. data/config/locales/pt-BR.yml +0 -16
  38. data/config/locales/pt-PT.yml +0 -17
  39. data/config/locales/sv.yml +0 -16
  40. data/config/locales/th.yml +0 -15
  41. data/config/locales/tr.yml +0 -17
  42. data/config/locales/vi.yml +0 -17
  43. data/config/locales/zh-CN.yml +0 -11
  44. data/config/locales/zh-TW.yml +0 -11
  45. data/config/routes.rb +2 -1
  46. data/docs/Quickstart.md +9 -2
  47. data/docs/Troubleshooting.md +0 -23
  48. data/docs/Upgrading.md +64 -1
  49. data/docs/shopify_app/authentication.md +179 -58
  50. data/docs/shopify_app/controller-concerns.md +53 -12
  51. data/docs/shopify_app/generators.md +2 -2
  52. data/docs/shopify_app/sessions.md +358 -0
  53. data/docs/shopify_app/webhooks.md +88 -11
  54. data/karma.conf.js +6 -4
  55. data/lib/generators/shopify_app/add_declarative_webhook/add_declarative_webhook_generator.rb +53 -0
  56. data/lib/generators/shopify_app/add_declarative_webhook/templates/webhook_controller.rb.tt +13 -0
  57. data/lib/generators/shopify_app/add_declarative_webhook/templates/webhook_job.rb.tt +15 -0
  58. data/lib/generators/shopify_app/{add_gdpr_jobs/add_gdpr_jobs_generator.rb → add_privacy_jobs/add_privacy_jobs_generator.rb} +1 -1
  59. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +6 -1
  60. data/lib/generators/shopify_app/add_webhook/templates/webhook_job.rb.tt +1 -0
  61. data/lib/generators/shopify_app/install/templates/shopify_app.rb.tt +5 -2
  62. data/lib/generators/shopify_app/shopify_app_generator.rb +1 -1
  63. data/lib/generators/shopify_app/user_model/templates/db/migrate/add_user_expires_at_column.erb +5 -0
  64. data/lib/generators/shopify_app/user_model/user_model_generator.rb +20 -0
  65. data/lib/shopify_app/admin_api/with_token_refetch.rb +27 -0
  66. data/lib/shopify_app/auth/post_authenticate_tasks.rb +48 -0
  67. data/lib/shopify_app/auth/token_exchange.rb +73 -0
  68. data/lib/shopify_app/configuration.rb +69 -1
  69. data/lib/shopify_app/controller_concerns/app_proxy_verification.rb +1 -1
  70. data/lib/shopify_app/controller_concerns/csrf_protection.rb +2 -1
  71. data/lib/shopify_app/controller_concerns/embedded_app.rb +42 -3
  72. data/lib/shopify_app/controller_concerns/ensure_billing.rb +14 -3
  73. data/lib/shopify_app/controller_concerns/frame_ancestors.rb +1 -1
  74. data/lib/shopify_app/controller_concerns/localization.rb +11 -8
  75. data/lib/shopify_app/controller_concerns/login_protection.rb +34 -38
  76. data/lib/shopify_app/controller_concerns/redirect_for_embedded.rb +5 -0
  77. data/lib/shopify_app/controller_concerns/sanitized_params.rb +4 -0
  78. data/lib/shopify_app/controller_concerns/token_exchange.rb +111 -0
  79. data/lib/shopify_app/controller_concerns/with_shopify_id_token.rb +48 -0
  80. data/lib/shopify_app/engine.rb +5 -11
  81. data/lib/shopify_app/managers/webhooks_manager.rb +6 -2
  82. data/lib/shopify_app/middleware/jwt_middleware.rb +13 -9
  83. data/lib/shopify_app/session/in_memory_user_session_store.rb +1 -1
  84. data/lib/shopify_app/session/jwt.rb +9 -0
  85. data/lib/shopify_app/session/session_repository.rb +49 -8
  86. data/lib/shopify_app/session/shop_session_storage.rb +4 -0
  87. data/lib/shopify_app/session/shop_session_storage_with_scopes.rb +4 -0
  88. data/lib/shopify_app/session/user_session_storage.rb +4 -0
  89. data/lib/shopify_app/session/user_session_storage_with_scopes.rb +25 -0
  90. data/lib/shopify_app/test_helpers/shopify_session_helper.rb +1 -0
  91. data/lib/shopify_app/utils.rb +14 -1
  92. data/lib/shopify_app/version.rb +1 -1
  93. data/lib/shopify_app.rb +9 -3
  94. data/package.json +5 -6
  95. data/shopify_app.gemspec +4 -4
  96. data/yarn.lock +2134 -3905
  97. metadata +51 -60
  98. data/.github/workflows/stale.yml +0 -43
  99. data/app/assets/images/storage_access.svg +0 -1
  100. data/app/assets/javascripts/shopify_app/app_bridge_3.1.1.js +0 -10
  101. data/app/assets/javascripts/shopify_app/app_bridge_redirect.js +0 -22
  102. data/app/assets/javascripts/shopify_app/app_bridge_utils_3.1.1.js +0 -1
  103. data/app/controllers/concerns/shopify_app/authenticated.rb +0 -17
  104. data/app/controllers/concerns/shopify_app/require_known_shop.rb +0 -16
  105. data/docs/shopify_app/script-tags.md +0 -28
  106. data/docs/shopify_app/session-repository.md +0 -79
  107. data/lib/generators/shopify_app/add_marketing_activity_extension/add_marketing_activity_extension_generator.rb +0 -42
  108. data/lib/generators/shopify_app/add_marketing_activity_extension/templates/marketing_activities_controller.rb +0 -63
  109. data/lib/shopify_app/controller_concerns/itp.rb +0 -50
  110. data/lib/shopify_app/jobs/scripttags_manager_job.rb +0 -16
  111. data/lib/shopify_app/managers/scripttags_manager.rb +0 -85
  112. /data/lib/generators/shopify_app/{add_gdpr_jobs → add_privacy_jobs}/templates/customers_data_request_job.rb.tt +0 -0
  113. /data/lib/generators/shopify_app/{add_gdpr_jobs → add_privacy_jobs}/templates/customers_redact_job.rb.tt +0 -0
  114. /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