webpods
Version:
Append-only log service with OAuth authentication
57 lines • 2.17 kB
JavaScript
/**
* Rate limiting middleware
*/
import { checkRateLimit, getRateLimitStatus } from "../domain/ratelimit.js";
import { getDb } from "../db.js";
import { createLogger } from "../logger.js";
import { getIpAddress } from "../utils.js";
const logger = createLogger("webpods:ratelimit");
/**
* Rate limiting middleware factory
*/
export function rateLimit(action) {
return async (req, res, next) => {
try {
const db = getDb();
// Use user ID if authenticated, otherwise IP address with prefix
const key = req.auth
? req.auth.user_id
: `ip:${getIpAddress(req)}`;
const result = await checkRateLimit(db, key, action);
if (!result.success) {
logger.error("Rate limit check failed", { error: result.error });
res.status(500).json({
error: {
code: "INTERNAL_ERROR",
message: "Rate limit check failed",
},
});
return;
}
// Get the limit for headers
const statusResult = await getRateLimitStatus(db, key, action);
const limit = statusResult.success ? statusResult.data.limit : 1000;
// Set rate limit headers
res.setHeader("X-RateLimit-Limit", limit.toString());
res.setHeader("X-RateLimit-Remaining", result.data.remaining.toString());
res.setHeader("X-RateLimit-Reset", result.data.resetAt.toISOString());
if (!result.data.allowed) {
logger.warn("Rate limit exceeded", { key, action });
res.status(429).json({
error: {
code: "RATE_LIMIT_EXCEEDED",
message: "Too many requests",
},
});
return;
}
next();
}
catch (error) {
logger.error("Rate limit middleware error", { error });
// On error, allow the request to proceed
next();
}
};
}
//# sourceMappingURL=ratelimit.js.map