Skip to content

Commit a10b552

Browse files
committed
Flow to unlock users account after it gets locked added
1 parent 2059645 commit a10b552

File tree

7 files changed

+257
-7
lines changed

7 files changed

+257
-7
lines changed

Tombolo/client-reactjs/src/components/admin/userManagement/Table.jsx

+61-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import React from 'react';
22
import { Table, Tooltip, Popconfirm, message, Tag, Popover } from 'antd';
3-
import { EyeOutlined, EditOutlined, DeleteOutlined, LockOutlined, DownOutlined } from '@ant-design/icons';
3+
import {
4+
EyeOutlined,
5+
EditOutlined,
6+
DeleteOutlined,
7+
LockFilled,
8+
UnlockOutlined,
9+
KeyOutlined,
10+
DownOutlined,
11+
} from '@ant-design/icons';
412

5-
import { deleteUser, resetUserPassword } from './Utils.js';
13+
import { deleteUser, resetUserPassword, unlockUserAccount } from './Utils.js';
614

715
const UserManagementTable = ({
816
users,
@@ -25,6 +33,16 @@ const UserManagementTable = ({
2533
}
2634
};
2735

36+
// Unlock user account
37+
const handleAccountUnlock = async ({ id }) => {
38+
try {
39+
await unlockUserAccount({ id });
40+
message.success('User account unlocked successfully');
41+
} catch (err) {
42+
message.error('Failed to unlock user account');
43+
}
44+
};
45+
2846
// Const handle user deletion - display message and setUsers and filteredUsers
2947
const handleDeleteUser = async ({ id }) => {
3048
try {
@@ -39,12 +57,24 @@ const UserManagementTable = ({
3957

4058
// Columns for the table
4159
const columns = [
60+
{
61+
title: '',
62+
key: (record) => record.id,
63+
width: 1,
64+
render: (record) =>
65+
record.accountLocked &&
66+
record.accountLocked.isLocked && (
67+
<Tooltip title="Account Locked">
68+
<LockFilled style={{ color: 'var(--danger)' }} />{' '}
69+
</Tooltip>
70+
),
71+
},
72+
Table.SELECTION_COLUMN,
4273
{
4374
title: 'First Name',
4475
dataIndex: 'firstName',
4576
key: 'firstName',
4677
},
47-
4878
{
4979
title: 'Last Name',
5080
dataIndex: 'lastName',
@@ -132,9 +162,35 @@ const UserManagementTable = ({
132162
cancelText="No"
133163
cancelButtonProps={{ type: 'primary', ghost: true }}
134164
style={{ width: '500px !important' }}>
135-
<LockOutlined style={{ marginRight: 15 }} />
136-
Reset Password
165+
<div style={{ marginBottom: '8px' }}>
166+
<KeyOutlined style={{ marginRight: 7 }} /> Reset Password
167+
</div>
137168
</Popconfirm>
169+
170+
{record.accountLocked && record.accountLocked.isLocked && (
171+
<Popconfirm
172+
title={
173+
<>
174+
<div style={{ fontWeight: 'bold' }}>{`Unlock Account`} </div>
175+
<div style={{ maxWidth: 460 }}>
176+
{`Clicking 'Yes' will unlock the user's account and send them a password reset link. Do you want to continue?`}
177+
</div>
178+
</>
179+
}
180+
onConfirm={() => {
181+
handleAccountUnlock({ id: record.id });
182+
}}
183+
okText="Yes"
184+
okButtonProps={{ danger: true }}
185+
cancelText="No"
186+
cancelButtonProps={{ type: 'primary', ghost: true }}
187+
style={{ width: '500px !important' }}>
188+
<div>
189+
<UnlockOutlined style={{ marginRight: 10 }} />
190+
Unlock Account
191+
</div>
192+
</Popconfirm>
193+
)}
138194
</div>
139195
</div>
140196
}>

Tombolo/client-reactjs/src/components/admin/userManagement/Utils.js

+16
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,21 @@ const resetUserPassword = async ({ id }) => {
178178
}
179179
};
180180

181+
// Unlock user account
182+
const unlockUserAccount = async ({ id }) => {
183+
const payload = {
184+
method: 'POST',
185+
body: JSON.stringify({ id }),
186+
headers: authHeader(),
187+
};
188+
189+
const response = await fetch(`/api/user/unlock-account`, payload);
190+
191+
if (!response.ok) {
192+
throw new Error('Failed to unlock user account');
193+
}
194+
};
195+
181196
export {
182197
createUser,
183198
getAllUsers,
@@ -189,4 +204,5 @@ export {
189204
updateUserApplications,
190205
bulkDeleteUsers,
191206
resetUserPassword,
207+
unlockUserAccount,
192208
};

Tombolo/server/controllers/authController.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -576,8 +576,7 @@ const resetTempPassword = async (req, res) => {
576576
await generateAndSetCSRFToken(req, res, accessToken);
577577

578578
//set last login
579-
580-
await setLastLogin(newUser);
579+
await setLastLogin(user);
581580

582581
// User data obj to send to the client
583582
const userObj = {

Tombolo/server/controllers/userController.js

+54
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
checkPasswordSecurityViolations,
1515
setPreviousPasswords,
1616
generatePassword,
17+
sendAccountUnlockedEmail,
1718
} = require("../utils/authUtil");
1819

1920
// Constants
@@ -612,6 +613,58 @@ const resetPasswordForUser = async (req, res) => {
612613
}
613614
};
614615

616+
// unlockAccount
617+
const unlockAccount = async (req, res) => {
618+
try {
619+
// Get user by ID
620+
const { id } = req.body;
621+
const user = await User.findOne({ where: { id } });
622+
623+
// If user not found
624+
if (!user) {
625+
return res
626+
.status(404)
627+
.json({ success: false, message: "User not found" });
628+
}
629+
630+
// Generate temporary password/hash
631+
const tempPassword = generatePassword();
632+
const salt = bcrypt.genSaltSync(10);
633+
634+
// Add updated user details
635+
user.hash = bcrypt.hashSync(tempPassword, salt);
636+
user.forcePasswordReset = true;
637+
user.passwordExpiresAt = new Date(
638+
new Date().setDate(new Date().getDate() + 2) // 48 hours
639+
);
640+
user.loginAttempts = 0;
641+
user.accountLocked = {isLocked : false,lockedReason: []};
642+
643+
// Save user with updated details
644+
await user.save();
645+
646+
// Send notification to the user
647+
const verificationCode = UUIDV4();
648+
649+
// Create account verification code
650+
await AccountVerificationCodes.create({
651+
code: verificationCode,
652+
userId: user.id,
653+
expiresAt: new Date(Date.now() + 172800000),
654+
});
655+
656+
await sendAccountUnlockedEmail({ user, tempPassword, verificationCode });
657+
658+
// Response
659+
res.status(200).json({ success: true, message: "User account unlocked successfully" });
660+
} catch (err) {
661+
logger.error(`Unlock account: ${err.message}`);
662+
res
663+
.status(err.status || 500)
664+
.json({ success: false, message: err.message });
665+
}
666+
};
667+
615668
//Exports
616669
module.exports = {
617670
createUser,
@@ -625,4 +678,5 @@ module.exports = {
625678
updateUserRoles,
626679
updateUserApplications,
627680
resetPasswordForUser,
681+
unlockAccount,
628682
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<style>
6+
.main_body {
7+
width: 100%;
8+
border-bottom: 1px solid lightgray !important;
9+
}
10+
11+
.section-footer-td {
12+
color: #808080;
13+
border-top: 1px solid #808080;
14+
}
15+
16+
.section-header-td {
17+
padding-top: 8px;
18+
padding-bottom: 8px;
19+
}
20+
21+
.list {
22+
padding: 10px;
23+
}
24+
25+
.tabbedText {
26+
padding-left: 40px;
27+
}
28+
29+
.bold-text {
30+
font-weight: bold;
31+
}
32+
33+
.important-text {
34+
color: #FF0000;
35+
}
36+
</style>
37+
</head>
38+
39+
<table class="main_body">
40+
<tbody>
41+
<tr>
42+
<td>
43+
<table>
44+
<tbody>
45+
<tr>
46+
<td class="section-header-td">
47+
<div>
48+
Hello,
49+
</div>
50+
<div>
51+
Your account has been unlocked, and a temporary password has been issued.
52+
<div>
53+
You must change this temporary password <span class="bold-text"> within 48 hours </span> to maintain account security. Below are the details:
54+
</div>
55+
</div>
56+
</td>
57+
</tr>
58+
59+
60+
<tr>
61+
<td class="section-header-td">
62+
<div class="tabbedText"> <span class="bold-text">Password Reset Link: </span><a href=<%= typeof passwordResetLink !== 'undefined' ? passwordResetLink : "" %>><%= typeof passwordResetLink !== 'undefined' ? passwordResetLink : "" %></a> </div>
63+
<div class="tabbedText"> <span class="bold-text">Temporary Password: </span> <%= typeof tempPassword !== 'undefined' ? tempPassword : "" %> </div>
64+
</td>
65+
</tr>
66+
67+
68+
<tr>
69+
<td>
70+
<p class="important-text">
71+
If you did not request your account to be unlocked or if this notification is unexpected, please contact your administrator immediately.
72+
</p>
73+
</td>
74+
</tr>
75+
76+
<tr>
77+
<td class="section-header-td">
78+
<div>
79+
Best Regards,
80+
</div>
81+
<div>
82+
Tombolo
83+
</div>
84+
</td>
85+
</tr>
86+
</tbody>
87+
</table>
88+
</td>
89+
</tr>
90+
</tbody>
91+
</table>
92+
93+
</html>

Tombolo/server/routes/userRoutes.js

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const {
2828
updateUserRoles,
2929
updateUserApplications,
3030
resetPasswordForUser,
31+
unlockAccount,
3132
} = require("../controllers/userController");
3233

3334
const { validateUserRole } = require("../middlewares/rbacMiddleware");
@@ -62,5 +63,6 @@ router.patch(
6263
); // Update a user by id
6364
router.patch("/applications/:id",validateUserId, updateUserApplications); // Update a user's applications
6465
router.post("/reset-password-for-user", validateUserIdInBody, resetPasswordForUser); // Reset password for user
66+
router.post("/unlock-account", validateUserIdInBody, unlockAccount); // Unlock account
6567
//Export
6668
module.exports = router;

Tombolo/server/utils/authUtil.js

+30
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,35 @@ const sendAccountLockedEmail = async (user) => {
467467
});
468468
};
469469

470+
// Send account unlocked email
471+
const sendAccountUnlockedEmail = async ({
472+
user,
473+
tempPassword,
474+
verificationCode,
475+
}) => {
476+
await NotificationQueue.create({
477+
type: "email",
478+
templateName: "accountUnlocked",
479+
notificationOrigin: "User Authentication",
480+
deliveryType: "immediate",
481+
metaData: {
482+
notificationId: `ACC_UNLOCKED_${moment().format("YYYYMMDD_HHmmss_SSS")}`,
483+
recipientName: user.firstName,
484+
notificationOrigin: "User Authentication",
485+
subject: "Your Account Has Been Unlocked – Action Required",
486+
mainRecipients: [user.email],
487+
notificationDescription: "Account unlocked by admin",
488+
userName: user.firstName + " " + user.lastName,
489+
userEmail: user.email,
490+
tempPassword,
491+
passwordResetLink: `${trimURL(
492+
process.env.WEB_URL
493+
)}/reset-temporary-password/${verificationCode}`,
494+
},
495+
createdBy: user.id,
496+
});
497+
};
498+
470499
//Exports
471500
module.exports = {
472501
generateAccessToken,
@@ -487,4 +516,5 @@ module.exports = {
487516
setLastLogin,
488517
setLastLoginAndReturn,
489518
handleInvalidLoginAttempt,
519+
sendAccountUnlockedEmail,
490520
};

0 commit comments

Comments
 (0)