コレクションにデータを入力したときにクエリで返されないように、Mongoose / MongoDBのパスワードフィールドを保護するにはどうすればよいですか?


83

2つのコレクション/スキーマがあるとします。1つは、ユーザー名とパスワードのフィールドを持つユーザースキーマです。次に、作成者フィールドにユーザースキーマへの参照があるブログスキーマがあります。マングースを使って次のようなことをした場合

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

ブログドキュメントとユーザーも入力しますが、Mongoose / MongoDBがパスワードフィールドを返さないようにするにはどうすればよいですか?パスワードフィールドはハッシュ化されていますが、返されるべきではありません。

パスワードフィールドを省略して、簡単なクエリで残りのフィールドを返すことができることはわかっていますが、populateを使用してそれを行うにはどうすればよいですか。また、これを行うためのエレガントな方法はありますか?

また、ユーザーがログインしたりパスワードを変更したりする場合など、状況によってはパスワードフィールドを取得する必要があります。


.populate( 'user':1、 'password':0)
Sudhanshu Gaur 2015

回答:


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

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

スキーマオプションを使用したJohnnyHKの回答は、おそらくここに行く方法です。

またquery.exclude()、2.xブランチにのみ存在することに注意してください。


これも機能します.populate( 'user':1、 'password':0)
Sudhanshu Gaur 2015

「-」の追加が機能する理由がわかりませんでした。興味があったので、ドキュメントで適切な説明を見つけました。文字列構文を使用する場合、パスの前に-を付けると、そのパスに除外のフラグが付けられます。パスに-プレフィックスがない場合は、含まれます。最後に、パスの前に+が付いている場合は、パスを強制的に含めます。これは、スキーマレベルで除外されたパスに役立ちます。ここでは完全なもののためのリンクですmongoosejs.com/docs/api.html#query_Query-selectは
コナー

297

selectフィールドの属性を使用して、スキーマ定義レベルでデフォルトの動作を変更できます。

password: { type: String, select: false }

次に、必要に応じてfindそれをpopulate取り込み、フィールド選択を介してを呼び出すことができます'+password'。例えば:

Users.findOne({_id: id}).select('+password').exec(...);

3
すごい。findに追加する人の例を教えてください。私が持っていると仮定すると:Users.find({id:_id})どこに「+ password +?」を追加する必要がありますか?
ルイスエリゾンド2012

あなたが提供したリンクで例を見つけました。mongoosejs.com/docs/api.html#schematype_SchemaType-selectありがとう
ルイスエリゾンド2012

8
save()コールバックに渡されるオブジェクトにこれを適用する方法はありますか?ユーザープロファイルを保存するときに、パスワードがコールバックパラメーターに含まれないようにします。
マット

2
これは私の意見では断然最良の答えです。これを一度追加すれば除外されます。すべてのクエリに選択または除外オプションを追加するよりもはるかに優れています。
AndyH 2016

これが究極の答えになるはずです。スキーマに追加され、クエリ中に除外することを忘れる必要はありません。
KhoPhi

22

編集:

両方のアプローチを試した後、パスポートローカル戦略を使用した何らかの理由で、常に除外するアプローチが機能していないことがわかりました。理由はよくわかりません。

だから、これは私が使用することになったものです:

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

常に除外するアプローチには何の問題もありません。何らかの理由でパスポートでは機能しませんでした。私のテストでは、実際にはパスワードが必要なときに除外/含まれていることがわかりました。include alwaysアプローチの唯一の問題は、基本的に、データベースに対して行うすべての呼び出しを実行し、パスワードを除外する必要があることです。これは大変な作業です。


いくつかの素晴らしい答えの後で、これを行うには2つの方法があることがわかりました。「常に含めると除外する」と、「常に除外して含める」です。

両方の例:

常に含めるが、時々除外する例:

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

または

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

常に説明しますが、例を含めることもあります。

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

ただし、スキーマで定義する必要があります。

password: { type: String, select: false }

私は最後の選択肢に行きます。あなたが本当にそれで必要なログイン/ passwordUpdate機能を除き、パスワードを選択することはありません。
rdrey

何らかの理由で、そのオプションがPassport.jsローカル戦略で機能しませんでした。理由はわかりません。
ルイスエリゾンド2012

良い答え、ありがとう!!! 理由はわかりませんが、私が行う.select("+field")と、必要なフィールド__id.select("-field")適切に除外されていても、のみが表示されます
Renato Gama

申し訳ありませんが、それは完璧に動作します、それselect: falseが必須であることに気づいていませんでした
Renato Gama

1
これは私のローカル戦略で機能しています:await User.findOne({email:username}、{password:1}、async(err、user)=> {...});
TomoMiha

10

User.find().select('-password')正解です。select: falseログインしたい場合、スキーマは機能しないため、スキーマを追加することはできません。


ログインエンドポイントの動作をオーバーライドできませんか?もしそうなら、これは最も安全なオプションのようです。
2018

@erfling、そうではありません。
jrran 9019

1
それは、const query = model.findOne({ username }).select("+password");ログインとパスワードの変更/リセットルートでそれを使用して使用することができ、そうでなければそれが決して出てこないことを保証します。これは、人々がimoを間違えることが証明されているため、デフォルトで戻らないほうがはるかに安全です
Cacoon

9

これは、スキーマを使用して実現できます。次に例を示します。

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

3
私はすべての答えをテストしました。APIを開発している場合、これが最良のオプションだと思います。
asmmahmud 2018

これは、母集団のフィールドを削除しません。
JulianSoto

1
私のための最良のオプションは、このアプローチは、私の認証方法ではない紛争をdoest
Mathiasfc

4

RESTJSON応答でパスワードフィールドを非表示にするために使用しています

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

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

3

パスワードフィールドが「パスワード」であると仮定すると、次のことができます。

.exclude('password')

ここにもっと広範な例があります

それはコメントに焦点を合わせていますが、それは同じ原則です。

これは、MongoDBのクエリでプロジェクションを使用{"password" : 0}し、プロジェクションフィールドに渡すのと同じです。こちらをご覧ください


すごい。ありがとう。私はこのアプローチが好きです。
ルイスエリゾンド2012


2

解決策は、プレーンテキストのパスワードを決して保存しないことです。bcryptpassword-hashなどのパッケージを使用する必要があります

パスワードをハッシュするための使用例:

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

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

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

パスワードを確認するための使用例:

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

7
パスワードがハッシュされているかどうかに関係なく、パスワード/ハッシュは決してユーザーに表示されるべきではありません。攻撃者は、ハッシュされたパスワード(どのアルゴリズムが使用されたか)などの重要な情報を入手できます。
マルコ

2

DocumentToObjectOptionsオブジェクトをschema.toJSON()またはschema.toObject()に渡すことができます

@ types / mongooseのTypeScript定義を参照してください

 /**
 * 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には、ドキュメントをjavascriptオブジェクトに変換した後にカスタム関数を実行する変換オプションがあります。ここでは、ニーズを満たすためにプロパティを非表示または変更できます。

したがって、schema.toObject()を使用していて、ユーザースキーマからパスワードパスを非表示にしたいとします。すべてのtoObject()呼び出しの後に実行される一般的な変換関数を構成する必要があります。

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

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

1

使用中はpassword: { type: String, select: false }、認証に必要なときにパスワードも除外されることに注意してください。だからあなたが望むようにそれを扱う準備をしてください。


1

スキーマ構成にいくつかの設定を追加することで、これを行う別の方法を見つけました。

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

除外するフィールド名を持つtoJSONオブジェクトに変換関数を追加する。以下のように述べたドキュメント

機密情報を削除したり、カスタムオブジェクトを返したりするなど、いくつかの基準に基づいて結果のオブジェクトの変換を実行する必要がある場合があります。この場合、オプションのtransform機能を設定します。


0

これは元の質問の当然の結果ですが、これは私の問題を解決しようとして出くわした質問でした...

つまり、パスワードフィールドなしでuser.save()コールバックでユーザーをクライアントに送り返す方法。

ユースケース:アプリケーションユーザーは、クライアントからプロファイル情報/設定(パスワード、連絡先情報、whatevs)を更新します。更新されたユーザー情報が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);
    });
});

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.