Skip to content

Commit b71b11b

Browse files
committed
Merged with dev, resolved conflicts
2 parents a10b552 + 8881d8f commit b71b11b

File tree

8 files changed

+275
-16
lines changed

8 files changed

+275
-16
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const UserManagementActionButton = ({
4444
</Menu.Item>
4545
<Menu.Item key="3" disabled={selectedRows.length < 2}>
4646
<Popconfirm
47-
title={`Are you sure you want to delete selected ${selectedRows.length} users?. `}
47+
title={`Are you sure you want to delete selected ${selectedRows.length} users? `}
4848
okButtonProps={{ type: 'primary', danger: true }}
4949
okText="Delete"
5050
onConfirm={deleteSelected}>

Tombolo/server/controllers/authController.js

-1
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,6 @@ const requestAccess = async (req, res) => {
11331133

11341134
// Resend verification code - user provides email
11351135
const resendVerificationCode = async (req, res) => {
1136-
t;
11371136
try {
11381137
const { email } = req.body;
11391138

Tombolo/server/controllers/userController.js

+27-8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
setPreviousPasswords,
1616
generatePassword,
1717
sendAccountUnlockedEmail,
18+
deleteUser: deleteUserUtil,
1819
} = require("../utils/authUtil");
1920

2021
// Constants
@@ -30,11 +31,11 @@ const deleteUser = async (req, res) => {
3031
try {
3132
const { id } = req.params;
3233

33-
const deletedCount = await User.destroy({ where: { id } });
34+
const deleted = await deleteUserUtil(id, "Admin Removal");
3435

3536
// If deleted count is 0, user not found
36-
if (deletedCount === 0) {
37-
throw { status: 404, message: "User not found" };
37+
if (!deleted) {
38+
throw { status: 404, message: "Error Removing User." };
3839
}
3940

4041
// User successfully deleted
@@ -226,14 +227,32 @@ const changePassword = async (req, res) => {
226227
const bulkDeleteUsers = async (req, res) => {
227228
try {
228229
const { ids } = req.body;
229-
const deletedCount = await User.destroy({ where: { id: ids } });
230230

231-
// If deleted count is 0, user not found
232-
if (deletedCount === 0) {
233-
throw { status: 404, message: "Users not found" };
231+
let deletedCount = 0;
232+
let idsCount = ids.length;
233+
// Loop through each user and delete
234+
for (let id of ids) {
235+
const deleted = await deleteUserUtil(id, "Admin Removal");
236+
if (deleted) {
237+
deletedCount++;
238+
}
234239
}
240+
241+
if (deletedCount !== idsCount) {
242+
res.status(207).json({
243+
success: false,
244+
message: "Some users could not be deleted",
245+
data: { deletedCount, idsCount },
246+
});
247+
}
248+
249+
res.status(200).json({
250+
success: true,
251+
message: "Users deleted successfully",
252+
data: { deletedCount },
253+
});
235254
} catch (err) {
236-
logger.error(`Update user applications: ${err.message}`);
255+
logger.error(`Bulk Delete Users: ${err.message}`);
237256
res
238257
.status(err.status || 500)
239258
.json({ success: false, message: err.message });

Tombolo/server/jobs/userManagement/accountDelete.js

+13-5
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ const { v4: uuidv4 } = require("uuid");
55

66
//Local Imports
77
const models = require("../../models");
8-
const { trimURL, getSupportContactEmails } = require("../../utils/authUtil");
9-
const { trim } = require("lodash");
8+
const { trimURL, deleteUser } = require("../../utils/authUtil");
109

1110
// Constants
1211
const User = models.user;
@@ -201,15 +200,24 @@ const updateUserAndSendNotification = async (user, daysToExpiry, version) => {
201200
});
202201

203202
// delete user account
204-
// TODO -- Move user to archive table
205-
await user.destroy();
203+
const deleted = await deleteUser(userInternal.id, "Inactivity");
204+
if (!deleted) {
205+
parentPort &&
206+
parentPort.postMessage({
207+
level: "error",
208+
text:
209+
"Failed to delete userwith email " +
210+
userInternal.email +
211+
", due to inactivity.",
212+
});
213+
}
206214
}
207215
}
208216

209217
parentPort &&
210218
parentPort.postMessage({
211219
level: "info",
212-
text: "Account lock check job completed ...",
220+
text: "Account Delete check job completed ...",
213221
});
214222
} catch (error) {
215223
parentPort &&

Tombolo/server/jobs/userManagement/removeUnverifiedUsers.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// imports
22
const { parentPort } = require("worker_threads");
33
const { Op } = require("sequelize");
4+
const { deleteUser } = require("../../utils/authUtil");
45

56

67
//Local Imports
@@ -27,7 +28,7 @@ const { user} = models;
2728
parentPort && parentPort.postMessage({level: "info", text : `Number of unverified users to be removed: ${unverifiedUsers.length}`});
2829

2930
for (const user of unverifiedUsers) {
30-
await user.destroy();
31+
await deleteUser(user.id, "unverified user deleted by system");
3132
}
3233

3334
parentPort && parentPort.postMessage({level: "info", text : "Job to remove unverified user completed ..."});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"use strict";
2+
3+
module.exports = {
4+
up: async (queryInterface, Sequelize) => {
5+
await queryInterface.createTable("user_archive", {
6+
id: {
7+
primaryKey: true,
8+
type: Sequelize.UUID,
9+
defaultValue: Sequelize.UUIDV4,
10+
allowNull: false,
11+
},
12+
removedBy: {
13+
type: Sequelize.STRING,
14+
allowNull: false,
15+
},
16+
removedAt: {
17+
type: Sequelize.DATE,
18+
allowNull: false,
19+
},
20+
firstName: {
21+
type: Sequelize.STRING,
22+
allowNull: false,
23+
},
24+
lastName: {
25+
type: Sequelize.STRING,
26+
allowNull: false,
27+
},
28+
email: {
29+
type: Sequelize.STRING,
30+
allowNull: false,
31+
validate: {
32+
isEmail: true,
33+
},
34+
},
35+
registrationMethod: {
36+
type: Sequelize.STRING,
37+
allowNull: false,
38+
validate: {
39+
isIn: [["traditional", "microsoft"]],
40+
},
41+
},
42+
verifiedUser: {
43+
type: Sequelize.BOOLEAN,
44+
allowNull: false,
45+
defaultValue: false,
46+
},
47+
verifiedAt: {
48+
type: Sequelize.DATE,
49+
allowNull: true,
50+
},
51+
registrationStatus: {
52+
type: Sequelize.STRING,
53+
allowNull: false,
54+
validate: {
55+
isIn: [["pending", "active", "revoked"]],
56+
},
57+
},
58+
forcePasswordReset: {
59+
type: Sequelize.BOOLEAN,
60+
allowNull: false,
61+
defaultValue: false,
62+
},
63+
passwordExpiresAt: {
64+
type: Sequelize.DATE,
65+
allowNull: true,
66+
},
67+
lastLoginAt: {
68+
type: Sequelize.DATE,
69+
allowNull: true,
70+
},
71+
lastAccessedAt: {
72+
type: Sequelize.DATE,
73+
allowNull: true,
74+
},
75+
metaData: {
76+
type: Sequelize.JSON,
77+
defaultValue: {},
78+
allowNull: true,
79+
},
80+
createdAt: {
81+
allowNull: false,
82+
type: Sequelize.DATE,
83+
},
84+
updatedAt: {
85+
allowNull: false,
86+
type: Sequelize.DATE,
87+
},
88+
deletedAt: {
89+
type: Sequelize.DATE,
90+
allowNull: true,
91+
},
92+
});
93+
},
94+
down: async (queryInterface, Sequelize) => {
95+
await queryInterface.dropTable("users");
96+
},
97+
};

Tombolo/server/models/user_archive.js

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"use strict";
2+
3+
module.exports = (sequelize, DataTypes) => {
4+
const userArchive = sequelize.define(
5+
"userArchive",
6+
{
7+
id: {
8+
primaryKey: true,
9+
type: DataTypes.UUID,
10+
defaultValue: DataTypes.UUIDV4,
11+
allowNull: false,
12+
},
13+
removedBy: {
14+
type: DataTypes.STRING,
15+
allowNull: false,
16+
},
17+
removedAt: {
18+
type: DataTypes.DATE,
19+
allowNull: false,
20+
},
21+
firstName: {
22+
type: DataTypes.STRING,
23+
allowNull: false,
24+
},
25+
lastName: {
26+
type: DataTypes.STRING,
27+
allowNull: false,
28+
},
29+
email: {
30+
type: DataTypes.STRING,
31+
allowNull: false,
32+
validate: {
33+
isEmail: true,
34+
},
35+
},
36+
registrationMethod: {
37+
type: DataTypes.STRING,
38+
allowNull: false,
39+
validate: {
40+
isIn: [["traditional", "azure"]],
41+
},
42+
},
43+
verifiedUser: {
44+
type: DataTypes.BOOLEAN,
45+
allowNull: false,
46+
defaultValue: false,
47+
},
48+
verifiedAt: {
49+
type: DataTypes.DATE,
50+
allowNull: true,
51+
},
52+
registrationStatus: {
53+
type: DataTypes.STRING,
54+
allowNull: false,
55+
defaultValue: "pending",
56+
validate: {
57+
isIn: [["pending", "active", "revoked"]], // Must be one of these values
58+
},
59+
},
60+
forcePasswordReset: {
61+
type: DataTypes.BOOLEAN,
62+
allowNull: false,
63+
defaultValue: false,
64+
},
65+
passwordExpiresAt: {
66+
type: DataTypes.DATE,
67+
allowNull: true,
68+
},
69+
lastLoginAt: {
70+
type: DataTypes.DATE,
71+
allowNull: true,
72+
},
73+
lastAccessedAt: {
74+
type: DataTypes.DATE,
75+
allowNull: true,
76+
},
77+
metaData: {
78+
type: DataTypes.JSON,
79+
defaultValue: {}, //put default value to avoid null conflicts
80+
allowNull: true,
81+
},
82+
},
83+
{
84+
tableName: "user_archive",
85+
timestamps: true,
86+
}
87+
);
88+
89+
return userArchive;
90+
};

Tombolo/server/utils/authUtil.js

+45
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const bcrypt = require("bcryptjs");
1414
const User = model.user;
1515
const UserRoles = model.UserRoles;
1616
const RoleTypes = model.RoleTypes;
17+
const userArchive = model.userArchive;
1718
const user_application = model.user_application;
1819
const Application = model.application;
1920
const InstanceSettings = model.instance_settings;
@@ -495,6 +496,49 @@ const sendAccountUnlockedEmail = async ({
495496
createdBy: user.id,
496497
});
497498
};
499+
const deleteUser = async (id, reason) => {
500+
try {
501+
if (!reason || reason === "") {
502+
throw new Error("Reason for deletion is required");
503+
}
504+
505+
//get user
506+
const user = await User.findByPk(id);
507+
508+
if (!user) {
509+
throw new Error("User not found");
510+
}
511+
512+
const removedAt = Date.now();
513+
const removedBy = reason;
514+
515+
//remove hash from user
516+
user.dataValues.hash = null;
517+
518+
const archivedUser = await userArchive.create({
519+
...user.dataValues,
520+
removedAt,
521+
removedBy,
522+
});
523+
524+
if (!archivedUser) {
525+
throw new Error("Failed to archive user");
526+
}
527+
528+
//hard delete without paranoid
529+
await User.destroy({
530+
where: {
531+
id: id,
532+
},
533+
force: true,
534+
});
535+
536+
return true;
537+
} catch (e) {
538+
logger.error("Error while deleting user:" + e);
539+
return false;
540+
}
541+
};
498542

499543
//Exports
500544
module.exports = {
@@ -517,4 +561,5 @@ module.exports = {
517561
setLastLoginAndReturn,
518562
handleInvalidLoginAttempt,
519563
sendAccountUnlockedEmail,
564+
deleteUser,
520565
};

0 commit comments

Comments
 (0)