Actualización módulo auth, implementación del método de cambio de contraseña
This commit is contained in:
@ -31,6 +31,7 @@ export interface IAuditLog {
|
|||||||
| "REJECT"
|
| "REJECT"
|
||||||
| "LOGIN"
|
| "LOGIN"
|
||||||
| "LOGOUT"
|
| "LOGOUT"
|
||||||
|
| "CHANGE_PASSWORD"
|
||||||
| "CLOSE";
|
| "CLOSE";
|
||||||
|
|
||||||
recordId?: ObjectId;
|
recordId?: ObjectId;
|
||||||
|
|||||||
@ -70,6 +70,8 @@ const auditLogSchema = new mongoose.Schema(
|
|||||||
|
|
||||||
"LOGOUT",
|
"LOGOUT",
|
||||||
|
|
||||||
|
"CHANGE_PASSWORD",
|
||||||
|
|
||||||
"CLOSE"
|
"CLOSE"
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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,
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -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,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
|
||||||
if(!authHeader){
|
try {
|
||||||
return res.status(401).json({
|
|
||||||
message:"Token requerido"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//Verificar formato Bearer
|
const user = (req as any).user;
|
||||||
|
|
||||||
if(!authHeader.startsWith("Bearer ")) {
|
|
||||||
return res.status(401).json({
|
|
||||||
message:"Formato de token inválido"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const token = authHeader.split(" ")[1];
|
|
||||||
|
|
||||||
if (!token)
|
if (!user) {
|
||||||
{
|
|
||||||
return res.status(401).json({
|
|
||||||
message:"Token inválido"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
return res.status(401).json({
|
||||||
|
message: "Usuario no autenticado."
|
||||||
|
});
|
||||||
|
|
||||||
const decoded = jwt.verify (
|
}
|
||||||
token,
|
|
||||||
process.env.JWT_SECRET as string,
|
|
||||||
);
|
|
||||||
|
|
||||||
(req as any).user =
|
|
||||||
decoded;
|
|
||||||
|
|
||||||
next();
|
if (!user.permissions) {
|
||||||
} catch{
|
|
||||||
return res.status(401).json({
|
return res.status(403).json({
|
||||||
message:"Token inválido"
|
message: "El usuario no tiene permisos asignados."
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const hasPermission = user.permissions.some(
|
||||||
|
|
||||||
|
(permission: any) =>
|
||||||
|
|
||||||
|
permission.module === module &&
|
||||||
|
permission.action === action
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (!hasPermission) {
|
||||||
|
|
||||||
|
return res.status(403).json({
|
||||||
|
|
||||||
|
message: "No tiene permisos para realizar esta acción."
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
next();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
return res.status(500).json({
|
||||||
|
|
||||||
|
message: "Error al validar permisos.",
|
||||||
|
|
||||||
|
error
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -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."
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
7
backend/src/modules/auth/changePassword.interface.ts
Normal file
7
backend/src/modules/auth/changePassword.interface.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface IChangePassword {
|
||||||
|
|
||||||
|
currentPassword: string;
|
||||||
|
|
||||||
|
newPassword: string;
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user