Skip to content

Commit

Permalink
fix required types not respected
Browse files Browse the repository at this point in the history
  • Loading branch information
vejja committed May 30, 2024
1 parent 6f6ecc1 commit 1bdb71d
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 18 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@
"src/utils/hash.ts",
"src/utils/headers.ts",
"src/utils/merge.ts"
],
"externals": [
"unstorage"
]
}
}
2 changes: 1 addition & 1 deletion src/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ModuleOptions } from './types/module'

const defaultThrowErrorValue = { throwError: true }

export const defaultSecurityConfig = (serverlUrl: string): Partial<ModuleOptions> => ({
export const defaultSecurityConfig = (serverlUrl: string): ModuleOptions => ({
headers: {
crossOriginResourcePolicy: 'same-origin',
crossOriginOpenerPolicy: 'same-origin',
Expand Down
12 changes: 9 additions & 3 deletions src/runtime/server/middleware/rateLimiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ 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,
date: number
}

const storage = useStorage<StorageItem>('#rate-limiter-storage')
const defaultRateLimiter = defaultSecurityConfig('').rateLimiter as Required<RateLimiter>

export default defineEventHandler(async(event) => {
// Disable rate limiter in prerender mode
Expand All @@ -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

Expand Down Expand Up @@ -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<RateLimiter, 'driver'>, url: string) {
async function setStorageItem(rateLimiter: Required<RateLimiter>, url: string) {
const rateLimitedObject: StorageItem = { value: rateLimiter.tokensPerInterval, date: Date.now() }
await storage.setItem(url, rateLimitedObject)
}
Expand Down
14 changes: 11 additions & 3 deletions src/runtime/server/middleware/requestSizeLimiter.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
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<RequestSizeLimiter>

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')

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)
Expand Down
15 changes: 10 additions & 5 deletions src/types/middlewares.ts
Original file line number Diff line number Diff line change
@@ -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<T extends BuiltinDriverName = BuiltinDriverName> = {
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;
};
Expand Down
8 changes: 2 additions & 6 deletions src/types/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import type { AllowedHTTPMethods, BasicAuth, RateLimiter, RequestSizeLimiter, Xs
import type { HookResult } from '@nuxt/schema'


type RequiredWithoutThrowError<T> = Omit<T, 'throwError'>;
type OptionalThrowError<T> = Pick<T, 'throwError'>;
type QualifiedConfig<T> = Required<RequiredWithoutThrowError<T>> & Partial<OptionalThrowError<T>>;

export type Ssg = {
meta?: boolean;
hashScripts?: boolean;
Expand Down Expand Up @@ -36,9 +32,9 @@ export interface ModuleOptions {

export type NuxtSecurityRouteRules = Partial<
Omit<ModuleOptions, 'csrf' | 'basicAuth' | 'rateLimiter' | 'ssg' | 'requestSizeLimiter' >
& { rateLimiter: QualifiedConfig<Omit<RateLimiter, 'driver'>> | false }
& { rateLimiter: Omit<RateLimiter, 'driver'> | false }
& { ssg: Omit<Ssg, 'exportToPresets'> | false }
& { requestSizeLimiter: QualifiedConfig<RequestSizeLimiter> | false }
& { requestSizeLimiter: RequestSizeLimiter | false }
>

declare module 'nuxt/schema' {
Expand Down

0 comments on commit 1bdb71d

Please sign in to comment.