@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.
- package/LICENSE.md +9 -0
- package/README.md +373 -0
- package/build/cjs/adapters/node/index.js +13 -0
- package/build/cjs/auth/admin/authenticate.js +599 -0
- package/build/cjs/auth/admin/graphql-client.js +57 -0
- package/build/cjs/auth/admin/rest-client.js +124 -0
- package/build/cjs/auth/const.js +17 -0
- package/build/cjs/auth/helpers/add-response-headers.js +69 -0
- package/build/cjs/auth/helpers/begin-auth.js +18 -0
- package/build/cjs/auth/helpers/get-session-token-header.js +10 -0
- package/build/cjs/auth/helpers/handle-client-error.js +37 -0
- package/build/cjs/auth/helpers/redirect-to-auth-page.js +22 -0
- package/build/cjs/auth/helpers/redirect-with-app-bridge-headers.js +31 -0
- package/build/cjs/auth/helpers/redirect-with-exitiframe.js +23 -0
- package/build/cjs/auth/helpers/reject-bot-request.js +23 -0
- package/build/cjs/auth/helpers/validate-session-token.js +25 -0
- package/build/cjs/auth/login/login.js +54 -0
- package/build/cjs/auth/public/authenticate.js +32 -0
- package/build/cjs/auth/webhooks/authenticate.js +68 -0
- package/build/cjs/auth/webhooks/register.js +40 -0
- package/build/cjs/billing/cancel.js +40 -0
- package/build/cjs/billing/request.js +83 -0
- package/build/cjs/billing/require.js +47 -0
- package/build/cjs/index.js +140 -0
- package/build/cjs/override-logger.js +55 -0
- package/build/cjs/types.js +25 -0
- package/build/cjs/version.js +7 -0
- package/build/ts/adapters/node/index.d.ts +2 -0
- package/build/ts/adapters/node/index.d.ts.map +1 -0
- package/build/ts/adapters/node/index.js +10 -0
- package/build/ts/adapters/node/index.js.map +1 -0
- package/build/ts/auth/admin/authenticate.d.ts +32 -0
- package/build/ts/auth/admin/authenticate.d.ts.map +1 -0
- package/build/ts/auth/admin/authenticate.js +571 -0
- package/build/ts/auth/admin/authenticate.js.map +1 -0
- package/build/ts/auth/admin/graphql-client.d.ts +22 -0
- package/build/ts/auth/admin/graphql-client.d.ts.map +1 -0
- package/build/ts/auth/admin/graphql-client.js +53 -0
- package/build/ts/auth/admin/graphql-client.js.map +1 -0
- package/build/ts/auth/admin/rest-client.d.ts +34 -0
- package/build/ts/auth/admin/rest-client.d.ts.map +1 -0
- package/build/ts/auth/admin/rest-client.js +134 -0
- package/build/ts/auth/admin/rest-client.js.map +1 -0
- package/build/ts/auth/admin/types.d.ts +100 -0
- package/build/ts/auth/admin/types.d.ts.map +1 -0
- package/build/ts/auth/admin/types.js +3 -0
- package/build/ts/auth/admin/types.js.map +1 -0
- package/build/ts/auth/const.d.ts +9 -0
- package/build/ts/auth/const.d.ts.map +1 -0
- package/build/ts/auth/const.js +12 -0
- package/build/ts/auth/const.js.map +1 -0
- package/build/ts/auth/helpers/add-response-headers.d.ts +5 -0
- package/build/ts/auth/helpers/add-response-headers.d.ts.map +1 -0
- package/build/ts/auth/helpers/add-response-headers.js +60 -0
- package/build/ts/auth/helpers/add-response-headers.js.map +1 -0
- package/build/ts/auth/helpers/begin-auth.d.ts +3 -0
- package/build/ts/auth/helpers/begin-auth.d.ts.map +1 -0
- package/build/ts/auth/helpers/begin-auth.js +24 -0
- package/build/ts/auth/helpers/begin-auth.js.map +1 -0
- package/build/ts/auth/helpers/get-session-token-header.d.ts +2 -0
- package/build/ts/auth/helpers/get-session-token-header.d.ts.map +1 -0
- package/build/ts/auth/helpers/get-session-token-header.js +9 -0
- package/build/ts/auth/helpers/get-session-token-header.js.map +1 -0
- package/build/ts/auth/helpers/handle-client-error.d.ts +11 -0
- package/build/ts/auth/helpers/handle-client-error.d.ts.map +1 -0
- package/build/ts/auth/helpers/handle-client-error.js +40 -0
- package/build/ts/auth/helpers/handle-client-error.js.map +1 -0
- package/build/ts/auth/helpers/index.d.ts +8 -0
- package/build/ts/auth/helpers/index.d.ts.map +1 -0
- package/build/ts/auth/helpers/index.js +18 -0
- package/build/ts/auth/helpers/index.js.map +1 -0
- package/build/ts/auth/helpers/redirect-to-auth-page.d.ts +3 -0
- package/build/ts/auth/helpers/redirect-to-auth-page.d.ts.map +1 -0
- package/build/ts/auth/helpers/redirect-to-auth-page.js +33 -0
- package/build/ts/auth/helpers/redirect-to-auth-page.js.map +1 -0
- package/build/ts/auth/helpers/redirect-with-app-bridge-headers.d.ts +4 -0
- package/build/ts/auth/helpers/redirect-with-app-bridge-headers.d.ts.map +1 -0
- package/build/ts/auth/helpers/redirect-with-app-bridge-headers.js +24 -0
- package/build/ts/auth/helpers/redirect-with-app-bridge-headers.js.map +1 -0
- package/build/ts/auth/helpers/redirect-with-exitiframe.d.ts +3 -0
- package/build/ts/auth/helpers/redirect-with-exitiframe.d.ts.map +1 -0
- package/build/ts/auth/helpers/redirect-with-exitiframe.js +18 -0
- package/build/ts/auth/helpers/redirect-with-exitiframe.js.map +1 -0
- package/build/ts/auth/helpers/reject-bot-request.d.ts +3 -0
- package/build/ts/auth/helpers/reject-bot-request.d.ts.map +1 -0
- package/build/ts/auth/helpers/reject-bot-request.js +14 -0
- package/build/ts/auth/helpers/reject-bot-request.js.map +1 -0
- package/build/ts/auth/helpers/validate-session-token.d.ts +4 -0
- package/build/ts/auth/helpers/validate-session-token.d.ts.map +1 -0
- package/build/ts/auth/helpers/validate-session-token.js +36 -0
- package/build/ts/auth/helpers/validate-session-token.js.map +1 -0
- package/build/ts/auth/login/login.d.ts +3 -0
- package/build/ts/auth/login/login.d.ts.map +1 -0
- package/build/ts/auth/login/login.js +56 -0
- package/build/ts/auth/login/login.js.map +1 -0
- package/build/ts/auth/public/authenticate.d.ts +4 -0
- package/build/ts/auth/public/authenticate.d.ts.map +1 -0
- package/build/ts/auth/public/authenticate.js +35 -0
- package/build/ts/auth/public/authenticate.js.map +1 -0
- package/build/ts/auth/public/types.d.ts +27 -0
- package/build/ts/auth/public/types.d.ts.map +1 -0
- package/build/ts/auth/public/types.js +3 -0
- package/build/ts/auth/public/types.js.map +1 -0
- package/build/ts/auth/webhooks/authenticate.d.ts +5 -0
- package/build/ts/auth/webhooks/authenticate.d.ts.map +1 -0
- package/build/ts/auth/webhooks/authenticate.js +71 -0
- package/build/ts/auth/webhooks/authenticate.js.map +1 -0
- package/build/ts/auth/webhooks/index.d.ts +2 -0
- package/build/ts/auth/webhooks/index.d.ts.map +1 -0
- package/build/ts/auth/webhooks/index.js +6 -0
- package/build/ts/auth/webhooks/index.js.map +1 -0
- package/build/ts/auth/webhooks/register.d.ts +4 -0
- package/build/ts/auth/webhooks/register.d.ts.map +1 -0
- package/build/ts/auth/webhooks/register.js +39 -0
- package/build/ts/auth/webhooks/register.js.map +1 -0
- package/build/ts/auth/webhooks/types.d.ts +18 -0
- package/build/ts/auth/webhooks/types.d.ts.map +1 -0
- package/build/ts/auth/webhooks/types.js +3 -0
- package/build/ts/auth/webhooks/types.js.map +1 -0
- package/build/ts/billing/cancel.d.ts +5 -0
- package/build/ts/billing/cancel.d.ts.map +1 -0
- package/build/ts/billing/cancel.js +43 -0
- package/build/ts/billing/cancel.js.map +1 -0
- package/build/ts/billing/index.d.ts +4 -0
- package/build/ts/billing/index.d.ts.map +1 -0
- package/build/ts/billing/index.js +10 -0
- package/build/ts/billing/index.js.map +1 -0
- package/build/ts/billing/request.d.ts +6 -0
- package/build/ts/billing/request.d.ts.map +1 -0
- package/build/ts/billing/request.js +84 -0
- package/build/ts/billing/request.js.map +1 -0
- package/build/ts/billing/require.d.ts +6 -0
- package/build/ts/billing/require.d.ts.map +1 -0
- package/build/ts/billing/require.js +55 -0
- package/build/ts/billing/require.js.map +1 -0
- package/build/ts/billing/types.d.ts +228 -0
- package/build/ts/billing/types.d.ts.map +1 -0
- package/build/ts/billing/types.js +3 -0
- package/build/ts/billing/types.js.map +1 -0
- package/build/ts/config-types.d.ts +325 -0
- package/build/ts/config-types.d.ts.map +1 -0
- package/build/ts/config-types.js +3 -0
- package/build/ts/config-types.js.map +1 -0
- package/build/ts/index.d.ts +29 -0
- package/build/ts/index.d.ts.map +1 -0
- package/build/ts/index.js +108 -0
- package/build/ts/index.js.map +1 -0
- package/build/ts/override-logger.d.ts +3 -0
- package/build/ts/override-logger.d.ts.map +1 -0
- package/build/ts/override-logger.js +40 -0
- package/build/ts/override-logger.js.map +1 -0
- package/build/ts/types.d.ts +353 -0
- package/build/ts/types.d.ts.map +1 -0
- package/build/ts/types.js +17 -0
- package/build/ts/types.js.map +1 -0
- package/build/ts/version.d.ts +2 -0
- package/build/ts/version.d.ts.map +1 -0
- package/build/ts/version.js +5 -0
- package/build/ts/version.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- 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;
|