fix(framework): Ensure that CORS and Auth middleware is applied for routes only defined in middlewares.ts (#10339)

This commit is contained in:
Kasper Fabricius Kristensen
2024-12-04 14:49:43 +01:00
committed by GitHub
parent 582f52b31d
commit a5263083fa
4 changed files with 125 additions and 41 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/framework": patch
---
fix(framework): Apply CORS and auth middleware for global middleware that is not already applied by routes
+1
View File
@@ -59,6 +59,7 @@
"@mikro-orm/postgresql": "5.9.7", "@mikro-orm/postgresql": "5.9.7",
"@swc/core": "^1.7.28", "@swc/core": "^1.7.28",
"@swc/jest": "^0.2.36", "@swc/jest": "^0.2.36",
"@types/cors": "^2.8.17",
"@types/jsonwebtoken": "^8.5.9", "@types/jsonwebtoken": "^8.5.9",
"awilix": "^8.0.1", "awilix": "^8.0.1",
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
+109 -41
View File
@@ -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 // TODO this router would need a proper rework, but it is out of scope right now
export class ApiRoutesLoader { export class ApiRoutesLoader {
@@ -589,13 +627,15 @@ export class ApiRoutesLoader {
/** /**
* Applies middleware that checks if a valid publishable key is set on store request * 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 let middleware = ensurePublishableApiKeyMiddleware as unknown as
| RequestHandler | RequestHandler
| MiddlewareFunction | MiddlewareFunction
if (ApiRoutesLoader.traceMiddleware) { if (ApiRoutesLoader.traceMiddleware) {
middleware = ApiRoutesLoader.traceMiddleware(middleware, { route: route }) middleware = ApiRoutesLoader.traceMiddleware(middleware, {
route: String(route),
})
} }
this.#router.use(route, middleware as RequestHandler) this.#router.use(route, middleware as RequestHandler)
@@ -606,7 +646,7 @@ export class ApiRoutesLoader {
* needed to pass the middleware via the trace calls * needed to pass the middleware via the trace calls
*/ */
applyAuthMiddleware( applyAuthMiddleware(
route: string, route: string | RegExp,
actorType: string | string[], actorType: string | string[],
authType: AuthType | AuthType[], authType: AuthType | AuthType[],
options?: { allowUnauthenticated?: boolean; allowUnregistered?: boolean } options?: { allowUnauthenticated?: boolean; allowUnregistered?: boolean }
@@ -617,7 +657,7 @@ export class ApiRoutesLoader {
if (ApiRoutesLoader.traceMiddleware) { if (ApiRoutesLoader.traceMiddleware) {
authenticateMiddleware = ApiRoutesLoader.traceMiddleware( authenticateMiddleware = ApiRoutesLoader.traceMiddleware(
authenticateMiddleware, authenticateMiddleware,
{ route: route } { route: String(route) }
) )
} }
@@ -632,6 +672,14 @@ export class ApiRoutesLoader {
*/ */
applyRouteSpecificMiddlewares(): void { applyRouteSpecificMiddlewares(): void {
const prioritizedRoutes = prioritize([...this.#routesMap.values()]) const prioritizedRoutes = prioritize([...this.#routesMap.values()])
const handledPaths = new Set<string>()
const middlewarePaths = new Set<string | RegExp>()
const globalRoutes = this.#globalMiddlewaresDescriptor?.config?.routes ?? []
for (const route of globalRoutes) {
middlewarePaths.add(route.matcher)
}
for (const descriptor of prioritizedRoutes) { for (const descriptor of prioritizedRoutes) {
if (!descriptor.config?.routes?.length) { if (!descriptor.config?.routes?.length) {
@@ -639,58 +687,33 @@ export class ApiRoutesLoader {
} }
const config = descriptor.config const config = descriptor.config
const routes = descriptor.config.routes handledPaths.add(descriptor.route)
/**
* Apply default store and admin middlewares if
* not opted out of.
*/
if (config.shouldAppendAdminCors) { if (config.shouldAppendAdminCors) {
/** applyCors(
* Apply the admin cors this.#router,
*/
this.#router.use(
descriptor.route, descriptor.route,
cors({ createCorsOptions(configManager.config.projectConfig.http.adminCors)
origin: parseCorsOrigins(
configManager.config.projectConfig.http.adminCors
),
credentials: true,
})
) )
} }
if (config.shouldAppendAuthCors) { if (config.shouldAppendAuthCors) {
/** applyCors(
* Apply the auth cors this.#router,
*/
this.#router.use(
descriptor.route, descriptor.route,
cors({ createCorsOptions(configManager.config.projectConfig.http.authCors)
origin: parseCorsOrigins(
configManager.config.projectConfig.http.authCors
),
credentials: true,
})
) )
} }
if (config.shouldAppendStoreCors) { if (config.shouldAppendStoreCors) {
/** applyCors(
* Apply the store cors this.#router,
*/
this.#router.use(
descriptor.route, descriptor.route,
cors({ createCorsOptions(configManager.config.projectConfig.http.storeCors)
origin: parseCorsOrigins(
configManager.config.projectConfig.http.storeCors
),
credentials: true,
})
) )
} }
// Apply other middlewares
if (config.routeType === "store") { if (config.routeType === "store") {
this.applyStorePublishableKeyMiddleware(descriptor.route) 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 * Apply the body parser middleware if the route
* has not opted out of it. * has not opted out of it.
@@ -723,6 +746,51 @@ export class ApiRoutesLoader {
this.applyBodyParserMiddleware(descriptor.route, route.method!) 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
}
}
} }
/** /**
+10
View File
@@ -5754,6 +5754,7 @@ __metadata:
"@opentelemetry/api": ^1.9.0 "@opentelemetry/api": ^1.9.0
"@swc/core": ^1.7.28 "@swc/core": ^1.7.28
"@swc/jest": ^0.2.36 "@swc/jest": ^0.2.36
"@types/cors": ^2.8.17
"@types/express": ^4.17.17 "@types/express": ^4.17.17
"@types/jsonwebtoken": ^8.5.9 "@types/jsonwebtoken": ^8.5.9
awilix: ^8.0.1 awilix: ^8.0.1
@@ -13414,6 +13415,15 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "@types/doctrine@npm:^0.0.9":
version: 0.0.9 version: 0.0.9
resolution: "@types/doctrine@npm:0.0.9" resolution: "@types/doctrine@npm:0.0.9"