feature: add telemetry to the HTTP layer (#9116)
--------- Co-authored-by: adrien2p <adrien.deperetti@gmail.com>
This commit is contained in:
@@ -20,7 +20,10 @@ const API_KEY_AUTH = "api-key"
|
||||
// This is the only hard-coded actor type, as API keys have special handling for now. We could also generalize API keys to carry the actor type with them.
|
||||
const ADMIN_ACTOR_TYPE = "user"
|
||||
|
||||
type AuthType = typeof SESSION_AUTH | typeof BEARER_AUTH | typeof API_KEY_AUTH
|
||||
export type AuthType =
|
||||
| typeof SESSION_AUTH
|
||||
| typeof BEARER_AUTH
|
||||
| typeof API_KEY_AUTH
|
||||
|
||||
type MedusaSession = {
|
||||
auth_context: AuthContext
|
||||
@@ -31,7 +34,7 @@ export const authenticate = (
|
||||
authType: AuthType | AuthType[],
|
||||
options: { allowUnauthenticated?: boolean; allowUnregistered?: boolean } = {}
|
||||
): RequestHandler => {
|
||||
const handler = async (
|
||||
const authenticateMiddleware = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse,
|
||||
next: NextFunction
|
||||
@@ -105,7 +108,7 @@ export const authenticate = (
|
||||
res.status(401).json({ message: "Unauthorized" })
|
||||
}
|
||||
|
||||
return handler as unknown as RequestHandler
|
||||
return authenticateMiddleware as unknown as RequestHandler
|
||||
}
|
||||
|
||||
const getApiKeyInfo = async (req: MedusaRequest): Promise<ApiKeyDTO | null> => {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { parseCorsOrigins, promiseAll, wrapHandler } from "@medusajs/utils"
|
||||
import cors from "cors"
|
||||
import { type Express, json, Router, text, urlencoded } from "express"
|
||||
import {
|
||||
type Express,
|
||||
json,
|
||||
RequestHandler,
|
||||
Router,
|
||||
text,
|
||||
urlencoded,
|
||||
} from "express"
|
||||
import { readdir } from "fs/promises"
|
||||
import { extname, join, parse, sep } from "path"
|
||||
import {
|
||||
@@ -8,15 +15,17 @@ import {
|
||||
HTTP_METHODS,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
MiddlewareFunction,
|
||||
MiddlewareRoute,
|
||||
MiddlewaresConfig,
|
||||
MiddlewareVerb,
|
||||
ParserConfigArgs,
|
||||
RouteConfig,
|
||||
RouteDescriptor,
|
||||
RouteHandler,
|
||||
RouteVerb,
|
||||
} from "./types"
|
||||
import { authenticate, errorHandler } from "./middlewares"
|
||||
import { authenticate, AuthType, errorHandler } from "./middlewares"
|
||||
import { configManager } from "../config"
|
||||
import { logger } from "../logger"
|
||||
|
||||
@@ -212,6 +221,24 @@ class ApiRoutesLoader {
|
||||
*/
|
||||
readonly #sourceDir: string
|
||||
|
||||
/**
|
||||
* Wrap the original route handler implementation for
|
||||
* instrumentation.
|
||||
*/
|
||||
static traceRoute?: (
|
||||
handler: RouteHandler,
|
||||
route: { route: string; method: string }
|
||||
) => RouteHandler
|
||||
|
||||
/**
|
||||
* Wrap the original middleware handler implementation for
|
||||
* instrumentation.
|
||||
*/
|
||||
static traceMiddleware?: (
|
||||
handler: RequestHandler | MiddlewareFunction,
|
||||
route: { route: string; method?: string }
|
||||
) => RequestHandler
|
||||
|
||||
constructor({
|
||||
app,
|
||||
activityId,
|
||||
@@ -560,6 +587,27 @@ class ApiRoutesLoader {
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the route middleware on a route. Encapsulates the logic
|
||||
* needed to pass the middleware via the trace calls
|
||||
*/
|
||||
applyAuthMiddleware(
|
||||
route: string,
|
||||
actorType: string | string[],
|
||||
authType: AuthType | AuthType[],
|
||||
options?: { allowUnauthenticated?: boolean; allowUnregistered?: boolean }
|
||||
) {
|
||||
let authenticateMiddleware = authenticate(actorType, authType, options)
|
||||
if (ApiRoutesLoader.traceMiddleware) {
|
||||
authenticateMiddleware = ApiRoutesLoader.traceMiddleware(
|
||||
authenticateMiddleware,
|
||||
{ route: route }
|
||||
)
|
||||
}
|
||||
|
||||
this.#router.use(route, authenticateMiddleware)
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the route specific middlewares to the router,
|
||||
* this includes the cors, authentication and
|
||||
@@ -629,20 +677,22 @@ class ApiRoutesLoader {
|
||||
|
||||
// We only apply the auth middleware to store routes to populate the auth context. For actual authentication, users can just reapply the middleware.
|
||||
if (!config.optedOutOfAuth && config.routeType === "store") {
|
||||
this.#router.use(
|
||||
this.applyAuthMiddleware(
|
||||
descriptor.route,
|
||||
authenticate("customer", ["bearer", "session"], {
|
||||
"customer",
|
||||
["bearer", "session"],
|
||||
{
|
||||
allowUnauthenticated: true,
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (!config.optedOutOfAuth && config.routeType === "admin") {
|
||||
// We probably don't want to allow access to all endpoints using an api key, but it will do until we revamp our routing.
|
||||
this.#router.use(
|
||||
descriptor.route,
|
||||
authenticate("user", ["bearer", "session", "api-key"])
|
||||
)
|
||||
this.applyAuthMiddleware(descriptor.route, "user", [
|
||||
"bearer",
|
||||
"session",
|
||||
"api-key",
|
||||
])
|
||||
}
|
||||
|
||||
for (const route of routes) {
|
||||
@@ -708,13 +758,26 @@ class ApiRoutesLoader {
|
||||
}`,
|
||||
})
|
||||
|
||||
let handler: RequestHandler | RouteHandler = route.handler
|
||||
|
||||
/**
|
||||
* Give handler to the trace route handler for instrumentation
|
||||
* from outside-in.
|
||||
*/
|
||||
if (ApiRoutesLoader.traceRoute) {
|
||||
handler = ApiRoutesLoader.traceRoute(handler, {
|
||||
method: route.method!,
|
||||
route: descriptor.route,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user hasn't opted out of error handling then
|
||||
* we wrap the handler in a try/catch block.
|
||||
*/
|
||||
const handler = shouldWrapHandler
|
||||
? wrapHandler(route.handler as Parameters<typeof wrapHandler>[0])
|
||||
: route.handler
|
||||
if (shouldWrapHandler) {
|
||||
handler = wrapHandler(handler as Parameters<typeof wrapHandler>[0])
|
||||
}
|
||||
|
||||
this.#router[route.method!.toLowerCase()](descriptor.route, handler)
|
||||
}
|
||||
@@ -756,7 +819,17 @@ class ApiRoutesLoader {
|
||||
message: `Registering middleware [${method}] - ${route.matcher}`,
|
||||
})
|
||||
|
||||
this.#router[method.toLowerCase()](route.matcher, ...route.middlewares)
|
||||
let middlewares = route.middlewares
|
||||
if (ApiRoutesLoader.traceMiddleware) {
|
||||
middlewares = middlewares.map((middleware) =>
|
||||
ApiRoutesLoader.traceMiddleware!(middleware, {
|
||||
route: String(route.matcher),
|
||||
method,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
this.#router[method.toLowerCase()](route.matcher, ...middlewares)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -840,6 +913,27 @@ export class RoutesLoader {
|
||||
*/
|
||||
readonly #sourceDir: string | string[]
|
||||
|
||||
static instrument: {
|
||||
/**
|
||||
* Instrument middleware function calls by wrapping the original
|
||||
* middleware handler inside a custom implementation
|
||||
*/
|
||||
middleware: (callback: (typeof ApiRoutesLoader)["traceMiddleware"]) => void
|
||||
|
||||
/**
|
||||
* Instrument route handler function calls by wrapping the original
|
||||
* middleware handler inside a custom implementation
|
||||
*/
|
||||
route: (callback: (typeof ApiRoutesLoader)["traceRoute"]) => void
|
||||
} = {
|
||||
middleware(callback) {
|
||||
ApiRoutesLoader.traceMiddleware = callback
|
||||
},
|
||||
route(callback) {
|
||||
ApiRoutesLoader.traceRoute = callback
|
||||
},
|
||||
}
|
||||
|
||||
constructor({
|
||||
app,
|
||||
activityId,
|
||||
|
||||
@@ -36,7 +36,7 @@ export type AsyncRouteHandler = (
|
||||
res: MedusaResponse
|
||||
) => Promise<void>
|
||||
|
||||
type RouteHandler = SyncRouteHandler | AsyncRouteHandler
|
||||
export type RouteHandler = SyncRouteHandler | AsyncRouteHandler
|
||||
|
||||
export type RouteImplementation = {
|
||||
method?: RouteVerb
|
||||
|
||||
Reference in New Issue
Block a user