From cd1193c7461902716fcfd8eb717825f1ddbc73f3 Mon Sep 17 00:00:00 2001 From: "tomosa.sarkar" Date: Fri, 8 Aug 2025 13:06:08 +0530 Subject: [PATCH 01/16] feat : admin feature --- .env | 3 -- .gitignore | 2 + src/controllers/admin_auth.controller.js | 62 ++++++++++++++++++++++++ src/middlewares/admin.middleware.js | 28 +++++++++++ src/middlewares/auth.middleware.js | 1 - src/routes/admin_auth.route.js | 10 ++++ src/routes/index.js | 3 ++ src/services/admin.auth.service.js | 40 +++++++++++++++ src/util/jwt.js | 6 +-- 9 files changed, 148 insertions(+), 7 deletions(-) delete mode 100644 .env create mode 100644 src/controllers/admin_auth.controller.js create mode 100644 src/middlewares/admin.middleware.js create mode 100644 src/routes/admin_auth.route.js create mode 100644 src/services/admin.auth.service.js diff --git a/.env b/.env deleted file mode 100644 index 34e3e37..0000000 --- a/.env +++ /dev/null @@ -1,3 +0,0 @@ -PORT=8080 -DATABASE_URL=postgresql://kmobile_app_rw:kmobile@localhost:5431/kmobile_banking -JWT_SECRET=supersecret diff --git a/.gitignore b/.gitignore index c2658d7..28d7714 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules/ +.vscode/ +.env/ diff --git a/src/controllers/admin_auth.controller.js b/src/controllers/admin_auth.controller.js new file mode 100644 index 0000000..08cdbcc --- /dev/null +++ b/src/controllers/admin_auth.controller.js @@ -0,0 +1,62 @@ +const adminAuthService = require('../services/admin.auth.service'); +const { generateToken } = require('../util/jwt'); +const { logger } = require('../util/logger'); +const db = require('../config/db'); +// const authenticate = require('../middlewares/auth.middleware'); + +async function login(req, res) { + const { userName, password } = req.body; + + if (!userName || !password) { + return res + .status(400) + .json({ error: 'UserName and Password are required' }); + } + const currentTime = new Date().toISOString(); + try { + const admin = await adminAuthService.validateAdmin(userName, password); + if (!admin) return res.status(401).json({ error: 'invalid credentials' }); + + const token = generateToken(admin.username, 'admin', '1d'); + await db.query('UPDATE admin SET last_login = $1 WHERE username = $2', [ + currentTime, + userName, + ]); + res.json({ token }); + } catch (err) { + logger.error(err, 'login failed'); + res.status(500).json({ error: 'something went wrong' }); + } +} + +async function fetchAdminDetails(req, res) { + const customerNo = req.admin; + try { + const admin = await adminAuthService.findAdminByUserName(customerNo); + if (!admin) return res.status(404).json({ message: 'ADMIN_USER_NOT_FOUND' }); + return res.json(admin); + + } catch (err) { + logger.error(err, 'error occurred while fetching admin details'); + res.status(500).json({ error: 'something went wrong' }); + } +} + +async function getUserDetails(req, res) { + const { CIF } = req.query; + if (!CIF) { + res.status(400).json({ + error: 'CIF number is required', + }); + } + try { + const userDetails = await adminAuthService.getCustomerDetails(CIF); + if (!userDetails) + return res.status(401).json({ error: 'invalid CIF number' }); + return res.json(userDetails); + } catch (error) { + logger.error('while fetching customer details', error); + res.status(500).json({ error: 'invalid CIF number'}); + } +} +module.exports = { login, fetchAdminDetails, getUserDetails }; diff --git a/src/middlewares/admin.middleware.js b/src/middlewares/admin.middleware.js new file mode 100644 index 0000000..0c823e4 --- /dev/null +++ b/src/middlewares/admin.middleware.js @@ -0,0 +1,28 @@ +const { verifyToken } = require('../util/jwt'); +const { logger } = require('../util/logger'); + +function checkAdmin (req,res,next){ + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res + .status(401) + .json({ error: 'missing or malformed authorization header' }); + } + + const token = authHeader.split(' ')[1]; + try { + const payload = verifyToken(token); + // console.log("hi",payload); + if(payload.customerNo && payload.role === 'admin'){ + req.admin = payload.customerNo; + next(); + } + else + return res.status(403).json({error :'Only admin can access'}) + } catch (err) { + logger.error(err, 'error verifying token'); + return res.status(401).json({ error: 'invalid or expired token' }); + } +} +module.exports = checkAdmin; diff --git a/src/middlewares/auth.middleware.js b/src/middlewares/auth.middleware.js index ee2b4c2..dec4e69 100644 --- a/src/middlewares/auth.middleware.js +++ b/src/middlewares/auth.middleware.js @@ -21,5 +21,4 @@ function auth(req, res, next) { return res.status(401).json({ error: 'invalid or expired token' }); } } - module.exports = auth; diff --git a/src/routes/admin_auth.route.js b/src/routes/admin_auth.route.js new file mode 100644 index 0000000..0125f7e --- /dev/null +++ b/src/routes/admin_auth.route.js @@ -0,0 +1,10 @@ +const adminAuthController = require('../controllers/admin_auth.controller'); +const adminAuthenticate = require('../middlewares/admin.middleware'); +const express = require('express'); + +const router = express.Router(); + +router.post('/login', adminAuthController.login); +router.get('/admin_details', adminAuthenticate, adminAuthController.fetchAdminDetails); +router.get('/fetch/customer_details',adminAuthenticate,adminAuthController.getUserDetails); +module.exports = router; diff --git a/src/routes/index.js b/src/routes/index.js index 7a31d85..c503594 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,5 +1,6 @@ const express = require('express'); const authRoute = require('./auth.route'); +const adminAuthRoute =require('./admin_auth.route'); const detailsRoute = require('./customer_details.route'); const transactionRoute = require('./transactions.route'); const authenticate = require('../middlewares/auth.middleware'); @@ -9,9 +10,11 @@ const beneficiaryRoute = require('./beneficiary.route'); const router = express.Router(); router.use('/auth', authRoute); +router.use('/auth/admin',adminAuthRoute); router.use('/customer', authenticate, detailsRoute); router.use('/transactions/account/:accountNo', authenticate, transactionRoute); router.use('/payment/transfer', authenticate, transferRoute); router.use('/beneficiary', beneficiaryRoute); + module.exports = router; diff --git a/src/services/admin.auth.service.js b/src/services/admin.auth.service.js new file mode 100644 index 0000000..3f28b16 --- /dev/null +++ b/src/services/admin.auth.service.js @@ -0,0 +1,40 @@ +const db = require('../config/db'); +const { comparePassword, hashPassword } = require('../util/hash'); +const axios = require('axios'); + +async function findAdminByUserName(customerNo) { + const result = await db.query('SELECT * FROM admin WHERE username = $1', [ + customerNo, + ]); + return result.rows[0]; +} + +async function validateAdmin(customerNo, password) { + const user = await findAdminByUserName(customerNo); + if (!user) return null; + const isMatch = await comparePassword(password, user.password); + return isMatch ? user : null; +} + +async function getCustomerDetails(customerNo) { + try { + const response = await axios.get( + 'http://localhost:8686/kccb/cbs/custInfo/details', + { params: { stcustno: customerNo } } + ); + const details = response.data; + const processedDetails = details.map((acc) => ({ + ...acc, + activeAccounts: details.length, + cifNumber: customerNo, + })); + return processedDetails; + } catch (error) { + logger.error('while fetching customer details', error); + throw new Error( + 'API call failed: ' + (error.response?.data?.message || error.message) + ); + } +} + +module.exports = { validateAdmin, findAdminByUserName,getCustomerDetails }; diff --git a/src/util/jwt.js b/src/util/jwt.js index cea8ad0..e26a641 100644 --- a/src/util/jwt.js +++ b/src/util/jwt.js @@ -2,9 +2,9 @@ const jwt = require('jsonwebtoken'); const { jwtSecret } = require('../config/config'); const { logger } = require('./logger'); -function generateToken(customerNo, expiresIn = '10d') { - logger.info({ customerNo }, 'payload to encode'); - return jwt.sign({ customerNo }, jwtSecret, { expiresIn }); +function generateToken(customerNo, role = 'user', expiresIn = '10d') { + logger.info({ customerNo, role }, 'payload to encode'); + return jwt.sign({ customerNo, role }, jwtSecret, { expiresIn }); } function verifyToken(token) { From 52225828d053cb4c3be03e1072a724bade713629 Mon Sep 17 00:00:00 2001 From: "nabanita.jana" Date: Mon, 25 Aug 2025 11:45:23 +0530 Subject: [PATCH 02/16] OTP binding --- .gitignore | 1 + src/app.js | 1 + src/controllers/neft.controller.js | 2 + src/controllers/otp.controller.js | 98 ++++++++++++++++++++++++++++++ src/otpgenerator.js | 12 ++++ src/routes/index.js | 2 + src/routes/otp.route.js | 13 ++++ src/util/sms_template.js | 23 +++++++ 8 files changed, 152 insertions(+) create mode 100644 src/controllers/otp.controller.js create mode 100644 src/otpgenerator.js create mode 100644 src/routes/otp.route.js create mode 100644 src/util/sms_template.js diff --git a/.gitignore b/.gitignore index 713d500..16208e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ +.vscode/ .env diff --git a/src/app.js b/src/app.js index 3c86b46..c2cd73c 100644 --- a/src/app.js +++ b/src/app.js @@ -17,3 +17,4 @@ app.use((err, _req, res, _next) => { }); module.exports = app; + diff --git a/src/controllers/neft.controller.js b/src/controllers/neft.controller.js index 091b7a3..8cc47dc 100644 --- a/src/controllers/neft.controller.js +++ b/src/controllers/neft.controller.js @@ -30,6 +30,8 @@ async function send( 'API call failed: ' + (error.response?.data?.message || error.message) ); } + + } module.exports = { send }; diff --git a/src/controllers/otp.controller.js b/src/controllers/otp.controller.js new file mode 100644 index 0000000..b3037b5 --- /dev/null +++ b/src/controllers/otp.controller.js @@ -0,0 +1,98 @@ + +const { setJson, getJson } = require('../config/redis'); +const { generateOTP } = require('../otpgenerator'); +const { logger } = require('../util/logger'); +const axios = require('axios'); +const templates = require('../util/sms_template'); + +// Send OTP +async function SendOtp(req, res) { + const { mobileNumber, type, amount, beneficiary, ifsc, acctFrom, acctTo, ref, date } = req.body; + + if (!mobileNumber || !type) { + return res.status(400).json({ error: 'Mobile number and type are required' }); + } + + try { + const otp = generateOTP(6); + let message; + + // Pick template based on type + switch (type) { + case 'IMPS': + message = templates.IMPS(otp); + break; + case 'NEFT': + message = templates.NEFT(otp, amount, beneficiary); + break; + case 'RTGS': + message = templates.RTGS(otp, amount, beneficiary); + break; + case 'BENEFICIARY_ADD': + message = templates.BENEFICIARY_ADD(otp, beneficiary, ifsc); + break; + case 'BENEFICIARY_SUCCESS': + message = templates.BENEFICIARY_SUCCESS(beneficiary); + break; + case 'NOTIFICATION': + message = templates.NOTIFICATION(acctFrom, acctTo, amount, ref, date); + break; + case 'FORGOT_PASSWORD': + message = templates.FORGOT_PASSWORD(otp); + break; + default: + return res.status(400).json({ error: 'Invalid OTP type' }); + } + + // Call SMS API + const response = await axios.post('http://localhost:9999/api/SendtoMessage', { + mobileNumber, + stMessage: message, + }); + + if (response.data) { + // Save OTP only if it's OTP based (skip notifications without OTP) + if (message.includes('OTP')) { + await setJson(`otp:${mobileNumber}`, otp, 300); + } + + logger.info(`Sent OTP [${otp}] for type [${type}] to ${mobileNumber}`); + } + + return res.status(200).json({ message: 'Message sent successfully' }); + + } catch (err) { + logger.error(err, 'Error sending OTP'); + return res.status(500).json({ error: 'Internal server error' }); + } +} + + +// Verify OTP +async function VerifyOtp(req, res) { + const { mobileNumber } = req.query; + const {otp} =req.body + + if (!mobileNumber || !otp) { + return res.status(400).json({ error: 'Phone number and OTP are required' }); + } + + try { + const storedOtp = await getJson(`otp:${mobileNumber}`); + + if (!storedOtp) { + return res.status(400).json({ error: 'OTP expired or not found' }); + } + + if (parseInt(otp, 10) !== parseInt(storedOtp, 10)) { + return res.status(400).json({ error: 'Invalid OTP' }); + } + + return res.status(200).json({ message: 'OTP verified successfully' }); + } catch (err) { + logger.error(err, 'Error verifying OTP'); + return res.status(500).json({ error: 'Internal server error' }); + } +} + +module.exports = { SendOtp, VerifyOtp }; diff --git a/src/otpgenerator.js b/src/otpgenerator.js new file mode 100644 index 0000000..b66a46b --- /dev/null +++ b/src/otpgenerator.js @@ -0,0 +1,12 @@ + +function generateOTP(length) { + const digits = '0123456789'; + let otp = ''; + otp += digits[Math.floor(Math.random() * 9) + 1]; // first digit cannot be zero + for (let i = 1; i < length; i++) { + otp += digits[Math.floor(Math.random() * digits.length)]; + } + return otp; +} + +module.exports = { generateOTP }; diff --git a/src/routes/index.js b/src/routes/index.js index 9599fbd..749ec5c 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -8,6 +8,7 @@ const beneficiaryRoute = require('./beneficiary.route'); const neftRoute = require('./neft.route'); const rtgsRoute = require('./rtgs.route'); const { npciResponse } = require('../controllers/npci.controller'); +const otp = require('./otp.route'); const router = express.Router(); @@ -19,5 +20,6 @@ router.use('/payment/neft', authenticate, neftRoute); router.use('/payment/rtgs', authenticate, rtgsRoute); router.use('/beneficiary', authenticate, beneficiaryRoute); router.use('/npci/beneficiary-response', npciResponse); +router.use('/otp', otp); module.exports = router; diff --git a/src/routes/otp.route.js b/src/routes/otp.route.js new file mode 100644 index 0000000..fdf2a7d --- /dev/null +++ b/src/routes/otp.route.js @@ -0,0 +1,13 @@ + +const express = require('express'); +const otpController = require('../controllers/otp.controller'); + +const router = express.Router(); + +// Send OTP (POST request with body) +router.post('/send', otpController.SendOtp); + +// Verify OTP (GET request with query params ?mobileNumber=xxx&otp=123456) +router.post('/verify', otpController.VerifyOtp); + +module.exports = router; diff --git a/src/util/sms_template.js b/src/util/sms_template.js new file mode 100644 index 0000000..9afb6e3 --- /dev/null +++ b/src/util/sms_template.js @@ -0,0 +1,23 @@ +const templates = { + IMPS: (otp) => `Dear Customer, Please complete the fund transfer with OTP ${otp} -KCCB`, + + NEFT: (otp, amount, beneficiary) => + `Dear Customer, Please complete the NEFT of Rs.${amount} to ${beneficiary} with OTP:${otp} -KCCB`, + + RTGS: (otp, amount, beneficiary) => + `Dear Customer, Please complete the RTGS of Rs.${amount} to ${beneficiary} with OTP:${otp} -KCCB`, + + BENEFICIARY_ADD: (otp, beneficiary, ifsc) => + `Dear Customer, You have added beneficiary ${beneficiary} ${ifsc} for NEFT/RTGS. Please endorse the beneficiary with OTP ${otp} -KCCB`, + + BENEFICIARY_SUCCESS: (beneficiary) => + `Dear Customer, Your Beneficiary: ${beneficiary} for Net Banking is added successfully -KCCB`, + + NOTIFICATION: (acctFrom, acctTo, amount, ref, date) => + `Your A/c ${acctFrom} is debited for Rs. ${amount} to the credit of A/c ${acctTo} thru Net Banking - ref: ${ref} - ${date} - Kangra Central Co-Operative Bank -KCCB`, + + FORGOT_PASSWORD: (otp) => + `Dear Customer, Forgot Password OTP is ${otp} -KCCB`, +}; + +module.exports = templates; \ No newline at end of file From 470d8e15f6f5ca7f87e441bf835fc99b82ba59e5 Mon Sep 17 00:00:00 2001 From: asif Date: Wed, 20 Aug 2025 12:14:40 +0530 Subject: [PATCH 03/16] fixed typo in rtgs.route.js --- src/routes/rtgs.route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/rtgs.route.js b/src/routes/rtgs.route.js index 389bb93..c0832c5 100644 --- a/src/routes/rtgs.route.js +++ b/src/routes/rtgs.route.js @@ -32,7 +32,7 @@ const rtgsRoute = async (req, res) => { return res.json({ message: 'SUCCESS', utr }); } else if (result.status.includes('INSUFFICIENT FUNDS')) { return res.status(422).json({ error: 'INSUFFICIENT_FUNDS' }); - } else if (result.status.include('INVALID CHECK DIGIT')) { + } else if (result.status.includes('INVALID CHECK DIGIT')) { return res.status(400).json({ error: 'INVALID_ACCOUNT_NUMBER' }); } else { return res.status(400).json({ error: 'PROBLEM_TRANSFERRING_FUNDS' }); From 6e6690746aea9c7383b2cd5dec6fe116a6366320 Mon Sep 17 00:00:00 2001 From: asif Date: Fri, 22 Aug 2025 15:31:13 +0530 Subject: [PATCH 04/16] added imps transactions payment feature --- src/controllers/imps.controller.js | 43 ++++++++++++++++++++++++++++++ src/routes/imps.route.js | 36 +++++++++++++++++++++++++ src/routes/index.js | 2 ++ src/validators/imps.validator.js | 36 +++++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 src/controllers/imps.controller.js create mode 100644 src/routes/imps.route.js create mode 100644 src/validators/imps.validator.js diff --git a/src/controllers/imps.controller.js b/src/controllers/imps.controller.js new file mode 100644 index 0000000..0eb933d --- /dev/null +++ b/src/controllers/imps.controller.js @@ -0,0 +1,43 @@ +const axios = require('axios'); +const { logger } = require('../util/logger'); + +async function send( + fromAccount, + toAccount, + amount, + ifscCode, + beneficiaryName, + beneficiaryAcctType = 'SAVING', + remarks = '' +) { + try { + const reqData = { + stBenAccNo: toAccount, + stBeneName: beneficiaryName, + stBenAccType: beneficiaryAcctType, + stBenIFSC: ifscCode, + stFromAccDetails: fromAccount, + stTransferAmount: amount, + stRemarks: remarks, + }; + logger.info(reqData, 'request data to be sent to IMPS server'); + const response = await axios.post( + 'http://localhost:6768/kccb/api/IMPS/Producer', + reqData, + { + headers: { + 'Content-Type': 'application/json', + }, + } + ); + logger.info(response, 'response from IMPS'); + return response.data; + } catch (error) { + logger.error(error, 'error from IMPS'); + throw new Error( + 'API call failed: ' + (error.response?.data?.message || error.message) + ); + } +} + +module.exports = { send }; diff --git a/src/routes/imps.route.js b/src/routes/imps.route.js new file mode 100644 index 0000000..7f6a31d --- /dev/null +++ b/src/routes/imps.route.js @@ -0,0 +1,36 @@ +const express = require('express'); +const impsController = require('../controllers/imps.controller'); +const { logger } = require('../util/logger'); +const impsValidator = require('../validators/imps.validator'); +const paymentSecretValidator = require('../validators/payment.secret.validator'); + +const router = express.Router(); +router.use(impsValidator, paymentSecretValidator); + +const impsRoute = async (req, res) => { + const { fromAccount, toAccount, ifscCode, amount, beneficiaryName } = + req.body; + + try { + const result = await impsController.send( + fromAccount, + toAccount, + amount, + ifscCode, + beneficiaryName, + 'SAVING', + 'check' + ); + + if (result.startsWith('Message produced successfully')) { + return res.json({ message: 'SUCCESS' }); + } else { + return res.json({ error: 'INVALID_REQUEST' }); + } + } catch (error) { + logger.error(error, 'error occured while doing IMPS'); + return res.json({ error: 'INTERVAL_SERVER_ERROR' }); + } +}; + +module.exports = impsRoute; diff --git a/src/routes/index.js b/src/routes/index.js index 749ec5c..8e86aac 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -7,6 +7,7 @@ const transferRoute = require('./transfer.route'); const beneficiaryRoute = require('./beneficiary.route'); const neftRoute = require('./neft.route'); const rtgsRoute = require('./rtgs.route'); +const impsRoute = require('./imps.route'); const { npciResponse } = require('../controllers/npci.controller'); const otp = require('./otp.route'); @@ -18,6 +19,7 @@ router.use('/transactions/account/:accountNo', authenticate, transactionRoute); router.use('/payment/transfer', authenticate, transferRoute); router.use('/payment/neft', authenticate, neftRoute); router.use('/payment/rtgs', authenticate, rtgsRoute); +router.use('/payment/imps', authenticate, impsRoute); router.use('/beneficiary', authenticate, beneficiaryRoute); router.use('/npci/beneficiary-response', npciResponse); router.use('/otp', otp); diff --git a/src/validators/imps.validator.js b/src/validators/imps.validator.js new file mode 100644 index 0000000..76d787d --- /dev/null +++ b/src/validators/imps.validator.js @@ -0,0 +1,36 @@ +const impsValidator = (req, res, next) => { + const { + fromAccount, + toAccount, + amount, + remitterName, + beneficiaryName, + ifscCode, + } = req.body; + + if (!isAccountNumbersValid(fromAccount, toAccount)) { + return res.status(400).json({ error: 'INVALID_ACCOUNT_NUMBER_FORMAT' }); + } + + if (amount < 1) { + return res.status(400).json({ error: 'INVALID_AMOUNT' }); + } + + if (!remitterName || !beneficiaryName) { + return res + .status(400) + .json({ error: 'REMITTER_NAME AND BENEFICIARY_NAME REQUIRED' }); + } + + if (!ifscCode || !/^[A-Z]{4}0[0-9]{6}$/.test(ifscCode)) { + return res.status(400).json({ error: 'INVALID_IFSC_CODE' }); + } + + next(); +}; + +const isAccountNumbersValid = (fromAcct, toAcct) => { + return !(!fromAcct || !toAcct || fromAcct.length != 11 || toAcct.length < 7); +}; + +module.exports = impsValidator; From bb446f41d82f5e3ce1c81e0d560c4d61a64a5acc Mon Sep 17 00:00:00 2001 From: asif Date: Tue, 26 Aug 2025 19:21:16 +0530 Subject: [PATCH 05/16] formatted auth route --- src/routes/auth.route.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/routes/auth.route.js b/src/routes/auth.route.js index ac8c9fb..2ab5178 100644 --- a/src/routes/auth.route.js +++ b/src/routes/auth.route.js @@ -9,6 +9,10 @@ router.get('/user_details', authenticate, authController.fetchUserDetails); router.get('/tpin', authenticate, authController.tpin); router.post('/tpin', authenticate, authController.setTpin); router.post('/login_password', authenticate, authController.setLoginPassword); -router.post('/transaction_password', authenticate, authController.setTransactionPassword); +router.post( + '/transaction_password', + authenticate, + authController.setTransactionPassword +); module.exports = router; From b1f2277c923410a0a5c15e270afea7eed5843e5f Mon Sep 17 00:00:00 2001 From: asif Date: Tue, 26 Aug 2025 11:47:49 +0530 Subject: [PATCH 06/16] fixed a bug where data and pin validations were not called --- src/routes/imps.route.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/imps.route.js b/src/routes/imps.route.js index 7f6a31d..d0ff998 100644 --- a/src/routes/imps.route.js +++ b/src/routes/imps.route.js @@ -32,5 +32,6 @@ const impsRoute = async (req, res) => { return res.json({ error: 'INTERVAL_SERVER_ERROR' }); } }; +router.post('/', impsRoute); -module.exports = impsRoute; +module.exports = router; From 65519e64037d571a94ad4267b7ddd2f6ad800927 Mon Sep 17 00:00:00 2001 From: asif Date: Wed, 27 Aug 2025 12:29:27 +0530 Subject: [PATCH 07/16] added beneficiary deletion feature --- src/controllers/beneficiary.controller.js | 19 +++++++++++++++++++ src/routes/beneficiary.route.js | 4 ++++ src/services/beneficiary.service.js | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/src/controllers/beneficiary.controller.js b/src/controllers/beneficiary.controller.js index ccac3bf..b59d2db 100644 --- a/src/controllers/beneficiary.controller.js +++ b/src/controllers/beneficiary.controller.js @@ -95,6 +95,24 @@ async function getBeneficiary(req, res) { } } +async function deleteBeneficiary(req, res) { + const { beneficiaryAccountNo } = req.params; + try { + await beneficiaryService.deleteBeneficiary(req.user, beneficiaryAccountNo); + res.status(204).send(); + } catch (error) { + if (error.message === 'ACCOUNT_NOT_FOUND') { + logger.warn( + `beneficiary ${beneficiaryAccountNo} does not exist for the customer ${req.user}` + ); + return res.status(400).json({ error: 'INVALID_BENEFICIARY_ACCOUNT_NO' }); + } else { + logger.error(error, 'error deleting beneficiary'); + return res.status(500).json({ error: 'INTERNAL_SERVER_ERROR' }); + } + } +} + async function getIfscDetails(req, res) { const { ifscCode } = req.query; if (!ifscCode) { @@ -127,4 +145,5 @@ module.exports = { addBeneficiary, getIfscDetails, getBeneficiary, + deleteBeneficiary, }; diff --git a/src/routes/beneficiary.route.js b/src/routes/beneficiary.route.js index d1483d8..47e7eba 100644 --- a/src/routes/beneficiary.route.js +++ b/src/routes/beneficiary.route.js @@ -9,5 +9,9 @@ router.get('/validate/outside-bank', beneficiaryController.validateOutsideBank); router.get('/ifsc-details', beneficiaryController.getIfscDetails); router.get('/', beneficiaryController.getBeneficiary); router.post('/', newBeneficiaryValidator, beneficiaryController.addBeneficiary); +router.delete( + '/:beneficiaryAccountNo', + beneficiaryController.deleteBeneficiary +); module.exports = router; diff --git a/src/services/beneficiary.service.js b/src/services/beneficiary.service.js index 136f3d1..1eeb7d1 100644 --- a/src/services/beneficiary.service.js +++ b/src/services/beneficiary.service.js @@ -44,6 +44,16 @@ async function getSingleBeneficiary(customerNo, accountNo) { return result.rows[0]; } +async function deleteBeneficiary(customerNo, beneficiaryAccountNo) { + const queryStr = + 'DELETE FROM beneficiaries WHERE customer_no = $1 AND account_no = $2'; + const result = await db.query(queryStr, [customerNo, beneficiaryAccountNo]); + if (result.rowCount == 0) { + throw new Error('ACCOUNT_NOT_FOUND'); + } + return; +} + 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 ifsc_details i ON b.ifsc_code = i.ifsc_code WHERE customer_no = $1'; @@ -66,4 +76,5 @@ module.exports = { validateOutsideBank, getAllBeneficiaries, getSingleBeneficiary, + deleteBeneficiary, }; From 33aa50413caab6868b8e93163d3858d7947aebdb Mon Sep 17 00:00:00 2001 From: asif Date: Fri, 29 Aug 2025 13:40:57 +0530 Subject: [PATCH 08/16] fix: check if password is not null on login req --- src/controllers/auth.controller.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 9a90add..94e23f8 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -14,7 +14,8 @@ async function login(req, res) { const currentTime = new Date().toISOString(); try { const user = await authService.validateUser(customerNo, password); - if (!user) return res.status(401).json({ error: 'invalid credentials' }); + if (!user || !password) + return res.status(401).json({ error: 'invalid credentials' }); const token = generateToken(user.customer_no, '1d'); const FirstTimeLogin = await authService.CheckFirstTimeLogin(customerNo); await db.query('UPDATE users SET last_login = $1 WHERE customer_no = $2', [ @@ -34,7 +35,6 @@ async function fetchUserDetails(req, res) { const user = await authService.findUserByCustomerNo(customerNo); if (!user) return res.status(404).json({ message: 'USER_NOT_FOUND' }); return res.json(user); - } catch (err) { logger.error(err, 'error occured while fetching user details'); res.status(500).json({ error: 'something went wrong' }); @@ -102,4 +102,11 @@ async function setTransactionPassword(req, res) { } } -module.exports = { login, tpin, setTpin, setLoginPassword, setTransactionPassword,fetchUserDetails }; +module.exports = { + login, + tpin, + setTpin, + setLoginPassword, + setTransactionPassword, + fetchUserDetails, +}; From 6533c5081c437cfd7aab22011b2787d0399cf939 Mon Sep 17 00:00:00 2001 From: asif Date: Fri, 29 Aug 2025 15:40:11 +0530 Subject: [PATCH 09/16] added logger for logging all kinds of requests with headers, body, ip and other data --- src/app.js | 22 +++++++++++++++++++++- src/util/logger.js | 25 +++++++++++++++++++++---- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/app.js b/src/app.js index c2cd73c..e666308 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,6 @@ const express = require('express'); const cors = require('cors'); -const { logger } = require('./util/logger'); +const { logger, requestLogger } = require('./util/logger'); const routes = require('./routes'); const app = express(); @@ -9,6 +9,26 @@ app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); +app.use((req, res, next) => { + const start = Date.now(); + + res.on('finish', () => { + requestLogger.info( + { + ip: req.ip || req.connection.remoteAddress, + method: req.method, + url: req.originalUrl, + headers: req.headers, + body: req.body, + status: res.statusCode, + responseTime: `${Date.now() - start}ms`, + }, + 'Incoming request' + ); + }); + next(); +}); + app.use('/api', routes); app.get('/health', (_, res) => res.send('server is healthy')); app.use((err, _req, res, _next) => { diff --git a/src/util/logger.js b/src/util/logger.js index eb033b0..4b6d33a 100644 --- a/src/util/logger.js +++ b/src/util/logger.js @@ -1,6 +1,19 @@ const pino = require('pino'); +const fs = require('fs'); +const path = require('path'); + const isDev = process.env.NODE_ENV !== 'production'; +const logDir = path.join(__dirname, 'logs'); +if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir); +} + +const requestLoggerStream = pino.destination({ + dest: path.join(logDir, 'requests.log'), + sync: false, +}); + const logger = pino({ transport: isDev ? { @@ -15,8 +28,12 @@ const logger = pino({ level: isDev ? 'debug' : 'info', }); -const requestLogger = (req, _res, next) => { - logger.info(`${req.method} ${req.url}`); - next(); -}; +const requestLogger = pino( + { + level: 'info', + base: null, + }, + requestLoggerStream +); + module.exports = { logger, requestLogger }; From 90f60f73245f3112a6d6072370d3ce8cbe950684 Mon Sep 17 00:00:00 2001 From: asif Date: Fri, 29 Aug 2025 15:40:36 +0530 Subject: [PATCH 10/16] added log files in .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 16208e3..535f0cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ .vscode/ .env +src/util/logs/requests.log From b213259c7cf5ad34081df5ba14b23a6d13afc79b Mon Sep 17 00:00:00 2001 From: asif Date: Fri, 29 Aug 2025 15:49:35 +0530 Subject: [PATCH 11/16] changed the directory of log directory --- .gitignore | 2 +- src/util/logger.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 535f0cf..a4713c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ node_modules/ .vscode/ .env -src/util/logs/requests.log +logs/requests.log diff --git a/src/util/logger.js b/src/util/logger.js index 4b6d33a..18c891f 100644 --- a/src/util/logger.js +++ b/src/util/logger.js @@ -4,7 +4,7 @@ const path = require('path'); const isDev = process.env.NODE_ENV !== 'production'; -const logDir = path.join(__dirname, 'logs'); +const logDir = path.join(__dirname, '../..', 'logs'); if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir); } From 9f6c2f9cc1154ae96b0b02523ddf8c007b8d64d5 Mon Sep 17 00:00:00 2001 From: "tomosa.sarkar" Date: Tue, 2 Sep 2025 12:41:39 +0530 Subject: [PATCH 12/16] feat: user can change login password feat: user can change transaction password wip : Admin feature rights -InProgress --- .gitignore | 2 + .vscode/settings.json | 1 + package-lock.json | 7 +++ package.json | 1 + src/controllers/admin_auth.controller.js | 67 ++++++++++++++++++++++-- src/controllers/auth.controller.js | 55 ++++++++++++++++++- src/controllers/otp.controller.js | 5 +- src/routes/admin_auth.route.js | 1 + src/routes/auth.route.js | 9 ++-- src/services/auth.service.js | 43 +++++++++++++-- src/util/sms_template.js | 3 ++ 11 files changed, 181 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 713d500..a4713c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules/ +.vscode/ .env +logs/requests.log diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c51a6b..029c1b9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "cSpell.words": [ + "otpgenerator", "tpassword", "tpin" ] diff --git a/package-lock.json b/package-lock.json index 2e75d14..d38797a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "axios": "^1.9.0", "bcrypt": "^6.0.0", "cors": "^2.8.5", + "dayjs": "^1.11.18", "dotenv": "^16.5.0", "express": "^5.1.0", "jsonwebtoken": "^9.0.2", @@ -842,6 +843,12 @@ "node": "*" } }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", diff --git a/package.json b/package.json index bb10d6d..308780e 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "axios": "^1.9.0", "bcrypt": "^6.0.0", "cors": "^2.8.5", + "dayjs": "^1.11.18", "dotenv": "^16.5.0", "express": "^5.1.0", "jsonwebtoken": "^9.0.2", diff --git a/src/controllers/admin_auth.controller.js b/src/controllers/admin_auth.controller.js index 08cdbcc..58250ec 100644 --- a/src/controllers/admin_auth.controller.js +++ b/src/controllers/admin_auth.controller.js @@ -1,8 +1,11 @@ const adminAuthService = require('../services/admin.auth.service'); +const authService = require('../services/auth.service'); const { generateToken } = require('../util/jwt'); const { logger } = require('../util/logger'); +const { hashPassword } = require('../util/hash'); const db = require('../config/db'); -// const authenticate = require('../middlewares/auth.middleware'); +const { generateOTP } = require('../otpgenerator'); + async function login(req, res) { const { userName, password } = req.body; @@ -56,7 +59,65 @@ async function getUserDetails(req, res) { return res.json(userDetails); } catch (error) { logger.error('while fetching customer details', error); - res.status(500).json({ error: 'invalid CIF number'}); + res.status(500).json({ error: 'invalid CIF number' }); } } -module.exports = { login, fetchAdminDetails, getUserDetails }; +async function getUserRights(req, res) { + // const { CIF } = req.query; + // if (!CIF) { + // res.status(400).json({ + // error: 'CIF number is required', + // }); + // } + // try { + // const userDetails = await adminAuthService.getCustomerDetails(CIF); + // if (!userDetails) + // return res.status(401).json({ error: 'invalid CIF number' }); + // return res.json(userDetails); + // } catch (error) { + // logger.error('while fetching customer details', error); + // res.status(500).json({ error: 'invalid CIF number'}); + // } +} +async function UserRights(req, res) { + const { CIF, ib_access_level, mb_access_level } = req.body; + const first_time_pass = generateOTP(6); + if (!CIF) { + res.status(400).json({ + error: 'CIF number is required', + }); + } + const currentTime = new Date().toISOString(); + const user = await authService.findUserByCustomerNo(CIF); + const password = await hashPassword(first_time_pass); + if (user) { + try { + await db.query('UPDATE users SET customer_no = $1,password_hash=$2,updated_at=$5,ib_access_level=$3,mb_access_level=$4 WHERE customer_no = $1', [ + CIF, + password, + ib_access_level, + mb_access_level, + currentTime, + ]); + res.json({otp:`${first_time_pass}`}); + } catch (err) { + console.log(err); + logger.error(err, 'Right Update failed'); + res.status(500).json({ error: 'something went wrong' }); + } + } + if (!user) { + try { + await db.query('INSERT INTO users (customer_no, password_hash,ib_access_level,mb_access_level) VALUES ($1, $2, $3, $4)', + [CIF, password, ib_access_level, mb_access_level] + ); + res.json({message:'User created and Rights Updated.'}); + } catch (err) { + console.log(err); + logger.error(err, 'Right Update failed'); + res.status(500).json({ error: 'something went wrong' }); + } + } +} + +module.exports = { login, fetchAdminDetails, getUserDetails,UserRights}; diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 94e23f8..061e159 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -2,6 +2,8 @@ const authService = require('../services/auth.service'); const { generateToken } = require('../util/jwt'); const { logger } = require('../util/logger'); const db = require('../config/db'); +const dayjs =require("dayjs"); +const { comparePassword } = require('../util/hash'); async function login(req, res) { const { customerNo, password } = req.body; @@ -18,11 +20,15 @@ async function login(req, res) { return res.status(401).json({ error: 'invalid credentials' }); const token = generateToken(user.customer_no, '1d'); const FirstTimeLogin = await authService.CheckFirstTimeLogin(customerNo); + // For registration : if try to login first time after 7 days. + if(FirstTimeLogin && dayjs(user.created_at).diff(currentTime,"day") > 8) + return res.status(401).json({ error: 'Password Expired.Please Contact with Administrator' }); + const loginPswExpiry = user.password_hash_expiry; await db.query('UPDATE users SET last_login = $1 WHERE customer_no = $2', [ currentTime, customerNo, ]); - res.json({ token, FirstTimeLogin }); + res.json({ token, FirstTimeLogin, loginPswExpiry }); } catch (err) { logger.error(err, 'login failed'); res.status(500).json({ error: 'something went wrong' }); @@ -88,6 +94,7 @@ async function setLoginPassword(req, res) { return res.status(500).json({ error: 'SOMETHING_WENT_WRONG' }); } } + async function setTransactionPassword(req, res) { const customerNo = req.user; try { @@ -102,6 +109,50 @@ async function setTransactionPassword(req, res) { } } +async function changeLoginPassword(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 { OldLPsw ,newLPsw ,confirmLPsw } = req.body; + const isMatch = await comparePassword(OldLPsw, user.password_hash); + if(!isMatch) + return res.json({error: 'Please Enter Correct Old Login Password'}); + if(newLPsw !== confirmLPsw) + return res.json ({error: 'New Password and Confirm Password not Match'}) + const isMatchWithOldPassword = await comparePassword(newLPsw, user.password_hash); + if(isMatchWithOldPassword) + return res.json ({error: 'New Password will be different from Previous Password'}) + authService.changeLoginPassword(customerNo, newLPsw); + return res.json({ message: 'New Login Password changed successfully' }); + } catch (error) { + logger.error(error); + return res.status(500).json({ error: 'SOMETHING_WENT_WRONG' }); + } +} + +async function changeTransPassword(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 { OldTPsw ,newTPsw ,confirmTPsw } = req.body; + const isMatch = await comparePassword(OldTPsw, user.transaction_password); + if(!isMatch) + return res.json({error: 'Please Enter Correct Old Transaction Password'}); + if(newTPsw !== confirmTPsw) + return res.json ({error: 'New Transaction Password and Confirm Transaction Password not Match'}) + const isMatchWithOldPassword = await comparePassword(newTPsw, user.transaction_password); + if(isMatchWithOldPassword) + return res.json ({error: 'New Transaction Password will be different from Previous Transaction Password'}) + authService.changeTransPassword(customerNo, newTPsw); + return res.json({ message: 'New Transaction Password changed successfully' }); + } catch (error) { + logger.error(error); + return res.status(500).json({ error: 'SOMETHING_WENT_WRONG' }); + } +} + module.exports = { login, tpin, @@ -109,4 +160,6 @@ module.exports = { setLoginPassword, setTransactionPassword, fetchUserDetails, + changeLoginPassword, + changeTransPassword, }; diff --git a/src/controllers/otp.controller.js b/src/controllers/otp.controller.js index b3037b5..6536686 100644 --- a/src/controllers/otp.controller.js +++ b/src/controllers/otp.controller.js @@ -40,6 +40,9 @@ async function SendOtp(req, res) { case 'FORGOT_PASSWORD': message = templates.FORGOT_PASSWORD(otp); break; + case 'REGISTRATION': + message = templates.REGISTRATION(otp); + break; default: return res.status(400).json({ error: 'Invalid OTP type' }); } @@ -71,7 +74,7 @@ async function SendOtp(req, res) { // Verify OTP async function VerifyOtp(req, res) { const { mobileNumber } = req.query; - const {otp} =req.body + const { otp } = req.body if (!mobileNumber || !otp) { return res.status(400).json({ error: 'Phone number and OTP are required' }); diff --git a/src/routes/admin_auth.route.js b/src/routes/admin_auth.route.js index 0125f7e..d4a9a58 100644 --- a/src/routes/admin_auth.route.js +++ b/src/routes/admin_auth.route.js @@ -7,4 +7,5 @@ const router = express.Router(); router.post('/login', adminAuthController.login); router.get('/admin_details', adminAuthenticate, adminAuthController.fetchAdminDetails); router.get('/fetch/customer_details',adminAuthenticate,adminAuthController.getUserDetails); +router.post('/user/rights',adminAuthenticate,adminAuthController.UserRights); module.exports = router; diff --git a/src/routes/auth.route.js b/src/routes/auth.route.js index 2ab5178..01df214 100644 --- a/src/routes/auth.route.js +++ b/src/routes/auth.route.js @@ -9,10 +9,9 @@ router.get('/user_details', authenticate, authController.fetchUserDetails); router.get('/tpin', authenticate, authController.tpin); router.post('/tpin', authenticate, authController.setTpin); router.post('/login_password', authenticate, authController.setLoginPassword); -router.post( - '/transaction_password', - authenticate, - authController.setTransactionPassword -); +router.post('/transaction_password',authenticate,authController.setTransactionPassword); +router.post('/change/login_password',authenticate,authController.changeLoginPassword); +router.post('/change/transaction_password',authenticate,authController.changeTransPassword); + module.exports = router; diff --git a/src/services/auth.service.js b/src/services/auth.service.js index b5cfe71..3ec7f00 100644 --- a/src/services/auth.service.js +++ b/src/services/auth.service.js @@ -1,5 +1,6 @@ const db = require('../config/db'); const { comparePassword, hashPassword } = require('../util/hash'); +const dayjs =require("dayjs"); async function findUserByCustomerNo(customerNo) { const result = await db.query('SELECT * FROM users WHERE customer_no = $1', [ @@ -69,14 +70,48 @@ async function validateTransactionPassword(customerNo, tpassword) { // Set transaction password async function setTransactionPassword(customerNo, trans_psw) { const hashedTransPassword = await hashPassword(trans_psw); + const currentTime = dayjs().toISOString(); + const password_expiry = dayjs().add(90,"day").toISOString(); try { await db.query( - 'UPDATE users SET transaction_password = $1 WHERE customer_no = $2', - [hashedTransPassword, customerNo] + 'UPDATE users SET transaction_password = $1 ,updated_at = $3,transaction_password_expiry =$4 WHERE customer_no = $2', + [hashedTransPassword, customerNo , currentTime ,password_expiry] ); } catch (error) { throw new Error( - `error occured while while setting new Transaction Password ${error.message}` + `error occurred while while setting new Transaction Password ${error.message}` + ); + } +} + +async function changeLoginPassword(customerNo, login_psw) { + const hashedLoginPassword = await hashPassword(login_psw); + const currentTime = dayjs().toISOString(); + const password_expiry = dayjs().add(90,"day").toISOString(); + try { + await db.query( + 'UPDATE users SET password_hash = $1 ,updated_at = $3,password_hash_expiry =$4 WHERE customer_no = $2', + [hashedLoginPassword, customerNo , currentTime ,password_expiry] + ); + } catch (error) { + throw new Error( + `error occured while while setting new Login Password ${error.message}` + ); + } +} + +async function changeTransPassword(customerNo, trans_psw) { + const hashedTransPassword = await hashPassword(trans_psw); + const currentTime = dayjs().toISOString(); + const password_expiry = dayjs().add(90,"day").toISOString(); + try { + await db.query( + 'UPDATE users SET transaction_password = $1 ,updated_at = $3,transaction_password_expiry =$4 WHERE customer_no = $2', + [hashedTransPassword, customerNo , currentTime ,password_expiry] + ); + } catch (error) { + throw new Error( + `error occurred while while setting new Login Password ${error.message}` ); } } @@ -90,4 +125,6 @@ module.exports = { setLoginPassword, validateTransactionPassword, setTransactionPassword, + changeLoginPassword, + changeTransPassword, }; diff --git a/src/util/sms_template.js b/src/util/sms_template.js index 9afb6e3..1587358 100644 --- a/src/util/sms_template.js +++ b/src/util/sms_template.js @@ -18,6 +18,9 @@ const templates = { FORGOT_PASSWORD: (otp) => `Dear Customer, Forgot Password OTP is ${otp} -KCCB`, + + REGISTRATION: (otp) => + `Dear Customer, Your CIF is enable.First time login password: ${otp} (valid for 7 days).Please login and change your password -KCCB`, }; module.exports = templates; \ No newline at end of file From f60329672ce3975afbd166dce071fd9ce047f044 Mon Sep 17 00:00:00 2001 From: "tomosa.sarkar" Date: Tue, 2 Sep 2025 12:52:13 +0530 Subject: [PATCH 13/16] feat : Change login password feature update --- src/services/auth.service.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/services/auth.service.js b/src/services/auth.service.js index 3ec7f00..52e448c 100644 --- a/src/services/auth.service.js +++ b/src/services/auth.service.js @@ -48,14 +48,16 @@ async function setTpin(customerNo, tpin) { // Set login password async function setLoginPassword(customerNo, login_psw) { const hashedLoginPassword = await hashPassword(login_psw); + const currentTime = dayjs().toISOString(); + const password_expiry = dayjs().add(90,"day").toISOString(); try { await db.query( - 'UPDATE users SET password_hash = $1 ,is_first_login = false WHERE customer_no = $2', - [hashedLoginPassword, customerNo] + 'UPDATE users SET password_hash = $1 ,is_first_login = false,updated_at = $3,password_hash_expiry =$4 WHERE customer_no = $2', + [hashedLoginPassword, customerNo ,currentTime ,password_expiry] ); } catch (error) { throw new Error( - `error occured while while setting new Login Password ${error.message}` + `error occurred while while setting new Login Password ${error.message}` ); } } From 4857ef6cab58c93f48c8c940f29de6ad7729841f Mon Sep 17 00:00:00 2001 From: "tomosa.sarkar" Date: Tue, 2 Sep 2025 16:05:11 +0530 Subject: [PATCH 14/16] feat: Change login Password --- src/controllers/auth.controller.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 061e159..cbd9b9b 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -117,12 +117,12 @@ async function changeLoginPassword(req,res){ const { OldLPsw ,newLPsw ,confirmLPsw } = req.body; const isMatch = await comparePassword(OldLPsw, user.password_hash); if(!isMatch) - return res.json({error: 'Please Enter Correct Old Login Password'}); + return res.status(500).json({error: 'Please Enter Correct Old Login Password'}); if(newLPsw !== confirmLPsw) - return res.json ({error: 'New Password and Confirm Password not Match'}) + return res.status(500).json ({error: 'New Password and Confirm Password not Match'}) const isMatchWithOldPassword = await comparePassword(newLPsw, user.password_hash); if(isMatchWithOldPassword) - return res.json ({error: 'New Password will be different from Previous Password'}) + return res.status(500).json ({error: 'New Password will be different from Previous Password'}) authService.changeLoginPassword(customerNo, newLPsw); return res.json({ message: 'New Login Password changed successfully' }); } catch (error) { @@ -139,12 +139,12 @@ async function changeTransPassword(req,res){ const { OldTPsw ,newTPsw ,confirmTPsw } = req.body; const isMatch = await comparePassword(OldTPsw, user.transaction_password); if(!isMatch) - return res.json({error: 'Please Enter Correct Old Transaction Password'}); + return res.status(500).json({error: 'Please Enter Correct Old Transaction Password'}); if(newTPsw !== confirmTPsw) - return res.json ({error: 'New Transaction Password and Confirm Transaction Password not Match'}) + return res.status(500).json ({error: 'New Transaction Password and Confirm Transaction Password not Match'}) const isMatchWithOldPassword = await comparePassword(newTPsw, user.transaction_password); if(isMatchWithOldPassword) - return res.json ({error: 'New Transaction Password will be different from Previous Transaction Password'}) + return res.status(500).json ({error: 'New Transaction Password will be different from Previous Transaction Password'}) authService.changeTransPassword(customerNo, newTPsw); return res.json({ message: 'New Transaction Password changed successfully' }); } catch (error) { From aaa11287a141960e0f58d39ab6244240e7eda651 Mon Sep 17 00:00:00 2001 From: "tomosa.sarkar" Date: Fri, 5 Sep 2025 13:54:25 +0530 Subject: [PATCH 15/16] feat : admin user rights updated. feat : update for otp sent for registration. feat: login Api updated passes "rights" in response --- src/controllers/admin_auth.controller.js | 2 +- src/controllers/auth.controller.js | 45 +++++++++++++----------- src/controllers/otp.controller.js | 5 +-- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/controllers/admin_auth.controller.js b/src/controllers/admin_auth.controller.js index 58250ec..dbcbb8a 100644 --- a/src/controllers/admin_auth.controller.js +++ b/src/controllers/admin_auth.controller.js @@ -111,7 +111,7 @@ async function UserRights(req, res) { await db.query('INSERT INTO users (customer_no, password_hash,ib_access_level,mb_access_level) VALUES ($1, $2, $3, $4)', [CIF, password, ib_access_level, mb_access_level] ); - res.json({message:'User created and Rights Updated.'}); + res.json({otp:`${first_time_pass}`}); } catch (err) { console.log(err); logger.error(err, 'Right Update failed'); diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index cbd9b9b..53aa2d3 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -2,7 +2,7 @@ const authService = require('../services/auth.service'); const { generateToken } = require('../util/jwt'); const { logger } = require('../util/logger'); const db = require('../config/db'); -const dayjs =require("dayjs"); +const dayjs = require("dayjs"); const { comparePassword } = require('../util/hash'); async function login(req, res) { @@ -18,17 +18,22 @@ async function login(req, res) { const user = await authService.validateUser(customerNo, password); if (!user || !password) return res.status(401).json({ error: 'invalid credentials' }); - const token = generateToken(user.customer_no, '1d'); const FirstTimeLogin = await authService.CheckFirstTimeLogin(customerNo); // For registration : if try to login first time after 7 days. - if(FirstTimeLogin && dayjs(user.created_at).diff(currentTime,"day") > 8) + if (FirstTimeLogin && dayjs(user.created_at).diff(currentTime, "day") > 8) return res.status(401).json({ error: 'Password Expired.Please Contact with Administrator' }); + + const token = generateToken(user.customer_no, '1d'); const loginPswExpiry = user.password_hash_expiry; + const rights = { + 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, ]); - res.json({ token, FirstTimeLogin, loginPswExpiry }); + res.json({ token, FirstTimeLogin, loginPswExpiry, rights }); } catch (err) { logger.error(err, 'login failed'); res.status(500).json({ error: 'something went wrong' }); @@ -109,20 +114,20 @@ async function setTransactionPassword(req, res) { } } -async function changeLoginPassword(req,res){ +async function changeLoginPassword(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 { OldLPsw ,newLPsw ,confirmLPsw } = req.body; + const { OldLPsw, newLPsw, confirmLPsw } = req.body; const isMatch = await comparePassword(OldLPsw, user.password_hash); - if(!isMatch) - return res.status(500).json({error: 'Please Enter Correct Old Login Password'}); - if(newLPsw !== confirmLPsw) - return res.status(500).json ({error: 'New Password and Confirm Password not Match'}) + if (!isMatch) + return res.status(500).json({ error: 'Please Enter Correct Old Login Password' }); + if (newLPsw !== confirmLPsw) + return res.status(500).json({ error: 'New Password and Confirm Password not Match' }) const isMatchWithOldPassword = await comparePassword(newLPsw, user.password_hash); - if(isMatchWithOldPassword) - return res.status(500).json ({error: 'New Password will be different from Previous Password'}) + if (isMatchWithOldPassword) + return res.status(500).json({ error: 'New Password will be different from Previous Password' }) authService.changeLoginPassword(customerNo, newLPsw); return res.json({ message: 'New Login Password changed successfully' }); } catch (error) { @@ -131,20 +136,20 @@ async function changeLoginPassword(req,res){ } } -async function changeTransPassword(req,res){ +async function changeTransPassword(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 { OldTPsw ,newTPsw ,confirmTPsw } = req.body; + const { OldTPsw, newTPsw, confirmTPsw } = req.body; const isMatch = await comparePassword(OldTPsw, user.transaction_password); - if(!isMatch) - return res.status(500).json({error: 'Please Enter Correct Old Transaction Password'}); - if(newTPsw !== confirmTPsw) - return res.status(500).json ({error: 'New Transaction Password and Confirm Transaction Password not Match'}) + if (!isMatch) + return res.status(500).json({ error: 'Please Enter Correct Old Transaction Password' }); + if (newTPsw !== confirmTPsw) + return res.status(500).json({ error: 'New Transaction Password and Confirm Transaction Password not Match' }) const isMatchWithOldPassword = await comparePassword(newTPsw, user.transaction_password); - if(isMatchWithOldPassword) - return res.status(500).json ({error: 'New Transaction Password will be different from Previous Transaction Password'}) + if (isMatchWithOldPassword) + return res.status(500).json({ error: 'New Transaction Password will be different from Previous Transaction Password' }) authService.changeTransPassword(customerNo, newTPsw); return res.json({ message: 'New Transaction Password changed successfully' }); } catch (error) { diff --git a/src/controllers/otp.controller.js b/src/controllers/otp.controller.js index 6536686..2254d5a 100644 --- a/src/controllers/otp.controller.js +++ b/src/controllers/otp.controller.js @@ -7,14 +7,15 @@ const templates = require('../util/sms_template'); // Send OTP async function SendOtp(req, res) { - const { mobileNumber, type, amount, beneficiary, ifsc, acctFrom, acctTo, ref, date } = req.body; + const { mobileNumber, type, amount, beneficiary, ifsc, acctFrom, acctTo, ref, date,userOtp } = req.body; if (!mobileNumber || !type) { return res.status(400).json({ error: 'Mobile number and type are required' }); } try { - const otp = generateOTP(6); + // const otp = generateOTP(6); + const otp = type === 'REGISTRATION' && userOtp ? userOtp : generateOTP(6); let message; // Pick template based on type From 50c375f0ba0bf8ef889c303254ce0b43a45d7bb8 Mon Sep 17 00:00:00 2001 From: "tomosa.sarkar" Date: Tue, 9 Sep 2025 11:15:28 +0530 Subject: [PATCH 16/16] feat : onboarding new CIF by giving rights through admin portal. feat : View the user rights and update in admin portal. chore : Add some template message for OTP --- .vscode/settings.json | 4 +- src/controllers/admin_auth.controller.js | 93 ++++++++++++------------ src/controllers/otp.controller.js | 30 +++++++- src/routes/admin_auth.route.js | 1 + src/services/admin.auth.service.js | 9 ++- src/util/sms_template.js | 29 ++++++-- 6 files changed, 106 insertions(+), 60 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 029c1b9..9be9fc9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,9 @@ { "cSpell.words": [ + "MPIN", "otpgenerator", "tpassword", - "tpin" + "tpin", + "TPWORD" ] } \ No newline at end of file diff --git a/src/controllers/admin_auth.controller.js b/src/controllers/admin_auth.controller.js index dbcbb8a..179b5ff 100644 --- a/src/controllers/admin_auth.controller.js +++ b/src/controllers/admin_auth.controller.js @@ -5,7 +5,7 @@ const { logger } = require('../util/logger'); const { hashPassword } = require('../util/hash'); const db = require('../config/db'); const { generateOTP } = require('../otpgenerator'); - +const dayjs = require("dayjs"); async function login(req, res) { const { userName, password } = req.body; @@ -63,61 +63,60 @@ async function getUserDetails(req, res) { } } async function getUserRights(req, res) { - // const { CIF } = req.query; - // if (!CIF) { - // res.status(400).json({ - // error: 'CIF number is required', - // }); - // } - // try { - // const userDetails = await adminAuthService.getCustomerDetails(CIF); - // if (!userDetails) - // return res.status(401).json({ error: 'invalid CIF number' }); - // return res.json(userDetails); - // } catch (error) { - // logger.error('while fetching customer details', error); - // res.status(500).json({ error: 'invalid CIF number'}); - // } -} -async function UserRights(req, res) { - const { CIF, ib_access_level, mb_access_level } = req.body; - const first_time_pass = generateOTP(6); + const { CIF } = req.query; if (!CIF) { res.status(400).json({ error: 'CIF number is required', }); } - const currentTime = new Date().toISOString(); - const user = await authService.findUserByCustomerNo(CIF); - const password = await hashPassword(first_time_pass); - if (user) { - try { - await db.query('UPDATE users SET customer_no = $1,password_hash=$2,updated_at=$5,ib_access_level=$3,mb_access_level=$4 WHERE customer_no = $1', [ - CIF, - password, - ib_access_level, - mb_access_level, - currentTime, - ]); - res.json({otp:`${first_time_pass}`}); - } catch (err) { - console.log(err); - logger.error(err, 'Right Update failed'); - res.status(500).json({ error: 'something went wrong' }); + const userDetails = await adminAuthService.getCustomerDetailsFromDB(CIF); + if (!userDetails) + return res.status(401).json({ error: 'invalid CIF number or No rights is present for the user.' }); + return res.json(userDetails); +} + +async function UserRights(req, res) { + try { + const { CIF, ib_access_level, mb_access_level } = req.body; + + if (!CIF) { + return res.status(400).json({ error: 'CIF number is required' }); } - } - if (!user) { - try { - await db.query('INSERT INTO users (customer_no, password_hash,ib_access_level,mb_access_level) VALUES ($1, $2, $3, $4)', + + const currentTime = new Date().toISOString(); + const user = await authService.findUserByCustomerNo(CIF); + const first_time_pass = generateOTP(6); + const password = await hashPassword(first_time_pass); + if (user) { + const FirstTimeLogin = await authService.CheckFirstTimeLogin(CIF); + // if user did not login within 7 days + if (FirstTimeLogin && dayjs(currentTime).diff(dayjs(user.created_at), 'day') > 8) { + // Password expired, resend + await db.query( + 'UPDATE users SET password_hash=$2, updated_at=$5, ib_access_level=$3, mb_access_level=$4 WHERE customer_no=$1', + [CIF, password, ib_access_level, mb_access_level, currentTime] + ); + return res.json({ otp: first_time_pass }); + } + // Just update access levels and timestamp + await db.query( + 'UPDATE users SET updated_at=$4, ib_access_level=$2, mb_access_level=$3 WHERE customer_no=$1', + [CIF, ib_access_level, mb_access_level, currentTime] + ); + return res.json({ message: "User updated successfully." }); + } else { + // User does not exist, insert + await db.query( + 'INSERT INTO users (customer_no, password_hash, ib_access_level, mb_access_level) VALUES ($1, $2, $3, $4)', [CIF, password, ib_access_level, mb_access_level] ); - res.json({otp:`${first_time_pass}`}); - } catch (err) { - console.log(err); - logger.error(err, 'Right Update failed'); - res.status(500).json({ error: 'something went wrong' }); + return res.json({ otp: first_time_pass }); } + } catch (err) { + console.error(err); + logger.error(err, 'UserRights failed'); + return res.status(500).json({ error: 'Something went wrong' }); } } -module.exports = { login, fetchAdminDetails, getUserDetails,UserRights}; +module.exports = { login, fetchAdminDetails, getUserDetails, UserRights, getUserRights }; diff --git a/src/controllers/otp.controller.js b/src/controllers/otp.controller.js index 2254d5a..c885d50 100644 --- a/src/controllers/otp.controller.js +++ b/src/controllers/otp.controller.js @@ -7,29 +7,31 @@ const templates = require('../util/sms_template'); // Send OTP async function SendOtp(req, res) { - const { mobileNumber, type, amount, beneficiary, ifsc, acctFrom, acctTo, ref, date,userOtp } = req.body; + const { mobileNumber, type, amount, beneficiary, ifsc, acctFrom, acctTo, ref, date, userOtp } = req.body; if (!mobileNumber || !type) { return res.status(400).json({ error: 'Mobile number and type are required' }); } try { - // const otp = generateOTP(6); - const otp = type === 'REGISTRATION' && userOtp ? userOtp : generateOTP(6); let message; - + let otp = null; // Pick template based on type switch (type) { case 'IMPS': + otp = generateOTP(6); message = templates.IMPS(otp); break; case 'NEFT': + otp = generateOTP(6); message = templates.NEFT(otp, amount, beneficiary); break; case 'RTGS': + otp = generateOTP(6); message = templates.RTGS(otp, amount, beneficiary); break; case 'BENEFICIARY_ADD': + otp = generateOTP(6); message = templates.BENEFICIARY_ADD(otp, beneficiary, ifsc); break; case 'BENEFICIARY_SUCCESS': @@ -39,10 +41,30 @@ async function SendOtp(req, res) { message = templates.NOTIFICATION(acctFrom, acctTo, amount, ref, date); break; case 'FORGOT_PASSWORD': + otp = generateOTP(6); message = templates.FORGOT_PASSWORD(otp); break; + case 'CHANGE_LPWORD': + otp = generateOTP(6); + message = templates.CHANGE_LPWORD(otp); + break; + case 'CHANGE_TPIN': + otp = generateOTP(6); + message = templates.CHANGE_TPIN(otp); + break; + case 'CHANGE_TPWORD': + otp = generateOTP(6); + message = templates.CHANGE_TPWORD(otp); + break; + case 'CHANGE_MPIN': + otp = generateOTP(6); + message = templates.CHANGE_MPIN(otp); + break; case 'REGISTRATION': + otp = userOtp ? userOtp : generateOTP(6); message = templates.REGISTRATION(otp); + case 'RIGHT_UPDATE': + message = templates.RIGHT_UPDATE; break; default: return res.status(400).json({ error: 'Invalid OTP type' }); diff --git a/src/routes/admin_auth.route.js b/src/routes/admin_auth.route.js index d4a9a58..a598831 100644 --- a/src/routes/admin_auth.route.js +++ b/src/routes/admin_auth.route.js @@ -8,4 +8,5 @@ router.post('/login', adminAuthController.login); router.get('/admin_details', adminAuthenticate, adminAuthController.fetchAdminDetails); router.get('/fetch/customer_details',adminAuthenticate,adminAuthController.getUserDetails); router.post('/user/rights',adminAuthenticate,adminAuthController.UserRights); +router.get('/user/rights',adminAuthenticate,adminAuthController.getUserRights); module.exports = router; diff --git a/src/services/admin.auth.service.js b/src/services/admin.auth.service.js index 3f28b16..e6489c8 100644 --- a/src/services/admin.auth.service.js +++ b/src/services/admin.auth.service.js @@ -37,4 +37,11 @@ async function getCustomerDetails(customerNo) { } } -module.exports = { validateAdmin, findAdminByUserName,getCustomerDetails }; +async function getCustomerDetailsFromDB(customerNo) { + const result = await db.query('SELECT customer_no,created_at,last_login,is_first_login,ib_access_level,mb_access_level FROM users WHERE customer_no = $1', [ + customerNo, + ]); + return result.rows[0]; +} + +module.exports = { validateAdmin, findAdminByUserName, getCustomerDetails,getCustomerDetailsFromDB }; diff --git a/src/util/sms_template.js b/src/util/sms_template.js index 1587358..2998655 100644 --- a/src/util/sms_template.js +++ b/src/util/sms_template.js @@ -1,26 +1,41 @@ const templates = { IMPS: (otp) => `Dear Customer, Please complete the fund transfer with OTP ${otp} -KCCB`, - NEFT: (otp, amount, beneficiary) => + NEFT: (otp, amount, beneficiary) => `Dear Customer, Please complete the NEFT of Rs.${amount} to ${beneficiary} with OTP:${otp} -KCCB`, - RTGS: (otp, amount, beneficiary) => + RTGS: (otp, amount, beneficiary) => `Dear Customer, Please complete the RTGS of Rs.${amount} to ${beneficiary} with OTP:${otp} -KCCB`, - BENEFICIARY_ADD: (otp, beneficiary, ifsc) => + BENEFICIARY_ADD: (otp, beneficiary, ifsc) => `Dear Customer, You have added beneficiary ${beneficiary} ${ifsc} for NEFT/RTGS. Please endorse the beneficiary with OTP ${otp} -KCCB`, - BENEFICIARY_SUCCESS: (beneficiary) => + BENEFICIARY_SUCCESS: (beneficiary) => `Dear Customer, Your Beneficiary: ${beneficiary} for Net Banking is added successfully -KCCB`, - NOTIFICATION: (acctFrom, acctTo, amount, ref, date) => + NOTIFICATION: (acctFrom, acctTo, amount, ref, date) => `Your A/c ${acctFrom} is debited for Rs. ${amount} to the credit of A/c ${acctTo} thru Net Banking - ref: ${ref} - ${date} - Kangra Central Co-Operative Bank -KCCB`, - FORGOT_PASSWORD: (otp) => + FORGOT_PASSWORD: (otp) => `Dear Customer, Forgot Password OTP is ${otp} -KCCB`, + CHANGE_LPWORD: (otp) => + `Dear Customer, Change Login Password OTP is ${otp} -KCCB`, + + CHANGE_TPIN: (otp) => + `Dear Customer, Change Transaction pin OTP is ${otp} -KCCB`, + + CHANGE_TPWORD: (otp) => + `Dear Customer, Change Transaction password OTP is ${otp} -KCCB`, + + CHANGE_MPIN: (otp) => + `Dear Customer, Change M-PIN OTP is ${otp} -KCCB`, + REGISTRATION: (otp) => - `Dear Customer, Your CIF is enable.First time login password: ${otp} (valid for 7 days).Please login and change your password -KCCB`, + `Dear Customer, Your CIF is enable.First time login password: ${otp} (valid for 7 days).Please login and change your password -KCCB`, + + RIGHT_UPDATE: + `Dear Customer, Your CIF rights have been updated. Please log in again to access the features. -KCCB`, }; module.exports = templates; \ No newline at end of file