Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ fastify.listen({ port: 3000 }, err => {
`@fastify/helmet` is a wrapper around `helmet` that adds an `'onRequest'` hook
and a `reply.helmet` decorator.

TypeScript note: after `fastify.register(helmet)`, `reply.helmet()` and
`reply.cspNonce` are inferred from that registered instance. The `helmet`
route option still uses Fastify route option augmentation.

It accepts the same options as `helmet`. See [helmet documentation](https://helmetjs.github.io/).

### Apply Helmet to all routes
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"@types/node": "^25.0.3",
"c8": "^11.0.0",
"eslint": "^9.17.0",
"fastify": "^5.0.0",
"fastify": "github:fastify/fastify#feat/typed-decorators",
"neostandard": "^0.12.0",
"tsd": "^0.33.0"
},
Expand Down
44 changes: 29 additions & 15 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { FastifyPluginAsync, RawServerBase, RawServerDefault } from 'fastify'
import {
AnyFastifyInstance,
ApplyDecorators,
FastifyPluginAsync,
RawServerBase,
RawServerDefault,
UnEncapsulatedPlugin
} from 'fastify'
import helmet, { contentSecurityPolicy, HelmetOptions } from 'helmet'

declare module 'fastify' {
Expand All @@ -7,22 +14,29 @@ declare module 'fastify' {
RawServer extends RawServerBase = RawServerDefault
> extends fastifyHelmet.FastifyHelmetRouteOptions { }

interface FastifyReply {
cspNonce: {
script: string;
style: string;
},
helmet: (opts?: HelmetOptions) => typeof helmet
}

export interface RouteOptions extends fastifyHelmet.FastifyHelmetRouteOptions { }
}

type FastifyHelmet = FastifyPluginAsync<fastifyHelmet.FastifyHelmetOptions> & {
contentSecurityPolicy: typeof contentSecurityPolicy;
}

declare namespace fastifyHelmet {
export type FastifyHelmetPluginDecorators = {
reply: {
cspNonce: {
script: string;
style: string;
};
helmet: (opts?: HelmetOptions) => typeof helmet;
}
}

export type FastifyHelmetPlugin<TInstance extends AnyFastifyInstance = AnyFastifyInstance> = UnEncapsulatedPlugin<
FastifyPluginAsync<
fastifyHelmet.FastifyHelmetOptions,
TInstance,
ApplyDecorators<TInstance, FastifyHelmetPluginDecorators>
>
> & {
contentSecurityPolicy: typeof contentSecurityPolicy;
}

export interface FastifyHelmetRouteOptions {
helmet?: Omit<FastifyHelmetOptions, 'global'> | false;
Expand All @@ -33,9 +47,9 @@ declare namespace fastifyHelmet {
global?: boolean;
} & NonNullable<HelmetOptions>

export const fastifyHelmet: FastifyHelmet
export const fastifyHelmet: FastifyHelmetPlugin
export { fastifyHelmet as default }
}

declare function fastifyHelmet (...params: Parameters<FastifyHelmet>): ReturnType<FastifyHelmet>
declare function fastifyHelmet (...params: Parameters<fastifyHelmet.FastifyHelmetPlugin>): ReturnType<fastifyHelmet.FastifyHelmetPlugin>
export = fastifyHelmet
65 changes: 25 additions & 40 deletions types/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import fastify, { FastifyPluginAsync } from 'fastify'
import fastify from 'fastify'
import helmet from 'helmet'
import { expectAssignable, expectError, expectType } from 'tsd'
import fastifyHelmet, { FastifyHelmetOptions, FastifyHelmetRouteOptions } from '..'
import fastifyHelmet, {
FastifyHelmetOptions,
FastifyHelmetPlugin,
FastifyHelmetPluginDecorators,
FastifyHelmetRouteOptions
} from '..'

// Plugin registered with no options
const appOne = fastify()
appOne.register(fastifyHelmet)
const appOne = fastify().register(fastifyHelmet)

// Plugin registered with an empty object option
const appTwo = fastify()
expectAssignable<FastifyHelmetOptions>({})
appTwo.register(fastifyHelmet, {})
const appTwo = fastify().register(fastifyHelmet, {})

// Plugin registered with all helmet middlewares disabled
const appThree = fastify()
const helmetOptions = {
contentSecurityPolicy: false,
dnsPrefetchControl: false,
Expand All @@ -27,11 +29,10 @@ const helmetOptions = {
xssFilter: false
}
expectAssignable<FastifyHelmetOptions>(helmetOptions)
appThree.register(fastifyHelmet, helmetOptions)
const appThree = fastify().register(fastifyHelmet, helmetOptions)

// Plugin registered with helmet middlewares custom settings
const appFour = fastify()
appFour.register(fastifyHelmet, {
const appFour = fastify().register(fastifyHelmet, {
contentSecurityPolicy: {
directives: {
'directive-1': ['foo', 'bar']
Expand Down Expand Up @@ -64,19 +65,14 @@ appFour.register(fastifyHelmet, {
})

// Plugin registered with `enableCSPNonces` option and helmet default CSP settings
const appFive = fastify()
appFive.register(fastifyHelmet, { enableCSPNonces: true })
const appFive = fastify().register(fastifyHelmet, { enableCSPNonces: true })

appFive.get('/', function (_request, reply) {
expectType<{
script: string;
style: string;
}>(reply.cspNonce)
expectType<FastifyHelmetPluginDecorators['reply']['cspNonce']>(reply.cspNonce)
})

// Plugin registered with `enableCSPNonces` option and custom CSP settings
const appSix = fastify()
appSix.register(fastifyHelmet, {
const appSix = fastify().register(fastifyHelmet, {
enableCSPNonces: true,
contentSecurityPolicy: {
directives: {
Expand All @@ -87,20 +83,17 @@ appSix.register(fastifyHelmet, {
})

appSix.get('/', function (_request, reply) {
expectType<{
script: string;
style: string;
}>(reply.cspNonce)
expectType<FastifyHelmetPluginDecorators['reply']['cspNonce']>(reply.cspNonce)
})

const csp = fastifyHelmet.contentSecurityPolicy
expectType<typeof helmet.contentSecurityPolicy>(csp)

// Plugin registered with `global` set to `true`
const appSeven = fastify()
appSeven.register(fastifyHelmet, { global: true })
const appSeven = fastify().register(fastifyHelmet, { global: true })

appSeven.get('/route-with-disabled-helmet', { helmet: false }, function (_request, reply) {
expectType<FastifyHelmetPluginDecorators['reply']['helmet']>(reply.helmet)
expectType<typeof helmet>(reply.helmet())
})

Expand All @@ -113,8 +106,7 @@ expectError(
)

// Plugin registered with `global` set to `false`
const appEight = fastify()
appEight.register(fastifyHelmet, { global: false })
const appEight = fastify().register(fastifyHelmet, { global: false })

appEight.get('/disabled-helmet', function (_request, reply) {
expectType<typeof helmet>(reply.helmet(helmetOptions))
Expand Down Expand Up @@ -152,20 +144,14 @@ expectAssignable<FastifyHelmetRouteOptions>(routeHelmetOptions)

appEight.get('/enabled-helmet', routeHelmetOptions, function (_request, reply) {
expectType<typeof helmet>(reply.helmet())
expectType<{
script: string;
style: string;
}>(reply.cspNonce)
expectType<FastifyHelmetPluginDecorators['reply']['cspNonce']>(reply.cspNonce)
})

appEight.get('/enable-framegard', {
helmet: { frameguard: true }
}, function (_request, reply) {
expectType<typeof helmet>(reply.helmet())
expectType<{
script: string;
style: string;
}>(reply.cspNonce)
expectType<FastifyHelmetPluginDecorators['reply']['cspNonce']>(reply.cspNonce)
})

// Plugin registered with an invalid helmet option
Expand All @@ -176,9 +162,8 @@ expectError(
})
)

// fastify-helmet instance is using the FastifyHelmetOptions options
expectType<
FastifyPluginAsync<FastifyHelmetOptions> & {
contentSecurityPolicy: typeof helmet.contentSecurityPolicy;
}
>(fastifyHelmet)
expectType<FastifyHelmetPlugin>(fastifyHelmet)
expectType<typeof appOne>(appOne)
expectType<typeof appTwo>(appTwo)
expectType<typeof appThree>(appThree)
expectType<typeof appFour>(appFour)
Loading