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";
|
import type { Session, User } from "better-auth";
|
||||||
|
|
||||||
// Minimal app to test the limiter in isolation
|
|
||||||
function createTestApp() {
|
function createTestApp() {
|
||||||
const app = express();
|
const app = express();
|
||||||
app.set("trust proxy", 1);
|
app.set("trust proxy", 1);
|
||||||
|
|
@ -20,29 +19,51 @@ describe("authLimiter", () => {
|
||||||
let app: ReturnType<typeof createTestApp>;
|
let app: ReturnType<typeof createTestApp>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Fresh app = fresh in-memory store = counters reset between tests
|
|
||||||
app = createTestApp();
|
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");
|
const res = await request(app).post("/api/auth/sign-in");
|
||||||
expect(res.status).toBe(200);
|
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;
|
const limit = 20;
|
||||||
for (let i = 0; i < limit; i++) {
|
for (let i = 0; i < limit; i++) {
|
||||||
await request(app).post("/api/auth/sign-in");
|
await request(app).post("/api/auth/sign-in");
|
||||||
}
|
}
|
||||||
const res = 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.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");
|
const res = await request(app).post("/api/auth/sign-in");
|
||||||
expect(res.headers).toHaveProperty("ratelimit");
|
expect(res.headers).toHaveProperty("ratelimit");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,15 @@ export const authLimiter = rateLimit({
|
||||||
limit: 20,
|
limit: 20,
|
||||||
standardHeaders: "draft-8",
|
standardHeaders: "draft-8",
|
||||||
legacyHeaders: false,
|
legacyHeaders: false,
|
||||||
|
skip: (req) => {
|
||||||
|
const path = req.path;
|
||||||
|
return (
|
||||||
|
path.includes("/get-session") ||
|
||||||
|
path.includes("/sign-out") ||
|
||||||
|
path.startsWith("/callback/") ||
|
||||||
|
path.includes("/callback/")
|
||||||
|
);
|
||||||
|
},
|
||||||
message: {
|
message: {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Too many requests, please try again later.",
|
error: "Too many requests, please try again later.",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue