59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
import type { Request, RequestHandler, Response } from 'express'
|
|
import {
|
|
getActionForPath,
|
|
throwIfRateLimitedFactory,
|
|
type RateLimitBreached,
|
|
type RateLimiterMapping
|
|
} from '@/modules/core/utils/ratelimiter'
|
|
import { getRequestPath } from '@/modules/core/helpers/server'
|
|
import { getTokenFromRequest } from '@/modules/shared/middleware'
|
|
import { getIpFromRequest } from '@/modules/shared/utils/ip'
|
|
|
|
export const createRateLimiterMiddleware = (params: {
|
|
rateLimiterEnabled: boolean
|
|
rateLimiterMapping?: RateLimiterMapping
|
|
}): RequestHandler => {
|
|
const { rateLimiterEnabled } = params
|
|
const throwIfRateLimited = throwIfRateLimitedFactory(params)
|
|
|
|
return async (req, res, next) => {
|
|
if (!rateLimiterEnabled) return next()
|
|
const path = getRequestPath(req) || ''
|
|
const action = getActionForPath(path, req.method)
|
|
const source = getSourceFromRequest(req)
|
|
const rateLimitResult = await throwIfRateLimited({
|
|
action,
|
|
source,
|
|
handleRateLimitBreachPriorToThrowing: addRateLimitHeadersToResponseFactory(res)
|
|
})
|
|
|
|
if (res.headersSent) return res
|
|
if (rateLimitResult)
|
|
res.setHeader('X-RateLimit-Remaining', rateLimitResult.remainingPoints)
|
|
return next()
|
|
}
|
|
}
|
|
|
|
export const addRateLimitHeadersToResponseFactory = (res: Response) => {
|
|
return (rateLimitBreached: RateLimitBreached) => {
|
|
if (res.headersSent) return res
|
|
res.setHeader('Retry-After', rateLimitBreached.msBeforeNext / 1000)
|
|
res.removeHeader('X-RateLimit-Remaining')
|
|
res.setHeader(
|
|
'X-RateLimit-Reset',
|
|
new Date(Date.now() + rateLimitBreached.msBeforeNext).toISOString()
|
|
)
|
|
res.setHeader('X-Speckle-Meditation', 'https://http.cat/429')
|
|
}
|
|
}
|
|
|
|
export const getSourceFromRequest = (req: Request): string => {
|
|
let source: string | null =
|
|
req?.context?.userId ||
|
|
getTokenFromRequest(req)?.substring(10) || // token ID
|
|
getIpFromRequest(req)
|
|
|
|
if (!source) source = 'unknown'
|
|
return source
|
|
}
|