Skip to content

Commit c4996bc

Browse files
authored
Mfancher/store prev passwords (#1015)
* install bcrypt on front-end * store prev passwords on backend * Check for prev passwords on backend * Update authUtil.js
1 parent 8a0153e commit c4996bc

File tree

6 files changed

+114
-19
lines changed

6 files changed

+114
-19
lines changed

Tombolo/client-reactjs/package-lock.json

+13-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tombolo/client-reactjs/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"@antv/x6-react-shape": "^2.2.3",
1313
"@monaco-editor/react": "^4.4.5",
1414
"antd": "^5.13.2",
15+
"bcryptjs-react": "^2.4.6",
1516
"cronstrue": "^2.14.0",
1617
"dayjs": "^1.11.10",
1718
"font-awesome": "^4.7.0",

Tombolo/server/controllers/authController.js

+27-3
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ const {
1414
setPasswordExpiry,
1515
setAndSendPasswordExpiredEmail,
1616
checkPasswordSecurityViolations,
17+
setPreviousPasswords,
1718
} = require("../utils/authUtil");
1819
const { blacklistToken } = require("../utils/tokenBlackListing");
1920

2021
const { generateAndSetCSRFToken } = require("../utils/authUtil");
21-
const { get } = require("lodash");
2222

2323
const User = models.user;
2424
const UserRoles = models.UserRoles;
@@ -83,6 +83,7 @@ const createApplicationOwner = async (req, res) => {
8383
const salt = bcrypt.genSaltSync(10);
8484
payload.hash = bcrypt.hashSync(req.body.password, salt);
8585
setPasswordExpiry(payload);
86+
setPreviousPasswords(payload);
8687

8788
// Save user to DB
8889
const user = await User.create(payload);
@@ -167,6 +168,7 @@ const createBasicUser = async (req, res) => {
167168
const salt = bcrypt.genSaltSync(10);
168169
payload.hash = bcrypt.hashSync(req.body.password, salt);
169170
setPasswordExpiry(payload);
171+
setPreviousPasswords(payload);
170172

171173
// Save user to DB
172174
const user = await User.create(payload);
@@ -374,9 +376,20 @@ const resetPasswordWithToken = async (req, res) => {
374376
user.verifiedAt = new Date();
375377
user.forcePasswordReset = false;
376378
setPasswordExpiry(user);
379+
setPreviousPasswords(user);
377380

378381
// Save user with updated details
379-
await user.save();
382+
await User.update(
383+
{
384+
hash: user.hash,
385+
metaData: user.metaData,
386+
passwordExpiresAt: user.passwordExpiresAt,
387+
forcePasswordReset: user.forcePasswordReset,
388+
},
389+
{
390+
where: { id: user.id },
391+
}
392+
);
380393

381394
// Delete the account verification code
382395
await AccountVerificationCodes.destroy({
@@ -490,9 +503,20 @@ const resetTempPassword = async (req, res) => {
490503
user.verifiedAt = new Date();
491504
user.forcePasswordReset = false;
492505
setPasswordExpiry(user);
506+
setPreviousPasswords(user);
493507

494508
// Save user with updated details
495-
await user.save();
509+
await User.update(
510+
{
511+
hash: user.hash,
512+
metaData: user.metaData,
513+
passwordExpiresAt: user.passwordExpiresAt,
514+
forcePasswordReset: user.forcePasswordReset,
515+
},
516+
{
517+
where: { id: user.id },
518+
}
519+
);
496520

497521
// Delete the account verification code
498522
await AccountVerificationCodes.destroy({

Tombolo/server/controllers/userController.js

+29-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ const user_application = models.user_application;
1010
const NotificationQueue = models.notification_queue;
1111
const AccountVerificationCodes = models.AccountVerificationCodes;
1212

13-
const { setPasswordExpiry } = require("../utils/authUtil");
13+
const {
14+
checkPasswordSecurityViolations,
15+
setPasswordExpiry,
16+
setPreviousPasswords,
17+
} = require("../utils/authUtil");
1418

1519
// Delete user with ID
1620
const deleteUser = async (req, res) => {
@@ -163,15 +167,38 @@ const changePassword = async (req, res) => {
163167
throw { status: 400, message: "Current password is incorrect" };
164168
}
165169

170+
//check for password security violations
171+
const errors = checkPasswordSecurityViolations({
172+
password: newPassword,
173+
user: existingUser,
174+
});
175+
176+
if (errors.length > 0) {
177+
throw { status: 400, message: errors };
178+
}
179+
166180
// Update password
167181
const salt = bcrypt.genSaltSync(10);
168182
existingUser.hash = bcrypt.hashSync(newPassword, salt);
169183

170184
//set password expiry
171185
setPasswordExpiry(existingUser);
172186

187+
//set previous passwords
188+
setPreviousPasswords(existingUser);
189+
173190
// Save user with updated details
174-
const updatedUser = await existingUser.save();
191+
const updatedUser = await User.update(
192+
{
193+
hash: existingUser.hash,
194+
metaData: existingUser.metaData,
195+
passwordExpiresAt: existingUser.passwordExpiresAt,
196+
forcePasswordReset: existingUser.forcePasswordReset,
197+
},
198+
{
199+
where: { id },
200+
}
201+
);
175202

176203
res.status(200).json({
177204
success: true,

Tombolo/server/controllers/wizardController.js

+1
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ const createUser = async (
193193
third: false,
194194
final: false,
195195
},
196+
previousPasswords: [hash],
196197
},
197198
},
198199
{ transaction }

Tombolo/server/utils/authUtil.js

+43-10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { Op } = require("sequelize");
77
const logger = require("../config/logger");
88
const model = require("../models");
99
const { generateToken } = require("../middlewares/csrfMiddleware");
10+
const bcrypt = require("bcryptjs");
1011

1112
// Constants
1213
const User = model.user;
@@ -131,18 +132,22 @@ const setPasswordExpiry = (user) => {
131132
return user;
132133
};
133134

134-
135135
// Get Support Notification Recipient's Emails
136136
const getSupportContactEmails = async () => {
137137
// Get Instance Setting
138138
const instanceSetting = await InstanceSettings.findOne({ raw: true });
139139

140-
let supportEmailRecipientsEmail = instanceSetting.metaData.supportEmailRecipients || [];
141-
let supportEmailRecipientsRoles = instanceSetting.metaData.supportEmailRecipientsRoles || [];
140+
let supportEmailRecipientsEmail =
141+
instanceSetting.metaData.supportEmailRecipients || [];
142+
let supportEmailRecipientsRoles =
143+
instanceSetting.metaData.supportEmailRecipientsRoles || [];
142144
const supportRolesEmail = [];
143145

144146
// If support email recipients exist and no support email recipients roles
145-
if(supportEmailRecipientsEmail.length > 0 && supportEmailRecipientsRoles.length === 0){
147+
if (
148+
supportEmailRecipientsEmail.length > 0 &&
149+
supportEmailRecipientsRoles.length === 0
150+
) {
146151
return supportEmailRecipientsEmail;
147152
}
148153

@@ -166,8 +171,6 @@ const getSupportContactEmails = async () => {
166171
],
167172
});
168173

169-
170-
171174
// Get the e-mail addresses
172175
ownerAndAdminEmails.forEach((user) => {
173176
supportRolesEmail.push(user.email);
@@ -182,12 +185,17 @@ const getAccessRequestContactEmails = async () => {
182185
// Get Instance Setting
183186
const instanceSetting = await InstanceSettings.findOne({ raw: true });
184187

185-
let accessRequestEmailRecipientsEmail = instanceSetting.metaData.accessRequestEmailRecipientsEmail || [];
186-
let accessRequestEmailRecipientsRoles = instanceSetting.metaData.accessRequestEmailRecipientsRoles || [];
188+
let accessRequestEmailRecipientsEmail =
189+
instanceSetting.metaData.accessRequestEmailRecipientsEmail || [];
190+
let accessRequestEmailRecipientsRoles =
191+
instanceSetting.metaData.accessRequestEmailRecipientsRoles || [];
187192
const accessRequestRolesEmail = [];
188193

189194
// If access email recipients exist and no support email recipients roles
190-
if(accessRequestEmailRecipientsEmail.length > 0 && accessRequestEmailRecipientsRoles.length === 0){
195+
if (
196+
accessRequestEmailRecipientsEmail.length > 0 &&
197+
accessRequestEmailRecipientsRoles.length === 0
198+
) {
191199
return accessRequestEmailRecipientsEmail;
192200
}
193201

@@ -219,7 +227,6 @@ const getAccessRequestContactEmails = async () => {
219227
return [...accessRequestEmailRecipientsEmail, ...accessRequestRolesEmail];
220228
};
221229

222-
223230
const setAndSendPasswordExpiredEmail = async (user) => {
224231
//if the forcePasswordReset flag isn't set but the password has expired, set the flag
225232
if (user.passwordExpiresAt < new Date() && !user.forcePasswordReset) {
@@ -286,6 +293,7 @@ const checkPasswordSecurityViolations = ({ password, user }) => {
286293
const email = user.email;
287294
const firstName = user.firstName;
288295
const lastName = user.lastName;
296+
const previousPasswords = user.metaData.previousPasswords || [];
289297

290298
if (password.includes(email)) {
291299
passwordViolations.push("Password contains email address");
@@ -302,10 +310,34 @@ const checkPasswordSecurityViolations = ({ password, user }) => {
302310
}
303311

304312
//TODO -- check if password contains any of previous 12 passwords
313+
previousPasswords.forEach((oldPassword) => {
314+
if (bcrypt.compareSync(password, oldPassword)) {
315+
passwordViolations.push(
316+
"Password cannot be the same as one of the previous passwords"
317+
);
318+
}
319+
});
305320

306321
return passwordViolations;
307322
};
308323

324+
const setPreviousPasswords = async (user) => {
325+
//get existing previous passwords
326+
let previousPasswords = user.metaData.previousPasswords || [];
327+
328+
//add current password to the list
329+
previousPasswords.push(user.hash);
330+
331+
//if there are more than 12 previous passwords, remove the oldest one
332+
if (previousPasswords.length > 12) {
333+
previousPasswords.shift();
334+
}
335+
336+
user.metaData.previousPasswords = previousPasswords;
337+
338+
return user;
339+
};
340+
309341
//Exports
310342
module.exports = {
311343
generateAccessToken,
@@ -321,4 +353,5 @@ module.exports = {
321353
checkPasswordSecurityViolations,
322354
getSupportContactEmails,
323355
getAccessRequestContactEmails,
356+
setPreviousPasswords,
324357
};

0 commit comments

Comments
 (0)