From e4817ffdf775160403763f25be4c690d6921bdb9 Mon Sep 17 00:00:00 2001 From: Akhil Meka Date: Mon, 4 Aug 2025 11:59:52 +0530 Subject: [PATCH] feat: block user after 5 failed login attemtps --- src/auth/auth.route.ts | 16 +++++++++++++++- src/user/user.schema.ts | 7 +++++++ src/user/user.service.ts | 4 +++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/auth/auth.route.ts b/src/auth/auth.route.ts index d4cb02f..a96b133 100644 --- a/src/auth/auth.route.ts +++ b/src/auth/auth.route.ts @@ -44,6 +44,8 @@ export async function authRoutes(fastify: FastifyInstance) { const hashedPassword = await hash(password); userInDB.passwordHash = hashedPassword; + userInDB.blocked = false; + userInDB.faliedLoginCount = 0; await userInDB.save(); } catch (err) { return err; @@ -79,9 +81,20 @@ export async function authRoutes(fastify: FastifyInstance) { if (!userInDB.passwordHash) return res.code(401).send({ error: "invalid email or password" }); + if (userInDB.blocked) + return res + .code(401) + .send({ error: "Account blocked. Contact admin." }); + const match = await verify(userInDB.passwordHash, password); - if (!match) + if (!match) { + if (!userInDB.faliedLoginCount) userInDB.faliedLoginCount = 0; + if (userInDB.faliedLoginCount >= 4) userInDB.blocked = true; + userInDB.faliedLoginCount++; + await userInDB.save(); + return res.code(401).send({ error: "invalid email or password" }); + } const newSession = await createSession( userInDB.id, @@ -90,6 +103,7 @@ export async function authRoutes(fastify: FastifyInstance) { ); userInDB.lastLogin = new Date(); + userInDB.faliedLoginCount = 0; await userInDB.save(); res.send({ session_token: newSession.sid }); diff --git a/src/user/user.schema.ts b/src/user/user.schema.ts index 5646018..6874008 100644 --- a/src/user/user.schema.ts +++ b/src/user/user.schema.ts @@ -51,6 +51,11 @@ const userSchema = new mongoose.Schema({ }, lastLogin: Date, dev: Boolean, + blocked: Boolean, + faliedLoginCount: { + type: Number, + default: 0, + }, }); export const userModel = mongoose.model("user", userSchema); @@ -116,6 +121,8 @@ const userResponse = z.object({ createdAt: z.string().optional(), createdBy: z.string().optional(), lastLogin: z.string().optional(), + blocked: z.boolean().optional(), + failedLoginCount: z.number().optional(), }); export type CreateUserInput = z.infer; diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 9103046..6849c52 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -38,6 +38,8 @@ export async function createUser( createdBy: user.userId, status: input.password ? "active" : "invited", passwordHash: hashedPassword, + blocked: false, + faliedLoginCount: 0, ...input, }); @@ -156,7 +158,7 @@ export async function listUsers(user: AuthenticatedUser) { ], }) .select( - "_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin" + "_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin blocked failedLoginCount" ) .populate({ path: "orgId", select: "_id pid name avatar" }) .populate({ path: "createdBy", select: "_id pid name avatar" });