UNPKG

nuxt-security

Version:

🛡️ Security Module for Nuxt based on HTTP Headers and Middleware

73 lines (72 loc) 3.07 kB
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; }