From d45137018d2a289d7bc898a311e11002e5a3a409 Mon Sep 17 00:00:00 2001 From: Niklas Wulf Date: Wed, 7 Jun 2017 15:37:05 +0200 Subject: [PATCH] [Hapi] Fix input and response validation function signatures (#16802) * [Hapi] Fix input and response validation function signatures Fixes #16750 * [Hapi] Allow custom validation options in route input and response * [Hapi] Adjust validation contexts to match Hapi version 16.1.1 * [Hapi] Adjust test/route/validate.ts to match newest changes * [Hapi] Add custom options to response validation test * [Hapi] Update TypeScript version to 2.3 in packages that import Hapi * [Hapi] Clarify documentation on validation function value, allow null --- types/h2o2/index.d.ts | 2 +- types/hapi-auth-basic/index.d.ts | 2 +- types/hapi-decorators/index.d.ts | 1 + types/hapi/index.d.ts | 149 +++++++++++++++++++++--------- types/hapi/test/route/validate.ts | 46 ++++++++- types/inert/index.d.ts | 1 + types/nes/index.d.ts | 1 + types/vision/index.d.ts | 2 +- 8 files changed, 154 insertions(+), 50 deletions(-) diff --git a/types/h2o2/index.d.ts b/types/h2o2/index.d.ts index a1a3680e5c..7ca3ccb85f 100644 --- a/types/h2o2/index.d.ts +++ b/types/h2o2/index.d.ts @@ -2,7 +2,7 @@ // Project: https://github.com/hapijs/catbox // Definitions by: Jason Swearingen , AJP // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.2 +// TypeScript Version: 2.3 /// diff --git a/types/hapi-auth-basic/index.d.ts b/types/hapi-auth-basic/index.d.ts index 23b06de111..07c3c8cc20 100644 --- a/types/hapi-auth-basic/index.d.ts +++ b/types/hapi-auth-basic/index.d.ts @@ -2,7 +2,7 @@ // Project: https://github.com/hapijs/hapi-auth-basic // Definitions by: AJP // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.2 +// TypeScript Version: 2.3 import * as Hapi from 'hapi'; diff --git a/types/hapi-decorators/index.d.ts b/types/hapi-decorators/index.d.ts index 2933945e64..e4d7c40991 100644 --- a/types/hapi-decorators/index.d.ts +++ b/types/hapi-decorators/index.d.ts @@ -2,6 +2,7 @@ // Project: https://github.com/knownasilya/hapi-decorators // Definitions by: Ken Howard // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.3 import * as hapi from 'hapi'; import * as Joi from 'joi'; diff --git a/types/hapi/index.d.ts b/types/hapi/index.d.ts index 8dca27d66e..9b03944e9a 100644 --- a/types/hapi/index.d.ts +++ b/types/hapi/index.d.ts @@ -2,7 +2,7 @@ // Project: https://github.com/hapijs/hapi // Definitions by: Jason Swearingen , AJP // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.2 +// TypeScript Version: 2.3 /* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1310,7 +1310,7 @@ export interface RoutePrerequisiteObjects { /** * For context see RouteAdditionalConfigurationOptions > response */ -export interface RouteResponseConfigurationObject { +export interface RouteResponseConfigurationObject { /** the default HTTP status code when the payload is empty. Value can be 200 or 204. Note that a 200 status code is converted to a 204 only at the time or response transmission (the response status code will remain 200 throughout the request lifecycle unless manually set). Defaults to 200. */ emptyStatusCode?: number; /** @@ -1326,16 +1326,20 @@ export interface RouteResponseConfigurationObject { failAction?: 'error' | 'log' | ((request: Request, reply: ReplyWithContinue, source: string, error: Boom.BoomError) => void); /** if true, applies the validation rule changes to the response payload. Defaults to false. */ modify?: boolean; - /** options to pass to Joi. Useful to set global options such as stripUnknown or abortEarly (the complete list is available [here](https://github.com/hapijs/joi/blob/master/API.md#validatevalue-schema-options-callback) ). Defaults to no options. */ - options?: JoiValidationOptions; + /** + * options to pass to Joi. Useful to set global options such as stripUnknown or abortEarly (the complete list is available [here](https://github.com/hapijs/joi/blob/master/API.md#validatevalue-schema-options-callback) ). + * If a custom validation function (see `schema` or `status` below) is defined then `options` can an arbitrary object that will be passed to this function as the second parameter. + * Defaults to no options. + */ + options?: ValidationOptions; /** if false, payload range support is disabled. Defaults to true. */ ranges?: boolean; /** the percent of response payloads validated (0 - 100). Set to 0 to disable all validation. Defaults to 100 (all response payloads). */ sample?: number; /** the default response payload validation rules (for all non-error responses) */ - schema?: RouteResponseConfigurationScheme; + schema?: RouteResponseConfigurationScheme; /** HTTP status-code-specific payload validation rules. The status key is set to an object where each key is a 3 digit HTTP status code and the value has the same definition as schema. If a response status code is not present in the status object, the schema definition is used, except for errors which are not validated by default. */ - status?: Dictionary; + status?: Dictionary>; } /** @@ -1343,27 +1347,54 @@ export interface RouteResponseConfigurationObject { * * true - any payload allowed (no validation performed). This is the default. * * false - no payload allowed. * * a Joi validation object. This will receive the request's headers, params, query, payload, and auth credentials and isAuthenticated flags as context. - * * a validation function using the signature function(value, options, next) where: - * * value - the object containing the response object. - * * options - the server validation options, merged with an object containing the request's headers, params, payload, and auth credentials object and isAuthenticated flag. - * * next(err) - the callback function called when validation is completed. + * * a validation function + * * TODO check JoiValidationObject is correct for "a Joi validation object" * * For context see RouteAdditionalConfigurationOptions > response > schema * and * For context see RouteAdditionalConfigurationOptions > response > status */ -export type RouteResponseConfigurationScheme = boolean | JoiValidationObject | ValidationFunctionForRouteResponse; +export type RouteResponseConfigurationScheme = boolean | JoiValidationObject | ValidationFunctionForRouteResponse; /** * see RouteResponseConfigurationScheme * - * TODO check `value: Response` is correct as it says "**the object containing** the response object." not just "the response object". - * TODO check `options: JoiValidationOptions` is correct - * Also see ValidationFunctionForRouteValidate + * a validation function using the signature function(value, options, next) where: + * * value - the value of the response passed to `reply(value)` in the handler. + * * options - the server validation options, merged with an object containing the request's headers, params, payload, and auth credentials object and `isAuthenticated` flag. + * * next([err, [value]]) - the callback function called when validation is completed. `value` will be used as the response value when `err` is falsy, when `value` is not `undefined`, and when `route.settings.response.modify` is `true`. If the response is already a `Boom` error it will be set as its `message` value. */ -export interface ValidationFunctionForRouteResponse { - (value: Response, options: JoiValidationOptions, next: ContinuationFunction): void; +export interface ValidationFunctionForRouteResponse { + (value: any, options: RouteResponseValidationContext & ValidationOptions, next: ContinuationValueFunction): void; +} + +/** + * A context for route input validation via a Joi schema or validation function. + * + * This object is merged with the route response options and passed into the validation function. + * + * See https://github.com/hapijs/hapi/blob/v16.1.1/lib/validation.js#L217 + */ +export interface RouteResponseValidationContext { + context: { + /** The request headers */ + headers: Dictionary; + /** The request path parameters */ + params: any; + /** The request query parameters */ + query: any; + /** The request payload parameters */ + payload: any; + + /** Partial request authentication information */ + auth: { + /** true if the request has been successfully authenticated, otherwise false. */ + isAuthenticated: boolean; + /** the credential object received during the authentication process. The presence of an object does not mean successful authentication. */ + credentials: AuthenticatedCredentials; + }; + } } /** @@ -1399,7 +1430,7 @@ export interface RouteSecurityConfigurationObject { * For context see RouteAdditionalConfigurationOptions > validate * TODO check JoiValidationObject is correct for "a Joi validation object" */ -export interface RouteValidationConfigurationObject { +export interface RouteValidationConfigurationObject { /** * validation rules for incoming request headers (note that all header field names must be in lowercase to match the headers normalized by node). Values allowed: * * true - any headers allowed (no validation performed). This is the default. @@ -1408,25 +1439,25 @@ export interface RouteValidationConfigurationObject { * * a validation function using the signature function(value, options, next) where: * * value - the object containing the request headers. * * options - the server validation options. - * * next(err, value) - the callback function called when validation is completed. + * * next(err, value) - the callback function called when validation is completed. `value` will be used as the `headers` value when `err` is falsy. If `next` is called with `undefined` or no arguments then the original value of `value` will be used. */ - headers?: boolean | JoiValidationObject | ValidationFunctionForRouteValidate; + headers?: boolean | JoiValidationObject | ValidationFunctionForRouteInput; /** * validation rules for incoming request path parameters, after matching the path against the route and extracting any parameters then stored in request.params. Values allowed: * Same as `headers`, see above. */ - params?: boolean | JoiValidationObject | ValidationFunctionForRouteValidate; + params?: boolean | JoiValidationObject | ValidationFunctionForRouteInput; /** * validation rules for an incoming request URI query component (the key-value part of the URI between '?' and '#'). The query is parsed into its individual key-value pairs and stored in request.query prior to validation. Values allowed: * Same as `headers`, see above. */ - query?: boolean | JoiValidationObject | ValidationFunctionForRouteValidate; + query?: boolean | JoiValidationObject | ValidationFunctionForRouteInput; /** * validation rules for an incoming request payload (request body). Values allowed: * Same as `headers`, see above, with the addition that: * * a Joi validation object. Note that empty payloads are represented by a null value. If a validation schema is provided and empty payload are supported, it must be explicitly defined by setting the payload value to a joi schema with null allowed (e.g. Joi.object({ /* keys here * / }).allow(null)). */ - payload?: boolean | JoiValidationObject | ValidationFunctionForRouteValidate; + payload?: boolean | JoiValidationObject | ValidationFunctionForRouteInput; /** an optional object with error fields copied into every validation error response. */ errorFields?: any; /** @@ -1437,24 +1468,46 @@ export interface RouteValidationConfigurationObject { * * a custom error handler function with the signature function(request, reply, source, error) see RouteFailFunction */ failAction?: 'error' | 'log' | 'ignore' | RouteFailFunction; - /** options to pass to Joi. Useful to set global options such as stripUnknown or abortEarly (the complete list is [available here](https://github.com/hapijs/joi/blob/master/API.md#validatevalue-schema-options-callback)). Defaults to no options. */ - options?: JoiValidationOptions; + /** + * options to pass to Joi. Useful to set global options such as stripUnknown or abortEarly (the complete list is [available here](https://github.com/hapijs/joi/blob/master/API.md#validatevalue-schema-options-callback)). + * If a custom validation function (see `headers`, `params`, `query`, or `payload` above) is defined then `options` can an arbitrary object that will be passed to this function as the second parameter. + * Defaults to no options. + */ + options?: ValidationOptions; } /** * a validation function using the signature function(value, options, next) where: - * For context see RouteAdditionalConfigurationOptions > validate (IRouteValidationConfigurationObject) + * For context see RouteAdditionalConfigurationOptions > validate (RouteValidationConfigurationObject) * - * TODO check `value: Response` is correct as it says "**the object containing** the response object." not just "the response object". - * TODO check `options: JoiValidationOptions` is correct - * TODO type of the returned value? * Also see ValidationFunctionForRouteResponse - * @param value - the object containing the request headers. + * @param value - the object containing the request headers, query, path params or payload. * @param options - the server validation options. - * @param next(err, value) - the callback function called when validation is completed. + * @param next([err, [value]]) - the callback function called when validation is completed. */ -export interface ValidationFunctionForRouteValidate { - (value: Response, options: JoiValidationOptions, next: ContinuationValueFunction): void; +export interface ValidationFunctionForRouteInput { + (value: any, options: RouteInputValidationContext & ValidationOptions, next: ContinuationValueFunction): void; +} + +/** + * A context for route input validation via a Joi schema or validation function. + * + * This object is merged with the route validation options and passed into the validation function. + * + * See https://github.com/hapijs/hapi/blob/v16.1.1/lib/validation.js#L122 + */ +export interface RouteInputValidationContext { + context: { + // These are only set when *not* validating the respective source (e.g. params, query and payload are set when validating headers): + // See https://github.com/hapijs/hapi/blob/v16.1.1/lib/validation.js#L132 + headers?: Dictionary; + params?: any; + query?: any; + payload?: any; + + /** The request authentication information */ + auth: RequestAuthenticationInformation; + } } /** @@ -1795,18 +1848,7 @@ export class Request extends Podium { /** application-specific state. Provides a safe place to store application data without potential conflicts with the framework. Should not be used by plugins which should use plugins[name]. */ app: any; /** authentication information */ - auth: { - /** true if the request has been successfully authenticated, otherwise false. */ - isAuthenticated: boolean; - /** the credential object received during the authentication process. The presence of an object does not mean successful authentication. */ - credentials: any; - /** an artifact object received from the authentication strategy and used in authentication-related actions. */ - artifacts: any; - /** the route authentication mode. */ - mode: string; - /** the authentication error is failed and mode set to 'try'. */ - error: Error; - }; + auth: RequestAuthenticationInformation; /** the connection the request was received by. */ connection: ServerConnection; /** the node domain object used to protect against exceptions thrown in extensions, handlers and route prerequisites. Can be used to manually bind callback functions otherwise bound to other domains. Set to null when the server useDomains options is false. */ @@ -1964,6 +2006,19 @@ export class Request extends Podium { // [index: string]: any; } +export interface RequestAuthenticationInformation { + /** true if the request has been successfully authenticated, otherwise false. */ + isAuthenticated: boolean; + /** the credential object received during the authentication process. The presence of an object does not mean successful authentication. */ + credentials: any; + /** an artifact object received from the authentication strategy and used in authentication-related actions. */ + artifacts: any; + /** the route authentication mode. */ + mode: string; + /** the authentication error is failed and mode set to 'try'. */ + error: Error; +} + export type HTTP_METHODS_PARTIAL_lowercase = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'options'; export type HTTP_METHODS_PARTIAL = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | HTTP_METHODS_PARTIAL_lowercase; export type HTTP_METHODS = 'HEAD' | 'head' | HTTP_METHODS_PARTIAL; @@ -2049,7 +2104,7 @@ export interface RequestHandler { /** * Used by server extension points - * err can be BoomError or Error that will be wrapped as a BoomError + * err can be `Boom` error or Error that will be wrapped as a `Boom` error * For source [See code](https://github.com/hapijs/hapi/blob/v16.1.1/lib/reply.js#L109-L118) * For source [See code](https://github.com/hapijs/hapi/blob/v16.1.1/lib/response.js#L60-L65) */ @@ -2061,7 +2116,9 @@ export interface ContinuationFunction { * TODO Can value be typed with a useful generic? */ export interface ContinuationValueFunction { - (err: Boom.BoomError, value: any): void; + (err: Boom.BoomError): void; + (err: null | undefined, value: any): void; + (): void; } /* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/types/hapi/test/route/validate.ts b/types/hapi/test/route/validate.ts index f160641016..f579bff8a8 100644 --- a/types/hapi/test/route/validate.ts +++ b/types/hapi/test/route/validate.ts @@ -13,8 +13,52 @@ const validate: Hapi.RouteValidationConfigurationObject = { query: { providerId: Joi.string(), }, + options: { + abortEarly: true, + }, }; -const config: Hapi.RouteAdditionalConfigurationOptions = { +let config: Hapi.RouteAdditionalConfigurationOptions = { validate, + response: { + schema: Joi.object(), + }, +}; + +interface CustomValidationOptions { + myOption: number; +} + +const inputValidationFunction: Hapi.ValidationFunctionForRouteInput = (value, options, next) => { + options.myOption; // check custom options + options.context.auth.artifacts; // check context + next(null, value); // check with value + next(); // check without value +}; + +const validateWithFunctions: Hapi.RouteValidationConfigurationObject = { + params: inputValidationFunction, + headers: inputValidationFunction, + payload: inputValidationFunction, + query: inputValidationFunction, + options: { + myOption: 18 + } +}; + +const responseValidationFunction: Hapi.ValidationFunctionForRouteResponse = (value, options, next) => { + options.myOption; // check custom options + options.context.auth.isAuthenticated; // check context + next(null, value); // check with value + next(); // check without value +}; + +config = { + validate: validateWithFunctions, + response: >{ + schema: responseValidationFunction, + options: { + myOption: 18 + } + } }; diff --git a/types/inert/index.d.ts b/types/inert/index.d.ts index a54841eac7..28e4efbe58 100644 --- a/types/inert/index.d.ts +++ b/types/inert/index.d.ts @@ -2,6 +2,7 @@ // Project: https://github.com/hapijs/inert/ // Definitions by: Steve Ognibene , AJP // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.3 import * as hapi from 'hapi'; diff --git a/types/nes/index.d.ts b/types/nes/index.d.ts index 87e428bc29..aab6827ed7 100644 --- a/types/nes/index.d.ts +++ b/types/nes/index.d.ts @@ -2,6 +2,7 @@ // Project: https://github.com/hapijs/nes // Definitions by: Ivo Stratev // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.3 /* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/types/vision/index.d.ts b/types/vision/index.d.ts index 2b0164cc0f..4257e0f7b9 100644 --- a/types/vision/index.d.ts +++ b/types/vision/index.d.ts @@ -2,7 +2,7 @@ // Project: https://github.com/hapijs/vision // Definitions by: Jason Swearingen , AJP // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.2 +// TypeScript Version: 2.3 import * as Hapi from 'hapi';