2024-05-27 11:57:25 -07:00
const {
2024-10-01 10:29:32 +08:00
registrationBodyValidation ,
loginValidation ,
editUserParamValidation ,
editUserBodyValidation ,
recoveryValidation ,
recoveryTokenValidation ,
newPasswordValidation ,
2024-05-27 11:57:25 -07:00
} = require ( "../validation/joi" );
2024-05-15 12:16:04 -07:00
const logger = require ( "../utils/logger" );
2024-05-22 10:48:58 -07:00
require ( "dotenv" ). config ();
2024-10-01 10:29:32 +08:00
const { errorMessages , successMessages } = require ( "../utils/messages" );
const jwt = require ( "jsonwebtoken" );
2024-09-26 11:50:50 +08:00
const SERVICE_NAME = "AuthController" ;
2024-10-01 10:29:32 +08:00
const { getTokenFromHeaders } = require ( "../utils/utils" );
2024-09-30 10:08:37 +08:00
const crypto = require ( "crypto" );
2024-05-09 17:06:00 -04:00
2024-05-22 11:38:36 -07:00
/**
* Creates and returns JWT token with an arbitrary payload
* @function
* @param {Object} payload
2024-10-01 10:29:32 +08:00
* @param {Object} appSettings
2024-05-22 11:38:36 -07:00
* @returns {String}
2024-10-01 10:29:32 +08:00
* @throws {Error}
2024-05-22 11:38:36 -07:00
*/
2024-10-01 10:55:51 +08:00
const issueToken = ( payload , appSettings ) => {
2024-10-01 10:29:32 +08:00
try {
const tokenTTL = appSettings . jwtTTL ? appSettings . jwtTTL : "2h" ;
return jwt . sign ( payload , appSettings . jwtSecret , { expiresIn : tokenTTL });
} catch ( error ) {
error . service === undefined ? ( error . service = SERVICE_NAME ) : null ;
error . method === undefined ? ( error . method = "issueToken" ) : null ;
2024-10-01 10:55:51 +08:00
throw error ;
2024-10-01 10:29:32 +08:00
}
2024-05-22 11:38:36 -07:00
};
2024-05-09 17:06:00 -04:00
/**
2024-10-01 10:55:51 +08:00
* Registers a new user. If the user is the first account, a JWT secret is created. If not, an invite token is required.
* @async
* @param {Object} req - The Express request object.
* @property {Object} req.body - The body of the request.
* @property {string} req.body.inviteToken - The invite token for registration.
* @property {Object} req.file - The file object for the user's profile image.
* @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 creation of the user, the created user data, and a JWT token.
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
2024-05-09 17:06:00 -04:00
*/
2024-10-01 10:29:32 +08:00
const registerUser = async ( req , res , next ) => {
// joi validation
try {
await registrationBodyValidation . validateAsync ( req . body );
} catch ( error ) {
error . status = 422 ;
error . service = SERVICE_NAME ;
error . message = error . details ? .[ 0 ] ? . message || error . message || "Validation Error" ;
next ( error );
return ;
2024-09-25 15:12:07 +08:00
}
2024-10-01 10:29:32 +08:00
// Create a new user
try {
const { inviteToken } = req . body ;
// If superAdmin exists, a token should be attached to all further register requests
const superAdminExists = await req . db . checkSuperadmin ( req , res );
if ( superAdminExists ) {
await req . db . getInviteTokenAndDelete ( inviteToken );
} else {
// This is the first account, create JWT secret to use if one is not supplied by env
const jwtSecret = crypto . randomBytes ( 64 ). toString ( "hex" );
await req . db . updateAppSettings ({ jwtSecret });
}
const newUser = await req . db . insertUser ({... req . body }, req . file );
logger . info ( successMessages . AUTH_CREATE_USER , {
service : SERVICE_NAME , userId : newUser . _id ,
});
2024-09-30 10:08:37 +08:00
2024-10-01 10:29:32 +08:00
const userForToken = {... newUser . _doc };
delete userForToken . profileImage ;
delete userForToken . avatarImage ;
const appSettings = await req . settingsService . getSettings ();
const token = issueToken ( userForToken , appSettings );
req . emailService
. buildAndSendEmail ( "welcomeEmailTemplate" , { name : newUser . firstName }, newUser . email , "Welcome to Uptime Monitor" )
. catch (( error ) => {
logger . error ( "Error sending welcome email" , {
service : SERVICE_NAME , error : error . message ,
});
});
return res . status ( 200 ). json ({
success : true , msg : successMessages . AUTH_CREATE_USER , data : { user : newUser , token : token },
2024-08-26 15:21:06 -07:00
});
2024-10-01 10:29:32 +08:00
} catch ( error ) {
error . service === undefined ? ( error . service = SERVICE_NAME ) : null ;
error . method === undefined ? ( error . method = "registerController" ) : null ;
next ( error );
}
2024-05-15 12:16:04 -07:00
};
2024-05-22 10:48:58 -07:00
/**
2024-10-01 10:55:51 +08:00
* Logs in a user by validating the user's credentials and issuing a JWT token.
2024-05-22 10:48:58 -07:00
* @async
2024-10-01 10:55:51 +08:00
* @param {Object} req - The Express request object.
* @property {Object} req.body - The body of the request.
* @property {string} req.body.email - The email of the user.
* @property {string} req.body.password - The 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 login of the user, the user data (without password and avatar image), and a JWT token.
* @throws {Error} If there is an error during the process, especially if there is a validation error (422) or the password is incorrect.
2024-05-22 10:48:58 -07:00
*/
2024-10-01 10:29:32 +08:00
const loginUser = async ( req , res , next ) => {
try {
await loginValidation . validateAsync ( req . body );
} catch ( error ) {
error . status = 422 ;
error . service = SERVICE_NAME ;
error . message = error . details ? .[ 0 ] ? . message || error . message || "Validation Error" ;
next ( error );
return ;
}
try {
const { email , password } = req . body ;
// Check if user exists
const user = await req . db . getUserByEmail ( email );
// Compare password
const match = await user . comparePassword ( password );
if ( match !== true ) {
2024-10-01 10:55:51 +08:00
next ( new Error ( errorMessages . AUTH_INCORRECT_PASSWORD ));
return ;
2024-10-01 10:29:32 +08:00
}
// Remove password from user object. Should this be abstracted to DB layer?
const userWithoutPassword = {... user . _doc };
delete userWithoutPassword . password ;
delete userWithoutPassword . avatarImage ;
// Happy path, return token
const appSettings = req . settingsService . getSettings ();
const token = issueToken ( userWithoutPassword , appSettings );
// reset avatar image
userWithoutPassword . avatarImage = user . avatarImage ;
return res . status ( 200 ). json ({
success : true , msg : successMessages . AUTH_LOGIN_USER , data : { user : userWithoutPassword , token : token },
});
} catch ( error ) {
error . status = 500 ;
error . service === undefined ? ( error . service = SERVICE_NAME ) : null ;
error . method === undefined ? ( error . method = "loginController" ) : null ;
next ( error );
2024-05-15 12:16:04 -07:00
}
2024-05-14 11:12:22 -07:00
};
2024-05-09 17:06:00 -04:00
2024-10-01 10:55:51 +08:00
/**
* Edits a user's information. If the user wants to change their password, the current password is checked before updating to the new password.
* @async
* @param {Object} req - The Express request object.
* @property {Object} req.params - The parameters of the request.
* @property {string} req.params.userId - The ID of the user to be edited.
* @property {Object} req.body - The body of the request.
* @property {string} req.body.password - The current password of the user.
* @property {string} req.body.newPassword - 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 update of the user, and the updated user data.
* @throws {Error} If there is an error during the process, especially if there is a validation error (422), the user is unauthorized (401), or the password is incorrect (403).
*/
2024-10-01 10:29:32 +08:00
const editUser = async ( req , res , next ) => {
try {
await editUserParamValidation . validateAsync ( req . params );
await editUserBodyValidation . validateAsync ( req . body );
} catch ( error ) {
error . status = 422 ;
error . service = SERVICE_NAME ;
error . message = error . details ? .[ 0 ] ? . message || error . message || "Validation Error" ;
next ( error );
return ;
2024-07-10 11:51:27 -07:00
}
2024-10-01 10:29:32 +08:00
// TODO is this neccessary any longer? Verify ownership middleware should handle this
if ( req . params . userId !== req . user . _id . toString ()) {
const error = new Error ( errorMessages . AUTH_UNAUTHORIZED );
error . status = 401 ;
error . service = SERVICE_NAME ;
next ( error );
return ;
}
2024-05-27 11:57:25 -07:00
2024-07-30 13:54:30 -07:00
try {
2024-10-01 10:29:32 +08:00
// Change Password check
if ( req . body . password && req . body . newPassword ) {
// Get token from headers
const token = getTokenFromHeaders ( req . headers );
// Get email from token
const { jwtSecret } = req . settingsService . getSettings ();
const { email } = jwt . verify ( token , jwtSecret );
// Add user email to body for DB operation
req . body . email = email ;
// Get user
const user = await req . db . getUserByEmail ( email );
// Compare passwords
const match = await user . comparePassword ( req . body . password );
// If not a match, throw a 403
if ( ! match ) {
const error = new Error ( errorMessages . AUTH_INCORRECT_PASSWORD );
error . status = 403 ;
2024-10-01 10:55:51 +08:00
next ( error )
return ;
2024-10-01 10:29:32 +08:00
}
// If a match, update the password
req . body . password = req . body . newPassword ;
}
const updatedUser = await req . db . updateUser ( req , res );
return res . status ( 200 ). json ({
success : true , msg : successMessages . AUTH_UPDATE_USER , data : updatedUser ,
});
2024-07-30 13:54:30 -07:00
} catch ( error ) {
2024-10-01 10:29:32 +08:00
error . service === undefined ? ( error . service = SERVICE_NAME ) : null ;
error . method === undefined ? ( error . method = "userEditController" ) : null ;
next ( error );
}
2024-07-23 12:27:23 -07:00
};
2024-07-02 10:38:02 -07:00
/**
2024-10-01 10:55:51 +08:00
* Checks if a superadmin account exists in the database.
2024-07-02 10:38:02 -07:00
* @async
2024-10-01 10:55:51 +08:00
* @param {Object} req - The Express request object.
* @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 existence of a superadmin, and a boolean indicating the existence of a superadmin.
* @throws {Error} If there is an error during the process.
2024-07-02 10:38:02 -07:00
*/
2024-10-01 10:55:51 +08:00
const checkSuperAdminExists = async ( req , res , next ) => {
2024-10-01 10:29:32 +08:00
try {
const superAdminExists = await req . db . checkSuperadmin ( req , res );
return res . status ( 200 ). json ({
success : true , msg : successMessages . AUTH_ADMIN_EXISTS , data : superAdminExists ,
});
} catch ( error ) {
error . service === undefined ? ( error . service = SERVICE_NAME ) : null ;
error . method === undefined ? ( error . method = "checkSuperadminController" ) : null ;
next ( error );
}
2024-07-02 10:38:02 -07:00
};
2024-06-11 11:41:57 -07:00
/**
2024-10-01 10:55:51 +08:00
* Requests a recovery token for a user. The user's email is validated and a recovery token is created and sent via email.
2024-06-11 11:41:57 -07:00
* @async
2024-10-01 10:55:51 +08:00
* @param {Object} req - The Express request object.
* @property {Object} req.body - The body of the request.
* @property {string} req.body.email - The email of the user requesting recovery.
* @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 creation of the recovery token, and the message ID of the sent email.
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
2024-06-11 11:41:57 -07:00
*/
2024-10-01 10:29:32 +08:00
const requestRecovery = async ( req , res , next ) => {
try {
await recoveryValidation . validateAsync ( req . body );
} catch ( error ) {
error . status = 422 ;
error . service = SERVICE_NAME ;
error . message = error . details ? .[ 0 ] ? . message || error . message || "Validation Error" ;
next ( error );
return ;
}
try {
const { email } = req . body ;
const user = await req . db . getUserByEmail ( email );
if ( user ) {
const recoveryToken = await req . db . requestRecoveryToken ( req , res );
const name = user . firstName ;
const email = req . body . email ;
const { clientHost } = req . settingsService . getSettings ();
const url = ` ${ clientHost } /set-new-password/ ${ recoveryToken . token } ` ;
const msgId = await req . emailService . buildAndSendEmail ( "passwordResetTemplate" , {
name ,
email ,
url
2024-10-01 10:55:51 +08:00
}, email , "Bluewave Uptime Password Reset" );
2024-10-01 10:29:32 +08:00
return res . status ( 200 ). json ({
success : true , msg : successMessages . AUTH_CREATE_RECOVERY_TOKEN , data : msgId ,
});
}
} catch ( error ) {
error . service === undefined ? ( error . service = SERVICE_NAME ) : null ;
error . method === undefined ? ( error . method = "recoveryRequestController" ) : null ;
next ( error );
2024-06-10 15:54:45 -07:00
}
2024-06-10 16:07:52 -07:00
};
2024-06-11 11:41:57 -07:00
/**
2024-10-01 10:55:51 +08:00
* Validates a recovery token. The recovery token is validated and if valid, a success message is returned.
2024-06-11 11:41:57 -07:00
* @async
2024-10-01 10:55:51 +08:00
* @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).
2024-06-11 11:41:57 -07:00
*/
2024-10-01 10:29:32 +08:00
const validateRecovery = async ( req , res , next ) => {
try {
await recoveryTokenValidation . validateAsync ( req . body );
} catch ( error ) {
error . status = 422 ;
error . service = SERVICE_NAME ;
error . message = error . details ? .[ 0 ] ? . message || error . message || "Validation Error" ;
next ( error );
return ;
}
try {
await req . db . validateRecoveryToken ( req , res );
return res . status ( 200 ). json ({
success : true , msg : successMessages . AUTH_VERIFY_RECOVERY_TOKEN ,
});
} catch ( error ) {
error . service === undefined ? ( error . service = SERVICE_NAME ) : null ;
error . method === undefined ? ( error . method = "validateRecoveryTokenController" ) : null ;
next ( error );
}
2024-06-10 15:54:45 -07:00
};
2024-06-11 10:36:56 -07:00
2024-06-11 11:41:57 -07:00
/**
2024-10-01 10:55:51 +08: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.
2024-06-11 11:41:57 -07:00
* @async
2024-10-01 10:55:51 +08:00
* @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).
2024-06-11 11:41:57 -07:00
*/
2024-10-01 10:29:32 +08:00
const resetPassword = async ( req , res , next ) => {
try {
await newPasswordValidation . validateAsync ( req . body );
} catch ( error ) {
error . status = 422 ;
error . service = SERVICE_NAME ;
error . message = error . details ? .[ 0 ] ? . message || error . message || "Validation Error" ;
next ( error );
return ;
}
try {
const user = await req . db . resetPassword ( req , res );
const appSettings = await req . settingsService . getSettings ();
const token = issueToken ( user . _doc , appSettings );
res . status ( 200 ). json ({
success : true , msg : successMessages . AUTH_RESET_PASSWORD , data : { user , token },
});
} catch ( error ) {
error . service === undefined ? ( error . service = SERVICE_NAME ) : null ;
error . method === undefined ? ( error . method = "resetPasswordController" ) : null ;
next ( error );
}
2024-06-11 10:36:56 -07:00
};
2024-06-28 13:30:14 +03:30
2024-06-29 11:58:27 +03:30
/**
* 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.
*/
2024-10-01 10:29:32 +08:00
const deleteUser = async ( req , res , next ) => {
try {
const token = getTokenFromHeaders ( req . headers );
const decodedToken = jwt . decode ( token );
2024-10-01 10:55:51 +08:00
const { email } = decodedToken ;
2024-06-28 13:30:14 +03:30
2024-10-01 10:29:32 +08:00
// Check if the user exists
const user = await req . db . getUserByEmail ( email );
if ( ! user ) {
2024-10-01 10:55:51 +08:00
next ( new Error ( errorMessages . DB_USER_NOT_FOUND ));
return ;
2024-10-01 10:29:32 +08:00
}
// 1. Find all the monitors associated with the team ID if superadmin
const result = await req . db . getMonitorsByTeamId ({
params : { teamId : user . teamId },
});
if ( user . role . includes ( "superadmin" )) {
//2. Remove all jobs, delete checks and alerts
result ? . monitors . length > 0 && ( await Promise . all ( result . monitors . map ( async ( monitor ) => {
await req . jobQueue . deleteJob ( monitor );
await req . db . deleteChecks ( monitor . _id );
await req . db . deletePageSpeedChecksByMonitorId ( monitor . _id );
await req . db . deleteNotificationsByMonitorId ( monitor . _id );
})));
// 3. Delete team
await req . db . deleteTeam ( user . teamId );
// 4. Delete all other team members
await req . db . deleteAllOtherUsers ();
// 5. Delete each monitor
await req . db . deleteMonitorsByUserId ( user . _id );
}
// 6. Delete the user by id
await req . db . deleteUser ( user . _id );
return res . status ( 200 ). json ({
success : true , msg : successMessages . AUTH_DELETE_USER ,
});
} catch ( error ) {
error . service === undefined ? ( error . service = SERVICE_NAME ) : null ;
error . method === undefined ? ( error . method = "deleteUserController" ) : null ;
next ( error );
2024-06-29 11:58:27 +03:30
}
2024-06-28 13:30:14 +03:30
};
2024-10-01 10:29:32 +08:00
const getAllUsers = async ( req , res ) => {
try {
const allUsers = await req . db . getAllUsers ( req , res );
res
. status ( 200 )
. json ({ success : true , msg : "Got all users" , data : allUsers });
} catch ( error ) {
error . service === undefined ? ( error . service = SERVICE_NAME ) : null ;
error . method === undefined ? ( error . method = "getAllUsersController" ) : null ;
next ( error );
}
2024-07-22 11:49:22 -07:00
};
2024-06-28 13:30:14 +03:30
module . exports = {
2024-10-01 10:29:32 +08:00
registerUser ,
loginUser ,
editUser ,
2024-10-01 10:55:51 +08:00
checkSuperadminExists : checkSuperAdminExists ,
2024-10-01 10:29:32 +08:00
requestRecovery ,
validateRecovery ,
resetPassword ,
deleteUser ,
getAllUsers ,
2024-06-28 13:30:14 +03:30
};