From 7b4b73a111b559877381f995f5f4c04c81042285 Mon Sep 17 00:00:00 2001 From: admirsaheta Date: Thu, 29 Aug 2024 10:14:46 +0200 Subject: [PATCH 1/3] handle:middleware/auth-express --- .../validate-authenticated-session.ts | 66 ++++++++++++++++--- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/packages/apps/shopify-app-express/src/middlewares/validate-authenticated-session.ts b/packages/apps/shopify-app-express/src/middlewares/validate-authenticated-session.ts index 6d4746c32..b2b6b3b98 100644 --- a/packages/apps/shopify-app-express/src/middlewares/validate-authenticated-session.ts +++ b/packages/apps/shopify-app-express/src/middlewares/validate-authenticated-session.ts @@ -10,6 +10,21 @@ import {hasValidAccessToken} from './has-valid-access-token'; interface validateAuthenticatedSessionParams extends ApiAndConfigParams {} +/** + * Middleware to validate the session for authenticated requests. + * + * This middleware ensures that the incoming request has a valid session, + * and it checks if the session has an active and valid access token. + * + * If the session is invalid, it redirects the user to the authentication flow. + * + * Additionally, this middleware handles preflight `OPTIONS` requests for CORS + * by bypassing the authentication checks and responding with the appropriate + * CORS headers. + * + * @param {validateAuthenticatedSessionParams} params - The parameters required for the middleware, including the API and config. + * @returns {ValidateAuthenticatedSessionMiddleware} The middleware function that validates the session. + */ export function validateAuthenticatedSession({ api, config, @@ -18,6 +33,21 @@ export function validateAuthenticatedSession({ return async (req: Request, res: Response, next: NextFunction) => { config.logger.info('Running validateAuthenticatedSession'); + // Handle preflight OPTIONS requests for CORS + // Bypasses authentication and responds with the necessary CORS headers. + if (req.method === 'OPTIONS') { + res.header('Access-Control-Allow-Origin', '*'); + res.header( + 'Access-Control-Allow-Methods', + 'GET,POST,PUT,DELETE,OPTIONS', + ); + res.header( + 'Access-Control-Allow-Headers', + 'Content-Type, Authorization', + ); + return res.sendStatus(200); // Respond with 200 OK for OPTIONS requests + } + let sessionId: string | undefined; try { sessionId = await api.session.getCurrentId({ @@ -29,7 +59,6 @@ export function validateAuthenticatedSession({ config.logger.error( `Error when loading session from storage: ${error}`, ); - handleSessionError(req, res, error); return undefined; } @@ -42,9 +71,7 @@ export function validateAuthenticatedSession({ config.logger.error( `Error when loading session from storage: ${error}`, ); - - res.status(500); - res.send(error.message); + res.status(500).send(error.message); return undefined; } } @@ -52,15 +79,20 @@ export function validateAuthenticatedSession({ let shop = api.utils.sanitizeShop(req.query.shop as string) || session?.shop; + // Check if the session is associated with the same shop as the request if (session && shop && session.shop !== shop) { config.logger.debug( 'Found a session for a different shop in the request', - {currentShop: session.shop, requestShop: shop}, + { + currentShop: session.shop, + requestShop: shop, + }, ); return redirectToAuth({req, res, api, config}); } + // Validate if the session is active and has a valid access token if (session) { config.logger.debug('Request session found and loaded', { shop: session.shop, @@ -76,6 +108,7 @@ export function validateAuthenticatedSession({ shop: session.shop, }); + // Attach the session to the response's locals for further use res.locals.shopify = { ...res.locals.shopify, session, @@ -85,6 +118,7 @@ export function validateAuthenticatedSession({ } } + // Handle Bearer token in Authorization header for API requests const bearerPresent = req.headers.authorization?.match(/Bearer (.*)/); if (bearerPresent) { if (!shop) { @@ -96,6 +130,7 @@ export function validateAuthenticatedSession({ } } + // Redirect to the authentication flow if the session is not valid const redirectUri = `${config.auth.path}?shop=${shop}`; config.logger.info( `Session was not valid. Redirecting to ${redirectUri}`, @@ -112,19 +147,32 @@ export function validateAuthenticatedSession({ }; } +/** + * Handles session errors by sending the appropriate response to the client. + * + * @param {Request} _req - The Express request object. + * @param {Response} res - The Express response object. + * @param {Error} error - The error encountered while handling the session. + */ function handleSessionError(_req: Request, res: Response, error: Error) { switch (true) { case error instanceof InvalidJwtError: - res.status(401); - res.send(error.message); + res.status(401).send(error.message); break; default: - res.status(500); - res.send(error.message); + res.status(500).send(error.message); break; } } +/** + * Sets the shop value from the session or token for API requests. + * + * @param {Shopify} api - The Shopify API instance. + * @param {Session | undefined} session - The session object. + * @param {string} token - The Bearer token from the Authorization header. + * @returns {Promise} The shop domain if available. + */ async function setShopFromSessionOrToken( api: Shopify, session: Session | undefined, From aef536603426f1e54390344902c9d71be9a3c97c Mon Sep 17 00:00:00 2001 From: admirsaheta Date: Thu, 29 Aug 2024 10:19:50 +0200 Subject: [PATCH 2/3] add:changeset(express/preflight-options) --- .changeset/quiet-masks-fetch.md | 5 +++++ .../src/middlewares/validate-authenticated-session.ts | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/quiet-masks-fetch.md diff --git a/.changeset/quiet-masks-fetch.md b/.changeset/quiet-masks-fetch.md new file mode 100644 index 000000000..94d449412 --- /dev/null +++ b/.changeset/quiet-masks-fetch.md @@ -0,0 +1,5 @@ +--- +'@shopify/shopify-app-express': minor +--- + +Fixes OPTIONS object inside authenticated session middleware diff --git a/packages/apps/shopify-app-express/src/middlewares/validate-authenticated-session.ts b/packages/apps/shopify-app-express/src/middlewares/validate-authenticated-session.ts index b2b6b3b98..952d9476c 100644 --- a/packages/apps/shopify-app-express/src/middlewares/validate-authenticated-session.ts +++ b/packages/apps/shopify-app-express/src/middlewares/validate-authenticated-session.ts @@ -36,7 +36,7 @@ export function validateAuthenticatedSession({ // Handle preflight OPTIONS requests for CORS // Bypasses authentication and responds with the necessary CORS headers. if (req.method === 'OPTIONS') { - res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Origin', '*'); res.header( 'Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS', @@ -45,7 +45,8 @@ export function validateAuthenticatedSession({ 'Access-Control-Allow-Headers', 'Content-Type, Authorization', ); - return res.sendStatus(200); // Respond with 200 OK for OPTIONS requests + // Respond with 200 OK for OPTIONS requests + return res.sendStatus(200); } let sessionId: string | undefined; From 8c0aaf8395418f41a39f733b8caafb4b2900a5e6 Mon Sep 17 00:00:00 2001 From: admirsaheta Date: Wed, 11 Sep 2024 13:26:00 +0200 Subject: [PATCH 3/3] retrigger:markdown-test --- packages/api-clients/storefront-api-client/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api-clients/storefront-api-client/README.md b/packages/api-clients/storefront-api-client/README.md index 92730e2a5..eb4034f98 100644 --- a/packages/api-clients/storefront-api-client/README.md +++ b/packages/api-clients/storefront-api-client/README.md @@ -20,7 +20,7 @@ pnpm add @shopify/storefront-api-client ### CDN -The UMD builds of each release version are available via the [`unpkg` CDN](https://unpkg.com/browse/@shopify/storefront-api-client@latest/dist/umd/) +The UMD builds of each release version are available via the [`unpkg` CDN](https://unpkg.com/browse/@shopify/storefront-api-client@latest/dist/umd/) ```html // The minified `0.2.3` version of the Storefront API Client