@shopify/shopify-app-remix 2.3.0 → 2.4.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 (128) hide show
  1. package/build/cjs/server/adapters/node/index.js +1 -0
  2. package/build/cjs/server/authenticate/admin/authenticate.js +17 -14
  3. package/build/cjs/server/authenticate/admin/billing/cancel.js +1 -1
  4. package/build/cjs/server/authenticate/admin/billing/require.js +1 -1
  5. package/build/cjs/server/authenticate/admin/helpers/create-admin-api-context.js +2 -5
  6. package/build/cjs/server/authenticate/admin/helpers/handle-client-error.js +8 -4
  7. package/build/cjs/server/authenticate/admin/helpers/redirect-to-bounce-page.js +4 -2
  8. package/build/cjs/server/authenticate/admin/helpers/redirect.js +1 -5
  9. package/build/cjs/server/authenticate/admin/helpers/trigger-after-auth-hook.js +2 -2
  10. package/build/cjs/server/authenticate/admin/strategies/auth-code-flow.js +31 -4
  11. package/build/cjs/server/authenticate/admin/strategies/token-exchange.js +155 -0
  12. package/build/cjs/server/authenticate/const.js +4 -0
  13. package/build/cjs/server/authenticate/helpers/idempotent-promise-handler.js +40 -0
  14. package/build/cjs/server/authenticate/helpers/respond-to-invalid-session-token.js +33 -0
  15. package/build/cjs/server/authenticate/login/login.js +5 -1
  16. package/build/cjs/server/authenticate/public/checkout/authenticate.js +1 -0
  17. package/build/cjs/server/authenticate/webhooks/authenticate.js +1 -1
  18. package/build/cjs/server/authenticate/webhooks/register.js +16 -0
  19. package/build/cjs/server/shopify-app.js +11 -6
  20. package/build/cjs/server/version.js +1 -1
  21. package/build/ts/react/components/AppProvider/AppProvider.js +1 -1
  22. package/build/ts/react/components/AppProvider/AppProvider.js.map +1 -1
  23. package/build/ts/server/__test-helpers/const.d.ts +2 -1
  24. package/build/ts/server/__test-helpers/const.d.ts.map +1 -1
  25. package/build/ts/server/__test-helpers/const.js +3 -2
  26. package/build/ts/server/__test-helpers/const.js.map +1 -1
  27. package/build/ts/server/__test-helpers/expect-document-request-headers.js.map +1 -1
  28. package/build/ts/server/__test-helpers/expect-exit-iframe.js.map +1 -1
  29. package/build/ts/server/__test-helpers/get-thrown-response.js.map +1 -1
  30. package/build/ts/server/__test-helpers/request-mock.js.map +1 -1
  31. package/build/ts/server/__test-helpers/setup-valid-session.d.ts +1 -1
  32. package/build/ts/server/__test-helpers/setup-valid-session.d.ts.map +1 -1
  33. package/build/ts/server/__test-helpers/setup-valid-session.js +5 -5
  34. package/build/ts/server/__test-helpers/setup-valid-session.js.map +1 -1
  35. package/build/ts/server/__test-helpers/test-config.d.ts +1 -0
  36. package/build/ts/server/__test-helpers/test-config.d.ts.map +1 -1
  37. package/build/ts/server/__test-helpers/test-config.js +1 -0
  38. package/build/ts/server/__test-helpers/test-config.js.map +1 -1
  39. package/build/ts/server/adapters/node/index.js.map +1 -1
  40. package/build/ts/server/authenticate/admin/authenticate.d.ts +7 -1
  41. package/build/ts/server/authenticate/admin/authenticate.d.ts.map +1 -1
  42. package/build/ts/server/authenticate/admin/authenticate.js +14 -14
  43. package/build/ts/server/authenticate/admin/authenticate.js.map +1 -1
  44. package/build/ts/server/authenticate/admin/billing/request.js.map +1 -1
  45. package/build/ts/server/authenticate/admin/helpers/create-admin-api-context.d.ts +2 -2
  46. package/build/ts/server/authenticate/admin/helpers/create-admin-api-context.d.ts.map +1 -1
  47. package/build/ts/server/authenticate/admin/helpers/create-admin-api-context.js +2 -5
  48. package/build/ts/server/authenticate/admin/helpers/create-admin-api-context.js.map +1 -1
  49. package/build/ts/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.js.map +1 -1
  50. package/build/ts/server/authenticate/admin/helpers/handle-client-error.d.ts +2 -5
  51. package/build/ts/server/authenticate/admin/helpers/handle-client-error.d.ts.map +1 -1
  52. package/build/ts/server/authenticate/admin/helpers/handle-client-error.js +6 -5
  53. package/build/ts/server/authenticate/admin/helpers/handle-client-error.js.map +1 -1
  54. package/build/ts/server/authenticate/admin/helpers/redirect-to-bounce-page.d.ts.map +1 -1
  55. package/build/ts/server/authenticate/admin/helpers/redirect-to-bounce-page.js +4 -2
  56. package/build/ts/server/authenticate/admin/helpers/redirect-to-bounce-page.js.map +1 -1
  57. package/build/ts/server/authenticate/admin/helpers/redirect-with-exitiframe.js.map +1 -1
  58. package/build/ts/server/authenticate/admin/helpers/redirect.js +0 -3
  59. package/build/ts/server/authenticate/admin/helpers/redirect.js.map +1 -1
  60. package/build/ts/server/authenticate/admin/helpers/render-app-bridge.js.map +1 -1
  61. package/build/ts/server/authenticate/admin/helpers/trigger-after-auth-hook.d.ts +2 -1
  62. package/build/ts/server/authenticate/admin/helpers/trigger-after-auth-hook.d.ts.map +1 -1
  63. package/build/ts/server/authenticate/admin/helpers/trigger-after-auth-hook.js +2 -2
  64. package/build/ts/server/authenticate/admin/helpers/trigger-after-auth-hook.js.map +1 -1
  65. package/build/ts/server/authenticate/admin/helpers/validate-shop-and-host-params.js.map +1 -1
  66. package/build/ts/server/authenticate/admin/strategies/auth-code-flow.d.ts +4 -2
  67. package/build/ts/server/authenticate/admin/strategies/auth-code-flow.d.ts.map +1 -1
  68. package/build/ts/server/authenticate/admin/strategies/auth-code-flow.js +26 -4
  69. package/build/ts/server/authenticate/admin/strategies/auth-code-flow.js.map +1 -1
  70. package/build/ts/server/authenticate/admin/strategies/token-exchange.d.ts +17 -0
  71. package/build/ts/server/authenticate/admin/strategies/token-exchange.d.ts.map +1 -0
  72. package/build/ts/server/authenticate/admin/strategies/token-exchange.js +163 -0
  73. package/build/ts/server/authenticate/admin/strategies/token-exchange.js.map +1 -0
  74. package/build/ts/server/authenticate/admin/strategies/types.d.ts +16 -5
  75. package/build/ts/server/authenticate/admin/strategies/types.d.ts.map +1 -1
  76. package/build/ts/server/authenticate/admin/types.d.ts +0 -4
  77. package/build/ts/server/authenticate/admin/types.d.ts.map +1 -1
  78. package/build/ts/server/authenticate/const.d.ts +3 -0
  79. package/build/ts/server/authenticate/const.d.ts.map +1 -1
  80. package/build/ts/server/authenticate/const.js +4 -1
  81. package/build/ts/server/authenticate/const.js.map +1 -1
  82. package/build/ts/server/authenticate/helpers/add-response-headers.js.map +1 -1
  83. package/build/ts/server/authenticate/helpers/ensure-cors-headers.js.map +1 -1
  84. package/build/ts/server/authenticate/helpers/idempotent-promise-handler.d.ts +12 -0
  85. package/build/ts/server/authenticate/helpers/idempotent-promise-handler.d.ts.map +1 -0
  86. package/build/ts/server/authenticate/helpers/idempotent-promise-handler.js +53 -0
  87. package/build/ts/server/authenticate/helpers/idempotent-promise-handler.js.map +1 -0
  88. package/build/ts/server/authenticate/helpers/index.d.ts +1 -0
  89. package/build/ts/server/authenticate/helpers/index.d.ts.map +1 -1
  90. package/build/ts/server/authenticate/helpers/index.js +1 -0
  91. package/build/ts/server/authenticate/helpers/index.js.map +1 -1
  92. package/build/ts/server/authenticate/helpers/reject-bot-request.js.map +1 -1
  93. package/build/ts/server/authenticate/helpers/respond-to-invalid-session-token.d.ts +9 -0
  94. package/build/ts/server/authenticate/helpers/respond-to-invalid-session-token.d.ts.map +1 -0
  95. package/build/ts/server/authenticate/helpers/respond-to-invalid-session-token.js +20 -0
  96. package/build/ts/server/authenticate/helpers/respond-to-invalid-session-token.js.map +1 -0
  97. package/build/ts/server/authenticate/helpers/respond-to-options-request.js.map +1 -1
  98. package/build/ts/server/authenticate/login/login.d.ts.map +1 -1
  99. package/build/ts/server/authenticate/login/login.js +6 -2
  100. package/build/ts/server/authenticate/login/login.js.map +1 -1
  101. package/build/ts/server/authenticate/public/appProxy/authenticate.js.map +1 -1
  102. package/build/ts/server/authenticate/public/checkout/authenticate.js.map +1 -1
  103. package/build/ts/server/authenticate/public/factory.js.map +1 -1
  104. package/build/ts/server/authenticate/webhooks/authenticate.js.map +1 -1
  105. package/build/ts/server/authenticate/webhooks/register.d.ts +1 -1
  106. package/build/ts/server/authenticate/webhooks/register.d.ts.map +1 -1
  107. package/build/ts/server/authenticate/webhooks/register.js +20 -1
  108. package/build/ts/server/authenticate/webhooks/register.js.map +1 -1
  109. package/build/ts/server/boundary/error.js.map +1 -1
  110. package/build/ts/server/boundary/headers.js.map +1 -1
  111. package/build/ts/server/clients/admin/rest.js.map +1 -1
  112. package/build/ts/server/config-types.d.ts +2 -0
  113. package/build/ts/server/config-types.d.ts.map +1 -1
  114. package/build/ts/server/future/flags.d.ts +6 -0
  115. package/build/ts/server/future/flags.d.ts.map +1 -1
  116. package/build/ts/server/override-logger.js.map +1 -1
  117. package/build/ts/server/shopify-app.d.ts.map +1 -1
  118. package/build/ts/server/shopify-app.js +10 -5
  119. package/build/ts/server/shopify-app.js.map +1 -1
  120. package/build/ts/server/types.d.ts +9 -4
  121. package/build/ts/server/types.d.ts.map +1 -1
  122. package/build/ts/server/types.js +2 -2
  123. package/build/ts/server/types.js.map +1 -1
  124. package/build/ts/server/unauthenticated/admin/factory.js.map +1 -1
  125. package/build/ts/server/unauthenticated/storefront/factory.js.map +1 -1
  126. package/build/ts/server/version.d.ts +1 -1
  127. package/build/ts/server/version.js +1 -1
  128. package/package.json +8 -12
@@ -4,6 +4,7 @@ var crypto = require('crypto');
4
4
  var runtime = require('@shopify/shopify-api/runtime');
5
5
  var appBridgeUrl = require('../../authenticate/helpers/app-bridge-url.js');
6
6
  require('isbot');
7
+ require('@remix-run/server-runtime');
7
8
 
8
9
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
9
10
 
@@ -7,6 +7,7 @@ var validateSessionToken = require('../helpers/validate-session-token.js');
7
7
  var getSessionTokenHeader = require('../helpers/get-session-token-header.js');
8
8
  var rejectBotRequest = require('../helpers/reject-bot-request.js');
9
9
  var respondToOptionsRequest = require('../helpers/respond-to-options-request.js');
10
+ require('@remix-run/server-runtime');
10
11
  var cancel = require('./billing/cancel.js');
11
12
  var require$1 = require('./billing/require.js');
12
13
  var request = require('./billing/request.js');
@@ -14,7 +15,6 @@ var createAdminApiContext = require('./helpers/create-admin-api-context.js');
14
15
  var ensureAppIsEmbeddedIfRequired = require('./helpers/ensure-app-is-embedded-if-required.js');
15
16
  var ensureSessionTokenSearchParamIfRequired = require('./helpers/ensure-session-token-search-param-if-required.js');
16
17
  require('@shopify/shopify-api');
17
- require('@remix-run/server-runtime');
18
18
  var redirect = require('./helpers/redirect.js');
19
19
  var renderAppBridge = require('./helpers/render-app-bridge.js');
20
20
  var validateShopAndHostParams = require('./helpers/validate-shop-and-host-params.js');
@@ -55,13 +55,9 @@ function authStrategyFactory({
55
55
  });
56
56
  }
57
57
  }
58
- function createContext(request$1, session, sessionToken) {
58
+ function createContext(request$1, session, authStrategy, sessionToken) {
59
59
  const context = {
60
- admin: createAdminApiContext.createAdminApiContext(request$1, session, {
61
- api,
62
- logger,
63
- config
64
- }),
60
+ admin: createAdminApiContext.createAdminApiContext(session, params, authStrategy.handleClientError(request$1)),
65
61
  billing: {
66
62
  require: require$1.requireBillingFactory(params, request$1, session),
67
63
  request: request.requestBillingFactory(params, request$1, session),
@@ -99,18 +95,23 @@ function authStrategyFactory({
99
95
  const {
100
96
  payload,
101
97
  shop,
102
- sessionId
98
+ sessionId,
99
+ sessionToken
103
100
  } = await getSessionTokenContext(params, request);
104
101
  logger.debug('Loading session from storage', {
105
102
  sessionId
106
103
  });
107
104
  const existingSession = sessionId ? await config.sessionStorage.loadSession(sessionId) : undefined;
108
- const session = await strategy.authenticate(request, existingSession, shop);
105
+ const session = await strategy.authenticate(request, {
106
+ session: existingSession,
107
+ sessionToken,
108
+ shop
109
+ });
109
110
  logger.debug('Request is valid, loaded session from session token', {
110
111
  shop: session.shop,
111
112
  isOnline: session.isOnline
112
113
  });
113
- return createContext(request, session, payload);
114
+ return createContext(request, session, strategy, payload);
114
115
  } catch (errorOrResponse) {
115
116
  if (errorOrResponse instanceof Response) {
116
117
  ensureCorsHeaders.ensureCORSHeadersFactory(params, request)(errorOrResponse);
@@ -129,10 +130,10 @@ async function getSessionTokenContext(params, request) {
129
130
  const searchParamSessionToken = getSessionTokenHeader.getSessionTokenFromUrlParam(request);
130
131
  const sessionToken = headerSessionToken || searchParamSessionToken;
131
132
  logger.debug('Attempting to authenticate session token', {
132
- sessionToken: {
133
+ sessionToken: JSON.stringify({
133
134
  header: headerSessionToken,
134
135
  search: searchParamSessionToken
135
- }
136
+ })
136
137
  });
137
138
  if (config.isEmbeddedApp) {
138
139
  const payload = await validateSessionToken.validateSessionToken({
@@ -150,7 +151,8 @@ async function getSessionTokenContext(params, request) {
150
151
  return {
151
152
  shop,
152
153
  payload,
153
- sessionId
154
+ sessionId,
155
+ sessionToken
154
156
  };
155
157
  }
156
158
  const url = new URL(request.url);
@@ -162,7 +164,8 @@ async function getSessionTokenContext(params, request) {
162
164
  return {
163
165
  shop,
164
166
  sessionId,
165
- payload: undefined
167
+ payload: undefined,
168
+ sessionToken
166
169
  };
167
170
  }
168
171
 
@@ -3,8 +3,8 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var shopifyApi = require('@shopify/shopify-api');
6
- var redirectToAuthPage = require('../helpers/redirect-to-auth-page.js');
7
6
  require('@remix-run/server-runtime');
7
+ var redirectToAuthPage = require('../helpers/redirect-to-auth-page.js');
8
8
 
9
9
  function cancelBillingFactory(params, request, session) {
10
10
  return async function cancelBilling(options) {
@@ -3,8 +3,8 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var shopifyApi = require('@shopify/shopify-api');
6
- var redirectToAuthPage = require('../helpers/redirect-to-auth-page.js');
7
6
  require('@remix-run/server-runtime');
7
+ var redirectToAuthPage = require('../helpers/redirect-to-auth-page.js');
8
8
 
9
9
  function requireBillingFactory(params, request, session) {
10
10
  const {
@@ -3,15 +3,12 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var factory = require('../../../clients/admin/factory.js');
6
- var handleClientError = require('./handle-client-error.js');
7
6
 
8
- function createAdminApiContext(request, session, params) {
7
+ function createAdminApiContext(session, params, handleClientError) {
9
8
  return factory.adminClientFactory({
10
9
  session,
11
10
  params,
12
- handleClientError: handleClientError.handleClientErrorFactory({
13
- request
14
- })
11
+ handleClientError
15
12
  });
16
13
  }
17
14
 
@@ -3,10 +3,10 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var shopifyApi = require('@shopify/shopify-api');
6
- var redirectToAuthPage = require('./redirect-to-auth-page.js');
7
6
 
8
7
  function handleClientErrorFactory({
9
- request
8
+ request,
9
+ onError
10
10
  }) {
11
11
  return async function handleClientError({
12
12
  error,
@@ -22,8 +22,12 @@ function handleClientErrorFactory({
22
22
  statusText: error.response.statusText,
23
23
  body: JSON.stringify(error.response.body)
24
24
  });
25
- if (error.response.code === 401) {
26
- throw await redirectToAuthPage.redirectToAuthPage(params, request, session.shop);
25
+ if (onError) {
26
+ await onError({
27
+ request,
28
+ session,
29
+ error
30
+ });
27
31
  }
28
32
 
29
33
  // forward a minimal copy of the upstream HTTP response instead of an Error:
@@ -11,12 +11,14 @@ const redirectToBouncePage = (params, url) => {
11
11
 
12
12
  // Make sure we always point to the configured app URL so it also works behind reverse proxies (that alter the Host
13
13
  // header).
14
- url.searchParams.set('shopify-reload', `${config.appUrl}${url.pathname}${url.search}`);
14
+ const searchParams = url.searchParams;
15
+ searchParams.delete('id_token');
16
+ searchParams.set('shopify-reload', `${config.appUrl}${url.pathname}?${searchParams.toString()}`);
15
17
 
16
18
  // eslint-disable-next-line no-warning-comments
17
19
  // TODO Make sure this works on chrome without a tunnel (weird HTTPS redirect issue)
18
20
  // https://github.com/orgs/Shopify/projects/6899/views/1?pane=issue&itemId=28376650
19
- throw serverRuntime.redirect(`${config.auth.patchSessionTokenPath}${url.search}`);
21
+ throw serverRuntime.redirect(`${config.auth.patchSessionTokenPath}?${searchParams.toString()}`);
20
22
  };
21
23
 
22
24
  exports.redirectToBouncePage = redirectToBouncePage;
@@ -49,13 +49,9 @@ function isBounceRequest(request) {
49
49
  return Boolean(getSessionTokenHeader.getSessionTokenHeader(request)) && request.headers.has('X-Shopify-Bounce');
50
50
  }
51
51
  function isDataRequest(request) {
52
- const {
53
- searchParams
54
- } = new URL(request.url);
55
52
  const isGet = request.method === 'GET';
56
53
  const sessionTokenHeader = Boolean(getSessionTokenHeader.getSessionTokenHeader(request));
57
- const sessionTokenSearchParam = searchParams.has('id_token');
58
- return sessionTokenHeader && !sessionTokenSearchParam && !isBounceRequest(request) && (!isEmbeddedRequest(request) || !isGet);
54
+ return sessionTokenHeader && !isBounceRequest(request) && (!isEmbeddedRequest(request) || !isGet);
59
55
  }
60
56
  function isEmbeddedRequest(request) {
61
57
  const {
@@ -4,7 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var createAdminApiContext = require('./create-admin-api-context.js');
6
6
 
7
- async function triggerAfterAuthHook(params, session, request) {
7
+ async function triggerAfterAuthHook(params, session, request, authStrategy) {
8
8
  const {
9
9
  config,
10
10
  logger
@@ -13,7 +13,7 @@ async function triggerAfterAuthHook(params, session, request) {
13
13
  logger.info('Running afterAuth hook');
14
14
  await config.hooks.afterAuth({
15
15
  session,
16
- admin: createAdminApiContext.createAdminApiContext(request, session, params)
16
+ admin: createAdminApiContext.createAdminApiContext(session, params, authStrategy.handleClientError(request))
17
17
  });
18
18
  }
19
19
  }
@@ -4,9 +4,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var shopifyApi = require('@shopify/shopify-api');
6
6
  var beginAuth = require('../helpers/begin-auth.js');
7
- var redirectToAuthPage = require('../helpers/redirect-to-auth-page.js');
8
7
  var redirectToShopifyOrAppRoot = require('../helpers/redirect-to-shopify-or-app-root.js');
9
8
  require('@remix-run/server-runtime');
9
+ var handleClientError = require('../helpers/handle-client-error.js');
10
+ var redirectToAuthPage = require('../helpers/redirect-to-auth-page.js');
10
11
  var redirectWithExitiframe = require('../helpers/redirect-with-exitiframe.js');
11
12
  var getSessionTokenHeader = require('../../helpers/get-session-token-header.js');
12
13
  var triggerAfterAuthHook = require('../helpers/trigger-after-auth-hook.js');
@@ -52,12 +53,16 @@ class AuthCodeFlowStrategy {
52
53
  await this.ensureInstalledOnShop(request);
53
54
  }
54
55
  }
55
- async authenticate(request, session, shop) {
56
+ async authenticate(request, sessionContext) {
56
57
  const {
57
58
  api,
58
59
  config,
59
60
  logger
60
61
  } = this;
62
+ const {
63
+ shop,
64
+ session
65
+ } = sessionContext;
61
66
  if (!session) {
62
67
  logger.debug('No session found, redirecting to OAuth', {
63
68
  shop
@@ -82,6 +87,28 @@ class AuthCodeFlowStrategy {
82
87
  });
83
88
  return session;
84
89
  }
90
+ handleClientError(request) {
91
+ const {
92
+ api,
93
+ config,
94
+ logger
95
+ } = this;
96
+ return handleClientError.handleClientErrorFactory({
97
+ request,
98
+ onError: async ({
99
+ session,
100
+ error
101
+ }) => {
102
+ if (error.response.code === 401) {
103
+ throw await redirectToAuthPage.redirectToAuthPage({
104
+ api,
105
+ config,
106
+ logger
107
+ }, request, session.shop);
108
+ }
109
+ }
110
+ });
111
+ }
85
112
  async ensureInstalledOnShop(request) {
86
113
  const {
87
114
  api,
@@ -197,7 +224,7 @@ class AuthCodeFlowStrategy {
197
224
  api,
198
225
  config,
199
226
  logger
200
- }, session, request);
227
+ }, session, request, this);
201
228
  throw await redirectToShopifyOrAppRoot.redirectToShopifyOrAppRoot(request, {
202
229
  api,
203
230
  config,
@@ -293,7 +320,7 @@ class AuthCodeFlowStrategy {
293
320
  shop
294
321
  };
295
322
  if (error.response) {
296
- context.response = JSON.stringify(error.response);
323
+ context.response = JSON.stringify(error.body);
297
324
  }
298
325
  logger.error(`Unexpected error during session validation: ${error.message}`, context);
299
326
  throw new Response(undefined, {
@@ -0,0 +1,155 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var shopifyApi = require('@shopify/shopify-api');
6
+ require('isbot');
7
+ var respondToInvalidSessionToken = require('../../helpers/respond-to-invalid-session-token.js');
8
+ require('@remix-run/server-runtime');
9
+ var handleClientError = require('../helpers/handle-client-error.js');
10
+ var triggerAfterAuthHook = require('../helpers/trigger-after-auth-hook.js');
11
+
12
+ class TokenExchangeStrategy {
13
+ constructor({
14
+ api,
15
+ config,
16
+ logger
17
+ }) {
18
+ this.api = void 0;
19
+ this.config = void 0;
20
+ this.logger = void 0;
21
+ this.api = api;
22
+ this.config = config;
23
+ this.logger = logger;
24
+ }
25
+ async respondToOAuthRequests(_request) {}
26
+ async authenticate(request, sessionContext) {
27
+ const {
28
+ api,
29
+ config,
30
+ logger
31
+ } = this;
32
+ const {
33
+ shop,
34
+ session,
35
+ sessionToken
36
+ } = sessionContext;
37
+ if (!sessionToken) throw new shopifyApi.InvalidJwtError();
38
+ if (!session || session.isExpired()) {
39
+ logger.info('No valid session found');
40
+ logger.info('Requesting offline access token');
41
+ const {
42
+ session: offlineSession
43
+ } = await this.exchangeToken({
44
+ request,
45
+ sessionToken,
46
+ shop,
47
+ requestedTokenType: shopifyApi.RequestedTokenType.OfflineAccessToken
48
+ });
49
+ await config.sessionStorage.storeSession(offlineSession);
50
+ let newSession = offlineSession;
51
+ if (config.useOnlineTokens) {
52
+ logger.info('Requesting online access token');
53
+ const {
54
+ session: onlineSession
55
+ } = await this.exchangeToken({
56
+ request,
57
+ sessionToken,
58
+ shop,
59
+ requestedTokenType: shopifyApi.RequestedTokenType.OnlineAccessToken
60
+ });
61
+ await config.sessionStorage.storeSession(onlineSession);
62
+ newSession = onlineSession;
63
+ }
64
+ try {
65
+ await this.handleAfterAuthHook({
66
+ api,
67
+ config,
68
+ logger
69
+ }, newSession, request, sessionToken);
70
+ } catch (error) {
71
+ throw new Response(undefined, {
72
+ status: 500,
73
+ statusText: 'Internal Server Error'
74
+ });
75
+ }
76
+ return newSession;
77
+ }
78
+ return session;
79
+ }
80
+ handleClientError(request) {
81
+ const {
82
+ api,
83
+ config,
84
+ logger
85
+ } = this;
86
+ return handleClientError.handleClientErrorFactory({
87
+ request,
88
+ onError: async ({
89
+ session,
90
+ error
91
+ }) => {
92
+ if (error.response.code === 401) {
93
+ config.sessionStorage.deleteSession(session.id);
94
+ respondToInvalidSessionToken.respondToInvalidSessionToken({
95
+ params: {
96
+ config,
97
+ api,
98
+ logger
99
+ },
100
+ request
101
+ });
102
+ }
103
+ }
104
+ });
105
+ }
106
+ async exchangeToken({
107
+ request,
108
+ shop,
109
+ sessionToken,
110
+ requestedTokenType
111
+ }) {
112
+ const {
113
+ api,
114
+ config,
115
+ logger
116
+ } = this;
117
+ try {
118
+ return await api.auth.tokenExchange({
119
+ sessionToken,
120
+ shop,
121
+ requestedTokenType
122
+ });
123
+ } catch (error) {
124
+ var _error$response$body;
125
+ if (error instanceof shopifyApi.InvalidJwtError || error instanceof shopifyApi.HttpResponseError && error.response.code === 400 && ((_error$response$body = error.response.body) === null || _error$response$body === void 0 ? void 0 : _error$response$body.error) === 'invalid_subject_token') {
126
+ throw respondToInvalidSessionToken.respondToInvalidSessionToken({
127
+ params: {
128
+ api,
129
+ config,
130
+ logger
131
+ },
132
+ request,
133
+ retryRequest: true
134
+ });
135
+ }
136
+ throw new Response(undefined, {
137
+ status: 500,
138
+ statusText: 'Internal Server Error'
139
+ });
140
+ }
141
+ }
142
+ async handleAfterAuthHook(params, session, request, sessionToken) {
143
+ const {
144
+ config
145
+ } = params;
146
+ await config.idempotentPromiseHandler.handlePromise({
147
+ promiseFunction: () => {
148
+ return triggerAfterAuthHook.triggerAfterAuthHook(params, session, request, this);
149
+ },
150
+ identifier: sessionToken
151
+ });
152
+ }
153
+ }
154
+
155
+ exports.TokenExchangeStrategy = TokenExchangeStrategy;
@@ -4,6 +4,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  const APP_BRIDGE_URL = 'https://cdn.shopify.com/shopifycloud/app-bridge.js';
6
6
  const REAUTH_URL_HEADER = 'X-Shopify-API-Request-Failure-Reauthorize-Url';
7
+ const RETRY_INVALID_SESSION_HEADER = {
8
+ 'X-Shopify-Retry-Invalid-Session-Request': '1'
9
+ };
7
10
 
8
11
  exports.APP_BRIDGE_URL = APP_BRIDGE_URL;
9
12
  exports.REAUTH_URL_HEADER = REAUTH_URL_HEADER;
13
+ exports.RETRY_INVALID_SESSION_HEADER = RETRY_INVALID_SESSION_HEADER;
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const IDENTIFIER_TTL_MS = 60000;
6
+ class IdempotentPromiseHandler {
7
+ constructor() {
8
+ this.identifiers = void 0;
9
+ this.identifiers = new Map();
10
+ }
11
+ async handlePromise({
12
+ promiseFunction,
13
+ identifier
14
+ }) {
15
+ try {
16
+ if (this.isPromiseRunnable(identifier)) {
17
+ await promiseFunction();
18
+ }
19
+ } finally {
20
+ this.clearStaleIdentifiers();
21
+ }
22
+ return Promise.resolve();
23
+ }
24
+ isPromiseRunnable(identifier) {
25
+ if (!this.identifiers.has(identifier)) {
26
+ this.identifiers.set(identifier, Date.now());
27
+ return true;
28
+ }
29
+ return false;
30
+ }
31
+ async clearStaleIdentifiers() {
32
+ this.identifiers.forEach((date, identifier, map) => {
33
+ if (Date.now() - date > IDENTIFIER_TTL_MS) {
34
+ map.delete(identifier);
35
+ }
36
+ });
37
+ }
38
+ }
39
+
40
+ exports.IdempotentPromiseHandler = IdempotentPromiseHandler;
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var redirectToBouncePage = require('../admin/helpers/redirect-to-bounce-page.js');
6
+ var _const = require('../const.js');
7
+
8
+ function respondToInvalidSessionToken({
9
+ params,
10
+ request,
11
+ retryRequest = false
12
+ }) {
13
+ const {
14
+ api,
15
+ logger,
16
+ config
17
+ } = params;
18
+ const isDocumentRequest = !request.headers.get('authorization');
19
+ if (isDocumentRequest) {
20
+ return redirectToBouncePage.redirectToBouncePage({
21
+ api,
22
+ logger,
23
+ config
24
+ }, new URL(request.url));
25
+ }
26
+ throw new Response(undefined, {
27
+ status: 401,
28
+ statusText: 'Unauthorized',
29
+ headers: retryRequest ? _const.RETRY_INVALID_SESSION_HEADER : {}
30
+ });
31
+ }
32
+
33
+ exports.respondToInvalidSessionToken = respondToInvalidSessionToken;
@@ -37,7 +37,11 @@ function loginFactory(params) {
37
37
  shop: types.LoginErrorType.InvalidShop
38
38
  };
39
39
  }
40
- const redirectUrl = `${config.appUrl}${config.auth.path}?shop=${sanitizedShop}`;
40
+ const authPath = `${config.appUrl}${config.auth.path}?shop=${sanitizedShop}`;
41
+ const adminPath = api.utils.legacyUrlToShopAdminUrl(sanitizedShop);
42
+ const installPath = `https://${adminPath}/oauth/install?client_id=${config.apiKey}`;
43
+ const shouldInstall = config.isEmbeddedApp && config.future.unstable_newEmbeddedAuthStrategy;
44
+ const redirectUrl = shouldInstall ? installPath : authPath;
41
45
  logger.info(`Redirecting login request to ${redirectUrl}`, {
42
46
  shop: sanitizedShop
43
47
  });
@@ -7,6 +7,7 @@ var validateSessionToken = require('../../helpers/validate-session-token.js');
7
7
  var getSessionTokenHeader = require('../../helpers/get-session-token-header.js');
8
8
  var rejectBotRequest = require('../../helpers/reject-bot-request.js');
9
9
  var respondToOptionsRequest = require('../../helpers/respond-to-options-request.js');
10
+ require('@remix-run/server-runtime');
10
11
 
11
12
  function authenticateCheckoutFactory(params) {
12
13
  return async function authenticateCheckout(request, options = {}) {
@@ -4,8 +4,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var shopifyApi = require('@shopify/shopify-api');
6
6
  var factory = require('../../clients/admin/factory.js');
7
- var handleClientError = require('../admin/helpers/handle-client-error.js');
8
7
  require('@remix-run/server-runtime');
8
+ var handleClientError = require('../admin/helpers/handle-client-error.js');
9
9
 
10
10
  function authenticateWebhookFactory(params) {
11
11
  const {
@@ -33,6 +33,22 @@ function registerWebhooksFactory({
33
33
  });
34
34
  });
35
35
  return response;
36
+ }).catch(error => {
37
+ var _error$body, _error$body$errors;
38
+ const graphQLErrors = ((_error$body = error.body) === null || _error$body === void 0 ? void 0 : (_error$body$errors = _error$body.errors) === null || _error$body$errors === void 0 ? void 0 : _error$body$errors.graphQLErrors) || [];
39
+ const throttled = graphQLErrors.find(({
40
+ extensions: {
41
+ code
42
+ }
43
+ }) => code === 'THROTTLED');
44
+ if (throttled) {
45
+ logger.error('Failed to register webhooks', {
46
+ shop: session.shop,
47
+ error: JSON.stringify(error)
48
+ });
49
+ } else {
50
+ throw error;
51
+ }
36
52
  });
37
53
  };
38
54
  }
@@ -12,11 +12,14 @@ 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');
15
+ require('@remix-run/server-runtime');
15
16
  var login = require('./authenticate/login/login.js');
16
17
  var factory$1 = require('./unauthenticated/admin/factory.js');
17
18
  var factory = require('./authenticate/public/factory.js');
18
19
  var factory$2 = require('./unauthenticated/storefront/factory.js');
19
20
  var authCodeFlow = require('./authenticate/admin/strategies/auth-code-flow.js');
21
+ var tokenExchange = require('./authenticate/admin/strategies/token-exchange.js');
22
+ var idempotentPromiseHandler = require('./authenticate/helpers/idempotent-promise-handler.js');
20
23
 
21
24
  /**
22
25
  * Creates an object your app will use to interact with Shopify.
@@ -51,10 +54,9 @@ function shopifyApp(appConfig) {
51
54
  config,
52
55
  logger
53
56
  };
54
- const oauth = new authCodeFlow.AuthCodeFlowStrategy(params);
55
57
  const authStrategy = authenticate.authStrategyFactory({
56
58
  ...params,
57
- strategy: oauth
59
+ strategy: config.future.unstable_newEmbeddedAuthStrategy && config.isEmbeddedApp ? new tokenExchange.TokenExchangeStrategy(params) : new authCodeFlow.AuthCodeFlowStrategy(params)
58
60
  });
59
61
  const shopify = {
60
62
  sessionStorage: config.sessionStorage,
@@ -82,7 +84,7 @@ function isSingleMerchantApp(_shopify, config) {
82
84
  return config.distribution === types.AppDistribution.SingleMerchant;
83
85
  }
84
86
  function deriveApi(appConfig) {
85
- var _appConfig$isEmbedded, _appConfig$apiVersion;
87
+ var _appConfig$isEmbedded, _appConfig$apiVersion, _appConfig$future;
86
88
  let appUrl;
87
89
  try {
88
90
  appUrl = new URL(appConfig.appUrl);
@@ -108,11 +110,13 @@ function deriveApi(appConfig) {
108
110
  isEmbeddedApp: (_appConfig$isEmbedded = appConfig.isEmbeddedApp) !== null && _appConfig$isEmbedded !== void 0 ? _appConfig$isEmbedded : true,
109
111
  apiVersion: (_appConfig$apiVersion = appConfig.apiVersion) !== null && _appConfig$apiVersion !== void 0 ? _appConfig$apiVersion : shopifyApi.LATEST_API_VERSION,
110
112
  isCustomStoreApp: appConfig.distribution === types.AppDistribution.ShopifyAdmin,
111
- future: {}
113
+ future: {
114
+ unstable_tokenExchange: (_appConfig$future = appConfig.future) === null || _appConfig$future === void 0 ? void 0 : _appConfig$future.unstable_newEmbeddedAuthStrategy
115
+ }
112
116
  });
113
117
  }
114
118
  function deriveConfig(appConfig, apiConfig) {
115
- var _appConfig$distributi, _appConfig$useOnlineT, _appConfig$hooks, _appConfig$future;
119
+ var _appConfig$distributi, _appConfig$useOnlineT, _appConfig$hooks, _appConfig$future2;
116
120
  if (!appConfig.sessionStorage) {
117
121
  throw new shopifyApi.ShopifyError('Please provide a valid session storage. Refer to https://github.com/Shopify/shopify-app-js/blob/main/README.md#session-storage-options for options.');
118
122
  }
@@ -121,11 +125,12 @@ function deriveConfig(appConfig, apiConfig) {
121
125
  return {
122
126
  ...appConfig,
123
127
  ...apiConfig,
128
+ idempotentPromiseHandler: new idempotentPromiseHandler.IdempotentPromiseHandler(),
124
129
  canUseLoginForm: appConfig.distribution !== types.AppDistribution.ShopifyAdmin,
125
130
  useOnlineTokens: (_appConfig$useOnlineT = appConfig.useOnlineTokens) !== null && _appConfig$useOnlineT !== void 0 ? _appConfig$useOnlineT : false,
126
131
  hooks: (_appConfig$hooks = appConfig.hooks) !== null && _appConfig$hooks !== void 0 ? _appConfig$hooks : {},
127
132
  sessionStorage: appConfig.sessionStorage,
128
- future: (_appConfig$future = appConfig.future) !== null && _appConfig$future !== void 0 ? _appConfig$future : {},
133
+ future: (_appConfig$future2 = appConfig.future) !== null && _appConfig$future2 !== void 0 ? _appConfig$future2 : {},
129
134
  auth: {
130
135
  path: authPathPrefix,
131
136
  callbackPath: `${authPathPrefix}/callback`,
@@ -2,6 +2,6 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- const SHOPIFY_REMIX_LIBRARY_VERSION = '2.3.0';
5
+ const SHOPIFY_REMIX_LIBRARY_VERSION = '2.4.0';
6
6
 
7
7
  exports.SHOPIFY_REMIX_LIBRARY_VERSION = SHOPIFY_REMIX_LIBRARY_VERSION;