@@ -20,26 +20,59 @@ export enum Response {
20
20
pong = 0 ,
21
21
}
22
22
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
+ }
26
50
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
+ ] ) ;
32
64
33
- const [ clientId , secret ] = credentials . split ( ':' , 2 ) ;
34
- const authResult = await processor . authenticate ( clientId , secret ) ;
65
+ clearTimeout ( timeout ) ;
35
66
36
- if ( ! authResult ) {
37
- logger ?. info ( `Unknown client ID "${ clientId } " or invalid secret` ) ;
38
- stream . writeUint8 ( Response . invalidAuth ) ;
67
+ if ( ! clientId ) {
39
68
return ;
40
69
}
41
70
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
+ } ) ;
43
76
44
77
while ( ! stream . isClosed ( ) ) {
45
78
const commandCode = await stream . readUint8 ( ) ;
0 commit comments