@shopify/shopify-app-remix
Version:
Shopify Remix - to simplify the building of Shopify Apps with Remix
153 lines (150 loc) • 7.34 kB
JavaScript
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 '../../types.mjs';
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