Se reacomoda un poco el servicio auth.service.js
haciendo una separación de responsabilidades, se crea el método sendRecovery
el cual contiene la lógica para generar un link para recuperar la contraseña y el método sendMail
contiene la configuración para poder enviar un email (transporter
).
Lo que hace es:
-
Validar si el email se encuentra en la base de datos, si todo bien, se obtiene el
user
. -
Se genera un
payload
con eluser.id
. -
Se genera un
token
que expira en 15 minutos, incluye elpayload
con el id del usuario que solicita recuperar su contraseña. Por seguridad, el token debe guardarse en la BD y comprobarlo para evitar que envíen un token indeseado. -
Se genera un link que incluye el token necesario para recuperar la contraseña. Desde el frontend debe haber una vista para ello.
-
Se llama el método
update
deUserService
para actualizar los datos del usuario asignandole el token generado. -
Se establece el cuerpo del
email
. -
Se ejecuta el método
sendMail
que recibe el cuerpo delemail
.
const boom = require('@hapi/boom');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const nodemailer = require('nodemailer');
const { config } = require('../config/config');
const UserService = require('./user.service');
const service = new UserService();
class AuthService {
async getUser(email, password) {
const user = await service.findByEmail(email);
if (!user) {
throw boom.unauthorized();
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
throw boom.unauthorized();
}
delete user.dataValues.password;
return user;
}
signToken(user) {
const payload = {
sub: user.id,
role: user.role,
};
const token = jwt.sign(payload, config.jwtSecret);
return {
user,
token,
};
}
async sendRecovery(email) {
const user = await service.findByEmail(email);
if (!user) {
throw boom.unauthorized();
}
const payload = { sub: user.id };
const token = jwt.sign(payload, config.jwtRecoverySecret, {
expiresIn: '15min',
});
const link = `https://myfrontend.com/recovery?token=${token}`;
await service.update(user.id, { recoveryToken: token });
const mail = {
from: `"Foo Boo 👻" <${config.mailerEmail}>`,
to: `${user.email}`,
subject: 'Email para recuperar contraseña',
html: `<b>Ingresa a este link para recuperar tu contraseña: ${link}</b>`,
};
const rta = await this.sendMail(mail);
return rta;
}
async sendMail(infoMail) {
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
secure: true, // true for 465, false for other ports
port: 465,
auth: {
user: config.mailerEmail,
pass: config.mailerPassword,
},
});
await transporter.sendMail(infoMail);
return { message: 'Mail sent' };
}
}
module.exports = AuthService;
En el endpoint /recovery
de auth.router.js
se hace el cambio del método sendMail
por sendRecovery
:
router.post('/recovery', async (req, res, next) => {
try {
const { email } = req.body;
const rta = await service.sendRecovery(email);
res.json(rta);
} catch (error) {
next(error);
}
});
Debido a que se necesita ingresar un nuevo campo, se requiere generar una nueva migración (recovery-token-field
), en el boilerplate de dicha migración se agrega una nueva columna a USER_TABLE
. Esto contiene el nombre de la tabla, el nombre del nuevo campo y los tipos de datos, así como un método para hacer rollback (removeColumn
):
'use strict';
const { USER_TABLE } = require('../models/user.model');
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn(USER_TABLE, 'recovery_token', {
field: 'recovery_token',
allowNull: true,
type: Sequelize.DataTypes.STRING,
});
},
down: async (queryInterface) => {
await queryInterface.removeColumn(USER_TABLE, 'recovery_token');
},
};
También es necesario agregar un nuevo campo al modelo user.model.js
:
recoveryToken: {
field: 'recovery_token',
allowNull: true,
type: DataTypes.STRING,
},
Después de tener el boilerplate listo y haber modificado el modelo de usuario, se corre la migración con npm run migrations:run
.
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?