diff --git a/.vscode/settings.json b/.vscode/settings.json index fef8a46..fefb1ec 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "cSpell.words": [ + "emandate", "MPIN", "occured", "otpgenerator", diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 74756f5..b4222b2 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -7,13 +7,14 @@ const { comparePassword } = require('../util/hash'); const customerController = require('../controllers/customer_details.controller.js'); const { setJson, getJson } = require('../config/redis'); - async function login(req, res) { let { customerNo, userName, password, otp } = req.body; const loginType = req.headers['x-login-type'] || 'standard'; if ((!customerNo && !userName) || !password) { - return res.status(400).json({ error: 'customerNo and password are required' }); + 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 @@ -23,24 +24,30 @@ async function login(req, res) { const blockedKey = `login:blocked:${customerNo}`; const attemptsKey = `login:attempts:${customerNo}`; if (!customerNo && userName) { - const result = await db.query('SELECT * FROM users WHERE preferred_name = $1', [ - userName, - ]); + const result = await db.query( + 'SELECT * FROM users WHERE preferred_name = $1', + [userName] + ); if (result.rows.length === 0) { - logger.error("Customer not found with this user name."); - return res.status(404).json({ error: 'No user found with this username.' }); + logger.error('Customer not found with this user name.'); + return res + .status(404) + .json({ error: 'No user found with this username.' }); } - logger.info("Customer found with user name."); + logger.info('Customer found with user name.'); customerNo = result.rows[0].customer_no; } const userCheck = await authService.findUserByCustomerNo(customerNo); + if (!userCheck) { + return res.status(404).json({ error: 'customer not found' }); + } if (loginType.toUpperCase() === 'IB') { // check DB locked flag if (userCheck && userCheck.locked) { await setJson(blockedKey, true, BLOCK_DURATION); - logger.error("USER Account Locked"); + logger.error('USER Account Locked'); return res.status(423).json({ error: 'Your account is locked. Please contact the administrator.', }); @@ -48,9 +55,13 @@ async function login(req, res) { } // --- Step 2: Check migration status - const isMigratedUser = await authService.isMigratedUser(customerNo); - if (isMigratedUser) + const migratedPassword = `${userCheck.customer_no}@KCCB`; + const isMigratedUser = userCheck.password_hash === migratedPassword; + if (isMigratedUser) { + if (password !== migratedPassword) + return res.status(401).json({ error: 'Invalid credentials.' }); return res.status(401).json({ error: 'MIGRATED_USER_HAS_NO_PASSWORD' }); + } // --- Step 3: Validate credentials --- const user = await authService.validateUser(customerNo, password); @@ -61,12 +72,16 @@ async function login(req, res) { attempts += 1; if (attempts >= MAX_ATTEMPTS) { - await db.query('UPDATE users SET locked = true WHERE customer_no = $1', [customerNo]); + await db.query( + 'UPDATE users SET locked = true WHERE customer_no = $1', + [customerNo] + ); await setJson(blockedKey, true, BLOCK_DURATION); await setJson(attemptsKey, 0); return res.status(423).json({ - error: 'Your account has been locked due to multiple failed login attempts. Please contact the administrator.', + error: + 'Your account has been locked due to multiple failed login attempts. Please contact the administrator.', }); } else { await setJson(attemptsKey, attempts, BLOCK_DURATION); @@ -107,6 +122,8 @@ async function login(req, res) { // --- Step 7: Generate token and update last login --- const token = generateToken(user.customer_no); const loginPswExpiry = user.password_hash_expiry; + const mobileTncAccepted = user.tnc_mobile; + const tnc = { mobile: mobileTncAccepted }; const rights = { ibAccess: user.ib_access_level, mbAccess: user.mb_access_level, @@ -116,7 +133,7 @@ async function login(req, res) { customerNo, ]); logger.info(`Login successful | Type: ${loginType}`); - return res.json({ token, FirstTimeLogin, loginPswExpiry, rights }); + return res.json({ token, FirstTimeLogin, loginPswExpiry, rights, tnc }); } catch (err) { logger.error(err, `login failed | Type: ${loginType}`); return res.status(500).json({ error: 'something went wrong' }); @@ -161,7 +178,28 @@ async function setTpin(req, res) { const { tpin } = req.body; if (!/^\d{6}$/.test(tpin)) return res.status(400).json({ error: 'INVALID_TPIN_FORMAT' }); - authService.setTpin(customerNo, tpin); + await authService.setTpin(customerNo, tpin); + return res.json({ message: 'TPIN_SET' }); + } catch (error) { + logger.error(error); + return res.status(500).json({ error: 'SOMETHING_WENT_WRONG' }); + } +} + +async function changeTpin(req, res) { + const customerNo = req.user; + try { + const user = await authService.findUserByCustomerNo(customerNo); + if (!user) return res.status(404).json({ error: 'USER_NOT_FOUND' }); + if (!user.tpin) + return res.status(400).json({ error: 'USER_DOESNT_HAVE_A_TPIN' }); + const { oldTpin, newTpin } = req.body; + const isMatch = await comparePassword(oldTpin, user.tpin); + if (!isMatch) return res.status(400).json({ error: 'TPIN_DOESNT_MATCH' }); + + if (!/^\d{6}$/.test(newTpin)) + return res.status(400).json({ error: 'INVALID_TPIN_FORMAT' }); + await authService.setTpin(customerNo, newTpin); return res.json({ message: 'TPIN_SET' }); } catch (error) { logger.error(error); @@ -298,18 +336,25 @@ async function setUserName(req, res) { 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', + 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()); + 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" }); + 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" }); + 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.'); @@ -321,10 +366,34 @@ async function setUserName(req, res) { } } +async function getTncAcceptanceFlag(req, res) { + try { + const flag = await authService.getTncFlag(req.user, req.client); + res.json({ tnc_accepted: flag }); + } catch (error) { + logger.error(error, 'error occured while getting tnc flag'); + res.status(500).json({ error: 'INTERNAL SERVER ERROR' }); + } +} + +async function setTncAcceptanceFlag(req, res) { + try { + const { flag } = req.body; + if (flag !== 'Y' && flag !== 'N') + res.status(400).json({ error: 'invalid value for flag' }); + await authService.setTncFlag(req.user, req.client, flag); + return res.json({ message: 'SUCCESS' }); + } catch (error) { + logger.error(error, 'error occured while updating tnc flag'); + res.status(500).json({ error: 'INTERNAL SERVER ERROR' }); + } +} + module.exports = { login, tpin, setTpin, + changeTpin, setLoginPassword, setTransactionPassword, fetchUserDetails, @@ -332,4 +401,6 @@ module.exports = { changeTransPassword, isUserNameExits, setUserName, + getTncAcceptanceFlag, + setTncAcceptanceFlag, }; diff --git a/src/middlewares/cooldown.middleware.js b/src/middlewares/cooldown.middleware.js new file mode 100644 index 0000000..80db4e2 --- /dev/null +++ b/src/middlewares/cooldown.middleware.js @@ -0,0 +1,29 @@ +const { logger } = require('../util/logger'); +const { getSingleBeneficiary } = require('../services/beneficiary.service'); + +async function checkBeneficiaryCooldown(req, res, next) { + const cooldownTime = parseInt( + process.env.BENEFICIARY_COOLDOWN_TIME || '60', + 10 + ); + const customerNo = req.user; + const { toAccount } = req.body; + const beneficiary = await getSingleBeneficiary(customerNo, toAccount); + + if (beneficiary) { + const now = new Date(); + const cooldownPeriod = new Date(now.getTime() - cooldownTime * 60 * 1000); + const createdAt = new Date(beneficiary['created_at']); + if (createdAt > cooldownPeriod) { + const remaining = (now - createdAt) / (60 * 1000); + logger.warn('TRANSACTION_FAILED BENEFICIARY_COOLDOWN_ACTIVE'); + return res.status(403).json({ + remaining, + error: 'beneficiary cooldown period active', + }); + } + } + next(); +} + +module.exports = { checkBeneficiaryCooldown }; diff --git a/src/routes/atm.route.js b/src/routes/atm.route.js new file mode 100644 index 0000000..879e2dd --- /dev/null +++ b/src/routes/atm.route.js @@ -0,0 +1,19 @@ +const express = require('express'); +const { logger } = require('../util/logger'); +const db = require('../config/db'); + +const router = express.Router(); + +const atmRoute = async (req, res) => { + try { + const query_str = 'SELECT * FROM atm'; + const result = await db.query(query_str); + return res.json(result.rows); + } catch (error) { + logger.error(error); + res.status(500).json({ error: 'INTERNAL SERVER ERROR' }); + } +}; +router.get('/', atmRoute); + +module.exports = router; diff --git a/src/routes/auth.route.js b/src/routes/auth.route.js index 5bd61f6..60511ef 100644 --- a/src/routes/auth.route.js +++ b/src/routes/auth.route.js @@ -8,12 +8,27 @@ router.post('/login', authController.login); router.get('/user_details', authenticate, authController.fetchUserDetails); router.get('/tpin', authenticate, authController.tpin); router.post('/tpin', authenticate, authController.setTpin); +router.post('/change/tpin', authenticate, authController.changeTpin); 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); +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); +router.get('/tnc', authenticate, authController.getTncAcceptanceFlag); +router.post('/tnc', authenticate, authController.setTncAcceptanceFlag); module.exports = router; diff --git a/src/routes/branch.route.js b/src/routes/branch.route.js new file mode 100644 index 0000000..548a886 --- /dev/null +++ b/src/routes/branch.route.js @@ -0,0 +1,19 @@ +const express = require('express'); +const { logger } = require('../util/logger'); +const db = require('../config/db'); + +const router = express.Router(); + +const branchRoute = async (req, res) => { + try { + const query_str = 'SELECT * FROM branches'; + const result = await db.query(query_str); + return res.json(result.rows); + } catch (error) { + logger.error(error); + res.status(500).json({ error: 'INTERNAL SERVER ERROR' }); + } +}; +router.get('/', branchRoute); + +module.exports = router; diff --git a/src/routes/emandate.route.js b/src/routes/emandate.route.js new file mode 100644 index 0000000..90f7c29 --- /dev/null +++ b/src/routes/emandate.route.js @@ -0,0 +1,24 @@ +const express = require('express'); +const axios = require('axios'); +const { logger } = require('../util/logger'); +const router = express.Router(); +const emandateData = async (req, res) => { + const { data, mandateRequest, mandateType } = req.body; + if (!data || !mandateRequest | !mandateType) + return res.status(404).json({ error: 'DATA NOT FOUND FROM CLIENT' }) + try { + const reqData = { data, mandateRequest, mandateType }; + const response = await axios.post('http://192.168.1.166:9992/kccb/validation', reqData, + { + headers: { 'Content-Type': 'application/json', }, + } + ); + logger.info("Data validate"); + return response.data; + } catch (error) { + logger.error(error, 'error occured while E-Mandate validation'); + return res.status(500).json({ error: 'INTERNAL_SERVER_ERROR' }); + } +}; +router.post('/validation', emandateData); +module.exports = router; \ No newline at end of file diff --git a/src/routes/imps.route.js b/src/routes/imps.route.js index f663283..dc4ecaf 100644 --- a/src/routes/imps.route.js +++ b/src/routes/imps.route.js @@ -4,9 +4,17 @@ const { logger } = require('../util/logger'); const impsValidator = require('../validators/imps.validator'); const paymentSecretValidator = require('../validators/payment.secret.validator'); const { checkLimit } = require('../middlewares/limitCheck.middleware'); +const { + checkBeneficiaryCooldown, +} = require('../middlewares/cooldown.middleware'); const router = express.Router(); -router.use(impsValidator, paymentSecretValidator, checkLimit); +router.use( + impsValidator, + paymentSecretValidator, + checkLimit, + checkBeneficiaryCooldown +); const impsRoute = async (req, res) => { const { fromAccount, toAccount, ifscCode, amount, beneficiaryName, remarks } = diff --git a/src/routes/index.js b/src/routes/index.js index 45ed4ae..fc9a525 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -10,10 +10,16 @@ const beneficiaryRoute = require('./beneficiary.route'); const neftRoute = require('./neft.route'); const rtgsRoute = require('./rtgs.route'); const impsRoute = require('./imps.route'); +const branchRoute = require('./branch.route'); +const atmRoute = require('./atm.route'); const { npciResponse } = require('../controllers/npci.controller'); const otp = require('./otp.route'); +<<<<<<< HEAD const reports =require('./report.route'); +======= +const eMandate = require('./emandate.route'); +>>>>>>> 7e162e741d4d126fd029b1875bd5e4e0d3c460cf const router = express.Router(); router.use('/auth', authRoute); @@ -28,5 +34,8 @@ router.use('/beneficiary', authenticate, beneficiaryRoute); router.use('/npci/beneficiary-response', npciResponse); router.use('/report',adminAuthenticate,reports); router.use('/otp', otp); +router.use('/e-mandate', authenticate, eMandate); +router.use('/branch', authenticate, branchRoute); +router.use('/atm', authenticate, atmRoute); module.exports = router; diff --git a/src/routes/neft.route.js b/src/routes/neft.route.js index bccbed7..d62d6b8 100644 --- a/src/routes/neft.route.js +++ b/src/routes/neft.route.js @@ -4,9 +4,17 @@ const { logger } = require('../util/logger'); const neftValidator = require('../validators/neft.validator.js'); const paymentSecretValidator = require('../validators/payment.secret.validator'); const { checkLimit } = require('../middlewares/limitCheck.middleware'); +const { + checkBeneficiaryCooldown, +} = require('../middlewares/cooldown.middleware'); const router = express.Router(); -router.use(neftValidator, paymentSecretValidator, checkLimit); +router.use( + neftValidator, + paymentSecretValidator, + checkLimit, + checkBeneficiaryCooldown +); const neftRoute = async (req, res) => { const { diff --git a/src/routes/rtgs.route.js b/src/routes/rtgs.route.js index 7534715..9a8c189 100644 --- a/src/routes/rtgs.route.js +++ b/src/routes/rtgs.route.js @@ -4,9 +4,17 @@ const { logger } = require('../util/logger'); const rtgsValidator = require('../validators/rtgs.validator.js'); const paymentSecretValidator = require('../validators/payment.secret.validator'); const { checkLimit } = require('../middlewares/limitCheck.middleware'); +const { + checkBeneficiaryCooldown, +} = require('../middlewares/cooldown.middleware'); const router = express.Router(); -router.use(rtgsValidator, paymentSecretValidator, checkLimit); +router.use( + rtgsValidator, + paymentSecretValidator, + checkLimit, + checkBeneficiaryCooldown +); const rtgsRoute = async (req, res) => { const { diff --git a/src/routes/transfer.route.js b/src/routes/transfer.route.js index 6602b4d..dfabc49 100644 --- a/src/routes/transfer.route.js +++ b/src/routes/transfer.route.js @@ -4,9 +4,17 @@ const express = require('express'); const transferValidator = require('../validators/transfer.validator'); const passwordValidator = require('../validators/payment.secret.validator.js'); const { checkLimit } = require('../middlewares/limitCheck.middleware'); +const { + checkBeneficiaryCooldown, +} = require('../middlewares/cooldown.middleware'); const router = express.Router(); -router.use(passwordValidator, transferValidator, checkLimit); +router.use( + passwordValidator, + transferValidator, + checkLimit, + checkBeneficiaryCooldown +); const transferRoute = async (req, res) => { const { fromAccount, toAccount, toAccountType, amount, remarks } = req.body; diff --git a/src/services/auth.service.js b/src/services/auth.service.js index 5dd1922..367141f 100644 --- a/src/services/auth.service.js +++ b/src/services/auth.service.js @@ -127,16 +127,16 @@ 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', + 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;; + return result.rows[0].preferred_name; } else { return null; } - } - catch (error) { + } catch (error) { throw new Error( `error occurred while fetch the preferred name ${error.message}` ); @@ -150,12 +150,12 @@ async function setUserName(customerNo, username) { 'UPDATE users SET preferred_name = $1 ,updated_at = $2 WHERE customer_no = $3', [username, currentTime, customerNo] ); - logger.info("user table updated"); + logger.info('user table updated'); await db.query( - "INSERT INTO preferred_name_history (customer_no, preferred_name) VALUES ($1, $2)", + 'INSERT INTO preferred_name_history (customer_no, preferred_name) VALUES ($1, $2)', [customerNo, username] ); - logger.info("preferred_name_history table updated"); + logger.info('preferred_name_history table updated'); } catch (error) { if (error.code === '23505') { throw new Error('PREFERRED_NAME_ALREADY_EXISTS'); @@ -166,6 +166,32 @@ async function setUserName(customerNo, username) { } } +async function getTncFlag(customerNo, clientType) { + let query = ''; + if (clientType === 'MB') { + query = 'SELECT tnc_mobile AS tnc_flag FROM users WHERE customer_no = $1'; + } else if (clientType === 'IB') { + query = 'SELECT tnc_inb AS tnc_flag FROM users WHERE customer_no = $1'; + } else { + throw new Error('UNKNOWN_CLIENT_TYPE. ONLY IB AND MB ALLOWED'); + } + + const result = await db.query(query, [customerNo]); + return result.rows[0]['tnc_flag']; +} + +async function setTncFlag(customerNo, clientType, flag) { + let query = ''; + if (clientType === 'MB') { + query = 'UPDATE users SET tnc_mobile = $1 WHERE customer_no = $2'; + } else if (clientType === 'IB') { + query = 'UPDATE users SET tnc_inb = $1 WHERE customer_no = $2'; + } else { + throw new Error('UNKNOWN_CLIENT_TYPE. ONLY IB AND MB ALLOWED'); + } + await db.query(query, [flag, customerNo]); +} + module.exports = { validateUser, findUserByCustomerNo, @@ -180,5 +206,6 @@ module.exports = { isMigratedUser, CheckUserName, setUserName, - + getTncFlag, + setTncFlag, }; diff --git a/src/services/beneficiary.service.js b/src/services/beneficiary.service.js index d5a6cfc..7068daf 100644 --- a/src/services/beneficiary.service.js +++ b/src/services/beneficiary.service.js @@ -36,7 +36,7 @@ async function validateOutsideBank(accountNo, ifscCode, name) { async function getSingleBeneficiary(customerNo, accountNo) { const queryStr = - 'SELECT b.account_no, b.name, b.account_type, b.ifsc_code, i.bank_name, i.branch_name FROM beneficiaries b JOIN ifsc_details i ON b.ifsc_code = i.ifsc_code WHERE customer_no = $1 AND account_no = $2'; + 'SELECT b.account_no, b.name, b.account_type, b.ifsc_code, b.created_at, i.bank_name, i.branch_name FROM beneficiaries b JOIN ifsc_details i ON b.ifsc_code = i.ifsc_code WHERE customer_no = $1 AND account_no = $2'; const result = await db.query(queryStr, [customerNo, accountNo]); return result.rows[0]; } @@ -53,13 +53,14 @@ async function deleteBeneficiary(customerNo, beneficiaryAccountNo) { async function getAllBeneficiaries(customerNo) { const queryStr = - 'SELECT b.account_no, b.name, b.account_type, b.ifsc_code, i.bank_name, i.branch_name FROM beneficiaries b JOIN LATERAL( SELECT * FROM ifsc_details i WHERE i.ifsc_code = b.ifsc_code LIMIT 1 ) i ON true WHERE customer_no = $1'; + 'SELECT b.account_no, b.name, b.account_type, b.ifsc_code, b.created_at, i.bank_name, i.branch_name FROM beneficiaries b JOIN LATERAL( SELECT * FROM ifsc_details i WHERE i.ifsc_code = b.ifsc_code LIMIT 1 ) i ON true WHERE customer_no = $1'; const result = await db.query(queryStr, [customerNo]); const list = result.rows.map((row) => { const details = { accountNo: row['account_no'], name: row['name'], accountType: row['account_type'], + createdAt: row['created_at'], }; if (row['ifsc_code'] === '_') { details['bankName'] = 'THE KANGRA CENTRAL COOPERATIVE BANK LIMITED';