¿Cómo proteger el campo de contraseña en Mongoose / MongoDB para que no regrese en una consulta cuando complete las colecciones?

84

Supongamos que tengo dos colecciones / esquemas. Uno es el esquema de usuarios con campos de nombre de usuario y contraseña, luego, tengo un esquema de blogs que tiene una referencia al esquema de usuarios en el campo del autor. Si uso Mongoose para hacer algo como

Blogs.findOne({...}).populate("user").exec()

También tendré el documento del Blog y el usuario, pero ¿cómo puedo evitar que Mongoose / MongoDB devuelva el campo de contraseña? El campo de contraseña tiene un hash, pero no debe devolverse.

Sé que puedo omitir el campo de contraseña y devolver el resto de los campos en una consulta simple, pero ¿cómo lo hago con rellenar? Además, ¿hay alguna forma elegante de hacer esto?

Además, en algunas situaciones, necesito obtener el campo de contraseña, como cuando el usuario quiere iniciar sesión o cambiar la contraseña.

Luis Elizondo
fuente
también puede hacer .populate ('usuario': 1, 'contraseña': 0)
Sudhanshu Gaur

Respuestas:

66
.populate('user' , '-password')

http://mongoosejs.com/docs/populate.html

La respuesta de JohnnyHK usando las opciones de esquema es probablemente el camino a seguir aquí.

También tenga en cuenta que query.exclude()solo existe en la rama 2.x.

Aaronheckmann
fuente
esto también funcionará .populate ('usuario': 1, 'contraseña': 0)
Sudhanshu Gaur
No entendí por qué funciona agregar "-" y tenía curiosidad, así que encontré una explicación decente en los documentos: cuando se usa la sintaxis de cadena, anteponer una ruta con - marcará esa ruta como excluida. Cuando una ruta no tiene el prefijo -, se incluye. Por último, si una ruta tiene el prefijo +, fuerza la inclusión de la ruta, lo que es útil para las rutas excluidas en el nivel de esquema. Aquí está el enlace para la cosa completa mongoosejs.com/docs/api.html#query_Query-select
Connor
298

Puede cambiar el comportamiento predeterminado en el nivel de definición de esquema utilizando el selectatributo del campo:

password: { type: String, select: false }

Luego, puede ingresarlo según sea necesario findy populatellamar mediante la selección de campo como '+password'. Por ejemplo:

Users.findOne({_id: id}).select('+password').exec(...);
JohnnyHK
fuente
3
Excelente. ¿Puede proporcionar un ejemplo sobre a quién agregarlo en buscar? Suponiendo que tengo: Users.find ({id: _id}) ¿dónde debo agregar el "+ contraseña +?
Luis Elizondo
Encontré el ejemplo en el enlace que proporcionó. mongoosejs.com/docs/api.html#schematype_SchemaType-select Gracias
Luis Elizondo
8
¿Hay alguna forma de aplicar esto al objeto pasado a la devolución de llamada save ()? De modo que cuando guardo un perfil de usuario, la contraseña no se incluye en el parámetro de devolución de llamada.
Matt
2
Esta es, con mucho, la mejor respuesta en mi opinión. Agregue esto una vez y se excluirá. Mucho mejor que agregar una opción de selección o exclusión a cada consulta.
AndyH
Esta debería ser la respuesta definitiva. Se agregó al esquema y no tiene que olvidar la exclusión durante las consultas.
KhoPhi
22

Editar:

Después de probar ambos enfoques, descubrí que el enfoque de excluir siempre no funcionaba para mí por alguna razón al usar la estrategia local de pasaporte, realmente no sé por qué.

Entonces, esto es lo que terminé usando:

Blogs.findOne({_id: id})
    .populate("user", "-password -someOtherField -AnotherField")
    .populate("comments.items.user")
    .exec(function(error, result) {
        if(error) handleError(error);
        callback(error, result);
    });

No hay nada de malo en el enfoque de excluir siempre, simplemente no funcionó con el pasaporte por alguna razón, mis pruebas me dijeron que, de hecho, la contraseña estaba siendo excluida / incluida cuando quería. El único problema con el enfoque de incluir siempre es que básicamente necesito pasar por cada llamada que hago a la base de datos y excluir la contraseña, lo cual es mucho trabajo.


Después de un par de excelentes respuestas, descubrí que hay dos formas de hacer esto, "incluir siempre y excluir a veces" y "excluir siempre e incluir a veces".

Un ejemplo de ambos:

El ejemplo incluir siempre pero excluir a veces :

Users.find().select("-password")

o

Users.find().exclude("password")

El exlucde siempre pero incluye a veces el ejemplo:

Users.find().select("+password")

pero debes definir en el esquema:

password: { type: String, select: false }
Luis Elizondo
fuente
Yo optaría por la última opción. Nunca seleccione la contraseña, excepto en las funciones logIn / passwordUpdate en las que realmente la necesita
rdrey
Por alguna razón, esa opción no funcionó con la estrategia local de Passport.js, no sé por qué.
Luis Elizondo
Buena respuesta, gracias !!! No sé por qué, pero cuando lo hago .select("+field"), solo trae el __id, aunque .select("-field")excluye muy bien el campo que quiero
Renato Gama
Lo siento, funciona perfecto, no me di cuenta de que select: falsees obligatorio
Renato Gama
1
Esto funciona para mi estrategia local: await User.findOne ({email: username}, {password: 1}, async (err, user) => {...});
TomoMiha
10

User.find().select('-password')es la respuesta correcta. No puede agregar select: falseel esquema, ya que no funcionará si desea iniciar sesión.

Ylli Gashi
fuente
¿No puede anular el comportamiento en el punto final de inicio de sesión? Si es así, esta parece la opción más segura.
erfling
@erfling, no lo hará.
jrran90
1
Lo hará, puede usarlo const query = model.findOne({ username }).select("+password");y usarlo en las rutas de inicio de sesión y cambio / restablecimiento de contraseña y, de lo contrario, asegurarse de que nunca salga. Esto es mucho más seguro para que no regrese de forma predeterminada, ya que se ha demostrado que las personas cometen errores en mi opinión
Cacoon
10

Puede lograrlo usando el esquema, por ejemplo:

const UserSchema = new Schema({/* */})

UserSchema.set('toJSON', {
    transform: function(doc, ret, opt) {
        delete ret['password']
        return ret
    }
})

const User = mongoose.model('User', UserSchema)
User.findOne() // This should return an object excluding the password field
Ikbel
fuente
3
He probado todas las respuestas, creo que esta es la mejor opción si está desarrollando una API.
asmmahmud
Esto no elimina los campos de población.
JulianSoto
1
La mejor opción para mí, este enfoque no entra en conflicto con mi método de autenticación
Mathiasfc
Esta es la mejor manera de hacerlo si está utilizando una API. Nunca tendrás que preocuparte por olvidarte de eliminar un campo :)
Sam Munroe hace
4

Estoy usando para ocultar el campo de contraseña en mi respuesta REST JSON

UserSchema.methods.toJSON = function() {
 var obj = this.toObject(); //or var obj = this;
 delete obj.password;
 return obj;
}

module.exports = mongoose.model('User', UserSchema);
Gere
fuente
3

Suponiendo que su campo de contraseña es "contraseña", puede hacer lo siguiente:

.exclude('password')

Hay un ejemplo más extenso aquí.

Eso se centra en los comentarios, pero es el mismo principio en juego.

Esto es lo mismo que usar una proyección en la consulta en MongoDB y pasar {"password" : 0}el campo de proyección. Ver aqui

Adam Comerford
fuente
Excelente. Gracias. Me gusta este enfoque.
Luis Elizondo
2

Blogs.findOne({ _id: id }, { "password": 0 }).populate("user").exec()

Ricky
fuente
2

La solución es nunca almacenar contraseñas de texto sin formato. Debería utilizar un paquete como bcrypt o password-hash .

Ejemplo de uso para hash la contraseña:

 var passwordHash = require('password-hash');

    var hashedPassword = passwordHash.generate('password123');

    console.log(hashedPassword); // sha1$3I7HRwy7$cbfdac6008f9cab4083784cbd1874f76618d2a97

Ejemplo de uso para verificar la contraseña:

var passwordHash = require('./lib/password-hash');

var hashedPassword = 'sha1$3I7HRwy7$cbfdac6008f9cab4083784cbd1874f76618d2a97';

console.log(passwordHash.verify('password123', hashedPassword)); // true
console.log(passwordHash.verify('Password0', hashedPassword)); // false
Cameron Hudson
fuente
7
Independientemente de si la contraseña está codificada o no, la contraseña / el hash nunca debe mostrarse al usuario. Un atacante puede obtener información importante de la contraseña hash (qué algoritmo se utilizó) y así.
Marco
2

Puede pasar un objeto DocumentToObjectOptions a schema.toJSON () o schema.toObject () .

Consulte la definición de TypeScript de @ types / mongoose

 /**
 * The return value of this method is used in calls to JSON.stringify(doc).
 * This method accepts the same options as Document#toObject. To apply the
 * options to every document of your schema by default, set your schemas
 * toJSON option to the same argument.
 */
toJSON(options?: DocumentToObjectOptions): any;

/**
 * Converts this document into a plain javascript object, ready for storage in MongoDB.
 * Buffers are converted to instances of mongodb.Binary for proper storage.
 */
toObject(options?: DocumentToObjectOptions): any;

DocumentToObjectOptions tiene una opción de transformación que ejecuta una función personalizada después de convertir el documento en un objeto javascript. Aquí puede ocultar o modificar propiedades para satisfacer sus necesidades.

Entonces, digamos que está usando schema.toObject () y desea ocultar la ruta de la contraseña de su esquema de usuario. Debe configurar una función de transformación general que se ejecutará después de cada llamada a toObject ().

UserSchema.set('toObject', {
  transform: (doc, ret, opt) => {
   delete ret.password;
   return ret;
  }
});
netishix
fuente
2

Encontré otra forma de hacer esto, agregando algunas configuraciones a la configuración del esquema.

const userSchema = new Schema({
    name: {type: String, required: false, minlength: 5},
    email: {type: String, required: true, minlength: 5},
    phone: String,
    password: String,
    password_reset: String,
}, { toJSON: { 
              virtuals: true,
              transform: function (doc, ret) {
                delete ret._id;
                delete ret.password;
                delete ret.password_reset;
                return ret;
              }

            }, timestamps: true });

Añadiendo la función de transformación al objeto toJSON con el nombre de campo para excluir. como se indica en los documentos :

Es posible que necesitemos realizar una transformación del objeto resultante en función de algunos criterios, por ejemplo, para eliminar información confidencial o devolver un objeto personalizado. En este caso configuramos la transformfunción opcional .

Nerius Jok
fuente
2
router.get('/users',auth,(req,res)=>{
   User.findById(req.user.id)
    //skip password
    .select('-password')
    .then(user => {
        res.json(user)
    })
})
Desai Ramesh
fuente
1

Mientras lo usa password: { type: String, select: false }, debe tener en cuenta que también excluirá la contraseña cuando la necesitemos para la autenticación. Así que prepárate para manejarlo como quieras.

Anand Kapdi
fuente
0

Esto es más un corolario de la pregunta original, pero esta fue la pregunta que encontré al intentar resolver mi problema ...

Es decir, cómo enviar al usuario de vuelta al cliente en la devolución de llamada user.save () sin el campo de contraseña.

Caso de uso: el usuario de la aplicación actualiza la información / configuración de su perfil desde el cliente (contraseña, información de contacto, lo que sea). Desea enviar la información de usuario actualizada al cliente en la respuesta, una vez que se haya guardado correctamente en mongoDB.

User.findById(userId, function (err, user) {
    // err handling

    user.propToUpdate = updateValue;

    user.save(function(err) {
         // err handling

         /**
          * convert the user document to a JavaScript object with the 
          * mongoose Document's toObject() method,
          * then create a new object without the password property...
          * easiest way is lodash's _.omit function if you're using lodash 
          */

         var sanitizedUser = _.omit(user.toObject(), 'password');
         return res.status(201).send(sanitizedUser);
    });
});
Bennett Adams
fuente
0

const userSchema = new mongoose.Schema(
  {
    email: {
      type: String,
      required: true,
    },
    password: {
      type: String,
      required: true,
    },
  },
  {
    toJSON: {
      transform(doc, ret) {
        delete ret.password;
        delete ret.__v;
      },
    },
  }
);

Rafał Bochniewicz
fuente