@shopify/shopify-app-remix 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/build/cjs/server/authenticate/admin/authenticate.js +119 -501
  2. package/build/cjs/server/authenticate/admin/helpers/create-admin-api-context.js +18 -0
  3. package/build/cjs/server/authenticate/admin/helpers/ensure-app-is-embedded-if-required.js +27 -0
  4. package/build/cjs/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.js +25 -0
  5. package/build/cjs/server/authenticate/admin/helpers/redirect-to-bounce-page.js +22 -0
  6. package/build/cjs/server/authenticate/admin/helpers/redirect-to-shopify-or-app-root.js +22 -0
  7. package/build/cjs/server/authenticate/admin/helpers/trigger-after-auth-hook.js +21 -0
  8. package/build/cjs/server/authenticate/admin/helpers/validate-shop-and-host-params.js +32 -0
  9. package/build/cjs/server/authenticate/admin/strategies/auth-code-flow.js +307 -0
  10. package/build/cjs/server/authenticate/helpers/get-session-token-header.js +6 -0
  11. package/build/cjs/server/authenticate/helpers/reject-bot-request.js +2 -2
  12. package/build/cjs/server/authenticate/public/checkout/authenticate.js +1 -1
  13. package/build/cjs/server/authenticate/webhooks/authenticate.js +14 -6
  14. package/build/cjs/server/clients/admin/graphql.js +7 -16
  15. package/build/cjs/server/clients/storefront/factory.js +5 -12
  16. package/build/cjs/server/shopify-app.js +13 -6
  17. package/build/cjs/server/version.js +1 -1
  18. package/build/ts/server/__test-helpers/expect-admin-api-client.d.ts.map +1 -1
  19. package/build/ts/server/__test-helpers/expect-admin-api-client.js +2 -2
  20. package/build/ts/server/__test-helpers/expect-admin-api-client.js.map +1 -1
  21. package/build/ts/server/__test-helpers/expect-storefront-api-client.js +1 -1
  22. package/build/ts/server/__test-helpers/expect-storefront-api-client.js.map +1 -1
  23. package/build/ts/server/__test-helpers/request-mock.d.ts.map +1 -1
  24. package/build/ts/server/__test-helpers/request-mock.js +1 -1
  25. package/build/ts/server/__test-helpers/request-mock.js.map +1 -1
  26. package/build/ts/server/__test-helpers/test-config.d.ts +36 -7
  27. package/build/ts/server/__test-helpers/test-config.d.ts.map +1 -1
  28. package/build/ts/server/__test-helpers/test-config.js +34 -6
  29. package/build/ts/server/__test-helpers/test-config.js.map +1 -1
  30. package/build/ts/server/authenticate/admin/authenticate.d.ts +8 -25
  31. package/build/ts/server/authenticate/admin/authenticate.d.ts.map +1 -1
  32. package/build/ts/server/authenticate/admin/authenticate.js +124 -488
  33. package/build/ts/server/authenticate/admin/authenticate.js.map +1 -1
  34. package/build/ts/server/authenticate/admin/billing/types.d.ts +11 -3
  35. package/build/ts/server/authenticate/admin/billing/types.d.ts.map +1 -1
  36. package/build/ts/server/authenticate/admin/helpers/create-admin-api-context.d.ts +5 -0
  37. package/build/ts/server/authenticate/admin/helpers/create-admin-api-context.d.ts.map +1 -0
  38. package/build/ts/server/authenticate/admin/helpers/create-admin-api-context.js +16 -0
  39. package/build/ts/server/authenticate/admin/helpers/create-admin-api-context.js.map +1 -0
  40. package/build/ts/server/authenticate/admin/helpers/ensure-app-is-embedded-if-required.d.ts +3 -0
  41. package/build/ts/server/authenticate/admin/helpers/ensure-app-is-embedded-if-required.d.ts.map +1 -0
  42. package/build/ts/server/authenticate/admin/helpers/ensure-app-is-embedded-if-required.js +25 -0
  43. package/build/ts/server/authenticate/admin/helpers/ensure-app-is-embedded-if-required.js.map +1 -0
  44. package/build/ts/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.d.ts +3 -0
  45. package/build/ts/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.d.ts.map +1 -0
  46. package/build/ts/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.js +23 -0
  47. package/build/ts/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.js.map +1 -0
  48. package/build/ts/server/authenticate/admin/helpers/index.d.ts +7 -0
  49. package/build/ts/server/authenticate/admin/helpers/index.d.ts.map +1 -1
  50. package/build/ts/server/authenticate/admin/helpers/index.js +7 -0
  51. package/build/ts/server/authenticate/admin/helpers/index.js.map +1 -1
  52. package/build/ts/server/authenticate/admin/helpers/redirect-to-bounce-page.d.ts +3 -0
  53. package/build/ts/server/authenticate/admin/helpers/redirect-to-bounce-page.d.ts.map +1 -0
  54. package/build/ts/server/authenticate/admin/helpers/redirect-to-bounce-page.js +16 -0
  55. package/build/ts/server/authenticate/admin/helpers/redirect-to-bounce-page.js.map +1 -0
  56. package/build/ts/server/authenticate/admin/helpers/redirect-to-shopify-or-app-root.d.ts +3 -0
  57. package/build/ts/server/authenticate/admin/helpers/redirect-to-shopify-or-app-root.d.ts.map +1 -0
  58. package/build/ts/server/authenticate/admin/helpers/redirect-to-shopify-or-app-root.js +32 -0
  59. package/build/ts/server/authenticate/admin/helpers/redirect-to-shopify-or-app-root.js.map +1 -0
  60. package/build/ts/server/authenticate/admin/helpers/trigger-after-auth-hook.d.ts +4 -0
  61. package/build/ts/server/authenticate/admin/helpers/trigger-after-auth-hook.d.ts.map +1 -0
  62. package/build/ts/server/authenticate/admin/helpers/trigger-after-auth-hook.js +28 -0
  63. package/build/ts/server/authenticate/admin/helpers/trigger-after-auth-hook.js.map +1 -0
  64. package/build/ts/server/authenticate/admin/helpers/validate-shop-and-host-params.d.ts +3 -0
  65. package/build/ts/server/authenticate/admin/helpers/validate-shop-and-host-params.d.ts.map +1 -0
  66. package/build/ts/server/authenticate/admin/helpers/validate-shop-and-host-params.js +26 -0
  67. package/build/ts/server/authenticate/admin/helpers/validate-shop-and-host-params.js.map +1 -0
  68. package/build/ts/server/authenticate/admin/strategies/auth-code-flow.d.ts +22 -0
  69. package/build/ts/server/authenticate/admin/strategies/auth-code-flow.d.ts.map +1 -0
  70. package/build/ts/server/authenticate/admin/strategies/auth-code-flow.js +321 -0
  71. package/build/ts/server/authenticate/admin/strategies/auth-code-flow.js.map +1 -0
  72. package/build/ts/server/authenticate/admin/strategies/types.d.ts +11 -0
  73. package/build/ts/server/authenticate/admin/strategies/types.d.ts.map +1 -0
  74. package/build/ts/server/authenticate/admin/strategies/types.js +3 -0
  75. package/build/ts/server/authenticate/admin/strategies/types.js.map +1 -0
  76. package/build/ts/server/authenticate/admin/types.d.ts +4 -0
  77. package/build/ts/server/authenticate/admin/types.d.ts.map +1 -1
  78. package/build/ts/server/authenticate/helpers/get-session-token-header.d.ts +1 -0
  79. package/build/ts/server/authenticate/helpers/get-session-token-header.d.ts.map +1 -1
  80. package/build/ts/server/authenticate/helpers/get-session-token-header.js +7 -1
  81. package/build/ts/server/authenticate/helpers/get-session-token-header.js.map +1 -1
  82. package/build/ts/server/authenticate/helpers/reject-bot-request.d.ts +1 -1
  83. package/build/ts/server/authenticate/helpers/reject-bot-request.d.ts.map +1 -1
  84. package/build/ts/server/authenticate/helpers/reject-bot-request.js +3 -3
  85. package/build/ts/server/authenticate/helpers/reject-bot-request.js.map +1 -1
  86. package/build/ts/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.d.ts.map +1 -1
  87. package/build/ts/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.js +2 -2
  88. package/build/ts/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.js.map +1 -1
  89. package/build/ts/server/authenticate/public/appProxy/types.d.ts +6 -3
  90. package/build/ts/server/authenticate/public/appProxy/types.d.ts.map +1 -1
  91. package/build/ts/server/authenticate/public/checkout/authenticate.js +1 -1
  92. package/build/ts/server/authenticate/public/checkout/authenticate.js.map +1 -1
  93. package/build/ts/server/authenticate/webhooks/authenticate.d.ts.map +1 -1
  94. package/build/ts/server/authenticate/webhooks/authenticate.js +12 -2
  95. package/build/ts/server/authenticate/webhooks/authenticate.js.map +1 -1
  96. package/build/ts/server/authenticate/webhooks/types.d.ts +12 -5
  97. package/build/ts/server/authenticate/webhooks/types.d.ts.map +1 -1
  98. package/build/ts/server/clients/admin/graphql.d.ts +3 -3
  99. package/build/ts/server/clients/admin/graphql.d.ts.map +1 -1
  100. package/build/ts/server/clients/admin/graphql.js +6 -9
  101. package/build/ts/server/clients/admin/graphql.js.map +1 -1
  102. package/build/ts/server/clients/admin/types.d.ts +53 -11
  103. package/build/ts/server/clients/admin/types.d.ts.map +1 -1
  104. package/build/ts/server/clients/storefront/factory.d.ts.map +1 -1
  105. package/build/ts/server/clients/storefront/factory.js +5 -8
  106. package/build/ts/server/clients/storefront/factory.js.map +1 -1
  107. package/build/ts/server/clients/storefront/types.d.ts +6 -5
  108. package/build/ts/server/clients/storefront/types.d.ts.map +1 -1
  109. package/build/ts/server/clients/types.d.ts +4 -7
  110. package/build/ts/server/clients/types.d.ts.map +1 -1
  111. package/build/ts/server/config-types.d.ts +5 -5
  112. package/build/ts/server/config-types.d.ts.map +1 -1
  113. package/build/ts/server/future/flags.d.ts.map +1 -1
  114. package/build/ts/server/shopify-app.d.ts +1 -0
  115. package/build/ts/server/shopify-app.d.ts.map +1 -1
  116. package/build/ts/server/shopify-app.js +6 -3
  117. package/build/ts/server/shopify-app.js.map +1 -1
  118. package/build/ts/server/types.d.ts +3 -2
  119. package/build/ts/server/types.d.ts.map +1 -1
  120. package/build/ts/server/version.d.ts +1 -1
  121. package/build/ts/server/version.js +1 -1
  122. package/package.json +12 -10
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var redirectToShopifyOrAppRoot = require('./redirect-to-shopify-or-app-root.js');
6
+
7
+ const ensureAppIsEmbeddedIfRequired = async (params, request) => {
8
+ const {
9
+ api,
10
+ logger,
11
+ config
12
+ } = params;
13
+ const url = new URL(request.url);
14
+ const shop = url.searchParams.get('shop');
15
+ if (api.config.isEmbeddedApp && url.searchParams.get('embedded') !== '1') {
16
+ logger.debug('App is not embedded, redirecting to Shopify', {
17
+ shop
18
+ });
19
+ await redirectToShopifyOrAppRoot.redirectToShopifyOrAppRoot(request, {
20
+ api,
21
+ logger,
22
+ config
23
+ });
24
+ }
25
+ };
26
+
27
+ exports.ensureAppIsEmbeddedIfRequired = ensureAppIsEmbeddedIfRequired;
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var redirectToBouncePage = require('./redirect-to-bounce-page.js');
6
+
7
+ const SESSION_TOKEN_PARAM = 'id_token';
8
+ const ensureSessionTokenSearchParamIfRequired = async (params, request) => {
9
+ const {
10
+ api,
11
+ logger
12
+ } = params;
13
+ const url = new URL(request.url);
14
+ const shop = url.searchParams.get('shop');
15
+ const searchParamSessionToken = url.searchParams.get(SESSION_TOKEN_PARAM);
16
+ const isEmbedded = url.searchParams.get('embedded') === '1';
17
+ if (api.config.isEmbeddedApp && isEmbedded && !searchParamSessionToken) {
18
+ logger.debug('Missing session token in search params, going to bounce page', {
19
+ shop
20
+ });
21
+ redirectToBouncePage.redirectToBouncePage(params, url);
22
+ }
23
+ };
24
+
25
+ exports.ensureSessionTokenSearchParamIfRequired = ensureSessionTokenSearchParamIfRequired;
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var serverRuntime = require('@remix-run/server-runtime');
6
+
7
+ const redirectToBouncePage = (params, url) => {
8
+ const {
9
+ config
10
+ } = params;
11
+
12
+ // Make sure we always point to the configured app URL so it also works behind reverse proxies (that alter the Host
13
+ // header).
14
+ url.searchParams.set('shopify-reload', `${config.appUrl}${url.pathname}${url.search}`);
15
+
16
+ // eslint-disable-next-line no-warning-comments
17
+ // TODO Make sure this works on chrome without a tunnel (weird HTTPS redirect issue)
18
+ // https://github.com/orgs/Shopify/projects/6899/views/1?pane=issue&itemId=28376650
19
+ throw serverRuntime.redirect(`${config.auth.patchSessionTokenPath}${url.search}`);
20
+ };
21
+
22
+ exports.redirectToBouncePage = redirectToBouncePage;
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var serverRuntime = require('@remix-run/server-runtime');
6
+
7
+ async function redirectToShopifyOrAppRoot(request, params, responseHeaders) {
8
+ const {
9
+ api
10
+ } = params;
11
+ const url = new URL(request.url);
12
+ const host = api.utils.sanitizeHost(url.searchParams.get('host'));
13
+ const shop = api.utils.sanitizeShop(url.searchParams.get('shop'));
14
+ const redirectUrl = api.config.isEmbeddedApp ? await api.auth.getEmbeddedAppUrl({
15
+ rawRequest: request
16
+ }) : `/?shop=${shop}&host=${encodeURIComponent(host)}`;
17
+ throw serverRuntime.redirect(redirectUrl, {
18
+ headers: responseHeaders
19
+ });
20
+ }
21
+
22
+ exports.redirectToShopifyOrAppRoot = redirectToShopifyOrAppRoot;
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var createAdminApiContext = require('./create-admin-api-context.js');
6
+
7
+ async function triggerAfterAuthHook(params, session, request) {
8
+ const {
9
+ config,
10
+ logger
11
+ } = params;
12
+ if (config.hooks.afterAuth) {
13
+ logger.info('Running afterAuth hook');
14
+ await config.hooks.afterAuth({
15
+ session,
16
+ admin: createAdminApiContext.createAdminApiContext(request, session, params)
17
+ });
18
+ }
19
+ }
20
+
21
+ exports.triggerAfterAuthHook = triggerAfterAuthHook;
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var serverRuntime = require('@remix-run/server-runtime');
6
+
7
+ function validateShopAndHostParams(params, request) {
8
+ const {
9
+ api,
10
+ config,
11
+ logger
12
+ } = params;
13
+ if (config.isEmbeddedApp) {
14
+ const url = new URL(request.url);
15
+ const shop = api.utils.sanitizeShop(url.searchParams.get('shop'));
16
+ if (!shop) {
17
+ logger.debug('Missing or invalid shop, redirecting to login path', {
18
+ shop
19
+ });
20
+ throw serverRuntime.redirect(config.auth.loginPath);
21
+ }
22
+ const host = api.utils.sanitizeHost(url.searchParams.get('host'));
23
+ if (!host) {
24
+ logger.debug('Invalid host, redirecting to login path', {
25
+ host: url.searchParams.get('host')
26
+ });
27
+ throw serverRuntime.redirect(config.auth.loginPath);
28
+ }
29
+ }
30
+ }
31
+
32
+ exports.validateShopAndHostParams = validateShopAndHostParams;
@@ -0,0 +1,307 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var shopifyApi = require('@shopify/shopify-api');
6
+ var beginAuth = require('../helpers/begin-auth.js');
7
+ var redirectToAuthPage = require('../helpers/redirect-to-auth-page.js');
8
+ var redirectToShopifyOrAppRoot = require('../helpers/redirect-to-shopify-or-app-root.js');
9
+ require('@remix-run/server-runtime');
10
+ var redirectWithExitiframe = require('../helpers/redirect-with-exitiframe.js');
11
+ var getSessionTokenHeader = require('../../helpers/get-session-token-header.js');
12
+ var triggerAfterAuthHook = require('../helpers/trigger-after-auth-hook.js');
13
+ var validateShopAndHostParams = require('../helpers/validate-shop-and-host-params.js');
14
+ require('isbot');
15
+
16
+ class AuthCodeFlowStrategy {
17
+ constructor({
18
+ api,
19
+ config,
20
+ logger
21
+ }) {
22
+ this.api = void 0;
23
+ this.config = void 0;
24
+ this.logger = void 0;
25
+ this.api = api;
26
+ this.config = config;
27
+ this.logger = logger;
28
+ }
29
+ async respondToOAuthRequests(request) {
30
+ const {
31
+ api,
32
+ config
33
+ } = this;
34
+ const url = new URL(request.url);
35
+ const isAuthRequest = url.pathname === config.auth.path;
36
+ const isAuthCallbackRequest = url.pathname === config.auth.callbackPath;
37
+ if (isAuthRequest || isAuthCallbackRequest) {
38
+ const shop = api.utils.sanitizeShop(url.searchParams.get('shop'));
39
+ if (!shop) throw new Response('Shop param is invalid', {
40
+ status: 400
41
+ });
42
+ if (isAuthRequest) {
43
+ throw await this.handleAuthBeginRequest(request, shop);
44
+ } else {
45
+ throw await this.handleAuthCallbackRequest(request, shop);
46
+ }
47
+ }
48
+ if (!getSessionTokenHeader.getSessionTokenHeader(request)) {
49
+ // This is a document request that doesn't contain a session token. We check if the app is installed.
50
+ // If the app isn't installed, we initiate the OAuth auth code flow.
51
+ // Requests with a header can only happen after the app is installed.
52
+ await this.ensureInstalledOnShop(request);
53
+ }
54
+ }
55
+ async authenticate(request, session, shop) {
56
+ const {
57
+ api,
58
+ config,
59
+ logger
60
+ } = this;
61
+ if (!session) {
62
+ logger.debug('No session found, redirecting to OAuth', {
63
+ shop
64
+ });
65
+ await redirectToAuthPage.redirectToAuthPage({
66
+ config,
67
+ logger,
68
+ api
69
+ }, request, shop);
70
+ } else if (!session.isActive(config.scopes)) {
71
+ logger.debug('Found a session, but it has expired, redirecting to OAuth', {
72
+ shop
73
+ });
74
+ await redirectToAuthPage.redirectToAuthPage({
75
+ config,
76
+ logger,
77
+ api
78
+ }, request, shop);
79
+ }
80
+ logger.debug('Found a valid session', {
81
+ shop
82
+ });
83
+ return session;
84
+ }
85
+ async ensureInstalledOnShop(request) {
86
+ const {
87
+ api,
88
+ config,
89
+ logger
90
+ } = this;
91
+ validateShopAndHostParams.validateShopAndHostParams({
92
+ api,
93
+ config,
94
+ logger
95
+ }, request);
96
+ const url = new URL(request.url);
97
+ let shop = url.searchParams.get('shop');
98
+
99
+ // Ensure app is installed
100
+ logger.debug('Ensuring app is installed on shop', {
101
+ shop
102
+ });
103
+ if (!(await this.hasValidOfflineId(request))) {
104
+ logger.info("Could not find a shop, can't authenticate request");
105
+ throw new Response(undefined, {
106
+ status: 400,
107
+ statusText: 'Bad Request'
108
+ });
109
+ }
110
+ const offlineSession = await this.getOfflineSession(request);
111
+ const isEmbedded = url.searchParams.get('embedded') === '1';
112
+ if (!offlineSession) {
113
+ logger.info("Shop hasn't installed app yet, redirecting to OAuth", {
114
+ shop
115
+ });
116
+ if (isEmbedded) {
117
+ redirectWithExitiframe.redirectWithExitIframe({
118
+ api,
119
+ config,
120
+ logger
121
+ }, request, shop);
122
+ } else {
123
+ throw await beginAuth.beginAuth({
124
+ api,
125
+ config,
126
+ logger
127
+ }, request, false, shop);
128
+ }
129
+ }
130
+ shop = shop || offlineSession.shop;
131
+ if (config.isEmbeddedApp && !isEmbedded) {
132
+ try {
133
+ logger.debug('Ensuring offline session is valid before embedding', {
134
+ shop
135
+ });
136
+ await this.testSession(offlineSession);
137
+ logger.debug('Offline session is still valid, embedding app', {
138
+ shop
139
+ });
140
+ } catch (error) {
141
+ await this.handleInvalidOfflineSession(error, request, shop);
142
+ }
143
+ }
144
+ }
145
+ async handleAuthBeginRequest(request, shop) {
146
+ const {
147
+ api,
148
+ config,
149
+ logger
150
+ } = this;
151
+ logger.info('Handling OAuth begin request', {
152
+ shop
153
+ });
154
+
155
+ // If we're loading from an iframe, we need to break out of it
156
+ if (config.isEmbeddedApp && request.headers.get('Sec-Fetch-Dest') === 'iframe') {
157
+ logger.debug('Auth request in iframe detected, exiting iframe', {
158
+ shop
159
+ });
160
+ throw redirectWithExitiframe.redirectWithExitIframe({
161
+ api,
162
+ config,
163
+ logger
164
+ }, request, shop);
165
+ } else {
166
+ throw await beginAuth.beginAuth({
167
+ api,
168
+ config,
169
+ logger
170
+ }, request, false, shop);
171
+ }
172
+ }
173
+ async handleAuthCallbackRequest(request, shop) {
174
+ const {
175
+ api,
176
+ config,
177
+ logger
178
+ } = this;
179
+ logger.info('Handling OAuth callback request');
180
+ try {
181
+ const {
182
+ session,
183
+ headers: responseHeaders
184
+ } = await api.auth.callback({
185
+ rawRequest: request
186
+ });
187
+ await config.sessionStorage.storeSession(session);
188
+ if (config.useOnlineTokens && !session.isOnline) {
189
+ logger.info('Requesting online access token for offline session');
190
+ await beginAuth.beginAuth({
191
+ api,
192
+ config,
193
+ logger
194
+ }, request, true, shop);
195
+ }
196
+ await triggerAfterAuthHook.triggerAfterAuthHook({
197
+ api,
198
+ config,
199
+ logger
200
+ }, session, request);
201
+ throw await redirectToShopifyOrAppRoot.redirectToShopifyOrAppRoot(request, {
202
+ api,
203
+ config,
204
+ logger
205
+ }, responseHeaders);
206
+ } catch (error) {
207
+ if (error instanceof Response) throw error;
208
+ throw await this.oauthCallbackError(error, request, shop);
209
+ }
210
+ }
211
+ async getOfflineSession(request) {
212
+ const offlineId = await this.getOfflineSessionId(request);
213
+ return this.config.sessionStorage.loadSession(offlineId);
214
+ }
215
+ async hasValidOfflineId(request) {
216
+ return Boolean(await this.getOfflineSessionId(request));
217
+ }
218
+ async getOfflineSessionId(request) {
219
+ const {
220
+ api
221
+ } = this;
222
+ const url = new URL(request.url);
223
+ const shop = url.searchParams.get('shop');
224
+ return shop ? api.session.getOfflineId(shop) : api.session.getCurrentId({
225
+ isOnline: false,
226
+ rawRequest: request
227
+ });
228
+ }
229
+ async testSession(session) {
230
+ const {
231
+ api
232
+ } = this;
233
+ const client = new api.clients.Graphql({
234
+ session
235
+ });
236
+ await client.request(`#graphql
237
+ query shopifyAppShopName {
238
+ shop {
239
+ name
240
+ }
241
+ }
242
+ `);
243
+ }
244
+ async oauthCallbackError(error, request, shop) {
245
+ const {
246
+ logger
247
+ } = this;
248
+ logger.error('Error during OAuth callback', {
249
+ error: error.message
250
+ });
251
+ if (error instanceof shopifyApi.CookieNotFound) {
252
+ return this.handleAuthBeginRequest(request, shop);
253
+ }
254
+ if (error instanceof shopifyApi.InvalidHmacError || error instanceof shopifyApi.InvalidOAuthError) {
255
+ return new Response(undefined, {
256
+ status: 400,
257
+ statusText: 'Invalid OAuth Request'
258
+ });
259
+ }
260
+ return new Response(undefined, {
261
+ status: 500,
262
+ statusText: 'Internal Server Error'
263
+ });
264
+ }
265
+ async handleInvalidOfflineSession(error, request, shop) {
266
+ const {
267
+ api,
268
+ logger,
269
+ config
270
+ } = this;
271
+ if (error instanceof shopifyApi.HttpResponseError) {
272
+ if (error.response.code === 401) {
273
+ logger.info('Shop session is no longer valid, redirecting to OAuth', {
274
+ shop
275
+ });
276
+ throw await beginAuth.beginAuth({
277
+ api,
278
+ config,
279
+ logger
280
+ }, request, false, shop);
281
+ } else {
282
+ const message = JSON.stringify(error.response.body, null, 2);
283
+ logger.error(`Unexpected error during session validation: ${message}`, {
284
+ shop
285
+ });
286
+ throw new Response(undefined, {
287
+ status: error.response.code,
288
+ statusText: error.response.statusText
289
+ });
290
+ }
291
+ } else if (error instanceof shopifyApi.GraphqlQueryError) {
292
+ const context = {
293
+ shop
294
+ };
295
+ if (error.response) {
296
+ context.response = JSON.stringify(error.response);
297
+ }
298
+ logger.error(`Unexpected error during session validation: ${error.message}`, context);
299
+ throw new Response(undefined, {
300
+ status: 500,
301
+ statusText: 'Internal Server Error'
302
+ });
303
+ }
304
+ }
305
+ }
306
+
307
+ exports.AuthCodeFlowStrategy = AuthCodeFlowStrategy;
@@ -2,9 +2,15 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ const SESSION_TOKEN_PARAM = 'id_token';
5
6
  function getSessionTokenHeader(request) {
6
7
  var _request$headers$get;
7
8
  return (_request$headers$get = request.headers.get('authorization')) === null || _request$headers$get === void 0 ? void 0 : _request$headers$get.replace('Bearer ', '');
8
9
  }
10
+ function getSessionTokenFromUrlParam(request) {
11
+ const url = new URL(request.url);
12
+ return url.searchParams.get(SESSION_TOKEN_PARAM);
13
+ }
9
14
 
15
+ exports.getSessionTokenFromUrlParam = getSessionTokenFromUrlParam;
10
16
  exports.getSessionTokenHeader = getSessionTokenHeader;
@@ -8,7 +8,7 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
8
8
 
9
9
  var isbot__default = /*#__PURE__*/_interopDefaultLegacy(isbot);
10
10
 
11
- function rejectBotRequest({
11
+ function respondToBotRequest({
12
12
  logger
13
13
  }, request) {
14
14
  if (isbot__default["default"](request.headers.get('User-Agent'))) {
@@ -20,4 +20,4 @@ function rejectBotRequest({
20
20
  }
21
21
  }
22
22
 
23
- exports.rejectBotRequest = rejectBotRequest;
23
+ exports.respondToBotRequest = respondToBotRequest;
@@ -15,7 +15,7 @@ function authenticateCheckoutFactory(params) {
15
15
  logger
16
16
  } = params;
17
17
  const corsHeaders = (_options$corsHeaders = options.corsHeaders) !== null && _options$corsHeaders !== void 0 ? _options$corsHeaders : [];
18
- rejectBotRequest.rejectBotRequest(params, request);
18
+ rejectBotRequest.respondToBotRequest(params, request);
19
19
  respondToOptionsRequest.respondToOptionsRequest(params, request, corsHeaders);
20
20
  const sessionTokenHeader = getSessionTokenHeader.getSessionTokenHeader(request);
21
21
  logger.info('Authenticating checkout request');
@@ -2,8 +2,8 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var shopifyApi = require('@shopify/shopify-api');
5
6
  var factory = require('../../clients/admin/factory.js');
6
- require('@shopify/shopify-api/runtime');
7
7
  var handleClientError = require('../admin/helpers/handle-client-error.js');
8
8
  require('@remix-run/server-runtime');
9
9
 
@@ -30,11 +30,19 @@ function authenticateWebhookFactory(params) {
30
30
  rawRequest: request
31
31
  });
32
32
  if (!check.valid) {
33
- logger.debug('Webhook validation failed', check);
34
- throw new Response(undefined, {
35
- status: 400,
36
- statusText: 'Bad Request'
37
- });
33
+ if (check.reason === shopifyApi.WebhookValidationErrorReason.InvalidHmac) {
34
+ logger.debug('Webhook HMAC validation failed', check);
35
+ throw new Response(undefined, {
36
+ status: 401,
37
+ statusText: 'Unauthorized'
38
+ });
39
+ } else {
40
+ logger.debug('Webhook validation failed', check);
41
+ throw new Response(undefined, {
42
+ status: 400,
43
+ statusText: 'Bad Request'
44
+ });
45
+ }
38
46
  }
39
47
  const sessionId = api.session.getOfflineId(check.domain);
40
48
  const session = await config.sessionStorage.loadSession(sessionId);
@@ -2,8 +2,6 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var runtime = require('@shopify/shopify-api/runtime');
6
-
7
5
  // eslint-disable-next-line no-warning-comments
8
6
  // TODO: This is actually just a call through to the Shopify API client, but with a different API. We should eventually
9
7
  // move this over to the library layer. While doing that, we should also allow the apiVersion to be passed into the REST
@@ -13,26 +11,19 @@ function graphqlClientFactory({
13
11
  handleClientError,
14
12
  session
15
13
  }) {
16
- return async function query(query, options) {
17
- var _options;
14
+ return async function query(operation, options) {
18
15
  const client = new params.api.clients.Graphql({
19
16
  session,
20
- apiVersion: (_options = options) === null || _options === void 0 ? void 0 : _options.apiVersion
17
+ apiVersion: options === null || options === void 0 ? void 0 : options.apiVersion
21
18
  });
22
19
  try {
23
- var _options2, _options3, _options4;
24
20
  // We convert the incoming response to a Response object to bring this client closer to the Remix client.
25
- const apiResponse = await client.query({
26
- data: {
27
- query,
28
- variables: (_options2 = options) === null || _options2 === void 0 ? void 0 : _options2.variables
29
- },
30
- tries: (_options3 = options) === null || _options3 === void 0 ? void 0 : _options3.tries,
31
- extraHeaders: (_options4 = options) === null || _options4 === void 0 ? void 0 : _options4.headers
32
- });
33
- return new Response(JSON.stringify(apiResponse.body), {
34
- headers: runtime.flatHeaders(apiResponse.headers)
21
+ const apiResponse = await client.request(operation, {
22
+ variables: options === null || options === void 0 ? void 0 : options.variables,
23
+ retries: options !== null && options !== void 0 && options.tries ? options.tries - 1 : 0,
24
+ headers: options === null || options === void 0 ? void 0 : options.headers
35
25
  });
26
+ return new Response(JSON.stringify(apiResponse));
36
27
  } catch (error) {
37
28
  if (handleClientError) {
38
29
  throw await handleClientError({
@@ -2,8 +2,6 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var runtime = require('@shopify/shopify-api/runtime');
6
-
7
5
  function storefrontClientFactory({
8
6
  params,
9
7
  session
@@ -17,17 +15,12 @@ function storefrontClientFactory({
17
15
  session,
18
16
  apiVersion: options.apiVersion
19
17
  });
20
- const apiResponse = await client.query({
21
- data: {
22
- query,
23
- variables: options === null || options === void 0 ? void 0 : options.variables
24
- },
25
- tries: options.tries,
26
- extraHeaders: options.headers
27
- });
28
- return new Response(JSON.stringify(apiResponse.body), {
29
- headers: runtime.flatHeaders(apiResponse.headers)
18
+ const apiResponse = await client.request(query, {
19
+ variables: options === null || options === void 0 ? void 0 : options.variables,
20
+ retries: options !== null && options !== void 0 && options.tries ? options.tries - 1 : 0,
21
+ headers: options === null || options === void 0 ? void 0 : options.headers
30
22
  });
23
+ return new Response(JSON.stringify(apiResponse));
31
24
  }
32
25
  };
33
26
  }
@@ -7,8 +7,8 @@ var shopifyApi = require('@shopify/shopify-api');
7
7
  var types = require('./types.js');
8
8
  var version = require('./version.js');
9
9
  var register = require('./authenticate/webhooks/register.js');
10
- var authenticate$1 = require('./authenticate/admin/authenticate.js');
11
- var authenticate = require('./authenticate/webhooks/authenticate.js');
10
+ var authenticate = require('./authenticate/admin/authenticate.js');
11
+ var authenticate$1 = require('./authenticate/webhooks/authenticate.js');
12
12
  var overrideLogger = require('./override-logger.js');
13
13
  var addResponseHeaders = require('./authenticate/helpers/add-response-headers.js');
14
14
  require('isbot');
@@ -16,6 +16,7 @@ var login = require('./authenticate/login/login.js');
16
16
  var factory$1 = require('./unauthenticated/admin/factory.js');
17
17
  var factory = require('./authenticate/public/factory.js');
18
18
  var factory$2 = require('./unauthenticated/storefront/factory.js');
19
+ var authCodeFlow = require('./authenticate/admin/strategies/auth-code-flow.js');
19
20
 
20
21
  /**
21
22
  * Creates an object your app will use to interact with Shopify.
@@ -26,6 +27,7 @@ var factory$2 = require('./unauthenticated/storefront/factory.js');
26
27
  * @example
27
28
  * <caption>The minimum viable configuration</caption>
28
29
  * ```ts
30
+ * // /shopify.server.ts
29
31
  * import { shopifyApp } from "@shopify/shopify-app-remix/server";
30
32
  *
31
33
  * const shopify = shopifyApp({
@@ -49,15 +51,19 @@ function shopifyApp(appConfig) {
49
51
  config,
50
52
  logger
51
53
  };
52
- const oauth = new authenticate$1.AuthStrategy(params);
54
+ const oauth = new authCodeFlow.AuthCodeFlowStrategy(params);
55
+ const authStrategy = authenticate.authStrategyFactory({
56
+ ...params,
57
+ strategy: oauth
58
+ });
53
59
  const shopify = {
54
60
  sessionStorage: config.sessionStorage,
55
61
  addDocumentResponseHeaders: addResponseHeaders.addDocumentResponseHeadersFactory(params),
56
62
  registerWebhooks: register.registerWebhooksFactory(params),
57
63
  authenticate: {
58
- admin: oauth.authenticateAdmin.bind(oauth),
64
+ admin: authStrategy,
59
65
  public: factory.authenticatePublicFactory(params),
60
- webhook: authenticate.authenticateWebhookFactory(params)
66
+ webhook: authenticate$1.authenticateWebhookFactory(params)
61
67
  },
62
68
  unauthenticated: {
63
69
  admin: factory$1.unauthenticatedAdminContextFactory(params),
@@ -101,7 +107,8 @@ function deriveApi(appConfig) {
101
107
  userAgentPrefix,
102
108
  isEmbeddedApp: (_appConfig$isEmbedded = appConfig.isEmbeddedApp) !== null && _appConfig$isEmbedded !== void 0 ? _appConfig$isEmbedded : true,
103
109
  apiVersion: (_appConfig$apiVersion = appConfig.apiVersion) !== null && _appConfig$apiVersion !== void 0 ? _appConfig$apiVersion : shopifyApi.LATEST_API_VERSION,
104
- isCustomStoreApp: appConfig.distribution === types.AppDistribution.ShopifyAdmin
110
+ isCustomStoreApp: appConfig.distribution === types.AppDistribution.ShopifyAdmin,
111
+ future: {}
105
112
  });
106
113
  }
107
114
  function deriveConfig(appConfig, apiConfig) {
@@ -2,6 +2,6 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- const SHOPIFY_REMIX_LIBRARY_VERSION = '2.1.0';
5
+ const SHOPIFY_REMIX_LIBRARY_VERSION = '2.3.0';
6
6
 
7
7
  exports.SHOPIFY_REMIX_LIBRARY_VERSION = SHOPIFY_REMIX_LIBRARY_VERSION;