feat : customer can set User Name

feat : customer can update username.
wip : update the SMS template for user name.
This commit is contained in:
2025-10-24 13:04:58 +05:30
parent 96af9ff264
commit 08e47e2e92
6 changed files with 116 additions and 10 deletions

View File

@@ -1,6 +1,7 @@
{
"cSpell.words": [
"MPIN",
"occured",
"otpgenerator",
"tpassword",
"tpin",

View File

@@ -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,
};

View File

@@ -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' });
}

View File

@@ -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;

View File

@@ -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,
};

View File

@@ -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;