fix: separate rate limit request and response handling (#7939)

This commit is contained in:
Alex Yang
2026-02-12 10:40:51 +00:00
committed by GitHub
parent 61c7d07857
commit 86ca6d0b19
2 changed files with 41 additions and 12 deletions
+3 -2
View File
@@ -12,7 +12,7 @@ import { createRouter } from "better-call";
import type { UnionToIntersection } from "../types";
import { isAPIError } from "../utils/is-api-error";
import { originCheckMiddleware } from "./middlewares";
import { onRequestRateLimit } from "./rate-limiter";
import { onRequestRateLimit, onResponseRateLimit } from "./rate-limiter";
import {
accountInfo,
callbackOAuth,
@@ -302,7 +302,8 @@ export const router = <Option extends BetterAuthOptions>(
return currentRequest;
},
async onResponse(res) {
async onResponse(res, req) {
await onResponseRateLimit(req, ctx);
for (const plugin of ctx.options.plugins || []) {
if (plugin.onResponse) {
const response = await plugin.onResponse(res, ctx);
@@ -157,17 +157,14 @@ function getRateLimitStorage(
return createDatabaseStorageWrapper(ctx);
}
export async function onRequestRateLimit(req: Request, ctx: AuthContext) {
if (!ctx.rateLimit.enabled) {
return;
}
async function resolveRateLimitConfig(req: Request, ctx: AuthContext) {
const basePath = new URL(ctx.baseURL).pathname;
const path = normalizePathname(req.url, basePath);
let currentWindow = ctx.rateLimit.window;
let currentMax = ctx.rateLimit.max;
const ip = getIp(req, ctx.options);
if (!ip) {
return;
return null;
}
const key = createRateLimitKey(ip, path);
const specialRules = getDefaultSpecialRules();
@@ -214,11 +211,45 @@ export async function onRequestRateLimit(req: Request, ctx: AuthContext) {
}
if (resolved === false) {
return;
return null;
}
}
}
return { key, currentWindow, currentMax };
}
export async function onRequestRateLimit(req: Request, ctx: AuthContext) {
if (!ctx.rateLimit.enabled) {
return;
}
const config = await resolveRateLimitConfig(req, ctx);
if (!config) {
return;
}
const { key, currentWindow, currentMax } = config;
const storage = getRateLimitStorage(ctx, {
window: currentWindow,
});
const data = await storage.get(key);
if (data && shouldRateLimit(currentMax, currentWindow, data)) {
const retryAfter = getRetryAfter(data.lastRequest, currentWindow);
return rateLimitResponse(retryAfter);
}
}
export async function onResponseRateLimit(req: Request, ctx: AuthContext) {
if (!ctx.rateLimit.enabled) {
return;
}
const config = await resolveRateLimitConfig(req, ctx);
if (!config) {
return;
}
const { key, currentWindow } = config;
const storage = getRateLimitStorage(ctx, {
window: currentWindow,
});
@@ -234,10 +265,7 @@ export async function onRequestRateLimit(req: Request, ctx: AuthContext) {
} else {
const timeSinceLastRequest = now - data.lastRequest;
if (shouldRateLimit(currentMax, currentWindow, data)) {
const retryAfter = getRetryAfter(data.lastRequest, currentWindow);
return rateLimitResponse(retryAfter);
} else if (timeSinceLastRequest > currentWindow * 1000) {
if (timeSinceLastRequest > currentWindow * 1000) {
// Reset the count if the window has passed since the last request
await storage.set(
key,