@@ -20,26 +20,59 @@ export enum Response {
2020 pong = 0 ,
2121}
2222
23- const handleClient = async ( stream : Stream , processor : Processor , logger ?: Logger ) : Promise < void > => {
24- const credentialsLength = await stream . readUint8 ( ) ;
25- const credentials = ( await stream . readExact ( credentialsLength ) ) . toString ( 'ascii' ) ;
23+ const handleClient = async (
24+ stream : Stream ,
25+ processor : Processor ,
26+ logger ?: Logger ,
27+ authTimeout = 1_000 ,
28+ idleTimeout = 30_000 ,
29+ ) : Promise < void > => {
30+ // We handle the auth timeout via race instead of a socket timeout. This forces the client to complete
31+ // authentication in a fixed amount of time. Otherwise, a malicious client could send individual bytes to keep the
32+ // connection open for up 256 times the socket timeout.
33+ let timeout ;
34+
35+ const clientId = await Promise . race ( [
36+ new Promise < null > ( resolve => {
37+ timeout = setTimeout ( ( ) => {
38+ resolve ( null ) ;
39+ } , authTimeout ) ;
40+ } ) ,
41+ ( async ( ) => {
42+ const credentialsLength = await stream . readUint8 ( ) ;
43+ const credentials = ( await stream . readExact ( credentialsLength ) ) . toString ( 'ascii' ) ;
44+
45+ if ( ! credentials . includes ( ':' ) ) {
46+ logger ?. info ( 'Malformed credentials' ) ;
47+ stream . writeUint8 ( Response . invalidAuth ) ;
48+ return null ;
49+ }
2650
27- if ( ! credentials . includes ( ':' ) ) {
28- logger ?. info ( 'Malformed credentials' ) ;
29- stream . writeUint8 ( Response . invalidAuth ) ;
30- return ;
31- }
51+ const [ clientId , secret ] = credentials . split ( ':' , 2 ) ;
52+ const authResult = await processor . authenticate ( clientId , secret ) ;
53+
54+ if ( ! authResult ) {
55+ logger ?. info ( `Unknown client ID "${ clientId } " or invalid secret` ) ;
56+ stream . writeUint8 ( Response . invalidAuth ) ;
57+ return null ;
58+ }
59+
60+ stream . writeUint8 ( Response . validAuth ) ;
61+ return clientId ;
62+ } ) ( ) ,
63+ ] ) ;
3264
33- const [ clientId , secret ] = credentials . split ( ':' , 2 ) ;
34- const authResult = await processor . authenticate ( clientId , secret ) ;
65+ clearTimeout ( timeout ) ;
3566
36- if ( ! authResult ) {
37- logger ?. info ( `Unknown client ID "${ clientId } " or invalid secret` ) ;
38- stream . writeUint8 ( Response . invalidAuth ) ;
67+ if ( ! clientId ) {
3968 return ;
4069 }
4170
42- stream . writeUint8 ( Response . validAuth ) ;
71+ // At this point we can be certain that it's not a random client anymore, so further inactivity is handled via
72+ // socket timeouts.
73+ stream . setTimeout ( idleTimeout , ( ) => {
74+ stream . close ( ) ;
75+ } ) ;
4376
4477 while ( ! stream . isClosed ( ) ) {
4578 const commandCode = await stream . readUint8 ( ) ;
0 commit comments