diff --git a/.changeset/nice-tools-sell.md b/.changeset/nice-tools-sell.md new file mode 100644 index 0000000000..a0bc59b250 --- /dev/null +++ b/.changeset/nice-tools-sell.md @@ -0,0 +1,5 @@ +--- +"@medusajs/framework": patch +--- + +fix(framework): Apply CORS and auth middleware for global middleware that is not already applied by routes diff --git a/packages/core/framework/package.json b/packages/core/framework/package.json index ca1e715712..47338c9ab5 100644 --- a/packages/core/framework/package.json +++ b/packages/core/framework/package.json @@ -59,6 +59,7 @@ "@mikro-orm/postgresql": "5.9.7", "@swc/core": "^1.7.28", "@swc/jest": "^0.2.36", + "@types/cors": "^2.8.17", "@types/jsonwebtoken": "^8.5.9", "awilix": "^8.0.1", "ioredis": "^5.4.1", diff --git a/packages/core/framework/src/http/router.ts b/packages/core/framework/src/http/router.ts index 90124df53d..ed375548e9 100644 --- a/packages/core/framework/src/http/router.ts +++ b/packages/core/framework/src/http/router.ts @@ -183,6 +183,44 @@ function getBodyParserMiddleware(args?: ParserConfigArgs) { ] } +function createCorsOptions(origin: string): cors.CorsOptions { + return { + origin: parseCorsOrigins(origin), + credentials: true, + } +} + +function applyCors( + router: Router, + route: string | RegExp, + corsConfig: cors.CorsOptions +) { + router.use(route, cors(corsConfig)) +} + +function getRouteContext( + path: string | RegExp +): "admin" | "store" | "auth" | null { + /** + * We cannot reliably guess the route context from a regex, so we skip it. + */ + if (path instanceof RegExp) { + return null + } + + if (path.startsWith("/admin")) { + return "admin" + } + if (path.startsWith("/store")) { + return "store" + } + if (path.startsWith("/auth")) { + return "auth" + } + + return null +} + // TODO this router would need a proper rework, but it is out of scope right now export class ApiRoutesLoader { @@ -589,13 +627,15 @@ export class ApiRoutesLoader { /** * Applies middleware that checks if a valid publishable key is set on store request */ - applyStorePublishableKeyMiddleware(route: string) { + applyStorePublishableKeyMiddleware(route: string | RegExp) { let middleware = ensurePublishableApiKeyMiddleware as unknown as | RequestHandler | MiddlewareFunction if (ApiRoutesLoader.traceMiddleware) { - middleware = ApiRoutesLoader.traceMiddleware(middleware, { route: route }) + middleware = ApiRoutesLoader.traceMiddleware(middleware, { + route: String(route), + }) } this.#router.use(route, middleware as RequestHandler) @@ -606,7 +646,7 @@ export class ApiRoutesLoader { * needed to pass the middleware via the trace calls */ applyAuthMiddleware( - route: string, + route: string | RegExp, actorType: string | string[], authType: AuthType | AuthType[], options?: { allowUnauthenticated?: boolean; allowUnregistered?: boolean } @@ -617,7 +657,7 @@ export class ApiRoutesLoader { if (ApiRoutesLoader.traceMiddleware) { authenticateMiddleware = ApiRoutesLoader.traceMiddleware( authenticateMiddleware, - { route: route } + { route: String(route) } ) } @@ -632,6 +672,14 @@ export class ApiRoutesLoader { */ applyRouteSpecificMiddlewares(): void { const prioritizedRoutes = prioritize([...this.#routesMap.values()]) + const handledPaths = new Set() + const middlewarePaths = new Set() + + const globalRoutes = this.#globalMiddlewaresDescriptor?.config?.routes ?? [] + + for (const route of globalRoutes) { + middlewarePaths.add(route.matcher) + } for (const descriptor of prioritizedRoutes) { if (!descriptor.config?.routes?.length) { @@ -639,58 +687,33 @@ export class ApiRoutesLoader { } const config = descriptor.config - const routes = descriptor.config.routes - - /** - * Apply default store and admin middlewares if - * not opted out of. - */ + handledPaths.add(descriptor.route) if (config.shouldAppendAdminCors) { - /** - * Apply the admin cors - */ - this.#router.use( + applyCors( + this.#router, descriptor.route, - cors({ - origin: parseCorsOrigins( - configManager.config.projectConfig.http.adminCors - ), - credentials: true, - }) + createCorsOptions(configManager.config.projectConfig.http.adminCors) ) } if (config.shouldAppendAuthCors) { - /** - * Apply the auth cors - */ - this.#router.use( + applyCors( + this.#router, descriptor.route, - cors({ - origin: parseCorsOrigins( - configManager.config.projectConfig.http.authCors - ), - credentials: true, - }) + createCorsOptions(configManager.config.projectConfig.http.authCors) ) } if (config.shouldAppendStoreCors) { - /** - * Apply the store cors - */ - this.#router.use( + applyCors( + this.#router, descriptor.route, - cors({ - origin: parseCorsOrigins( - configManager.config.projectConfig.http.storeCors - ), - credentials: true, - }) + createCorsOptions(configManager.config.projectConfig.http.storeCors) ) } + // Apply other middlewares if (config.routeType === "store") { this.applyStorePublishableKeyMiddleware(descriptor.route) } @@ -715,7 +738,7 @@ export class ApiRoutesLoader { ]) } - for (const route of routes) { + for (const route of descriptor.config.routes) { /** * Apply the body parser middleware if the route * has not opted out of it. @@ -723,6 +746,51 @@ export class ApiRoutesLoader { this.applyBodyParserMiddleware(descriptor.route, route.method!) } } + + /** + * Apply CORS and auth middleware for paths defined in global middleware but not already handled by routes. + */ + for (const path of middlewarePaths) { + if (typeof path === "string" && handledPaths.has(path)) { + continue + } + + const context = getRouteContext(path) + + if (!context) { + continue + } + + switch (context) { + case "admin": + applyCors( + this.#router, + path, + createCorsOptions(configManager.config.projectConfig.http.adminCors) + ) + this.applyAuthMiddleware(path, "user", [ + "bearer", + "session", + "api-key", + ]) + break + case "store": + applyCors( + this.#router, + path, + createCorsOptions(configManager.config.projectConfig.http.storeCors) + ) + this.applyStorePublishableKeyMiddleware(path) + break + case "auth": + applyCors( + this.#router, + path, + createCorsOptions(configManager.config.projectConfig.http.authCors) + ) + break + } + } } /** diff --git a/yarn.lock b/yarn.lock index 3c41ebc51b..0e2a5efeda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5754,6 +5754,7 @@ __metadata: "@opentelemetry/api": ^1.9.0 "@swc/core": ^1.7.28 "@swc/jest": ^0.2.36 + "@types/cors": ^2.8.17 "@types/express": ^4.17.17 "@types/jsonwebtoken": ^8.5.9 awilix: ^8.0.1 @@ -13414,6 +13415,15 @@ __metadata: languageName: node linkType: hard +"@types/cors@npm:^2.8.17": + version: 2.8.17 + resolution: "@types/cors@npm:2.8.17" + dependencies: + "@types/node": "*" + checksum: 457364c28c89f3d9ed34800e1de5c6eaaf344d1bb39af122f013322a50bc606eb2aa6f63de4e41a7a08ba7ef454473926c94a830636723da45bf786df032696d + languageName: node + linkType: hard + "@types/doctrine@npm:^0.0.9": version: 0.0.9 resolution: "@types/doctrine@npm:0.0.9"