No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Aprende todo un fin de semana sin pagar una suscripci贸n 馃敟

Aprende todo un fin de semana sin pagar una suscripci贸n 馃敟

Reg铆strate

Comienza en:

2D
19H
39M
3S

Implementando hashing para usuarios

6/20
Recursos

Aportes 21

Preguntas 6

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

o inicia sesi贸n.

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鈥檓 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;
  }

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

  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',]
      }
    }
  }

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.

Eliminar el password de la respuesta =>

delete newCustomer.user.dataValues.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);

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鈥!

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;

鉃# 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.

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: ''

delete newCustomer.dataValues.user.dataValues.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 鈥榵鈥 datos

No se me hab铆a ocurrido hashear los usuarios

Reto conseguido!