No tienes acceso a esta clase

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

Convierte tus certificados en títulos universitarios en USA

Antes: $249

Currency
$209

Paga en 4 cuotas sin intereses

Paga en 4 cuotas sin intereses
Suscríbete

Termina en:

16 Días
9 Hrs
7 Min
46 Seg

Implementando hashing para usuarios

6/20
Recursos

Aportes 27

Preguntas 7

Ordenar por:

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

Pueden utlizar los hooks con sequelize para que realice el hash de la contraseña antes de guardar los datos. Solo tienes que agregar la opción hooks en el método config de la clase User que se encuentra en user.model.js

static config(sequelize) {
    return {
      sequelize,
      tableName: USER_TABLE,
      modelName: 'User',
      timestamps: false,
      hooks: {
        beforeCreate: async (user, options) => {
          const password = await bcrypt.hash(user.password, 10);
          user.password = password;
        },
      }
    };
  }

De esta forma puedes evitar realizar el hash en los servicios user y customer y dejarlos como estaban anteriormente.

Resolviendo Reto

Para poder resolver este reto lo que debemos de hacer entrar al objeto de dataValues luego al objeto de usuario y por ultimo al objeto de usuario. Y ya podemos acceder a la propiedad de contraseña para proceder a borrarla.

Codigo

delete newCustomer.dataValues.user.dataValues.password;
return newCustomer;

Hi! I’m practicing my english. So I will post my notes:

There are too many ways to encrypt passwords, but we are going to do it through models hooks.

To encrypt a password before being send to the database, just add a hook in model.config

static config(sequelize) {
        return {
            sequelize,
            tableName: USER_TABLE,
            modelName: 'User',
            timestamps: false,
            **hooks: {
                beforeCreate: async(user) => {
                    const password = await bcrypt.hash(user.password, 10);
                    user.password = password;
                },
            }**
        }
    }

(This part of the code was made by @Carlos Alberto Angel Angel, some comments above this)

This is good! But when we create a password, user will receive the hash too, so we need to delete it from the info that will be sent.

In user.service: We delete password from dataValues when object is created

async create(data) {

        const newUser = await models.User.create(data);
        //Delete password to don't send it when created
        **delete newUser.dataValues.password;**
        return newUser;
    }

Se debe hacer hashing por el lado de users y customers para guardar la contraseña hasheada en la base de datos, para ello modificamos la parte del servicio en ambos lados.

user.service.js, en esta parte se usa delete newUser.dataValues.password para que la respuesta no devuelva la password al crear un usuario:

async create(data) {
    const hash = await bcrypt.hash(data.password, 10);
    const newUser = await models.User.create({
      ...data,
      password: hash,
    });
    delete newUser.dataValues.password;
    return newUser;
  }

customer.service.js, lo que se hace es clonar la información (data) y al subobjeto le pasamos la información y específicamente el password (hash). Se usa delete newCustomer.user.dataValues.password para que la respuesta no devuelva la password al crear un cliente (ligado a usuario):

async create(data) {
    const hash = await bcrypt.hash(data.user.password, 10);
    const newData = {
      ...data,
      user: {
        ...data.user,
        password: hash,
      },
    };
    const newCustomer = await models.Customer.create(newData, {
      include: ['user'],
    });
    delete newCustomer.user.dataValues.password;
    return newCustomer;
  }

Si usas sequelize, mejor usa la serialización que viene por defecto para excluir atributos, y el scope para poder usar todos:

{
    sequelize,
    modelName: 'User',
    tableName: 'users',
    defaultScope: {
      attributes: {
        exclude: ['password']
      }
    },
    scopes: {
      allProperties: {
        attributes: ['id', 'email', 'name','password',]
      }
    }
  }

Podemos excluir el password de las consultas que incluyan el User cambiando el defaultScope, y si necesitamos hacer una consulta con el password podemos crear otro scope

static config(sequelize){
    return {
      sequelize,
      tableName: USER_TABLE,
      modelName: 'User',
      timestamps: false,
      hooks: {
        beforeCreate: async (user) => {
          const password = await bcrypt.hash(user.password, 10);
          user.password = password;
        },
      },
      defaultScope: {
        attributes: { exclude: ['password'] },
      },
      scopes: {
        withPassword:{ attributes: {}, }
      },
    }
  }

Para realizar las consulta con el scope withPassword:

const user = await models.User.scope("withPassword").findByPk(id);

Para eliminar el password de la respuesta, simplemente colocar esto antes del return:

  delete newCustomer.user.dataValues.password;
  return newCustomer;
}

➕# Clase #6: Implementando hashing para usuarios 6/20 ➕
 

Continuando con el Proyecto: 📝

 

  • Se va a realizar el hashing tanto para los usuarios (users) y clientes (customers), para ello vamos a VSC, buscamos la carpeta services, luego abrimos el archivo user.service.js, se modificará la función de crear usuario, se necesitará importar en la cabecera el bcrypt para la encriptación:
     
const bcrypt = require('bcrypt');

 
La función create(data) queda:
 

async create(data) {
    //El password viene de data.password
    const hash = await bcrypt.hash(data.password, 10);
    const newUser = await models.User.create({
      //Se clona todo el objeto con data
      ...data,
      //Se asigna el password
      password: hash
    });
    //Con el delete se elimina el password de la respuesta para no mostrar el password en pantalla
    //Lo elimina de dataValues
    delete newUser.dataValues.password;
    return newUser;
  }

 

  • Guardamos, vamos a la terminal y en caso de que no se haya levantado docker ni pgAdmin, hacerlo con:
     
docker-compose up -d postgres

docker-compose up -d pgadmin

 
Luego correr con dev:
 

npm run dev

 

  • Abrir Insomnia, e ir a la carpeta users, con la opción dev para la dirección de la API, vamos al POST de users y colocamos en el JSON:
     
{
	"email": "admin @ mail. com",
	"password": "12356 .2019",
	"role": "admin"
}

 
Al enviar (Send) en la salida debe estar el código 201 Created con:
 

{
	"createdAt": "2023-03-16T23:16:53.124Z",
	"id": 1,
	"email": "admin @ mail. com",
	"role": "admin"
}

 
Nota: No sale el password impreso ya que se agregó el código el método delete para borrar en la respuesta el hash del password.
 
En cuanto a customer, dentro de la carpeta services la función create(data) queda:
 

async create(data) {
    //Se crea el hash
    const hash = await bcrypt.hash(data.user.password, 10);
    //Se hace un clonación de data
    const newData = {
      ...data,
      //Se sobreescribe el sujeto user
      user: {
        ...data.user,
        //Se modifica el password
        password: hash
      }
    }
    const newCustomer = await models.Customer.create(newData, {
      include: ['user']
    });
    delete newCustomer.dataValues.user.dataValues.password;
    return newCustomer;
  }

 
Nota: Tomar en cuenta que en éste caso tenemos anidado la creación de un customer.
 

  • Guardamos, con la terminal corriendo: npm run dev
     
  • Vamos a Insomnia, ir a la carpeta customers, con la opción dev para la dirección de la API, vamos al POST de customers y colocamos en el JSON:
     
{
	"name": "Santiago",
	"lastName": "Molina",
	"phone": "9898",
	"user": {
		"email": "santi @ mail.
com",
		"password": "23 23 23"
	}
}

 
Al enviar (Send) en la salida debe estar el código 201 Created con:
 

{
	"createdAt": "2023-03-16T23:37:01.327Z",
	"id": 1,
	"name": "Santiago",
	"lastName": "Molina",
	"phone": "9898",
	"user": {
		"role": "customer",
		"createdAt": "2023-03-16T23:37:01.328Z",
		"id": 2,
		"email": "santi @ mail. com"
	},
	"userId": 2
}

 
Nota: No sale el password impreso ya que se agregó el código el método delete para borrar en la respuesta el hash del password.
 

entiendo que el hash para un valor determinado es siempre el mismo, por lo tanto si dos usuarios tienen la misma contraseña el hash sería el mismo, esto hace vulnerable a la aplicación porque sería posible de cierta manera identificar los usuarios que tienen la misma contraseña. Sugiero que al construir el hash se concatene el nombre de usuario al hash y con esto se obtiene un hash único por usuario aunque tengan claves iguales.

Yo lo hice con MongoDB y para eliminar el campo de password en las solicitudes es tan sencillo como lo siguiente:

const userSchema = new Schema(
  {
    email: {
      type: String,
      required: true,
      unique: true,
    },
    password: {
      type: String,
      required: true,
    },
  },
  { timestamps: true }
);

userSchema.methods.toJSON = function () {
  const user = this.toObject();
  delete user.password;
  return user;
}

const User = mongoose.model('User', userSchema);

module.exports = User;

El tema de eliminar el password es interesante pero yo vengo un poco de django y con rest_framework, y en los serializaers podemos definir que campos mostrar, y sequelize tiene esta opción, por lo que muestro mi código para que conozcan un poco mas…!

User Service

// Crea un nuevo usuario y lo retorna
  async create(body) {
    let password_hashed = await bcrypt.hash(body.password, 10)
    body.password = password_hashed
    const newUser = await models.User.create(body);
    if (body.profile) {
      const profile = await models.Profile.create({...body.profile, userId: newUser.id})
    }
    if (body.address) {
      const address = await models.Address.create({...body.address, userId: newUser.id})
    }
    return await this.getOne(newUser.id);
  }
// Retorna un usuario por su primaryKey
  async getOne(id) {
    const user = await models.User.findByPk(id, {
      attributes: ['id', 'email', 'role', 'isActive', 'isAdmin', 'isStaff'],
      include: [
        {
          association: 'profile',
          attributes: ['name', 'lastName', 'image', 'phone']
        },
        {
          association: 'address',
          attributes: ["postalCode", "country", "city", "description", "reference"]
        }
      ],
    })
    if (!user) {
      throw boom.notFound(`User with id:${id} not exits..!`)
    }
    return user
  }

En donde no se si esta mal realizar otra petición, sin embargo creo que era lo mejor para reducir código…!

Yo no use customer al contrario use Profile

Profile Service

 // Crea un nuevo perfil y lo retorna
  async create(body) {
    if (body.user) {
      let password_hashed = await bcrypt.hash(body.user.password, 10)
      body.user.password = password_hashed
    }
    const newProfile = await models.Profile.create(body, {
      attributes: ['id', 'name', 'lastName', 'image', 'phone', 'userId'],
      include: ['user']
    });
    return await this.getOne(newProfile.id);
  }

  // Retorna un perfil por su primaryKey
  async getOne(id) {
    const profile = await models.Profile.findByPk(id, {
      attributes: ['id', 'name', 'lastName', 'image', 'phone', 'userId'],
      include: {
        association: 'user',
        attributes: ['id', 'email', 'role', 'isActive', 'isAdmin', 'isStaff']
      }
    });
    if (!profile) {
      throw boom.notFound(`Profile with id:${id} not exits..!`);
    };
    return profile
  }

Como se observa le podemos decir que atributos o campos mostrar y de esta misma manera para los includes…!
Espero a alguien le sirva…!

Eliminar el password de la respuesta =>

delete newCustomer.user.dataValues.password;
delete newCustomer.dataValues.user.dataValues.password;

Esta fue la manera en que elimine yo la propiedad ‘password’, este código pertenece a mi servicio de ‘customers’.

  async findAll() {
    const rta = await models.Customer.findAll({
      include: [
        {
          association: "user",
          attributes: { exclude: ["createdAt", "updatedAt", "password"] },
        },
      ],
      attributes: { exclude: ["createdAt", "updatedAt"] },
    });
    return rta;
  }

  async findOne(id) {
    const rta = await models.Customer.findByPk(id, {
      include: [{ association: "user", attributes: { exclude: "password" } }],
    });
    if (!rta) throw boom.notFound("Customer not Found");
    return rta;
  }

En mi caso yo creo a los ‘customers’ desde ‘users’

sencillamente agregue el user antes del dataValues y funciono

este es el resultado

para el reto de eliminar el passwor lo realize de la siguiente manera: ```js async create(data) { const hash = await bcrypt.hash(data.user.password, 10); const newData = { ...data, user: { ...data.user, password: hash, }, }; const newCustomer = await models.Customer.create(newData, { include: ['user'], }); delete newCustomer.dataValues.user.dataValues.password; return newCustomer; } ```async create(data) {    const hash = await bcrypt.hash(data.user.password, 10);    const newData = {      ...data,      user: {        ...data.user,        password: hash,      },    };    const newCustomer = await models.Customer.create(newData, {      include: \['user'],    });     delete newCustomer.dataValues.user.dataValues.password;    return newCustomer;  }
Para aportar a los compañeros que ya explicaron el hook de **beforeCreate** para encriptar la contraseña, les comparto el hook **afterCreate**: ```js afterCreate: async (user) => { delete user.dataValues.password; }, ```Con este pueden eliminar directamente el atributo **password** retornado, Esto no elimina el valor en el campo (porque ya se izo el insert) y permite definir una única regla en el modelo sin tener que estar eliminando la propiedad desde los diferente servicios que usen el modelo

Una manera más sencilla para hacer que la contraseña tenga el hash para la entidad de Customer, puede ser de la siguiente manera:

data.user.password = await bcrypt.hash(data.user.password, 10)

mi solucion al reto fue el siguiente

delete newCustomer.user.dataValues.password;

en el cual especifico que entro al objeto user y luego en sus valores al password

Si usamos Typescript para hacer nuestra API no es tan sencillo usar delete para eliminar el password, yo cuando me enfrente a ese reto lo que hice fue setear la propiedad password a password: ''

Para que no se vean las contraseñas al momento de hacer GET de los clientes y de los usuarios, debemos de excluir ese atributo mediante Sequelize. Se realiza de la siguiente manera:

Nuestros métodos findUsers y findOneUser o como el profesor lo tiene en el curso: find y findOne, quedaría así:

// user.service.js
async findUsers () {
    const users = await models.User.findAll({
      attributes: {
        exclude: ['password']
      },
      include: ['customer']
    })
    return users
  }

async findOneUser (id) {
    const user = await models.User.findByPk(id, {
      attributes: {
        exclude: ['password']
      }
    })
    if (!user) {
      throw boom.notFound('User not found')
    }
    return user
  }

Y nuestro método findCustomers o como el profesor lo tiene en el curso: find, quedaría así:

// customer.service.js
async findCustomers () {
    const customers = await models.Customer.findAll({
      include: [{
        model: User,
        as: 'user',
        attributes: {
          exclude: ['password']
        }
      }]
    })
    return customers
  }

a ver el tema de hacer el delete dentro del create está bien, pero me chilla que estamos haciendo otra cosa más allá de crear dentro de método(por el tema de la S de los principios SOLID), tal vez si separamos esa lógica en un mapper sería mejor, idk solo quería comentarlo 😁

delete newCustomer.user.dataValues.password

Si utilizas Mongoose (MongoDB), para eliminar la propiedad, hay que acceder a ._doc:

const dbRes = await newUser.save();
    delete dbRes._doc.password;
    return dbRes;

Saludos!

No se si tendrá un efecto práctico pero tengo duda sobre como quitar los hash en el service get de los clientes. No se si en include[iser] se puede limitar que solo traiga ‘x’ datos

No se me había ocurrido hashear los usuarios

Reto conseguido!