diff --git a/src/middleware.ts b/src/middleware.ts index 6162580..a32a4ef 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,6 +1,8 @@ import crypto from "crypto"; import * as jose from "jose"; -import type { Middleware } from "retes"; +import jwt, { JwtPayload } from "jsonwebtoken"; +import jwks, { CertSigningKey, RsaSigningKey } from "jwks-rsa"; +import type { Middleware, Request } from "retes"; import { Response } from "retes/response"; import { SALEOR_DOMAIN_HEADER, SALEOR_EVENT_HEADER } from "./const"; @@ -33,7 +35,6 @@ export const withSaleorEventMatch = (handler) => async (request) => { const receivedEvent = request.headers[SALEOR_EVENT_HEADER]; - if (receivedEvent !== expectedEvent) { return Response.BadRequest({ success: false, @@ -90,12 +91,12 @@ export const withWebhookSignatureVerified = signature, }; - const jwks = jose.createRemoteJWKSet( + const remoteJwks = jose.createRemoteJWKSet( new URL(jwksUrl(saleorDomain)) ) as jose.FlattenedVerifyGetKey; try { - await jose.flattenedVerify(jws, jwks); + await jose.flattenedVerify(jws, remoteJwks); } catch { return Response.BadRequest({ success: false, @@ -106,3 +107,84 @@ export const withWebhookSignatureVerified = return handler(request); }; + +export interface DashboardTokenPayload extends JwtPayload { + app: string; +} + +export const withJWTVerified = + (getAppId: (request: Request) => Promise): Middleware => + (handler) => + async (request) => { + const { [SALEOR_DOMAIN_HEADER]: saleorDomain, "authorization-bearer": token } = request.headers; + + if (token === undefined) { + return Response.BadRequest({ + success: false, + message: "Missing token.", + }); + } + + let tokenClaims; + try { + tokenClaims = jwt.decode(token as string); + } catch (e) { + console.error(e); + return Response.BadRequest({ + success: false, + message: "Invalid token.", + }); + } + + if (tokenClaims === null) { + return Response.BadRequest({ + success: false, + message: "Invalid token.", + }); + } + + if ((tokenClaims as DashboardTokenPayload).iss !== saleorDomain) { + return Response.BadRequest({ + success: false, + message: "Invalid token.", + }); + } + + let appId: string | undefined; + try { + appId = await getAppId(request); + } catch (error) { + console.error("Error during getting the app ID."); + console.error(error); + return Response.BadRequest({ + success: false, + message: "Error during token invalidation - could not obtain the app ID.", + }); + } + + if (!appId || (tokenClaims as DashboardTokenPayload).app !== appId) { + return Response.BadRequest({ + success: false, + message: "Invalid token.", + }); + } + + const jwksClient = jwks({ + jwksUri: `https://${saleorDomain}/.well-known/jwks.json`, + }); + const signingKey = await jwksClient.getSigningKey(); + const signingSecret = + (signingKey as CertSigningKey).publicKey || (signingKey as RsaSigningKey).rsaPublicKey; + + try { + jwt.verify(token as string, signingSecret); + } catch (e) { + console.error(e); + return Response.BadRequest({ + success: false, + message: "Invalid token.", + }); + } + + return handler(request); + };