@shopify/shopify-app-remix 4.1.1 → 4.2.1
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/AppProxyForm/AppProxyForm.js +4 -3
- package/dist/cjs/react/components/AppProxyForm/AppProxyForm.js.map +1 -1
- package/dist/cjs/react/components/AppProxyLink/AppProxyLink.js +4 -3
- package/dist/cjs/react/components/AppProxyLink/AppProxyLink.js.map +1 -1
- package/dist/cjs/react/components/AppProxyProvider/AppProxyProvider.js.map +1 -1
- package/dist/cjs/server/authenticate/admin/helpers/redirect.js +2 -2
- package/dist/cjs/server/authenticate/admin/helpers/redirect.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/future/flags.js.map +1 -1
- package/dist/cjs/server/shopify-app.js +1 -0
- package/dist/cjs/server/shopify-app.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/AppProxyForm/AppProxyForm.mjs +5 -4
- package/dist/esm/react/components/AppProxyForm/AppProxyForm.mjs.map +1 -1
- package/dist/esm/react/components/AppProxyLink/AppProxyLink.mjs +5 -4
- package/dist/esm/react/components/AppProxyLink/AppProxyLink.mjs.map +1 -1
- package/dist/esm/react/components/AppProxyProvider/AppProxyProvider.mjs.map +1 -1
- package/dist/esm/server/authenticate/admin/helpers/redirect.mjs +2 -2
- package/dist/esm/server/authenticate/admin/helpers/redirect.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/future/flags.mjs.map +1 -1
- package/dist/esm/server/shopify-app.mjs +1 -0
- package/dist/esm/server/shopify-app.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 +5 -1
- package/dist/ts/react/components/AppProvider/AppProvider.d.ts.map +1 -1
- package/dist/ts/react/components/AppProxyForm/AppProxyForm.d.ts +2 -1
- package/dist/ts/react/components/AppProxyForm/AppProxyForm.d.ts.map +1 -1
- package/dist/ts/react/components/AppProxyLink/AppProxyLink.d.ts +2 -1
- package/dist/ts/react/components/AppProxyLink/AppProxyLink.d.ts.map +1 -1
- package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.d.ts +5 -1
- package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/billing/types.d.ts +4 -0
- package/dist/ts/server/authenticate/admin/billing/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/admin/scope/types.d.ts +2 -1
- package/dist/ts/server/authenticate/admin/scope/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 +10 -0
- package/dist/ts/server/authenticate/admin/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/flow/types.d.ts +4 -0
- package/dist/ts/server/authenticate/flow/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/fulfillment-service/types.d.ts +4 -0
- package/dist/ts/server/authenticate/fulfillment-service/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/public/appProxy/types.d.ts +4 -0
- package/dist/ts/server/authenticate/public/appProxy/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/public/checkout/types.d.ts +4 -0
- package/dist/ts/server/authenticate/public/checkout/types.d.ts.map +1 -1
- package/dist/ts/server/authenticate/public/customer-account/types.d.ts +4 -0
- package/dist/ts/server/authenticate/public/customer-account/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 +36 -3
- package/dist/ts/server/authenticate/webhooks/types.d.ts.map +1 -1
- package/dist/ts/server/boundary/error.d.ts +1 -1
- package/dist/ts/server/boundary/error.d.ts.map +1 -1
- package/dist/ts/server/clients/admin/types.d.ts +4 -0
- package/dist/ts/server/clients/admin/types.d.ts.map +1 -1
- package/dist/ts/server/clients/storefront/types.d.ts +4 -0
- package/dist/ts/server/clients/storefront/types.d.ts.map +1 -1
- package/dist/ts/server/config-types.d.ts.map +1 -1
- package/dist/ts/server/future/flags.d.ts +6 -0
- package/dist/ts/server/future/flags.d.ts.map +1 -1
- package/dist/ts/server/shopify-app.d.ts +1 -0
- package/dist/ts/server/shopify-app.d.ts.map +1 -1
- package/dist/ts/server/unauthenticated/admin/types.d.ts +4 -0
- package/dist/ts/server/unauthenticated/admin/types.d.ts.map +1 -1
- package/dist/ts/server/unauthenticated/storefront/types.d.ts +4 -0
- package/dist/ts/server/unauthenticated/storefront/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 +19 -15
- package/src/react/components/AppProvider/AppProvider.tsx +151 -0
- package/src/react/components/AppProvider/index.ts +1 -0
- package/src/react/components/AppProxyForm/AppProxyForm.tsx +85 -0
- package/src/react/components/AppProxyForm/index.ts +1 -0
- package/src/react/components/AppProxyLink/AppProxyLink.tsx +61 -0
- package/src/react/components/AppProxyLink/index.ts +1 -0
- package/src/react/components/AppProxyProvider/AppProxyProvider.tsx +104 -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 +32 -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.ts +232 -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 +617 -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/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 +127 -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 +226 -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.ts +68 -0
- package/src/server/authenticate/flow/types.ts +89 -0
- package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.example.ts +52 -0
- package/src/server/authenticate/fulfillment-service/authenticate.ts +82 -0
- package/src/server/authenticate/fulfillment-service/types.ts +86 -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.ts +150 -0
- package/src/server/authenticate/public/appProxy/types.ts +219 -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.ts +13 -0
- package/src/server/authenticate/public/checkout/types.ts +69 -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.ts +13 -0
- package/src/server/authenticate/public/customer-account/types.ts +69 -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/index.ts +1 -0
- package/src/server/authenticate/webhooks/register.ts +48 -0
- package/src/server/authenticate/webhooks/types.ts +267 -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/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 +124 -0
- package/src/server/clients/index.ts +2 -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 +82 -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 +68 -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.ts +216 -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 +83 -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 +109 -0
- package/src/server/unauthenticated/types.ts +65 -0
- package/src/server/version.ts +1 -0
- package/dist/ts/react/components/AppProvider/AppProvider.doc.d.ts +0 -4
- package/dist/ts/react/components/AppProvider/AppProvider.doc.d.ts.map +0 -1
- package/dist/ts/react/components/AppProxyForm/AppProxyForm.doc.d.ts +0 -4
- package/dist/ts/react/components/AppProxyForm/AppProxyForm.doc.d.ts.map +0 -1
- package/dist/ts/react/components/AppProxyLink/AppProxyLink.doc.d.ts +0 -4
- package/dist/ts/react/components/AppProxyLink/AppProxyLink.doc.d.ts.map +0 -1
- package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.doc.d.ts +0 -4
- package/dist/ts/react/components/AppProxyProvider/AppProxyProvider.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/admin/authenticate.admin.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/admin/authenticate.admin.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/admin/billing/authenticate.admin.billing.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/admin/billing/authenticate.admin.billing.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/admin/scope/authenticate.admin.scopes.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/admin/scope/authenticate.admin.scopes.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/flow/authenticate.flow.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/flow/authenticate.flow.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/public/appProxy/authenticate.public.app-proxy.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/public/checkout/authenticate.public.checkout.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/public/checkout/authenticate.public.checkout.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/public/customer-account/authenticate.public.customerAccount.doc.d.ts.map +0 -1
- package/dist/ts/server/authenticate/webhooks/authenticate.webhooks.doc.d.ts +0 -4
- package/dist/ts/server/authenticate/webhooks/authenticate.webhooks.doc.d.ts.map +0 -1
- package/dist/ts/server/clients/admin/authenticate.admin.api.doc.d.ts +0 -4
- package/dist/ts/server/clients/admin/authenticate.admin.api.doc.d.ts.map +0 -1
- package/dist/ts/server/clients/storefront/authenticate.storefront.api.doc.d.ts +0 -4
- package/dist/ts/server/clients/storefront/authenticate.storefront.api.doc.d.ts.map +0 -1
- package/dist/ts/server/shopify-app.doc.d.ts +0 -4
- package/dist/ts/server/shopify-app.doc.d.ts.map +0 -1
- package/dist/ts/server/unauthenticated/admin/unauthenticated.admin.doc.d.ts +0 -4
- package/dist/ts/server/unauthenticated/admin/unauthenticated.admin.doc.d.ts.map +0 -1
- package/dist/ts/server/unauthenticated/storefront/unauthenticated.storefront.doc.d.ts +0 -4
- package/dist/ts/server/unauthenticated/storefront/unauthenticated.storefront.doc.d.ts.map +0 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import type {AdminApiContext} from '../../clients';
|
|
4
|
+
|
|
5
|
+
export interface FlowContext {
|
|
6
|
+
/**
|
|
7
|
+
* A session with an offline token for the shop.
|
|
8
|
+
*
|
|
9
|
+
* Returned only if there is a session for the shop.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* <caption>Shopify session for the Flow request.</caption>
|
|
13
|
+
* <description>Use the session associated with this request.</description>
|
|
14
|
+
* ```ts
|
|
15
|
+
* // /app/routes/flow.tsx
|
|
16
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
17
|
+
* import { authenticate } from "../shopify.server";
|
|
18
|
+
*
|
|
19
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
20
|
+
* const { session, admin } = await authenticate.flow(request);
|
|
21
|
+
*
|
|
22
|
+
* console.log(session.id)
|
|
23
|
+
*
|
|
24
|
+
* return new Response();
|
|
25
|
+
* };
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
session: Session;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The payload from the Flow request.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* <caption>Flow payload.</caption>
|
|
35
|
+
* <description>Get the request's POST payload.</description>
|
|
36
|
+
* ```ts
|
|
37
|
+
* // /app/routes/flow.tsx
|
|
38
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
39
|
+
* import { authenticate } from "../shopify.server";
|
|
40
|
+
*
|
|
41
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
42
|
+
* const { payload } = await authenticate.flow(request);
|
|
43
|
+
* return new Response();
|
|
44
|
+
* };
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
payload: any;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* An admin context for the Flow request.
|
|
51
|
+
*
|
|
52
|
+
* Returned only if there is a session for the shop.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* <caption>Flow admin context.</caption>
|
|
56
|
+
* <description>Use the `admin` object in the context to interact with the Admin API.</description>
|
|
57
|
+
* ```ts
|
|
58
|
+
* // /app/routes/flow.tsx
|
|
59
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
60
|
+
* import { authenticate } from "../shopify.server";
|
|
61
|
+
*
|
|
62
|
+
* export async function action({ request }: ActionFunctionArgs) {
|
|
63
|
+
* const { admin } = await authenticate.flow(request);
|
|
64
|
+
*
|
|
65
|
+
* const response = await admin?.graphql(
|
|
66
|
+
* `#graphql
|
|
67
|
+
* mutation populateProduct($input: ProductInput!) {
|
|
68
|
+
* productCreate(input: $input) {
|
|
69
|
+
* product {
|
|
70
|
+
* id
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
* }`,
|
|
74
|
+
* { variables: { input: { title: "Product Name" } } }
|
|
75
|
+
* );
|
|
76
|
+
*
|
|
77
|
+
* const productData = await response.json();
|
|
78
|
+
* return json({ data: productData.data });
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
admin: AdminApiContext;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Verifies requests coming from Shopify Flow extensions.
|
|
87
|
+
* @publicDocs
|
|
88
|
+
*/
|
|
89
|
+
export type AuthenticateFlow = (request: Request) => Promise<FlowContext>;
|
package/src/server/authenticate/fulfillment-service/authenticate.fulfillment-service.doc.example.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
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.fulfillmentService(request);
|
|
7
|
+
|
|
8
|
+
const kind = payload.kind;
|
|
9
|
+
|
|
10
|
+
if (kind === 'FULFILLMENT_REQUEST') {
|
|
11
|
+
const response = await admin?.graphql(
|
|
12
|
+
`#graphql
|
|
13
|
+
query {
|
|
14
|
+
shop {
|
|
15
|
+
assignedFulfillmentOrders(first: 10, assignmentStatus: FULFILLMENT_REQUESTED) {
|
|
16
|
+
edges {
|
|
17
|
+
node {
|
|
18
|
+
id
|
|
19
|
+
destination {
|
|
20
|
+
firstName
|
|
21
|
+
lastName
|
|
22
|
+
}
|
|
23
|
+
lineItems(first: 10) {
|
|
24
|
+
edges {
|
|
25
|
+
node {
|
|
26
|
+
id
|
|
27
|
+
productTitle
|
|
28
|
+
sku
|
|
29
|
+
remainingQuantity
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
merchantRequests(first: 10, kind: FULFILLMENT_REQUEST) {
|
|
34
|
+
edges {
|
|
35
|
+
node {
|
|
36
|
+
message
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}`,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const fulfillments = await response.json();
|
|
48
|
+
console.log(fulfillments);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return new Response();
|
|
52
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {ShopifyHeader} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import {adminClientFactory} from '../../clients/admin';
|
|
4
|
+
import {BasicParams} from '../../types';
|
|
5
|
+
import {ensureValidOfflineSession} from '../../helpers';
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
AuthenticateFulfillmentService,
|
|
9
|
+
FulfillmentServiceContext,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
export function authenticateFulfillmentServiceFactory(
|
|
13
|
+
params: BasicParams,
|
|
14
|
+
): AuthenticateFulfillmentService {
|
|
15
|
+
const {api, logger} = params;
|
|
16
|
+
|
|
17
|
+
return async function authenticate(
|
|
18
|
+
request: Request,
|
|
19
|
+
): Promise<FulfillmentServiceContext> {
|
|
20
|
+
logger.info('Authenticating fulfillment service request');
|
|
21
|
+
|
|
22
|
+
if (request.method !== 'POST') {
|
|
23
|
+
logger.debug(
|
|
24
|
+
'Received a non-POST request for fulfillment service. Only POST requests are allowed.',
|
|
25
|
+
{url: request.url, method: request.method},
|
|
26
|
+
);
|
|
27
|
+
throw new Response(undefined, {
|
|
28
|
+
status: 405,
|
|
29
|
+
statusText: 'Method not allowed',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const rawBody = await request.text();
|
|
34
|
+
const result = await api.fulfillmentService.validate({
|
|
35
|
+
rawBody,
|
|
36
|
+
rawRequest: request,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!result.valid) {
|
|
40
|
+
logger.error('Received an invalid fulfillment service request', {
|
|
41
|
+
reason: result.reason,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
throw new Response(undefined, {
|
|
45
|
+
status: 400,
|
|
46
|
+
statusText: 'Bad Request',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const payload = JSON.parse(rawBody);
|
|
51
|
+
const shop = request.headers.get(ShopifyHeader.Domain) || '';
|
|
52
|
+
|
|
53
|
+
logger.debug(
|
|
54
|
+
'Fulfillment service request is valid, looking for an offline session',
|
|
55
|
+
{
|
|
56
|
+
shop,
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const session = await ensureValidOfflineSession(params, shop);
|
|
61
|
+
|
|
62
|
+
if (!session) {
|
|
63
|
+
logger.info('Fulfillment service request could not find session', {
|
|
64
|
+
shop,
|
|
65
|
+
});
|
|
66
|
+
throw new Response(undefined, {
|
|
67
|
+
status: 400,
|
|
68
|
+
statusText: 'Bad Request',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
logger.debug('Found a session for the fulfillment service request', {
|
|
73
|
+
shop,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
session,
|
|
78
|
+
payload,
|
|
79
|
+
admin: adminClientFactory({params, session}),
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import type {AdminApiContext} from '../../clients';
|
|
4
|
+
|
|
5
|
+
export type FulfillmentServicePayload = Record<string, any> & {
|
|
6
|
+
kind: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export interface FulfillmentServiceContext {
|
|
10
|
+
/**
|
|
11
|
+
* A session with an offline token for the shop.
|
|
12
|
+
*
|
|
13
|
+
* Returned only if there is a session for the shop.
|
|
14
|
+
* @example
|
|
15
|
+
* <caption>Shopify session for the fulfillment service notification request.</caption>
|
|
16
|
+
* <description>Use the session associated with this request.</description>
|
|
17
|
+
* ```ts
|
|
18
|
+
* // /app/routes/fulfillment_service_notification.tsx
|
|
19
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
20
|
+
* import { authenticate } from "../shopify.server";
|
|
21
|
+
*
|
|
22
|
+
* export const action = async ({ request }: ActionFunctionArgs) => {
|
|
23
|
+
* const { session, admin } = await authenticate.fulfillmentService(request);
|
|
24
|
+
*
|
|
25
|
+
* console.log(session.id)
|
|
26
|
+
*
|
|
27
|
+
* return new Response();
|
|
28
|
+
* };
|
|
29
|
+
* ```
|
|
30
|
+
* */
|
|
31
|
+
session: Session;
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* An admin context for the fulfillment service request.
|
|
35
|
+
*
|
|
36
|
+
* Returned only if there is a session for the shop.
|
|
37
|
+
* @example
|
|
38
|
+
* <caption>Shopify session for the fulfillment service request.</caption>
|
|
39
|
+
* <description>Use the session associated with this request to use the Admin GraphQL API </description>
|
|
40
|
+
* ```ts
|
|
41
|
+
* // /app/routes/fulfillment_order_notification.ts
|
|
42
|
+
* import { ActionFunctionArgs } from "@remix-run/node";
|
|
43
|
+
* import { authenticate } from "../shopify.server";
|
|
44
|
+
*
|
|
45
|
+
* export async function action({ request }: ActionFunctionArgs) {
|
|
46
|
+
* const { admin, session } = await authenticate.fulfillmentService(request);
|
|
47
|
+
*
|
|
48
|
+
* console.log(session.id)
|
|
49
|
+
*
|
|
50
|
+
* return new Response();
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
admin: AdminApiContext;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The payload from the fulfillment service request.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* <caption>Fulfillment service request payload.</caption>
|
|
61
|
+
* <description>Get the request's POST payload.</description>
|
|
62
|
+
* ```ts
|
|
63
|
+
* /app/routes/fulfillment_order_notification.ts
|
|
64
|
+
* import { ActionFunction } from "@remix-run/node";
|
|
65
|
+
* import { authenticate } from "../shopify.server";
|
|
66
|
+
*
|
|
67
|
+
* export const action: ActionFunction = async ({ request }) => {
|
|
68
|
+
* const { payload } = await authenticate.fulfillmentService(request);
|
|
69
|
+
* if(payload.kind === 'FULFILLMENT_REQUEST') {
|
|
70
|
+
* // handle fulfillment request
|
|
71
|
+
* } else if (payload.kind === 'CANCELLATION_REQUEST') {
|
|
72
|
+
* // handle cancellation request
|
|
73
|
+
* };
|
|
74
|
+
* return new Response();
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
payload: FulfillmentServicePayload;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Verifies requests coming from Shopify to fulfillment service apps.
|
|
82
|
+
* @publicDocs
|
|
83
|
+
*/
|
|
84
|
+
export type AuthenticateFulfillmentService = (
|
|
85
|
+
request: Request,
|
|
86
|
+
) => Promise<FulfillmentServiceContext>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {BasicParams} from '../../types';
|
|
2
|
+
|
|
3
|
+
export type AddDocumentResponseHeadersFunction = (
|
|
4
|
+
request: Request,
|
|
5
|
+
headers: Headers,
|
|
6
|
+
) => void;
|
|
7
|
+
|
|
8
|
+
export function addDocumentResponseHeadersFactory(
|
|
9
|
+
params: BasicParams,
|
|
10
|
+
): AddDocumentResponseHeadersFunction {
|
|
11
|
+
const {api, config} = params;
|
|
12
|
+
|
|
13
|
+
return function (request: Request, headers: Headers) {
|
|
14
|
+
const {searchParams} = new URL(request.url);
|
|
15
|
+
const shop = api.utils.sanitizeShop(searchParams.get('shop')!);
|
|
16
|
+
|
|
17
|
+
addDocumentResponseHeaders(headers, config.isEmbeddedApp, shop);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function addDocumentResponseHeaders(
|
|
22
|
+
headers: Headers,
|
|
23
|
+
isEmbeddedApp: boolean,
|
|
24
|
+
shop: string | null | undefined,
|
|
25
|
+
) {
|
|
26
|
+
if (shop) {
|
|
27
|
+
headers.set(
|
|
28
|
+
'Link',
|
|
29
|
+
'<https://cdn.shopify.com/shopifycloud/app-bridge.js>; rel="preload"; as="script";',
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (isEmbeddedApp) {
|
|
34
|
+
if (shop) {
|
|
35
|
+
headers.set(
|
|
36
|
+
'Content-Security-Policy',
|
|
37
|
+
`frame-ancestors https://${shop} https://admin.shopify.com https://*.spin.dev https://admin.myshopify.io https://admin.shop.dev;`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
headers.set('Content-Security-Policy', `frame-ancestors 'none';`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {APP_BRIDGE_URL} from '../const';
|
|
2
|
+
|
|
3
|
+
let appBridgeUrlOverride: string | undefined;
|
|
4
|
+
export function setAppBridgeUrlOverride(url: string) {
|
|
5
|
+
appBridgeUrlOverride = url;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function appBridgeUrl() {
|
|
9
|
+
return appBridgeUrlOverride || APP_BRIDGE_URL;
|
|
10
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {BasicParams} from '../../types';
|
|
2
|
+
import {REAUTH_URL_HEADER} from '../const';
|
|
3
|
+
|
|
4
|
+
export interface EnsureCORSFunction {
|
|
5
|
+
(response: Response): Response;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ensureCORSHeadersFactory(
|
|
9
|
+
params: BasicParams,
|
|
10
|
+
request: Request,
|
|
11
|
+
corsHeaders: string[] = [],
|
|
12
|
+
): EnsureCORSFunction {
|
|
13
|
+
const {logger, config} = params;
|
|
14
|
+
|
|
15
|
+
return function ensureCORSHeaders(response) {
|
|
16
|
+
const origin = request.headers.get('Origin');
|
|
17
|
+
if (origin && origin !== config.appUrl) {
|
|
18
|
+
logger.debug(
|
|
19
|
+
'Request comes from a different origin, adding CORS headers',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const corsHeadersSet = new Set([
|
|
23
|
+
'Authorization',
|
|
24
|
+
'Content-Type',
|
|
25
|
+
...corsHeaders,
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
response.headers.set('Access-Control-Allow-Origin', '*');
|
|
29
|
+
response.headers.set(
|
|
30
|
+
'Access-Control-Allow-Headers',
|
|
31
|
+
[...corsHeadersSet].join(', '),
|
|
32
|
+
);
|
|
33
|
+
response.headers.set('Access-Control-Expose-Headers', REAUTH_URL_HEADER);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return response;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const SESSION_TOKEN_PARAM = 'id_token';
|
|
2
|
+
|
|
3
|
+
export function getSessionTokenHeader(request: Request): string | undefined {
|
|
4
|
+
return request.headers.get('authorization')?.replace('Bearer ', '');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getSessionTokenFromUrlParam(request: Request): string | null {
|
|
8
|
+
const url = new URL(request.url);
|
|
9
|
+
|
|
10
|
+
return url.searchParams.get(SESSION_TOKEN_PARAM);
|
|
11
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface IdempotentHandlePromiseParams {
|
|
2
|
+
promiseFunction: () => Promise<any>;
|
|
3
|
+
identifier: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const IDENTIFIER_TTL_MS = 60000;
|
|
7
|
+
|
|
8
|
+
export class IdempotentPromiseHandler {
|
|
9
|
+
protected identifiers: Map<string, number>;
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.identifiers = new Map<string, number>();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async handlePromise({
|
|
16
|
+
promiseFunction,
|
|
17
|
+
identifier,
|
|
18
|
+
}: IdempotentHandlePromiseParams): Promise<any> {
|
|
19
|
+
try {
|
|
20
|
+
if (this.isPromiseRunnable(identifier)) {
|
|
21
|
+
await promiseFunction();
|
|
22
|
+
}
|
|
23
|
+
} finally {
|
|
24
|
+
this.clearStaleIdentifiers();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return Promise.resolve();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private isPromiseRunnable(identifier: string) {
|
|
31
|
+
if (!this.identifiers.has(identifier)) {
|
|
32
|
+
this.identifiers.set(identifier, Date.now());
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private async clearStaleIdentifiers() {
|
|
39
|
+
this.identifiers.forEach((date, identifier, map) => {
|
|
40
|
+
if (Date.now() - date > IDENTIFIER_TTL_MS) {
|
|
41
|
+
map.delete(identifier);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './app-bridge-url';
|
|
2
|
+
export * from './add-response-headers';
|
|
3
|
+
export * from './ensure-cors-headers';
|
|
4
|
+
export * from './validate-session-token';
|
|
5
|
+
export * from './get-session-token-header';
|
|
6
|
+
export * from './invalidate-access-token';
|
|
7
|
+
export * from './reject-bot-request';
|
|
8
|
+
export * from './respond-to-options-request';
|
|
9
|
+
export * from './respond-to-invalid-session-token';
|
|
10
|
+
export * from './get-shop-from-request';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {Session} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import type {BasicParams} from '../../types';
|
|
4
|
+
|
|
5
|
+
export async function invalidateAccessToken(
|
|
6
|
+
params: BasicParams,
|
|
7
|
+
session: Session,
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
const {logger, config} = params;
|
|
10
|
+
|
|
11
|
+
logger.debug(`Invalidating access token for session - ${session.id}`, {
|
|
12
|
+
shop: session.shop,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
session.accessToken = undefined;
|
|
16
|
+
await config.sessionStorage!.storeSession(session);
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {isbot} from 'isbot';
|
|
2
|
+
|
|
3
|
+
import type {BasicParams} from '../../types';
|
|
4
|
+
|
|
5
|
+
const SHOPIFY_POS_USER_AGENT = /Shopify POS\//;
|
|
6
|
+
const SHOPIFY_MOBILE_USER_AGENT = /Shopify Mobile\//;
|
|
7
|
+
|
|
8
|
+
const SHOPIFY_USER_AGENTS = [SHOPIFY_POS_USER_AGENT, SHOPIFY_MOBILE_USER_AGENT];
|
|
9
|
+
|
|
10
|
+
export function respondToBotRequest(
|
|
11
|
+
{logger}: BasicParams,
|
|
12
|
+
request: Request,
|
|
13
|
+
): void | never {
|
|
14
|
+
const userAgent = request.headers.get('User-Agent') ?? '';
|
|
15
|
+
|
|
16
|
+
// We call isbot below to prevent good (self-identifying) bots from triggering auth requests, but there are some
|
|
17
|
+
// Shopify-specific cases we want to allow that are identified as bots by isbot.
|
|
18
|
+
if (SHOPIFY_USER_AGENTS.some((agent) => agent.test(userAgent))) {
|
|
19
|
+
logger.debug('Request is from a Shopify agent, allow');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isbot(userAgent)) {
|
|
24
|
+
logger.debug('Request is from a bot, skipping auth');
|
|
25
|
+
throw new Response(undefined, {status: 410, statusText: 'Gone'});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {redirectToBouncePage} from '../admin/helpers/redirect-to-bounce-page';
|
|
2
|
+
import {RETRY_INVALID_SESSION_HEADER} from '../const';
|
|
3
|
+
import {BasicParams} from '../../types';
|
|
4
|
+
|
|
5
|
+
interface RespondToInvalidSessionTokenParams {
|
|
6
|
+
params: BasicParams;
|
|
7
|
+
request: Request;
|
|
8
|
+
retryRequest?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function respondToInvalidSessionToken({
|
|
12
|
+
params,
|
|
13
|
+
request,
|
|
14
|
+
retryRequest = false,
|
|
15
|
+
}: RespondToInvalidSessionTokenParams) {
|
|
16
|
+
const {api, logger, config} = params;
|
|
17
|
+
|
|
18
|
+
const isDocumentRequest = !request.headers.get('authorization');
|
|
19
|
+
if (isDocumentRequest) {
|
|
20
|
+
return redirectToBouncePage({api, logger, config}, new URL(request.url));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
throw new Response(undefined, {
|
|
24
|
+
status: 401,
|
|
25
|
+
statusText: 'Unauthorized',
|
|
26
|
+
headers: retryRequest ? RETRY_INVALID_SESSION_HEADER : {},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {BasicParams} from '../../types';
|
|
2
|
+
|
|
3
|
+
import {ensureCORSHeadersFactory} from './ensure-cors-headers';
|
|
4
|
+
|
|
5
|
+
export function respondToOptionsRequest(
|
|
6
|
+
params: BasicParams,
|
|
7
|
+
request: Request,
|
|
8
|
+
corsHeaders?: string[],
|
|
9
|
+
) {
|
|
10
|
+
if (request.method === 'OPTIONS') {
|
|
11
|
+
const ensureCORSHeaders = ensureCORSHeadersFactory(
|
|
12
|
+
params,
|
|
13
|
+
request,
|
|
14
|
+
corsHeaders,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
throw ensureCORSHeaders(
|
|
18
|
+
new Response(null, {
|
|
19
|
+
status: 204,
|
|
20
|
+
headers: {
|
|
21
|
+
'Access-Control-Max-Age': '7200',
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {JwtPayload} from '@shopify/shopify-api';
|
|
2
|
+
|
|
3
|
+
import type {BasicParams} from '../../types';
|
|
4
|
+
|
|
5
|
+
import {respondToInvalidSessionToken} from './respond-to-invalid-session-token';
|
|
6
|
+
import {getShopFromRequest} from './get-shop-from-request';
|
|
7
|
+
|
|
8
|
+
interface ValidateSessionTokenOptions {
|
|
9
|
+
checkAudience?: boolean;
|
|
10
|
+
retryRequest?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function validateSessionToken(
|
|
14
|
+
params: BasicParams,
|
|
15
|
+
request: Request,
|
|
16
|
+
token: string,
|
|
17
|
+
{checkAudience = true, retryRequest = true}: ValidateSessionTokenOptions = {},
|
|
18
|
+
): Promise<JwtPayload> {
|
|
19
|
+
const {api, logger} = params;
|
|
20
|
+
const shop = getShopFromRequest(request);
|
|
21
|
+
logger.debug('Validating session token', {shop});
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const payload = await api.session.decodeSessionToken(token, {
|
|
25
|
+
checkAudience,
|
|
26
|
+
});
|
|
27
|
+
logger.debug('Session token is valid - validated', {
|
|
28
|
+
shop,
|
|
29
|
+
payload: JSON.stringify(payload),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return payload;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
logger.debug(`Failed to validate session token: ${error.message}`, {
|
|
35
|
+
shop,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
throw respondToInvalidSessionToken({params, request, retryRequest});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {redirect} from '@remix-run/server-runtime';
|
|
2
|
+
|
|
3
|
+
import {BasicParams, LoginError, LoginErrorType} from '../../types';
|
|
4
|
+
|
|
5
|
+
export function loginFactory(params: BasicParams) {
|
|
6
|
+
const {api, config, logger} = params;
|
|
7
|
+
|
|
8
|
+
return async function login(request: Request): Promise<LoginError | never> {
|
|
9
|
+
const url = new URL(request.url);
|
|
10
|
+
const shopParam = url.searchParams.get('shop');
|
|
11
|
+
|
|
12
|
+
if (request.method === 'GET' && !shopParam) {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const shop: string | null =
|
|
17
|
+
shopParam || ((await request.formData()).get('shop') as string);
|
|
18
|
+
|
|
19
|
+
if (!shop) {
|
|
20
|
+
logger.debug('Missing shop parameter', {shop});
|
|
21
|
+
return {shop: LoginErrorType.MissingShop};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const shopWithoutProtocol = shop
|
|
25
|
+
.replace(/^https?:\/\//, '')
|
|
26
|
+
.replace(/\/$/, '');
|
|
27
|
+
const shopWithDomain =
|
|
28
|
+
shop?.indexOf('.') === -1
|
|
29
|
+
? `${shopWithoutProtocol}.myshopify.com`
|
|
30
|
+
: shopWithoutProtocol;
|
|
31
|
+
const sanitizedShop = api.utils.sanitizeShop(shopWithDomain);
|
|
32
|
+
|
|
33
|
+
if (!sanitizedShop) {
|
|
34
|
+
logger.debug('Invalid shop parameter', {shop});
|
|
35
|
+
return {shop: LoginErrorType.InvalidShop};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const authPath = `${config.appUrl}${config.auth.path}?shop=${sanitizedShop}`;
|
|
39
|
+
|
|
40
|
+
const adminPath = api.utils.legacyUrlToShopAdminUrl(sanitizedShop);
|
|
41
|
+
const installPath = `https://${adminPath}/oauth/install?client_id=${config.apiKey}`;
|
|
42
|
+
|
|
43
|
+
const shouldInstall =
|
|
44
|
+
config.isEmbeddedApp && config.future.unstable_newEmbeddedAuthStrategy;
|
|
45
|
+
const redirectUrl = shouldInstall ? installPath : authPath;
|
|
46
|
+
|
|
47
|
+
logger.info(`Redirecting login request to ${redirectUrl}`, {
|
|
48
|
+
shop: sanitizedShop,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
throw redirect(redirectUrl);
|
|
52
|
+
};
|
|
53
|
+
}
|