UNPKG

@shopify/shopify-app-remix

Version:

Shopify Remix - to simplify the building of Shopify Apps with Remix

152 lines (149 loc) 7.31 kB
import { ensureCORSHeadersFactory } from '../helpers/ensure-cors-headers.mjs'; import { validateSessionToken } from '../helpers/validate-session-token.mjs'; import { getSessionTokenHeader, getSessionTokenFromUrlParam } from '../helpers/get-session-token-header.mjs'; import { respondToBotRequest } from '../helpers/reject-bot-request.mjs'; import { respondToOptionsRequest } from '../helpers/respond-to-options-request.mjs'; import '@remix-run/server-runtime'; import { getShopFromRequest } from '../helpers/get-shop-from-request.mjs'; import { cancelBillingFactory } from './billing/cancel.mjs'; import { requireBillingFactory } from './billing/require.mjs'; import { requestBillingFactory } from './billing/request.mjs'; import { checkBillingFactory } from './billing/check.mjs'; import { createUsageRecordFactory } from './billing/create-usage-record.mjs'; import { updateUsageCappedAmountFactory } from './billing/update-usage-subscription-capped-amount.mjs'; import { createAdminApiContext } from './helpers/create-admin-api-context.mjs'; import { ensureAppIsEmbeddedIfRequired } from './helpers/ensure-app-is-embedded-if-required.mjs'; import { ensureSessionTokenSearchParamIfRequired } from './helpers/ensure-session-token-search-param-if-required.mjs'; import '@shopify/shopify-api'; import { redirectFactory } from './helpers/redirect.mjs'; import { renderAppBridge } from './helpers/render-app-bridge.mjs'; import { validateShopAndHostParams } from './helpers/validate-shop-and-host-params.mjs'; import { scopesApiFactory } from './scope/factory.mjs'; function authStrategyFactory({ strategy, ...params }) { const { api, logger, config } = params; async function respondToBouncePageRequest(request) { const url = new URL(request.url); if (url.pathname === config.auth.patchSessionTokenPath) { logger.debug('Rendering bounce page', { shop: getShopFromRequest(request), }); throw renderAppBridge({ config}, request); } } async function respondToExitIframeRequest(request) { const url = new URL(request.url); if (url.pathname === config.auth.exitIframePath) { const destination = url.searchParams.get('exitIframe'); logger.debug('Rendering exit iframe page', { shop: getShopFromRequest(request), destination, }); throw renderAppBridge({ config}, request, { url: destination }); } } function createContext(request, session, authStrategy, sessionToken) { let context = { admin: createAdminApiContext(session, params, authStrategy.handleClientError(request)), billing: { require: requireBillingFactory(params, request, session), check: checkBillingFactory(params, request, session), request: requestBillingFactory(params, request, session), cancel: cancelBillingFactory(params, request, session), createUsageRecord: createUsageRecordFactory(params, request, session), updateUsageCappedAmount: updateUsageCappedAmountFactory(params, request, session), }, session, cors: ensureCORSHeadersFactory(params, request), }; context = addEmbeddedFeatures(context, request, session, sessionToken); context = addScopesFeatures(context); return context; } function addEmbeddedFeatures(context, request, session, sessionToken) { if (config.isEmbeddedApp) { return { ...context, sessionToken, redirect: redirectFactory(params, request, session.shop), }; } return context; } function addScopesFeatures(context) { return { ...context, scopes: scopesApiFactory(params, context.session, context.admin), }; } return async function authenticateAdmin(request) { try { respondToBotRequest(params, request); respondToOptionsRequest(params, request); await respondToBouncePageRequest(request); await respondToExitIframeRequest(request); await strategy.respondToOAuthRequests(request); // If this is a valid request, but it doesn't have a session token header, this is a document request. We need to // ensure we're embedded if needed and we have the information needed to load the session. if (!getSessionTokenHeader(request)) { validateShopAndHostParams(params, request); await ensureAppIsEmbeddedIfRequired(params, request); await ensureSessionTokenSearchParamIfRequired(params, request); } logger.info('Authenticating admin request', { shop: getShopFromRequest(request), }); const { payload, shop, sessionId, sessionToken } = await getSessionTokenContext(params, request); logger.debug('Loading session from storage', { shop, sessionId }); const existingSession = sessionId ? await config.sessionStorage.loadSession(sessionId) : undefined; const session = await strategy.authenticate(request, { session: existingSession, sessionToken, shop, }); return createContext(request, session, strategy, payload); } catch (errorOrResponse) { if (errorOrResponse instanceof Response) { logger.debug('Authenticate returned a response', { shop: getShopFromRequest(request), }); ensureCORSHeadersFactory(params, request)(errorOrResponse); } throw errorOrResponse; } }; } async function getSessionTokenContext(params, request) { const { api, config, logger } = params; const headerSessionToken = getSessionTokenHeader(request); const searchParamSessionToken = getSessionTokenFromUrlParam(request); const sessionToken = (headerSessionToken || searchParamSessionToken); logger.debug('Attempting to authenticate session token', { shop: getShopFromRequest(request), sessionToken: JSON.stringify({ header: headerSessionToken, search: searchParamSessionToken, }), }); if (config.isEmbeddedApp) { const payload = await validateSessionToken(params, request, sessionToken); const dest = new URL(payload.dest); const shop = dest.hostname; logger.debug('Session token is valid - authenticated', { shop, payload }); const sessionId = config.useOnlineTokens ? api.session.getJwtSessionId(shop, payload.sub) : api.session.getOfflineId(shop); return { shop, payload, sessionId, sessionToken }; } const url = new URL(request.url); const shop = url.searchParams.get('shop'); const sessionId = await api.session.getCurrentId({ isOnline: config.useOnlineTokens, rawRequest: request, }); return { shop, sessionId, payload: undefined, sessionToken }; } export { authStrategyFactory }; //# sourceMappingURL=authenticate.mjs.map