shopify_app 20.1.1 → 20.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/close-waiting-for-response-issues.yml +20 -0
- data/.github/workflows/remove-labels-on-activity.yml +16 -0
- data/.github/workflows/stale.yml +12 -6
- data/CHANGELOG.md +10 -0
- data/Gemfile.lock +4 -4
- data/README.md +13 -24
- data/app/controllers/concerns/shopify_app/ensure_authenticated_links.rb +1 -1
- data/app/controllers/shopify_app/webhooks_controller.rb +0 -2
- data/docs/Quickstart.md +14 -7
- data/docs/Troubleshooting.md +5 -10
- data/docs/Upgrading.md +7 -0
- data/docs/shopify_app/authentication.md +5 -1
- data/docs/shopify_app/content-security-policy.md +10 -0
- data/docs/shopify_app/session-repository.md +3 -3
- data/lib/generators/shopify_app/home_controller/templates/index.html.erb +42 -38
- data/lib/shopify_app/access_scopes/user_strategy.rb +2 -3
- data/lib/shopify_app/controller_concerns/ensure_billing.rb +2 -13
- data/lib/shopify_app/controller_concerns/frame_ancestors.rb +2 -2
- data/lib/shopify_app/controller_concerns/login_protection.rb +2 -6
- data/lib/shopify_app/errors.rb +34 -0
- data/lib/shopify_app/managers/scripttags_manager.rb +1 -3
- data/lib/shopify_app/managers/webhooks_manager.rb +3 -4
- data/lib/shopify_app/session/in_memory_session_store.rb +1 -3
- data/lib/shopify_app/session/jwt.rb +8 -12
- data/lib/shopify_app/session/null_user_session_store.rb +1 -1
- data/lib/shopify_app/session/session_repository.rb +2 -3
- data/lib/shopify_app/session/user_session_storage.rb +1 -1
- data/lib/shopify_app/version.rb +1 -1
- data/lib/shopify_app.rb +3 -0
- data/package.json +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb6f3007d376f75a09be6f17e5dd378e4adc0a7ccc5feeac7ee18ecaf5469ee5
|
4
|
+
data.tar.gz: 62c9f9d55bf842fb2c20c4123555d0c1e3f234e11855825e8afeec2e38db971d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
|
data/.github/workflows/stale.yml
CHANGED
@@ -12,14 +12,20 @@ jobs:
|
|
12
12
|
steps:
|
13
13
|
- uses: actions/stale@v5
|
14
14
|
with:
|
15
|
-
days-before-issue-stale:
|
15
|
+
days-before-issue-stale: 90
|
16
16
|
days-before-issue-close: 14
|
17
|
-
stale-issue-label: "
|
17
|
+
stale-issue-label: "Stale"
|
18
18
|
stale-issue-message: >
|
19
|
-
This issue is stale because it has been open for
|
20
|
-
close-issue-message:
|
21
|
-
|
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!
|
22
28
|
days-before-pr-stale: -1
|
23
29
|
days-before-pr-close: -1
|
24
30
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
25
|
-
|
31
|
+
exempt-issue-labels: "feature request"
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,16 @@
|
|
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
|
+
|
4
14
|
20.1.1 (September 2, 2022)
|
5
15
|
----------
|
6
16
|
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
shopify_app (20.
|
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.
|
88
|
+
browser_sniffer (2.1.0)
|
89
89
|
builder (3.2.4)
|
90
90
|
byebug (11.1.3)
|
91
91
|
coderay (1.1.3)
|
@@ -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.
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
67
|
+
rails db:migrate
|
84
68
|
```
|
85
69
|
|
86
|
-
6.
|
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
|
-
|
75
|
+
rails server
|
90
76
|
```
|
91
77
|
|
92
|
-
|
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::
|
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)
|
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
|
-
[
|
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
|
-
##
|
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
|
-
|
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
|
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
|
-
```
|
20
|
-
#
|
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
|
-
|
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
|
|
data/docs/Troubleshooting.md
CHANGED
@@ -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
|
-
|
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
|
-
|
147
|
+
### I'm stuck in a redirect loop after OAuth
|
150
148
|
|
151
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
32
|
-
|
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
|
|
@@ -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? %>
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
<%
|
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,
|
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["
|
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 || "
|
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
|
@@ -194,12 +190,12 @@ module ShopifyApp
|
|
194
190
|
|
195
191
|
return shopify_domain if shopify_domain.present?
|
196
192
|
|
197
|
-
raise ShopifyDomainNotFound
|
193
|
+
raise ::ShopifyApp::ShopifyDomainNotFound
|
198
194
|
end
|
199
195
|
|
200
196
|
def return_address
|
201
197
|
return_address_with_params(shop: current_shopify_domain, host: host)
|
202
|
-
rescue ShopifyDomainNotFound, ShopifyHostNotFound
|
198
|
+
rescue ::ShopifyApp::ShopifyDomainNotFound, ::ShopifyApp::ShopifyHostNotFound
|
203
199
|
base_return_address
|
204
200
|
end
|
205
201
|
|
@@ -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
|
@@ -4,8 +4,6 @@ require "uri"
|
|
4
4
|
|
5
5
|
module ShopifyApp
|
6
6
|
class WebhooksManager
|
7
|
-
class CreationFailed < StandardError; end
|
8
|
-
|
9
7
|
class << self
|
10
8
|
def queue(shop_domain, shop_token)
|
11
9
|
ShopifyApp::WebhooksManagerJob.perform_later(
|
@@ -64,12 +62,13 @@ module ShopifyApp
|
|
64
62
|
elsif uri&.path&.present?
|
65
63
|
uri.path
|
66
64
|
else
|
67
|
-
raise ShopifyApp::MissingWebhookJobError,
|
65
|
+
raise ::ShopifyApp::MissingWebhookJobError,
|
66
|
+
"The :path attribute is required for webhook registration."
|
68
67
|
end
|
69
68
|
end
|
70
69
|
|
71
70
|
def webhook_job_klass(path)
|
72
|
-
webhook_job_klass_name(path).safe_constantize || raise(ShopifyApp::MissingWebhookJobError)
|
71
|
+
webhook_job_klass_name(path).safe_constantize || raise(::ShopifyApp::MissingWebhookJobError)
|
73
72
|
end
|
74
73
|
|
75
74
|
def webhook_job_klass_name(path)
|
@@ -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,
|
62
|
-
|
63
|
-
raise
|
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
|
@@ -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,
|
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
|
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!
|
data/lib/shopify_app/version.rb
CHANGED
data/lib/shopify_app.rb
CHANGED
data/package.json
CHANGED
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.
|
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-
|
11
|
+
date: 2022-10-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activeresource
|
@@ -262,7 +262,9 @@ 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"
|
267
269
|
- ".github/workflows/stale.yml"
|
268
270
|
- ".gitignore"
|
@@ -339,6 +341,7 @@ files:
|
|
339
341
|
- docs/Troubleshooting.md
|
340
342
|
- docs/Upgrading.md
|
341
343
|
- docs/shopify_app/authentication.md
|
344
|
+
- docs/shopify_app/content-security-policy.md
|
342
345
|
- docs/shopify_app/engine.md
|
343
346
|
- docs/shopify_app/generators.md
|
344
347
|
- docs/shopify_app/handling-access-scopes-changes.md
|
@@ -411,6 +414,7 @@ files:
|
|
411
414
|
- lib/shopify_app/controller_concerns/sanitized_params.rb
|
412
415
|
- lib/shopify_app/controller_concerns/webhook_verification.rb
|
413
416
|
- lib/shopify_app/engine.rb
|
417
|
+
- lib/shopify_app/errors.rb
|
414
418
|
- lib/shopify_app/jobs/scripttags_manager_job.rb
|
415
419
|
- lib/shopify_app/jobs/webhooks_manager_job.rb
|
416
420
|
- lib/shopify_app/managers/scripttags_manager.rb
|