Skip to content

Commit 6976eae

Browse files
authored
Merge pull request #1978 from NMDSdevopsServiceAdm/test
Merge Test to Live for Release 3.4
2 parents fde4e24 + d21c9d8 commit 6976eae

File tree

23 files changed

+8922
-6532
lines changed

23 files changed

+8922
-6532
lines changed

package-lock.json

+8,428-6,402
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"axios": "^0.19.0",
4444
"bcrypt-nodejs": "0.0.3",
4545
"c8": "^3.2.1",
46+
"cheerio": "^1.0.0-rc.3",
4647
"concurrently": "^4.1.2",
4748
"convict": "^4.4.1",
4849
"cookie-parser": "^1.4.3",
@@ -69,6 +70,7 @@
6970
"passport-jwt": "^4.0.0",
7071
"pg": "^7.12.1",
7172
"pg-hstore": "^2.3.2",
73+
"rate-limiter-flexible": "^1.3.2",
7274
"rfr": "^1.2.3",
7375
"rxjs": "^6.5.2",
7476
"rxjs-compat": "^6.5.2",

server/config/config.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,24 @@ const config = convict({
421421
env: 'TEST_ADMINPASSWORD'
422422
}
423423
}
424-
}
424+
},
425+
rateLimiting: {
426+
points: {
427+
doc: 'How many times you want allow a user to visit sensitive endpoints',
428+
format: 'int',
429+
default: 60
430+
},
431+
duration: {
432+
doc: 'How long a peroid you want to monitor a user visiting endpoints',
433+
format: 'int',
434+
default: 1 * 60 * 60 // 1 hour
435+
},
436+
table: {
437+
doc: 'The table name you want to create/update to log user requests',
438+
format: String,
439+
default: 'SensitiveSessions'
440+
}
441+
}
425442
});
426443

427444
// Load environment dependent configuration

server/models/classes/user.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,7 @@ class User {
952952
include: [
953953
{
954954
model: models.login,
955-
attributes: ['username', 'lastLogin'],
955+
attributes: ['username', 'lastLogin', 'status'],
956956
},
957957
],
958958
attributes: [
@@ -981,11 +981,12 @@ class User {
981981
updated: thisUser.updated.toJSON(),
982982
updatedBy: thisUser.updatedBy,
983983
isPrimary: thisUser.isPrimary ? true : false,
984+
status: thisUser.login && thisUser.login.status ? thisUser.login.status : null
984985
});
985986
});
986987

987988
allUsers = allUsers.map(user => {
988-
return Object.assign(user, { status: user.username == null ? 'Pending' : 'Active' });
989+
return Object.assign(user, { status: user.username == null ? 'Pending' : (user.status !== null)? user.status: 'Active' });
989990
});
990991

991992
allUsers.sort((a, b) => {

server/routes/admin/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const search = require('./search');
77
const recalcWdf = require('./recalcWdf');
88
const registrations = require('./registrations');
99
const approval = require('./approval');
10+
const unlockAccount = require('./unlock-account');
1011

1112
// middleware authentication - only role=Admin from here on in
1213
router.use('/', isAdmin);
@@ -15,6 +16,7 @@ router.use('/search', search);
1516
router.use('/recalcWdf', recalcWdf);
1617
router.use('/registrations', registrations);
1718
router.use('/approval', approval);
19+
router.use('/unlock-account', unlockAccount);
1820

1921
router.route('/').post(async function (req, res) {
2022
return res.status(200).send({success: "from admin"});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// default route for admin/approval
2+
const express = require('express');
3+
const router = express.Router();
4+
const models = require('../../../models');
5+
6+
const unlockAccount = async (req, res) => {
7+
// parse input - escaped to prevent SQL injection
8+
9+
// Sanatize username
10+
if(req.body.username){
11+
const username = escape(req.body.username.toLowerCase());
12+
13+
try {
14+
// Find the user matching the username
15+
const login = await models.login.findOne({
16+
where: {
17+
username: username
18+
},
19+
attributes: ['id', 'username'],
20+
});
21+
// Make sure we have the matching user
22+
if ((login && login.id) && (username === login.username)) {
23+
try {
24+
const updateduser = await login.update({
25+
isActive: true,
26+
invalidAttempt: 9,
27+
status: null
28+
});
29+
if (updateduser) {
30+
res.status(200);
31+
return res.json({status: '0', message: 'User has been set as active'})
32+
} else {
33+
res.status(503)
34+
return res.send();
35+
}
36+
} catch(error) {
37+
console.error(error);
38+
res.status(503)
39+
return res.send();
40+
}
41+
} else {
42+
res.status(400);
43+
return res.send();
44+
}
45+
} catch (error) {
46+
console.log(error);
47+
}
48+
} else {
49+
res.status(400)
50+
return res.send();
51+
}
52+
};
53+
54+
router.route('/').post(async (req, res) => {
55+
await unlockAccount(req, res);
56+
});
57+
58+
module.exports = router;
59+
module.exports.unlockAccount = unlockAccount;

server/routes/login.js

+27-9
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,9 @@ router.post('/', async (req, res) => {
3838
try {
3939
let establishmentUser = givenEstablishmentUid === null ? await models.login.findOne({
4040
where: {
41-
username: givenUsername,
42-
isActive:true
41+
username: givenUsername
4342
},
44-
attributes: ['id', 'username', 'isActive', 'invalidAttempt', 'registrationId', 'firstLogin', 'Hash', 'lastLogin', 'tribalHash', 'tribalSalt', 'agreedUpdatedTerms'],
43+
attributes: ['id', 'username', 'isActive', 'invalidAttempt', 'registrationId', 'firstLogin', 'Hash', 'lastLogin', 'tribalHash', 'tribalSalt', 'agreedUpdatedTerms', 'status'],
4544
include: [ {
4645
model: models.user,
4746
attributes: ['id', 'uid', 'FullNameValue', 'EmailValue', 'isPrimary', 'establishmentId', "UserRoleValue", 'tribalId'],
@@ -61,10 +60,9 @@ router.post('/', async (req, res) => {
6160
// before returning error, check to see if this is a superadmin user with a given establishment UID, to be assumed as their "logged in session" primary establishment
6261
establishmentUser = await models.login.findOne({
6362
where: {
64-
username: givenUsername,
65-
isActive:true,
63+
username: givenUsername
6664
},
67-
attributes: ['id', 'username', 'isActive', 'invalidAttempt', 'registrationId', 'firstLogin', 'Hash', 'lastLogin', 'tribalHash', 'tribalSalt', 'agreedUpdatedTerms'],
65+
attributes: ['id', 'username', 'isActive', 'invalidAttempt', 'registrationId', 'firstLogin', 'Hash', 'lastLogin', 'tribalHash', 'tribalSalt', 'agreedUpdatedTerms', 'status'],
6866
include: [ {
6967
model: models.user,
7068
attributes: ['id', 'uid', 'FullNameValue', 'EmailValue', 'isPrimary', 'establishmentId', "UserRoleValue", 'tribalId'],
@@ -109,6 +107,23 @@ router.post('/', async (req, res) => {
109107
}
110108
}
111109

110+
//check weather posted user is locked or pending
111+
if(establishmentUser){
112+
if(!establishmentUser.isActive && establishmentUser.status === 'Locked'){
113+
//check for locked status, if locked then return with 409 error
114+
console.error(`POST .../login failed: User status is locked`);
115+
return res.status(409).send({
116+
message: 'Authentication failed.',
117+
});
118+
} else if(!establishmentUser.isActive && establishmentUser.status === 'PENDING'){
119+
//check for Pending status, if Pending then return with 403 error
120+
console.error(`POST .../login failed: User status is pending`);
121+
return res.status(405).send({
122+
message: 'Authentication failed.',
123+
});
124+
}
125+
}
126+
112127
// if this found login account is a migrated tribal account, and there is no current hash, then
113128
// we need to first validate password using tribal hashing
114129
let tribalErr = null;
@@ -218,7 +233,8 @@ router.post('/', async (req, res) => {
218233
if (establishmentUser.invalidAttempt === (maxNumberOfFailedAttempts+1)) {
219234
// lock the account
220235
const loginUpdate = {
221-
isActive: false
236+
isActive: false,
237+
status: 'Locked'
222238
};
223239
await establishmentUser.update(loginUpdate, {transaction: t});
224240

@@ -238,9 +254,11 @@ router.post('/', async (req, res) => {
238254

239255
const resetLink = `${req.protocol}://${req.get('host')}/api/registration/validateResetPassword?reset=${requestUuid}`;
240256

241-
// send email to recipient with the reset UUID
257+
// send email to recipient with the reset UUID if user is not locked
242258
try {
243-
await sendMail(establishmentUser.user.EmailValue, establishmentUser.user.FullNameValue, requestUuid);
259+
if(establishmentUser.isActive && establishmentUser.status !== 'Locked'){
260+
await sendMail(establishmentUser.user.EmailValue, establishmentUser.user.FullNameValue, requestUuid);
261+
}
244262
} catch (err) {
245263
console.error(err);
246264
}

server/routes/registration.js

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const generateJWT = require('../utils/security/generateJWT');
2222
const passwordCheck = require('../utils/security/passwordValidation').isPasswordValid;
2323
const usernameCheck = require('../utils/security/usernameValidation').isUsernameValid;
2424
const sendMail = require('../utils/email/notify-email').sendPasswordReset;
25+
const rateLimiting = require('../utils/middleware/rateLimiting').rateLimiting;
2526
// const pCodeCheck = require('../utils/postcodeSanitizer');
2627

2728
class RegistrationException {
@@ -73,6 +74,8 @@ router.get('/username', (req, res) => {
7374
message: 'Username not found',
7475
});
7576
});
77+
78+
router.use('/username/:username', rateLimiting);
7679
router.get('/username/:username', async (req, res) => {
7780
const requestedUsername = req.params.username.toLowerCase();
7881
try {

0 commit comments

Comments
 (0)