mirror of
https://github.com/documenso/documenso.git
synced 2026-07-13 06:25:01 +10:00
89 lines
2.7 KiB
TypeScript
89 lines
2.7 KiB
TypeScript
import type { Context } from 'hono';
|
|
import type { MiddlewareHandler } from 'hono/types';
|
|
|
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
|
import { getIpAddress } from '../../universal/get-ip-address';
|
|
import type { createRateLimit, RateLimitCheckResult } from './rate-limit';
|
|
|
|
/**
|
|
* Set rate limit response headers on a Hono context.
|
|
*/
|
|
const setRateLimitHeaders = (c: Context, result: RateLimitCheckResult) => {
|
|
c.header('X-RateLimit-Limit', String(result.limit));
|
|
c.header('X-RateLimit-Remaining', String(result.remaining));
|
|
c.header('X-RateLimit-Reset', String(Math.ceil(result.reset.getTime() / 1000)));
|
|
};
|
|
|
|
/**
|
|
* Create a Hono middleware that applies rate limiting to a route.
|
|
*
|
|
* Uses IP address for identification. Optionally accepts an identifier
|
|
* function for per-user/per-entity limiting.
|
|
*/
|
|
export const createRateLimitMiddleware = (
|
|
limiter: ReturnType<typeof createRateLimit>,
|
|
options?: { identifierFn?: (c: Context) => string | undefined },
|
|
): MiddlewareHandler => {
|
|
return async (c, next) => {
|
|
let ip: string;
|
|
|
|
try {
|
|
ip = getIpAddress(c.req.raw);
|
|
} catch {
|
|
ip = 'unknown';
|
|
}
|
|
|
|
const identifier = options?.identifierFn?.(c);
|
|
|
|
const result = await limiter.check({ ip, identifier });
|
|
|
|
setRateLimitHeaders(c, result);
|
|
|
|
if (result.isLimited) {
|
|
c.header('Retry-After', String(Math.max(1, Math.ceil((result.reset.getTime() - Date.now()) / 1000))));
|
|
|
|
return c.json({ error: 'Too many requests, please try again later.' }, 429);
|
|
}
|
|
|
|
await next();
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Helper for inline rate limit checks in Hono auth routes.
|
|
*
|
|
* Returns a 429 Response with rate limit headers if limited, or `null` if allowed.
|
|
*/
|
|
export const rateLimitResponse = (c: Context, result: RateLimitCheckResult): Response | null => {
|
|
setRateLimitHeaders(c, result);
|
|
|
|
if (result.isLimited) {
|
|
c.header('Retry-After', String(Math.max(1, Math.ceil((result.reset.getTime() - Date.now()) / 1000))));
|
|
|
|
return c.json({ error: 'Too many requests, please try again later.' }, 429);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Helper for inline rate limit checks in tRPC routes.
|
|
*
|
|
* Throws an AppError with TOO_MANY_REQUESTS code if limited.
|
|
*/
|
|
export const assertRateLimit = (result: RateLimitCheckResult): void => {
|
|
if (result.isLimited) {
|
|
const retryAfter = String(Math.max(1, Math.ceil((result.reset.getTime() - Date.now()) / 1000)));
|
|
|
|
throw new AppError(AppErrorCode.TOO_MANY_REQUESTS, {
|
|
message: 'Too many requests, please try again later.',
|
|
headers: {
|
|
'X-RateLimit-Limit': String(result.limit),
|
|
'X-RateLimit-Remaining': String(result.remaining),
|
|
'X-RateLimit-Reset': String(Math.ceil(result.reset.getTime() / 1000)),
|
|
'Retry-After': retryAfter,
|
|
},
|
|
});
|
|
}
|
|
};
|