nuxt-security
Version:
🛡️ Security Module for Nuxt based on HTTP Headers and Middleware
73 lines (72 loc) • 3.07 kB
JavaScript
import { defineEventHandler, createError, setResponseHeader, getRequestIP, getRequestHeader } from "h3";
import { useStorage } from "nitropack/runtime";
import { resolveSecurityRoute, resolveSecurityRules } from "../../nitro/context/index.js";
import { defaultSecurityConfig } from "../../../defaultConfig";
import defu from "defu";
const storage = useStorage("#rate-limiter-storage");
const defaultRateLimiter = defaultSecurityConfig("", true).rateLimiter;
export default defineEventHandler(async (event) => {
if (import.meta.prerender) {
return;
}
const rules = resolveSecurityRules(event);
const route = resolveSecurityRoute(event);
if (rules.enabled && rules.rateLimiter) {
const rateLimiter = defu(
rules.rateLimiter,
defaultRateLimiter
);
const ip = getIP(event, rateLimiter.ipHeader);
if (rateLimiter.whiteList && rateLimiter.whiteList.includes(ip)) {
return;
}
const url = ip + route;
let storageItem = await storage.getItem(url);
if (!storageItem) {
await setStorageItem(rateLimiter, url);
} else {
if (typeof storageItem !== "object") {
return;
}
const timeSinceFirstRateLimit = storageItem.date;
const timeForInterval = storageItem.date + Number(rateLimiter.interval);
if (Date.now() >= timeForInterval) {
await setStorageItem(rateLimiter, url);
storageItem = await storage.getItem(url);
}
const isLimited = timeSinceFirstRateLimit <= timeForInterval && storageItem.value === 0;
if (isLimited) {
const tooManyRequestsError = {
statusCode: 429,
statusMessage: "Too Many Requests"
};
if (rules.rateLimiter.headers) {
setResponseHeader(event, "x-ratelimit-remaining", 0);
setResponseHeader(event, "x-ratelimit-limit", rateLimiter.tokensPerInterval);
setResponseHeader(event, "x-ratelimit-reset", timeForInterval);
}
if (rateLimiter.throwError === false) {
return tooManyRequestsError;
}
throw createError(tooManyRequestsError);
}
const newItemDate = timeSinceFirstRateLimit > timeForInterval ? Date.now() : storageItem.date;
const newStorageItem = { value: storageItem.value - 1, date: newItemDate };
await storage.setItem(url, newStorageItem);
const currentItem = await storage.getItem(url);
if (currentItem && rateLimiter.headers) {
setResponseHeader(event, "x-ratelimit-remaining", currentItem.value);
setResponseHeader(event, "x-ratelimit-limit", rateLimiter.tokensPerInterval);
setResponseHeader(event, "x-ratelimit-reset", timeForInterval);
}
}
}
});
async function setStorageItem(rateLimiter, url) {
const rateLimitedObject = { value: rateLimiter.tokensPerInterval, date: Date.now() };
await storage.setItem(url, rateLimitedObject);
}
function getIP(event, customIpHeader) {
const ip = customIpHeader ? getRequestHeader(event, customIpHeader) || "" : getRequestIP(event, { xForwardedFor: true }) || "";
return ip;
}