@shopify/shopify-app-remix 4.1.1 → 4.2.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/dist/cjs/react/components/AppProvider/AppProvider.js.map +1 -1
- package/dist/cjs/react/components/AppProxyLink/AppProxyLink.js.map +1 -1
- package/dist/cjs/server/authenticate/admin/strategies/auth-code-flow.js.map +1 -1
- package/dist/cjs/server/authenticate/admin/strategies/merchant-custom-app.js.map +1 -1
- package/dist/cjs/server/authenticate/admin/strategies/token-exchange.js.map +1 -1
- package/dist/cjs/server/authenticate/webhooks/authenticate.js +34 -10
- package/dist/cjs/server/authenticate/webhooks/authenticate.js.map +1 -1
- package/dist/cjs/server/version.js +1 -1
- package/dist/cjs/server/version.js.map +1 -1
- package/dist/esm/react/components/AppProvider/AppProvider.mjs.map +1 -1
- package/dist/esm/react/components/AppProxyLink/AppProxyLink.mjs.map +1 -1
- package/dist/esm/server/authenticate/admin/strategies/auth-code-flow.mjs.map +1 -1
- package/dist/esm/server/authenticate/admin/strategies/merchant-custom-app.mjs.map +1 -1
- package/dist/esm/server/authenticate/admin/strategies/token-exchange.mjs.map +1 -1
- package/dist/esm/server/authenticate/webhooks/authenticate.mjs +35 -11
- package/dist/esm/server/authenticate/webhooks/authenticate.mjs.map +1 -1
- package/dist/esm/server/version.mjs +1 -1
- package/dist/esm/server/version.mjs.map +1 -1
- package/dist/ts/react/components/AppProvider/AppProvider.d.ts.map +1 -1
- package/dist/ts/react/components/AppProxyLink/AppProxyLink.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/billing/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/strategies/auth-code-flow.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/strategies/merchant-custom-app.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/strategies/token-exchange.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/public/extension/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/webhooks/authenticate.d.ts.map +1 -1
- package/dist/ts/server/authenticate/webhooks/types.d.ts +34 -2
- package/dist/ts/server/authenticate/webhooks/types.d.ts.map +1 -1
- package/dist/ts/server/config-types.d.ts.map +1 -1
- package/dist/ts/server/version.d.ts +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +18 -14
- package/src/react/components/AppProvider/AppProvider.doc.ts +35 -0
- package/src/react/components/AppProvider/AppProvider.tsx +147 -0
- package/src/react/components/AppProvider/index.ts +1 -0
- package/src/react/components/AppProxyForm/AppProxyForm.doc.ts +28 -0
- package/src/react/components/AppProxyForm/AppProxyForm.tsx +82 -0
- package/src/react/components/AppProxyForm/index.ts +1 -0
- package/src/react/components/AppProxyLink/AppProxyLink.doc.ts +28 -0
- package/src/react/components/AppProxyLink/AppProxyLink.tsx +58 -0
- package/src/react/components/AppProxyLink/index.ts +1 -0
- package/src/react/components/AppProxyProvider/AppProxyProvider.doc.ts +40 -0
- package/src/react/components/AppProxyProvider/AppProxyProvider.tsx +100 -0
- package/src/react/components/AppProxyProvider/index.ts +5 -0
- package/src/react/components/RemixPolarisLink.tsx +14 -0
- package/src/react/components/index.ts +4 -0
- package/src/react/const.ts +2 -0
- package/src/react/index.ts +1 -0
- package/src/server/__test-helpers/const.ts +14 -0
- package/src/server/__test-helpers/expect-admin-api-client.ts +51 -0
- package/src/server/__test-helpers/expect-begin-auth-redirect.ts +22 -0
- package/src/server/__test-helpers/expect-document-request-headers.ts +25 -0
- package/src/server/__test-helpers/expect-exit-iframe.ts +37 -0
- package/src/server/__test-helpers/expect-login-redirect.ts +8 -0
- package/src/server/__test-helpers/expect-storefront-api-client.ts +49 -0
- package/src/server/__test-helpers/expect-token-refresh.ts +188 -0
- package/src/server/__test-helpers/get-hmac.ts +7 -0
- package/src/server/__test-helpers/get-jwt.ts +10 -0
- package/src/server/__test-helpers/get-thrown-error.ts +19 -0
- package/src/server/__test-helpers/get-thrown-response.ts +17 -0
- package/src/server/__test-helpers/index.ts +21 -0
- package/src/server/__test-helpers/mock-graphql-request.ts +60 -0
- package/src/server/__test-helpers/request-mock.ts +169 -0
- package/src/server/__test-helpers/setup-embedded-flow.ts +30 -0
- package/src/server/__test-helpers/setup-fetch-flow.ts +29 -0
- package/src/server/__test-helpers/setup-non-embedded-flow.ts +31 -0
- package/src/server/__test-helpers/setup-valid-request.ts +78 -0
- package/src/server/__test-helpers/setup-valid-session.ts +28 -0
- package/src/server/__test-helpers/sign-request-cookie.ts +21 -0
- package/src/server/__test-helpers/test-config.ts +56 -0
- package/src/server/adapters/node/index.ts +13 -0
- package/src/server/adapters/vercel/index.ts +13 -0
- package/src/server/authenticate/admin/authenticate.admin.doc.example.ts +34 -0
- package/src/server/authenticate/admin/authenticate.admin.doc.ts +51 -0
- package/src/server/authenticate/admin/authenticate.ts +232 -0
- package/src/server/authenticate/admin/billing/authenticate.admin.billing.doc.ts +29 -0
- package/src/server/authenticate/admin/billing/cancel.ts +38 -0
- package/src/server/authenticate/admin/billing/check.ts +41 -0
- package/src/server/authenticate/admin/billing/create-usage-record.ts +36 -0
- package/src/server/authenticate/admin/billing/helpers.ts +42 -0
- package/src/server/authenticate/admin/billing/index.ts +6 -0
- package/src/server/authenticate/admin/billing/request.ts +64 -0
- package/src/server/authenticate/admin/billing/require.ts +58 -0
- package/src/server/authenticate/admin/billing/types.ts +613 -0
- package/src/server/authenticate/admin/billing/update-usage-subscription-capped-amount.ts +55 -0
- package/src/server/authenticate/admin/helpers/begin-auth.ts +17 -0
- package/src/server/authenticate/admin/helpers/create-admin-api-context.ts +20 -0
- package/src/server/authenticate/admin/helpers/ensure-app-is-embedded-if-required.ts +18 -0
- package/src/server/authenticate/admin/helpers/ensure-session-token-search-param-if-required.ts +25 -0
- package/src/server/authenticate/admin/helpers/handle-client-error.ts +45 -0
- package/src/server/authenticate/admin/helpers/index.ts +14 -0
- package/src/server/authenticate/admin/helpers/redirect-to-auth-page.ts +28 -0
- package/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts +23 -0
- package/src/server/authenticate/admin/helpers/redirect-to-install-page.ts +53 -0
- package/src/server/authenticate/admin/helpers/redirect-to-shopify-or-app-root.ts +21 -0
- package/src/server/authenticate/admin/helpers/redirect-with-app-bridge-headers.ts +13 -0
- package/src/server/authenticate/admin/helpers/redirect-with-exitiframe.ts +28 -0
- package/src/server/authenticate/admin/helpers/redirect.ts +164 -0
- package/src/server/authenticate/admin/helpers/render-app-bridge.ts +46 -0
- package/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts +29 -0
- package/src/server/authenticate/admin/helpers/validate-redirect-url.ts +67 -0
- package/src/server/authenticate/admin/helpers/validate-shop-and-host-params.ts +46 -0
- package/src/server/authenticate/admin/scope/authenticate.admin.scopes.doc.ts +28 -0
- package/src/server/authenticate/admin/scope/client/fetch-scopes-details.ts +43 -0
- package/src/server/authenticate/admin/scope/client/revoke-scopes.ts +38 -0
- package/src/server/authenticate/admin/scope/factory.ts +21 -0
- package/src/server/authenticate/admin/scope/query.ts +51 -0
- package/src/server/authenticate/admin/scope/request.ts +32 -0
- package/src/server/authenticate/admin/scope/revoke.ts +48 -0
- package/src/server/authenticate/admin/scope/types.ts +126 -0
- package/src/server/authenticate/admin/strategies/auth-code-flow.ts +332 -0
- package/src/server/authenticate/admin/strategies/merchant-custom-app.ts +65 -0
- package/src/server/authenticate/admin/strategies/token-exchange.ts +188 -0
- package/src/server/authenticate/admin/strategies/types.ts +29 -0
- package/src/server/authenticate/admin/types.ts +216 -0
- package/src/server/authenticate/const.ts +15 -0
- package/src/server/authenticate/flow/authenticate.flow.doc.example.ts +39 -0
- package/src/server/authenticate/flow/authenticate.flow.doc.ts +47 -0
- package/src/server/authenticate/flow/authenticate.ts +68 -0
- package/src/server/authenticate/flow/types.ts +85 -0
- package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.example.ts +52 -0
- package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.ts +48 -0
- package/src/server/authenticate/fulfillment-service/authenticate.ts +82 -0
- package/src/server/authenticate/fulfillment-service/types.ts +82 -0
- package/src/server/authenticate/helpers/add-response-headers.ts +43 -0
- package/src/server/authenticate/helpers/app-bridge-url.ts +10 -0
- package/src/server/authenticate/helpers/ensure-cors-headers.ts +38 -0
- package/src/server/authenticate/helpers/get-session-token-header.ts +11 -0
- package/src/server/authenticate/helpers/get-shop-from-request.ts +4 -0
- package/src/server/authenticate/helpers/idempotent-promise-handler.ts +45 -0
- package/src/server/authenticate/helpers/index.ts +10 -0
- package/src/server/authenticate/helpers/invalidate-access-token.ts +17 -0
- package/src/server/authenticate/helpers/reject-bot-request.ts +27 -0
- package/src/server/authenticate/helpers/respond-to-invalid-session-token.ts +28 -0
- package/src/server/authenticate/helpers/respond-to-options-request.ts +26 -0
- package/src/server/authenticate/helpers/validate-session-token.ts +40 -0
- package/src/server/authenticate/login/login.ts +53 -0
- package/src/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.example.ts +27 -0
- package/src/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.ts +55 -0
- package/src/server/authenticate/public/appProxy/authenticate.ts +150 -0
- package/src/server/authenticate/public/appProxy/types.ts +215 -0
- package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.offers.ts +21 -0
- package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.example.ts +17 -0
- package/src/server/authenticate/public/checkout/authenticate.public.checkout.doc.ts +49 -0
- package/src/server/authenticate/public/checkout/authenticate.ts +13 -0
- package/src/server/authenticate/public/checkout/types.ts +65 -0
- package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.offers.ts +36 -0
- package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.example.ts +18 -0
- package/src/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.ts +49 -0
- package/src/server/authenticate/public/customer-account/authenticate.ts +13 -0
- package/src/server/authenticate/public/customer-account/types.ts +65 -0
- package/src/server/authenticate/public/extension/authenticate.ts +54 -0
- package/src/server/authenticate/public/extension/types.ts +11 -0
- package/src/server/authenticate/public/factory.ts +24 -0
- package/src/server/authenticate/public/index.ts +1 -0
- package/src/server/authenticate/public/pos/authenticate.ts +8 -0
- package/src/server/authenticate/public/pos/types.ts +65 -0
- package/src/server/authenticate/public/types.ts +96 -0
- package/src/server/authenticate/webhooks/authenticate.ts +105 -0
- package/src/server/authenticate/webhooks/authenticate.webhooks.doc.example.ts +43 -0
- package/src/server/authenticate/webhooks/authenticate.webhooks.doc.ts +39 -0
- package/src/server/authenticate/webhooks/index.ts +1 -0
- package/src/server/authenticate/webhooks/register.ts +48 -0
- package/src/server/authenticate/webhooks/types.ts +266 -0
- package/src/server/boundary/error.tsx +14 -0
- package/src/server/boundary/headers.ts +15 -0
- package/src/server/boundary/index.ts +39 -0
- package/src/server/boundary/types.ts +4 -0
- package/src/server/clients/admin/authenticate.admin.api.doc.ts +34 -0
- package/src/server/clients/admin/factory.ts +22 -0
- package/src/server/clients/admin/graphql.ts +39 -0
- package/src/server/clients/admin/index.ts +2 -0
- package/src/server/clients/admin/types.ts +120 -0
- package/src/server/clients/index.ts +2 -0
- package/src/server/clients/storefront/authenticate.storefront.api.doc.ts +34 -0
- package/src/server/clients/storefront/factory.ts +32 -0
- package/src/server/clients/storefront/index.ts +2 -0
- package/src/server/clients/storefront/types.ts +78 -0
- package/src/server/clients/types.ts +47 -0
- package/src/server/config-types.ts +309 -0
- package/src/server/errors.ts +3 -0
- package/src/server/future/flags.ts +62 -0
- package/src/server/helpers/create-or-load-offline-session.ts +19 -0
- package/src/server/helpers/ensure-offline-token-is-not-expired.ts +32 -0
- package/src/server/helpers/ensure-valid-offline-session.ts +15 -0
- package/src/server/helpers/index.ts +4 -0
- package/src/server/helpers/refresh-token.ts +35 -0
- package/src/server/index.ts +24 -0
- package/src/server/override-logger.ts +42 -0
- package/src/server/shopify-app.doc.ts +45 -0
- package/src/server/shopify-app.ts +215 -0
- package/src/server/test-helpers/index.ts +1 -0
- package/src/server/test-helpers/test-config.ts +79 -0
- package/src/server/types-contexts.ts +44 -0
- package/src/server/types.ts +507 -0
- package/src/server/unauthenticated/admin/factory.ts +23 -0
- package/src/server/unauthenticated/admin/index.ts +1 -0
- package/src/server/unauthenticated/admin/types.ts +79 -0
- package/src/server/unauthenticated/admin/unauthenticated.admin.doc.ts +40 -0
- package/src/server/unauthenticated/storefront/factory.ts +28 -0
- package/src/server/unauthenticated/storefront/index.ts +1 -0
- package/src/server/unauthenticated/storefront/types.ts +105 -0
- package/src/server/unauthenticated/storefront/unauthenticated.storefront.doc.ts +40 -0
- package/src/server/unauthenticated/types.ts +65 -0
- package/src/server/version.ts +1 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {Session, Shopify, ShopifyError} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import {AppConfig, AppConfigArg} from '../../../config-types';
|
|
4
|
+
import {BasicParams} from '../../../types';
|
|
5
|
+
import {ApiConfigWithFutureFlags, ApiFutureFlags} from '../../../future/flags';
|
|
6
|
+
import {HandleAdminClientError} from '../../../clients';
|
|
7
|
+
import {handleClientErrorFactory} from '../helpers';
|
|
8
|
+
import {getShopFromRequest} from '../../helpers';
|
|
9
|
+
|
|
10
|
+
import {AuthorizationStrategy, OnErrorOptions, SessionContext} from './types';
|
|
11
|
+
|
|
12
|
+
export class MerchantCustomAuth<
|
|
13
|
+
Config extends AppConfigArg,
|
|
14
|
+
> implements AuthorizationStrategy {
|
|
15
|
+
protected api: Shopify<
|
|
16
|
+
ApiConfigWithFutureFlags<Config['future']>,
|
|
17
|
+
ApiFutureFlags<Config['future']>
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
protected config: AppConfig;
|
|
21
|
+
protected logger: Shopify['logger'];
|
|
22
|
+
|
|
23
|
+
public constructor({api, config, logger}: BasicParams<Config['future']>) {
|
|
24
|
+
this.api = api;
|
|
25
|
+
this.config = config;
|
|
26
|
+
this.logger = logger;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public async respondToOAuthRequests(request: Request): Promise<void> {
|
|
30
|
+
this.logger.debug('Skipping OAuth request for merchant custom app', {
|
|
31
|
+
shop: getShopFromRequest(request),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public async authenticate(
|
|
36
|
+
_request: Request,
|
|
37
|
+
sessionContext: SessionContext,
|
|
38
|
+
): Promise<Session | never> {
|
|
39
|
+
const {shop} = sessionContext;
|
|
40
|
+
|
|
41
|
+
this.logger.debug(
|
|
42
|
+
'Building session from configured access token for merchant custom app',
|
|
43
|
+
{shop},
|
|
44
|
+
);
|
|
45
|
+
const session = this.api.session.customAppSession(shop);
|
|
46
|
+
|
|
47
|
+
return session;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public handleClientError(request: Request): HandleAdminClientError {
|
|
51
|
+
return handleClientErrorFactory({
|
|
52
|
+
request,
|
|
53
|
+
onError: async ({error}: OnErrorOptions) => {
|
|
54
|
+
if (error.response.code === 401) {
|
|
55
|
+
this.logger.info(
|
|
56
|
+
'Request failed with 401. Review your API credentials or generate new tokens. https://shopify.dev/docs/apps/build/authentication-authorization/access-token-types/generate-app-access-tokens-admin#rotating-api-credentials-for-admin-created-apps ',
|
|
57
|
+
);
|
|
58
|
+
throw new ShopifyError(
|
|
59
|
+
'Unauthorized: Access token has been revoked.',
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HttpResponseError,
|
|
3
|
+
InvalidJwtError,
|
|
4
|
+
RequestedTokenType,
|
|
5
|
+
Session,
|
|
6
|
+
Shopify,
|
|
7
|
+
} from '@shopify/shopify-api';
|
|
8
|
+
|
|
9
|
+
import {AppConfig, AppConfigArg} from '../../../config-types';
|
|
10
|
+
import {BasicParams} from '../../../types';
|
|
11
|
+
import {
|
|
12
|
+
respondToInvalidSessionToken,
|
|
13
|
+
invalidateAccessToken,
|
|
14
|
+
getShopFromRequest,
|
|
15
|
+
} from '../../helpers';
|
|
16
|
+
import {handleClientErrorFactory, triggerAfterAuthHook} from '../helpers';
|
|
17
|
+
import {HandleAdminClientError} from '../../../clients';
|
|
18
|
+
import type {
|
|
19
|
+
ApiConfigWithFutureFlags,
|
|
20
|
+
ApiFutureFlags,
|
|
21
|
+
} from '../../../future/flags';
|
|
22
|
+
import {WITHIN_MILLISECONDS_OF_EXPIRY} from '../../../helpers';
|
|
23
|
+
|
|
24
|
+
import {AuthorizationStrategy, SessionContext, OnErrorOptions} from './types';
|
|
25
|
+
|
|
26
|
+
export class TokenExchangeStrategy<
|
|
27
|
+
Config extends AppConfigArg,
|
|
28
|
+
> implements AuthorizationStrategy {
|
|
29
|
+
protected api: Shopify<
|
|
30
|
+
ApiConfigWithFutureFlags<Config['future']>,
|
|
31
|
+
ApiFutureFlags<Config['future']>
|
|
32
|
+
>;
|
|
33
|
+
|
|
34
|
+
protected config: AppConfig;
|
|
35
|
+
protected logger: Shopify['logger'];
|
|
36
|
+
|
|
37
|
+
public constructor({api, config, logger}: BasicParams<Config['future']>) {
|
|
38
|
+
this.api = api;
|
|
39
|
+
this.config = config;
|
|
40
|
+
this.logger = logger;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public async respondToOAuthRequests(_request: Request): Promise<void> {}
|
|
44
|
+
|
|
45
|
+
public async authenticate(
|
|
46
|
+
request: Request,
|
|
47
|
+
sessionContext: SessionContext,
|
|
48
|
+
): Promise<Session> {
|
|
49
|
+
const {api, config, logger} = this;
|
|
50
|
+
const {shop, session, sessionToken} = sessionContext;
|
|
51
|
+
|
|
52
|
+
if (!sessionToken) throw new InvalidJwtError();
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
!session ||
|
|
56
|
+
!session.isActive(undefined, WITHIN_MILLISECONDS_OF_EXPIRY)
|
|
57
|
+
) {
|
|
58
|
+
logger.info('No valid session found', {shop});
|
|
59
|
+
logger.info('Requesting offline access token', {shop});
|
|
60
|
+
const {session: offlineSession} = await this.exchangeToken({
|
|
61
|
+
request,
|
|
62
|
+
sessionToken,
|
|
63
|
+
shop,
|
|
64
|
+
requestedTokenType: RequestedTokenType.OfflineAccessToken,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await config.sessionStorage!.storeSession(offlineSession);
|
|
68
|
+
|
|
69
|
+
let newSession = offlineSession;
|
|
70
|
+
|
|
71
|
+
if (config.useOnlineTokens) {
|
|
72
|
+
logger.info('Requesting online access token', {shop});
|
|
73
|
+
const {session: onlineSession} = await this.exchangeToken({
|
|
74
|
+
request,
|
|
75
|
+
sessionToken,
|
|
76
|
+
shop,
|
|
77
|
+
requestedTokenType: RequestedTokenType.OnlineAccessToken,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await config.sessionStorage!.storeSession(onlineSession);
|
|
81
|
+
newSession = onlineSession;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
logger.debug('Request is valid, loaded session from session token', {
|
|
85
|
+
shop: newSession.shop,
|
|
86
|
+
isOnline: newSession.isOnline,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await this.handleAfterAuthHook(
|
|
91
|
+
{api, config, logger},
|
|
92
|
+
newSession,
|
|
93
|
+
request,
|
|
94
|
+
sessionToken,
|
|
95
|
+
);
|
|
96
|
+
} catch (errorOrResponse) {
|
|
97
|
+
if (errorOrResponse instanceof Response) {
|
|
98
|
+
throw errorOrResponse;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Response(undefined, {
|
|
102
|
+
status: 500,
|
|
103
|
+
statusText: 'Internal Server Error',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return newSession;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return session!;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public handleClientError(request: Request): HandleAdminClientError {
|
|
114
|
+
const {api, config, logger} = this;
|
|
115
|
+
return handleClientErrorFactory({
|
|
116
|
+
request,
|
|
117
|
+
onError: async ({session, error}: OnErrorOptions) => {
|
|
118
|
+
if (error.response.code === 401) {
|
|
119
|
+
logger.debug('Responding to invalid access token', {
|
|
120
|
+
shop: getShopFromRequest(request),
|
|
121
|
+
});
|
|
122
|
+
await invalidateAccessToken({config, api, logger}, session);
|
|
123
|
+
|
|
124
|
+
respondToInvalidSessionToken({
|
|
125
|
+
params: {config, api, logger},
|
|
126
|
+
request,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private async exchangeToken({
|
|
134
|
+
request,
|
|
135
|
+
shop,
|
|
136
|
+
sessionToken,
|
|
137
|
+
requestedTokenType,
|
|
138
|
+
}: {
|
|
139
|
+
request: Request;
|
|
140
|
+
shop: string;
|
|
141
|
+
sessionToken: string;
|
|
142
|
+
requestedTokenType: RequestedTokenType;
|
|
143
|
+
}): Promise<{session: Session}> {
|
|
144
|
+
const {api, config, logger} = this;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
return await api.auth.tokenExchange({
|
|
148
|
+
sessionToken,
|
|
149
|
+
shop,
|
|
150
|
+
requestedTokenType,
|
|
151
|
+
expiring: config.future.expiringOfflineAccessTokens,
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (
|
|
155
|
+
error instanceof InvalidJwtError ||
|
|
156
|
+
(error instanceof HttpResponseError &&
|
|
157
|
+
error.response.code === 400 &&
|
|
158
|
+
error.response.body?.error === 'invalid_subject_token')
|
|
159
|
+
) {
|
|
160
|
+
throw respondToInvalidSessionToken({
|
|
161
|
+
params: {api, config, logger},
|
|
162
|
+
request,
|
|
163
|
+
retryRequest: true,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
throw new Response(undefined, {
|
|
168
|
+
status: 500,
|
|
169
|
+
statusText: 'Internal Server Error',
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private async handleAfterAuthHook(
|
|
175
|
+
params: BasicParams,
|
|
176
|
+
session: Session,
|
|
177
|
+
request: Request,
|
|
178
|
+
sessionToken: string,
|
|
179
|
+
) {
|
|
180
|
+
const {config} = params;
|
|
181
|
+
await config.idempotentPromiseHandler.handlePromise({
|
|
182
|
+
promiseFunction: () => {
|
|
183
|
+
return triggerAfterAuthHook(params, session, request, this);
|
|
184
|
+
},
|
|
185
|
+
identifier: sessionToken,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {HttpResponseError, Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import {HandleAdminClientError} from '../../../clients';
|
|
4
|
+
|
|
5
|
+
export interface SessionContext {
|
|
6
|
+
shop: string;
|
|
7
|
+
session?: Session;
|
|
8
|
+
sessionToken?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface OnErrorOptions {
|
|
12
|
+
request: Request;
|
|
13
|
+
session: Session;
|
|
14
|
+
error: HttpResponseError;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface HandleClientErrorOptions {
|
|
18
|
+
request: Request;
|
|
19
|
+
onError?: ({session, error}: OnErrorOptions) => Promise<void | never>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AuthorizationStrategy {
|
|
23
|
+
respondToOAuthRequests: (request: Request) => Promise<void | never>;
|
|
24
|
+
authenticate: (
|
|
25
|
+
request: Request,
|
|
26
|
+
sessionContext: SessionContext,
|
|
27
|
+
) => Promise<Session | never>;
|
|
28
|
+
handleClientError: (request: Request) => HandleAdminClientError;
|
|
29
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import {JwtPayload, Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import {EnsureCORSFunction} from '../helpers/ensure-cors-headers';
|
|
4
|
+
import type {AppConfigArg} from '../../config-types';
|
|
5
|
+
import type {AdminApiContext} from '../../clients';
|
|
6
|
+
|
|
7
|
+
import type {BillingContext} from './billing/types';
|
|
8
|
+
import {RedirectFunction} from './helpers/redirect';
|
|
9
|
+
import {ScopesApiContext} from './scope/types';
|
|
10
|
+
|
|
11
|
+
interface AdminContextInternal<Config extends AppConfigArg> {
|
|
12
|
+
/**
|
|
13
|
+
* The session for the user who made the request.
|
|
14
|
+
*
|
|
15
|
+
* This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.
|
|
16
|
+
*
|
|
17
|
+
* Use this to get shop or user-specific data.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* <caption>Using offline sessions.</caption>
|
|
21
|
+
* <description>Get your app's shop-specific data using an offline session.</description>
|
|
22
|
+
* ```ts
|
|
23
|
+
* // /app/routes/**\/*.ts
|
|
24
|
+
* import { LoaderFunctionArgs, json } from "@remix-run/node";
|
|
25
|
+
* import { authenticate } from "../shopify.server";
|
|
26
|
+
* import { getMyAppData } from "~/db/model.server";
|
|
27
|
+
*
|
|
28
|
+
* export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
29
|
+
* const { session } = await authenticate.admin(request);
|
|
30
|
+
* return json(await getMyAppData({shop: session.shop));
|
|
31
|
+
* };
|
|
32
|
+
* ```
|
|
33
|
+
* ```ts
|
|
34
|
+
* // shopify.server.ts
|
|
35
|
+
* import { shopifyApp } from "@shopify/shopify-app-remix/server";
|
|
36
|
+
*
|
|
37
|
+
* const shopify = shopifyApp({
|
|
38
|
+
* // ...etc
|
|
39
|
+
* });
|
|
40
|
+
* export default shopify;
|
|
41
|
+
* export const authenticate = shopify.authenticate;
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* <caption>Using online sessions.</caption>
|
|
46
|
+
* <description>Get your app's user-specific data using an online session.</description>
|
|
47
|
+
* ```ts
|
|
48
|
+
* // /app/routes/**\/*.ts
|
|
49
|
+
* import { LoaderFunctionArgs, json } from "@remix-run/node";
|
|
50
|
+
* import { authenticate } from "../shopify.server";
|
|
51
|
+
* import { getMyAppData } from "~/db/model.server";
|
|
52
|
+
*
|
|
53
|
+
* export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
54
|
+
* const { session } = await authenticate.admin(request);
|
|
55
|
+
* return json(await getMyAppData({user: session.onlineAccessInfo!.id}));
|
|
56
|
+
* };
|
|
57
|
+
* ```
|
|
58
|
+
* ```ts
|
|
59
|
+
* // shopify.server.ts
|
|
60
|
+
* import { shopifyApp } from "@shopify/shopify-app-remix/server";
|
|
61
|
+
*
|
|
62
|
+
* const shopify = shopifyApp({
|
|
63
|
+
* // ...etc
|
|
64
|
+
* useOnlineTokens: true,
|
|
65
|
+
* });
|
|
66
|
+
* export default shopify;
|
|
67
|
+
* export const authenticate = shopify.authenticate;
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
session: Session;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Methods for interacting with the GraphQL Admin API for the store that made the request.
|
|
74
|
+
*/
|
|
75
|
+
admin: AdminApiContext;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Billing methods for this store, based on the plans defined in the `billing` config option.
|
|
79
|
+
*
|
|
80
|
+
* {@link https://shopify.dev/docs/apps/billing}
|
|
81
|
+
*/
|
|
82
|
+
billing: BillingContext<Config>;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A function that ensures the CORS headers are set correctly for the response.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* <caption>Setting CORS headers for a admin request.</caption>
|
|
89
|
+
* <description>Use the `cors` helper to ensure your app can respond to requests from admin extensions.</description>
|
|
90
|
+
* ```ts
|
|
91
|
+
* // /app/routes/admin/my-route.ts
|
|
92
|
+
* import { LoaderFunctionArgs, json } from "@remix-run/node";
|
|
93
|
+
* import { authenticate } from "../shopify.server";
|
|
94
|
+
* import { getMyAppData } from "~/db/model.server";
|
|
95
|
+
*
|
|
96
|
+
* export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
97
|
+
* const { session, cors } = await authenticate.admin(request);
|
|
98
|
+
* return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));
|
|
99
|
+
* };
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
cors: EnsureCORSFunction;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface EmbeddedAdminContext<
|
|
106
|
+
Config extends AppConfigArg,
|
|
107
|
+
> extends AdminContextInternal<Config> {
|
|
108
|
+
/**
|
|
109
|
+
* The decoded and validated session token for the request.
|
|
110
|
+
*
|
|
111
|
+
* Returned only if `isEmbeddedApp` is `true`.
|
|
112
|
+
*
|
|
113
|
+
* {@link https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload}
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* <caption>Using the decoded session token.</caption>
|
|
117
|
+
* <description>Get user-specific data using the `sessionToken` object.</description>
|
|
118
|
+
* ```ts
|
|
119
|
+
* // /app/routes/**\/*.ts
|
|
120
|
+
* import { LoaderFunctionArgs, json } from "@remix-run/node";
|
|
121
|
+
* import { authenticate } from "../shopify.server";
|
|
122
|
+
* import { getMyAppData } from "~/db/model.server";
|
|
123
|
+
*
|
|
124
|
+
* export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
125
|
+
* const { sessionToken } = await authenticate.admin(
|
|
126
|
+
* request
|
|
127
|
+
* );
|
|
128
|
+
* return json(await getMyAppData({user: sessionToken.sub}));
|
|
129
|
+
* };
|
|
130
|
+
* ```
|
|
131
|
+
* ```ts
|
|
132
|
+
* // shopify.server.ts
|
|
133
|
+
* import { shopifyApp } from "@shopify/shopify-app-remix/server";
|
|
134
|
+
*
|
|
135
|
+
* const shopify = shopifyApp({
|
|
136
|
+
* // ...etc
|
|
137
|
+
* useOnlineTokens: true,
|
|
138
|
+
* });
|
|
139
|
+
* export default shopify;
|
|
140
|
+
* export const authenticate = shopify.authenticate;
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
sessionToken: JwtPayload;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded
|
|
147
|
+
* apps.
|
|
148
|
+
*
|
|
149
|
+
* Returned only if `isEmbeddedApp` is `true`.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* <caption>Redirecting to an app route.</caption>
|
|
153
|
+
* <description>Use the `redirect` helper to safely redirect between pages.</description>
|
|
154
|
+
* ```ts
|
|
155
|
+
* // /app/routes/admin/my-route.ts
|
|
156
|
+
* import { LoaderFunctionArgs, json } from "@remix-run/node";
|
|
157
|
+
* import { authenticate } from "../shopify.server";
|
|
158
|
+
*
|
|
159
|
+
* export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
160
|
+
* const { session, redirect } = await authenticate.admin(request);
|
|
161
|
+
* return redirect("/");
|
|
162
|
+
* };
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* <caption>Redirecting to a page in the Shopify Admin.</caption>
|
|
167
|
+
* <description>Redirects to a product page in the Shopify admin. Pass in a `target` option of `_top` or `_parent` to navigate in the current window, or `_blank` to open a new tab.</description>
|
|
168
|
+
* ```ts
|
|
169
|
+
* // /app/routes/admin/my-route.ts
|
|
170
|
+
* import { LoaderFunctionArgs, json } from "@remix-run/node";
|
|
171
|
+
* import { authenticate } from "../shopify.server";
|
|
172
|
+
*
|
|
173
|
+
* export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
174
|
+
* const { session, redirect } = await authenticate.admin(request);
|
|
175
|
+
* return redirect("shopify://admin/products/123456", { target: '_parent' });
|
|
176
|
+
* };
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* <caption>Redirecting outside of the Admin embedded app page.</caption>
|
|
181
|
+
* <description>Pass in a `target` option of `_top` or `_parent` to navigate in the current window, or `_blank` to open a new tab.</description>
|
|
182
|
+
* ```ts
|
|
183
|
+
* // /app/routes/admin/my-route.ts
|
|
184
|
+
* import { LoaderFunctionArgs, json } from "@remix-run/node";
|
|
185
|
+
* import { authenticate } from "../shopify.server";
|
|
186
|
+
*
|
|
187
|
+
* export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
188
|
+
* const { session, redirect } = await authenticate.admin(request);
|
|
189
|
+
* return redirect("/", { target: '_parent' });
|
|
190
|
+
* };
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
redirect: RedirectFunction;
|
|
194
|
+
}
|
|
195
|
+
export interface NonEmbeddedAdminContext<
|
|
196
|
+
Config extends AppConfigArg,
|
|
197
|
+
> extends AdminContextInternal<Config> {}
|
|
198
|
+
|
|
199
|
+
type EmbeddedTypedAdminContext<Config extends AppConfigArg> =
|
|
200
|
+
Config['isEmbeddedApp'] extends false
|
|
201
|
+
? NonEmbeddedAdminContext<Config>
|
|
202
|
+
: EmbeddedAdminContext<Config>;
|
|
203
|
+
|
|
204
|
+
export interface ScopesContext {
|
|
205
|
+
/**
|
|
206
|
+
* Methods to manage scopes for the store that made the request.
|
|
207
|
+
*/
|
|
208
|
+
scopes: ScopesApiContext;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export type AdminContext<Config extends AppConfigArg> =
|
|
212
|
+
EmbeddedTypedAdminContext<Config> & ScopesContext;
|
|
213
|
+
|
|
214
|
+
export type AuthenticateAdmin<Config extends AppConfigArg> = (
|
|
215
|
+
request: Request,
|
|
216
|
+
) => Promise<AdminContext<Config>>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const APP_BRIDGE_URL =
|
|
2
|
+
'https://cdn.shopify.com/shopifycloud/app-bridge.js';
|
|
3
|
+
|
|
4
|
+
export const REAUTH_URL_HEADER =
|
|
5
|
+
'X-Shopify-API-Request-Failure-Reauthorize-Url';
|
|
6
|
+
|
|
7
|
+
export const RETRY_INVALID_SESSION_HEADER = {
|
|
8
|
+
'X-Shopify-Retry-Invalid-Session-Request': '1',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const CORS_HEADERS = {
|
|
12
|
+
'Access-Control-Allow-Origin': '*',
|
|
13
|
+
'Access-Control-Allow-Headers': 'Authorization',
|
|
14
|
+
'Access-Control-Expose-Headers': REAUTH_URL_HEADER,
|
|
15
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {type ActionFunctionArgs} from '@remix-run/node';
|
|
2
|
+
|
|
3
|
+
import {authenticate} from '../shopify.server';
|
|
4
|
+
|
|
5
|
+
export const action = async ({request}: ActionFunctionArgs) => {
|
|
6
|
+
const {admin, payload} = await authenticate.flow(request);
|
|
7
|
+
|
|
8
|
+
const customerId = payload.properties.customer_id;
|
|
9
|
+
|
|
10
|
+
const response = await admin.graphql(
|
|
11
|
+
`#graphql
|
|
12
|
+
mutation setMetafield($customerId: ID!, $time: String!) {
|
|
13
|
+
metafieldsSet(metafields: {
|
|
14
|
+
ownerId: $customerId
|
|
15
|
+
namespace: "my-app",
|
|
16
|
+
key: "last_flow_update",
|
|
17
|
+
value: $time,
|
|
18
|
+
type: "string",
|
|
19
|
+
}) {
|
|
20
|
+
metafields {
|
|
21
|
+
key
|
|
22
|
+
value
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`,
|
|
27
|
+
{
|
|
28
|
+
variables: {
|
|
29
|
+
customerId,
|
|
30
|
+
time: new Date().toISOString(),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
const body = await response.json();
|
|
35
|
+
|
|
36
|
+
console.log('Updated value', body.data!.metafieldsSet!.metafields![0].value);
|
|
37
|
+
|
|
38
|
+
return new Response();
|
|
39
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
|
|
2
|
+
|
|
3
|
+
const data: ReferenceEntityTemplateSchema = {
|
|
4
|
+
name: 'Flow',
|
|
5
|
+
description:
|
|
6
|
+
'Contains functions for verifying Shopify Flow extensions.' +
|
|
7
|
+
'\n\nSee the [Flow documentation](https://shopify.dev/docs/apps/flow/actions/endpoints) for more information.',
|
|
8
|
+
category: 'Authenticate',
|
|
9
|
+
type: 'object',
|
|
10
|
+
isVisualComponent: false,
|
|
11
|
+
definitions: [
|
|
12
|
+
{
|
|
13
|
+
title: 'authenticate.flow',
|
|
14
|
+
description: 'Verifies requests coming from Shopify Flow extensions.',
|
|
15
|
+
type: 'AuthenticateFlow',
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
defaultExample: {
|
|
19
|
+
description: 'Handle a flow action call',
|
|
20
|
+
codeblock: {
|
|
21
|
+
title: 'Set a metafield on a customer after a flow call',
|
|
22
|
+
tabs: [
|
|
23
|
+
{
|
|
24
|
+
title: '/app/routes/**.ts',
|
|
25
|
+
language: 'typescript',
|
|
26
|
+
code: './authenticate.flow.doc.example.ts',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
jsDocTypeExamples: ['FlowContext'],
|
|
32
|
+
related: [
|
|
33
|
+
{
|
|
34
|
+
name: 'Admin API context',
|
|
35
|
+
subtitle: 'Interact with the Admin API.',
|
|
36
|
+
url: '/docs/api/shopify-app-remix/apis/admin-api',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Flow action endpoints',
|
|
40
|
+
subtitle: 'Receive requests from Flow.',
|
|
41
|
+
url: '/docs/apps/flow/actions/endpoints',
|
|
42
|
+
type: 'shopify',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default data;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {adminClientFactory} from '../../clients/admin';
|
|
2
|
+
import {ensureValidOfflineSession} from '../../helpers';
|
|
3
|
+
import {BasicParams} from '../../types';
|
|
4
|
+
|
|
5
|
+
import type {AuthenticateFlow, FlowContext} from './types';
|
|
6
|
+
|
|
7
|
+
export function authenticateFlowFactory(params: BasicParams): AuthenticateFlow {
|
|
8
|
+
const {api, logger} = params;
|
|
9
|
+
|
|
10
|
+
return async function authenticate(request: Request): Promise<FlowContext> {
|
|
11
|
+
logger.info('Authenticating flow request');
|
|
12
|
+
|
|
13
|
+
if (request.method !== 'POST') {
|
|
14
|
+
logger.debug(
|
|
15
|
+
'Received a non-POST request for flow. Only POST requests are allowed.',
|
|
16
|
+
{url: request.url, method: request.method},
|
|
17
|
+
);
|
|
18
|
+
throw new Response(undefined, {
|
|
19
|
+
status: 405,
|
|
20
|
+
statusText: 'Method not allowed',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const rawBody = await request.text();
|
|
25
|
+
const result = await api.flow.validate({
|
|
26
|
+
rawBody,
|
|
27
|
+
rawRequest: request,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!result.valid) {
|
|
31
|
+
logger.error('Received an invalid flow request', {reason: result.reason});
|
|
32
|
+
|
|
33
|
+
throw new Response(undefined, {
|
|
34
|
+
status: 400,
|
|
35
|
+
statusText: 'Bad Request',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const payload = JSON.parse(rawBody);
|
|
40
|
+
|
|
41
|
+
logger.debug('Flow request is valid, looking for an offline session', {
|
|
42
|
+
shop: payload.shopify_domain,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const session = await ensureValidOfflineSession(
|
|
46
|
+
params,
|
|
47
|
+
payload.shopify_domain,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (!session) {
|
|
51
|
+
logger.info('Flow request could not find session', {
|
|
52
|
+
shop: payload.shopify_domain,
|
|
53
|
+
});
|
|
54
|
+
throw new Response(undefined, {
|
|
55
|
+
status: 400,
|
|
56
|
+
statusText: 'Bad Request',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
logger.debug('Found a session for the flow request', {shop: session.shop});
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
session,
|
|
64
|
+
payload,
|
|
65
|
+
admin: adminClientFactory({params, session}),
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|