From 08e47e2e924c5d62291f626b6453388cd773af95 Mon Sep 17 00:00:00 2001 From: "tomosa.sarkar" Date: Fri, 24 Oct 2025 13:04:58 +0530 Subject: [PATCH] feat : customer can set User Name feat : customer can update username. wip : update the SMS template for user name. --- .vscode/settings.json | 1 + src/controllers/auth.controller.js | 63 ++++++++++++++++++++++++++---- src/controllers/otp.controller.js | 8 ++++ src/routes/auth.route.js | 2 + src/services/auth.service.js | 42 ++++++++++++++++++++ src/util/sms_template.js | 10 ++++- 6 files changed, 116 insertions(+), 10 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9be9fc9..f05d849 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "MPIN", + "occured", "otpgenerator", "tpassword", "tpin", diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 2a3dc46..0e05672 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -12,11 +12,9 @@ async function login(req, res) { const { customerNo, password, otp } = req.body; const loginType = req.headers['x-login-type'] || 'standard'; - if (!customerNo || !password) { return res.status(400).json({ error: 'customerNo and password are required' }); } - const currentTime = new Date().toISOString(); const MAX_ATTEMPTS = 3; // Max invalid attempts before lock const BLOCK_DURATION = 15 * 60 * 60; // 1 day @@ -25,7 +23,6 @@ async function login(req, res) { const blockedKey = `login:blocked:${customerNo}`; const attemptsKey = `login:attempts:${customerNo}`; - const userCheck = await authService.findUserByCustomerNo(customerNo); if (loginType.toUpperCase() === 'IB') { @@ -70,7 +67,6 @@ async function login(req, res) { } } - // --- Step 4: If login successful, reset Redis attempts --- await setJson(attemptsKey, 0); // reset counter @@ -103,12 +99,10 @@ async function login(req, res) { ibAccess: user.ib_access_level, mbAccess: user.mb_access_level, }; - await db.query('UPDATE users SET last_login = $1 WHERE customer_no = $2', [ currentTime, customerNo, ]); - logger.info(`Login successful | Type: ${loginType}`); return res.json({ token, FirstTimeLogin, loginPswExpiry, rights }); } catch (err) { @@ -117,7 +111,6 @@ async function login(req, res) { } } - async function fetchUserDetails(req, res) { const customerNo = req.user; try { @@ -146,7 +139,6 @@ async function tpin(req, res) { } } - async function setTpin(req, res) { const customerNo = req.user; try { @@ -259,6 +251,59 @@ async function changeTransPassword(req, res) { } } +async function isUserNameExits(req, res) { + try { + const customerNo = req.user; + const user = await authService.findUserByCustomerNo(customerNo); + if (!user) { + return res.status(404).json({ error: 'USER_NOT_FOUND' }); + } + const userName = await authService.CheckUserName(customerNo); + return res.json({ user_name: userName }); + } catch (error) { + console.error(error); + return res.status(500).json({ error: 'INTERNAL_SERVER_ERROR' }); + } +} + +async function setUserName(req, res) { + const customerNo = req.user; + try { + const user = await authService.findUserByCustomerNo(customerNo); + if (!user) { + return res.status(404).json({ error: 'USER_NOT_FOUND' }); + } + const userNameIsExits = await authService.CheckUserName(customerNo); + const { user_name } = req.body; + if (!userNameIsExits) { + await authService.setUserName(customerNo, user_name); + logger.info('User name has been set for first time.'); + return res.json({ message: 'All set! Your username has been saved.' }); + } + if (userNameIsExits) { + const historyRes = await db.query('SELECT preferred_name FROM preferred_name_history WHERE customer_no = $1 ORDER BY changed_at DESC LIMIT 5', + [customerNo] + ); + // maximum 5 times can changed username + const history = historyRes.rows.map((r) => r.preferred_name.toLowerCase()); + if (history.length >= 5) { + return res.status(429).json({ error: "Preferred name change limit reached -5 times" }); + } + // Cannot match last 2 + const lastTwo = history.slice(0, 2); + if (lastTwo.includes(user_name.toLowerCase())) { + return res.status(409).json({ error: "Preferred name cannot match last 2 preferred names"}); + } + await authService.setUserName(customerNo, user_name); + logger.info('User name has been updated.'); + return res.json({ message: 'All set! Your username has been updated.' }); + } + } catch (error) { + logger.error(error); + return res.status(500).json({ error: 'CANNOT UPDATE USER NAME' }); + } +} + module.exports = { login, tpin, @@ -268,4 +313,6 @@ module.exports = { fetchUserDetails, changeLoginPassword, changeTransPassword, + isUserNameExits, + setUserName, }; diff --git a/src/controllers/otp.controller.js b/src/controllers/otp.controller.js index 33cc689..231bd7d 100644 --- a/src/controllers/otp.controller.js +++ b/src/controllers/otp.controller.js @@ -21,6 +21,7 @@ async function SendOtp(req, res) { ref, date, userOtp, + PreferName, } = req.body; if (!mobileNumber || !type) { @@ -99,6 +100,13 @@ async function SendOtp(req, res) { otp = generateOTP(6); message = templates.EMandate(otp); break; + case 'USERNAME_UPDATED': + otp = generateOTP(6); + message = templates.USERNAME_UPDATED(otp); + break; + case 'USERNAME_SAVED': + message = templates.USERNAME_SAVED(PreferName); + break; default: return res.status(400).json({ error: 'Invalid OTP type' }); } diff --git a/src/routes/auth.route.js b/src/routes/auth.route.js index 01df214..5bd61f6 100644 --- a/src/routes/auth.route.js +++ b/src/routes/auth.route.js @@ -12,6 +12,8 @@ router.post('/login_password', authenticate, authController.setLoginPassword); router.post('/transaction_password',authenticate,authController.setTransactionPassword); router.post('/change/login_password',authenticate,authController.changeLoginPassword); router.post('/change/transaction_password',authenticate,authController.changeTransPassword); +router.get('/user_name',authenticate,authController.isUserNameExits); +router.post('/user_name',authenticate,authController.setUserName); module.exports = router; diff --git a/src/services/auth.service.js b/src/services/auth.service.js index a466b37..894b948 100644 --- a/src/services/auth.service.js +++ b/src/services/auth.service.js @@ -1,6 +1,7 @@ const db = require('../config/db'); const { comparePassword, hashPassword } = require('../util/hash'); const dayjs = require('dayjs'); +const { logger } = require('../util/logger'); async function findUserByCustomerNo(customerNo) { const result = await db.query('SELECT * FROM users WHERE customer_no = $1', [ @@ -124,6 +125,44 @@ async function changeTransPassword(customerNo, trans_psw) { } } +async function CheckUserName(customerNo) { + try { + const result = await db.query('SELECT preferred_name from users WHERE customer_no = $1', + [customerNo] + ); + if (result.rows.length > 0) { + return result.rows[0].preferred_name;; + } else { + return null; + } + } + catch (error) { + throw new Error( + `error occurred while fetch the preferred name ${error.message}` + ); + } +} + +async function setUserName(customerNo, username) { + const currentTime = dayjs().toISOString(); + try { + await db.query( + 'UPDATE users SET preferred_name = $1 ,updated_at = $2 WHERE customer_no = $3', + [username, currentTime, customerNo] + ); + logger.info("user table updated"); + await db.query( + "INSERT INTO preferred_name_history (customer_no, preferred_name) VALUES ($1, $2)", + [customerNo, username] + ); + logger.info("preferred_name_history table updated"); + } catch (error) { + throw new Error( + `error occured while setting new preferred name ${error.message}` + ); + } +} + module.exports = { validateUser, findUserByCustomerNo, @@ -136,4 +175,7 @@ module.exports = { changeLoginPassword, changeTransPassword, isMigratedUser, + CheckUserName, + setUserName, + }; diff --git a/src/util/sms_template.js b/src/util/sms_template.js index bdf95cc..e4a6373 100644 --- a/src/util/sms_template.js +++ b/src/util/sms_template.js @@ -1,5 +1,5 @@ const templates = { - LOGIN_OTP :(otp,username) =>`Dear Customer, Your username ${username} have been verified. Please enter the OTP: ${otp} to complete your login. -KCCB `, + LOGIN_OTP: (otp, username) => `Dear Customer, Your username ${username} have been verified. Please enter the OTP: ${otp} to complete your login. -KCCB `, IMPS: (otp) => `Dear Customer, Please complete the fund transfer with OTP ${otp} -KCCB`, @@ -46,7 +46,13 @@ const templates = { `Dear Customer, Your CIF rights have been updated. Please log in again to access the features. -KCCB`, EMandate: (otp) => - `Dear Customer, Your OTP for e-Mandate is ${otp}.It is valid for 1 minute.Do not share this OTP with anyone. -KCCB` + `Dear Customer, Your OTP for e-Mandate is ${otp}.It is valid for 1 minute.Do not share this OTP with anyone. -KCCB`, + + USERNAME_UPDATED: (otp) => + `Dear Customer, Your OTP for updating your Preferred Name is ${otp}. It is valid for 1 minute. Do not share this OTP with anyone. -KCCB`, + + USERNAME_SAVED: (PreferName) => + `Dear Customer, Your Preferred Name -${PreferName} has been updated successfully. If this change was not made by you, please contact our support team immediately.` }; module.exports = templates; \ No newline at end of file