Jak zabezpieczyć pole hasła w Mongoose / MongoDB, aby nie zwracało się w zapytaniu, gdy zapełniam kolekcje?

85

Załóżmy, że mam dwie kolekcje / schematy. Jednym z nich jest schemat użytkowników z polami nazwy użytkownika i hasła, a zatem mam schemat blogów, który zawiera odniesienie do schematu użytkowników w polu autora. Jeśli użyję Mongoose do zrobienia czegoś takiego

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

Będę mieć również wypełniony dokument Blog i nazwę użytkownika, ale jak zapobiec zwracaniu pola hasła przez Mongoose / MongoDB? Pole hasła jest zaszyfrowane, ale nie powinno być zwracane.

Wiem, że mogę pominąć pole hasła i zwrócić pozostałe pola w prostym zapytaniu, ale jak to zrobić z wypełnieniem. Czy jest na to jakiś elegancki sposób?

Ponadto w niektórych sytuacjach potrzebuję pola hasła, na przykład gdy użytkownik chce się zalogować lub zmienić hasło.

Luis Elizondo
źródło
możesz też zrobić .populate ('user': 1, 'password': 0)
Sudhanshu Gaur

Odpowiedzi:

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

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

Odpowiedź JohnnyHKs za pomocą opcji schematu jest prawdopodobnie sposobem na przejście tutaj.

Należy również pamiętać, że query.exclude()istnieje tylko w gałęzi 2.x.

aaronheckmann
źródło
to też zadziała .populate ('user': 1, 'password': 0)
Sudhanshu Gaur
Nie rozumiałem, dlaczego dodawanie „-” działa i byłem zaciekawiony, więc znalazłem przyzwoite wyjaśnienie w dokumentacji: Podczas używania składni ciągu, przedrostek ścieżki przedrostkiem - oznacza tę ścieżkę jako wykluczoną. Jeśli ścieżka nie ma przedrostka -, jest dołączana. Na koniec, jeśli ścieżka jest poprzedzona znakiem +, wymusza włączenie ścieżki, co jest przydatne w przypadku ścieżek wykluczonych na poziomie schematu. Oto link do pełnej rzeczy mongoosejs.com/docs/api.html#query_Query-select
Connor
304

Możesz zmienić domyślne zachowanie na poziomie definicji schematu za pomocą selectatrybutu pola:

password: { type: String, select: false }

Następnie możesz pobrać go w razie potrzeby findi populatewywołać przez wybór pola jako '+password'. Na przykład:

Users.findOne({_id: id}).select('+password').exec(...);
JohnnyHK
źródło
3
Świetny. Czy możesz podać przykład, kto ma go dodać w find? Zakładając, że mam: Users.find ({id: _id}) gdzie mam dodać „+ hasło +?
Luis Elizondo
Przykład znajduje się pod podanym linkiem. mongoosejs.com/docs/api.html#schematype_SchemaType-select Thanks
Luis Elizondo
9
Czy istnieje sposób na zastosowanie tego do obiektu przekazanego do wywołania zwrotnego save ()? Takie, że kiedy zapisuję profil użytkownika, hasło nie jest zawarte w parametrze wywołania zwrotnego.
Matt
2
Moim zdaniem jest to zdecydowanie najlepsza odpowiedź. Dodaj to raz i to jest wykluczone. Znacznie lepsze niż dodawanie opcji wybierz lub wyklucz do każdego zapytania.
AndyH
To powinna być ostateczna odpowiedź. Dodano do schematu i nie trzeba zapominać o wyłączaniu podczas zapytań.
KhoPhi
23

Edytować:

Po wypróbowaniu obu podejść stwierdziłem, że metoda wykluczania zawsze nie działa dla mnie z jakiegoś powodu przy użyciu strategii lokalnej paszportowej, naprawdę nie wiem dlaczego.

Oto, co ostatecznie użyłem:

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

Nie ma nic złego w podejściu wykluczaj zawsze, po prostu z jakiegoś powodu nie działało z paszportem, moje testy wykazały, że w rzeczywistości hasło zostało wykluczone / uwzględnione, kiedy chciałem. Jedynym problemem związanym z podejściem włączającym zawsze jest to, że w zasadzie muszę przejść przez każde wywołanie bazy danych i wykluczyć hasło, co jest dużo pracy.


Po kilku świetnych odpowiedziach odkryłem, że można to zrobić na dwa sposoby: „zawsze dołączaj i wykluczaj czasami” oraz „zawsze wykluczaj i dołączaj czasami”?

Przykład obu:

Uwzględnij zawsze, ale czasami wykluczaj przykład:

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

lub

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

Exlucde zawsze, ale czasami zawiera przykład:

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

ale musisz zdefiniować w schemacie:

password: { type: String, select: false }
Luis Elizondo
źródło
Wybrałbym ostatnią opcję. Nigdy nie wybieraj hasła, z wyjątkiem funkcji logowania / hasła Aktualizacja, w których naprawdę jest to potrzebne.
rdrey
Z jakiegoś powodu ta opcja nie działała z lokalną strategią Passport.js, nie wiem dlaczego.
Luis Elizondo
Dobra odpowiedź, dzięki !!! Nie wiem dlaczego, ale kiedy to robię, .select("+field")to przynosi tylko __id, choć .select("-field")ładnie wyklucza pole, które chcę
Renato Gama
Przepraszamy, działa idealnie, nie zauważyłem, że select: falsejest to obowiązkowe
Renato Gama,
1
To działa dla mojej strategii lokalnej: await User.findOne ({email: nazwa użytkownika}, {hasło: 1}, async (err, user) => {...});
TomoMiha
10

User.find().select('-password')to właściwa odpowiedź. Nie możesz dodać select: falseschematu, ponieważ nie zadziała, jeśli chcesz się zalogować.

Ylli Gashi
źródło
Czy nie możesz zastąpić zachowania w punkcie końcowym logowania? Jeśli tak, wydaje się, że jest to najbezpieczniejsza opcja.
erfling
@erfling, nie będzie.
jrran90
1
Będzie, możesz go używać const query = model.findOne({ username }).select("+password");i używać do zmiany / resetowania tras logowania i hasła, a także upewnij się, że nigdy nie wyjdzie. Jest to o wiele bezpieczniejsze, aby nie wracało domyślnie, ponieważ ludziom udowodniono błędy imo
Cacoon
10

Możesz to osiągnąć za pomocą schematu, na przykład:

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
źródło
3
Przetestowałem każdą odpowiedź, myślę, że jest to najlepsza opcja, jeśli tworzysz interfejs API.
asmmahmud
Nie powoduje to usunięcia pól z populacji.
JulianSoto,
1
Najlepsza opcja dla mnie, takie podejście nie koliduje z moją metodą uwierzytelniania
Mathiasfc
To najlepszy sposób, jeśli używasz interfejsu API. Nigdy nie musisz się martwić, że zapomnisz usunąć pole :)
Sam Munroe
4

Używam do ukrywania pola hasła w mojej odpowiedzi 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
źródło
3

Zakładając, że Twoje hasło to „hasło”, możesz po prostu:

.exclude('password')

Jest bardziej rozległe przykład tutaj

Skupia się na komentarzach, ale ta sama zasada obowiązuje w grze.

Jest to to samo, co użycie projekcji w kwerendzie w MongoDB i przekazanie {"password" : 0}w polu projekcji. Zobacz tutaj

Adam Comerford
źródło
Świetny. Dzięki. Podoba mi się to podejście.
Luis Elizondo
2

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

Ricky
źródło
2

Rozwiązaniem jest, aby nigdy nie przechowywać haseł w postaci zwykłego tekstu. Powinieneś użyć pakietu takiego jak bcrypt lub password-hash .

Przykładowe użycie haszowania hasła:

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

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

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

Przykładowe użycie do weryfikacji hasł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
źródło
8
Niezależnie od tego, czy hasło jest zaszyfrowane, czy nie, hasło / skrót nigdy nie powinny być pokazywane użytkownikowi. Atakujący może uzyskać ważne informacje o zaszyfrowanym haśle (który algorytm został użyty) i tak dalej.
Marco
2

Obiekt DocumentToObjectOptions można przekazać do schema.toJSON () lub schema.toObject () .

Zobacz definicję TypeScript z @ 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 ma opcję transformacji, która uruchamia niestandardową funkcję po przekonwertowaniu dokumentu na obiekt javascript. Tutaj możesz ukryć lub zmodyfikować właściwości, aby spełnić swoje potrzeby.

Więc powiedzmy, że używasz schema.toObject () i chcesz ukryć ścieżkę do hasła ze schematu użytkownika. Powinieneś skonfigurować ogólną funkcję transformacji, która będzie wykonywana po każdym wywołaniu toObject ().

UserSchema.set('toObject', {
  transform: (doc, ret, opt) => {
   delete ret.password;
   return ret;
  }
});
netishix
źródło
2

Znalazłem inny sposób, aby to zrobić, dodając pewne ustawienia do konfiguracji schematu.

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

Dodając funkcję transformacji do obiektu toJSON z nazwą pola do wykluczenia. jak w dokumentach stwierdzono :

Może być konieczne przeprowadzenie transformacji wynikowego obiektu na podstawie pewnych kryteriów, na przykład usunięcie niektórych poufnych informacji lub zwrócenie niestandardowego obiektu. W tym przypadku ustawiamy opcjonalną transformfunkcję.

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

Podczas korzystania password: { type: String, select: false }należy pamiętać, że spowoduje to również wykluczenie hasła, gdy będziemy go potrzebować do uwierzytelnienia. Przygotuj się więc na obsługę tego, jak chcesz.

Anand Kapdi
źródło
0

Jest to bardziej konsekwencja pierwotnego pytania, ale było to pytanie, na które natknąłem się, próbując rozwiązać mój problem ...

Mianowicie, jak odesłać użytkownika z powrotem do klienta w wywołaniu zwrotnym user.save () bez pola hasła.

Przykład zastosowania: użytkownik aplikacji aktualizuje swoje dane profilowe / ustawienia z klienta (hasło, dane kontaktowe, cokolwiek). Chcesz wysłać zaktualizowane informacje o użytkowniku z powrotem do klienta w odpowiedzi, po pomyślnym zapisaniu ich w 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
źródło
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
źródło