diff --git a/README.md b/README.md index 40e609d..0716903 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/package.json b/package.json index 6a68f84..5a09bac 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/types/index.d.ts b/types/index.d.ts index 2920830..3c78121 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -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' { @@ -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 & { - contentSecurityPolicy: typeof contentSecurityPolicy; -} - declare namespace fastifyHelmet { + export type FastifyHelmetPluginDecorators = { + reply: { + cspNonce: { + script: string; + style: string; + }; + helmet: (opts?: HelmetOptions) => typeof helmet; + } + } + + export type FastifyHelmetPlugin = UnEncapsulatedPlugin< + FastifyPluginAsync< + fastifyHelmet.FastifyHelmetOptions, + TInstance, + ApplyDecorators + > + > & { + contentSecurityPolicy: typeof contentSecurityPolicy; + } export interface FastifyHelmetRouteOptions { helmet?: Omit | false; @@ -33,9 +47,9 @@ declare namespace fastifyHelmet { global?: boolean; } & NonNullable - export const fastifyHelmet: FastifyHelmet + export const fastifyHelmet: FastifyHelmetPlugin export { fastifyHelmet as default } } -declare function fastifyHelmet (...params: Parameters): ReturnType +declare function fastifyHelmet (...params: Parameters): ReturnType export = fastifyHelmet diff --git a/types/index.test-d.ts b/types/index.test-d.ts index aa9a6ca..a35e3c9 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -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({}) -appTwo.register(fastifyHelmet, {}) +const appTwo = fastify().register(fastifyHelmet, {}) // Plugin registered with all helmet middlewares disabled -const appThree = fastify() const helmetOptions = { contentSecurityPolicy: false, dnsPrefetchControl: false, @@ -27,11 +29,10 @@ const helmetOptions = { xssFilter: false } expectAssignable(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'] @@ -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(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: { @@ -87,20 +83,17 @@ appSix.register(fastifyHelmet, { }) appSix.get('/', function (_request, reply) { - expectType<{ - script: string; - style: string; - }>(reply.cspNonce) + expectType(reply.cspNonce) }) const csp = fastifyHelmet.contentSecurityPolicy expectType(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(reply.helmet) expectType(reply.helmet()) }) @@ -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(reply.helmet(helmetOptions)) @@ -152,20 +144,14 @@ expectAssignable(routeHelmetOptions) appEight.get('/enabled-helmet', routeHelmetOptions, function (_request, reply) { expectType(reply.helmet()) - expectType<{ - script: string; - style: string; - }>(reply.cspNonce) + expectType(reply.cspNonce) }) appEight.get('/enable-framegard', { helmet: { frameguard: true } }, function (_request, reply) { expectType(reply.helmet()) - expectType<{ - script: string; - style: string; - }>(reply.cspNonce) + expectType(reply.cspNonce) }) // Plugin registered with an invalid helmet option @@ -176,9 +162,8 @@ expectError( }) ) -// fastify-helmet instance is using the FastifyHelmetOptions options -expectType< - FastifyPluginAsync & { - contentSecurityPolicy: typeof helmet.contentSecurityPolicy; - } ->(fastifyHelmet) +expectType(fastifyHelmet) +expectType(appOne) +expectType(appTwo) +expectType(appThree) +expectType(appFour)