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 8

Preguntas 3

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

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.

Definitivamente cualquier persona que quiera ser full stack debe de tomar este maravilloso curso. Pa’ lante compañeros el camino es largo pero la recompensa es jugosa. 😁💻🍗

Yo hice un MailService para tener la logica de los correos ahi

const nodemailer = require('nodemailer');
const config = require('../config');
const { host, port, user, password } = config.smtp;

class MailService {
  constructor() {
    this.transporter = nodemailer.createTransport({
      host,
      port,
      secure: true,
      auth: {
        user,
        pass: password,
      },
    });
  }

  async sendEmail(emailData) {
    await this.transporter.sendMail({
      from: emailData.from,
      to: emailData.to,
      subject: emailData.subject,
      text: emailData.text,
      html: emailData.html,
    });
  }
}
module.exports = MailService;


si les da un error como este.

{
  "message": "self signed certificate in certificate chain",
  "stack": "Error: self signed certificate in certificate chain\n    at TLSSocket.onConnectSecure (node:_tls_wrap:1535:34)\n    at TLSSocket.emit (node:events:513:28)\n    at TLSSocket._finishInit (node:_tls_wrap:949:8)\n    at TLSWrap.ssl.onhandshakedone (node:_tls_wrap:730:12)"
}```

deben poner el siguiente código asi


tls: {
rejectUnauthorized: false
}



nodemailer.createTransport({
host: process.env.MAIL_SERVER,
secure: false,
port: 587,
auth: {
user: process.env.MAIL_USERNAME,
pass: process.env.MAIL_PASSWORD
},
tls: {
rejectUnauthorized: false
}
}

Con eso se me arreglo.


Así configure la constante con la información del correo:

const mail = {
      from: config.emailUser,
      to: `${user.email}`,
      subject: "ApiFakeStore: Recupera tu cuenta 🗝️",
      html: `
        <b>Ingresa a este link 👇👇 para recuperar tu cuenta 🗝️</b>
        <br>
        <a href="${link}" target="_blank">Recuperar cuenta</a>
      `,
    }

Para generar el link yo utilicé lo siguiente:

const url = `${req.protocol}://${req.get('host')}`
const link = `${url}/recovery?token=${token}`

De esta manera obtengo el dominio desde donde se está haciendo la petición para hacer el link.

No me di cuenta en que momento del video cambiamos el router para agregarle el sendRecovery. Excelente clase.

estas clases son geniales