@agnostack/next-shopify
Version:
Please contact agnoStack via info@agnostack.com for any questions
145 lines • 9.1 kB
JavaScript
;
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