import { Lifecycle , Request , ResponseToolkit , Server , ServerOptions , Util } from '@hapi/hapi' ; import Hoek from '@hapi/hoek' ; import { ServerOptions as TLSOptions } from 'https' ; import { ValidationError } from 'joi' ; import uuid from 'uuid' ; import { HttpConfig } from './http_config' ; import { validateObject } from './prototype_pollution' ; /**
 * Converts OpenSearch Dashboards `HttpConfig` into `ServerOptions` that are accepted by the Hapi server.
 */ export function getServerOptions ( config , { configureTLS = true } = { } ) { const options = { host : config . host , port : config . port , routes : { cache : { privacy : 'private' , otherwise : 'private, no-cache, no-store, must-revalidate' , } , cors : config . cors , payload : { maxBytes : config . maxPayload . getValueInBytes ( ) , } , validate : { failAction : defaultValidationErrorHandler , options : { abortEarly : false , } , payload : ( value ) => Promise . resolve ( validateObject ( value ) ) , } , } , state : { strictHeader : false , isHttpOnly : true , isSameSite : false , } , } ; if ( configureTLS && config . ssl . enabled ) { const ssl = config . ssl ; const tlsOptions = { ca : ssl . certificateAuthorities , cert : ssl . certificate , ciphers : config . ssl . cipherSuites . join ( ':' ) , honorCipherOrder : true , key : ssl . key , passphrase : ssl . keyPassphrase , secureOptions : ssl . getSecureOptions ( ) , requestCert : ssl . requestCert , rejectUnauthorized : ssl . rejectUnauthorized , } ; options . tls = tlsOptions ; } return options ; } export function getListenerOptions ( config ) { return { keepaliveTimeout : config . keepaliveTimeout , socketTimeout : config . socketTimeout , } ; } interface ListenerOptions { keepaliveTimeout : number ; socketTimeout : number ; } export function createServer ( serverOptions , listenerOptions ) { const server = new Server ( serverOptions ) ; server . listener . headersTimeout = listenerOptions . keepaliveTimeout + 2 * server . listener . headersTimeout ; server . listener . keepAliveTimeout = listenerOptions . keepaliveTimeout ; server . listener . setTimeout ( listenerOptions . socketTimeout ) ; server . listener . on ( 'timeout' , ( socket ) => { socket . destroy ( ) ; } ) ; server . listener . on ( 'clientError' , ( err , socket ) => { if ( socket . writable ) { socket . end ( Buffer . from ( 'HTTP/1.1 400 Bad Request\r\n\r\n' , 'ascii' ) ) ; } else { socket . destroy ( err ) ; } } ) ; return server ; } /**
 * Hapi extends the ValidationError interface to add this output key with more data.
 */ export interface HapiValidationError extends ValidationError { output : { statusCode : number ; headers : Util . Dictionary < string | string [ ] > ; payload : { statusCode : number ; error : string ; message ? : string ; validation : { source : string ; keys : string [ ] ; } ; } ; } ; } /**
 * Used to replicate Hapi v16 and below's validation responses. Should be used in the routes.validate.failAction key.
 */ export function defaultValidationErrorHandler ( request , h , err ? ) { if ( err && err . name === 'ValidationError' && err . hasOwnProperty ( 'output' ) ) { const validationError = err as HapiValidationError ; const validationKeys = [ ] ; validationError . details . forEach ( ( detail ) => { if ( detail . path . length > 0 ) { validationKeys . push ( Hoek . escapeHtml ( detail . path . join ( '.' ) ) ) ; } else { validationKeys . push ( 'value' ) ; } } ) ; validationError . output . payload . validation . keys = validationKeys ; } throw err ; } export function getRequestId ( request , options ) { return options . allowFromAnyIp || ( request . raw . req . socket ?. remoteAddress && options . ipAllowlist . includes ( request . raw . req . socket . remoteAddress ) ) ? request . headers [ 'x-opaque-id' ] ?? uuid . v4 ( ) : uuid . v4 ( ) ; }