Wie kann ich das Kennwortfeld in Mongoose / MongoDB schützen, damit es beim Auffüllen von Sammlungen nicht in einer Abfrage zurückgegeben wird?

85

Angenommen, ich habe zwei Sammlungen / Schemata. Eines ist das Benutzerschema mit Benutzernamen- und Kennwortfeldern. Dann habe ich ein Blogs-Schema, das einen Verweis auf das Benutzerschema im Autorenfeld enthält. Wenn ich Mungo benutze, um so etwas zu tun

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

Ich werde das Blog-Dokument und den Benutzer ebenfalls ausfüllen lassen, aber wie kann ich verhindern, dass Mongoose / MongoDB das Kennwortfeld zurückgibt? Das Passwortfeld ist gehasht, sollte aber nicht zurückgegeben werden.

Ich weiß, dass ich das Kennwortfeld weglassen und den Rest der Felder in einer einfachen Abfrage zurückgeben kann, aber wie mache ich das mit populate. Gibt es auch eine elegante Möglichkeit, dies zu tun?

In einigen Situationen muss ich auch das Kennwortfeld abrufen, z. B. wenn der Benutzer sich anmelden oder das Kennwort ändern möchte.

Luis Elizondo
quelle
Sie können auch .populate ('Benutzer': 1, 'Passwort': 0)
Sudhanshu Gaur

Antworten:

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

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

JohnnyHKs Antwort mit Schema-Optionen ist wahrscheinlich der richtige Weg.

Beachten Sie auch, dass query.exclude()nur in der 2.x-Verzweigung vorhanden ist.

aaronheckmann
quelle
Dies funktioniert auch .populate ('Benutzer': 1, 'Passwort': 0)
Sudhanshu Gaur
Ich habe nicht verstanden, warum das Hinzufügen von "-" funktioniert, und war neugierig. Daher habe ich in den Dokumenten eine anständige Erklärung gefunden: Wenn Sie die Zeichenfolgensyntax verwenden und einen Pfad mit - voranstellen, wird dieser Pfad als ausgeschlossen gekennzeichnet. Wenn ein Pfad nicht das Präfix - hat, wird er eingeschlossen. Wenn einem Pfad das Präfix + vorangestellt wird, wird die Aufnahme des Pfads erzwungen. Dies ist nützlich für Pfade, die auf Schemaebene ausgeschlossen sind. Hier ist der Link für die ganze Sache mongoosejs.com/docs/api.html#query_Query-select
Connor
303

Sie können das Standardverhalten auf der Ebene der Schemadefinition mithilfe des selectAttributs des Felds ändern :

password: { type: String, select: false }

Dann können Sie es nach Bedarf einziehen findund populateüber die Feldauswahl als anrufen '+password'. Zum Beispiel:

Users.findOne({_id: id}).select('+password').exec(...);
JohnnyHK
quelle
3
Großartig. Können Sie ein Beispiel dafür geben, wer es in find hinzufügen soll? Angenommen, ich habe: Users.find ({id: _id}), wo soll ich das "+ Passwort +?"
Luis Elizondo
Das Beispiel finden Sie unter dem von Ihnen angegebenen Link. mongoosejs.com/docs/api.html#schematype_SchemaType-select Danke
Luis Elizondo
9
Gibt es eine Möglichkeit, dies auf das Objekt anzuwenden, das an den Rückruf save () übergeben wurde? Wenn ich ein Benutzerprofil speichere, ist das Kennwort nicht im Rückrufparameter enthalten.
Matt
2
Dies ist meiner Meinung nach bei weitem die beste Antwort. Fügen Sie dies einmal hinzu, und es ist ausgeschlossen. Viel besser als das Hinzufügen einer Auswahl- oder Ausschlussoption zu jeder Abfrage.
AndyH
Dies sollte die ultimative Antwort sein. Zum Schema hinzugefügt und muss nicht vergessen, bei Abfragen auszuschließen.
KhoPhi
23

Bearbeiten:

Nachdem ich beide Ansätze ausprobiert hatte, stellte ich fest, dass der Ansatz "Ausschluss immer" bei mir aus irgendeinem Grund mit einer passportlokalen Strategie nicht funktionierte. Ich weiß nicht wirklich warum.

Das habe ich also benutzt:

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

Es ist nichts Falsches daran, immer auszuschließen, es hat aus irgendeinem Grund einfach nicht mit dem Reisepass funktioniert. Meine Tests haben mir gezeigt, dass das Passwort tatsächlich ausgeschlossen / eingeschlossen wurde, als ich wollte. Das einzige Problem mit dem Ansatz "Immer einschließen" ist, dass ich grundsätzlich jeden Aufruf der Datenbank durchlaufen und das Kennwort ausschließen muss, was viel Arbeit bedeutet.


Nach ein paar großartigen Antworten fand ich heraus, dass es zwei Möglichkeiten gibt, dies zu tun: "Immer einschließen und manchmal ausschließen" und "Immer ausschließen und manchmal einschließen"?

Ein Beispiel für beides:

Das schließt immer aber manchmal ein Beispiel ein:

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

oder

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

Die exlucde immer aber manchmal Beispiel enthalten:

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

Sie müssen jedoch im Schema Folgendes definieren:

password: { type: String, select: false }
Luis Elizondo
quelle
Ich würde für die letzte Option gehen. Wählen Sie niemals das Passwort aus, außer in den Funktionen Anmelden / Passwort aktualisieren, in denen Sie es wirklich benötigen.
Rdrey
Aus irgendeinem Grund funktionierte diese Option nicht mit der lokalen Strategie von Passport.j. Ich weiß nicht warum.
Luis Elizondo
Gute Antwort, danke !!! Ich weiß nicht warum, aber wenn ich es tue .select("+field"), bringt es nur das __id, obwohl es .select("-field")das Feld, das ich will, gut ausschließt
Renato Gama
Sorry, es funktioniert perfekt, habe nicht bemerkt, dass dies select: falseobligatorisch ist
Renato Gama
1
Dies funktioniert für meine lokale Strategie: Warten Sie auf User.findOne ({E-Mail: Benutzername}, {Passwort: 1}, asynchron (err, Benutzer) => {...});
TomoMiha
10

User.find().select('-password')ist die richtige Antwort. Sie können select: falsedas Schema nicht hinzufügen, da es nicht funktioniert, wenn Sie sich anmelden möchten.

Ylli Gashi
quelle
Können Sie das Verhalten im Anmeldeendpunkt nicht überschreiben? Wenn ja, scheint dies die sicherste Option zu sein.
Erfling
@erfling wird es nicht.
jrran90
1
Sie können dies const query = model.findOne({ username }).select("+password");bei Anmelde- und Kennwortänderungs- / Rücksetzrouten verwenden und ansonsten sicherstellen, dass es niemals herauskommt. Dies ist bei weitem sicherer, wenn es nicht standardmäßig zurückgegeben wird, da Menschen nachweislich Fehler imo
Cacoon
10

Sie können dies mit dem Schema erreichen, zum Beispiel:

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
quelle
3
Ich habe jede Antwort getestet. Ich denke, dies ist die beste Option, wenn Sie eine API entwickeln.
Asmmahmud
Dadurch werden keine Felder in der Bevölkerung entfernt.
JulianSoto
1
Die beste Option für mich, dieser Ansatz widerspricht nicht meiner Authentifizierungsmethode
Mathiasfc
Dies ist der beste Weg, wenn Sie eine API verwenden. Sie müssen sich nie darum kümmern, zu vergessen, ein Feld zu entfernen :)
Sam Munroe
4

Ich verwende zum Ausblenden des Kennwortfelds in meiner REST-JSON-Antwort

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

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

Angenommen, Ihr Passwortfeld ist "Passwort", können Sie einfach Folgendes tun:

.exclude('password')

Es gibt ein ausführlicheres Beispiel hier

Das konzentriert sich auf Kommentare, aber es ist das gleiche Prinzip im Spiel.

Dies entspricht der Verwendung einer Projektion in der Abfrage in MongoDB und der Übergabe {"password" : 0}im Projektionsfeld. Siehe hier

Adam Comerford
quelle
Großartig. Vielen Dank. Ich mag diesen Ansatz.
Luis Elizondo
2

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

Ricky
quelle
2

Die Lösung besteht darin, niemals Klartextkennwörter zu speichern. Sie sollten ein Paket wie bcrypt oder password-hash verwenden .

Beispiel für die Verwendung des Passworts:

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

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

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

Beispiel für die Verwendung zur Überprüfung des Kennworts:

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
quelle
8
Unabhängig davon, ob das Passwort gehasht wird oder nicht, sollte das Passwort / der Hash dem Benutzer niemals angezeigt werden. Ein Angreifer kann einige wichtige Informationen über das Hash-Passwort (welcher Algorithmus verwendet wurde) und so weiter erhalten.
Marco
2

Sie können ein DocumentToObjectOptions- Objekt an schema.toJSON () oder schema.toObject () übergeben .

Siehe TypeScript-Definition von @ 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 verfügt über eine Transformationsoption, die eine benutzerdefinierte Funktion ausführt, nachdem das Dokument in ein Javascript-Objekt konvertiert wurde. Hier können Sie Eigenschaften ausblenden oder ändern, um Ihre Anforderungen zu erfüllen.

Angenommen, Sie verwenden schema.toObject () und möchten den Kennwortpfad aus Ihrem Benutzerschema ausblenden. Sie sollten eine allgemeine Transformationsfunktion konfigurieren, die nach jedem Aufruf von toObject () ausgeführt wird.

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

Ich habe einen anderen Weg gefunden, dies zu tun, indem ich der Schemakonfiguration einige Einstellungen hinzugefügt habe.

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 });

Durch Hinzufügen einer Transformationsfunktion zum JSON-Objekt mit dem auszuschließenden Feldnamen. wie in den Dokumenten angegeben :

Möglicherweise müssen wir eine Transformation des resultierenden Objekts basierend auf bestimmten Kriterien durchführen, z. B. um vertrauliche Informationen zu entfernen oder ein benutzerdefiniertes Objekt zurückzugeben. In diesem Fall setzen wir die optionale transformFunktion.

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

Beachten password: { type: String, select: false }Sie bei der Verwendung, dass das Kennwort auch dann ausgeschlossen wird, wenn wir es zur Authentifizierung benötigen. Seien Sie also bereit, damit umzugehen, wie Sie wollen.

Anand Kapdi
quelle
0

Dies ist eher eine Folge der ursprünglichen Frage, aber dies war die Frage, auf die ich stieß, als ich versuchte, mein Problem zu lösen ...

Das heißt, wie der Benutzer im Rückruf user.save () ohne das Kennwortfeld an den Client zurückgesendet wird.

Anwendungsfall: Der Anwendungsbenutzer aktualisiert seine Profilinformationen / -einstellungen vom Client (Kennwort, Kontaktinformationen, Whatevs). Sie möchten die aktualisierten Benutzerinformationen in der Antwort an den Client zurücksenden, sobald sie erfolgreich in mongoDB gespeichert wurden.

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
quelle
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
quelle