UNPKG

@agnostack/next-shopify

Version:

Please contact agnoStack via info@agnostack.com for any questions

145 lines • 9.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.protectRoute = void 0; const chalk_1 = __importDefault(require("chalk")); const utils_1 = require("../../utils"); const shared_1 = require("../../../shared"); const processRedirect = (url, params, permanent = false) => ({ redirect: { destination: (0, shared_1.appendQuery)(url, new URLSearchParams((0, shared_1.ensureObject)(params)).toString()), permanent, }, }); const protectRoute = (serverRuntimeConfig, _props) => { const { PAGE_PATHS, API_PATHS, APP_CONFIG, PAYMENT_CONFIG } = serverRuntimeConfig; const { isSessionTokenEnabled } = APP_CONFIG; const exitPath = PAGE_PATHS[shared_1.PAGE_ROUTE_NAMES.EXIT]; const errorPath = PAGE_PATHS[shared_1.PAGE_ROUTE_NAMES.ERROR]; return (...args_1) => __awaiter(void 0, [...args_1], void 0, function* ({ req, res, query, locale, resolvedUrl, // NOTE: this comes in from nextJS } = {}, data) { var _a, _b, _c, _d; const [shop] = (0, shared_1.ensureArray)((_a = query === null || query === void 0 ? void 0 : query.shop) !== null && _a !== void 0 ? _a : (_b = req.query) === null || _b === void 0 ? void 0 : _b.shop); if ((0, shared_1.stringEmpty)(shop)) { return processRedirect(errorPath, Object.assign(Object.assign({}, query), { code: 'no_shop_provided' })); } const props = (0, shared_1.cleanObject)(Object.assign(Object.assign({ locale }, _props), data), false, shared_1.stringEmptyOnly); // NOTE: this should handle running in next to test non-shopify functionality const shouldBypassProtect = (0, utils_1.allowLocalBypass)(serverRuntimeConfig); if (shouldBypassProtect) { console.info(`${chalk_1.default.yellowBright('next-shopify')} - [${chalk_1.default.bold('protectRoute')}]: ${`Bypassing local protect - should ONLY happen when running in next to test non-shopify functionality!`}`); return { props }; } const { shopify, loadAppData, loadSession, storeSession, findSessionIds, validateSessionToken, exchangeSessionToken, ensureBilling: handleEnsureBilling, } = yield (0, utils_1.getShopifyHelpers)(serverRuntimeConfig, { shop }); if (shopify === null || shopify === void 0 ? void 0 : shopify.config) { res.setHeader('Content-Security-Policy', (0, utils_1.getSecurityHeader)(shop, shopify.config)); } // TODO: delete this for legacy auth // #region SESSION TOKEN FLOW if (isSessionTokenEnabled) { const idToken = (_c = query === null || query === void 0 ? void 0 : query[shared_1.SESSION_TOKEN_PARAM]) !== null && _c !== void 0 ? _c : (_d = req.query) === null || _d === void 0 ? void 0 : _d[shared_1.SESSION_TOKEN_PARAM]; const { isValid: isValidToken } = yield validateSessionToken({ idToken }); let isValid = isValidToken; let offlineSession; if (isValid) { const { isValid: isValidExchange, accessToken: { session, } = {}, } = yield exchangeSessionToken({ idToken }); isValid = isValidExchange; offlineSession = session; } // NOTE: this handles an invalid session token if (!isValid || !offlineSession) { const [resolvedPath, _resolvedParams] = (0, shared_1.ensureString)(resolvedUrl).split('?'); // eslint-disable-next-line @typescript-eslint/no-unused-vars const _e = (0, shared_1.querystringToObject)(_resolvedParams), _f = shared_1.SESSION_TOKEN_PARAM, _oldToken = _e[_f], resolvedParams = __rest(_e, [typeof _f === "symbol" ? _f : _f + ""]); return processRedirect(exitPath, Object.assign(Object.assign({}, query), { shop, redirectUri: (0, shared_1.appendQuery)(resolvedPath, new URLSearchParams(resolvedParams).toString()) })); } const existingSessionIds = yield findSessionIds(shop); // NOTE: this handles first time installs if (!existingSessionIds.includes(offlineSession.id)) { try { yield storeSession(offlineSession.toObject()); } catch (error) { console.error('Error storing session', error); // TODO add custom 'code=' param for this use case return processRedirect(errorPath, Object.assign(Object.assign({}, query), { shop })); } let appId; try { const { appInfo } = yield loadAppData(); appId = (0, utils_1.normalizeShopifyId)(appInfo === null || appInfo === void 0 ? void 0 : appInfo.id).id; // eslint-disable-next-line no-empty } catch (_ignore) { console.info('Ignoring error loading appData', _ignore); } // Register webhooks const prepareWebhookHandlers = (0, utils_1.getPrepareWebhookHandlers)(serverRuntimeConfig); const webhookHandlers = prepareWebhookHandlers({ appId }); shopify.webhooks.addHandlers(webhookHandlers); // NOTE: webhooks cannot use an online session to make API calls to Shopify (but not all webooks may be doing that for all apps) yield shopify.webhooks.register({ session: offlineSession }); if (PAYMENT_CONFIG === null || PAYMENT_CONFIG === void 0 ? void 0 : PAYMENT_CONFIG.required) { // NOTE: handleEnsureBilling handles calling encryptStoreSession for offline session when shouldCheckBilling const callbackUrl = yield handleEnsureBilling(offlineSession, Object.assign({}, (PAYMENT_CONFIG === null || PAYMENT_CONFIG === void 0 ? void 0 : PAYMENT_CONFIG.debugMode) && { forceBilling: true, })); if ((0, shared_1.stringNotEmpty)(callbackUrl)) { return processRedirect(exitPath, Object.assign(Object.assign({}, query), { shop, redirectUri: callbackUrl })); } } } return { props }; } // #endregion SESSION TOKEN FLOW // TODO: delete this for legacy auth // NOTE: (NON-EMBEDDED APP or NO SESSION TOKEN SUPPORT - typically via SHOPIFY_EMBEDDED_LEGACY) // #region NON-SESSION TOKEN FLOW const authPath = API_PATHS[shared_1.API_ROUTE_NAMES.AUTH_ONLINE]; const existingSessionIds = (0, shared_1.ensureArray)(yield findSessionIds(shop)).filter((sessionId) => (!sessionId.startsWith(shared_1.STATE_PREFIX))); if ((0, shared_1.arrayEmpty)(existingSessionIds)) { return processRedirect(authPath, Object.assign(Object.assign({}, query), { shop })); } try { // NOTE: this does not work if called INSIDE of embedded yield loadSession({ req, res }); return processRedirect(yield shopify.auth.getEmbeddedAppUrl({ rawRequest: req, rawResponse: res, })); } catch (error) { if (error instanceof shared_1.ShopifyAuthenticationError) { const reAuth = (error instanceof shared_1.ShopifySessionAuthenticationError); return processRedirect(authPath, Object.assign(Object.assign({}, query), { shop, reAuth })); } // TODO add custom 'code=' param for this use case return processRedirect(errorPath, Object.assign(Object.assign({}, query), { shop })); } // #endregion NON-SESSION TOKEN FLOW }); }; exports.protectRoute = protectRoute; //# sourceMappingURL=protect.js.map