feat : users are locked after three failed login attempts.
This commit is contained in:
@@ -5,66 +5,124 @@ const db = require('../config/db');
|
|||||||
const dayjs = require('dayjs');
|
const dayjs = require('dayjs');
|
||||||
const { comparePassword } = require('../util/hash');
|
const { comparePassword } = require('../util/hash');
|
||||||
const customerController = require('../controllers/customer_details.controller.js');
|
const customerController = require('../controllers/customer_details.controller.js');
|
||||||
|
const { setJson, getJson } = require('../config/redis');
|
||||||
|
|
||||||
|
|
||||||
async function login(req, res) {
|
async function login(req, res) {
|
||||||
const { customerNo, password, otp } = req.body;
|
const { customerNo, password, otp } = req.body;
|
||||||
const loginType = req.headers['x-login-type'] || 'standard';
|
const loginType = req.headers['x-login-type'] || 'standard';
|
||||||
|
|
||||||
|
|
||||||
if (!customerNo || !password) {
|
if (!customerNo || !password) {
|
||||||
return res
|
return res.status(400).json({ error: 'customerNo and password are required' });
|
||||||
.status(400)
|
|
||||||
.json({ error: 'customerNo and password are required' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentTime = new Date().toISOString();
|
const currentTime = new Date().toISOString();
|
||||||
|
const MAX_ATTEMPTS = 3; // Max invalid attempts before lock
|
||||||
|
const BLOCK_DURATION = 15 * 60 * 60; // 1 day
|
||||||
try {
|
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);
|
const isMigratedUser = await authService.isMigratedUser(customerNo);
|
||||||
if (isMigratedUser)
|
if (isMigratedUser)
|
||||||
return res.status(401).json({ error: 'MIGRATED_USER_HAS_NO_PASSWORD' });
|
return res.status(401).json({ error: 'MIGRATED_USER_HAS_NO_PASSWORD' });
|
||||||
|
|
||||||
|
// --- Step 3: Validate credentials ---
|
||||||
const user = await authService.validateUser(customerNo, password);
|
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);
|
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
|
return res.status(401).json({
|
||||||
.status(401)
|
error: 'Password Expired.Please Contact with Administrator',
|
||||||
.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 userDetails = await customerController.getDetails(customerNo);
|
||||||
const singleUserDetail = userDetails[0];
|
const singleUserDetail = userDetails[0];
|
||||||
if (!singleUserDetail?.mobileno)
|
if (!singleUserDetail?.mobileno)
|
||||||
return res.status(400).json({ error: 'USER_PHONE_NOT_FOUND' });
|
return res.status(400).json({ error: 'USER_PHONE_NOT_FOUND' });
|
||||||
const mobileNumber = singleUserDetail.mobileno;
|
const mobileNumber = singleUserDetail.mobileno;
|
||||||
|
|
||||||
// For otp generate in IB
|
// --- Step 6: OTP requirement for IB login ---
|
||||||
if (loginType.toUpperCase() === "IB" && !otp) {
|
if (loginType.toUpperCase() === 'IB' && !otp) {
|
||||||
logger.info(`credential verified but otp required | Type: ${loginType}`);
|
logger.info(`credential verified but otp required | Type: ${loginType}`);
|
||||||
return res.status(202).json({
|
return res.status(202).json({
|
||||||
status: "OTP_REQUIRED",
|
status: 'OTP_REQUIRED',
|
||||||
mobile: mobileNumber
|
mobile: mobileNumber,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Step 7: Generate token and update last login ---
|
||||||
const token = generateToken(user.customer_no);
|
const token = generateToken(user.customer_no);
|
||||||
const loginPswExpiry = user.password_hash_expiry;
|
const loginPswExpiry = user.password_hash_expiry;
|
||||||
const rights = {
|
const rights = {
|
||||||
ibAccess: user.ib_access_level,
|
ibAccess: user.ib_access_level,
|
||||||
mbAccess: user.mb_access_level,
|
mbAccess: user.mb_access_level,
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.query('UPDATE users SET last_login = $1 WHERE customer_no = $2', [
|
await db.query('UPDATE users SET last_login = $1 WHERE customer_no = $2', [
|
||||||
currentTime,
|
currentTime,
|
||||||
customerNo,
|
customerNo,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
logger.info(`Login successful | Type: ${loginType}`);
|
logger.info(`Login successful | Type: ${loginType}`);
|
||||||
res.json({ token, FirstTimeLogin, loginPswExpiry, rights });
|
return res.json({ token, FirstTimeLogin, loginPswExpiry, rights });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err, `login failed | Type: ${loginType}`);
|
logger.error(err, `login failed | Type: ${loginType}`);
|
||||||
res.status(500).json({ error: 'something went wrong' });
|
return res.status(500).json({ error: 'something went wrong' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function fetchUserDetails(req, res) {
|
async function fetchUserDetails(req, res) {
|
||||||
const customerNo = req.user;
|
const customerNo = req.user;
|
||||||
try {
|
try {
|
||||||
@@ -93,6 +151,7 @@ async function tpin(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function setTpin(req, res) {
|
async function setTpin(req, res) {
|
||||||
const customerNo = req.user;
|
const customerNo = req.user;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ async function SendOtp(req, res) {
|
|||||||
|
|
||||||
// Call SMS API
|
// Call SMS API
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
'http://192.168.1.77:9999/api/SendtoMessage',
|
'http://localhost:9999/api/SendtoMessage',
|
||||||
{
|
{
|
||||||
mobileNumber,
|
mobileNumber,
|
||||||
stMessage: message,
|
stMessage: message,
|
||||||
@@ -171,7 +171,7 @@ async function sendForSetPassword(req, res) {
|
|||||||
const otp = generateOTP(6);
|
const otp = generateOTP(6);
|
||||||
const message = templates.CHANGE_LPWORD(otp);
|
const message = templates.CHANGE_LPWORD(otp);
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
'http://192.168.1.77:9999/api/SendtoMessage',
|
'http://localhost:9999/api/SendtoMessage',
|
||||||
{
|
{
|
||||||
mobileNumber,
|
mobileNumber,
|
||||||
stMessage: message,
|
stMessage: message,
|
||||||
|
|||||||
Reference in New Issue
Block a user