@shopify/shopify-app-remix 1.0.0-rc.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 (161) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +373 -0
  3. package/build/cjs/adapters/node/index.js +13 -0
  4. package/build/cjs/auth/admin/authenticate.js +599 -0
  5. package/build/cjs/auth/admin/graphql-client.js +57 -0
  6. package/build/cjs/auth/admin/rest-client.js +124 -0
  7. package/build/cjs/auth/const.js +17 -0
  8. package/build/cjs/auth/helpers/add-response-headers.js +69 -0
  9. package/build/cjs/auth/helpers/begin-auth.js +18 -0
  10. package/build/cjs/auth/helpers/get-session-token-header.js +10 -0
  11. package/build/cjs/auth/helpers/handle-client-error.js +37 -0
  12. package/build/cjs/auth/helpers/redirect-to-auth-page.js +22 -0
  13. package/build/cjs/auth/helpers/redirect-with-app-bridge-headers.js +31 -0
  14. package/build/cjs/auth/helpers/redirect-with-exitiframe.js +23 -0
  15. package/build/cjs/auth/helpers/reject-bot-request.js +23 -0
  16. package/build/cjs/auth/helpers/validate-session-token.js +25 -0
  17. package/build/cjs/auth/login/login.js +54 -0
  18. package/build/cjs/auth/public/authenticate.js +32 -0
  19. package/build/cjs/auth/webhooks/authenticate.js +68 -0
  20. package/build/cjs/auth/webhooks/register.js +40 -0
  21. package/build/cjs/billing/cancel.js +40 -0
  22. package/build/cjs/billing/request.js +83 -0
  23. package/build/cjs/billing/require.js +47 -0
  24. package/build/cjs/index.js +140 -0
  25. package/build/cjs/override-logger.js +55 -0
  26. package/build/cjs/types.js +25 -0
  27. package/build/cjs/version.js +7 -0
  28. package/build/ts/adapters/node/index.d.ts +2 -0
  29. package/build/ts/adapters/node/index.d.ts.map +1 -0
  30. package/build/ts/adapters/node/index.js +10 -0
  31. package/build/ts/adapters/node/index.js.map +1 -0
  32. package/build/ts/auth/admin/authenticate.d.ts +32 -0
  33. package/build/ts/auth/admin/authenticate.d.ts.map +1 -0
  34. package/build/ts/auth/admin/authenticate.js +571 -0
  35. package/build/ts/auth/admin/authenticate.js.map +1 -0
  36. package/build/ts/auth/admin/graphql-client.d.ts +22 -0
  37. package/build/ts/auth/admin/graphql-client.d.ts.map +1 -0
  38. package/build/ts/auth/admin/graphql-client.js +53 -0
  39. package/build/ts/auth/admin/graphql-client.js.map +1 -0
  40. package/build/ts/auth/admin/rest-client.d.ts +34 -0
  41. package/build/ts/auth/admin/rest-client.d.ts.map +1 -0
  42. package/build/ts/auth/admin/rest-client.js +134 -0
  43. package/build/ts/auth/admin/rest-client.js.map +1 -0
  44. package/build/ts/auth/admin/types.d.ts +100 -0
  45. package/build/ts/auth/admin/types.d.ts.map +1 -0
  46. package/build/ts/auth/admin/types.js +3 -0
  47. package/build/ts/auth/admin/types.js.map +1 -0
  48. package/build/ts/auth/const.d.ts +9 -0
  49. package/build/ts/auth/const.d.ts.map +1 -0
  50. package/build/ts/auth/const.js +12 -0
  51. package/build/ts/auth/const.js.map +1 -0
  52. package/build/ts/auth/helpers/add-response-headers.d.ts +5 -0
  53. package/build/ts/auth/helpers/add-response-headers.d.ts.map +1 -0
  54. package/build/ts/auth/helpers/add-response-headers.js +60 -0
  55. package/build/ts/auth/helpers/add-response-headers.js.map +1 -0
  56. package/build/ts/auth/helpers/begin-auth.d.ts +3 -0
  57. package/build/ts/auth/helpers/begin-auth.d.ts.map +1 -0
  58. package/build/ts/auth/helpers/begin-auth.js +24 -0
  59. package/build/ts/auth/helpers/begin-auth.js.map +1 -0
  60. package/build/ts/auth/helpers/get-session-token-header.d.ts +2 -0
  61. package/build/ts/auth/helpers/get-session-token-header.d.ts.map +1 -0
  62. package/build/ts/auth/helpers/get-session-token-header.js +9 -0
  63. package/build/ts/auth/helpers/get-session-token-header.js.map +1 -0
  64. package/build/ts/auth/helpers/handle-client-error.d.ts +11 -0
  65. package/build/ts/auth/helpers/handle-client-error.d.ts.map +1 -0
  66. package/build/ts/auth/helpers/handle-client-error.js +40 -0
  67. package/build/ts/auth/helpers/handle-client-error.js.map +1 -0
  68. package/build/ts/auth/helpers/index.d.ts +8 -0
  69. package/build/ts/auth/helpers/index.d.ts.map +1 -0
  70. package/build/ts/auth/helpers/index.js +18 -0
  71. package/build/ts/auth/helpers/index.js.map +1 -0
  72. package/build/ts/auth/helpers/redirect-to-auth-page.d.ts +3 -0
  73. package/build/ts/auth/helpers/redirect-to-auth-page.d.ts.map +1 -0
  74. package/build/ts/auth/helpers/redirect-to-auth-page.js +33 -0
  75. package/build/ts/auth/helpers/redirect-to-auth-page.js.map +1 -0
  76. package/build/ts/auth/helpers/redirect-with-app-bridge-headers.d.ts +4 -0
  77. package/build/ts/auth/helpers/redirect-with-app-bridge-headers.d.ts.map +1 -0
  78. package/build/ts/auth/helpers/redirect-with-app-bridge-headers.js +24 -0
  79. package/build/ts/auth/helpers/redirect-with-app-bridge-headers.js.map +1 -0
  80. package/build/ts/auth/helpers/redirect-with-exitiframe.d.ts +3 -0
  81. package/build/ts/auth/helpers/redirect-with-exitiframe.d.ts.map +1 -0
  82. package/build/ts/auth/helpers/redirect-with-exitiframe.js +18 -0
  83. package/build/ts/auth/helpers/redirect-with-exitiframe.js.map +1 -0
  84. package/build/ts/auth/helpers/reject-bot-request.d.ts +3 -0
  85. package/build/ts/auth/helpers/reject-bot-request.d.ts.map +1 -0
  86. package/build/ts/auth/helpers/reject-bot-request.js +14 -0
  87. package/build/ts/auth/helpers/reject-bot-request.js.map +1 -0
  88. package/build/ts/auth/helpers/validate-session-token.d.ts +4 -0
  89. package/build/ts/auth/helpers/validate-session-token.d.ts.map +1 -0
  90. package/build/ts/auth/helpers/validate-session-token.js +36 -0
  91. package/build/ts/auth/helpers/validate-session-token.js.map +1 -0
  92. package/build/ts/auth/login/login.d.ts +3 -0
  93. package/build/ts/auth/login/login.d.ts.map +1 -0
  94. package/build/ts/auth/login/login.js +56 -0
  95. package/build/ts/auth/login/login.js.map +1 -0
  96. package/build/ts/auth/public/authenticate.d.ts +4 -0
  97. package/build/ts/auth/public/authenticate.d.ts.map +1 -0
  98. package/build/ts/auth/public/authenticate.js +35 -0
  99. package/build/ts/auth/public/authenticate.js.map +1 -0
  100. package/build/ts/auth/public/types.d.ts +27 -0
  101. package/build/ts/auth/public/types.d.ts.map +1 -0
  102. package/build/ts/auth/public/types.js +3 -0
  103. package/build/ts/auth/public/types.js.map +1 -0
  104. package/build/ts/auth/webhooks/authenticate.d.ts +5 -0
  105. package/build/ts/auth/webhooks/authenticate.d.ts.map +1 -0
  106. package/build/ts/auth/webhooks/authenticate.js +71 -0
  107. package/build/ts/auth/webhooks/authenticate.js.map +1 -0
  108. package/build/ts/auth/webhooks/index.d.ts +2 -0
  109. package/build/ts/auth/webhooks/index.d.ts.map +1 -0
  110. package/build/ts/auth/webhooks/index.js +6 -0
  111. package/build/ts/auth/webhooks/index.js.map +1 -0
  112. package/build/ts/auth/webhooks/register.d.ts +4 -0
  113. package/build/ts/auth/webhooks/register.d.ts.map +1 -0
  114. package/build/ts/auth/webhooks/register.js +39 -0
  115. package/build/ts/auth/webhooks/register.js.map +1 -0
  116. package/build/ts/auth/webhooks/types.d.ts +18 -0
  117. package/build/ts/auth/webhooks/types.d.ts.map +1 -0
  118. package/build/ts/auth/webhooks/types.js +3 -0
  119. package/build/ts/auth/webhooks/types.js.map +1 -0
  120. package/build/ts/billing/cancel.d.ts +5 -0
  121. package/build/ts/billing/cancel.d.ts.map +1 -0
  122. package/build/ts/billing/cancel.js +43 -0
  123. package/build/ts/billing/cancel.js.map +1 -0
  124. package/build/ts/billing/index.d.ts +4 -0
  125. package/build/ts/billing/index.d.ts.map +1 -0
  126. package/build/ts/billing/index.js +10 -0
  127. package/build/ts/billing/index.js.map +1 -0
  128. package/build/ts/billing/request.d.ts +6 -0
  129. package/build/ts/billing/request.d.ts.map +1 -0
  130. package/build/ts/billing/request.js +84 -0
  131. package/build/ts/billing/request.js.map +1 -0
  132. package/build/ts/billing/require.d.ts +6 -0
  133. package/build/ts/billing/require.d.ts.map +1 -0
  134. package/build/ts/billing/require.js +55 -0
  135. package/build/ts/billing/require.js.map +1 -0
  136. package/build/ts/billing/types.d.ts +228 -0
  137. package/build/ts/billing/types.d.ts.map +1 -0
  138. package/build/ts/billing/types.js +3 -0
  139. package/build/ts/billing/types.js.map +1 -0
  140. package/build/ts/config-types.d.ts +325 -0
  141. package/build/ts/config-types.d.ts.map +1 -0
  142. package/build/ts/config-types.js +3 -0
  143. package/build/ts/config-types.js.map +1 -0
  144. package/build/ts/index.d.ts +29 -0
  145. package/build/ts/index.d.ts.map +1 -0
  146. package/build/ts/index.js +108 -0
  147. package/build/ts/index.js.map +1 -0
  148. package/build/ts/override-logger.d.ts +3 -0
  149. package/build/ts/override-logger.d.ts.map +1 -0
  150. package/build/ts/override-logger.js +40 -0
  151. package/build/ts/override-logger.js.map +1 -0
  152. package/build/ts/types.d.ts +353 -0
  153. package/build/ts/types.d.ts.map +1 -0
  154. package/build/ts/types.js +17 -0
  155. package/build/ts/types.js.map +1 -0
  156. package/build/ts/version.d.ts +2 -0
  157. package/build/ts/version.d.ts.map +1 -0
  158. package/build/ts/version.js +5 -0
  159. package/build/ts/version.js.map +1 -0
  160. package/build/tsconfig.tsbuildinfo +1 -0
  161. package/package.json +65 -0
@@ -0,0 +1,599 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var serverRuntime = require('@remix-run/server-runtime');
6
+ var shopifyApi = require('@shopify/shopify-api');
7
+ var cancel = require('../../billing/cancel.js');
8
+ var require$1 = require('../../billing/require.js');
9
+ var request = require('../../billing/request.js');
10
+ var addResponseHeaders = require('../helpers/add-response-headers.js');
11
+ var redirectToAuthPage = require('../helpers/redirect-to-auth-page.js');
12
+ var redirectWithExitiframe = require('../helpers/redirect-with-exitiframe.js');
13
+ var beginAuth = require('../helpers/begin-auth.js');
14
+ var validateSessionToken = require('../helpers/validate-session-token.js');
15
+ var getSessionTokenHeader = require('../helpers/get-session-token-header.js');
16
+ var rejectBotRequest = require('../helpers/reject-bot-request.js');
17
+ var _const = require('../const.js');
18
+ var graphqlClient = require('./graphql-client.js');
19
+ var restClient = require('./rest-client.js');
20
+
21
+ const SESSION_TOKEN_PARAM = 'id_token';
22
+ class AuthStrategy {
23
+ constructor({
24
+ api,
25
+ config,
26
+ logger
27
+ }) {
28
+ this.api = void 0;
29
+ this.config = void 0;
30
+ this.logger = void 0;
31
+ this.api = api;
32
+ this.config = config;
33
+ this.logger = logger;
34
+ }
35
+ async authenticateAdmin(request) {
36
+ const {
37
+ api,
38
+ logger,
39
+ config
40
+ } = this;
41
+ rejectBotRequest.rejectBotRequest({
42
+ api,
43
+ logger,
44
+ config
45
+ }, request);
46
+ this.respondToOptionsRequest(request);
47
+ let sessionContext;
48
+ try {
49
+ sessionContext = await this.authenticateAndGetSessionContext(request);
50
+ } catch (errorOrResponse) {
51
+ if (errorOrResponse instanceof Response) {
52
+ this.ensureResponseHeaders(request, errorOrResponse);
53
+ }
54
+ throw errorOrResponse;
55
+ }
56
+ const context = {
57
+ admin: this.createAdminApiContext(request, sessionContext.session),
58
+ billing: this.createBillingContext(request, sessionContext.session),
59
+ session: sessionContext.session
60
+ };
61
+ if (config.isEmbeddedApp) {
62
+ return {
63
+ ...context,
64
+ sessionToken: sessionContext.token
65
+ };
66
+ } else {
67
+ return context;
68
+ }
69
+ }
70
+ async authenticateAndGetSessionContext(request) {
71
+ const {
72
+ api,
73
+ logger,
74
+ config
75
+ } = this;
76
+ const url = new URL(request.url);
77
+ const isPatchSessionToken = url.pathname === config.auth.patchSessionTokenPath;
78
+ const isExitIframe = url.pathname === config.auth.exitIframePath;
79
+ const isAuthRequest = url.pathname === config.auth.path;
80
+ const isAuthCallbackRequest = url.pathname === config.auth.callbackPath;
81
+ const sessionTokenHeader = getSessionTokenHeader.getSessionTokenHeader(request);
82
+ logger.info('Authenticating admin request');
83
+ if (isPatchSessionToken) {
84
+ logger.debug('Rendering bounce page');
85
+ throw this.renderAppBridge();
86
+ } else if (isExitIframe) {
87
+ const destination = url.searchParams.get('exitIframe');
88
+ logger.debug('Rendering exit iframe page', {
89
+ destination
90
+ });
91
+ throw this.renderAppBridge(destination);
92
+ } else if (isAuthCallbackRequest) {
93
+ throw await this.handleAuthCallbackRequest(request);
94
+ } else if (isAuthRequest) {
95
+ throw await this.handleAuthBeginRequest(request);
96
+ } else if (sessionTokenHeader) {
97
+ const sessionToken = await validateSessionToken.validateSessionToken({
98
+ api,
99
+ logger,
100
+ config
101
+ }, sessionTokenHeader);
102
+ return this.validateAuthenticatedSession(request, sessionToken);
103
+ } else {
104
+ await this.validateUrlParams(request);
105
+ await this.ensureInstalledOnShop(request);
106
+ await this.ensureAppIsEmbeddedIfRequired(request);
107
+ await this.ensureSessionTokenSearchParamIfRequired(request);
108
+ return this.ensureSessionExists(request);
109
+ }
110
+ }
111
+ async handleAuthBeginRequest(request) {
112
+ const {
113
+ api,
114
+ config,
115
+ logger
116
+ } = this;
117
+ logger.info('Handling OAuth begin request');
118
+ const shop = this.ensureValidShopParam(request);
119
+ logger.debug('OAuth request contained valid shop', {
120
+ shop
121
+ });
122
+
123
+ // If we're loading from an iframe, we need to break out of it
124
+ if (config.isEmbeddedApp && request.headers.get('Sec-Fetch-Dest') === 'iframe') {
125
+ logger.debug('Auth request in iframe detected, exiting iframe', {
126
+ shop
127
+ });
128
+ throw redirectWithExitiframe.redirectWithExitIframe({
129
+ api,
130
+ config,
131
+ logger
132
+ }, request, shop);
133
+ } else {
134
+ throw await beginAuth.beginAuth({
135
+ api,
136
+ config,
137
+ logger
138
+ }, request, false, shop);
139
+ }
140
+ }
141
+ async handleAuthCallbackRequest(request) {
142
+ const {
143
+ api,
144
+ config,
145
+ logger
146
+ } = this;
147
+ logger.info('Handling OAuth callback request');
148
+ const shop = this.ensureValidShopParam(request);
149
+ try {
150
+ const {
151
+ session,
152
+ headers: responseHeaders
153
+ } = await api.auth.callback({
154
+ rawRequest: request
155
+ });
156
+ await config.sessionStorage.storeSession(session);
157
+ if (config.useOnlineTokens && !session.isOnline) {
158
+ logger.info('Requesting online access token for offline session');
159
+ await beginAuth.beginAuth({
160
+ api,
161
+ config,
162
+ logger
163
+ }, request, true, shop);
164
+ }
165
+ if (config.hooks.afterAuth) {
166
+ logger.info('Running afterAuth hook');
167
+ await config.hooks.afterAuth({
168
+ session,
169
+ admin: this.createAdminApiContext(request, session)
170
+ });
171
+ }
172
+ throw await this.redirectToShopifyOrAppRoot(request, responseHeaders);
173
+ } catch (error) {
174
+ if (error instanceof Response) {
175
+ throw error;
176
+ }
177
+ logger.error('Error during OAuth callback', {
178
+ error: error.message
179
+ });
180
+ if (error instanceof shopifyApi.CookieNotFound) {
181
+ throw await this.handleAuthBeginRequest(request);
182
+ } else if (error instanceof shopifyApi.InvalidHmacError || error instanceof shopifyApi.InvalidOAuthError) {
183
+ throw new Response(undefined, {
184
+ status: 400,
185
+ statusText: 'Invalid OAuth Request'
186
+ });
187
+ } else {
188
+ throw new Response(undefined, {
189
+ status: 500,
190
+ statusText: 'Internal Server Error'
191
+ });
192
+ }
193
+ }
194
+ }
195
+ async validateUrlParams(request) {
196
+ const {
197
+ api,
198
+ config,
199
+ logger
200
+ } = this;
201
+ const url = new URL(request.url);
202
+ const shop = api.utils.sanitizeShop(url.searchParams.get('shop'));
203
+ if (!shop) {
204
+ logger.debug('Missing or invalid shop, redirecting to login path', {
205
+ shop
206
+ });
207
+ throw serverRuntime.redirect(config.auth.loginPath);
208
+ }
209
+ if (this.config.isEmbeddedApp) {
210
+ const host = api.utils.sanitizeHost(url.searchParams.get('host'));
211
+ if (!host) {
212
+ logger.debug('Invalid host, redirecting to login path', {
213
+ host: url.searchParams.get('host')
214
+ });
215
+ throw serverRuntime.redirect(config.auth.loginPath);
216
+ }
217
+ }
218
+ }
219
+ async ensureInstalledOnShop(request) {
220
+ const {
221
+ api,
222
+ config,
223
+ logger
224
+ } = this;
225
+ const url = new URL(request.url);
226
+ const shop = url.searchParams.get('shop');
227
+ const isEmbedded = url.searchParams.get('embedded') === '1';
228
+
229
+ // Ensure app is installed
230
+ logger.debug('Ensuring app is installed on shop', {
231
+ shop
232
+ });
233
+ const offlineSession = await config.sessionStorage.loadSession(api.session.getOfflineId(shop));
234
+ if (!offlineSession) {
235
+ logger.info("Shop hasn't installed app yet, redirecting to OAuth", {
236
+ shop
237
+ });
238
+ if (isEmbedded) {
239
+ redirectWithExitiframe.redirectWithExitIframe({
240
+ api,
241
+ config,
242
+ logger
243
+ }, request, shop);
244
+ } else {
245
+ throw await beginAuth.beginAuth({
246
+ api,
247
+ config,
248
+ logger
249
+ }, request, false, shop);
250
+ }
251
+ }
252
+ if (config.isEmbeddedApp && !isEmbedded) {
253
+ try {
254
+ logger.debug('Ensuring offline session is valid before embedding', {
255
+ shop
256
+ });
257
+ await this.testSession(offlineSession);
258
+ logger.debug('Offline session is still valid, embedding app', {
259
+ shop
260
+ });
261
+ } catch (error) {
262
+ if (error instanceof shopifyApi.HttpResponseError) {
263
+ if (error.response.code === 401) {
264
+ logger.info('Shop session is no longer valid, redirecting to OAuth', {
265
+ shop
266
+ });
267
+ throw await beginAuth.beginAuth({
268
+ api,
269
+ config,
270
+ logger
271
+ }, request, false, shop);
272
+ } else {
273
+ const message = JSON.stringify(error.response.body, null, 2);
274
+ logger.error(`Unexpected error during session validation: ${message}`, {
275
+ shop
276
+ });
277
+ throw new Response(undefined, {
278
+ status: error.response.code,
279
+ statusText: error.response.statusText
280
+ });
281
+ }
282
+ } else if (error instanceof shopifyApi.GraphqlQueryError) {
283
+ const context = {
284
+ shop
285
+ };
286
+ if (error.response) {
287
+ context.response = JSON.stringify(error.response);
288
+ }
289
+ logger.error(`Unexpected error during session validation: ${error.message}`, context);
290
+ throw new Response(undefined, {
291
+ status: 500,
292
+ statusText: 'Internal Server Error'
293
+ });
294
+ }
295
+ }
296
+ }
297
+ }
298
+ async testSession(session) {
299
+ const {
300
+ api
301
+ } = this;
302
+ const client = new api.clients.Graphql({
303
+ session
304
+ });
305
+ await client.query({
306
+ data: `#graphql
307
+ query {
308
+ shop {
309
+ name
310
+ }
311
+ }
312
+ `
313
+ });
314
+ }
315
+ ensureValidShopParam(request) {
316
+ const url = new URL(request.url);
317
+ const {
318
+ api
319
+ } = this;
320
+ const shop = api.utils.sanitizeShop(url.searchParams.get('shop'));
321
+ if (!shop) {
322
+ throw new Response('Shop param is invalid', {
323
+ status: 400
324
+ });
325
+ }
326
+ return shop;
327
+ }
328
+ async ensureAppIsEmbeddedIfRequired(request) {
329
+ const {
330
+ api,
331
+ logger
332
+ } = this;
333
+ const url = new URL(request.url);
334
+ const shop = url.searchParams.get('shop');
335
+ if (api.config.isEmbeddedApp && url.searchParams.get('embedded') !== '1') {
336
+ logger.debug('App is not embedded, redirecting to Shopify', {
337
+ shop
338
+ });
339
+ await this.redirectToShopifyOrAppRoot(request);
340
+ }
341
+ }
342
+ async ensureSessionTokenSearchParamIfRequired(request) {
343
+ const {
344
+ api,
345
+ logger
346
+ } = this;
347
+ const url = new URL(request.url);
348
+ const shop = url.searchParams.get('shop');
349
+ const searchParamSessionToken = url.searchParams.get(SESSION_TOKEN_PARAM);
350
+ if (api.config.isEmbeddedApp && !searchParamSessionToken) {
351
+ logger.debug('Missing session token in search params, going to bounce page', {
352
+ shop
353
+ });
354
+ this.redirectToBouncePage(url);
355
+ }
356
+ }
357
+ async ensureSessionExists(request) {
358
+ const {
359
+ api,
360
+ config,
361
+ logger
362
+ } = this;
363
+ const url = new URL(request.url);
364
+ const shop = url.searchParams.get('shop');
365
+ const searchParamSessionToken = url.searchParams.get(SESSION_TOKEN_PARAM);
366
+ if (api.config.isEmbeddedApp) {
367
+ logger.debug('Session token is present in query params, validating session', {
368
+ shop
369
+ });
370
+ const sessionToken = await validateSessionToken.validateSessionToken({
371
+ api,
372
+ config,
373
+ logger
374
+ }, searchParamSessionToken);
375
+ return this.validateAuthenticatedSession(request, sessionToken);
376
+ } else {
377
+ // eslint-disable-next-line no-warning-comments
378
+ // TODO move this check into loadSession once we add support for it in the library
379
+ // https://github.com/orgs/Shopify/projects/6899/views/1?pane=issue&itemId=28378114
380
+ const sessionId = await api.session.getCurrentId({
381
+ isOnline: config.useOnlineTokens,
382
+ rawRequest: request
383
+ });
384
+ if (!sessionId) {
385
+ logger.debug('Session id not found in cookies, redirecting to OAuth', {
386
+ shop
387
+ });
388
+ throw await beginAuth.beginAuth({
389
+ api,
390
+ config,
391
+ logger
392
+ }, request, false, shop);
393
+ }
394
+ return {
395
+ session: await this.loadSession(request, shop, sessionId)
396
+ };
397
+ }
398
+ }
399
+ async validateAuthenticatedSession(request, payload) {
400
+ const {
401
+ config,
402
+ logger,
403
+ api
404
+ } = this;
405
+ const dest = new URL(payload.dest);
406
+ const shop = dest.hostname;
407
+ const sessionId = config.useOnlineTokens ? api.session.getJwtSessionId(shop, payload.sub) : api.session.getOfflineId(shop);
408
+ const session = await this.loadSession(request, shop, sessionId);
409
+ logger.debug('Found session, request is valid', {
410
+ shop
411
+ });
412
+ return {
413
+ session,
414
+ token: payload
415
+ };
416
+ }
417
+ async loadSession(request, shop, sessionId) {
418
+ const {
419
+ api,
420
+ config,
421
+ logger
422
+ } = this;
423
+ logger.debug('Loading session from storage', {
424
+ sessionId
425
+ });
426
+ const session = await config.sessionStorage.loadSession(sessionId);
427
+ if (!session) {
428
+ logger.debug('No session found, redirecting to OAuth', {
429
+ shop
430
+ });
431
+ await redirectToAuthPage.redirectToAuthPage({
432
+ api,
433
+ config,
434
+ logger
435
+ }, request, shop);
436
+ } else if (!session.isActive(config.scopes)) {
437
+ logger.debug('Found a session, but it has expired, redirecting to OAuth', {
438
+ shop
439
+ });
440
+ await redirectToAuthPage.redirectToAuthPage({
441
+ api,
442
+ config,
443
+ logger
444
+ }, request, shop);
445
+ }
446
+ return session;
447
+ }
448
+ async redirectToShopifyOrAppRoot(request, responseHeaders) {
449
+ const {
450
+ api
451
+ } = this;
452
+ const url = new URL(request.url);
453
+ const host = api.utils.sanitizeHost(url.searchParams.get('host'));
454
+ const shop = api.utils.sanitizeShop(url.searchParams.get('shop'));
455
+ const redirectUrl = api.config.isEmbeddedApp ? await api.auth.getEmbeddedAppUrl({
456
+ rawRequest: request
457
+ }) : `/?shop=${shop}&host=${encodeURIComponent(host)}`;
458
+ throw serverRuntime.redirect(redirectUrl, {
459
+ headers: responseHeaders
460
+ });
461
+ }
462
+ redirectToBouncePage(url) {
463
+ const {
464
+ api,
465
+ config
466
+ } = this;
467
+
468
+ // eslint-disable-next-line no-warning-comments
469
+ // TODO this is to work around a remix bug
470
+ // https://github.com/orgs/Shopify/projects/6899/views/1?pane=issue&itemId=28376650
471
+ url.protocol = `${api.config.hostScheme}:`;
472
+ const params = new URLSearchParams(url.search);
473
+ params.set('shopify-reload', url.href);
474
+
475
+ // eslint-disable-next-line no-warning-comments
476
+ // TODO Make sure this works on chrome without a tunnel (weird HTTPS redirect issue)
477
+ // https://github.com/orgs/Shopify/projects/6899/views/1?pane=issue&itemId=28376650
478
+ throw serverRuntime.redirect(`${config.auth.patchSessionTokenPath}?${params.toString()}`);
479
+ }
480
+ renderAppBridge(redirectTo) {
481
+ const {
482
+ config
483
+ } = this;
484
+ let redirectToScript = '';
485
+ if (redirectTo) {
486
+ const redirectUrl = decodeURIComponent(redirectTo.startsWith('/') ? `${config.appUrl}${redirectTo}` : redirectTo);
487
+ redirectToScript = `<script>window.open("${redirectUrl}", "_top")</script>`;
488
+ }
489
+ throw new Response(`
490
+ <script data-api-key="${config.apiKey}" src="${_const.APP_BRIDGE_NEXT_URL}"></script>
491
+ ${redirectToScript}
492
+ `, {
493
+ headers: {
494
+ 'content-type': 'text/html;charset=utf-8'
495
+ }
496
+ });
497
+ }
498
+ respondToOptionsRequest(request) {
499
+ if (request.method === 'OPTIONS') {
500
+ throw new Response(null, {
501
+ status: 204,
502
+ headers: {
503
+ 'Access-Control-Max-Age': '7200'
504
+ }
505
+ });
506
+ }
507
+ }
508
+ ensureResponseHeaders(request, response) {
509
+ const {
510
+ config
511
+ } = this;
512
+ const shop = new URL(request.url).searchParams.get('shop');
513
+
514
+ // We want the headers to be present on all responses:
515
+ addResponseHeaders.addResponseHeaders(response.headers, config.isEmbeddedApp, shop);
516
+ }
517
+ overriddenRestClient(request, session) {
518
+ const {
519
+ api,
520
+ config,
521
+ logger
522
+ } = this;
523
+ const client = new restClient.RemixRestClient({
524
+ params: {
525
+ api,
526
+ config,
527
+ logger
528
+ },
529
+ request,
530
+ session
531
+ });
532
+ if (api.rest) {
533
+ client.resources = {};
534
+ const RestResourceClient = restClient.restResourceClientFactory({
535
+ params: {
536
+ api,
537
+ config,
538
+ logger
539
+ },
540
+ request,
541
+ session
542
+ });
543
+ Object.entries(api.rest).forEach(([name, resource]) => {
544
+ class RemixResource extends resource {}
545
+ RemixResource.Client = RestResourceClient;
546
+ Reflect.defineProperty(RemixResource, 'name', {
547
+ value: name
548
+ });
549
+ Reflect.set(client.resources, name, RemixResource);
550
+ });
551
+ }
552
+ return client;
553
+ }
554
+ createBillingContext(request$1, session) {
555
+ const {
556
+ api,
557
+ logger,
558
+ config
559
+ } = this;
560
+ return {
561
+ require: require$1.requireBillingFactory({
562
+ api,
563
+ logger,
564
+ config
565
+ }, request$1, session),
566
+ request: request.requestBillingFactory({
567
+ api,
568
+ logger,
569
+ config
570
+ }, request$1, session),
571
+ cancel: cancel.cancelBillingFactory({
572
+ api,
573
+ logger,
574
+ config
575
+ }, request$1, session)
576
+ };
577
+ }
578
+ createAdminApiContext(request, session) {
579
+ const {
580
+ api,
581
+ config,
582
+ logger
583
+ } = this;
584
+ return {
585
+ rest: this.overriddenRestClient(request, session),
586
+ graphql: graphqlClient.graphqlClientFactory({
587
+ params: {
588
+ api,
589
+ config,
590
+ logger
591
+ },
592
+ request,
593
+ session
594
+ })
595
+ };
596
+ }
597
+ }
598
+
599
+ exports.AuthStrategy = AuthStrategy;
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var runtime = require('@shopify/shopify-api/runtime');
6
+ var handleClientError = require('../helpers/handle-client-error.js');
7
+
8
+ // eslint-disable-next-line no-warning-comments
9
+ // TODO: This is actually just a call through to the Shopify API client, but with a different API. We should eventually
10
+ // move this over to the library layer. While doing that, we should also allow the apiVersion to be passed into the REST
11
+ // client request calls.
12
+ function graphqlClientFactory({
13
+ params,
14
+ request,
15
+ session
16
+ }) {
17
+ return async function query(query, options) {
18
+ var _options;
19
+ const {
20
+ api,
21
+ config,
22
+ logger
23
+ } = params;
24
+ const client = new api.clients.Graphql({
25
+ session,
26
+ apiVersion: (_options = options) === null || _options === void 0 ? void 0 : _options.apiVersion
27
+ });
28
+ try {
29
+ var _options2, _options3, _options4;
30
+ // We convert the incoming response to a Response object to bring this client closer to the Remix client.
31
+ const apiResponse = await client.query({
32
+ data: {
33
+ query,
34
+ variables: (_options2 = options) === null || _options2 === void 0 ? void 0 : _options2.variables
35
+ },
36
+ tries: (_options3 = options) === null || _options3 === void 0 ? void 0 : _options3.tries,
37
+ extraHeaders: (_options4 = options) === null || _options4 === void 0 ? void 0 : _options4.headers
38
+ });
39
+ return new Response(JSON.stringify(apiResponse.body), {
40
+ headers: runtime.flatHeaders(apiResponse.headers)
41
+ });
42
+ } catch (error) {
43
+ throw await handleClientError.handleClientError({
44
+ params: {
45
+ api,
46
+ config,
47
+ logger
48
+ },
49
+ request,
50
+ error,
51
+ shop: session.shop
52
+ });
53
+ }
54
+ };
55
+ }
56
+
57
+ exports.graphqlClientFactory = graphqlClientFactory;