Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 126 additions & 1 deletion src/controllers/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { renderHTML, MailOptions, sendMail } from '../utils/roboSender/sendEmail
import Role from '../models/role.model';
import IRole from '../interfaces/role.interface';
import axios from 'axios';

import crypto from 'crypto';
class UsersController {
public index = async (req: Request, res: Response): Promise<Response> => {
try {
Expand Down Expand Up @@ -46,6 +46,102 @@ class UsersController {
}
};

public requestEmailUpdate = async (req: Request, res: Response): Promise<Response> => {
try {
const { email, userId } = req.body;

if (!email) {
return res.status(400).json({ mensaje: 'Email requerido' });
}

// Verificar si el email ya existe en otro usuario
const emailExists = await this.validateEmailUniqueness(email, userId);
if (emailExists) {
return res.status(400).json({ mensaje: 'El email ya está registrado por otro usuario' });
}

// Generar token y expiración
const token = crypto.randomBytes(20).toString('hex');
const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 horas

const user = await User.findByIdAndUpdate(userId, {
pendingEmail: email,
emailConfirmationToken: token,
emailConfirmationExpires: expires
}, { new: true });

if (!user) {
return res.status(404).json({ mensaje: 'Usuario no encontrado' });
}

// Enviar email de confirmación
await this.sendEmailUpdateConfirmation(user, email, token);

return res.status(200).json({ mensaje: 'Se ha enviado un correo de confirmación a la nueva dirección.' });

} catch (e) {
return res.status(500).json({ mensaje: `${e}` });
}
};

public confirmEmailUpdate = async (req: Request, res: Response): Promise<Response> => {
try {
const { token } = req.body;

if (!token) {
return res.status(400).json({ mensaje: 'Token requerido' });
}

// Buscar usuario con el token y verificar expiración
const user = await User.findOne({
emailConfirmationToken: token,
emailConfirmationExpires: { $gt: new Date() }
}).populate('roles');

if (!user) {
return res.status(400).json({ mensaje: 'Token inválido o expirado' });
}

// Aplicar el cambio
user.email = user.pendingEmail!;

// Si es farmacia, actualizamos también el username
const isPharmacy = user.roles.some((role: any) => role.role === 'pharmacist');
if (isPharmacy) {
user.username = user.pendingEmail!;
}

user.pendingEmail = undefined;
user.emailConfirmationToken = undefined;
user.emailConfirmationExpires = undefined;

await user.save();

return res.status(200).json({ mensaje: 'Email actualizado correctamente' });

} catch (e) {
return res.status(500).json({ mensaje: `${e}` });
}
};

/**
* Valida que el email no esté siendo usado por otro usuario
* @param email - Email a validar
* @param userId - ID del usuario actual (para excluirlo de la búsqueda)
* @returns true si el email ya existe, false si está disponible
*/
private validateEmailUniqueness = async (email: string, userId: string): Promise<boolean> => {
try {
const existingUser = await User.findOne({
email,
_id: { $ne: userId }
});
return !!existingUser;
} catch (error) {
throw new Error(`Error validating email uniqueness: ${error}`);
}
};

public getUserInfo = async (req: Request, res: Response): Promise<Response> => {
// obtenemos la información personal del usuario por su ID
const { id } = req.params;
Expand Down Expand Up @@ -432,6 +528,35 @@ class UsersController {
return res.status(500).json('Server Error');
}
};

/**
* Envía un email con el token para confirmar el cambio de email
* @param user - Usuario
* @param newEmail - Nuevo email
* @param token - Token de confirmación
*/
private sendEmailUpdateConfirmation = async (user: IUser, newEmail: string, token: string): Promise<void> => {
try {
const extras: any = {
titulo: 'Confirmar cambio de email',
usuario: user,
url: `${process.env.APP_DOMAIN || 'https://recetar.andes.gob.ar'}/auth/confirm-update/${token}`
};

const htmlToSend = await renderHTML('emails/update-email.html', extras);
const options: MailOptions = {
from: `${process.env.EMAIL_USERNAME}`,
to: newEmail,
subject: 'Confirmar cambio de email - RecetAR',
text: '',
html: htmlToSend,
attachments: null
};
await sendMail(options);
} catch (error) {
console.error('Error enviando confirmación de cambio de email:', error);
}
};
};

export default new UsersController;
3 changes: 3 additions & 0 deletions src/interfaces/user.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import IProfesionAutorizada from './profesionAutorizada.interface';
export default interface IUser extends Document {
username: string;
email: string;
pendingEmail?: string;
emailConfirmationToken?: string;
emailConfirmationExpires?: Date;
businessName: string;
enrollment?: string;
cuil?: string;
Expand Down
9 changes: 9 additions & 0 deletions src/models/user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ export const userSchema = new Schema({
email: {
type: String
},
pendingEmail: {
type: String
},
emailConfirmationToken: {
type: String
},
emailConfirmationExpires: {
type: Date
},
enrollment: {
type: String
},
Expand Down
13 changes: 13 additions & 0 deletions src/templates/emails/update-email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{#> layout }}
<div class="title" style="font-family:Helvetica, Arial, sans-serif;font-size:18px;font-weight:600;color:#374550">
Hola {{ nombre }}!</div>
<br>
<h4>
Recibiste este mensaje porque solicitaste cambiar tu dirección de email.
</h4>
<br>

Para confirmar este cambio, haz click en el siguiente enlace: <a href="{{url}}">Confirmar cambio de email</a>
<br>
Este enlace expirará en 24 horas.
{{/layout}}
2 changes: 1 addition & 1 deletion src/utils/roboSender/sendEmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function sendMail(options: MailOptions) {
auth: {
user: `${process.env.EMAIL_USERNAME}`,
pass: `${process.env.EMAIL_PASSWORD}`
},
}
});

const mailOptions = {
Expand Down
Loading