fastify 3.27.0 → 3.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/Guides/Prototype-Poisoning.md +3 -3
- package/docs/Reference/Server.md +1 -1
- package/fastify.d.ts +1 -0
- package/fastify.js +1 -1
- package/lib/errors.js +5 -0
- package/lib/reply.js +16 -6
- package/lib/route.js +6 -1
- package/package.json +1 -1
- package/test/route.test.js +12 -0
- package/test/schema-serialization.test.js +41 -0
- package/test/types/fastify.test-d.ts +1 -0
- package/test/types/instance.test-d.ts +25 -0
- package/types/instance.d.ts +13 -1
- package/types/schema.d.ts +14 -0
|
@@ -117,7 +117,7 @@ an `id` ), and then pass it to our validation library, anything passed through
|
|
|
117
117
|
via `__proto__` would sneak in undetected.
|
|
118
118
|
|
|
119
119
|
### Oh joi!
|
|
120
|
-
<a id="pp-oh-joi">
|
|
120
|
+
<a id="pp-oh-joi"></a>
|
|
121
121
|
|
|
122
122
|
The first question is, of course, why does the validation module **joi** ignore
|
|
123
123
|
the prototype and let potentially harmful data through? We asked ourselves the
|
|
@@ -383,9 +383,9 @@ Because this work is important, I decided to try and make it not just
|
|
|
383
383
|
financially sustainable but to grow and expand it. There is so much to improve.
|
|
384
384
|
This is exactly what motivates me to implement the new [commercial licensing
|
|
385
385
|
plan](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi-licensing-a-preview-f982662ee898)
|
|
386
|
-
coming in March. You can
|
|
386
|
+
coming in March. You can read more about it
|
|
387
387
|
[here](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi-licensing-a-preview-f982662ee898).
|
|
388
388
|
|
|
389
389
|
Of all the time consuming things, security is at the very top. I hope this story
|
|
390
|
-
|
|
390
|
+
successfully conveyed not just the technical details, but also the human drama and
|
|
391
391
|
what it takes to keep the web secure.
|
package/docs/Reference/Server.md
CHANGED
|
@@ -13,7 +13,7 @@ describes the properties available in that options object.
|
|
|
13
13
|
- [`https`](#https)
|
|
14
14
|
- [`connectionTimeout`](#connectiontimeout)
|
|
15
15
|
- [`keepAliveTimeout`](#keepalivetimeout)
|
|
16
|
-
- [`forceCloseConnections](#forcecloseconnections)
|
|
16
|
+
- [`forceCloseConnections`](#forcecloseconnections)
|
|
17
17
|
- [`maxRequestsPerSocket`](#maxrequestspersocket)
|
|
18
18
|
- [`requestTimeout`](#requesttimeout)
|
|
19
19
|
- [`ignoreTrailingSlash`](#ignoretrailingslash)
|
package/fastify.d.ts
CHANGED
package/fastify.js
CHANGED
package/lib/errors.js
CHANGED
package/lib/reply.js
CHANGED
|
@@ -365,12 +365,18 @@ function preserializeHookEnd (err, request, reply, payload) {
|
|
|
365
365
|
return
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
368
|
+
try {
|
|
369
|
+
if (reply[kReplySerializer] !== null) {
|
|
370
|
+
payload = reply[kReplySerializer](payload)
|
|
371
|
+
} else if (reply.context && reply.context[kReplySerializerDefault]) {
|
|
372
|
+
payload = reply.context[kReplySerializerDefault](payload, reply.raw.statusCode)
|
|
373
|
+
} else {
|
|
374
|
+
payload = serialize(reply.context, payload, reply.raw.statusCode)
|
|
375
|
+
}
|
|
376
|
+
} catch (e) {
|
|
377
|
+
wrapSeralizationError(e, reply)
|
|
378
|
+
onErrorHook(reply, e)
|
|
379
|
+
return
|
|
374
380
|
}
|
|
375
381
|
|
|
376
382
|
flatstr(payload)
|
|
@@ -378,6 +384,10 @@ function preserializeHookEnd (err, request, reply, payload) {
|
|
|
378
384
|
onSendHook(reply, payload)
|
|
379
385
|
}
|
|
380
386
|
|
|
387
|
+
function wrapSeralizationError (error, reply) {
|
|
388
|
+
error.serialization = reply.context.config
|
|
389
|
+
}
|
|
390
|
+
|
|
381
391
|
function onSendHook (reply, payload) {
|
|
382
392
|
if (reply.context.onSend !== null) {
|
|
383
393
|
reply[kReplySent] = true
|
package/lib/route.js
CHANGED
|
@@ -18,7 +18,8 @@ const {
|
|
|
18
18
|
const {
|
|
19
19
|
FST_ERR_SCH_VALIDATION_BUILD,
|
|
20
20
|
FST_ERR_SCH_SERIALIZATION_BUILD,
|
|
21
|
-
FST_ERR_DEFAULT_ROUTE_INVALID_TYPE
|
|
21
|
+
FST_ERR_DEFAULT_ROUTE_INVALID_TYPE,
|
|
22
|
+
FST_ERR_INVALID_URL
|
|
22
23
|
} = require('./errors')
|
|
23
24
|
|
|
24
25
|
const {
|
|
@@ -99,6 +100,10 @@ function buildRouting (options) {
|
|
|
99
100
|
|
|
100
101
|
// Convert shorthand to extended route declaration
|
|
101
102
|
function prepareRoute (method, url, options, handler) {
|
|
103
|
+
if (typeof url !== 'string') {
|
|
104
|
+
throw new FST_ERR_INVALID_URL(typeof url)
|
|
105
|
+
}
|
|
106
|
+
|
|
102
107
|
if (!handler && typeof options === 'function') {
|
|
103
108
|
handler = options // for support over direct function calls such as fastify.get() options are reused as the handler
|
|
104
109
|
options = {}
|
package/package.json
CHANGED
package/test/route.test.js
CHANGED
|
@@ -8,6 +8,7 @@ const sget = require('simple-get').concat
|
|
|
8
8
|
const joi = require('@hapi/joi')
|
|
9
9
|
const Fastify = require('..')
|
|
10
10
|
const proxyquire = require('proxyquire')
|
|
11
|
+
const { FST_ERR_INVALID_URL } = require('../lib/errors')
|
|
11
12
|
|
|
12
13
|
test('route', t => {
|
|
13
14
|
t.plan(9)
|
|
@@ -1268,3 +1269,14 @@ test('Correct error message is produced if "path" option is used', t => {
|
|
|
1268
1269
|
})
|
|
1269
1270
|
}, new Error('Error Handler for POST:/test route, if defined, must be a function'))
|
|
1270
1271
|
})
|
|
1272
|
+
|
|
1273
|
+
test('invalid url attribute - non string URL', t => {
|
|
1274
|
+
t.plan(1)
|
|
1275
|
+
const fastify = Fastify()
|
|
1276
|
+
|
|
1277
|
+
try {
|
|
1278
|
+
fastify.get(/^\/(donations|skills|blogs)/, () => {})
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
t.equal(error.code, FST_ERR_INVALID_URL().code)
|
|
1281
|
+
}
|
|
1282
|
+
})
|
|
@@ -670,3 +670,44 @@ test('error in custom schema serialize compiler, throw FST_ERR_SCH_SERIALIZATION
|
|
|
670
670
|
t.equal(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD')
|
|
671
671
|
})
|
|
672
672
|
})
|
|
673
|
+
|
|
674
|
+
test('Errors in searilizer sended to errorHandler', async t => {
|
|
675
|
+
let savedError
|
|
676
|
+
|
|
677
|
+
const fastify = Fastify()
|
|
678
|
+
fastify.get('/', {
|
|
679
|
+
schema: {
|
|
680
|
+
response: {
|
|
681
|
+
200: {
|
|
682
|
+
type: 'object',
|
|
683
|
+
properties: {
|
|
684
|
+
name: { type: 'string' },
|
|
685
|
+
power: { type: 'string' }
|
|
686
|
+
},
|
|
687
|
+
required: ['name']
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
}, function (req, reply) {
|
|
693
|
+
reply.code(200).send({ no: 'thing' })
|
|
694
|
+
})
|
|
695
|
+
fastify.setErrorHandler((error, request, reply) => {
|
|
696
|
+
savedError = error
|
|
697
|
+
reply.code(500).send(error)
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
const res = await fastify.inject('/')
|
|
701
|
+
|
|
702
|
+
t.equal(res.statusCode, 500)
|
|
703
|
+
|
|
704
|
+
// t.same(savedError, new Error('"name" is required!'));
|
|
705
|
+
t.same(res.json(), {
|
|
706
|
+
statusCode: 500,
|
|
707
|
+
error: 'Internal Server Error',
|
|
708
|
+
message: '"name" is required!'
|
|
709
|
+
})
|
|
710
|
+
t.ok(savedError, 'error presents')
|
|
711
|
+
t.ok(savedError.serialization, 'Serialization sign presents')
|
|
712
|
+
t.end()
|
|
713
|
+
})
|
|
@@ -57,6 +57,7 @@ fastify({ http2: true, https: {} }).inject({}, lightMyRequestCallback)
|
|
|
57
57
|
expectAssignable<FastifyInstance<http2.Http2Server, http2.Http2ServerRequest, http2.Http2ServerResponse>>(fastify({ http2: true }))
|
|
58
58
|
expectAssignable<FastifyInstance>(fastify({ ignoreTrailingSlash: true }))
|
|
59
59
|
expectAssignable<FastifyInstance>(fastify({ connectionTimeout: 1000 }))
|
|
60
|
+
expectAssignable<FastifyInstance>(fastify({ forceCloseConnections: true }))
|
|
60
61
|
expectAssignable<FastifyInstance>(fastify({ keepAliveTimeout: 1000 }))
|
|
61
62
|
expectAssignable<FastifyInstance>(fastify({ pluginTimeout: 1000 }))
|
|
62
63
|
expectAssignable<FastifyInstance>(fastify({ bodyLimit: 100 }))
|
|
@@ -10,6 +10,7 @@ import { expectAssignable, expectError, expectType } from 'tsd'
|
|
|
10
10
|
import { FastifyRequest } from '../../types/request'
|
|
11
11
|
import { FastifyReply } from '../../types/reply'
|
|
12
12
|
import { HookHandlerDoneFunction } from '../../types/hooks'
|
|
13
|
+
import { FastifySchemaControllerOptions } from '../../types/schema'
|
|
13
14
|
|
|
14
15
|
const server = fastify()
|
|
15
16
|
|
|
@@ -112,6 +113,29 @@ server.setNotFoundHandler({ preHandler: notFoundpreHandlerHandler, preValidation
|
|
|
112
113
|
function invalidErrorHandler (error: number) {}
|
|
113
114
|
expectError(server.setErrorHandler(invalidErrorHandler))
|
|
114
115
|
|
|
116
|
+
server.setSchemaController({
|
|
117
|
+
bucket: (parentSchemas: unknown) => {
|
|
118
|
+
return {
|
|
119
|
+
addSchema (schema: unknown) {
|
|
120
|
+
expectType<unknown>(schema)
|
|
121
|
+
expectType<FastifyInstance>(server.addSchema({ type: 'null' }))
|
|
122
|
+
return server.addSchema({ type: 'null' })
|
|
123
|
+
},
|
|
124
|
+
getSchema (schemaId: string) {
|
|
125
|
+
expectType<string>(schemaId)
|
|
126
|
+
return server.getSchema('SchemaId')
|
|
127
|
+
},
|
|
128
|
+
getSchemas () {
|
|
129
|
+
expectType<Record<string, unknown>>(server.getSchemas())
|
|
130
|
+
return server.getSchemas()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
function invalidSchemaController (schemaControllerOptions: FastifySchemaControllerOptions) {}
|
|
137
|
+
expectError(server.setSchemaController(invalidSchemaController))
|
|
138
|
+
|
|
115
139
|
server.setReplySerializer(function (payload, statusCode) {
|
|
116
140
|
expectType<unknown>(payload)
|
|
117
141
|
expectType<number>(statusCode)
|
|
@@ -171,6 +195,7 @@ type InitialConfig = Readonly<{
|
|
|
171
195
|
keepAliveTimeout?: number,
|
|
172
196
|
bodyLimit?: number,
|
|
173
197
|
caseSensitive?: boolean,
|
|
198
|
+
forceCloseConnections?: boolean,
|
|
174
199
|
http2?: boolean,
|
|
175
200
|
https?: boolean | Readonly<{ allowHTTP1: boolean }>,
|
|
176
201
|
ignoreTrailingSlash?: boolean,
|
package/types/instance.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse, CallbackFunc as LightMyRequestCallback } from 'light-my-request'
|
|
2
2
|
import { RouteOptions, RouteShorthandMethod, RouteGenericInterface, DefaultRoute } from './route'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
FastifySchema,
|
|
5
|
+
FastifySchemaCompiler,
|
|
6
|
+
FastifySchemaValidationError,
|
|
7
|
+
FastifySerializerCompiler,
|
|
8
|
+
FastifySchemaControllerOptions
|
|
9
|
+
} from './schema'
|
|
4
10
|
import { RawServerBase, RawRequestDefaultExpression, RawServerDefault, RawReplyDefaultExpression, ContextConfigDefault } from './utils'
|
|
5
11
|
import { FastifyLoggerInstance } from './logger'
|
|
6
12
|
import { FastifyRegister } from './register'
|
|
@@ -380,6 +386,11 @@ export interface FastifyInstance<
|
|
|
380
386
|
*/
|
|
381
387
|
setSerializerCompiler<T = FastifySchema>(schemaCompiler: FastifySerializerCompiler<T>): FastifyInstance<RawServer, RawRequest, RawReply, Logger>;
|
|
382
388
|
|
|
389
|
+
/**
|
|
390
|
+
* Set the schema controller for all routes.
|
|
391
|
+
*/
|
|
392
|
+
setSchemaController(schemaControllerOpts: FastifySchemaControllerOptions): FastifyInstance<RawServer, RawRequest, RawReply, Logger>;
|
|
393
|
+
|
|
383
394
|
/**
|
|
384
395
|
* Set the reply serializer for all routes.
|
|
385
396
|
*/
|
|
@@ -427,6 +438,7 @@ export interface FastifyInstance<
|
|
|
427
438
|
initialConfig: Readonly<{
|
|
428
439
|
connectionTimeout?: number,
|
|
429
440
|
keepAliveTimeout?: number,
|
|
441
|
+
forceCloseConnections?: boolean,
|
|
430
442
|
bodyLimit?: number,
|
|
431
443
|
caseSensitive?: boolean,
|
|
432
444
|
http2?: boolean,
|
package/types/schema.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ValidatorCompiler } from '@fastify/ajv-compiler'
|
|
2
|
+
import { FastifyInstance, FastifyServerOptions } from '../fastify'
|
|
1
3
|
/**
|
|
2
4
|
* Schemas in Fastify follow the JSON-Schema standard. For this reason
|
|
3
5
|
* we have opted to not ship strict schema based types. Instead we provide
|
|
@@ -36,3 +38,15 @@ export interface FastifyValidationResult {
|
|
|
36
38
|
export type FastifySchemaCompiler<T> = (routeSchema: FastifyRouteSchemaDef<T>) => FastifyValidationResult
|
|
37
39
|
|
|
38
40
|
export type FastifySerializerCompiler<T> = (routeSchema: FastifyRouteSchemaDef<T>) => (data: any) => string
|
|
41
|
+
|
|
42
|
+
export interface FastifySchemaControllerOptions{
|
|
43
|
+
bucket?: (parentSchemas?: unknown) => {
|
|
44
|
+
addSchema(schema: unknown): FastifyInstance;
|
|
45
|
+
getSchema(schemaId: string): unknown;
|
|
46
|
+
getSchemas(): Record<string, unknown>;
|
|
47
|
+
};
|
|
48
|
+
compilersFactory?: {
|
|
49
|
+
buildValidator?: ValidatorCompiler;
|
|
50
|
+
buildSerializer?: (externalSchemas: unknown, serializerOptsServerOption: FastifyServerOptions['serializerOpts']) => FastifySerializerCompiler<unknown>;
|
|
51
|
+
};
|
|
52
|
+
}
|