From 1bdb71d8d3425f8a6fd44960154934984f7565b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Raffray?= Date: Thu, 30 May 2024 14:39:50 +0200 Subject: [PATCH] fix required types not respected --- package.json | 3 +++ src/defaultConfig.ts | 2 +- src/runtime/server/middleware/rateLimiter.ts | 12 +++++++++--- .../server/middleware/requestSizeLimiter.ts | 14 +++++++++++--- src/types/middlewares.ts | 15 ++++++++++----- src/types/module.ts | 8 ++------ 6 files changed, 36 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 964dcf50..56526000 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,9 @@ "src/utils/hash.ts", "src/utils/headers.ts", "src/utils/merge.ts" + ], + "externals": [ + "unstorage" ] } } diff --git a/src/defaultConfig.ts b/src/defaultConfig.ts index 0e9aa164..d393631d 100644 --- a/src/defaultConfig.ts +++ b/src/defaultConfig.ts @@ -2,7 +2,7 @@ import type { ModuleOptions } from './types/module' const defaultThrowErrorValue = { throwError: true } -export const defaultSecurityConfig = (serverlUrl: string): Partial => ({ +export const defaultSecurityConfig = (serverlUrl: string): ModuleOptions => ({ headers: { crossOriginResourcePolicy: 'same-origin', crossOriginOpenerPolicy: 'same-origin', diff --git a/src/runtime/server/middleware/rateLimiter.ts b/src/runtime/server/middleware/rateLimiter.ts index 4c5df7d4..d8421f14 100644 --- a/src/runtime/server/middleware/rateLimiter.ts +++ b/src/runtime/server/middleware/rateLimiter.ts @@ -2,6 +2,8 @@ import { defineEventHandler, createError, setResponseHeader, useStorage, getRequ import type { H3Event } from 'h3' import { resolveSecurityRoute, resolveSecurityRules } from '../../nitro/context' import type { RateLimiter } from '../../../types/middlewares' +import { defaultSecurityConfig } from '../../../defaultConfig' +import defu from 'defu' type StorageItem = { value: number, @@ -9,6 +11,7 @@ type StorageItem = { } const storage = useStorage('#rate-limiter-storage') +const defaultRateLimiter = defaultSecurityConfig('').rateLimiter as Required export default defineEventHandler(async(event) => { // Disable rate limiter in prerender mode @@ -20,7 +23,10 @@ export default defineEventHandler(async(event) => { const route = resolveSecurityRoute(event) if (rules.enabled && rules.rateLimiter) { - const { rateLimiter } = rules + const rateLimiter = defu( + rules.rateLimiter, + defaultRateLimiter + ) const ip = getIP(event) const url = ip + route @@ -68,14 +74,14 @@ export default defineEventHandler(async(event) => { if (currentItem && rateLimiter.headers) { setResponseHeader(event, 'x-ratelimit-remaining', currentItem.value) - setResponseHeader(event, 'x-ratelimit-limit', rateLimiter?.tokensPerInterval) + setResponseHeader(event, 'x-ratelimit-limit', rateLimiter.tokensPerInterval) setResponseHeader(event, 'x-ratelimit-reset', timeForInterval) } } } }) -async function setStorageItem(rateLimiter: Omit, url: string) { +async function setStorageItem(rateLimiter: Required, url: string) { const rateLimitedObject: StorageItem = { value: rateLimiter.tokensPerInterval, date: Date.now() } await storage.setItem(url, rateLimitedObject) } diff --git a/src/runtime/server/middleware/requestSizeLimiter.ts b/src/runtime/server/middleware/requestSizeLimiter.ts index b7854623..7778461b 100644 --- a/src/runtime/server/middleware/requestSizeLimiter.ts +++ b/src/runtime/server/middleware/requestSizeLimiter.ts @@ -1,12 +1,20 @@ import { defineEventHandler, getRequestHeader, createError } from '#imports' +import { defaultSecurityConfig } from '../../../defaultConfig' import { resolveSecurityRules } from '../../nitro/context' +import { type RequestSizeLimiter } from '../../../types/middlewares' +import defu from 'defu' const FILE_UPLOAD_HEADER = 'multipart/form-data' +const defaultSizeLimiter = defaultSecurityConfig('').requestSizeLimiter as Required export default defineEventHandler((event) => { const rules = resolveSecurityRules(event) if (rules.enabled && rules.requestSizeLimiter) { + const requestSizeLimiter = defu( + rules.requestSizeLimiter, + defaultSizeLimiter, + ) if (['POST', 'PUT', 'DELETE'].includes(event.node.req.method!)) { const contentLengthValue = getRequestHeader(event, 'content-length') const contentTypeValue = getRequestHeader(event, 'content-type') @@ -14,15 +22,15 @@ export default defineEventHandler((event) => { const isFileUpload = contentTypeValue?.includes(FILE_UPLOAD_HEADER) const requestLimit = isFileUpload - ? rules.requestSizeLimiter.maxUploadFileRequestInBytes - : rules.requestSizeLimiter.maxRequestSizeInBytes + ? requestSizeLimiter.maxUploadFileRequestInBytes + : requestSizeLimiter.maxRequestSizeInBytes if (parseInt(contentLengthValue as string) >= requestLimit) { const payloadTooLargeError = { statusCode: 413, statusMessage: 'Payload Too Large' } - if (rules.requestSizeLimiter.throwError === false) { + if (requestSizeLimiter.throwError === false) { return payloadTooLargeError } throw createError(payloadTooLargeError) diff --git a/src/types/middlewares.ts b/src/types/middlewares.ts index a2998238..a5207a78 100644 --- a/src/types/middlewares.ts +++ b/src/types/middlewares.ts @@ -1,18 +1,23 @@ import type { BuiltinDriverName, BuiltinDriverOptions } from 'unstorage' +// NOTE : unstorage is not an explicit dependency in package.json +// This causes @nuxt/module-builder to fail when preparing the module for publishing +// The solution is to add unstorage as an external dependency of unbuild +// This is done in the unbuild entry of package.json + export type RequestSizeLimiter = { maxRequestSizeInBytes?: number; maxUploadFileRequestInBytes?: number; throwError?: boolean; }; -export type RateLimiter = { +export type RateLimiter = { tokensPerInterval?: number; interval?: string | number; driver?: { - [K in T]: { - name: K; - options?: BuiltinDriverOptions[K] } - }[T]; + [driverName in BuiltinDriverName]: { + name: driverName; + options?: BuiltinDriverOptions[driverName] } + }[BuiltinDriverName]; headers?: boolean; throwError?: boolean; }; diff --git a/src/types/module.ts b/src/types/module.ts index f8e2c300..1f2f566e 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -5,10 +5,6 @@ import type { AllowedHTTPMethods, BasicAuth, RateLimiter, RequestSizeLimiter, Xs import type { HookResult } from '@nuxt/schema' -type RequiredWithoutThrowError = Omit; -type OptionalThrowError = Pick; -type QualifiedConfig = Required> & Partial>; - export type Ssg = { meta?: boolean; hashScripts?: boolean; @@ -36,9 +32,9 @@ export interface ModuleOptions { export type NuxtSecurityRouteRules = Partial< Omit - & { rateLimiter: QualifiedConfig> | false } + & { rateLimiter: Omit | false } & { ssg: Omit | false } - & { requestSizeLimiter: QualifiedConfig | false } + & { requestSizeLimiter: RequestSizeLimiter | false } > declare module 'nuxt/schema' {