fix(api): skip rate limiting for non-sensitive auth endpoints
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m50s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m50s
The authLimiter was blocking legitimate users because Better Auth's client polls /get-session frequently (on mount, route changes, focus), and /sign-out was also getting blocked after repeated session polls. Skip rate limiting for: - /get-session — read-only, requires valid cookie, no attack surface - /sign-out — no attack value in blocking logout - /callback/* — OAuth callbacks from providers Brute force protection remains on /sign-in, /sign-up, and other sensitive endpoints.
This commit is contained in:
parent
c57fc5a98b
commit
59049002fc
2 changed files with 39 additions and 9 deletions
|
|
@ -5,7 +5,6 @@ import { authLimiter, gameLimiter, lobbyLimiter } from "./rateLimiters.js";
|
|||
|
||||
import type { Session, User } from "better-auth";
|
||||
|
||||
// Minimal app to test the limiter in isolation
|
||||
function createTestApp() {
|
||||
const app = express();
|
||||
app.set("trust proxy", 1);
|
||||
|
|
@ -20,29 +19,51 @@ describe("authLimiter", () => {
|
|||
let app: ReturnType<typeof createTestApp>;
|
||||
|
||||
beforeEach(() => {
|
||||
// Fresh app = fresh in-memory store = counters reset between tests
|
||||
app = createTestApp();
|
||||
});
|
||||
|
||||
it("allows requests under the limit through", async () => {
|
||||
it("allows requests under the limit through on sensitive endpoints", async () => {
|
||||
const res = await request(app).post("/api/auth/sign-in");
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("returns 429 after exceeding the limit", async () => {
|
||||
it("returns 429 after exceeding the limit on sensitive endpoints", async () => {
|
||||
const limit = 20;
|
||||
for (let i = 0; i < limit; i++) {
|
||||
await request(app).post("/api/auth/sign-in");
|
||||
}
|
||||
const res = await request(app).post("/api/auth/sign-in");
|
||||
expect(res.status).toBe(429);
|
||||
expect(res.body).toEqual({
|
||||
success: false,
|
||||
error: "Too many requests, please try again later.",
|
||||
});
|
||||
});
|
||||
|
||||
it("sets RateLimit headers on responses", async () => {
|
||||
it("does not rate limit /get-session", async () => {
|
||||
const limit = 20;
|
||||
for (let i = 0; i < limit + 5; i++) {
|
||||
await request(app).get("/api/auth/get-session");
|
||||
}
|
||||
const res = await request(app).get("/api/auth/get-session");
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("does not rate limit /sign-out", async () => {
|
||||
const limit = 20;
|
||||
for (let i = 0; i < limit + 5; i++) {
|
||||
await request(app).post("/api/auth/sign-out");
|
||||
}
|
||||
const res = await request(app).post("/api/auth/sign-out");
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("does not rate limit OAuth callbacks", async () => {
|
||||
const limit = 20;
|
||||
for (let i = 0; i < limit + 5; i++) {
|
||||
await request(app).get("/api/auth/callback/google");
|
||||
}
|
||||
const res = await request(app).get("/api/auth/callback/google");
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it("sets RateLimit headers on sensitive responses", async () => {
|
||||
const res = await request(app).post("/api/auth/sign-in");
|
||||
expect(res.headers).toHaveProperty("ratelimit");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@ export const authLimiter = rateLimit({
|
|||
limit: 20,
|
||||
standardHeaders: "draft-8",
|
||||
legacyHeaders: false,
|
||||
skip: (req) => {
|
||||
const path = req.path;
|
||||
return (
|
||||
path.includes("/get-session") ||
|
||||
path.includes("/sign-out") ||
|
||||
path.startsWith("/callback/") ||
|
||||
path.includes("/callback/")
|
||||
);
|
||||
},
|
||||
message: {
|
||||
success: false,
|
||||
error: "Too many requests, please try again later.",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue