diff --git a/app/controllers/auth.controller.js b/app/controllers/auth.controller.js index 5ad086b..5088191 100644 --- a/app/controllers/auth.controller.js +++ b/app/controllers/auth.controller.js @@ -41,7 +41,7 @@ exports.login = async (req, res) => { // Check password const validPass = await bcrypt.compare(password, userData.password); if (!validPass) { - return res.status(401).json({ status: "failed", message: "Invalid password", data: "" }); + return res.status(401).json({ status: "failed", message: "Invalid password" }); } // Prepare token data @@ -115,62 +115,41 @@ exports.logout = (req, res) => { }); }; - // Register new user exports.register = async (req, res) => { try { const { name, email, password } = req.body; if (!name || !email || !password) - return res.status(400).send({'status':"failed",'message':"All fields required",'data': ""}); + return res.status(400).send({ + status: "error", + code: "MISSING_FIELDS", + message: "All fields are required", + data: "" + }); const existing = await User.findOne({ where: { email } }); - if (existing) res.status(400).send({'status':"failed",'message':"Email already used",'data': ""}); - + if (existing) { + return res.status(400).send({ + status: "error", + code: "EMAIL_IN_USE", + message: "Email already used", + data: "" + }); + } const hashedPassword = await bcrypt.hash(password, 10); const newUser = await User.create({ name, email, password: hashedPassword }); + return res.status(201).send({ + status: "ok", + code: "REGISTERED", + message: "User registered successfully", + data: newUser + }); - res.status(201).send({'status':"success",'message':"User registered successfully",'data': newUser }); } catch (err) { - res.status(500).send({'status':"failed",'message':err.message }); + return res.status(500).send({ + status: "error", + code: "SERVER_ERROR", + message: "An unexpected error occurred" + }); } }; - -// Login user -// exports.login = async (req, res) => { -// try { -// const { email, password } = req.body; - -// userRole = 'EstablishmentUser' -// userData = await EstablishmentUser.findOne({ where: { email, is_active : { [Op.or]: [true, 1] } } }); -// if (!userData) { -// userData = await User.findOne({ where: { email, is_active : { [Op.or]: [true, 1] } } }); -// userRole = 'Admin' -// if (!userData) res.status(404).send({'status':"failed",'message':"User not found",'data': ""}); -// } - - -// const validPass = await bcrypt.compare(password, userData.password); -// if (!validPass) res.status(401).send({'status':"failed",'message':"Invalid password",'data': ""}); - - -// if(userRole == 'Admin') -// { -// tokenData = { id: userData.id, email: userData.email , name: userData.name , role : userRole , last_login : userData.last_login } -// await User.update({ last_login : Date() }, { where: { id: userData.id } }); -// }else{ -// const establishment_data = await Establishment.findByPk(userData.establishment_id); -// tokenData = { id: userData.id, email: userData.email , name: userData.name , role : userRole , establishment_id: userData.establishment_id , establishment_data , last_login : userData.last_login } -// await EstablishmentUser.update({ last_login : Date(), updated_at: new Date() }, { where: { id: userData.id } }); -// } - -// const token = jwt.sign(tokenData, process.env.JWT_SECRET, { -// expiresIn: "6h", -// }); - -// res.status(200).send({'status':"success",'message':"Login successful",'data': token }); - -// } catch (err) { -// res.status(500).send({'status':"failed",'message':err.message }); -// } -// }; - diff --git a/app/controllers/establishment.controller.js b/app/controllers/establishment.controller.js index 1c7ed53..f0d9088 100644 --- a/app/controllers/establishment.controller.js +++ b/app/controllers/establishment.controller.js @@ -18,15 +18,16 @@ const csv = require("csv-parser"); const path = require("path"); const { version } = require("os"); const sequelize = db.sequelize; +const { sanitizeForLog } = require("../utils/sanitize"); exports.testEmail = async (req, res) => { placeHolderData = { contact_name : 'Gowtham', portal_url : process.env.FE_BASE_URL, - username : '--', - password : '--', - support_email : '--', - support_phone : '--', + username: 'test_user', + password: 'Test@123!', + support_email : process.env.SUPPORT_EMAIL, + support_phone : process.env.SUPPORT_PHONE, } await sendEmailService('gowthamceline46@gmail.com', 'establishment_user_creation_to_user', placeHolderData); @@ -186,15 +187,15 @@ exports.createEstablishment = async (req, res) => { //send email to user placeHolderData = { - contact_name : establishment_user.name, + contact_name : sanitizeForLog(establishment_user.name), portal_url : process.env.FE_BASE_URL, - username : establishment_user.email, + username : sanitizeForLog(establishment_user.email), password : establishment_user.password, support_email : process.env.SUPPORT_EMAIL, support_phone : process.env.SUPPORT_PHONE, } - await sendEmailService(establishment_user.email, 'establishment_user_creation_to_user', placeHolderData); + await sendEmailService(sanitizeForLog(establishment_user.email), 'establishment_user_creation_to_user', placeHolderData); // insert establishment_products // if (Array.isArray(establishment_products) && establishment_products.length > 0) { @@ -297,51 +298,6 @@ exports.createEstablishment = async (req, res) => { } }; -// Get all establishments -// exports.getAllEstablishments = async (req, res) => { -// try { - -// const data = await Establishment.findAll({ -// include: [ -// // { -// // model: EstablishmentUser, -// // as: "users", -// // attributes: ["id", "name", "email", "is_active"], -// // }, -// { -// model: CityTown, -// as: "establishment_city", -// attributes: ["name"], -// }, -// { -// model: Emirate, -// as: "establishment_emirate", -// attributes: ["name"], -// },{ -// model: CityTown, -// as: "corporate_city", -// attributes: ["name"], -// }, -// { -// model: Emirate, -// as: "corporate_emirate", -// attributes: ["name"], -// }, -// { -// model: user, -// as: "created_user", -// attributes: ["name"], -// } - -// ], -// }); - -// return res.status(200).send({'status':"success",'message':"Fetched successfully",'data': data }); - -// } catch (err) { -// return res.status(500).send({'status':"failed",'message':err.message }); -// } -// }; exports.getAllEstablishments = async (req, res) => { try { let { @@ -1026,7 +982,9 @@ exports.forgotPasswordRequestOTP = async (req, res) => { `

Dear ${adminUser.name},

Your OTP for password reset is ${otp}. It is valid for 10 minutes.

` ); } - logger.info(`OTP sent to ${registered_email}`); + + const safeEmail = sanitizeForLog(registered_email); + logger.info(`OTP sent email: ${safeEmail}`); return res.status(200).json({ status: "success", @@ -1100,7 +1058,8 @@ exports.forgotPasswordVerifyOTP = async (req, res) => { } - logger.info(`Password reset successful for user=${registered_email}`); + const safeEmail = sanitizeForLog(registered_email); + logger.info(`Password reset successful for user: ${safeEmail}`); return res.status(200).json({ status: "success", diff --git a/app/controllers/establishment_user.controller.js b/app/controllers/establishment_user.controller.js index 630a3fc..4e64c82 100644 --- a/app/controllers/establishment_user.controller.js +++ b/app/controllers/establishment_user.controller.js @@ -3,6 +3,7 @@ const bcrypt = require("bcryptjs"); const EstablishmentUser = db.EstablishmentUser; const { sendEmailService } = require("../services/email.service"); +const { sanitizeForLog } = require("../utils/sanitize"); // Create User exports.createUser = async (req, res) => { @@ -11,10 +12,25 @@ exports.createUser = async (req, res) => { const { establishment_id, name, email, password , gender} = req.body; const hashed = await bcrypt.hash(password, 10); - // Check if establishment user already exists - const existingEstablishmentUser = await EstablishmentUser.findOne({where: { email }, }); - if (existingEstablishmentUser) { - return res.status(400).send({status: "failed",message: "Email already exists", }); + email = sanitizeForLog(email || ""); + name = sanitizeForLog(name || ""); + gender = sanitizeForLog(gender || ""); + + if (!establishment_id || !email) { + return res.status(400).send({ + status: "error", + code: "MISSING_FIELDS", + message: "establishment_id and email are required" + }); + } + + const existing = await EstablishmentUser.findOne({ where: { email, is_active: true } }); + if (existing) { + return res.status(400).send({ + status: "error", + code: "EMAIL_EXISTS", + message: "Email already exists" + }); } const user = await EstablishmentUser.create({ @@ -38,10 +54,10 @@ exports.createUser = async (req, res) => { } await sendEmailService(email, 'establishment_user_creation_to_user', placeHolderData); - return res.status(201).send({'status':"success",'message':"Creation successful",'data': user }); + return res.status(201).send({'status':"ok", 'message':"Creation successful",'data': user }); } catch (err) { - return res.status(500).send({'status':"failed",'message':err.message }); + return res.status(500).send({'status':"error",'message':err.message }); } }; @@ -108,23 +124,39 @@ exports.changeUserPassword = async (req, res) => { // Validate inputs if (!old_password || !new_password || !confirm_password) { - return res.status(400).send({ status: "failed", message: "All password fields are required" }); + return res.status(400).send({ + status: "error", + code: "MISSING_FIELDS", + message: "All password fields are required" + }); } if (new_password !== confirm_password) { - return res.status(400).send({ status: "failed", message: "New password and confirm password do not match" }); + return res.status(400).send({ + status: "error", + code: "PASSWORD_MISMATCH", + message: "New password and confirm password do not match" + }); } // Find user const user = await EstablishmentUser.findByPk(userId); if (!user) { - return res.status(404).send({ status: "failed", message: "User not found" }); + return res.status(404).send({ + status: "error", + code: "USER_NOT_FOUND", + message: "User not found" + }); } // Verify old password const isMatch = await bcrypt.compare(old_password, user.password); if (!isMatch) { - return res.status(400).send({ status: "failed", message: "Old password is incorrect" }); + return res.status(401).send({ + status: "error", + code: "OLD_PASSWORD_INCORRECT", + message: "Old password is incorrect" + }); } // Hash and update new password @@ -134,10 +166,18 @@ exports.changeUserPassword = async (req, res) => { { where: { id: userId } } ); - return res.status(200).send({status: "success", message: "Password updated successfully",}); - + return res.status(200).send({ + status: "ok", + code: "PASSWORD_UPDATED", + message: "Password updated successfully" + }); + } catch (err) { - return res.status(500).send({ status: "failed", message: err.message }); + return res.status(500).send({ + status: "error", + code: "SERVER_ERROR", + message: "An unexpected error occurred" + }); } }; diff --git a/app/controllers/submission.controller.js b/app/controllers/submission.controller.js index 59eea7a..7ccc24f 100644 --- a/app/controllers/submission.controller.js +++ b/app/controllers/submission.controller.js @@ -18,7 +18,7 @@ const { Sequelize } = require("sequelize"); const { sendEmail } = require("../services/email.service"); const { sendEmailService } = require("../services/email.service"); const { getQuarterPeriods } = require("../services/quarterService"); - +const { sanitizeForLog } = require("../utils/sanitize"); exports.createSubmission = async (req, res) => { @@ -741,24 +741,30 @@ exports.approveOrRejectSubmission = async (req, res) => { try { const { id } = req.params; - approve_reject_status = req.body.approve_reject_status; - reject_reason = req.body.reject_reason; - approve_or_reject_by = req.body.approve_or_reject_by || req.user.id; + const approve_reject_status = sanitizeForLog(req.body.approve_reject_status); + const reject_reason = sanitizeForLog(req.body.reject_reason || ""); + const approve_or_reject_by = sanitizeForLog(req.body.approve_or_reject_by || req.user.id); - if(approve_reject_status == 1) - { - statusString = 'Approved'; - }else{ - statusString = 'Rejected'; + if (![1, "1", 0, "0"].includes(approve_reject_status)) { + return res.status(400).json({ + status: "failed", + message: "approve_reject_status must be 1 or 0" + }); } - // Check submission exists + const statusString = approve_reject_status == 1 ? "Approved" : "Rejected"; const submission = await Submission.findByPk(id); if (!submission) return res.status(404).json({ status: "failed", message: "Submission not found" }); - // Update submission main data - const result = await submission.update({ approve_reject_status : approve_reject_status , status : statusString, reject_reason, approve_or_reject_by: approve_or_reject_by, updated_by: req.user.id, updated_at: new Date() }); + const result = await submission.update({ + approve_reject_status, + status: statusString, + reject_reason, + approve_or_reject_by, + updated_by: req.user.id, + updated_at: new Date() + }); //send email to establishment user @@ -769,54 +775,42 @@ exports.approveOrRejectSubmission = async (req, res) => { { for await (const userObj of EstablishmentUserData) { - placeHolderData = { - user_name : userObj.name, - portal_url : process.env.FE_BASE_URL, - quarter : result['quarter'], - year : result['year'], - establishment_name : EstablishmentData['factory_name'], - submission_date : result['created_at'], - rejection_reason : result['reject_reason'], - support_email : process.env.SUPPORT_EMAIL, - support_phone : process.env.SUPPORT_PHONE, - } + placeHolderData = { + user_name: sanitizeForLog(userObj.name), + portal_url : process.env.FE_BASE_URL, + quarter : result['quarter'], + year : result['year'], + establishment_name : EstablishmentData['factory_name'], + submission_date : result['created_at'], + rejection_reason : result['reject_reason'], + support_email : process.env.SUPPORT_EMAIL, + support_phone : process.env.SUPPORT_PHONE, + }; - await sendEmailService(userObj.email, 'submission_approved_mail_to_establishment_user', placeHolderData); - } - - }else{ - - for await (const userObj of EstablishmentUserData) - { - placeHolderData = { - user_name : userObj.name, - portal_url : process.env.FE_BASE_URL, - quarter : result['quarter'], - year : result['year'], - establishment_name : EstablishmentData['factory_name'], - submission_date : result['created_at'], - rejection_reason : result['reject_reason'], - support_email : process.env.SUPPORT_EMAIL, - support_phone : process.env.SUPPORT_PHONE, - } + const template = approve_reject_status == 1 + ? "submission_approved_mail_to_establishment_user" + : "submission_rejected_mail_to_establishment_user"; - await sendEmailService(userObj.email, 'submission_rejected_mail_to_establishment_user', placeHolderData); - } + await sendEmailService( + sanitizeForLog(userObj.email), + template, + placeHolderData + ); } - - - res.status(200).json({ + } + return res.status(200).json({ status: "success", message: "Submission edit access updated", data: result, }); - } catch (err) { console.error(err); - res.status(500).json({ status: "failed", message: err.message }); + res.status(500).json({ status: "failed", message: sanitizeForLog(err.message) }); } + }; + exports.getQuarterPeriods = async (req, res) => { try { const { current_year, current_quarter } = req.body; diff --git a/app/controllers/user.controller.js b/app/controllers/user.controller.js index 2c69c2f..a6e8c0c 100644 --- a/app/controllers/user.controller.js +++ b/app/controllers/user.controller.js @@ -15,9 +15,9 @@ exports.createUser = async (req, res) => { const user = await User.create({ name, email, password: hashedPassword }); - logger.info(`User created: ${email}`); + logger.info("User created successfully"); - res.status(201).send({'status':"success",'message':"created successfully" }); + res.status(201).send({'status':"success",'message':"User created successfully" }); } catch (err) { logger.error(err.message); diff --git a/app/routes/routes.js b/app/routes/routes.js index c9bd875..1e4ff50 100644 --- a/app/routes/routes.js +++ b/app/routes/routes.js @@ -131,7 +131,7 @@ const upload = multer({ dest: uploadDir }); * example: john@example.com * password: * type: string - * example: secret123 + * example: "" * responses: * 201: * description: User registered successfully @@ -166,7 +166,7 @@ router.post("/auth/admin_register",[verifySignature], authController.register); * example: john@example.com * password: * type: string - * example: secret123 + * example: "" * responses: * 201: * description: User verifyed successfully @@ -281,7 +281,9 @@ router.get("/admin_users/:id", [verifySignature, verifyToken], adminUserControll * properties: * name: { type: string } * email: { type: string } - * password: { type: string } + * password: + * type: string + * example: "" * responses: * 201: * description: created successfully @@ -388,10 +390,10 @@ router.delete("/admin_users/:id",[verifySignature, verifyToken], adminUserContro * example: "OldPassword@123" * new_password: * type: string - * example: "NewPassword@123" + * example: "ExamplePassword123!" * confirm_password: * type: string - * example: "NewPassword@123" + * example: "ExamplePassword123!" * responses: * 200: * description: Password updated successfully @@ -490,7 +492,9 @@ router.get("/testEmail", establishmentController.testEmail); * properties: * name: { type: string } * email: { type: string } - * password: { type: string } + * password: + * type: string + * example: "" * establishment_products: * type: array * items: @@ -776,7 +780,7 @@ router.post("/establishments/uploadCSV",[verifySignature, verifyToken, upload.si * example: gem@alpha.com * password: * type: string - * example: StrongPass@123 + * example: "" * gender: * type: string * example: male @@ -871,7 +875,7 @@ router.get("/establishment-users/:id",[verifySignature, verifyToken], establishm * example: gem.updated@alpha.com * password: * type: string - * example: NewPassword@123 + * example: "" * gender: * type: string * example: male @@ -920,10 +924,10 @@ router.put("/establishment-users/:id",[verifySignature, verifyToken], establishm * example: "OldPassword@123" * new_password: * type: string - * example: "NewPassword@123" + * example: "ExamplePassword123!" * confirm_password: * type: string - * example: "NewPassword@123" + * example: "ExamplePassword123!" * responses: * 200: * description: Password updated successfully @@ -971,61 +975,6 @@ router.delete("/establishment-users/:id",[verifySignature, verifyToken], establi - - - - -// /** -// * @swagger -// * /api/auth/user_login: -// * post: -// * summary: Login as establishment user -// * tags: [Establishments User Auth] -// * security: -// * - appSignature: [] -// * requestBody: -// * required: true -// * content: -// * application/json: -// * schema: -// * type: object -// * required: -// * - email -// * - password -// * properties: -// * email: -// * type: string -// * example: gem@alpha.com -// * password: -// * type: string -// * example: StrongPass@123 -// * responses: -// * 200: -// * description: Login successful -// * 400: -// * description: Missing fields -// * 401: -// * description: Invalid credentials -// * 404: -// * description: User not found -// * 500: -// * description: Server error -// */ -// router.post("/auth/user_login",[verifySignature], establishmentUserAuthController.login); - - - - - - - - - - - - - - /** * @swagger * /api/products: @@ -2670,13 +2619,13 @@ router.put("/quarterly_windows/:id",[verifySignature, verifyToken], quarterlyWin * /api/password-reset-requests: * get: * summary: Get all establishment password reset requests - * tags: [Establishment Password Reset Requests] + * tags: [Establishment Reset Requests] * security: * - appSignature: [] - * cookieAuth: [] # or bearerAuth: [] if you use Authorization header + * cookieAuth: [] * responses: * 200: - * description: List of all password reset requests + * description: List of all reset requests * 500: * description: Internal server error */ @@ -2687,11 +2636,11 @@ router.get("/password-reset-requests", establishmentController.getAllRequests); * @swagger * /api/password-reset-requests: * post: - * summary: Create a new establishment password reset request - * tags: [Establishment Password Reset Requests] + * summary: Create a new establishment reset request + * tags: [Establishment Reset Requests] * security: * - appSignature: [] - * cookieAuth: [] # or bearerAuth: [] if you use Authorization header + * cookieAuth: [] * requestBody: * required: true * content: @@ -2720,10 +2669,10 @@ router.get("/password-reset-requests", establishmentController.getAllRequests); * example: "+971501234567" * additional_notes: * type: string - * example: "Forgot credentials and need password reset." + * example: "Forgot credentials and need account reset." * responses: * 201: - * description: Password reset request created successfully + * description: Reset request created successfully * 400: * description: Validation failed or mismatch between establishment and email * 404: @@ -2740,8 +2689,8 @@ router.post("/password-reset-requests", establishmentController.createRequest); * @swagger * /api/forgot-password/request-otp: * post: - * summary: Request OTP for establishment user password reset - * tags: [Establishment Password Reset Requests] + * summary: Request OTP for establishment user pwd reset + * tags: [Establishment Pwd Reset Requests] * requestBody: * required: true * content: @@ -2756,6 +2705,7 @@ router.post("/password-reset-requests", establishmentController.createRequest); * establishment_name: { type: string, example: "ABC Industries" } * establishment_code: { type: string, example: "EST1234" } * registered_email: { type: string, example: "contact@abcindustries.com" } + * user_type: {type: string, example: "establishment_user"} * responses: * 200: * description: OTP sent successfully @@ -2791,8 +2741,8 @@ router.post("/forgot-password/request-otp", establishmentController.forgotPasswo * properties: * registered_email: { type: string, example: "contact@abcindustries.com" } * otp: { type: string, example: "123456" } - * password: { type: string, example: "NewPassword@123" } - * confirm_password: { type: string, example: "NewPassword@123" } + * password: { type: string, example: "ExamplePassword123!" } + * confirm_password: { type: string, example: "ExamplePassword123!" } * responses: * 200: * description: Password reset successfully diff --git a/app/services/email.service.js b/app/services/email.service.js index f5bd10d..db19e6c 100644 --- a/app/services/email.service.js +++ b/app/services/email.service.js @@ -2,6 +2,7 @@ const nodemailer = require("nodemailer"); const { NotificationTemplate } = require("../models"); // adjust path if needed const logger = require("../services/logger"); require("dotenv").config(); +const { sanitizeForLog } = require("../utils/sanitize"); const port = Number(process.env.MAIL_PORT); @@ -45,12 +46,19 @@ function replacePlaceholders(templateHtml, data) { exports.sendEmailService = async (to, templateCode, data = {}) => { - const logPrefix = `[EMAIL SERVICE][${new Date().toISOString()}]`; - - logger.info(`${logPrefix} → Starting email send process`); - logger.info(`${logPrefix} → Template Code: ${templateCode}`); - logger.info(`${logPrefix} → Recipient: ${to}`); - logger.info(`${logPrefix} → Placeholder Data: ${JSON.stringify(data)}`); + + const sanitizedTo = sanitizeForLog(to); + const sanitizedTemplate = sanitizeForLog(templateCode); + const sanitizedData = sanitizeForLog( + typeof data === "object" ? JSON.stringify(data) : String(data) + ); + + const logPrefix = `[EMAIL SERVICE][${new Date().toISOString()}]`; + + logger.info(`${logPrefix} → Starting email process`); + logger.info(`${logPrefix} → Template Code: ${sanitizedTemplate}`); + logger.info(`${logPrefix} → Recipient: ${sanitizedTo}`); + logger.info(`${logPrefix} → Placeholder Data: ${sanitizedData}`); try { // 1. Fetch template from DB @@ -59,7 +67,7 @@ exports.sendEmailService = async (to, templateCode, data = {}) => { }); if (!template) { - logger.error(`${logPrefix} ❌ Template not found for code: ${templateCode}`); + logger.error(`${logPrefix} ❌ Template not found for code:${sanitizedTemplate}`); throw new Error(`Template not found for code: ${templateCode}`); } @@ -81,11 +89,11 @@ exports.sendEmailService = async (to, templateCode, data = {}) => { const info = await transporter.sendMail(mailOptions); logger.info(`${logPrefix} ✅ Email sent successfully`); - logger.info(`${logPrefix} Message ID: ${info.messageId}`); + logger.info(`${logPrefix} Message ID: ${sanitizeForLog(info.messageId)}`); logger.info(`${logPrefix} Response: ${info.response}`); return { - status: "success", + status: "ok", message: "Email sent successfully", messageId: info.messageId, response: info.response, diff --git a/app/utils/sanitize.js b/app/utils/sanitize.js index baa0d77..4d637aa 100644 --- a/app/utils/sanitize.js +++ b/app/utils/sanitize.js @@ -10,3 +10,11 @@ exports.sanitizeHtml = (html = "") => { disallowedTagsMode: "discard" }); }; + +exports.sanitizeForLog = (value) => { + if (typeof value !== "string") return value; + return value + .replace(/[\r\n]+/g, " ") + .replace(/\t+/g, " ") + .replace(/[^\x20-\x7E]+/g, " "); +};