Actualización módulo auth, implementación del método de cambio de contraseña

This commit is contained in:
2026-07-10 17:18:33 +00:00
parent 65047dee90
commit ed6f4e3441
6 changed files with 201 additions and 42 deletions

View File

@ -31,6 +31,7 @@ export interface IAuditLog {
| "REJECT" | "REJECT"
| "LOGIN" | "LOGIN"
| "LOGOUT" | "LOGOUT"
| "CHANGE_PASSWORD"
| "CLOSE"; | "CLOSE";
recordId?: ObjectId; recordId?: ObjectId;

View File

@ -70,6 +70,8 @@ const auditLogSchema = new mongoose.Schema(
"LOGOUT", "LOGOUT",
"CHANGE_PASSWORD",
"CLOSE" "CLOSE"
] ]

View File

@ -34,7 +34,7 @@ export class AuthController {
} catch(error:any){ } catch(error:any){
res.status(401).json({ res.status(500).json({
message:error.message message:error.message
}); });
@ -58,4 +58,32 @@ export class AuthController {
}); });
} }
} }
async changePassword(
req: Request,
res: Response
) {
try {
const user = (req as any).user;
const result = await authService.changePassword(
user.id,
req.body
);
res.status(200).json(result);
} catch (error: any) {
res.status(500).json({
message: error.message,
});
}
}
} }

View File

@ -1,50 +1,74 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
export const authMiddleware = ( export const permissionMiddleware = (
req:Request, module: string,
res:Response, action: string
next:NextFunction
) => { ) => {
const authHeader = req.headers.authorization; return (
req: Request,
if(!authHeader){ res: Response,
return res.status(401).json({ next: NextFunction
message:"Token requerido" ) => {
});
}
//Verificar formato Bearer
if(!authHeader.startsWith("Bearer ")) {
return res.status(401).json({
message:"Formato de token inválido"
});
}
const token = authHeader.split(" ")[1];
if (!token)
{
return res.status(401).json({
message:"Token inválido"
});
}
try { try {
const decoded = jwt.verify ( const user = (req as any).user;
token,
process.env.JWT_SECRET as string,
if (!user) {
return res.status(401).json({
message: "Usuario no autenticado."
});
}
if (!user.permissions) {
return res.status(403).json({
message: "El usuario no tiene permisos asignados."
});
}
const hasPermission = user.permissions.some(
(permission: any) =>
permission.module === module &&
permission.action === action
); );
(req as any).user =
decoded; if (!hasPermission) {
return res.status(403).json({
message: "No tiene permisos para realizar esta acción."
});
}
next(); next();
} catch{
return res.status(401).json({ } catch (error) {
message:"Token inválido"
return res.status(500).json({
message: "Error al validar permisos.",
error
}); });
} }
};
}; };

View File

@ -3,13 +3,20 @@ import jwt from "jsonwebtoken";
import userModel from "../user/user.model"; import userModel from "../user/user.model";
import auditLogModel from "../auditLogs/auditLog.model"; import auditLogModel from "../auditLogs/auditLog.model";
import { IChangePassword } from "./changePassword.interface";
import { RolePermissionService } from '../role-permissions/rolePermission.service';
const service = new RolePermissionService();
export class AuthService { export class AuthService {
async login ( async login (
userName:string, userName:string,
password:string password:string
){ ){
const user = await userModel.findOne({ const user =
await userModel.findOne({
userName userName
}).populate("roleId"); }).populate("roleId");
@ -49,12 +56,27 @@ export class AuthService {
const role = user.roleId as any; const role = user.roleId as any;
const rolePermissions = await service.getByRole(
role._id.toString()
);
const permissions = rolePermissions ?
(rolePermissions.permissions as any []).map(
permission => ({
name:permission.name,
module:permission.module,
action:permission.action
})
)
:[]
const token = jwt.sign( const token = jwt.sign(
{ {
id:user._id, id:user._id,
roleId: user.roleId, roleId: role._id,
roleName: role.roleName roleName: role.roleName,
permissions
}, },
process.env.JWT_SECRET as string, process.env.JWT_SECRET as string,
@ -76,6 +98,8 @@ export class AuthService {
roleName: role.roleName, roleName: role.roleName,
permissions,
active: user.active active: user.active
} }
}; };
@ -84,6 +108,12 @@ export class AuthService {
async logout ( async logout (
userId:string userId:string
){ ){
if(!userId){
throw new Error("Usuario no autenticado.");
}
await auditLogModel.create({ await auditLogModel.create({
userId, userId,
module:"Authentication", module:"Authentication",
@ -96,4 +126,71 @@ export class AuthService {
message:"Sesión cerrada exitosamente" message:"Sesión cerrada exitosamente"
}; };
} }
async changePassword(
userId: string,
data: IChangePassword
) {
// Buscar usuario
const user = await userModel.findById(userId);
if (!user) {
throw new Error("Usuario no encontrado.");
}
// Validar contraseña actual
const validPassword = await bcrypt.compare(
data.currentPassword,
user.passwordHash
);
if (!validPassword) {
throw new Error("La contraseña actual es incorrecta.");
}
// Validar que la nueva contraseña sea diferente
if (data.currentPassword === data.newPassword) {
throw new Error(
"La nueva contraseña debe ser diferente a la actual."
);
}
// Validar longitud mínima
if (data.newPassword.length < 8) {
throw new Error(
"La nueva contraseña debe tener al menos 8 caracteres."
);
}
// Encriptar nueva contraseña
user.passwordHash = await bcrypt.hash(
data.newPassword,
10
);
await user.save();
// Registrar en auditoría
await auditLogModel.create({
userId: user._id,
module: "Authentication",
action: "CHANGE_PASSWORD",
description: "El usuario cambió su contraseña.",
status: "EXITOSO"
});
return {
message: "Contraseña actualizada correctamente."
};
}
} }

View File

@ -0,0 +1,7 @@
export interface IChangePassword {
currentPassword: string;
newPassword: string;
}