diff --git a/packages/framework/framework/package.json b/packages/framework/framework/package.json index e6a47c3e01..bd66613e82 100644 --- a/packages/framework/framework/package.json +++ b/packages/framework/framework/package.json @@ -41,8 +41,10 @@ "devDependencies": { "@medusajs/types": "^1.11.16", "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^8.5.9", "cross-env": "^7.0.3", "rimraf": "^3.0.2", + "supertest": "^4.0.2", "tsc-alias": "^1.8.6", "typescript": "^5.1.6", "vite": "^5.2.11" @@ -55,11 +57,14 @@ "@medusajs/workflows-sdk": "^0.1.6", "awilix": "^8.0.0", "cookie-parser": "^1.4.6", + "cors": "^2.8.5", "express": "^4.18.2", "express-session": "^1.17.3", "ioredis": "^5.2.5", "ioredis-mock": "8.4.0", + "jsonwebtoken": "^9.0.2", "medusa-telemetry": "^0.0.17", - "morgan": "^1.9.1" + "morgan": "^1.9.1", + "zod": "3.22.4" } } diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/mocks/index.ts b/packages/framework/framework/src/http/__fixtures__/mocks/index.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/mocks/index.ts rename to packages/framework/framework/src/http/__fixtures__/mocks/index.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-duplicate-parameter/admin/customers/[id]/orders/[id]/route.ts b/packages/framework/framework/src/http/__fixtures__/routers-duplicate-parameter/admin/customers/[id]/orders/[id]/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-duplicate-parameter/admin/customers/[id]/orders/[id]/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers-duplicate-parameter/admin/customers/[id]/orders/[id]/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-error-handler/middlewares.ts b/packages/framework/framework/src/http/__fixtures__/routers-error-handler/middlewares.ts similarity index 91% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-error-handler/middlewares.ts rename to packages/framework/framework/src/http/__fixtures__/routers-error-handler/middlewares.ts index 1ca12d184f..ffb0bcdcb0 100644 --- a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-error-handler/middlewares.ts +++ b/packages/framework/framework/src/http/__fixtures__/routers-error-handler/middlewares.ts @@ -1,4 +1,4 @@ -import { defineMiddlewares } from "../../../../../utils/define-middlewares" +import { defineMiddlewares } from "../../utils/define-middlewares" export default defineMiddlewares({ errorHandler: (err, _req, res, _next) => { diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-error-handler/store/route.ts b/packages/framework/framework/src/http/__fixtures__/routers-error-handler/store/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-error-handler/store/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers-error-handler/store/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/admin/protected/route.ts b/packages/framework/framework/src/http/__fixtures__/routers-middleware/admin/protected/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/admin/protected/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers-middleware/admin/protected/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/admin/unprotected/route.ts b/packages/framework/framework/src/http/__fixtures__/routers-middleware/admin/unprotected/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/admin/unprotected/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers-middleware/admin/unprotected/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/customers/error/route.ts b/packages/framework/framework/src/http/__fixtures__/routers-middleware/customers/error/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/customers/error/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers-middleware/customers/error/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/customers/route.ts b/packages/framework/framework/src/http/__fixtures__/routers-middleware/customers/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/customers/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers-middleware/customers/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/middlewares.ts b/packages/framework/framework/src/http/__fixtures__/routers-middleware/middlewares.ts similarity index 87% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/middlewares.ts rename to packages/framework/framework/src/http/__fixtures__/routers-middleware/middlewares.ts index c8ca5aa185..e6d8b26d2e 100644 --- a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/middlewares.ts +++ b/packages/framework/framework/src/http/__fixtures__/routers-middleware/middlewares.ts @@ -1,10 +1,10 @@ -import { NextFunction, Request, Response, raw } from "express" +import { NextFunction, raw, Request, Response } from "express" import { customersCreateMiddlewareMock, customersGlobalMiddlewareMock, storeGlobalMiddlewareMock, } from "../mocks" -import { defineMiddlewares } from "../../../../../utils/define-middlewares" +import { defineMiddlewares } from "../../utils/define-middlewares" const customersGlobalMiddleware = ( req: Request, diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/store/customers/me/protected/route.ts b/packages/framework/framework/src/http/__fixtures__/routers-middleware/store/customers/me/protected/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/store/customers/me/protected/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers-middleware/store/customers/me/protected/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/store/customers/me/unprotected/route.ts b/packages/framework/framework/src/http/__fixtures__/routers-middleware/store/customers/me/unprotected/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/store/customers/me/unprotected/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers-middleware/store/customers/me/unprotected/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/store/products/[id]/sync/route.ts b/packages/framework/framework/src/http/__fixtures__/routers-middleware/store/products/[id]/sync/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/store/products/[id]/sync/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers-middleware/store/products/[id]/sync/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/webhooks/payment/route.ts b/packages/framework/framework/src/http/__fixtures__/routers-middleware/webhooks/payment/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers-middleware/webhooks/payment/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers-middleware/webhooks/payment/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/_private/route.ts b/packages/framework/framework/src/http/__fixtures__/routers/_private/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/_private/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers/_private/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/admin/orders/[id]/route.ts b/packages/framework/framework/src/http/__fixtures__/routers/admin/orders/[id]/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/admin/orders/[id]/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers/admin/orders/[id]/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/admin/orders/route.ts b/packages/framework/framework/src/http/__fixtures__/routers/admin/orders/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/admin/orders/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers/admin/orders/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/admin/products/[id]/route.ts b/packages/framework/framework/src/http/__fixtures__/routers/admin/products/[id]/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/admin/products/[id]/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers/admin/products/[id]/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/admin/products/route.ts b/packages/framework/framework/src/http/__fixtures__/routers/admin/products/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/admin/products/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers/admin/products/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/admin/route.ts b/packages/framework/framework/src/http/__fixtures__/routers/admin/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/admin/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers/admin/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/customers/[customer_id]/orders/[order_id]/route.ts b/packages/framework/framework/src/http/__fixtures__/routers/customers/[customer_id]/orders/[order_id]/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/customers/[customer_id]/orders/[order_id]/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers/customers/[customer_id]/orders/[order_id]/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/customers/route.ts b/packages/framework/framework/src/http/__fixtures__/routers/customers/route.ts similarity index 100% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/routers/customers/route.ts rename to packages/framework/framework/src/http/__fixtures__/routers/customers/route.ts diff --git a/packages/medusa/src/loaders/helpers/routing/__fixtures__/server/index.ts b/packages/framework/framework/src/http/__fixtures__/server/index.ts similarity index 86% rename from packages/medusa/src/loaders/helpers/routing/__fixtures__/server/index.ts rename to packages/framework/framework/src/http/__fixtures__/server/index.ts index 8f87d42b5d..278c014fa4 100644 --- a/packages/medusa/src/loaders/helpers/routing/__fixtures__/server/index.ts +++ b/packages/framework/framework/src/http/__fixtures__/server/index.ts @@ -3,24 +3,20 @@ import { ModulesDefinition, registerMedusaModule, } from "@medusajs/modules-sdk" -import { - configManager, - ConfigModule, - container, - featureFlagsLoader, - logger, -} from "@medusajs/framework" import { ContainerRegistrationKeys, generateJwtToken } from "@medusajs/utils" import { asValue } from "awilix" import express from "express" import querystring from "querystring" import supertest from "supertest" -import apiLoader from "../../../../api" -import { getResolvedPlugins } from "../../../../helpers/resolve-plugins" -import RoutesLoader from "../.." import { config } from "../mocks" import { MedusaContainer } from "@medusajs/types" +import { configManager, ConfigModule } from "../../../config" +import { container } from "../../../container" +import { featureFlagsLoader } from "../../../feature-flags" +import { logger } from "../../../logger" +import { MedusaRequest } from "../../types" +import { RoutesLoader } from "../../router" function asArray(resolvers) { return { @@ -90,30 +86,19 @@ export const createServer = async (rootDir) => { next() }) - const plugins = getResolvedPlugins(rootDir, config) || [] - await featureFlagsLoader() await moduleLoader({ container, moduleResolutions, logger }) app.use((req, res, next) => { - req.scope = container.createScope() as MedusaContainer + ;(req as MedusaRequest).scope = container.createScope() as MedusaContainer next() }) - // This where plugins normally load, but we simply load the routes await new RoutesLoader({ app, - rootDir, - configModule: config, + sourceDir: rootDir, }).load() - // the apiLoader needs to be called after plugins otherwise the core middleware bleads into the plugins - await apiLoader({ - container, - app: app, - plugins, - }) - const superRequest = supertest(app) return { diff --git a/packages/medusa/src/loaders/helpers/routing/__tests__/index.spec.ts b/packages/framework/framework/src/http/__tests__/index.spec.ts similarity index 99% rename from packages/medusa/src/loaders/helpers/routing/__tests__/index.spec.ts rename to packages/framework/framework/src/http/__tests__/index.spec.ts index 98469ae7fb..9c0ae60aae 100644 --- a/packages/medusa/src/loaders/helpers/routing/__tests__/index.spec.ts +++ b/packages/framework/framework/src/http/__tests__/index.spec.ts @@ -1,7 +1,6 @@ import express from "express" import { resolve } from "path" import { - config, customersCreateMiddlewareMock, customersGlobalMiddlewareMock, storeGlobalMiddlewareMock, @@ -230,8 +229,7 @@ describe("RoutesLoader", function () { ) const err = await new RoutesLoader({ app, - rootDir, - configModule: config, + sourceDir: rootDir, }) .load() .catch((e) => e) diff --git a/packages/framework/framework/src/http/index.ts b/packages/framework/framework/src/http/index.ts index a2967b8541..9900db069b 100644 --- a/packages/framework/framework/src/http/index.ts +++ b/packages/framework/framework/src/http/index.ts @@ -1 +1,5 @@ export * from "./express-loader" +export * from "./router" +export * from "./types" +export * from "./middlewares" +export * from "./utils/define-middlewares" diff --git a/packages/framework/framework/src/http/middlewares/authenticate-middleware.ts b/packages/framework/framework/src/http/middlewares/authenticate-middleware.ts new file mode 100644 index 0000000000..ee8c7d7713 --- /dev/null +++ b/packages/framework/framework/src/http/middlewares/authenticate-middleware.ts @@ -0,0 +1,208 @@ +import { ApiKeyDTO, IApiKeyModuleService } from "@medusajs/types" +import { + ContainerRegistrationKeys, + ModuleRegistrationName, +} from "@medusajs/utils" +import { NextFunction, RequestHandler } from "express" +import { JwtPayload, verify } from "jsonwebtoken" +import { + AuthContext, + AuthenticatedMedusaRequest, + MedusaRequest, + MedusaResponse, +} from "../types" +import { ConfigModule } from "../../config" + +const SESSION_AUTH = "session" +const BEARER_AUTH = "bearer" +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 + +type MedusaSession = { + auth_context: AuthContext +} + +export const authenticate = ( + actorType: string | string[], + authType: AuthType | AuthType[], + options: { allowUnauthenticated?: boolean; allowUnregistered?: boolean } = {} +): RequestHandler => { + const handler = async ( + req: MedusaRequest, + res: MedusaResponse, + next: NextFunction + ): Promise => { + const authTypes = Array.isArray(authType) ? authType : [authType] + const actorTypes = Array.isArray(actorType) ? actorType : [actorType] + const req_ = req as AuthenticatedMedusaRequest + + // We only allow authenticating using a secret API key on the admin + const isExclusivelyUser = + actorTypes.length === 1 && actorTypes[0] === ADMIN_ACTOR_TYPE + + if (authTypes.includes(API_KEY_AUTH) && isExclusivelyUser) { + const apiKey = await getApiKeyInfo(req) + if (apiKey) { + req_.auth_context = { + actor_id: apiKey.id, + actor_type: "api-key", + auth_identity_id: "", + app_metadata: {}, + } + + return next() + } + } + + // We try to extract the auth context either from the session or from a JWT token + let authContext: AuthContext | null = getAuthContextFromSession( + req.session, + authTypes, + actorTypes + ) + + if (!authContext) { + const { + projectConfig: { http }, + } = req.scope.resolve( + ContainerRegistrationKeys.CONFIG_MODULE + ) + + authContext = getAuthContextFromJwtToken( + req.headers.authorization, + http.jwtSecret!, + authTypes, + actorTypes + ) + } + + // If the entity is authenticated, and it is a registered actor we can continue + if (authContext?.actor_id) { + req_.auth_context = authContext + return next() + } + + // If the entity is authenticated, but there is no registered actor yet, we can continue (eg. in the case of a user invite) if allow unregistered is set + if (authContext?.auth_identity_id && options.allowUnregistered) { + req_.auth_context = authContext + return next() + } + + // If we allow unauthenticated requests (i.e public endpoints), just continue + if (options.allowUnauthenticated) { + return next() + } + + res.status(401).json({ message: "Unauthorized" }) + } + + return handler as unknown as RequestHandler +} + +const getApiKeyInfo = async (req: MedusaRequest): Promise => { + const authHeader = req.headers.authorization + if (!authHeader) { + return null + } + + const [tokenType, token] = authHeader.split(" ") + if (tokenType.toLowerCase() !== "basic" || !token) { + return null + } + + // The token could have been base64 encoded, we want to decode it first. + let normalizedToken = token + if (!token.startsWith("sk_")) { + normalizedToken = Buffer.from(token, "base64").toString("utf-8") + } + + // Basic auth is defined as a username:password set, and since the token is set to the username we need to trim the colon + if (normalizedToken.endsWith(":")) { + normalizedToken = normalizedToken.slice(0, -1) + } + + // Secret tokens start with 'sk_', and if it doesn't it could be a user JWT or a malformed token + if (!normalizedToken.startsWith("sk_")) { + return null + } + + const apiKeyModule = req.scope.resolve( + ModuleRegistrationName.API_KEY + ) as IApiKeyModuleService + try { + const apiKey = await apiKeyModule.authenticate(normalizedToken) + if (!apiKey) { + return null + } + + return apiKey + } catch (error) { + console.error(error) + return null + } +} + +const getAuthContextFromSession = ( + session: Partial = {}, + authTypes: AuthType[], + actorTypes: string[] +): AuthContext | null => { + if (!authTypes.includes(SESSION_AUTH)) { + return null + } + + if ( + session.auth_context && + (actorTypes.includes("*") || + actorTypes.includes(session.auth_context.actor_type)) + ) { + return session.auth_context + } + + return null +} + +const getAuthContextFromJwtToken = ( + authHeader: string | undefined, + jwtSecret: string, + authTypes: AuthType[], + actorTypes: string[] +): AuthContext | null => { + if (!authTypes.includes(BEARER_AUTH)) { + return null + } + + if (!authHeader) { + return null + } + + const re = /(\S+)\s+(\S+)/ + const matches = authHeader.match(re) + + // TODO: figure out how to obtain token (and store correct data in token) + if (matches) { + const tokenType = matches[1] + const token = matches[2] + if (tokenType.toLowerCase() === BEARER_AUTH) { + // get config jwt secret + // verify token and set authUser + try { + const verified = verify(token, jwtSecret) as JwtPayload + if ( + actorTypes.includes("*") || + actorTypes.includes(verified.actor_type) + ) { + return verified as AuthContext + } + } catch (err) { + return null + } + } + } + + return null +} diff --git a/packages/framework/framework/src/http/middlewares/error-handler.ts b/packages/framework/framework/src/http/middlewares/error-handler.ts new file mode 100644 index 0000000000..b315cd0448 --- /dev/null +++ b/packages/framework/framework/src/http/middlewares/error-handler.ts @@ -0,0 +1,102 @@ +import { NextFunction, Response } from "express" + +import { ContainerRegistrationKeys, MedusaError } from "@medusajs/utils" +import { formatException } from "./exception-formatter" +import { MedusaRequest } from "../types" +import { logger as logger_ } from "../../logger" + +const QUERY_RUNNER_RELEASED = "QueryRunnerAlreadyReleasedError" +const TRANSACTION_STARTED = "TransactionAlreadyStartedError" +const TRANSACTION_NOT_STARTED = "TransactionNotStartedError" + +const API_ERROR = "api_error" +const INVALID_REQUEST_ERROR = "invalid_request_error" +const INVALID_STATE_ERROR = "invalid_state_error" + +export function errorHandler() { + return ( + err: MedusaError, + req: MedusaRequest, + res: Response, + next: NextFunction + ) => { + const logger: typeof logger_ = req.scope.resolve( + ContainerRegistrationKeys.LOGGER + ) + + err = formatException(err) + + logger.error(err) + + const errorType = err.type || err.name + + const errObj = { + code: err.code, + type: err.type, + message: err.message, + } + + let statusCode = 500 + switch (errorType) { + case QUERY_RUNNER_RELEASED: + case TRANSACTION_STARTED: + case TRANSACTION_NOT_STARTED: + case MedusaError.Types.CONFLICT: + statusCode = 409 + errObj.code = INVALID_STATE_ERROR + errObj.message = + "The request conflicted with another request. You may retry the request with the provided Idempotency-Key." + break + case MedusaError.Types.UNAUTHORIZED: + statusCode = 401 + break + case MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR: + statusCode = 422 + break + case MedusaError.Types.DUPLICATE_ERROR: + statusCode = 422 + errObj.code = INVALID_REQUEST_ERROR + break + case MedusaError.Types.NOT_ALLOWED: + case MedusaError.Types.INVALID_DATA: + statusCode = 400 + break + case MedusaError.Types.NOT_FOUND: + statusCode = 404 + break + case MedusaError.Types.DB_ERROR: + statusCode = 500 + errObj.code = API_ERROR + break + case MedusaError.Types.UNEXPECTED_STATE: + case MedusaError.Types.INVALID_ARGUMENT: + break + default: + errObj.code = "unknown_error" + errObj.message = "An unknown error occurred." + errObj.type = "unknown_error" + break + } + + res.status(statusCode).json(errObj) + } +} + +/** + * @schema Error + * title: "Response Error" + * type: object + * properties: + * code: + * type: string + * description: A slug code to indicate the type of the error. + * enum: [invalid_state_error, invalid_request_error, api_error, unknown_error] + * message: + * type: string + * description: Description of the error that occurred. + * example: "first_name must be a string" + * type: + * type: string + * description: A slug indicating the type of the error. + * enum: [QueryRunnerAlreadyReleasedError, TransactionAlreadyStartedError, TransactionNotStartedError, conflict, unauthorized, payment_authorization_error, duplicate_error, not_allowed, invalid_data, not_found, database_error, unexpected_state, invalid_argument, unknown_error] + */ diff --git a/packages/framework/framework/src/http/middlewares/exception-formatter.ts b/packages/framework/framework/src/http/middlewares/exception-formatter.ts new file mode 100644 index 0000000000..afcd9481a4 --- /dev/null +++ b/packages/framework/framework/src/http/middlewares/exception-formatter.ts @@ -0,0 +1,56 @@ +import { MedusaError } from "@medusajs/utils" + +export enum PostgresError { + DUPLICATE_ERROR = "23505", + FOREIGN_KEY_ERROR = "23503", + SERIALIZATION_FAILURE = "40001", + NULL_VIOLATION = "23502", +} + +export const formatException = (err): MedusaError => { + switch (err.code) { + case PostgresError.DUPLICATE_ERROR: + return new MedusaError( + MedusaError.Types.DUPLICATE_ERROR, + `${err.table.charAt(0).toUpperCase()}${err.table.slice( + 1 + )} with ${err.detail.slice(4).replace(/[()=]/g, (s) => { + return s === "=" ? " " : "" + })}` + ) + case PostgresError.FOREIGN_KEY_ERROR: { + const matches = + /Key \(([\w-\d]+)\)=\(([\w-\d]+)\) is not present in table "(\w+)"/g.exec( + err.detail + ) + + if (matches?.length !== 4) { + return new MedusaError( + MedusaError.Types.NOT_FOUND, + JSON.stringify(matches) + ) + } + + return new MedusaError( + MedusaError.Types.NOT_FOUND, + `${matches[3]?.charAt(0).toUpperCase()}${matches[3]?.slice(1)} with ${ + matches[1] + } ${matches[2]} does not exist.` + ) + } + case PostgresError.SERIALIZATION_FAILURE: { + return new MedusaError( + MedusaError.Types.CONFLICT, + err?.detail ?? err?.message + ) + } + case PostgresError.NULL_VIOLATION: { + return new MedusaError( + MedusaError.Types.INVALID_DATA, + `Can't insert null value in field ${err?.column} on insert in table ${err?.table}` + ) + } + default: + return err + } +} diff --git a/packages/framework/framework/src/http/middlewares/index.ts b/packages/framework/framework/src/http/middlewares/index.ts new file mode 100644 index 0000000000..91e8a13a28 --- /dev/null +++ b/packages/framework/framework/src/http/middlewares/index.ts @@ -0,0 +1,3 @@ +export * from "./authenticate-middleware" +export * from "./error-handler" +export * from "./exception-formatter" diff --git a/packages/medusa/src/loaders/helpers/routing/index.ts b/packages/framework/framework/src/http/router.ts similarity index 72% rename from packages/medusa/src/loaders/helpers/routing/index.ts rename to packages/framework/framework/src/http/router.ts index 51d4ce11a0..a7ff8bee2d 100644 --- a/packages/medusa/src/loaders/helpers/routing/index.ts +++ b/packages/framework/framework/src/http/router.ts @@ -1,16 +1,13 @@ -import { ConfigModule } from "@medusajs/types" import { parseCorsOrigins, promiseAll, wrapHandler } from "@medusajs/utils" import cors from "cors" import { type Express, json, Router, text, urlencoded } from "express" import { readdir } from "fs/promises" -import { extname, join, sep } from "path" -import { MedusaRequest, MedusaResponse } from "../../../types/routing" -import { authenticate, errorHandler } from "../../../utils/middlewares" -import { logger } from "@medusajs/framework" +import { extname, join, parse, sep } from "path" import { - AsyncRouteHandler, GlobalMiddlewareDescriptor, HTTP_METHODS, + MedusaRequest, + MedusaResponse, MiddlewareRoute, MiddlewaresConfig, MiddlewareVerb, @@ -19,6 +16,9 @@ import { RouteDescriptor, RouteVerb, } from "./types" +import { authenticate, errorHandler } from "./middlewares" +import { configManager } from "../config" +import { logger } from "../logger" const log = ({ activityId, @@ -163,40 +163,68 @@ function getBodyParserMiddleware(args?: ParserConfigArgs) { ] } -export class RoutesLoader { - protected routesMap = new Map() - protected globalMiddlewaresDescriptor: GlobalMiddlewareDescriptor | undefined +// TODO this router would need a proper rework, but it is out of scope right now - protected app: Express - protected router: Router - protected activityId?: string - protected rootDir: string - protected configModule: ConfigModule - protected excludes: RegExp[] = [ +class ApiRoutesLoader { + /** + * Map of router path and its descriptor + * @private + */ + #routesMap = new Map() + + /** + * Global middleware descriptors + * @private + */ + #globalMiddlewaresDescriptor: GlobalMiddlewareDescriptor | undefined + + /** + * An express instance + * @private + */ + readonly #app: Express + + /** + * A router to assign the route to + * @private + */ + readonly #router: Router + + /** + * An eventual activity id for information tracking + * @private + */ + readonly #activityId?: string + + /** + * The list of file names to exclude from the routes scan + * @private + */ + #excludes: RegExp[] = [ /\.DS_Store/, - /(\.ts\.map|\.js\.map|\.d\.ts)/, + /(\.ts\.map|\.js\.map|\.d\.ts|\.md)/, /^_[^/\\]*(\.[^/\\]+)?$/, ] + /** + * Path from where to load the routes from + * @private + */ + readonly #sourceDir: string + constructor({ app, activityId, - rootDir, - configModule, - excludes = [], + sourceDir, }: { app: Express activityId?: string - rootDir: string - configModule: ConfigModule - excludes?: RegExp[] + sourceDir: string }) { - this.app = app - this.router = Router() - this.activityId = activityId - this.rootDir = rootDir - this.configModule = configModule - this.excludes.push(...(excludes ?? [])) + this.#app = app + this.#router = Router() + this.#activityId = activityId + this.#sourceDir = sourceDir } /** @@ -215,7 +243,7 @@ export class RoutesLoader { }): void { if (!config?.routes && !config?.errorHandler) { log({ - activityId: this.activityId, + activityId: this.#activityId, message: `Empty middleware config. Skipping middleware application.`, }) @@ -255,7 +283,7 @@ export class RoutesLoader { if (match?.[1] && !Number.isInteger(match?.[1])) { if (parameters.has(match?.[1])) { log({ - activityId: this.activityId, + activityId: this.#activityId, message: `Duplicate parameters found in route ${route} (${match?.[1]})`, }) @@ -289,12 +317,12 @@ export class RoutesLoader { */ protected async createRoutesConfig(): Promise { await promiseAll( - [...this.routesMap.values()].map(async (descriptor: RouteDescriptor) => { + [...this.#routesMap.values()].map(async (descriptor: RouteDescriptor) => { const absolutePath = descriptor.absolutePath const route = descriptor.route return await import(absolutePath).then((import_) => { - const map = this.routesMap + const map = this.#routesMap const config: RouteConfig = { routes: [], @@ -353,7 +381,7 @@ export class RoutesLoader { }) } else { log({ - activityId: this.activityId, + activityId: this.#activityId, message: `Skipping handler ${handler} in ${absolutePath}. Invalid HTTP method: ${handler}.`, }) } @@ -361,7 +389,7 @@ export class RoutesLoader { if (!config.routes?.length) { log({ - activityId: this.activityId, + activityId: this.#activityId, message: `No valid route handlers detected in ${absolutePath}. Skipping route configuration.`, }) @@ -376,29 +404,20 @@ export class RoutesLoader { ) } - protected createRoutesDescriptor({ - childPath, - parentPath, - }: { - childPath: string - parentPath?: string - }) { + protected createRoutesDescriptor(path: string) { const descriptor: RouteDescriptor = { - absolutePath: childPath, - relativePath: "", + absolutePath: path, + relativePath: path, route: "", priority: Infinity, } - if (parentPath) { - childPath = childPath.replace(parentPath, "") - } - + const childPath = path.replace(this.#sourceDir, "") descriptor.relativePath = childPath let routeToParse = childPath - const pathSegments = childPath.split(sep) + const pathSegments = routeToParse.split(sep) const lastSegment = pathSegments[pathSegments.length - 1] if (lastSegment.startsWith("route")) { @@ -409,40 +428,36 @@ export class RoutesLoader { descriptor.route = this.parseRoute(routeToParse) descriptor.priority = calculatePriority(descriptor.route) - this.routesMap.set(childPath, descriptor) + this.#routesMap.set(path, descriptor) } - protected async createMiddlewaresDescriptor({ - dirPath, - }: { - dirPath: string - }) { - const files = await readdir(dirPath) + protected async createMiddlewaresDescriptor() { + const filePaths = await readdir(this.#sourceDir) - const middlewareFilePath = files - .filter((path) => { - if ( - this.excludes.length && - this.excludes.some((exclude) => exclude.test(path)) - ) { - return false - } + const filteredFilePaths = filePaths.filter((path) => { + const pathToCheck = path.replace(this.#sourceDir, "") + return !pathToCheck + .split(sep) + .some((segment) => + this.#excludes.some((exclude) => exclude.test(segment)) + ) + }) - return true - }) - .find((file) => { - return file.replace(/\.[^/.]+$/, "") === MIDDLEWARES_NAME - }) + const middlewareFilePath = filteredFilePaths.find((file) => { + return file.replace(/\.[^/.]+$/, "") === MIDDLEWARES_NAME + }) if (!middlewareFilePath) { log({ - activityId: this.activityId, - message: `No middleware files found in ${dirPath}. Skipping middleware configuration.`, + activityId: this.#activityId, + message: `No middleware files found in ${ + this.#sourceDir + }. Skipping middleware configuration.`, }) return } - const absolutePath = join(dirPath, middlewareFilePath) + const absolutePath = join(this.#sourceDir, middlewareFilePath) try { await import(absolutePath).then((import_) => { @@ -452,7 +467,7 @@ export class RoutesLoader { if (!middlewaresConfig) { log({ - activityId: this.activityId, + activityId: this.#activityId, message: `No middleware configuration found in ${absolutePath}. Skipping middleware configuration.`, }) return @@ -471,11 +486,11 @@ export class RoutesLoader { this.validateMiddlewaresConfig(descriptor) - this.globalMiddlewaresDescriptor = descriptor + this.#globalMiddlewaresDescriptor = descriptor }) } catch (error) { log({ - activityId: this.activityId, + activityId: this.#activityId, message: `Failed to load middleware configuration in ${absolutePath}. Skipping middleware configuration.`, }) @@ -483,54 +498,34 @@ export class RoutesLoader { } } - protected async createRoutesMap({ - dirPath, - parentPath, - }: { - dirPath: string - parentPath?: string - }): Promise { + protected async createRoutesMap(): Promise { await promiseAll( - await readdir(dirPath, { withFileTypes: true }).then((entries) => { - return entries - .filter((entry) => { - if ( - this.excludes.length && - this.excludes.some((exclude) => exclude.test(entry.name)) - ) { - return false - } + await readdir(this.#sourceDir, { + recursive: true, + withFileTypes: true, + }).then((entries) => { + const fileEntries = entries.filter((entry) => { + const fullPathFromSource = join(entry.path, entry.name).replace( + this.#sourceDir, + "" + ) + const isExcluded = fullPathFromSource + .split(sep) + .some((segment) => + this.#excludes.some((exclude) => exclude.test(segment)) + ) - let name = entry.name + return ( + !entry.isDirectory() && + !isExcluded && + parse(entry.name).name === ROUTE_NAME + ) + }) - const extension = extname(name) - - if (extension) { - name = name.replace(extension, "") - } - - if (entry.isFile() && name !== ROUTE_NAME) { - return false - } - - return true - }) - .map(async (entry) => { - const childPath = join(dirPath, entry.name) - - if (entry.isDirectory()) { - return await this.createRoutesMap({ - dirPath: childPath, - parentPath: parentPath ?? dirPath, - }) - } - - return this.createRoutesDescriptor({ - childPath, - parentPath, - }) - }) - .flat(Infinity) + return fileEntries.map(async (entry) => { + const path = join(entry.path, entry.name) + return this.createRoutesDescriptor(path) + }) }) ) } @@ -539,7 +534,7 @@ export class RoutesLoader { * Apply the most specific body parser middleware to the router */ applyBodyParserMiddleware(path: string, method: RouteVerb): void { - const middlewareDescriptor = this.globalMiddlewaresDescriptor + const middlewareDescriptor = this.#globalMiddlewaresDescriptor const mostSpecificConfig = findMatch( path, @@ -548,13 +543,13 @@ export class RoutesLoader { ) if (!mostSpecificConfig || mostSpecificConfig?.bodyParser === undefined) { - this.router[method.toLowerCase()](path, ...getBodyParserMiddleware()) + this.#router[method.toLowerCase()](path, ...getBodyParserMiddleware()) return } if (mostSpecificConfig?.bodyParser) { - this.router[method.toLowerCase()]( + this.#router[method.toLowerCase()]( path, ...getBodyParserMiddleware(mostSpecificConfig?.bodyParser) ) @@ -572,7 +567,7 @@ export class RoutesLoader { * that they are applied before any other middleware. */ applyRouteSpecificMiddlewares(): void { - const prioritizedRoutes = prioritize([...this.routesMap.values()]) + const prioritizedRoutes = prioritize([...this.#routesMap.values()]) for (const descriptor of prioritizedRoutes) { if (!descriptor.config?.routes?.length) { @@ -591,11 +586,11 @@ export class RoutesLoader { /** * Apply the admin cors */ - this.router.use( + this.#router.use( descriptor.route, cors({ origin: parseCorsOrigins( - this.configModule.projectConfig.http.adminCors + configManager.config.projectConfig.http.adminCors ), credentials: true, }) @@ -606,11 +601,11 @@ export class RoutesLoader { /** * Apply the auth cors */ - this.router.use( + this.#router.use( descriptor.route, cors({ origin: parseCorsOrigins( - this.configModule.projectConfig.http.authCors + configManager.config.projectConfig.http.authCors ), credentials: true, }) @@ -621,11 +616,11 @@ export class RoutesLoader { /** * Apply the store cors */ - this.router.use( + this.#router.use( descriptor.route, cors({ origin: parseCorsOrigins( - this.configModule.projectConfig.http.storeCors + configManager.config.projectConfig.http.storeCors ), credentials: true, }) @@ -634,7 +629,7 @@ export class RoutesLoader { // 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.#router.use( descriptor.route, authenticate("customer", ["bearer", "session"], { allowUnauthenticated: true, @@ -644,7 +639,7 @@ export class RoutesLoader { 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( + this.#router.use( descriptor.route, authenticate("user", ["bearer", "session", "api-key"]) ) @@ -664,7 +659,7 @@ export class RoutesLoader { * Apply the error handler middleware to the router */ applyErrorHandlerMiddleware(): void { - const middlewareDescriptor = this.globalMiddlewaresDescriptor + const middlewareDescriptor = this.#globalMiddlewaresDescriptor const errorHandlerFn = middlewareDescriptor?.config?.errorHandler /** @@ -678,7 +673,7 @@ export class RoutesLoader { * If the user has provided a custom error handler then use it */ if (errorHandlerFn) { - this.router.use(errorHandlerFn) + this.#router.use(errorHandlerFn as any) return } @@ -686,17 +681,17 @@ export class RoutesLoader { * If the user has not provided a custom error handler then use the * default one. */ - this.router.use(errorHandler()) + this.#router.use(errorHandler() as any) } protected async registerRoutes(): Promise { - const middlewareDescriptor = this.globalMiddlewaresDescriptor + const middlewareDescriptor = this.#globalMiddlewaresDescriptor const shouldWrapHandler = middlewareDescriptor?.config ? middlewareDescriptor.config.errorHandler !== false : true - const prioritizedRoutes = prioritize([...this.routesMap.values()]) + const prioritizedRoutes = prioritize([...this.#routesMap.values()]) for (const descriptor of prioritizedRoutes) { if (!descriptor.config?.routes?.length) { @@ -707,7 +702,7 @@ export class RoutesLoader { for (const route of routes) { log({ - activityId: this.activityId, + activityId: this.#activityId, message: `Registering route [${route.method?.toUpperCase()}] - ${ descriptor.route }`, @@ -718,16 +713,16 @@ export class RoutesLoader { * we wrap the handler in a try/catch block. */ const handler = shouldWrapHandler - ? wrapHandler(route.handler as AsyncRouteHandler) + ? wrapHandler(route.handler as Parameters[0]) : route.handler - this.router[route.method!.toLowerCase()](descriptor.route, handler) + this.#router[route.method!.toLowerCase()](descriptor.route, handler) } } } protected async registerMiddlewares(): Promise { - const descriptor = this.globalMiddlewaresDescriptor + const descriptor = this.#globalMiddlewaresDescriptor if (!descriptor) { return @@ -757,17 +752,17 @@ export class RoutesLoader { for (const method of methods) { log({ - activityId: this.activityId, + activityId: this.#activityId, message: `Registering middleware [${method}] - ${route.matcher}`, }) - this.router[method.toLowerCase()](route.matcher, ...route.middlewares) + this.#router[method.toLowerCase()](route.matcher, ...route.middlewares) } } } async load() { - performance && performance.mark("file-base-routing-start" + this.rootDir) + performance && performance.mark("file-base-routing-start" + this.#sourceDir) let apiExists = true @@ -778,15 +773,15 @@ export class RoutesLoader { * directory does not exist. */ try { - await readdir(this.rootDir) + await readdir(this.#sourceDir) } catch (_error) { apiExists = false } if (apiExists) { - await this.createMiddlewaresDescriptor({ dirPath: this.rootDir }) + await this.createMiddlewaresDescriptor() - await this.createRoutesMap({ dirPath: this.rootDir }) + await this.createRoutesMap() await this.createRoutesConfig() this.applyRouteSpecificMiddlewares() @@ -802,28 +797,78 @@ export class RoutesLoader { * This prevents middleware from a plugin from * bleeding into the global middleware stack. */ - this.app.use("/", this.router) + this.#app.use("/", this.#router) } - performance && performance.mark("file-base-routing-end" + this.rootDir) + performance && performance.mark("file-base-routing-end" + this.#sourceDir) const timeSpent = performance && performance .measure( - "file-base-routing-measure" + this.rootDir, - "file-base-routing-start" + this.rootDir, - "file-base-routing-end" + this.rootDir + "file-base-routing-measure" + this.#sourceDir, + "file-base-routing-start" + this.#sourceDir, + "file-base-routing-end" + this.#sourceDir ) ?.duration?.toFixed(2) log({ - activityId: this.activityId, + activityId: this.#activityId, message: `Routes loaded in ${timeSpent} ms`, }) - this.routesMap.clear() - this.globalMiddlewaresDescriptor = undefined + this.#routesMap.clear() + this.#globalMiddlewaresDescriptor = undefined } } -export default RoutesLoader +export class RoutesLoader { + /** + * An express instance + * @private + */ + readonly #app: Express + + /** + * An eventual activity id for information tracking + * @private + */ + readonly #activityId?: string + + /** + * Path from where to load the routes from + * @private + */ + readonly #sourceDir: string | string[] + + constructor({ + app, + activityId, + sourceDir, + }: { + app: Express + activityId?: string + sourceDir: string | string[] + }) { + this.#app = app + this.#activityId = activityId + this.#sourceDir = sourceDir + } + + async load() { + const normalizedSourcePath = Array.isArray(this.#sourceDir) + ? this.#sourceDir + : [this.#sourceDir] + + const promises = normalizedSourcePath.map(async (sourcePath) => { + const apiRoutesLoader = new ApiRoutesLoader({ + app: this.#app, + activityId: this.#activityId, + sourceDir: sourcePath, + }) + + await apiRoutesLoader.load() + }) + + await promiseAll(promises) + } +} diff --git a/packages/framework/framework/src/http/types.ts b/packages/framework/framework/src/http/types.ts new file mode 100644 index 0000000000..f632d26079 --- /dev/null +++ b/packages/framework/framework/src/http/types.ts @@ -0,0 +1,180 @@ +import { ZodObject } from "zod" +import type { NextFunction, Request, Response } from "express" + +import { MedusaPricingContext, RequestQueryFields } from "@medusajs/types" +import * as core from "express-serve-static-core" +import { MedusaContainer } from "../container" + +export interface FindConfig { + select?: (keyof Entity)[] + skip?: number + take?: number + relations?: string[] + order?: { [K: string]: "ASC" | "DESC" } +} + +/** + * List of all the supported HTTP methods + */ +export const HTTP_METHODS = [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "OPTIONS", + "HEAD", +] as const + +export type RouteVerb = (typeof HTTP_METHODS)[number] +export type MiddlewareVerb = "USE" | "ALL" | RouteVerb + +type SyncRouteHandler = (req: MedusaRequest, res: MedusaResponse) => void + +export type AsyncRouteHandler = ( + req: MedusaRequest, + res: MedusaResponse +) => Promise + +type RouteHandler = SyncRouteHandler | AsyncRouteHandler + +export type RouteImplementation = { + method?: RouteVerb + handler: RouteHandler +} + +export type RouteConfig = { + optedOutOfAuth?: boolean + routeType?: "admin" | "store" | "auth" + shouldAppendAdminCors?: boolean + shouldAppendStoreCors?: boolean + shouldAppendAuthCors?: boolean + routes?: RouteImplementation[] +} + +export type MiddlewareFunction = + | MedusaRequestHandler + | ((...args: any[]) => any) + +export type MedusaErrorHandlerFunction = ( + error: any, + req: MedusaRequest, + res: MedusaResponse, + next: MedusaNextFunction +) => Promise | void + +export type ParserConfigArgs = { + sizeLimit?: string | number | undefined + preserveRawBody?: boolean +} + +export type ParserConfig = false | ParserConfigArgs + +export type MiddlewareRoute = { + method?: MiddlewareVerb | MiddlewareVerb[] + matcher: string | RegExp + bodyParser?: ParserConfig + middlewares?: MiddlewareFunction[] +} + +export type MiddlewaresConfig = { + errorHandler?: false | MedusaErrorHandlerFunction + routes?: MiddlewareRoute[] +} + +export type RouteDescriptor = { + absolutePath: string + relativePath: string + route: string + priority: number + config?: RouteConfig +} + +export type GlobalMiddlewareDescriptor = { + config?: MiddlewaresConfig +} + +export interface MedusaRequest + extends Request { + validatedBody: Body + validatedQuery: RequestQueryFields & Record + /** + * TODO: shouldn't this correspond to returnable fields instead of allowed fields? also it is used by the cleanResponseData util + */ + allowedProperties: string[] + /** + * An object containing the select, relation, skip, take and order to be used with medusa internal services + */ + listConfig: FindConfig + /** + * An object containing the select, relation to be used with medusa internal services + */ + retrieveConfig: FindConfig + /** + * An object containing fields and variables to be used with the remoteQuery + */ + remoteQueryConfig: { + fields: string[] + pagination: { order?: Record; skip?: number; take?: number } + } + /** + * An object containing the fields that are filterable e.g `{ id: Any }` + */ + filterableFields: Record + includes?: Record + /** + * An array of fields and relations that are allowed to be queried, this can be set by the + * consumer as part of a middleware and it will take precedence over the defaultAllowedFields + * @deprecated use `allowed` instead + */ + allowedFields?: string[] + /** + * An array of fields and relations that are allowed to be queried, this can be set by the + * consumer as part of a middleware and it will take precedence over the defaultAllowedFields set + * by the api + */ + allowed?: string[] + errors: string[] + scope: MedusaContainer + session?: any + rawBody?: any + requestId?: string + /** + * An object that carries the context that is used to calculate prices for variants + */ + pricingContext?: MedusaPricingContext + /** + * A generic context object that can be used across the request lifecycle + */ + context?: Record + /** + * Custom validators for the request body and query params that will be + * merged with the original validator of the route. + */ + extendedValidators?: { + body?: ZodObject + queryParams?: ZodObject + } +} + +export interface AuthContext { + actor_id: string + actor_type: string + auth_identity_id: string + app_metadata: Record +} + +export interface AuthenticatedMedusaRequest + extends MedusaRequest { + auth_context: AuthContext +} + +export type MedusaResponse = Response + +export type MedusaNextFunction = NextFunction + +export type MedusaRequestHandler = ( + req: MedusaRequest, + res: MedusaResponse, + next: MedusaNextFunction +) => Promise | void diff --git a/packages/framework/framework/src/http/utils/define-middlewares.ts b/packages/framework/framework/src/http/utils/define-middlewares.ts new file mode 100644 index 0000000000..61af0e4bff --- /dev/null +++ b/packages/framework/framework/src/http/utils/define-middlewares.ts @@ -0,0 +1,65 @@ +import { + MedusaNextFunction, + MedusaRequest, + MedusaRequestHandler, + MedusaResponse, + MiddlewaresConfig, + MiddlewareVerb, + ParserConfig, +} from "../types" +import { ZodObject } from "zod" + +/** + * A helper function to configure the routes by defining custom middleware, + * bodyparser config and validators to be merged with the pre-existing + * route validators. + */ +export function defineMiddlewares< + Route extends { + method?: MiddlewareVerb | MiddlewareVerb[] + matcher: string | RegExp + bodyParser?: ParserConfig + extendedValidators?: { + body?: ZodObject + queryParams?: ZodObject + } + // eslint-disable-next-line space-before-function-paren + middlewares?: (( + req: Req, + res: MedusaResponse, + next: MedusaNextFunction + ) => any)[] + } +>( + config: + | Route[] + | { routes?: Route[]; errorHandler?: MiddlewaresConfig["errorHandler"] } +): MiddlewaresConfig { + const routes = Array.isArray(config) ? config : config.routes || [] + const errorHandler = Array.isArray(config) ? undefined : config.errorHandler + + return { + errorHandler, + routes: routes.map((route) => { + const { middlewares, extendedValidators, ...rest } = route + const customMiddleware: MedusaRequestHandler[] = [] + + /** + * Define a custom validator when "extendedValidators.body" or + * "extendedValidators.queryParams" validation schema is + * provided. + */ + if (extendedValidators?.body || extendedValidators?.queryParams) { + customMiddleware.push((req, _, next) => { + req.extendedValidators = extendedValidators + next() + }) + } + + return { + ...rest, + middlewares: customMiddleware.concat(middlewares || []), + } + }), + } +} diff --git a/packages/medusa/src/api/admin/api-keys/middlewares.ts b/packages/medusa/src/api/admin/api-keys/middlewares.ts index c8f4857ec5..0b57d5af76 100644 --- a/packages/medusa/src/api/admin/api-keys/middlewares.ts +++ b/packages/medusa/src/api/admin/api-keys/middlewares.ts @@ -1,6 +1,6 @@ import * as QueryConfig from "./query-config" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { AdminCreateApiKey, diff --git a/packages/medusa/src/api/admin/campaigns/middlewares.ts b/packages/medusa/src/api/admin/campaigns/middlewares.ts index ccef152448..956bcbaf6d 100644 --- a/packages/medusa/src/api/admin/campaigns/middlewares.ts +++ b/packages/medusa/src/api/admin/campaigns/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import { createLinkBody } from "../../utils/validators" diff --git a/packages/medusa/src/api/admin/claims/middlewares.ts b/packages/medusa/src/api/admin/claims/middlewares.ts index 0ba32f06a0..5ed1f5e94d 100644 --- a/packages/medusa/src/api/admin/claims/middlewares.ts +++ b/packages/medusa/src/api/admin/claims/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/admin/collections/middlewares.ts b/packages/medusa/src/api/admin/collections/middlewares.ts index f2ce2c7318..3641a6f215 100644 --- a/packages/medusa/src/api/admin/collections/middlewares.ts +++ b/packages/medusa/src/api/admin/collections/middlewares.ts @@ -1,5 +1,5 @@ import * as QueryConfig from "./query-config" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { AdminCreateCollection, diff --git a/packages/medusa/src/api/admin/currencies/middlewares.ts b/packages/medusa/src/api/admin/currencies/middlewares.ts index 613237b379..4485669767 100644 --- a/packages/medusa/src/api/admin/currencies/middlewares.ts +++ b/packages/medusa/src/api/admin/currencies/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" import { AdminGetCurrencyParams, AdminGetCurrenciesParams } from "./validators" diff --git a/packages/medusa/src/api/admin/customer-groups/middlewares.ts b/packages/medusa/src/api/admin/customer-groups/middlewares.ts index 61363e157a..cdb01a4f48 100644 --- a/packages/medusa/src/api/admin/customer-groups/middlewares.ts +++ b/packages/medusa/src/api/admin/customer-groups/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import { createLinkBody } from "../../utils/validators" diff --git a/packages/medusa/src/api/admin/customers/middlewares.ts b/packages/medusa/src/api/admin/customers/middlewares.ts index 8f298ce59a..279d20461d 100644 --- a/packages/medusa/src/api/admin/customers/middlewares.ts +++ b/packages/medusa/src/api/admin/customers/middlewares.ts @@ -10,7 +10,7 @@ import { AdminUpdateCustomerAddress, } from "./validators" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" diff --git a/packages/medusa/src/api/admin/draft-orders/middlewares.ts b/packages/medusa/src/api/admin/draft-orders/middlewares.ts index baedf9cab0..04f11e4f0e 100644 --- a/packages/medusa/src/api/admin/draft-orders/middlewares.ts +++ b/packages/medusa/src/api/admin/draft-orders/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts b/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts index ab045e401e..b83cfb2264 100644 --- a/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts +++ b/packages/medusa/src/api/admin/fulfillment-providers/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/admin/fulfillment-sets/middlewares.ts b/packages/medusa/src/api/admin/fulfillment-sets/middlewares.ts index aaa273213c..1ee6911a52 100644 --- a/packages/medusa/src/api/admin/fulfillment-sets/middlewares.ts +++ b/packages/medusa/src/api/admin/fulfillment-sets/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/admin/fulfillments/middlewares.ts b/packages/medusa/src/api/admin/fulfillments/middlewares.ts index 63813659ba..af5a1643d4 100644 --- a/packages/medusa/src/api/admin/fulfillments/middlewares.ts +++ b/packages/medusa/src/api/admin/fulfillments/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/admin/inventory-items/middlewares.ts b/packages/medusa/src/api/admin/inventory-items/middlewares.ts index 34106ee488..79452445c6 100644 --- a/packages/medusa/src/api/admin/inventory-items/middlewares.ts +++ b/packages/medusa/src/api/admin/inventory-items/middlewares.ts @@ -1,5 +1,5 @@ import * as QueryConfig from "./query-config" -import { MiddlewareRoute } from "../../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { AdminCreateInventoryItem, diff --git a/packages/medusa/src/api/admin/invites/middlewares.ts b/packages/medusa/src/api/admin/invites/middlewares.ts index 9d37c1d0ef..6fae59b9a9 100644 --- a/packages/medusa/src/api/admin/invites/middlewares.ts +++ b/packages/medusa/src/api/admin/invites/middlewares.ts @@ -8,7 +8,7 @@ import { AdminInviteAccept, } from "./validators" -import { MiddlewareRoute } from "../../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" import { authenticate } from "../../../utils/middlewares/authenticate-middleware" import { validateAndTransformQuery } from "../../utils/validate-query" import { validateAndTransformBody } from "../../utils/validate-body" diff --git a/packages/medusa/src/api/admin/notifications/middlewares.ts b/packages/medusa/src/api/admin/notifications/middlewares.ts index b9423db726..fef91e0728 100644 --- a/packages/medusa/src/api/admin/notifications/middlewares.ts +++ b/packages/medusa/src/api/admin/notifications/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" import { diff --git a/packages/medusa/src/api/admin/orders/middlewares.ts b/packages/medusa/src/api/admin/orders/middlewares.ts index c6ed51e82e..8236a6df27 100644 --- a/packages/medusa/src/api/admin/orders/middlewares.ts +++ b/packages/medusa/src/api/admin/orders/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/admin/payments/middlewares.ts b/packages/medusa/src/api/admin/payments/middlewares.ts index d5119cfad4..59b3173008 100644 --- a/packages/medusa/src/api/admin/payments/middlewares.ts +++ b/packages/medusa/src/api/admin/payments/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" import { unlessPath } from "../../utils/unless-path" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" diff --git a/packages/medusa/src/api/admin/price-lists/middlewares.ts b/packages/medusa/src/api/admin/price-lists/middlewares.ts index e35531dda7..e076096ee2 100644 --- a/packages/medusa/src/api/admin/price-lists/middlewares.ts +++ b/packages/medusa/src/api/admin/price-lists/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import { createBatchBody, createLinkBody } from "../../utils/validators" diff --git a/packages/medusa/src/api/admin/price-preferences/middlewares.ts b/packages/medusa/src/api/admin/price-preferences/middlewares.ts index 3f3d5b2112..f90e2584d6 100644 --- a/packages/medusa/src/api/admin/price-preferences/middlewares.ts +++ b/packages/medusa/src/api/admin/price-preferences/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/admin/product-categories/middlewares.ts b/packages/medusa/src/api/admin/product-categories/middlewares.ts index 7c83f4b9ac..77fa02d46b 100644 --- a/packages/medusa/src/api/admin/product-categories/middlewares.ts +++ b/packages/medusa/src/api/admin/product-categories/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import { createLinkBody } from "../../utils/validators" diff --git a/packages/medusa/src/api/admin/product-tags/middlewares.ts b/packages/medusa/src/api/admin/product-tags/middlewares.ts index 6d9d7a7b49..bbfe02af64 100644 --- a/packages/medusa/src/api/admin/product-tags/middlewares.ts +++ b/packages/medusa/src/api/admin/product-tags/middlewares.ts @@ -1,5 +1,5 @@ import * as QueryConfig from "./query-config" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { AdminCreateProductTag, diff --git a/packages/medusa/src/api/admin/product-types/middlewares.ts b/packages/medusa/src/api/admin/product-types/middlewares.ts index f6316b0f79..aa5bb52c24 100644 --- a/packages/medusa/src/api/admin/product-types/middlewares.ts +++ b/packages/medusa/src/api/admin/product-types/middlewares.ts @@ -1,5 +1,5 @@ import * as QueryConfig from "./query-config" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { AdminCreateProductType, diff --git a/packages/medusa/src/api/admin/products/middlewares.ts b/packages/medusa/src/api/admin/products/middlewares.ts index 0e557b2359..cab1dea7a4 100644 --- a/packages/medusa/src/api/admin/products/middlewares.ts +++ b/packages/medusa/src/api/admin/products/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter" import { unlessPath } from "../../utils/unless-path" import { validateAndTransformBody } from "../../utils/validate-body" diff --git a/packages/medusa/src/api/admin/promotions/middlewares.ts b/packages/medusa/src/api/admin/promotions/middlewares.ts index 5c47243fb7..d6787afbbf 100644 --- a/packages/medusa/src/api/admin/promotions/middlewares.ts +++ b/packages/medusa/src/api/admin/promotions/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { unlessPath } from "../../utils/unless-path" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" diff --git a/packages/medusa/src/api/admin/regions/middlewares.ts b/packages/medusa/src/api/admin/regions/middlewares.ts index 0edd2dfc69..3756d9ab76 100644 --- a/packages/medusa/src/api/admin/regions/middlewares.ts +++ b/packages/medusa/src/api/admin/regions/middlewares.ts @@ -1,5 +1,5 @@ import * as QueryConfig from "./query-config" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { AdminCreateRegion, diff --git a/packages/medusa/src/api/admin/reservations/middlewares.ts b/packages/medusa/src/api/admin/reservations/middlewares.ts index 3e98524109..10cd60ed9e 100644 --- a/packages/medusa/src/api/admin/reservations/middlewares.ts +++ b/packages/medusa/src/api/admin/reservations/middlewares.ts @@ -1,6 +1,6 @@ import * as QueryConfig from "./query-config" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { AdminCreateReservation, diff --git a/packages/medusa/src/api/admin/return-reasons/middlewares.ts b/packages/medusa/src/api/admin/return-reasons/middlewares.ts index 583f7dd8b4..7e9efd5065 100644 --- a/packages/medusa/src/api/admin/return-reasons/middlewares.ts +++ b/packages/medusa/src/api/admin/return-reasons/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/admin/returns/middlewares.ts b/packages/medusa/src/api/admin/returns/middlewares.ts index cfe7eeb4bf..6138a6b71e 100644 --- a/packages/medusa/src/api/admin/returns/middlewares.ts +++ b/packages/medusa/src/api/admin/returns/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/admin/sales-channels/middlewares.ts b/packages/medusa/src/api/admin/sales-channels/middlewares.ts index dc47f29391..137aa7561e 100644 --- a/packages/medusa/src/api/admin/sales-channels/middlewares.ts +++ b/packages/medusa/src/api/admin/sales-channels/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" diff --git a/packages/medusa/src/api/admin/shipping-options/middlewares.ts b/packages/medusa/src/api/admin/shipping-options/middlewares.ts index b8b3f62270..3c4f003c0b 100644 --- a/packages/medusa/src/api/admin/shipping-options/middlewares.ts +++ b/packages/medusa/src/api/admin/shipping-options/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { AdminCreateShippingOption, AdminCreateShippingOptionRule, diff --git a/packages/medusa/src/api/admin/shipping-profiles/middlewares.ts b/packages/medusa/src/api/admin/shipping-profiles/middlewares.ts index 302470cf21..2d8e355a92 100644 --- a/packages/medusa/src/api/admin/shipping-profiles/middlewares.ts +++ b/packages/medusa/src/api/admin/shipping-profiles/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import { diff --git a/packages/medusa/src/api/admin/stock-locations/middlewares.ts b/packages/medusa/src/api/admin/stock-locations/middlewares.ts index 607787f78a..0df16c4416 100644 --- a/packages/medusa/src/api/admin/stock-locations/middlewares.ts +++ b/packages/medusa/src/api/admin/stock-locations/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" diff --git a/packages/medusa/src/api/admin/stores/middlewares.ts b/packages/medusa/src/api/admin/stores/middlewares.ts index 6403733079..5056664cc3 100644 --- a/packages/medusa/src/api/admin/stores/middlewares.ts +++ b/packages/medusa/src/api/admin/stores/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/admin/tax-rates/middlewares.ts b/packages/medusa/src/api/admin/tax-rates/middlewares.ts index 23ecfcfc1d..4f8e01d077 100644 --- a/packages/medusa/src/api/admin/tax-rates/middlewares.ts +++ b/packages/medusa/src/api/admin/tax-rates/middlewares.ts @@ -9,7 +9,7 @@ import { AdminUpdateTaxRate, } from "./validators" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" export const adminTaxRateRoutesMiddlewares: MiddlewareRoute[] = [ diff --git a/packages/medusa/src/api/admin/tax-regions/middlewares.ts b/packages/medusa/src/api/admin/tax-regions/middlewares.ts index 2fe5951bb1..8f60612701 100644 --- a/packages/medusa/src/api/admin/tax-regions/middlewares.ts +++ b/packages/medusa/src/api/admin/tax-regions/middlewares.ts @@ -6,7 +6,7 @@ import { AdminGetTaxRegionsParams, } from "./validators" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" diff --git a/packages/medusa/src/api/admin/uploads/middlewares.ts b/packages/medusa/src/api/admin/uploads/middlewares.ts index 4d393e90f8..e05f3b097b 100644 --- a/packages/medusa/src/api/admin/uploads/middlewares.ts +++ b/packages/medusa/src/api/admin/uploads/middlewares.ts @@ -1,5 +1,5 @@ import multer from "multer" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { retrieveUploadConfig } from "./query-config" import { AdminGetUploadParams } from "./validators" diff --git a/packages/medusa/src/api/admin/users/middlewares.ts b/packages/medusa/src/api/admin/users/middlewares.ts index 6c3857219e..68f05967e2 100644 --- a/packages/medusa/src/api/admin/users/middlewares.ts +++ b/packages/medusa/src/api/admin/users/middlewares.ts @@ -7,7 +7,7 @@ import { AdminUpdateUser, } from "./validators" -import { MiddlewareRoute } from "../../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" import { authenticate } from "../../../utils/middlewares/authenticate-middleware" import { validateAndTransformQuery } from "../../utils/validate-query" import { validateAndTransformBody } from "../../utils/validate-body" diff --git a/packages/medusa/src/api/admin/workflows-executions/middlewares.ts b/packages/medusa/src/api/admin/workflows-executions/middlewares.ts index 3b393d41e2..015b18efd2 100644 --- a/packages/medusa/src/api/admin/workflows-executions/middlewares.ts +++ b/packages/medusa/src/api/admin/workflows-executions/middlewares.ts @@ -7,7 +7,7 @@ import { AdminGetWorkflowExecutionsParams, } from "./validators" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { validateAndTransformBody } from "../../utils/validate-body" diff --git a/packages/medusa/src/api/auth/middlewares.ts b/packages/medusa/src/api/auth/middlewares.ts index 1e573a0123..0a580964fe 100644 --- a/packages/medusa/src/api/auth/middlewares.ts +++ b/packages/medusa/src/api/auth/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" import { authenticate } from "../../utils/middlewares/authenticate-middleware" export const authRoutesMiddlewares: MiddlewareRoute[] = [ diff --git a/packages/medusa/src/api/hooks/middlewares.ts b/packages/medusa/src/api/hooks/middlewares.ts index 109d7bb5cc..73873daecb 100644 --- a/packages/medusa/src/api/hooks/middlewares.ts +++ b/packages/medusa/src/api/hooks/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" export const hooksRoutesMiddlewares: MiddlewareRoute[] = [ { diff --git a/packages/medusa/src/api/store/carts/middlewares.ts b/packages/medusa/src/api/store/carts/middlewares.ts index da5fcdc7b4..e2a486e8c9 100644 --- a/packages/medusa/src/api/store/carts/middlewares.ts +++ b/packages/medusa/src/api/store/carts/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { ensurePublishableKeyAndSalesChannelMatch } from "../../utils/middlewares/common/ensure-pub-key-sales-channel-match" import { maybeAttachPublishableKeyScopes } from "../../utils/middlewares/common/maybe-attach-pub-key-scopes" import { validateAndTransformBody } from "../../utils/validate-body" diff --git a/packages/medusa/src/api/store/collections/middlewares.ts b/packages/medusa/src/api/store/collections/middlewares.ts index b6c54c750b..200fdce6d1 100644 --- a/packages/medusa/src/api/store/collections/middlewares.ts +++ b/packages/medusa/src/api/store/collections/middlewares.ts @@ -1,5 +1,5 @@ import * as QueryConfig from "./query-config" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { StoreGetCollectionParams, diff --git a/packages/medusa/src/api/store/currencies/middlewares.ts b/packages/medusa/src/api/store/currencies/middlewares.ts index 9899e82dbf..1166d2cae9 100644 --- a/packages/medusa/src/api/store/currencies/middlewares.ts +++ b/packages/medusa/src/api/store/currencies/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" import { StoreGetCurrenciesParams, StoreGetCurrencyParams } from "./validators" diff --git a/packages/medusa/src/api/store/customers/middlewares.ts b/packages/medusa/src/api/store/customers/middlewares.ts index 628a356825..47cb542eaf 100644 --- a/packages/medusa/src/api/store/customers/middlewares.ts +++ b/packages/medusa/src/api/store/customers/middlewares.ts @@ -10,7 +10,7 @@ import { StoreGetCustomerAddressParams, } from "./validators" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { authenticate } from "../../../utils/middlewares/authenticate-middleware" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" diff --git a/packages/medusa/src/api/store/orders/middlewares.ts b/packages/medusa/src/api/store/orders/middlewares.ts index 55f8413a44..e34a20ee22 100644 --- a/packages/medusa/src/api/store/orders/middlewares.ts +++ b/packages/medusa/src/api/store/orders/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { authenticate } from "../../../utils/middlewares/authenticate-middleware" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/store/payment-collections/middlewares.ts b/packages/medusa/src/api/store/payment-collections/middlewares.ts index 6d01835d22..88219e85ac 100644 --- a/packages/medusa/src/api/store/payment-collections/middlewares.ts +++ b/packages/medusa/src/api/store/payment-collections/middlewares.ts @@ -1,11 +1,11 @@ -import { MiddlewareRoute } from "../../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as queryConfig from "./query-config" import { - StoreGetPaymentCollectionParams, StoreCreatePaymentCollection, StoreCreatePaymentSession, + StoreGetPaymentCollectionParams, } from "./validators" export const storePaymentCollectionsMiddlewares: MiddlewareRoute[] = [ diff --git a/packages/medusa/src/api/store/payment-providers/middlewares.ts b/packages/medusa/src/api/store/payment-providers/middlewares.ts index 382758ef12..aef68ec1e9 100644 --- a/packages/medusa/src/api/store/payment-providers/middlewares.ts +++ b/packages/medusa/src/api/store/payment-providers/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../types/middlewares" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import * as queryConfig from "./query-config" import { StoreGetPaymentProvidersParams } from "./validators" diff --git a/packages/medusa/src/api/store/product-categories/middlewares.ts b/packages/medusa/src/api/store/product-categories/middlewares.ts index 93fdc48bc5..427aab9da7 100644 --- a/packages/medusa/src/api/store/product-categories/middlewares.ts +++ b/packages/medusa/src/api/store/product-categories/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { applyCategoryFilters } from "./helpers" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/store/products/middlewares.ts b/packages/medusa/src/api/store/products/middlewares.ts index e90b15844d..5aead67d8a 100644 --- a/packages/medusa/src/api/store/products/middlewares.ts +++ b/packages/medusa/src/api/store/products/middlewares.ts @@ -1,5 +1,5 @@ import { isPresent, ProductStatus } from "@medusajs/utils" -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter" import { applyDefaultFilters, diff --git a/packages/medusa/src/api/store/regions/middlewares.ts b/packages/medusa/src/api/store/regions/middlewares.ts index fe6d727d71..1380fad478 100644 --- a/packages/medusa/src/api/store/regions/middlewares.ts +++ b/packages/medusa/src/api/store/regions/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" import { StoreGetRegionParams, StoreGetRegionsParams } from "./validators" diff --git a/packages/medusa/src/api/store/return-reasons/middlewares.ts b/packages/medusa/src/api/store/return-reasons/middlewares.ts index 3e36280529..4d7be59fe2 100644 --- a/packages/medusa/src/api/store/return-reasons/middlewares.ts +++ b/packages/medusa/src/api/store/return-reasons/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" import { StoreReturnReasonParams } from "./validators" diff --git a/packages/medusa/src/api/store/return/middlewares.ts b/packages/medusa/src/api/store/return/middlewares.ts index 39e27b57c2..83f074b87e 100644 --- a/packages/medusa/src/api/store/return/middlewares.ts +++ b/packages/medusa/src/api/store/return/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformBody } from "../../utils/validate-body" import { validateAndTransformQuery } from "../../utils/validate-query" import * as QueryConfig from "./query-config" diff --git a/packages/medusa/src/api/store/shipping-options/middlewares.ts b/packages/medusa/src/api/store/shipping-options/middlewares.ts index e042f97bb7..39e8e8f93b 100644 --- a/packages/medusa/src/api/store/shipping-options/middlewares.ts +++ b/packages/medusa/src/api/store/shipping-options/middlewares.ts @@ -1,4 +1,4 @@ -import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" +import { MiddlewareRoute } from "@medusajs/framework" import { validateAndTransformQuery } from "../../utils/validate-query" import { listTransformQueryConfig } from "./query-config" import { StoreGetShippingOptions } from "./validators" diff --git a/packages/medusa/src/api/utils/unless-path.ts b/packages/medusa/src/api/utils/unless-path.ts index 6bdbea515d..59fe522e6e 100644 --- a/packages/medusa/src/api/utils/unless-path.ts +++ b/packages/medusa/src/api/utils/unless-path.ts @@ -1,6 +1,6 @@ import { NextFunction } from "express" import { MedusaRequest, MedusaResponse } from "../../types/routing" -import { MiddlewareFunction } from "../../types/middlewares" +import { MiddlewareFunction } from "@medusajs/framework" // Due to how our route loader works, where we load all middlewares before routes, ambiguous routes end up having all middlewares on different routes executed before the route handler is. // This function allows us to skip middlewares for particular routes, so we can temporarily solve this without completely breaking the route loader for everyone. diff --git a/packages/medusa/src/loaders/api.ts b/packages/medusa/src/loaders/api.ts index 21d3bcda16..a91310f22f 100644 --- a/packages/medusa/src/loaders/api.ts +++ b/packages/medusa/src/loaders/api.ts @@ -1,8 +1,7 @@ -import { ContainerRegistrationKeys } from "@medusajs/utils" import { Express } from "express" -import path from "path" +import { join } from "path" import qs from "qs" -import { RoutesLoader } from "./helpers/routing" +import { RoutesLoader } from "@medusajs/framework" import { MedusaContainer, PluginDetails } from "@medusajs/types" type Options = { @@ -24,9 +23,7 @@ export default async ({ app, container, plugins }: Options) => { next() }) - const configModule = container.resolve( - ContainerRegistrationKeys.CONFIG_MODULE - ) + const sourcePaths: string[] = [] /** * Always load plugin routes before the Medusa core routes, since it @@ -40,31 +37,27 @@ export default async ({ app, container, plugins }: Options) => { * route will never be resolved, because it will be handled by the * "/products/:id" route. */ - await Promise.all( - plugins.map(async (pluginDetails) => { - return new RoutesLoader({ - app: app, - configModule, - rootDir: path.join(pluginDetails.resolve, "api"), - }).load() - }) + sourcePaths.push( + ...plugins.map((pluginDetails) => { + return join(pluginDetails.resolve, "api") + }), + /** + * Register the Medusa CORE API routes using the file based routing. + */ + join(__dirname, "../api") ) // TODO: Figure out why this is causing issues with test when placed inside ./api.ts // Adding this here temporarily // Test: (packages/medusa/src/api/routes/admin/currencies/update-currency.ts) try { - /** - * Register the Medusa CORE API routes using the file based routing. - */ await new RoutesLoader({ app: app, - rootDir: path.join(__dirname, "../api"), - configModule, + sourceDir: sourcePaths, }).load() } catch (err) { throw Error( - "An error occurred while registering Medusa Core API Routes. See error in logs for more details.", + "An error occurred while registering API Routes. See error in logs for more details.", { cause: err } ) } diff --git a/packages/medusa/src/loaders/feature-flags/analytics.ts b/packages/medusa/src/loaders/feature-flags/analytics.ts index 54363f97c2..f5a1c29ee8 100644 --- a/packages/medusa/src/loaders/feature-flags/analytics.ts +++ b/packages/medusa/src/loaders/feature-flags/analytics.ts @@ -1,4 +1,4 @@ -import { FlagSettings } from "../../types/feature-flags" +import { FlagSettings } from "@medusajs/framework" const AnalyticsFeatureFlag: FlagSettings = { key: "analytics", diff --git a/packages/medusa/src/loaders/feature-flags/order-editing.ts b/packages/medusa/src/loaders/feature-flags/order-editing.ts index 32c52d7497..343f6e83a2 100644 --- a/packages/medusa/src/loaders/feature-flags/order-editing.ts +++ b/packages/medusa/src/loaders/feature-flags/order-editing.ts @@ -1,4 +1,4 @@ -import { FlagSettings } from "../../types/feature-flags" +import { FlagSettings } from "@medusajs/framework" const OrderEditingFeatureFlag: FlagSettings = { key: "order_editing", diff --git a/packages/medusa/src/loaders/feature-flags/product-categories.ts b/packages/medusa/src/loaders/feature-flags/product-categories.ts index e42a5efaec..7f0fd0015b 100644 --- a/packages/medusa/src/loaders/feature-flags/product-categories.ts +++ b/packages/medusa/src/loaders/feature-flags/product-categories.ts @@ -1,4 +1,4 @@ -import { FlagSettings } from "../../types/feature-flags" +import { FlagSettings } from "@medusajs/framework" const ProductCategoryFeatureFlag: FlagSettings = { key: "product_categories", diff --git a/packages/medusa/src/loaders/feature-flags/publishable-api-keys.ts b/packages/medusa/src/loaders/feature-flags/publishable-api-keys.ts index 6a167c152f..5ccedfd3a7 100644 --- a/packages/medusa/src/loaders/feature-flags/publishable-api-keys.ts +++ b/packages/medusa/src/loaders/feature-flags/publishable-api-keys.ts @@ -1,4 +1,4 @@ -import { FlagSettings } from "../../types/feature-flags" +import { FlagSettings } from "@medusajs/framework" const PublishableAPIKeysFeatureFlag: FlagSettings = { key: "publishable_api_keys", diff --git a/packages/medusa/src/loaders/feature-flags/sales-channels.ts b/packages/medusa/src/loaders/feature-flags/sales-channels.ts index 99f6a9c10a..f5671a35a8 100644 --- a/packages/medusa/src/loaders/feature-flags/sales-channels.ts +++ b/packages/medusa/src/loaders/feature-flags/sales-channels.ts @@ -1,4 +1,4 @@ -import { FlagSettings } from "../../types/feature-flags" +import { FlagSettings } from "@medusajs/framework" const SalesChannelFeatureFlag: FlagSettings = { key: "sales_channels", diff --git a/packages/medusa/src/loaders/feature-flags/tax-inclusive-pricing.ts b/packages/medusa/src/loaders/feature-flags/tax-inclusive-pricing.ts index 512f3f5cf1..6655b21283 100644 --- a/packages/medusa/src/loaders/feature-flags/tax-inclusive-pricing.ts +++ b/packages/medusa/src/loaders/feature-flags/tax-inclusive-pricing.ts @@ -1,4 +1,4 @@ -import { FlagSettings } from "../../types/feature-flags" +import { FlagSettings } from "@medusajs/framework" const TaxInclusivePricingFeatureFlag: FlagSettings = { key: "tax_inclusive_pricing", diff --git a/packages/medusa/src/loaders/helpers/routing/types.ts b/packages/medusa/src/loaders/helpers/routing/types.ts deleted file mode 100644 index ff2e308d49..0000000000 --- a/packages/medusa/src/loaders/helpers/routing/types.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { - MedusaNextFunction, - MedusaRequest, - MedusaRequestHandler, - MedusaResponse, -} from "../../../types/routing" - -/** - * List of all the supported HTTP methods - */ -export const HTTP_METHODS = [ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE", - "OPTIONS", - "HEAD", -] as const - -export type RouteVerb = (typeof HTTP_METHODS)[number] -export type MiddlewareVerb = "USE" | "ALL" | RouteVerb - -type SyncRouteHandler = (req: MedusaRequest, res: MedusaResponse) => void - -export type AsyncRouteHandler = ( - req: MedusaRequest, - res: MedusaResponse -) => Promise - -type RouteHandler = SyncRouteHandler | AsyncRouteHandler - -export type RouteImplementation = { - method?: RouteVerb - handler: RouteHandler -} - -export type RouteConfig = { - optedOutOfAuth?: boolean - routeType?: "admin" | "store" | "auth" - shouldAppendAdminCors?: boolean - shouldAppendStoreCors?: boolean - shouldAppendAuthCors?: boolean - routes?: RouteImplementation[] -} - -export type MiddlewareFunction = - | MedusaRequestHandler - | ((...args: any[]) => any) - -export type MedusaErrorHandlerFunction = ( - error: any, - req: MedusaRequest, - res: MedusaResponse, - next: MedusaNextFunction -) => Promise | void - -export type ParserConfigArgs = { - sizeLimit?: string | number | undefined - preserveRawBody?: boolean -} - -export type ParserConfig = false | ParserConfigArgs - -export type MiddlewareRoute = { - method?: MiddlewareVerb | MiddlewareVerb[] - matcher: string | RegExp - bodyParser?: ParserConfig - middlewares?: MiddlewareFunction[] -} - -export type MiddlewaresConfig = { - errorHandler?: false | MedusaErrorHandlerFunction - routes?: MiddlewareRoute[] -} - -export type RouteDescriptor = { - absolutePath: string - relativePath: string - route: string - priority: number - config?: RouteConfig -} - -export type GlobalMiddlewareDescriptor = { - config?: MiddlewaresConfig -} diff --git a/packages/medusa/src/types/feature-flags.ts b/packages/medusa/src/types/feature-flags.ts deleted file mode 100644 index 0131f3b7ba..0000000000 --- a/packages/medusa/src/types/feature-flags.ts +++ /dev/null @@ -1,32 +0,0 @@ -export interface IFlagRouter { - isFeatureEnabled: (key: string) => boolean - listFlags: () => FeatureFlagsResponse -} - -/** - * @schema FeatureFlagsResponse - * type: array - * items: - * type: object - * required: - * - key - * - value - * properties: - * key: - * description: The key of the feature flag. - * type: string - * value: - * description: The value of the feature flag. - * type: boolean - */ -export type FeatureFlagsResponse = { - key: string - value: boolean | Record -}[] - -export type FlagSettings = { - key: string - description: string - env_key: string - default_val: boolean -} diff --git a/packages/medusa/src/types/middlewares.ts b/packages/medusa/src/types/middlewares.ts index d90ca38974..4ef5ed4b5d 100644 --- a/packages/medusa/src/types/middlewares.ts +++ b/packages/medusa/src/types/middlewares.ts @@ -2,4 +2,4 @@ export type { MiddlewareFunction, MiddlewareRoute, MiddlewaresConfig, -} from "../loaders/helpers/routing/types" +} from "@medusajs/framework" diff --git a/packages/medusa/src/types/routing.ts b/packages/medusa/src/types/routing.ts index 0f604ea156..5c6420d8a2 100644 --- a/packages/medusa/src/types/routing.ts +++ b/packages/medusa/src/types/routing.ts @@ -9,6 +9,7 @@ import { import * as core from "express-serve-static-core" import { FindConfig } from "./common" +// TODO this will be reqorked and move to the framework at a later point unless decided otherwise export interface MedusaRequest extends Request { validatedBody: Body diff --git a/packages/medusa/src/utils/define-middlewares.ts b/packages/medusa/src/utils/define-middlewares.ts index 7b2a4da2a8..f966b11be5 100644 --- a/packages/medusa/src/utils/define-middlewares.ts +++ b/packages/medusa/src/utils/define-middlewares.ts @@ -1,67 +1,8 @@ -import { ZodObject } from "zod" -import { - MedusaRequest, - MedusaResponse, - MedusaNextFunction, - MedusaRequestHandler, -} from "../types/routing" -import { - ParserConfig, - MiddlewareVerb, - MiddlewaresConfig, -} from "../loaders/helpers/routing/types" +import { defineMiddlewares as originalDefineMiddlewares } from "@medusajs/framework" /** * A helper function to configure the routes by defining custom middleware, * bodyparser config and validators to be merged with the pre-existing * route validators. */ -export function defineMiddlewares< - Route extends { - method?: MiddlewareVerb | MiddlewareVerb[] - matcher: string | RegExp - bodyParser?: ParserConfig - extendedValidators?: { - body?: ZodObject - queryParams?: ZodObject - } - // eslint-disable-next-line space-before-function-paren - middlewares?: (( - req: Req, - res: MedusaResponse, - next: MedusaNextFunction - ) => any)[] - } ->( - config: - | Route[] - | { routes?: Route[]; errorHandler?: MiddlewaresConfig["errorHandler"] } -): MiddlewaresConfig { - const routes = Array.isArray(config) ? config : config.routes || [] - const errorHandler = Array.isArray(config) ? undefined : config.errorHandler - - return { - errorHandler, - routes: routes.map((route) => { - const { middlewares, extendedValidators, ...rest } = route - const customMiddleware: MedusaRequestHandler[] = [] - - /** - * Define a custom validator when "extendedValidators.body" or - * "extendedValidators.queryParams" validation schema is - * provided. - */ - if (extendedValidators?.body || extendedValidators?.queryParams) { - customMiddleware.push((req, _, next) => { - req.extendedValidators = extendedValidators - next() - }) - } - - return { - ...rest, - middlewares: customMiddleware.concat(middlewares || []), - } - }), - } -} +export const defineMiddlewares = originalDefineMiddlewares diff --git a/packages/medusa/src/utils/exception-formatter.ts b/packages/medusa/src/utils/exception-formatter.ts index afcd9481a4..d4907b22d9 100644 --- a/packages/medusa/src/utils/exception-formatter.ts +++ b/packages/medusa/src/utils/exception-formatter.ts @@ -1,56 +1,3 @@ -import { MedusaError } from "@medusajs/utils" +import { formatException as originalFormatException } from "@medusajs/framework" -export enum PostgresError { - DUPLICATE_ERROR = "23505", - FOREIGN_KEY_ERROR = "23503", - SERIALIZATION_FAILURE = "40001", - NULL_VIOLATION = "23502", -} - -export const formatException = (err): MedusaError => { - switch (err.code) { - case PostgresError.DUPLICATE_ERROR: - return new MedusaError( - MedusaError.Types.DUPLICATE_ERROR, - `${err.table.charAt(0).toUpperCase()}${err.table.slice( - 1 - )} with ${err.detail.slice(4).replace(/[()=]/g, (s) => { - return s === "=" ? " " : "" - })}` - ) - case PostgresError.FOREIGN_KEY_ERROR: { - const matches = - /Key \(([\w-\d]+)\)=\(([\w-\d]+)\) is not present in table "(\w+)"/g.exec( - err.detail - ) - - if (matches?.length !== 4) { - return new MedusaError( - MedusaError.Types.NOT_FOUND, - JSON.stringify(matches) - ) - } - - return new MedusaError( - MedusaError.Types.NOT_FOUND, - `${matches[3]?.charAt(0).toUpperCase()}${matches[3]?.slice(1)} with ${ - matches[1] - } ${matches[2]} does not exist.` - ) - } - case PostgresError.SERIALIZATION_FAILURE: { - return new MedusaError( - MedusaError.Types.CONFLICT, - err?.detail ?? err?.message - ) - } - case PostgresError.NULL_VIOLATION: { - return new MedusaError( - MedusaError.Types.INVALID_DATA, - `Can't insert null value in field ${err?.column} on insert in table ${err?.table}` - ) - } - default: - return err - } -} +export const formatException = originalFormatException diff --git a/packages/medusa/src/utils/middlewares/authenticate-middleware.ts b/packages/medusa/src/utils/middlewares/authenticate-middleware.ts index cd0d51fbe3..1e9fdac9a1 100644 --- a/packages/medusa/src/utils/middlewares/authenticate-middleware.ts +++ b/packages/medusa/src/utils/middlewares/authenticate-middleware.ts @@ -1,203 +1,3 @@ -import { ApiKeyDTO, ConfigModule, IApiKeyModuleService } from "@medusajs/types" -import { - ContainerRegistrationKeys, - ModuleRegistrationName, -} from "@medusajs/utils" -import { NextFunction, RequestHandler } from "express" -import jwt, { JwtPayload } from "jsonwebtoken" -import { - AuthContext, - AuthenticatedMedusaRequest, - MedusaRequest, - MedusaResponse, -} from "../../types/routing" +import { authenticate as originalAuthenticate } from "@medusajs/framework" -const SESSION_AUTH = "session" -const BEARER_AUTH = "bearer" -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 - -type MedusaSession = { - auth_context: AuthContext -} - -export const authenticate = ( - actorType: string | string[], - authType: AuthType | AuthType[], - options: { allowUnauthenticated?: boolean; allowUnregistered?: boolean } = {} -): RequestHandler => { - return async ( - req: MedusaRequest, - res: MedusaResponse, - next: NextFunction - ): Promise => { - const authTypes = Array.isArray(authType) ? authType : [authType] - const actorTypes = Array.isArray(actorType) ? actorType : [actorType] - const req_ = req as AuthenticatedMedusaRequest - - // We only allow authenticating using a secret API key on the admin - const isExclusivelyUser = - actorTypes.length === 1 && actorTypes[0] === ADMIN_ACTOR_TYPE - - if (authTypes.includes(API_KEY_AUTH) && isExclusivelyUser) { - const apiKey = await getApiKeyInfo(req) - if (apiKey) { - req_.auth_context = { - actor_id: apiKey.id, - actor_type: "api-key", - auth_identity_id: "", - app_metadata: {}, - } - - return next() - } - } - - // We try to extract the auth context either from the session or from a JWT token - let authContext: AuthContext | null = getAuthContextFromSession( - req.session, - authTypes, - actorTypes - ) - - if (!authContext) { - const { http } = req.scope.resolve( - ContainerRegistrationKeys.CONFIG_MODULE - ).projectConfig - - authContext = getAuthContextFromJwtToken( - req.headers.authorization, - http.jwtSecret!, - authTypes, - actorTypes - ) - } - - // If the entity is authenticated, and it is a registered actor we can continue - if (authContext?.actor_id) { - req_.auth_context = authContext - return next() - } - - // If the entity is authenticated, but there is no registered actor yet, we can continue (eg. in the case of a user invite) if allow unregistered is set - if (authContext?.auth_identity_id && options.allowUnregistered) { - req_.auth_context = authContext - return next() - } - - // If we allow unauthenticated requests (i.e public endpoints), just continue - if (options.allowUnauthenticated) { - return next() - } - - res.status(401).json({ message: "Unauthorized" }) - } -} - -const getApiKeyInfo = async (req: MedusaRequest): Promise => { - const authHeader = req.headers.authorization - if (!authHeader) { - return null - } - - const [tokenType, token] = authHeader.split(" ") - if (tokenType.toLowerCase() !== "basic" || !token) { - return null - } - - // The token could have been base64 encoded, we want to decode it first. - let normalizedToken = token - if (!token.startsWith("sk_")) { - normalizedToken = Buffer.from(token, "base64").toString("utf-8") - } - - // Basic auth is defined as a username:password set, and since the token is set to the username we need to trim the colon - if (normalizedToken.endsWith(":")) { - normalizedToken = normalizedToken.slice(0, -1) - } - - // Secret tokens start with 'sk_', and if it doesn't it could be a user JWT or a malformed token - if (!normalizedToken.startsWith("sk_")) { - return null - } - - const apiKeyModule = req.scope.resolve( - ModuleRegistrationName.API_KEY - ) as IApiKeyModuleService - try { - const apiKey = await apiKeyModule.authenticate(normalizedToken) - if (!apiKey) { - return null - } - - return apiKey - } catch (error) { - console.error(error) - return null - } -} - -const getAuthContextFromSession = ( - session: Partial = {}, - authTypes: AuthType[], - actorTypes: string[] -): AuthContext | null => { - if (!authTypes.includes(SESSION_AUTH)) { - return null - } - - if ( - session.auth_context && - (actorTypes.includes("*") || - actorTypes.includes(session.auth_context.actor_type)) - ) { - return session.auth_context - } - - return null -} - -const getAuthContextFromJwtToken = ( - authHeader: string | undefined, - jwtSecret: string, - authTypes: AuthType[], - actorTypes: string[] -): AuthContext | null => { - if (!authTypes.includes(BEARER_AUTH)) { - return null - } - - if (!authHeader) { - return null - } - - const re = /(\S+)\s+(\S+)/ - const matches = authHeader.match(re) - - // TODO: figure out how to obtain token (and store correct data in token) - if (matches) { - const tokenType = matches[1] - const token = matches[2] - if (tokenType.toLowerCase() === BEARER_AUTH) { - // get config jwt secret - // verify token and set authUser - try { - const verified = jwt.verify(token, jwtSecret) as JwtPayload - if ( - actorTypes.includes("*") || - actorTypes.includes(verified.actor_type) - ) { - return verified as AuthContext - } - } catch (err) { - return null - } - } - } - - return null -} +export const authenticate = originalAuthenticate diff --git a/packages/medusa/src/utils/middlewares/error-handler.ts b/packages/medusa/src/utils/middlewares/error-handler.ts index 9e83f5f5b5..9ee979e41d 100644 --- a/packages/medusa/src/utils/middlewares/error-handler.ts +++ b/packages/medusa/src/utils/middlewares/error-handler.ts @@ -1,99 +1,3 @@ -import { NextFunction, Request, Response } from "express" +import { errorHandler as originalErrorHandler } from "@medusajs/framework" -import { MedusaError } from "@medusajs/utils" -import { Logger } from "../../types/global" -import { formatException } from "../../utils" - -const QUERY_RUNNER_RELEASED = "QueryRunnerAlreadyReleasedError" -const TRANSACTION_STARTED = "TransactionAlreadyStartedError" -const TRANSACTION_NOT_STARTED = "TransactionNotStartedError" - -const API_ERROR = "api_error" -const INVALID_REQUEST_ERROR = "invalid_request_error" -const INVALID_STATE_ERROR = "invalid_state_error" - -export default () => { - return ( - err: MedusaError, - req: Request, - res: Response, - next: NextFunction - ) => { - const logger: Logger = req.scope.resolve("logger") - - err = formatException(err) - - logger.error(err) - - const errorType = err.type || err.name - - const errObj = { - code: err.code, - type: err.type, - message: err.message, - } - - let statusCode = 500 - switch (errorType) { - case QUERY_RUNNER_RELEASED: - case TRANSACTION_STARTED: - case TRANSACTION_NOT_STARTED: - case MedusaError.Types.CONFLICT: - statusCode = 409 - errObj.code = INVALID_STATE_ERROR - errObj.message = - "The request conflicted with another request. You may retry the request with the provided Idempotency-Key." - break - case MedusaError.Types.UNAUTHORIZED: - statusCode = 401 - break - case MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR: - statusCode = 422 - break - case MedusaError.Types.DUPLICATE_ERROR: - statusCode = 422 - errObj.code = INVALID_REQUEST_ERROR - break - case MedusaError.Types.NOT_ALLOWED: - case MedusaError.Types.INVALID_DATA: - statusCode = 400 - break - case MedusaError.Types.NOT_FOUND: - statusCode = 404 - break - case MedusaError.Types.DB_ERROR: - statusCode = 500 - errObj.code = API_ERROR - break - case MedusaError.Types.UNEXPECTED_STATE: - case MedusaError.Types.INVALID_ARGUMENT: - break - default: - errObj.code = "unknown_error" - errObj.message = "An unknown error occurred." - errObj.type = "unknown_error" - break - } - - res.status(statusCode).json(errObj) - } -} - -/** - * @schema Error - * title: "Response Error" - * type: object - * properties: - * code: - * type: string - * description: A slug code to indicate the type of the error. - * enum: [invalid_state_error, invalid_request_error, api_error, unknown_error] - * message: - * type: string - * description: Description of the error that occurred. - * example: "first_name must be a string" - * type: - * type: string - * description: A slug indicating the type of the error. - * enum: [QueryRunnerAlreadyReleasedError, TransactionAlreadyStartedError, TransactionNotStartedError, conflict, unauthorized, payment_authorization_error, duplicate_error, not_allowed, invalid_data, not_found, database_error, unexpected_state, invalid_argument, unknown_error] - */ +export const errorHandler = originalErrorHandler diff --git a/packages/medusa/src/utils/middlewares/index.ts b/packages/medusa/src/utils/middlewares/index.ts index 51736c60d9..daa89b95ef 100644 --- a/packages/medusa/src/utils/middlewares/index.ts +++ b/packages/medusa/src/utils/middlewares/index.ts @@ -1,2 +1,2 @@ export { authenticate } from "./authenticate-middleware" -export { default as errorHandler } from "./error-handler" +export { errorHandler } from "./error-handler" diff --git a/yarn.lock b/yarn.lock index a0eb4868b6..59d116cf5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4616,19 +4616,24 @@ __metadata: "@medusajs/utils": ^1.11.9 "@medusajs/workflows-sdk": ^0.1.6 "@types/express": ^4.17.17 + "@types/jsonwebtoken": ^8.5.9 awilix: ^8.0.0 cookie-parser: ^1.4.6 + cors: ^2.8.5 cross-env: ^7.0.3 express: ^4.18.2 express-session: ^1.17.3 ioredis: ^5.2.5 ioredis-mock: 8.4.0 + jsonwebtoken: ^9.0.2 medusa-telemetry: ^0.0.17 morgan: ^1.9.1 rimraf: ^3.0.2 + supertest: ^4.0.2 tsc-alias: ^1.8.6 typescript: ^5.1.6 vite: ^5.2.11 + zod: 3.22.4 languageName: unknown linkType: soft