From 3f9a8ec7e8a3f81e510b6b162ad52321746fde7a Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 13 Aug 2025 21:03:52 -0700 Subject: user agent rate limiting --- lib/app.ts | 52 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/lib/app.ts b/lib/app.ts index 44c50c1e..95eb1ae5 100644 --- a/lib/app.ts +++ b/lib/app.ts @@ -1,12 +1,12 @@ import config from "$config"; -import fastify from "fastify"; +import cors from "@fastify/cors"; +import fastifyProxy from "@fastify/http-proxy"; import fastifyMultipart from "@fastify/multipart"; -import fastifyStatic from "@fastify/static"; import fastifyPassport from "@fastify/passport"; -import fastifySecureSession from "@fastify/secure-session"; -import fastifyProxy from "@fastify/http-proxy"; import fastifyRateLimit from "@fastify/rate-limit"; -import cors from "@fastify/cors"; +import fastifySecureSession from "@fastify/secure-session"; +import fastifyStatic from "@fastify/static"; +import fastify from "fastify"; import pino from "pino"; import * as path from "path"; @@ -29,8 +29,22 @@ const resLogger = pino(pino.destination("res")); app.addHook("preHandler", async (req) => { const url = req.raw.url; - const ignore = ["/login","/ws","/me","/confirm","/public","/rates","/challenge", "/rate", "/lnurlp", "/subscriptions", "/accounts", "/contacts", "/users"]; - if (ignore.some(path => url.startsWith(path))) return; + const ignore = [ + "/login", + "/ws", + "/me", + "/confirm", + "/public", + "/rates", + "/challenge", + "/rate", + "/lnurlp", + "/subscriptions", + "/accounts", + "/contacts", + "/users", + ]; + if (ignore.some((path) => url.startsWith(path))) return; reqLogger.info({ method: req.method, @@ -39,7 +53,7 @@ app.addHook("preHandler", async (req) => { query: req.query, body: req.body, user: (req.user as any)?.username, - id: req.id + id: req.id, }); }); @@ -65,7 +79,11 @@ app.register(fastifyRateLimit, { allowList: (req) => req.raw.url?.includes("public"), max: 2000, timeWindow: 2000, - keyGenerator: (req) => req.headers["cf-connecting-ip"] as string, + keyGenerator: (req) => { + const ip = (req.headers["cf-connecting-ip"] as string) || req.ip; + const ua = req.headers["user-agent"] || "unknown-ua"; + return req.headers["rate-limit-by"] === "ua" ? ua : ip; + }, errorResponseBuilder: () => { return { statusCode: 429, @@ -75,6 +93,22 @@ app.register(fastifyRateLimit, { }, }); +app.register(fastifyRateLimit, { + allowList: (req) => { + const url = req.raw.url || ""; + const matches = url.includes("/login") || url.includes("/send"); + return !matches; + }, + max: 10, + timeWindow: 10000, + keyGenerator: (req) => (req.headers["user-agent"] as string) || "unknown-ua", + errorResponseBuilder: () => ({ + statusCode: 429, + error: "Too Many Requests", + message: "Rate limit exceeded", + }), +}); + app.register(fastifyProxy, { upstream: "http://localhost:3120", prefix: "/ws", -- cgit v1.2.3