@@ -56,20 +56,15 @@ const clientCertificateDetailsSchema = object({
5656} ) . test (
5757 'all-or-nothing-cert-details' ,
5858 'If any certificate detail is provided, all are required.' ,
59- function ( value , context ) {
59+ function ( value ) {
6060 if ( ! value ) {
6161 return true ;
6262 }
6363
64- const {
65- client_ca_certificate,
66- client_certificate,
67- client_private_key,
68- tls_hostname,
69- } = value ;
64+ const { client_ca_certificate, client_certificate, client_private_key } =
65+ value ;
7066
7167 const fields = [
72- tls_hostname ,
7368 client_ca_certificate ,
7469 client_certificate ,
7570 client_private_key ,
@@ -82,39 +77,30 @@ const clientCertificateDetailsSchema = object({
8277 }
8378
8479 const errors : ValidationError [ ] = [ ] ;
85- if ( ! hasValue ( tls_hostname ) ) {
86- errors . push (
87- context . createError ( {
88- path : `${ this . path } .tls_hostname` ,
89- message :
90- 'TLS Hostname is required when other Client Certificate details are provided.' ,
91- } ) ,
92- ) ;
93- }
9480 if ( ! hasValue ( client_ca_certificate ) ) {
9581 errors . push (
96- context . createError ( {
82+ this . createError ( {
9783 path : `${ this . path } .client_ca_certificate` ,
9884 message :
99- 'CA Certificate is required when other Client Certificate details are provided.' ,
85+ 'CA Certificate is required when other client certificate details are provided.' ,
10086 } ) ,
10187 ) ;
10288 }
10389 if ( ! hasValue ( client_certificate ) ) {
10490 errors . push (
105- context . createError ( {
91+ this . createError ( {
10692 path : `${ this . path } .client_certificate` ,
10793 message :
108- 'Client Certificate is required when other Client Certificate details are provided.' ,
94+ 'Client Certificate is required when other client certificate details are provided.' ,
10995 } ) ,
11096 ) ;
11197 }
11298 if ( ! hasValue ( client_private_key ) ) {
11399 errors . push (
114- context . createError ( {
100+ this . createError ( {
115101 path : `${ this . path } .client_private_key` ,
116102 message :
117- 'Client Key is required when other Client Certificate details are provided.' ,
103+ 'Client Key is required when other client certificate details are provided.' ,
118104 } ) ,
119105 ) ;
120106 }
@@ -123,27 +109,91 @@ const clientCertificateDetailsSchema = object({
123109 } ,
124110) ;
125111
112+ const forbiddenCustomHeaderNames = [
113+ 'content-type' ,
114+ 'encoding' ,
115+ 'authorization' ,
116+ 'host' ,
117+ 'akamai' ,
118+ ] ;
119+
126120const customHeaderSchema = object ( {
127121 name : string ( )
128122 . max ( maxLength , maxLengthMessage )
129- . required ( 'Custom Header Name is required.' ) ,
123+ . required ( 'Custom Header Name is required.' )
124+ . test (
125+ 'non-empty-name' ,
126+ 'Custom Header Name cannot be empty or whitespace only.' ,
127+ ( value ) => hasValue ( value ) ,
128+ )
129+ . test (
130+ 'forbidden-custom-header-name' ,
131+ 'This header name is not allowed.' ,
132+ ( value ) =>
133+ ! forbiddenCustomHeaderNames . includes ( value . trim ( ) . toLowerCase ( ) ) ,
134+ ) ,
130135 value : string ( )
131136 . max ( maxLength , maxLengthMessage )
132- . required ( 'Custom Header Value is required' ) ,
137+ . required ( 'Custom Header Value is required.' )
138+ . test (
139+ 'non-empty-value' ,
140+ 'Custom Header Value cannot be empty or whitespace only.' ,
141+ ( value ) => hasValue ( value ) ,
142+ ) ,
133143} ) ;
134144
145+ const urlRgx = / ^ ( h t t p s ? : \/ \/ ) ? ( w w w \. ) ? [ a - z A - Z 0 - 9 - ] + ( \. [ a - z A - Z ] + ) + ( \/ \S * ) ? $ / ;
146+
135147const customHTTPSDetailsSchema = object ( {
136148 authentication : authenticationSchema . required ( ) ,
137149 client_certificate_details : clientCertificateDetailsSchema . optional ( ) ,
138150 content_type : string ( )
139151 . oneOf ( [ 'application/json' , 'application/json; charset=utf-8' ] )
140152 . nullable ( )
141153 . optional ( ) ,
142- custom_headers : array ( ) . of ( customHeaderSchema ) . min ( 1 ) . optional ( ) ,
154+ custom_headers : array ( )
155+ . of ( customHeaderSchema )
156+ . min ( 1 )
157+ . optional ( )
158+ . test (
159+ 'unique-header-names' ,
160+ 'Custom Header Names must be unique.' ,
161+ function ( headers ) {
162+ if ( ! headers || headers . length === 0 ) {
163+ return true ;
164+ }
165+
166+ const seenNames = new Set < string > ( ) ;
167+ const errors : ValidationError [ ] = [ ] ;
168+
169+ headers . forEach ( ( header , index ) => {
170+ const trimmedName = header ?. name ?. trim ( ) . toLowerCase ( ) ;
171+ if ( ! trimmedName ) {
172+ return ;
173+ }
174+
175+ if ( seenNames . has ( trimmedName ) ) {
176+ errors . push (
177+ this . createError ( {
178+ path : `${ this . path } [${ index } ].name` ,
179+ message : 'Custom Header Name must be unique.' ,
180+ } ) ,
181+ ) ;
182+ } else {
183+ seenNames . add ( trimmedName ) ;
184+ }
185+ } ) ;
186+
187+ return errors . length === 0 || new ValidationError ( errors ) ;
188+ } ,
189+ ) ,
143190 data_compression : string ( ) . oneOf ( [ 'gzip' , 'None' ] ) . required ( ) ,
144191 endpoint_url : string ( )
145192 . max ( maxLength , maxLengthMessage )
146- . required ( 'Endpoint URL is required.' ) ,
193+ . required ( 'Endpoint URL is required.' )
194+ . test ( 'is-valid-url' , 'Endpoint URL must be a valid URL.' , ( value ) =>
195+ urlRgx . test ( value ) ,
196+ ) ,
147197} ) ;
148198
149199const hostRgx =
@@ -298,7 +348,7 @@ const detailsShouldNotExistOrBeNull = (schema: MixedSchema) =>
298348
299349const streamSchemaBase = object ( {
300350 label : string ( )
301- . min ( 3 , 'Stream name must have at least 3 characters' )
351+ . min ( 3 , 'Stream name must have at least 3 characters. ' )
302352 . max ( maxLength , maxLengthMessage )
303353 . required ( 'Stream name is required.' ) ,
304354 status : mixed < 'active' | 'inactive' | 'provisioning' > ( ) . oneOf ( [
@@ -338,7 +388,7 @@ export const updateStreamSchema = streamSchemaBase
338388 return detailsShouldNotExistOrBeNull ( mixed ( ) ) ;
339389 } ) ,
340390 } )
341- . noUnknown ( 'Object contains unknown fields' ) ;
391+ . noUnknown ( 'Object contains unknown fields. ' ) ;
342392
343393export const streamAndDestinationFormSchema = object ( {
344394 stream : streamSchemaBase . shape ( {
@@ -349,7 +399,7 @@ export const streamAndDestinationFormSchema = object({
349399 otherwise : ( schema ) =>
350400 schema
351401 . nullable ( )
352- . equals ( [ null ] , 'Details must be null for audit_logs type' ) ,
402+ . equals ( [ null ] , 'Details must be null for audit_logs type. ' ) ,
353403 } ) as Schema < InferType < typeof streamDetailsSchema > | null > ,
354404 } ) ,
355405 destination : destinationFormSchema . defined ( ) . when ( 'stream.destinations' , {
0 commit comments