Skip to content

Commit 80fcc95

Browse files
authored
Merge branch 'dev' into dependabot/npm_and_yarn/QualityControl/sequelize-6.37.8
2 parents cdd7fa3 + 0fa2560 commit 80fcc95

21 files changed

Lines changed: 1637 additions & 1534 deletions

File tree

Configuration/webapp/package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Control/lib/controllers/Lock.controller.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ class LockController {
7777
try {
7878
this._lockService.takeLock(detector, user, shouldForce);
7979
} catch (error) {
80-
console.error(error);
80+
this._logger.errorMessage(error, {level: LogLevel.DEVELOPER, facility: LOG_FACILITY});
8181
}
8282
});
8383
} else {
8484
this._lockService.takeLock(detectorId, user, shouldForce);
85-
}
85+
}
8686
res.status(200).json(this._lockService.locksByDetectorToJSON());
8787
} else if (action.toLocaleUpperCase() === DetectorLockAction.RELEASE) {
8888
if (detectorId === DetectorId.ALL) {
@@ -92,7 +92,7 @@ class LockController {
9292
try {
9393
this._lockService.releaseLock(detector, user, shouldForce);
9494
} catch (error) {
95-
console.error(error);
95+
this._logger.errorMessage(error, {level: LogLevel.DEVELOPER, facility: LOG_FACILITY});
9696
}
9797
});
9898
} else {

Control/lib/services/Lock.service.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,8 @@ class LockService {
8787
throw new NotFoundError(`Detector ${detectorName} not found in the list of detectors`);
8888
} else if (lock.isTaken()) {
8989
if (!lock.isOwnedBy(user) && !shouldForce) {
90-
throw new UnauthorizedAccessError(
91-
`Unauthorized TAKE action for lock of detector ${detectorName} by user ${user.fullName}`
92-
);
90+
throw new UnauthorizedAccessError(`Unauthorized TAKE action for lock ${detectorName} `
91+
+ `by ${ user.fullName } while lock is held by ${ lock.owner.fullName }`);
9392
}
9493
if (lock.isOwnedBy(user)) {
9594
return this._locksByDetector;
@@ -117,9 +116,8 @@ class LockService {
117116
} else if (lock.isFree()) {
118117
return this._locksByDetector;
119118
} else if (!lock.isOwnedBy(user) && !shouldForce) {
120-
throw new UnauthorizedAccessError(
121-
`Unauthorized RELEASE action for lock of detector ${detectorName} by user ${user.fullName}`
122-
);
119+
throw new UnauthorizedAccessError(`Unauthorized RELEASE action for lock ${detectorName} `
120+
+ `by ${ user.fullName } while lock is held by ${ lock.owner.fullName }`);
123121
}
124122
this._locksByDetector[detectorName].release();
125123

Control/test/api/lock/api-put-locks.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ describe(`'API - PUT - /locks/:action/:detectorId' test suite`, () => {
8282
await request(`${TEST_URL}/api/locks`)
8383
.put(`/${DetectorLockAction.TAKE}/MID?token=${ADMIN_TEST_TOKEN}`)
8484
.expect(403, {
85-
message: 'Unauthorized TAKE action for lock of detector MID by user Admin User',
85+
message: 'Unauthorized TAKE action for lock MID by Admin User while lock is held by Global User',
8686
title: 'Unauthorized Access',
8787
status: 403,
8888
});
@@ -172,7 +172,7 @@ describe(`'API - PUT - /locks/:action/:detectorId' test suite`, () => {
172172
await request(`${TEST_URL}/api/locks`)
173173
.put(`/${DetectorLockAction.RELEASE}/MID?token=${DET_MID_TEST_TOKEN}`)
174174
.expect(403, {
175-
message: 'Unauthorized RELEASE action for lock of detector MID by user Detector User',
175+
message: 'Unauthorized RELEASE action for lock MID by Detector User while lock is held by Admin User',
176176
status: 403,
177177
title: 'Unauthorized Access',
178178
});

Control/test/lib/controllers/mocha-lock.controller.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ describe(`'LockController' test suite`, () => {
115115
}, res);
116116
assert.ok(res.status.calledWith(403));
117117
assert.ok(res.json.calledWith({
118-
message: 'Unauthorized TAKE action for lock of detector ABC by user NotAnonymous',
118+
message: 'Unauthorized TAKE action for lock ABC by NotAnonymous while lock is held by Anonymous',
119119
status: 403,
120120
title: 'Unauthorized Access',
121121
}));
@@ -134,7 +134,7 @@ describe(`'LockController' test suite`, () => {
134134
}, res);
135135
assert.ok(res.status.calledWith(403));
136136
assert.ok(res.json.calledWith({
137-
message: 'Unauthorized RELEASE action for lock of detector ABC by user NotAnonymous',
137+
message: 'Unauthorized RELEASE action for lock ABC by NotAnonymous while lock is held by Anonymous',
138138
title: 'Unauthorized Access',
139139
status: 403,
140140
}));

Control/test/lib/services/mocha-lock.service.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe(`'LockService' test suite`, () => {
7070
it('should throw error when a user attempts to take a lock that is already held by another user', () => {
7171
assert.throws(
7272
() => lockService.takeLock('ABC', userB),
73-
new UnauthorizedAccessError(`Unauthorized TAKE action for lock of detector ABC by user userB`)
73+
new UnauthorizedAccessError('Unauthorized TAKE action for lock ABC by userB while lock is held by userA')
7474
);
7575
});
7676

@@ -98,7 +98,7 @@ describe(`'LockService' test suite`, () => {
9898
lockService.takeLock('ABC', userA);
9999
assert.throws(
100100
() => lockService.releaseLock('ABC', userB),
101-
new UnauthorizedAccessError(`Unauthorized RELEASE action for lock of detector ABC by user userB`)
101+
new UnauthorizedAccessError('Unauthorized RELEASE action for lock ABC by userB while lock is held by userA')
102102
);
103103
});
104104

Framework/Backend/db/mysql.js

Lines changed: 0 additions & 117 deletions
This file was deleted.

Framework/Backend/http/server.js

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)