chore(framework): Move and improve routes loader (#8392)
* chore(framework): Move and improve routes loader * cleanup * fix(framework): import
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { defineMiddlewares } from "../../../../../utils/define-middlewares"
|
||||
import { defineMiddlewares } from "../../utils/define-middlewares"
|
||||
|
||||
export default defineMiddlewares({
|
||||
errorHandler: (err, _req, res, _next) => {
|
||||
+2
-2
@@ -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,
|
||||
+8
-23
@@ -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 {
|
||||
+1
-3
@@ -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)
|
||||
@@ -1 +1,5 @@
|
||||
export * from "./express-loader"
|
||||
export * from "./router"
|
||||
export * from "./types"
|
||||
export * from "./middlewares"
|
||||
export * from "./utils/define-middlewares"
|
||||
|
||||
@@ -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<void> => {
|
||||
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<ConfigModule>(
|
||||
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<ApiKeyDTO | null> => {
|
||||
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<MedusaSession> = {},
|
||||
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
|
||||
}
|
||||
@@ -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]
|
||||
*/
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./authenticate-middleware"
|
||||
export * from "./error-handler"
|
||||
export * from "./exception-formatter"
|
||||
+200
-155
@@ -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<string, RouteDescriptor>()
|
||||
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<string, RouteDescriptor>()
|
||||
|
||||
/**
|
||||
* 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<void> {
|
||||
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<void> {
|
||||
protected async createRoutesMap(): Promise<void> {
|
||||
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<void> {
|
||||
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<typeof wrapHandler>[0])
|
||||
: route.handler
|
||||
|
||||
this.router[route.method!.toLowerCase()](descriptor.route, handler)
|
||||
this.#router[route.method!.toLowerCase()](descriptor.route, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async registerMiddlewares(): Promise<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<Entity> {
|
||||
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<void>
|
||||
|
||||
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> | 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<Body = unknown>
|
||||
extends Request<core.ParamsDictionary, any, Body> {
|
||||
validatedBody: Body
|
||||
validatedQuery: RequestQueryFields & Record<string, unknown>
|
||||
/**
|
||||
* 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<unknown>
|
||||
/**
|
||||
* An object containing the select, relation to be used with medusa internal services
|
||||
*/
|
||||
retrieveConfig: FindConfig<unknown>
|
||||
/**
|
||||
* An object containing fields and variables to be used with the remoteQuery
|
||||
*/
|
||||
remoteQueryConfig: {
|
||||
fields: string[]
|
||||
pagination: { order?: Record<string, string>; skip?: number; take?: number }
|
||||
}
|
||||
/**
|
||||
* An object containing the fields that are filterable e.g `{ id: Any<String> }`
|
||||
*/
|
||||
filterableFields: Record<string, unknown>
|
||||
includes?: Record<string, boolean>
|
||||
/**
|
||||
* 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<string, any>
|
||||
/**
|
||||
* Custom validators for the request body and query params that will be
|
||||
* merged with the original validator of the route.
|
||||
*/
|
||||
extendedValidators?: {
|
||||
body?: ZodObject<any, any>
|
||||
queryParams?: ZodObject<any, any>
|
||||
}
|
||||
}
|
||||
|
||||
export interface AuthContext {
|
||||
actor_id: string
|
||||
actor_type: string
|
||||
auth_identity_id: string
|
||||
app_metadata: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface AuthenticatedMedusaRequest<Body = never>
|
||||
extends MedusaRequest<Body> {
|
||||
auth_context: AuthContext
|
||||
}
|
||||
|
||||
export type MedusaResponse<Body = unknown> = Response<Body>
|
||||
|
||||
export type MedusaNextFunction = NextFunction
|
||||
|
||||
export type MedusaRequestHandler<Body = unknown, Res = unknown> = (
|
||||
req: MedusaRequest<Body>,
|
||||
res: MedusaResponse<Res>,
|
||||
next: MedusaNextFunction
|
||||
) => Promise<void> | void
|
||||
@@ -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<any, any>
|
||||
queryParams?: ZodObject<any, any>
|
||||
}
|
||||
// eslint-disable-next-line space-before-function-paren
|
||||
middlewares?: (<Req extends MedusaRequest>(
|
||||
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 || []),
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { MiddlewareRoute } from "@medusajs/framework"
|
||||
import {
|
||||
AdminCreateShippingOption,
|
||||
AdminCreateShippingOptionRule,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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[] = [
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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[] = [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MiddlewareRoute } from "../../types/middlewares"
|
||||
import { MiddlewareRoute } from "@medusajs/framework"
|
||||
|
||||
export const hooksRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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[] = [
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FlagSettings } from "../../types/feature-flags"
|
||||
import { FlagSettings } from "@medusajs/framework"
|
||||
|
||||
const AnalyticsFeatureFlag: FlagSettings = {
|
||||
key: "analytics",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FlagSettings } from "../../types/feature-flags"
|
||||
import { FlagSettings } from "@medusajs/framework"
|
||||
|
||||
const OrderEditingFeatureFlag: FlagSettings = {
|
||||
key: "order_editing",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FlagSettings } from "../../types/feature-flags"
|
||||
import { FlagSettings } from "@medusajs/framework"
|
||||
|
||||
const ProductCategoryFeatureFlag: FlagSettings = {
|
||||
key: "product_categories",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FlagSettings } from "../../types/feature-flags"
|
||||
import { FlagSettings } from "@medusajs/framework"
|
||||
|
||||
const PublishableAPIKeysFeatureFlag: FlagSettings = {
|
||||
key: "publishable_api_keys",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FlagSettings } from "../../types/feature-flags"
|
||||
import { FlagSettings } from "@medusajs/framework"
|
||||
|
||||
const SalesChannelFeatureFlag: FlagSettings = {
|
||||
key: "sales_channels",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FlagSettings } from "../../types/feature-flags"
|
||||
import { FlagSettings } from "@medusajs/framework"
|
||||
|
||||
const TaxInclusivePricingFeatureFlag: FlagSettings = {
|
||||
key: "tax_inclusive_pricing",
|
||||
|
||||
@@ -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<void>
|
||||
|
||||
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> | 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
|
||||
}
|
||||
@@ -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<string, boolean>
|
||||
}[]
|
||||
|
||||
export type FlagSettings = {
|
||||
key: string
|
||||
description: string
|
||||
env_key: string
|
||||
default_val: boolean
|
||||
}
|
||||
@@ -2,4 +2,4 @@ export type {
|
||||
MiddlewareFunction,
|
||||
MiddlewareRoute,
|
||||
MiddlewaresConfig,
|
||||
} from "../loaders/helpers/routing/types"
|
||||
} from "@medusajs/framework"
|
||||
|
||||
@@ -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<Body = unknown>
|
||||
extends Request<core.ParamsDictionary, any, Body> {
|
||||
validatedBody: Body
|
||||
|
||||
@@ -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<any, any>
|
||||
queryParams?: ZodObject<any, any>
|
||||
}
|
||||
// eslint-disable-next-line space-before-function-paren
|
||||
middlewares?: (<Req extends MedusaRequest>(
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<void> => {
|
||||
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<ConfigModule>(
|
||||
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<ApiKeyDTO | null> => {
|
||||
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<MedusaSession> = {},
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user