Files
speckle-server/packages/server/modules/core/rest/ratelimiter.ts
T

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
}