@@ -46,6 +46,10 @@ class HttpServer {
4646
4747 this . app = express ( ) ;
4848
49+ if ( process . env . NODE_ENV === 'production' ) {
50+ this . app . set ( 'trust proxy' , true ) ;
51+ }
52+
4953 this . configureHelmet ( httpConfig ) ;
5054
5155 this . o2TokenService = new O2TokenService ( jwtConfig ) ;
@@ -78,6 +82,13 @@ class HttpServer {
7882 this . listen ( ) ;
7983 }
8084
85+ /**
86+ * Map to keep track of similar logs by key
87+ * @key {string} - combination of IP address, error name and message
88+ * @value {object.count} - count of occurrences of the same error by the same key
89+ * @value {object.lastLoggedTimestamp} - timestamp of the last log of the same error by the same key
90+ */
91+ this . _jwtErrorsByIp = new Map ( ) ;
8192 this . logger = LogManager . getLogger ( `${ process . env . npm_config_log_label ?? 'framework' } /server` ) ;
8293 }
8394
@@ -303,7 +314,7 @@ class HttpServer {
303314 * Adds POST route using express router, the path will be prefix with "/api"
304315 * By default verifies JWT token unless public options is provided
305316 * @param {string } path - path that the callback will be bound to
306- * @param {function } callbacks - method that handles request and response: function(req, res);
317+ * @param {void } callbacks - method that handles request and response: function(req, res);
307318 * token should be passed as req.query.token;
308319 * more on req: https://expressjs.com/en/api.html#req
309320 * more on res: https://expressjs.com/en/api.html#res
@@ -318,7 +329,7 @@ class HttpServer {
318329 * Adds PUT route using express router, the path will be prefix with "/api"
319330 * By default verifies JWT token unless public options is provided
320331 * @param {string } path - path that the callback will be bound to
321- * @param {function } callbacks - method that handles request and response: function(req, res);
332+ * @param {void } callbacks - method that handles request and response: function(req, res);
322333 * token should be passed as req.query.token;
323334 * more on req: https://expressjs.com/en/api.html#req
324335 * more on res: https://expressjs.com/en/api.html#res
@@ -333,7 +344,7 @@ class HttpServer {
333344 * Adds PATCH route using express router, the path will be prefix with "/api"
334345 * By default verifies JWT token unless public options is provided
335346 * @param {string } path - path that the callback will be bound to
336- * @param {function } callbacks - method that handles request and response: function(req, res);
347+ * @param {void } callbacks - method that handles request and response: function(req, res);
337348 * token should be passed as req.query.token;
338349 * more on req: https://expressjs.com/en/api.html#req
339350 * more on res: https://expressjs.com/en/api.html#res
@@ -348,7 +359,7 @@ class HttpServer {
348359 * Adds DELETE route using express router, the path will be prefix with "/api"
349360 * By default verifies JWT token unless public options is provided
350361 * @param {string } path - path that the callback will be bound to
351- * @param {function } callbacks - method that handles request and response: function(req, res);
362+ * @param {void } callbacks - method that handles request and response: function(req, res);
352363 * token should be passed as req.query.token;
353364 * more on req: https://expressjs.com/en/api.html#req
354365 * more on res: https://expressjs.com/en/api.html#res
@@ -503,32 +514,60 @@ class HttpServer {
503514 return this . server ;
504515 }
505516
517+ /**
518+ * Logs a errors with throttling by IP address every 5 minutes with the first error logged immediately
519+ * and rest of occurrences after 5 minutes with number of occurrences.
520+ * @param {string } ip - IP address of the request
521+ * @param {string } name - Error name
522+ * @param {string } message - Error message
523+ * @private
524+ */
525+ _logErrorWithThrottling ( ip , name , message ) {
526+ const now = Date . now ( ) ;
527+ const compositeKey = `${ ip } /${ name } /${ message } ` ;
528+
529+ if ( ! this . _jwtErrorsByIp . has ( compositeKey ) ) {
530+ this . logger . errorMessage ( `${ name } : ${ message } (IP: ${ ip } )` ) ;
531+ this . _jwtErrorsByIp . set ( compositeKey , {
532+ count : 1 ,
533+ lastLoggedTimestamp : now ,
534+ } ) ;
535+ } else {
536+ const fiveMinutes = 5 * 60 * 1000 ;
537+ const lastErrorData = this . _jwtErrorsByIp . get ( compositeKey ) ;
538+ const timeSinceLastLog = now - lastErrorData . lastLoggedTimestamp ;
539+
540+ if ( timeSinceLastLog >= fiveMinutes ) {
541+ if ( lastErrorData . count > 1 ) {
542+ this . logger . errorMessage ( `${ name } : ${ message } (IP: ${ ip } ) (occurrences: ${ lastErrorData . count } )` ) ;
543+ } else {
544+ this . logger . errorMessage ( `${ name } : ${ message } (IP: ${ ip } )` ) ;
545+ }
546+ lastErrorData . count = 1 ;
547+ lastErrorData . lastLoggedTimestamp = now ;
548+ } else {
549+ lastErrorData . count ++ ;
550+ }
551+ }
552+ }
553+
506554 /**
507555 * Verifies JWT token synchronously.
508556 * @todo use promises or generators to call it asynchronously!
509557 * @param {object } req - HTTP request
510558 * @param {object } res - HTTP response
511- * @param {function } next - passes control to next matching route
559+ * @param {void } next - passes control to next matching route
512560 */
513561 jwtVerify ( req , res , next ) {
514562 try {
515563 this . jwtAuthenticate ( req ) ;
516564 } catch ( { name, message } ) {
517- this . logger . errorMessage ( `${ name } : ${ message } ` ) ;
518-
519- const response = { error : '403 - Json Web Token Error' } ;
520-
521- // Allow for a custom message for known error messages
522- switch ( message ) {
523- case 'jwt must be provided' :
524- response . message = 'You must provide a JWT token' ;
525- break ;
526- default :
527- response . message = 'Invalid JWT token provided' ;
528- break ;
529- }
565+ this . _logErrorWithThrottling ( req . ip , name , message ) ;
530566
531- res . status ( 403 ) . json ( response ) ;
567+ res . status ( 403 ) . json ( {
568+ error : '403 - Json Web Token Error' ,
569+ message,
570+ } ) ;
532571 return ;
533572 }
534573
@@ -540,6 +579,7 @@ class HttpServer {
540579 *
541580 * @param {Request } req - Express Request object
542581 * @return {void } resolves once the request is filled with authentication, and reject if jwt verification failed
582+ * @throws {UnauthorizedAccessError } if token is invalid, expired or secret is wrong
543583 */
544584 jwtAuthenticate ( req ) {
545585 const data = this . o2TokenService . verify ( req . query . token ) ;
0 commit comments