shopify_app 20.1.0 → 20.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/close-waiting-for-response-issues.yml +20 -0
  3. data/.github/workflows/remove-labels-on-activity.yml +16 -0
  4. data/.github/workflows/stale.yml +31 -0
  5. data/CHANGELOG.md +17 -0
  6. data/Gemfile.lock +6 -6
  7. data/README.md +13 -24
  8. data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +1 -1
  9. data/app/controllers/shopify_app/webhooks_controller.rb +0 -2
  10. data/docs/Quickstart.md +14 -7
  11. data/docs/Troubleshooting.md +5 -10
  12. data/docs/Upgrading.md +7 -0
  13. data/docs/shopify_app/authentication.md +5 -1
  14. data/docs/shopify_app/content-security-policy.md +10 -0
  15. data/docs/shopify_app/session-repository.md +3 -3
  16. data/docs/shopify_app/webhooks.md +4 -4
  17. data/lib/generators/shopify_app/add_webhook/add_webhook_generator.rb +5 -5
  18. data/lib/generators/shopify_app/home_controller/templates/index.html.erb +42 -38
  19. data/lib/shopify_app/access_scopes/user_strategy.rb +2 -3
  20. data/lib/shopify_app/controller_concerns/ensure_billing.rb +2 -13
  21. data/lib/shopify_app/controller_concerns/frame_ancestors.rb +2 -2
  22. data/lib/shopify_app/controller_concerns/login_protection.rb +4 -7
  23. data/lib/shopify_app/controller_concerns/redirect_for_embedded.rb +3 -2
  24. data/lib/shopify_app/controller_concerns/sanitized_params.rb +3 -2
  25. data/lib/shopify_app/errors.rb +34 -0
  26. data/lib/shopify_app/managers/scripttags_manager.rb +1 -3
  27. data/lib/shopify_app/managers/webhooks_manager.rb +28 -8
  28. data/lib/shopify_app/session/in_memory_session_store.rb +1 -3
  29. data/lib/shopify_app/session/jwt.rb +8 -12
  30. data/lib/shopify_app/session/null_user_session_store.rb +1 -1
  31. data/lib/shopify_app/session/session_repository.rb +2 -3
  32. data/lib/shopify_app/session/user_session_storage.rb +1 -1
  33. data/lib/shopify_app/version.rb +1 -1
  34. data/lib/shopify_app.rb +3 -0
  35. data/package.json +1 -1
  36. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd02305de4da4e2ffd996e7404103f9aa007d0f96dc7a8677e64bb358c27cd5c
4
- data.tar.gz: 5519b4ee6fbb5c38de4e7e7c93a815dfe0a2722bc5e5280b834db8845f5341d3
3
+ metadata.gz: cb6f3007d376f75a09be6f17e5dd378e4adc0a7ccc5feeac7ee18ecaf5469ee5
4
+ data.tar.gz: 62c9f9d55bf842fb2c20c4123555d0c1e3f234e11855825e8afeec2e38db971d
5
5
  SHA512:
6
- metadata.gz: cc566b8087de98a2b75454611371bb2fe78d56e6e6fdf257f492fee8babe4fd96442309422be84fce66472e6780ce4cf59e9a20ee1343c73b322b5fff0c6683a
7
- data.tar.gz: c760c3a573a3617adddcb0cb60b2517f8f27df2100781c172f88c7b8991bb728d4f2d03d0b86b3102b5342044580795bad02e3681bb4b7685de5fd640817e9c6
6
+ metadata.gz: d845e94acae7308bb796e46c1134faa06fdfa564fdfb8ecb65ec942a85d7e4b464de4e4d875b52add54d60ba6d3d4f4678f009957b8ac277067e683d3ebb70b2
7
+ data.tar.gz: 16fbc75dc6a020f758e2020b2735ed6357878065e110c63072ac1c9114ba4bdc0dad8378d74064e34f69d4287046069e68004f9a64a14ac9c6c587102933c01a
@@ -0,0 +1,20 @@
1
+ name: Close Waiting for Response Issues
2
+ on:
3
+ schedule:
4
+ - cron: "30 1 * * *"
5
+ workflow_dispatch:
6
+ jobs:
7
+ check-need-info:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - name: close-issues
11
+ uses: actions-cool/issues-helper@v3
12
+ with:
13
+ actions: 'close-issues'
14
+ token: ${{ secrets.GITHUB_TOKEN }}
15
+ labels: 'Waiting for Response'
16
+ inactive-day: 7
17
+ body: |
18
+ We are closing this issue because we did not hear back regarding additional details we needed to resolve this issue. If the issue persists and you are able to provide the missing clarification we need, feel free to respond and reopen this issue.
19
+
20
+ We appreciate your understanding as we try to manage our number of open issues.
@@ -0,0 +1,16 @@
1
+ name: Remove Stale or Waiting Labels
2
+ on:
3
+ issue_comment:
4
+ types: [created]
5
+ workflow_dispatch:
6
+ jobs:
7
+ remove-labels-on-activity:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v2
11
+ - uses: actions-ecosystem/action-remove-labels@v1
12
+ if: contains(github.event.issue.labels.*.name, 'Waiting for Response')
13
+ with:
14
+ labels: |
15
+ Waiting for Response
16
+
@@ -0,0 +1,31 @@
1
+ name: Close inactive issues
2
+ on:
3
+ schedule:
4
+ - cron: "30 1 * * *"
5
+
6
+ jobs:
7
+ close-issues:
8
+ runs-on: ubuntu-latest
9
+ permissions:
10
+ issues: write
11
+ pull-requests: write
12
+ steps:
13
+ - uses: actions/stale@v5
14
+ with:
15
+ days-before-issue-stale: 90
16
+ days-before-issue-close: 14
17
+ stale-issue-label: "Stale"
18
+ stale-issue-message: >
19
+ This issue is stale because it has been open for 90 days with no activity. It will be closed if no further action occurs in 14 days.
20
+ close-issue-message: |
21
+ We are closing this issue because it has been inactive for a few months.
22
+ This probably means that it is not reproducible or it has been fixed in a newer version.
23
+ If it’s an enhancement and hasn’t been taken on since it was submitted, then it seems other issues have taken priority.
24
+
25
+ If you still encounter this issue with the latest stable version, please reopen using the issue template. You can also contribute directly by submitting a pull request– see the [CONTRIBUTING.md](https://github.com/Shopify/shopify_app/blob/main/CONTRIBUTING.md) file for guidelines
26
+
27
+ Thank you!
28
+ days-before-pr-stale: -1
29
+ days-before-pr-close: -1
30
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
31
+ exempt-issue-labels: "feature request"
data/CHANGELOG.md CHANGED
@@ -1,6 +1,23 @@
1
1
  Unreleased
2
2
  ----------
3
3
 
4
+ 20.2.0 (September 30, 2022)
5
+ ----------
6
+ * Fixes a method signature error bug when raising `BillingError`. [#1513](https://github.com/Shopify/shopify_app/pull/1513)
7
+ * Fixes bug with Rails 7 and import maps with Safari/Firefox on the HomeController#index view. [#1506](https://github.com/Shopify/shopify_app/pull/1506)
8
+ * Refactors how default `domain_host` is populated in the CSP header added to responses in the `FrameAncestors` controller concern. [#1504](https://github.com/Shopify/shopify_app/pull/1504)
9
+ * Removes duplicate `;` added in CSP header. [#1500](https://github.com/Shopify/shopify_app/pull/1500)
10
+
11
+ * Fixed an issue where `ShopifyApp::UserSessionStorage` was causing an infinite OAuth loop when not checking scopes. [#1516](https://github.com/Shopify/shopify_app/pull/1516)
12
+ * Move all error classes created for this gem into `lib/shopify_app/errors.rb`. Constant names of errors nested by modules and classes have been removed to give a shorter namespace.
13
+
14
+ 20.1.1 (September 2, 2022)
15
+ ----------
16
+
17
+ * Fixed an issue where the `embedded_redirect_url` could lead to a redirect loop in server-side rendered (or production) apps. [#1497](https://github.com/Shopify/shopify_app/pull/1497)
18
+ * Fixes bug where webhooks were generated with addresses instead of the [path the Ruby API](https://github.com/Shopify/shopify-api-ruby/blob/7a08ae9d96a7a85abd0113dae4eb76398cba8c64/lib/shopify_api/webhooks/registrations/http.rb#L12) is expecting [#1474](https://github.com/Shopify/shopify_app/pull/1474). The breaking change that was accidentially already shipped was that `address` attribute for webhooks should be paths not addresses with `https://` and the host name. While the `address` attribute name will still work assuming the value is a path, this name is deprecated. Please configure webhooks with the `path` attribute name instead.
19
+ * Deduce webhook path from deprecated webhook address if initializer uses address attribute. This makes this attribute change a non-breaking change for those upgrading.
20
+
4
21
  20.1.0 (August 22, 2022)
5
22
  ----------
6
23
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify_app (20.1.0)
4
+ shopify_app (20.2.0)
5
5
  activeresource
6
6
  browser_sniffer (~> 2.0)
7
7
  jwt (>= 2.2.3)
@@ -85,7 +85,7 @@ GEM
85
85
  ast (2.4.2)
86
86
  binding_of_caller (1.0.0)
87
87
  debug_inspector (>= 0.0.1)
88
- browser_sniffer (2.0.0)
88
+ browser_sniffer (2.1.0)
89
89
  builder (3.2.4)
90
90
  byebug (11.1.3)
91
91
  coderay (1.1.3)
@@ -97,14 +97,14 @@ GEM
97
97
  erubi (1.10.0)
98
98
  globalid (1.0.0)
99
99
  activesupport (>= 5.0)
100
- hash_diff (1.0.0)
100
+ hash_diff (1.1.1)
101
101
  hashdiff (1.0.1)
102
102
  httparty (0.20.0)
103
103
  mime-types (~> 3.0)
104
104
  multi_xml (>= 0.5.2)
105
105
  i18n (1.10.0)
106
106
  concurrent-ruby (~> 1.0)
107
- jwt (2.4.1)
107
+ jwt (2.5.0)
108
108
  loofah (2.15.0)
109
109
  crass (~> 1.0.2)
110
110
  nokogiri (>= 1.5.9)
@@ -125,7 +125,7 @@ GEM
125
125
  mini_portile2 (~> 2.8.0)
126
126
  racc (~> 1.4)
127
127
  oj (3.13.21)
128
- openssl (3.0.0)
128
+ openssl (3.0.1)
129
129
  parallel (1.21.0)
130
130
  parser (3.1.0.0)
131
131
  ast (~> 2.4.1)
@@ -204,7 +204,7 @@ GEM
204
204
  securerandom
205
205
  sorbet-runtime
206
206
  zeitwerk (~> 2.5)
207
- sorbet-runtime (0.5.10354)
207
+ sorbet-runtime (0.5.10470)
208
208
  sprockets (4.1.1)
209
209
  concurrent-ruby (~> 1.0)
210
210
  rack (> 1, < 3)
data/README.md CHANGED
@@ -32,41 +32,25 @@ This gem requires that you have the following credentials:
32
32
  - **Shopify API key:** The API key app credential specified in your [Shopify Partners dashboard](https://partners.shopify.com/organizations).
33
33
  - **Shopify API secret:** The API secret key app credential specified in your [Shopify Partners dashboard](https://partners.shopify.com/organizations).
34
34
 
35
- ### OAuth Tunnel in Development
36
-
37
- In order to redirect OAuth requests securely to localhost, you'll need to setup a tunnel to redirect from the internet to localhost.
38
-
39
- We've validated that [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/trycloudflare/) works with this template.
40
-
41
- To do that, you can [install the `cloudflared` CLI tool](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/), and run:
42
-
43
- ```shell
44
- # Note that you can also use a different port
45
- cloudflared tunnel --url http://localhost:3000
46
- ```
47
-
48
- You will need to keep this window running to maintain the tunnel during development.
49
-
50
35
  ## Usage
51
36
 
52
37
  1. To get started, create a new Rails app:
53
38
 
54
39
  ``` sh
55
- $ rails new my_shopify_app
40
+ rails new my_shopify_app
56
41
  ```
57
42
 
58
43
  2. Add the Shopify App gem to `my_shopify_app`'s Gemfile.
59
44
 
60
45
  ```sh
61
- $ bundle add shopify_app
46
+ bundle add shopify_app
62
47
  ```
63
48
 
64
49
  3. Create a `.env` file in the root of `my_shopify_app` to specify your Shopify API credentials:
65
50
 
66
- ```
51
+ ```sh
67
52
  SHOPIFY_API_KEY=<Your Shopify API key>
68
53
  SHOPIFY_API_SECRET=<Your Shopify API secret>
69
- HOST=<Your SSH tunnel host>
70
54
  ```
71
55
 
72
56
  > In a development environment, you can use a gem like `dotenv-rails` to manage environment variables.
@@ -74,22 +58,26 @@ HOST=<Your SSH tunnel host>
74
58
  4. Run the default Shopify App generator to create an app that can be embedded in the Shopify Admin:
75
59
 
76
60
  ```sh
77
- $ rails generate shopify_app
61
+ rails generate shopify_app
78
62
  ```
79
63
 
80
64
  5. Run a migration to create the necessary tables in your database:
81
65
 
82
66
  ```sh
83
- $ rails db:migrate
67
+ rails db:migrate
84
68
  ```
85
69
 
86
- 6. Run the app:
70
+ 6. Setup a SSH tunnel to allow the OAuth redirect to work. See how in the [Setup SSH tunnel for development](/docs/Quickstart.md#setup-ssh-tunnel-for-development) section in [Quickstart](/docs/Quickstart.md)
71
+
72
+ 7. Run the app:
87
73
 
88
74
  ```sh
89
- $ rails server
75
+ rails server
90
76
  ```
91
77
 
92
- See [*Quickstart*](/docs/Quickstart.md) to learn how to install your app on a shop.
78
+ 8. Install the app by visiting the server's URL (e.g. http://127.0.0.1:3000) and specifying the subdomain of the shop where you want it to be installed to.
79
+
80
+ 9. After the app is installed, you're redirected to the embedded app.
93
81
 
94
82
  This app implements [OAuth 2.0](https://shopify.dev/tutorials/authenticate-with-oauth) with Shopify to authenticate requests made to Shopify APIs. By default, this app is configured to use [session tokens](https://shopify.dev/concepts/apps/building-embedded-apps-using-session-tokens) to authenticate merchants when embedded in the Shopify Admin.
95
83
 
@@ -121,6 +109,7 @@ You can find documentation on gem usage, concepts, mixins, installation, and mor
121
109
  * [Handling changes in access scopes](/docs/shopify_app/handling-access-scopes-changes.md)
122
110
  * [Testing](/docs/shopify_app/testing.md)
123
111
  * [Webhooks](/docs/shopify_app/webhooks.md)
112
+ * [Content Security Policy](/docs/shopify_app/content-security-policy.md)
124
113
 
125
114
  ### Engine
126
115
 
@@ -26,7 +26,7 @@ module ShopifyApp
26
26
 
27
27
  def redirect_to_splash_page
28
28
  redirect_to(splash_page)
29
- rescue ShopifyApp::LoginProtection::ShopifyDomainNotFound => error
29
+ rescue ::ShopifyApp::ShopifyDomainNotFound => error
30
30
  Rails.logger.warn("[ShopifyApp::EnsureAuthenticatedLinks] Redirecting to login: [#{error.class}] "\
31
31
  "Could not determine current shop domain")
32
32
  redirect_to(ShopifyApp.configuration.login_url)
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShopifyApp
4
- class MissingWebhookJobError < StandardError; end
5
-
6
4
  class WebhooksController < ActionController::Base
7
5
  include ShopifyApp::WebhookVerification
8
6
 
data/docs/Quickstart.md CHANGED
@@ -4,24 +4,31 @@ This guide assumes you have completed the steps to create a new Rails app using
4
4
 
5
5
  #### Table of contents
6
6
 
7
- [Make your app available to the internet](#make-your-app-available-to-the-internet)
7
+ [Setup SSH tunnel for development](#setup-ssh-tunnel-for-development)
8
8
 
9
9
  [Use Shopify App Bridge to embed your app in the Shopify Admin](#use-shopify-app-bridge-to-embed-your-app-in-the-shopify-admin)
10
10
 
11
- ## Make your app available to the internet
11
+ ## Setup SSH tunnel for development
12
12
 
13
13
  Your local app needs to be accessible from the public Internet in order to install it on a Shopify store, to use the [App Proxy Controller](/lib/generators/shopify_app/app_proxy_controller/templates/app_proxy_controller.rb) or receive [webhooks](/docs/shopify_app/webhooks.md).
14
14
 
15
- Use a tunneling service like [Cloudflare](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/trycloudflare/) to make your development environment accessible to the internet.
15
+ In order to receive requests securely, you'll need to setup a tunnel from the internet to localhost. You can use [Cloudflare](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/trycloudflare/) for this.
16
16
 
17
- To use Cloudflare, [install the `cloudflared` CLI tool](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/), and run:
17
+ To do so, [install the `cloudflared` CLI tool](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/), and run:
18
18
 
19
- ```shell
20
- # Note that you can also use a different port
19
+ ```sh
20
+ # The port must be the same as the one you run the Rails app on later. We use the Rails default below.
21
21
  cloudflared tunnel --url http://localhost:3000
22
22
  ```
23
23
 
24
- See the [*Embed the app in Shopify*](https://shopify.dev/tutorials/build-rails-react-app-that-uses-app-bridge-authentication#embed-the-app-in-shopify) section of [*Build a Shopify app with Rails, React, and App Bridge*](https://shopify.dev/tutorials/build-rails-react-app-that-uses-app-bridge-authentication) to learn more.
24
+ Keep this window running to keep the tunnel and make note of the URL this command prints out. The URL will look like `https://some-random-words.trycloudflare.com`.
25
+
26
+ Visit the "App Setup" section for your app in the [Shopify Partners dashboard](https://partners.shopify.com/organizations). Set the URL as "App URL" on this settings page and add it to the "Allowed redirection URL(s)", after appending `/auth/shopify/callback` to the end (e.g. `https://some-random-words.trycloudflare.com/auth/shopify/callback`).
27
+
28
+ Add the same URL as `HOST` in your `.env` file e.g.
29
+ ```sh
30
+ HOST='https://some-random-words.trycloudflare.com/'
31
+ ```
25
32
 
26
33
  ## Use Shopify App Bridge to embed your app in the Shopify Admin
27
34
 
@@ -16,8 +16,7 @@
16
16
  [JWT session tokens](#jwt-session-tokens)
17
17
  * [My app is still using cookies to authenticate](#my-app-is-still-using-cookies-to-authenticate)
18
18
  * [My app can't make requests to the Shopify API](#my-app-cant-make-requests-to-the-shopify-api)
19
-
20
- [Migrating to App Bridge 2.0](#migrating-to-app-bridge-2.0)
19
+ * [I'm stuck in a redirect loop after OAuth](#im-stuck-in-a-redirect-loop-after-oauth)
21
20
 
22
21
  ## Generators
23
22
 
@@ -26,7 +25,7 @@
26
25
  Rails uses spring by default to speed up development. To run the generator, spring has to be stopped:
27
26
 
28
27
  ```sh
29
- $ bundle exec spring stop
28
+ bundle exec spring stop
30
29
  ```
31
30
 
32
31
  Run shopify_app generator again.
@@ -144,13 +143,9 @@ X-Shopify-API-Request-Failure-Unauthorized: true
144
143
 
145
144
  Then, use the [Shopify App Bridge Redirect](https://shopify.dev/tools/app-bridge/actions/navigation/redirect) action to redirect your app frontend to the app login URL if this header is set.
146
145
 
147
- ## Migrating to App Bridge 2.0
148
146
 
149
- In order to upgrade your embedded app to the latest App Bridge 2.0 version, please refer to the [migration guide](https://shopify.dev/tutorials/migrate-your-app-to-app-bridge-2).
147
+ ### I'm stuck in a redirect loop after OAuth
150
148
 
151
- To ensure that your app's embedded layout doesn't import App Bridge 2.0 before fully migrating, make the following change to bind it to v1.x.
149
+ In previous versions of `ShopifyApp::Authenticated` controller concern, App Bridge embedded apps were able to include the `Authenticated` controller concern in the `HomeController` and other embedded controllers. This is no longer supported due to browsers blocking 3rd party cookies to increase privacy. App Bridge 3 is needed to handle all embedded sessions.
152
150
 
153
- ```diff
154
- - <script src="https://unpkg.com/@shopify/app-bridge"></script>
155
- + <script src="https://unpkg.com/@shopify/app-bridge@1"></script>
156
- ```
151
+ For more details on how to handle embeded sessions, refer to [the session token documentation](https://shopify.dev/apps/auth/oauth/session-tokens).
data/docs/Upgrading.md CHANGED
@@ -4,6 +4,8 @@ This file documents important changes needed to upgrade your app's Shopify App v
4
4
 
5
5
  #### Table of contents
6
6
 
7
+ [Upgrading to `v20.2.0`](#upgrading-to-v2020)
8
+
7
9
  [Upgrading to `v20.1.0`](#upgrading-to-v2010)
8
10
 
9
11
  [Upgrading to `v19.0.0`](#upgrading-to-v1900)
@@ -18,6 +20,11 @@ This file documents important changes needed to upgrade your app's Shopify App v
18
20
 
19
21
  [Upgrading from `v8.6` to `v9.0.0`](#upgrading-from-v86-to-v900)
20
22
 
23
+ ## Upgrading to `v20.2.0`
24
+ All custom errors defined inline within the `ShopifyApp` gem have been moved to `lib/shopify_app/errors.rb`.
25
+
26
+ - If you rescue any errors defined in this gem, you will need to rename them to match their new namespacing.
27
+
21
28
  ## Upgrading to `v20.1.0`
22
29
 
23
30
  Note that the following steps are *optional* and only apply to **embedded** applications. However, they can improve the loading time of your embedded app at installation and re-auth.
@@ -106,6 +106,10 @@ end
106
106
 
107
107
  For backwards compatibility, the engine still provides a controller called `ShopifyApp::AuthenticatedController` which includes the `ShopifyApp::Authenticated` concern. Note that it inherits directly from `ActionController::Base`, so you will not be able to share functionality between it and your application's `ApplicationController`.
108
108
 
109
+ #### Embedded apps and `ShopifyApp::Authenticated`
110
+
111
+ Embedded Shopify Admin apps can only use the `ShopifyApp::Authenticated` controller concern *if* the requests originate from App Bridge's `authenticatedFetch` method. Those who include this concern in the `HomeController` or some other embedded controller will see what looks like an OAuth redirect loop as the `ShopifyApp::Authenticated` concern will be fighting with the App Bridge. For more details on how to handle embedded sessions, refer to [the session token documentation](https://shopify.dev/apps/auth/oauth/session-tokens).
112
+
109
113
  ### `ShopifyApp::EnsureAuthenticatedLinks`
110
114
 
111
115
  The [`ShopifyApp::EnsureAuthenticatedLinks`](/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb) concern helps authenticate users that access protected pages of your app directly.
@@ -121,4 +125,4 @@ class AuthenticatedController < ApplicationController
121
125
  end
122
126
  ```
123
127
 
124
- See [Authenticate server-side rendered embedded apps using Rails and Turbolinks](https://shopify.dev/tutorials/authenticate-server-side-rendered-embedded-apps-using-rails-and-turbolinks) for more information.
128
+ See [Authenticate server-side rendered embedded apps using Rails and Turbolinks](https://shopify.dev/tutorials/authenticate-server-side-rendered-embedded-apps-using-rails-and-turbolinks) for more information.
@@ -0,0 +1,10 @@
1
+ # Content Security Policy Header
2
+
3
+ Shopify App [handles Rails' configuration](https://edgeguides.rubyonrails.org/security.html#content-security-policy-header) for [Content-Security-Policy Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) when the `ShopifyApp::FrameAncestors` controller concern is included in controllers. This is tyipcally done by including the [`ShopifyApp::Authenticated`](https://github.com/Shopify/shopify_app/blob/ed41165ca9598d2c9d514487365192f22b5eb096/app/controllers/concerns/shopify_app/authenticated.rb) controller concern rather that directly including it.
4
+
5
+ ## Included Domains
6
+
7
+ For actions that include the `ShopifyApp::FrameAncestors` controller concern, the following hosts are added to the Content-Security-Policy header as [per the store requirements](https://shopify.dev/apps/store/security/iframe-protection#embedded-apps):
8
+
9
+ 1. [`current_shopify_domain`](https://github.com/Shopify/shopify_app/blob/ed41165ca9598d2c9d514487365192f22b5eb096/app/controllers/concerns/shopify_app/require_known_shop.rb#L13) || `"*.myshopify.com"` if current shopify domain isn't present
10
+ 2. "https://admin.shopify.com"
@@ -20,7 +20,7 @@
20
20
 
21
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
22
  ```sh
23
- $ rails generate shopify_app:shop_model
23
+ rails generate shopify_app:shop_model
24
24
  ```
25
25
  This will generate a shop model which will be the storage for the tokens necessary for authentication.
26
26
 
@@ -28,8 +28,8 @@ This will generate a shop model which will be the storage for the tokens necessa
28
28
 
29
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
30
  ```sh
31
- $ rails generate shopify_app:shop_model
32
- $ rails generate shopify_app:user_model
31
+ rails generate shopify_app:shop_model
32
+ rails generate shopify_app:user_model
33
33
  ```
34
34
  This will generate a shop model and user model, which will be the storage for the tokens necessary for authentication.
35
35
 
@@ -12,7 +12,7 @@ ShopifyApp can manage your app's webhooks for you if you set which webhooks you
12
12
  ```ruby
13
13
  ShopifyApp.configure do |config|
14
14
  config.webhooks = [
15
- {topic: 'carts/update', address: 'https://example.com/webhooks/carts_update'}
15
+ {topic: 'carts/update', path: 'webhooks/carts_update'}
16
16
  ]
17
17
  end
18
18
  ```
@@ -34,7 +34,7 @@ If you are only interested in particular fields, you can optionally filter the d
34
34
  ```ruby
35
35
  ShopifyApp.configure do |config|
36
36
  config.webhooks = [
37
- {topic: 'products/update', address: 'https://example.com/webhooks/products_update', fields: ['title', 'vendor']}
37
+ {topic: 'products/update', path: 'webhooks/products_update', fields: ['title', 'vendor']}
38
38
  ]
39
39
  end
40
40
  ```
@@ -66,7 +66,7 @@ The WebhooksManager uses ActiveJob. If ActiveJob is not configured then by defau
66
66
  ShopifyApp can create webhooks for you using the `add_webhook` generator. This will add the new webhook to your config and create the required job class for you.
67
67
 
68
68
  ```
69
- rails g shopify_app:add_webhook -t carts/update -a /webhooks/carts_update
69
+ rails g shopify_app:add_webhook --topic carts/update --path webhooks/carts_update
70
70
  ```
71
71
 
72
- Where `-t` is the topic and `-a` is the address the webhook should be sent to.
72
+ Where `--topic` is the topic and `--path` is the path the webhook should be sent to.
@@ -7,7 +7,7 @@ module ShopifyApp
7
7
  class AddWebhookGenerator < Rails::Generators::Base
8
8
  source_root File.expand_path("../templates", __FILE__)
9
9
  class_option :topic, type: :string, aliases: "-t", required: true
10
- class_option :address, type: :string, aliases: "-a", required: true
10
+ class_option :path, type: :string, aliases: "-p", required: true
11
11
 
12
12
  hook_for :test_framework, as: :job, in: :rails do |instance, generator|
13
13
  instance.invoke(generator, [instance.send(:job_file_name)])
@@ -47,7 +47,7 @@ module ShopifyApp
47
47
  private
48
48
 
49
49
  def job_file_name
50
- address.split("/").last
50
+ path.split("/").last
51
51
  end
52
52
 
53
53
  def load_initializer
@@ -55,15 +55,15 @@ module ShopifyApp
55
55
  end
56
56
 
57
57
  def webhook_config
58
- "\n { topic: \"#{topic}\", address: \"#{address}\" },"
58
+ "\n { topic: \"#{topic}\", path: \"#{path}\" },"
59
59
  end
60
60
 
61
61
  def topic
62
62
  options["topic"]
63
63
  end
64
64
 
65
- def address
66
- options["address"]
65
+ def path
66
+ options["path"]
67
67
  end
68
68
  end
69
69
  end
@@ -7,48 +7,52 @@
7
7
  rel="stylesheet"
8
8
  href="https://unpkg.com/@shopify/polaris@4.25.0/styles.min.css"
9
9
  />
10
- <% if embedded_app? %> <script>
11
- document.addEventListener("DOMContentLoaded", async function() {
12
- <% if ShopifyApp.use_importmap? %>
13
- await import("lib/shopify_app")
14
- <% end %>
15
-
16
- var SessionToken = window["app-bridge"].actions.SessionToken
17
- var app = window.app;
10
+ <% if embedded_app? %>
11
+ <script type="module">
12
+ async function displayProducts() {
13
+ var SessionToken = window["app-bridge"].actions.SessionToken
14
+ var app = window.app;
18
15
 
19
- app.dispatch(
20
- SessionToken.request(),
21
- );
22
-
23
- // Save a session token for future requests
24
- window.sessionToken = await new Promise((resolve) => {
25
- app.subscribe(SessionToken.Action.RESPOND, (data) => {
26
- resolve(data.sessionToken || "");
16
+ app.dispatch(
17
+ SessionToken.request(),
18
+ );
19
+ // Save a session token for future requests
20
+ window.sessionToken = await new Promise((resolve) => {
21
+ app.subscribe(SessionToken.Action.RESPOND, (data) => {
22
+ resolve(data.sessionToken || "");
23
+ });
27
24
  });
28
- });
29
25
 
30
- var fetchProducts = function() {
31
- var headers = new Headers({ "Authorization": "Bearer " + window.sessionToken });
32
- return fetch("/products", { headers })
33
- .then(response => response.json())
34
- .then(data => {
35
- var products = data.products;
26
+ var fetchProducts = function() {
27
+ var headers = new Headers({ "Authorization": "Bearer " + window.sessionToken });
28
+ return fetch("/products", { headers })
29
+ .then(response => response.json())
30
+ .then(data => {
31
+ var products = data.products;
36
32
 
37
- if (products === undefined || products.length == 0) {
38
- document.getElementById("products").innerHTML = "<br>No products to display.";
39
- } else {
40
- var list = "";
41
- products.forEach((product) => {
42
- var link = `<a target="_top" href="https://<%%= @shop_origin %>/admin/products/${product.id}">`
43
- list += "<li>" + link + product.title + "</a></li>";
44
- });
45
- document.getElementById("products").innerHTML = "<ul>" + list + "</ul>";
46
- }
47
- });
48
- }();
49
- });
50
- </script>
51
- <% end %> </head>
33
+ if (products === undefined || products.length == 0) {
34
+ document.getElementById("products").innerHTML = "<br>No products to display.";
35
+ } else {
36
+ var list = "";
37
+ products.forEach((product) => {
38
+ var link = `<a target="_top" href="https://<%%= @shop_origin %>/admin/products/${product.id}">`
39
+ list += "<li>" + link + product.title + "</a></li>";
40
+ });
41
+ document.getElementById("products").innerHTML = "<ul>" + list + "</ul>";
42
+ }
43
+ });
44
+ }();
45
+ };
46
+
47
+ <% if ShopifyApp.use_importmap? %>
48
+ import "lib/shopify_app"
49
+ displayProducts();
50
+ <% else %>
51
+ document.addEventListener("DOMContentLoaded", displayProducts);
52
+ <% end %>
53
+ </script>
54
+ <% end %>
55
+ </head>
52
56
  <body>
53
57
  <h2>Products</h2>
54
58
  <% if embedded_app? %> <div id="products"><br>Loading...</div><% else %>
@@ -3,14 +3,13 @@
3
3
  module ShopifyApp
4
4
  module AccessScopes
5
5
  class UserStrategy
6
- class InvalidInput < StandardError; end
7
-
8
6
  class << self
9
7
  def update_access_scopes?(user_id: nil, shopify_user_id: nil)
10
8
  return update_access_scopes_for_user_id?(user_id) if user_id
11
9
  return update_access_scopes_for_shopify_user_id?(shopify_user_id) if shopify_user_id
12
10
 
13
- raise(InvalidInput, "#update_access_scopes? requires user_id or shopify_user_id parameter inputs")
11
+ raise(::ShopifyApp::InvalidInput,
12
+ "#update_access_scopes? requires user_id or shopify_user_id parameter inputs")
14
13
  end
15
14
 
16
15
  private
@@ -2,24 +2,13 @@
2
2
 
3
3
  module ShopifyApp
4
4
  module EnsureBilling
5
- class BillingError < StandardError
6
- attr_accessor :message
7
- attr_accessor :errors
8
-
9
- def initialize(message, errors)
10
- super
11
- @message = message
12
- @errors = errors
13
- end
14
- end
15
-
16
5
  extend ActiveSupport::Concern
17
6
 
18
7
  RECURRING_INTERVALS = [BillingConfiguration::INTERVAL_EVERY_30_DAYS, BillingConfiguration::INTERVAL_ANNUAL]
19
8
 
20
9
  included do
21
10
  before_action :check_billing, if: :billing_required?
22
- rescue_from BillingError, with: :handle_billing_error
11
+ rescue_from ::ShopifyApp::BillingError, with: :handle_billing_error
23
12
  end
24
13
 
25
14
  private
@@ -119,7 +108,7 @@ module ShopifyApp
119
108
  data = data["data"]["appPurchaseOneTimeCreate"]
120
109
  end
121
110
 
122
- raise BillingError.new("Error while billing the store", data["userErrros"]) unless data["userErrors"].empty?
111
+ raise BillingError.new("Error while billing the store", data["userErrors"]) unless data["userErrors"].empty?
123
112
 
124
113
  data["confirmationUrl"]
125
114
  end
@@ -7,8 +7,8 @@ module ShopifyApp
7
7
  included do
8
8
  content_security_policy do |policy|
9
9
  policy.frame_ancestors(-> do
10
- domain_host = current_shopify_domain || "*.myshopify.com"
11
- "https://#{domain_host} https://admin.shopify.com;"
10
+ domain_host = current_shopify_domain || "*.#{::ShopifyApp.configuration.myshopify_domain}"
11
+ "https://#{domain_host} https://admin.shopify.com"
12
12
  end)
13
13
  end
14
14
  end
@@ -8,10 +8,6 @@ module ShopifyApp
8
8
  include ShopifyApp::Itp
9
9
  include ShopifyApp::SanitizedParams
10
10
 
11
- class ShopifyDomainNotFound < StandardError; end
12
-
13
- class ShopifyHostNotFound < StandardError; end
14
-
15
11
  included do
16
12
  after_action :set_test_cookie
17
13
  rescue_from ShopifyAPI::Errors::HttpResponseError, with: :handle_http_error
@@ -117,7 +113,8 @@ module ShopifyApp
117
113
  else
118
114
  referer = URI(request.referer || "/")
119
115
  path = referer.path
120
- query = "#{referer.query}&#{sanitized_params.to_query}"
116
+ query = Rack::Utils.parse_nested_query(referer.query)
117
+ query = query.merge(sanitized_params).to_query
121
118
  end
122
119
  session[:return_to] = query.blank? ? path.to_s : "#{path}?#{query}"
123
120
  redirect_to(login_url_with_optional_shop)
@@ -193,12 +190,12 @@ module ShopifyApp
193
190
 
194
191
  return shopify_domain if shopify_domain.present?
195
192
 
196
- raise ShopifyDomainNotFound
193
+ raise ::ShopifyApp::ShopifyDomainNotFound
197
194
  end
198
195
 
199
196
  def return_address
200
197
  return_address_with_params(shop: current_shopify_domain, host: host)
201
- rescue ShopifyDomainNotFound, ShopifyHostNotFound
198
+ rescue ::ShopifyApp::ShopifyDomainNotFound, ::ShopifyApp::ShopifyHostNotFound
202
199
  base_return_address
203
200
  end
204
201
 
@@ -15,7 +15,8 @@ module ShopifyApp
15
15
  end
16
16
 
17
17
  def redirect_for_embedded
18
- redirect_to(redirect_uri_for_embedded)
18
+ # Don't actually redirect if we're already in the redirect route - we want the request to reach the FE
19
+ redirect_to(redirect_uri_for_embedded) unless request.path == ShopifyApp.configuration.embedded_redirect_url
19
20
  end
20
21
 
21
22
  def redirect_uri_for_embedded
@@ -26,7 +27,7 @@ module ShopifyApp
26
27
  redirect_query_params[:host] ||= params[:host] if params[:host].present?
27
28
  redirect_uri = "#{redirect_uri}?#{redirect_query_params.to_query}" if redirect_query_params.present?
28
29
 
29
- query_params = sanitized_params.except(:redirect_uri, :embedded)
30
+ query_params = sanitized_params.except(:redirect_uri)
30
31
  query_params[:redirectUri] = redirect_uri
31
32
 
32
33
  "#{ShopifyApp.configuration.embedded_redirect_url}?#{query_params.to_query}"
@@ -25,9 +25,10 @@ module ShopifyApp
25
25
  end
26
26
 
27
27
  def sanitized_params
28
- request.query_parameters.clone.tap do |query_params|
28
+ parameters = request.post? ? request.request_parameters : request.query_parameters
29
+ parameters.clone.tap do |params_copy|
29
30
  if params[:shop].is_a?(String)
30
- query_params[:shop] = sanitize_shop_param(params)
31
+ params_copy[:shop] = sanitize_shop_param(params)
31
32
  end
32
33
  end
33
34
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyApp
4
+ class BillingError < StandardError
5
+ attr_accessor :message
6
+ attr_accessor :errors
7
+
8
+ def initialize(message, errors)
9
+ super(message)
10
+ @message = message
11
+ @errors = errors
12
+ end
13
+ end
14
+
15
+ class ConfigurationError < StandardError; end
16
+
17
+ class CreationFailed < StandardError; end
18
+
19
+ class EnvironmentError < StandardError; end
20
+
21
+ class InvalidAudienceError < StandardError; end
22
+
23
+ class InvalidDestinationError < StandardError; end
24
+
25
+ class InvalidInput < StandardError; end
26
+
27
+ class MismatchedHostsError < StandardError; end
28
+
29
+ class MissingWebhookJobError < StandardError; end
30
+
31
+ class ShopifyDomainNotFound < StandardError; end
32
+
33
+ class ShopifyHostNotFound < StandardError; end
34
+ end
@@ -2,8 +2,6 @@
2
2
 
3
3
  module ShopifyApp
4
4
  class ScripttagsManager
5
- class CreationFailed < StandardError; end
6
-
7
5
  def self.queue(shop_domain, shop_token, scripttags)
8
6
  ShopifyApp::ScripttagsManagerJob.perform_later(
9
7
  shop_domain: shop_domain,
@@ -69,7 +67,7 @@ module ShopifyApp
69
67
  begin
70
68
  scripttag.save!
71
69
  rescue ShopifyAPI::Errors::HttpResponseError => e
72
- raise CreationFailed, e.message
70
+ raise ::ShopifyApp::CreationFailed, e.message
73
71
  end
74
72
 
75
73
  scripttag
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "uri"
4
+
3
5
  module ShopifyApp
4
6
  class WebhooksManager
5
- class CreationFailed < StandardError; end
6
-
7
7
  class << self
8
8
  def queue(shop_domain, shop_token)
9
9
  ShopifyApp::WebhooksManagerJob.perform_later(
@@ -38,11 +38,13 @@ module ShopifyApp
38
38
  return unless ShopifyApp.configuration.has_webhooks?
39
39
 
40
40
  ShopifyApp.configuration.webhooks.each do |attributes|
41
+ webhook_path = path(attributes)
42
+
41
43
  ShopifyAPI::Webhooks::Registry.add_registration(
42
44
  topic: attributes[:topic],
43
45
  delivery_method: attributes[:delivery_method] || :http,
44
- path: attributes[:address],
45
- handler: webhook_job_klass(attributes[:topic]),
46
+ path: webhook_path,
47
+ handler: webhook_job_klass(webhook_path),
46
48
  fields: attributes[:fields]
47
49
  )
48
50
  end
@@ -50,12 +52,30 @@ module ShopifyApp
50
52
 
51
53
  private
52
54
 
53
- def webhook_job_klass(topic)
54
- webhook_job_klass_name(topic).safe_constantize || raise(ShopifyApp::MissingWebhookJobError)
55
+ def path(webhook_attributes)
56
+ path = webhook_attributes[:path]
57
+ address = webhook_attributes[:address]
58
+ uri = URI(address) if address
59
+
60
+ if path.present?
61
+ path
62
+ elsif uri&.path&.present?
63
+ uri.path
64
+ else
65
+ raise ::ShopifyApp::MissingWebhookJobError,
66
+ "The :path attribute is required for webhook registration."
67
+ end
68
+ end
69
+
70
+ def webhook_job_klass(path)
71
+ webhook_job_klass_name(path).safe_constantize || raise(::ShopifyApp::MissingWebhookJobError)
55
72
  end
56
73
 
57
- def webhook_job_klass_name(topic)
58
- [ShopifyApp.configuration.webhook_jobs_namespace, "#{topic.gsub("/", "_")}_job"].compact.join("/").classify
74
+ def webhook_job_klass_name(path)
75
+ job_file_name = Pathname(path.to_s).basename
76
+
77
+ [ShopifyApp.configuration.webhook_jobs_namespace,
78
+ "#{job_file_name}_job",].compact.join("/").classify
59
79
  end
60
80
  end
61
81
  end
@@ -4,8 +4,6 @@ module ShopifyApp
4
4
  # rubocop:disable Style/ClassVars
5
5
  # Class var repo is needed here in order to share data between the 2 child classes.
6
6
  class InMemorySessionStore
7
- class EnvironmentError < StandardError; end
8
-
9
7
  def self.retrieve(id)
10
8
  repo[id]
11
9
  end
@@ -22,7 +20,7 @@ module ShopifyApp
22
20
 
23
21
  def self.repo
24
22
  if Rails.env.production?
25
- raise EnvironmentError, "Cannot use InMemorySessionStore in a Production environment. \
23
+ raise ::ShopifyApp::EnvironmentError, "Cannot use InMemorySessionStore in a Production environment. \
26
24
  Please initialize ShopifyApp with a model that can store and retrieve sessions"
27
25
  end
28
26
  @@repo ||= {}
@@ -2,20 +2,14 @@
2
2
 
3
3
  module ShopifyApp
4
4
  class JWT
5
- class InvalidDestinationError < StandardError; end
6
-
7
- class MismatchedHostsError < StandardError; end
8
-
9
- class InvalidAudienceError < StandardError; end
10
-
11
5
  WARN_EXCEPTIONS = [
12
6
  ::JWT::DecodeError,
13
7
  ::JWT::ExpiredSignature,
14
8
  ::JWT::ImmatureSignature,
15
9
  ::JWT::VerificationError,
16
- InvalidAudienceError,
17
- InvalidDestinationError,
18
- MismatchedHostsError,
10
+ ::ShopifyApp::InvalidAudienceError,
11
+ ::ShopifyApp::InvalidDestinationError,
12
+ ::ShopifyApp::MismatchedHostsError,
19
13
  ]
20
14
 
21
15
  def initialize(token)
@@ -58,9 +52,11 @@ module ShopifyApp
58
52
  iss_host = ShopifyApp::Utils.sanitize_shop_domain(payload["iss"])
59
53
  api_key = ShopifyApp.configuration.api_key
60
54
 
61
- raise InvalidAudienceError, "'aud' claim does not match api_key" unless payload["aud"] == api_key
62
- raise InvalidDestinationError, "'dest' claim host not a valid shopify host" unless dest_host
63
- raise MismatchedHostsError, "'dest' claim host does not match 'iss' claim host" unless dest_host == iss_host
55
+ raise ::ShopifyApp::InvalidAudienceError,
56
+ "'aud' claim does not match api_key" unless payload["aud"] == api_key
57
+ raise ::ShopifyApp::InvalidDestinationError, "'dest' claim host not a valid shopify host" unless dest_host
58
+ raise ::ShopifyApp::MismatchedHostsError,
59
+ "'dest' claim host does not match 'iss' claim host" unless dest_host == iss_host
64
60
 
65
61
  payload
66
62
  end
@@ -8,7 +8,7 @@ module ShopifyApp
8
8
  end
9
9
 
10
10
  def store(_, _)
11
- raise SessionRepository::ConfigurationError, "user_storage is not configured"
11
+ raise ::ShopifyApp::ConfigurationError, "user_storage is not configured"
12
12
  end
13
13
 
14
14
  def retrieve_by_shopify_user_id(_)
@@ -4,8 +4,6 @@ module ShopifyApp
4
4
  class SessionRepository
5
5
  extend ShopifyAPI::Auth::SessionStorage
6
6
 
7
- class ConfigurationError < StandardError; end
8
-
9
7
  class << self
10
8
  attr_writer :shop_storage
11
9
 
@@ -36,7 +34,8 @@ module ShopifyApp
36
34
  end
37
35
 
38
36
  def shop_storage
39
- load_shop_storage || raise(ConfigurationError, "ShopifySessionRepository.shop_storage is not configured!")
37
+ load_shop_storage || raise(::ShopifyApp::ConfigurationError,
38
+ "ShopifySessionRepository.shop_storage is not configured!")
40
39
  end
41
40
 
42
41
  def user_storage
@@ -11,7 +11,7 @@ module ShopifyApp
11
11
 
12
12
  class_methods do
13
13
  def store(auth_session, user)
14
- user = find_or_initialize_by(shopify_user_id: user[:id])
14
+ user = find_or_initialize_by(shopify_user_id: user.id)
15
15
  user.shopify_token = auth_session.access_token
16
16
  user.shopify_domain = auth_session.shop
17
17
  user.save!
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShopifyApp
4
- VERSION = "20.1.0"
4
+ VERSION = "20.2.0"
5
5
  end
data/lib/shopify_app.rb CHANGED
@@ -34,6 +34,9 @@ module ShopifyApp
34
34
  # utils
35
35
  require "shopify_app/utils"
36
36
 
37
+ # errors
38
+ require "shopify_app/errors"
39
+
37
40
  # controller concerns
38
41
  require "shopify_app/controller_concerns/csrf_protection"
39
42
  require "shopify_app/controller_concerns/localization"
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shopify_app",
3
- "version": "20.1.0",
3
+ "version": "20.2.0",
4
4
  "repository": "git@github.com:Shopify/shopify_app.git",
5
5
  "author": "Shopify",
6
6
  "license": "MIT",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify_app
3
3
  version: !ruby/object:Gem::Version
4
- version: 20.1.0
4
+ version: 20.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-22 00:00:00.000000000 Z
11
+ date: 2022-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activeresource
@@ -262,8 +262,11 @@ files:
262
262
  - ".github/PULL_REQUEST_TEMPLATE.md"
263
263
  - ".github/workflows/build.yml"
264
264
  - ".github/workflows/cla.yml"
265
+ - ".github/workflows/close-waiting-for-response-issues.yml"
265
266
  - ".github/workflows/release.yml"
267
+ - ".github/workflows/remove-labels-on-activity.yml"
266
268
  - ".github/workflows/rubocop.yml"
269
+ - ".github/workflows/stale.yml"
267
270
  - ".gitignore"
268
271
  - ".nvmrc"
269
272
  - ".rubocop.yml"
@@ -338,6 +341,7 @@ files:
338
341
  - docs/Troubleshooting.md
339
342
  - docs/Upgrading.md
340
343
  - docs/shopify_app/authentication.md
344
+ - docs/shopify_app/content-security-policy.md
341
345
  - docs/shopify_app/engine.md
342
346
  - docs/shopify_app/generators.md
343
347
  - docs/shopify_app/handling-access-scopes-changes.md
@@ -410,6 +414,7 @@ files:
410
414
  - lib/shopify_app/controller_concerns/sanitized_params.rb
411
415
  - lib/shopify_app/controller_concerns/webhook_verification.rb
412
416
  - lib/shopify_app/engine.rb
417
+ - lib/shopify_app/errors.rb
413
418
  - lib/shopify_app/jobs/scripttags_manager_job.rb
414
419
  - lib/shopify_app/jobs/webhooks_manager_job.rb
415
420
  - lib/shopify_app/managers/scripttags_manager.rb