Files
Checkmate/server/controllers/authController.js
T

285 lines
8.2 KiB
JavaScript
Raw Normal View History

2025-04-20 11:29:53 -07:00
import {
registrationBodyValidation,
loginValidation,
editUserBodyValidation,
recoveryValidation,
recoveryTokenValidation,
newPasswordValidation,
2025-07-22 13:26:45 -07:00
getUserByIdParamValidation,
editUserByIdParamValidation,
editUserByIdBodyValidation,
editSuperadminUserByIdBodyValidation,
2025-04-20 11:29:53 -07:00
} from "../validation/joi.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders } from "../utils/utils.js";
2025-07-23 11:46:59 -07:00
import { asyncHandler, createError } from "../utils/errorUtils.js";
2025-04-20 11:29:53 -07:00
const SERVICE_NAME = "authController";
class AuthController {
2025-07-23 11:46:59 -07:00
constructor({ db, settingsService, emailService, jobQueue, stringService, logger, userService }) {
2025-04-20 11:29:53 -07:00
this.db = db;
this.settingsService = settingsService;
this.emailService = emailService;
this.jobQueue = jobQueue;
this.stringService = stringService;
2025-06-19 09:44:24 +08:00
this.logger = logger;
2025-07-23 11:46:59 -07:00
this.userService = userService;
2025-04-20 11:29:53 -07:00
}
registerUser = asyncHandler(
async (req, res, next) => {
if (req.body?.email) {
2025-04-20 11:29:53 -07:00
req.body.email = req.body.email?.toLowerCase();
}
2025-04-20 11:29:53 -07:00
await registrationBodyValidation.validateAsync(req.body);
2025-07-23 11:46:59 -07:00
const { user, token } = await this.userService.registerUser(req.body, req.file);
2025-04-20 11:29:53 -07:00
res.success({
msg: this.stringService.authCreateUser,
2025-07-23 11:46:59 -07:00
data: { user, token },
2025-04-20 11:29:53 -07:00
});
},
SERVICE_NAME,
"registerUser"
);
2025-04-20 11:29:53 -07:00
loginUser = asyncHandler(
async (req, res, next) => {
if (req.body?.email) {
req.body.email = req.body.email?.toLowerCase();
2025-04-20 11:29:53 -07:00
}
await loginValidation.validateAsync(req.body);
2025-07-23 11:46:59 -07:00
const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
2025-04-20 11:29:53 -07:00
return res.success({
msg: this.stringService.authLoginUser,
data: {
2025-07-23 11:46:59 -07:00
user,
token,
2025-04-20 11:29:53 -07:00
},
});
},
SERVICE_NAME,
"loginUser"
);
2025-04-20 11:29:53 -07:00
editUser = asyncHandler(
async (req, res, next) => {
2025-04-20 11:29:53 -07:00
await editUserBodyValidation.validateAsync(req.body);
2025-07-23 11:46:59 -07:00
const updatedUser = await this.userService.editUser(req.body, req.file, req.user);
2025-04-20 11:29:53 -07:00
res.success({
msg: this.stringService.authUpdateUser,
data: updatedUser,
});
},
SERVICE_NAME,
"editUser"
);
2025-04-20 11:29:53 -07:00
checkSuperadminExists = asyncHandler(
async (req, res, next) => {
2025-07-23 11:48:47 -07:00
const superAdminExists = await this.userService.checkSuperadminExists();
2025-04-20 11:29:53 -07:00
return res.success({
msg: this.stringService.authAdminExists,
data: superAdminExists,
});
},
SERVICE_NAME,
"checkSuperadminExists"
);
2025-07-23 11:48:47 -07:00
requestRecovery = asyncHandler(
async (req, res, next) => {
2025-04-20 11:29:53 -07:00
await recoveryValidation.validateAsync(req.body);
const { email } = req.body;
const user = await this.db.getUserByEmail(email);
const recoveryToken = await this.db.requestRecoveryToken(req, res);
const name = user.firstName;
const { clientHost } = this.settingsService.getSettings();
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
2025-06-18 10:44:59 +08:00
const html = await this.emailService.buildEmail("passwordResetTemplate", {
name,
email,
url,
});
2025-07-21 10:34:16 -07:00
const msgId = await this.emailService.sendEmail(email, "Checkmate Password Reset", html);
2025-04-20 11:29:53 -07:00
return res.success({
msg: this.stringService.authCreateRecoveryToken,
data: msgId,
});
},
SERVICE_NAME,
"requestRecovery"
);
2025-04-20 11:29:53 -07:00
/**
* Validates a recovery token. The recovery token is validated and if valid, a success message is returned.
* @async
* @param {Object} req - The Express request object.
* @property {Object} req.body - The body of the request.
* @property {string} req.body.token - The recovery token to be validated.
* @param {Object} res - The Express response object.
* @param {function} next - The next middleware function.
* @returns {Object} The response object with a success status and a message indicating the validation of the recovery token.
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
validateRecovery = asyncHandler(
async (req, res, next) => {
2025-04-20 11:29:53 -07:00
await recoveryTokenValidation.validateAsync(req.body);
await this.db.validateRecoveryToken(req, res);
return res.success({
msg: this.stringService.authVerifyRecoveryToken,
});
},
SERVICE_NAME,
"validateRecovery"
);
2025-04-20 11:29:53 -07:00
/**
* Resets a user's password. The new password is validated and if valid, the user's password is updated in the database and a new JWT token is issued.
* @async
* @param {Object} req - The Express request object.
* @property {Object} req.body - The body of the request.
* @property {string} req.body.token - The recovery token.
* @property {string} req.body.password - The new password of the user.
* @param {Object} res - The Express response object.
* @param {function} next - The next middleware function.
* @returns {Object} The response object with a success status, a message indicating the reset of the password, the updated user data (without password and avatar image), and a new JWT token.
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
resetPassword = asyncHandler(
async (req, res, next) => {
2025-04-20 11:29:53 -07:00
await newPasswordValidation.validateAsync(req.body);
const user = await this.db.resetPassword(req, res);
const appSettings = await this.settingsService.getSettings();
const token = this.issueToken(user._doc, appSettings);
return res.success({
msg: this.stringService.authResetPassword,
data: { user, token },
});
},
SERVICE_NAME,
"resetPassword"
);
2025-04-20 11:29:53 -07:00
/**
* Deletes a user and all associated monitors, checks, and alerts.
*
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {Function} next - The next middleware function.
* @returns {Object} The response object with success status and message.
* @throws {Error} If user validation fails or user is not found in the database.
*/
deleteUser = asyncHandler(
async (req, res, next) => {
2025-07-21 11:05:48 -07:00
const email = req?.user?.email;
if (!email) {
throw new Error("No email in request");
}
2025-04-20 11:29:53 -07:00
2025-07-21 11:05:48 -07:00
const teamId = req?.user?.teamId;
const userId = req?.user?._id;
if (!teamId) {
throw new Error("No team ID in request");
}
if (!userId) {
throw new Error("No user ID in request");
}
const roles = req.user.role;
2025-07-21 15:05:17 -07:00
if (roles.includes("demo")) {
throw new Error("Demo user cannot be deleted");
}
2025-07-21 11:05:48 -07:00
2025-07-21 15:05:17 -07:00
// 1. Find all the monitors associated with the team ID if superadmin
2025-04-20 11:29:53 -07:00
const result = await this.db.getMonitorsByTeamId({
2025-07-21 11:05:48 -07:00
teamId: teamId,
2025-04-20 11:29:53 -07:00
});
2025-07-21 11:05:48 -07:00
if (roles.includes("superadmin")) {
// 2. Remove all jobs, delete checks and alerts
2025-04-20 11:29:53 -07:00
result?.monitors.length > 0 &&
(await Promise.all(
result.monitors.map(async (monitor) => {
await this.jobQueue.deleteJob(monitor);
})
));
}
// 6. Delete the user by id
2025-07-21 11:05:48 -07:00
await this.db.deleteUser(userId);
2025-04-20 11:29:53 -07:00
return res.success({
msg: this.stringService.authDeleteUser,
});
},
SERVICE_NAME,
"deleteUser"
);
2025-04-20 11:29:53 -07:00
getAllUsers = asyncHandler(
async (req, res, next) => {
2025-04-20 11:29:53 -07:00
const allUsers = await this.db.getAllUsers(req, res);
return res.success({
msg: this.stringService.authGetAllUsers,
data: allUsers,
});
},
SERVICE_NAME,
"getAllUsers"
);
2025-07-22 12:27:33 -07:00
getUserById = asyncHandler(
async (req, res, next) => {
2025-07-22 13:26:45 -07:00
await getUserByIdParamValidation.validateAsync(req.params);
2025-07-22 12:27:33 -07:00
const userId = req?.params?.userId;
const roles = req?.user?.role;
if (!userId) {
throw new Error("No user ID in request");
}
if (!roles || roles.length === 0) {
throw new Error("No roles in request");
}
const user = await this.db.getUserById(roles, userId);
return res.success({ msg: "ok", data: user });
},
SERVICE_NAME,
"getUserById"
);
editUserById = asyncHandler(
async (req, res, next) => {
const roles = req?.user?.role;
if (!roles.includes("superadmin")) {
throw createError("Unauthorized", 403);
}
2025-07-22 13:26:45 -07:00
const userId = req.params.userId;
const user = { ...req.body };
await editUserByIdParamValidation.validateAsync(req.params);
// If this is superadmin self edit, allow "superadmin" role
if (userId === req.user._id) {
await editSuperadminUserByIdBodyValidation.validateAsync(req.body);
} else {
await editUserByIdBodyValidation.validateAsync(req.body);
}
2025-07-22 12:27:33 -07:00
await this.db.editUserById(userId, user);
return res.success({ msg: "ok" });
},
SERVICE_NAME,
"editUserById"
);
2025-04-20 11:29:53 -07:00
}
export default AuthController;