From 75ebcf84074045bda8f5b7bf893fd0136b5f759f Mon Sep 17 00:00:00 2001 From: "nabanita.jana" Date: Tue, 21 Oct 2025 16:21:41 +0530 Subject: [PATCH] feat : users are locked after three failed login attempts. --- src/controllers/auth.controller.js | 93 ++++++++++++++++++++++++------ src/controllers/otp.controller.js | 4 +- 2 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index ab47161..0b049de 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -5,66 +5,124 @@ const db = require('../config/db'); const dayjs = require('dayjs'); const { comparePassword } = require('../util/hash'); const customerController = require('../controllers/customer_details.controller.js'); +const { setJson, getJson } = require('../config/redis'); + 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' }); + 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 try { + // --- Step 1: Check if user is already locked --- + const blockedKey = `login:blocked:${customerNo}`; + const attemptsKey = `login:attempts:${customerNo}`; + console.log("hi",blockedKey); + console.log("attempt Key",attemptsKey); + + + // check DB locked flag + const userCheck = await authService.findUserByCustomerNo(customerNo); + if (userCheck && userCheck.locked) { + await setJson(blockedKey, true, BLOCK_DURATION); + return res.status(423).json({ + error: 'Your account is locked. Please contact the administrator.', + }); + } + + // --- Step 2: Check migration status const isMigratedUser = await authService.isMigratedUser(customerNo); if (isMigratedUser) return res.status(401).json({ error: 'MIGRATED_USER_HAS_NO_PASSWORD' }); + + // --- Step 3: Validate credentials --- const user = await authService.validateUser(customerNo, password); - if (!user || !password) - return res.status(401).json({ error: 'INVALID_CREDENTIALS' }); + + if (!user) { + // Invalid credentials: increment Redis counter + let attempts = (await getJson(attemptsKey)) || 0; + attempts += 1; + + if (attempts >= MAX_ATTEMPTS) { + // lock the account in DB + await db.query('UPDATE users SET locked = true WHERE customer_no = $1', [customerNo]); + + // mark as blocked in Redis + await setJson(blockedKey, true, BLOCK_DURATION); + + // clear attempts counter + await setJson(attemptsKey, 0); + + logger.warn(`User ${customerNo} account locked after ${MAX_ATTEMPTS} failed attempts.`); + + return res.status(423).json({ + error:'Your account has been locked due to multiple failed login attempts. Please contact the administrator.', + }); + } + else { + // Save the incremented attempt count with TTL (optional 15 mins) + await setJson(attemptsKey, attempts, BLOCK_DURATION); + + return res.status(401).json({ + error: `Invalid credentials. ${MAX_ATTEMPTS - attempts} attempt(s) remaining.`, + }); + } + } + + // --- Step 4: If login successful, reset Redis attempts --- + await setJson(attemptsKey, 0); // reset counter 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' }); + return res.status(401).json({ + error: 'Password Expired.Please Contact with Administrator', + }); - // if present then get his phone number from CBS + // --- Step 5: Get user details (for OTP logic) --- const userDetails = await customerController.getDetails(customerNo); const singleUserDetail = userDetails[0]; if (!singleUserDetail?.mobileno) return res.status(400).json({ error: 'USER_PHONE_NOT_FOUND' }); const mobileNumber = singleUserDetail.mobileno; - // For otp generate in IB - if (loginType.toUpperCase() === "IB" && !otp) { + // --- Step 6: OTP requirement for IB login --- + if (loginType.toUpperCase() === 'IB' && !otp) { logger.info(`credential verified but otp required | Type: ${loginType}`); return res.status(202).json({ - status: "OTP_REQUIRED", - mobile: mobileNumber + status: 'OTP_REQUIRED', + mobile: mobileNumber, }); } + // --- Step 7: Generate token and update last login --- const token = generateToken(user.customer_no); 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, ]); + logger.info(`Login successful | Type: ${loginType}`); - res.json({ token, FirstTimeLogin, loginPswExpiry, rights }); + return res.json({ token, FirstTimeLogin, loginPswExpiry, rights }); } catch (err) { - logger.error(err, `login failed | Type: ${loginType}`); - res.status(500).json({ error: 'something went wrong' }); + logger.error(err, `login failed | Type: ${loginType}`); + return res.status(500).json({ error: 'something went wrong' }); } } + async function fetchUserDetails(req, res) { const customerNo = req.user; try { @@ -93,6 +151,7 @@ async function tpin(req, res) { } } + async function setTpin(req, res) { const customerNo = req.user; try { diff --git a/src/controllers/otp.controller.js b/src/controllers/otp.controller.js index d4a3d79..33cc689 100644 --- a/src/controllers/otp.controller.js +++ b/src/controllers/otp.controller.js @@ -105,7 +105,7 @@ async function SendOtp(req, res) { // Call SMS API const response = await axios.post( - 'http://192.168.1.77:9999/api/SendtoMessage', + 'http://localhost:9999/api/SendtoMessage', { mobileNumber, stMessage: message, @@ -171,7 +171,7 @@ async function sendForSetPassword(req, res) { const otp = generateOTP(6); const message = templates.CHANGE_LPWORD(otp); const response = await axios.post( - 'http://192.168.1.77:9999/api/SendtoMessage', + 'http://localhost:9999/api/SendtoMessage', { mobileNumber, stMessage: message,