No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Generando links de recuperación

17/20
Recursos

Aportes 1

Preguntas 0

Ordenar por:

Los aportes, preguntas y respuestas son vitales para aprender en comunidad. Regístrate o inicia sesión para participar.

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:

  1. Validar si el email se encuentra en la base de datos, si todo bien, se obtiene el user.

  2. Se genera un payload con el user.id.

  3. Se genera un token que expira en 15 minutos, incluye el payload 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.

  4. Se genera un link que incluye el token necesario para recuperar la contraseña. Desde el frontend debe haber una vista para ello.

  5. Se llama el método update de UserService para actualizar los datos del usuario asignandole el token generado.

  6. Se establece el cuerpo del email.

  7. Se ejecuta el método sendMail que recibe el cuerpo del email.

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.