@shopify/shopify-app-express
Version:
Shopify Express Middleware - to simplify the building of Shopify Apps with Express
177 lines (146 loc) • 4.61 kB
text/typescript
import {Request, Response, NextFunction} from 'express';
import {Session, Shopify} from '@shopify/shopify-api';
import {redirectToAuth} from '../redirect-to-auth';
import {AppConfigInterface} from '../config-types';
import {ApiAndConfigParams} from '../types';
import {AppInstallations} from '../app-installations';
import {EnsureInstalledMiddleware} from './types';
import {addCSPHeader} from './csp-headers';
import {validateAuthenticatedSession} from './validate-authenticated-session';
import {hasValidAccessToken} from './has-valid-access-token';
type EnsureInstalledParams = ApiAndConfigParams;
export function ensureInstalled({
api,
config,
}: EnsureInstalledParams): EnsureInstalledMiddleware {
return function ensureInstalledOnShop() {
return async (req: Request, res: Response, next: NextFunction) => {
config.logger.debug('Running ensureInstalledOnShop');
if (!api.config.isEmbeddedApp) {
config.logger.warning(
'ensureInstalledOnShop() should only be used in embedded apps; calling validateAuthenticatedSession() instead',
);
return validateAuthenticatedSession({api, config})()(req, res, next);
}
const shop = getRequestShop(api, config, req, res);
if (!shop) {
return undefined;
}
config.logger.debug('Checking if shop has installed the app', {shop});
const sessionId = api.session.getOfflineId(shop);
const session = await config.sessionStorage.loadSession(sessionId);
const exitIframeRE = new RegExp(`^${config.exitIframePath}`, 'i');
if (!session && !req.originalUrl.match(exitIframeRE)) {
config.logger.debug(
'App installation was not found for shop, redirecting to auth',
{shop},
);
return redirectToAuth({req, res, api, config});
}
if (api.config.isEmbeddedApp && req.query.embedded !== '1') {
if (await sessionHasValidAccessToken(api, config, session)) {
await embedAppIntoShopify(api, config, req, res, shop);
return undefined;
} else {
config.logger.info(
'Found a session, but it is not valid. Redirecting to auth',
{shop},
);
return redirectToAuth({req, res, api, config});
}
}
addCSPHeader(api, req, res);
config.logger.debug('App is installed and ready to load', {shop});
return next();
};
};
}
export function deleteAppInstallationHandler(
appInstallations: AppInstallations,
config: AppConfigInterface,
) {
return async function (
_topic: string,
shop: string,
_body: any,
_webhookId: string,
) {
config.logger.debug('Deleting shop sessions', {shop});
await appInstallations.delete(shop);
};
}
function getRequestShop(
api: Shopify,
config: AppConfigInterface,
req: Request,
res: Response,
): string | undefined {
if (typeof req.query.shop !== 'string') {
config.logger.error(
'ensureInstalledOnShop did not receive a shop query argument',
{shop: req.query.shop},
);
res.status(400);
res.send('No shop provided');
return undefined;
}
const shop = api.utils.sanitizeShop(req.query.shop);
if (!shop) {
config.logger.error(
'ensureInstalledOnShop did not receive a valid shop query argument',
{shop: req.query.shop},
);
res.status(422);
res.send('Invalid shop provided');
return undefined;
}
return shop;
}
async function sessionHasValidAccessToken(
api: Shopify,
config: AppConfigInterface,
session: Session | undefined,
): Promise<boolean> {
if (!session) {
return false;
}
try {
return (
session.isActive(api.config.scopes) &&
(await hasValidAccessToken(api, session))
);
} catch (error) {
config.logger.error(`Could not check if session was valid: ${error}`, {
shop: session.shop,
});
return false;
}
}
async function embedAppIntoShopify(
api: Shopify,
config: AppConfigInterface,
req: Request,
res: Response,
shop: string,
): Promise<void> {
let embeddedUrl: string;
try {
embeddedUrl = await api.auth.getEmbeddedAppUrl({
rawRequest: req,
rawResponse: res,
});
} catch (error) {
config.logger.error(
`ensureInstalledOnShop did not receive a host query argument`,
{shop},
);
res.status(400);
res.send('No host provided');
return;
}
config.logger.debug(
`Request is not embedded but app is. Redirecting to ${embeddedUrl} to embed the app`,
{shop},
);
res.redirect(embeddedUrl + req.path);
}