@shopify/shopify-app-express
Version:
Shopify Express Middleware - to simplify the building of Shopify Apps with Express
143 lines (121 loc) • 3.93 kB
text/typescript
import {Session, Shopify, InvalidJwtError} from '@shopify/shopify-api';
import {Request, Response, NextFunction} from 'express';
import {redirectToAuth} from '../redirect-to-auth';
import {ApiAndConfigParams} from '../types';
import {redirectOutOfApp} from '../redirect-out-of-app';
import {ValidateAuthenticatedSessionMiddleware} from './types';
import {hasValidAccessToken} from './has-valid-access-token';
type validateAuthenticatedSessionParams = ApiAndConfigParams;
export function validateAuthenticatedSession({
api,
config,
}: validateAuthenticatedSessionParams): ValidateAuthenticatedSessionMiddleware {
return function validateAuthenticatedSession() {
return async (req: Request, res: Response, next: NextFunction) => {
config.logger.debug('Running validateAuthenticatedSession');
let sessionId: string | undefined;
try {
sessionId = await api.session.getCurrentId({
isOnline: config.useOnlineTokens,
rawRequest: req,
rawResponse: res,
});
} catch (error) {
config.logger.error(
`Error when loading session from storage: ${error}`,
);
handleSessionError(req, res, error);
return undefined;
}
let session: Session | undefined;
if (sessionId) {
try {
session = await config.sessionStorage.loadSession(sessionId);
} catch (error) {
config.logger.error(
`Error when loading session from storage: ${error}`,
);
res.status(500);
res.send(error.message);
return undefined;
}
}
let shop =
api.utils.sanitizeShop(req.query.shop as string) || session?.shop;
if (session && shop && session.shop !== shop) {
config.logger.debug(
'Found a session for a different shop in the request',
{currentShop: session.shop, requestShop: shop},
);
return redirectToAuth({req, res, api, config});
}
if (session) {
config.logger.debug('Request session found and loaded', {
shop: session.shop,
});
if (session.isActive(api.config.scopes)) {
config.logger.debug('Request session exists and is active', {
shop: session.shop,
});
if (await hasValidAccessToken(api, session)) {
config.logger.debug('Request session has a valid access token', {
shop: session.shop,
});
res.locals.shopify = {
...res.locals.shopify,
session,
};
return next();
}
}
}
const bearerPresent = req.headers.authorization?.match(/Bearer (.*)/);
if (bearerPresent) {
if (!shop) {
shop = await setShopFromSessionOrToken(
api,
session,
bearerPresent[1],
);
}
}
const redirectUri = `${config.auth.path}?shop=${shop}`;
config.logger.info(
`Session was not valid. Redirecting to ${redirectUri}`,
{shop},
);
return redirectOutOfApp({api, config})({
req,
res,
redirectUri,
shop: shop!,
});
};
};
}
function handleSessionError(_req: Request, res: Response, error: Error) {
switch (true) {
case error instanceof InvalidJwtError:
res.status(401);
res.send(error.message);
break;
default:
res.status(500);
res.send(error.message);
break;
}
}
async function setShopFromSessionOrToken(
api: Shopify,
session: Session | undefined,
token: string,
): Promise<string | undefined> {
let shop: string | undefined;
if (session) {
shop = session.shop;
} else if (api.config.isEmbeddedApp) {
const payload = await api.session.decodeSessionToken(token);
shop = payload.dest.replace('https://', '');
}
return shop;
}