From 0c3ea69cf2dfd68615963e5e879c743382712785 Mon Sep 17 00:00:00 2001 From: WcaleNieWolny Date: Sun, 12 Jan 2025 05:09:40 +0000 Subject: [PATCH] feat: fork ratelimit to log instead of blocking wih 429 --- .../functions/_backend/utils/cfRateLimiter.ts | 61 +++++++++++++++++++ supabase/functions/import_map.json | 1 + 2 files changed, 62 insertions(+) create mode 100644 supabase/functions/_backend/utils/cfRateLimiter.ts diff --git a/supabase/functions/_backend/utils/cfRateLimiter.ts b/supabase/functions/_backend/utils/cfRateLimiter.ts new file mode 100644 index 000000000..02419f265 --- /dev/null +++ b/supabase/functions/_backend/utils/cfRateLimiter.ts @@ -0,0 +1,61 @@ +import type { Context, Next } from '@hono/hono' +import { createMiddleware } from 'hono/factory' + +const RATE_LIMIT_CONTEXT_KEY = '.rateLimited' +// const STATUS_TOO_MANY_REQUESTS = 429 + +export interface RateLimitBinding { + limit: LimitFunc +} + +export interface LimitFunc { + (options: LimitOptions): Promise +} + +interface RateLimitResult { + success: boolean +} + +export interface LimitOptions { + key: string +} + +export interface RateLimitResponse { + key: string + success: boolean +} + +export interface RateLimitOptions { + continueOnRateLimit: boolean +} + +export interface RateLimitKeyFunc { + (c: Context): string +} + +export function rateLimit(rateLimitBinding: RateLimitBinding, keyFunc: RateLimitKeyFunc, rateLimit: string = '') { + return createMiddleware(async (c: Context, next: Next) => { + const key = keyFunc(c) + if (!key) { + console.warn('the provided keyFunc returned an empty rate limiting key: bypassing rate limits') + } + if (key) { + const { success } = await rateLimitBinding.limit({ key }) + c.set(RATE_LIMIT_CONTEXT_KEY, success) + + if (!success) { + // throw new HTTPException(STATUS_TOO_MANY_REQUESTS, { + // res: c.text("rate limited", { status: STATUS_TOO_MANY_REQUESTS }), + // }); + console.warn(`The rate limit has been reached for key: ${key} and rate limit binding: ${rateLimit}`) + } + } + + // Call the next handler/middleware in the stack on success + await next() + }) +} + +export function wasRateLimited(c: Context): boolean { + return c.get(RATE_LIMIT_CONTEXT_KEY) as boolean +} diff --git a/supabase/functions/import_map.json b/supabase/functions/import_map.json index af4542330..c04dc2729 100644 --- a/supabase/functions/import_map.json +++ b/supabase/functions/import_map.json @@ -9,6 +9,7 @@ "hono/request-id": "jsr:@hono/hono@^4.6.13/request-id", "hono/cors": "jsr:@hono/hono@^4.6.13/cors", "hono/tiny": "jsr:@hono/hono@^4.6.13/tiny", + "hono/factory": "jsr:@hono/hono@^4.6.13/factory", "hono/http-exception": "jsr:@hono/hono@^4.6.13/http-exception", "hono/adapter": "jsr:@hono/hono@^4.6.13/adapter", "hono/utils/buffer": "jsr:@hono/hono@^4.6.13/utils/buffer",