mongoose-field-encryption icon indicating copy to clipboard operation
mongoose-field-encryption copied to clipboard

nested field encryption support

Open caltuntas opened this issue 5 years ago • 15 comments

Hello,

Firstly I want to thank you for this nice library. At the moment, library supports only outer level field encryption like in the PostSchema example you added. It can only encrypt "references" field as whole. But when only one of the fields inside "references" should be encrypted, it doesn't work.

So It would be nice to support nested field encryption with "dot notation" used in mongoDB itself.

Query on Nested Field

For example , I should be able to define nested field as below

PostSchema.plugin(mongooseFieldEncyption, { fields: ["message", "references.author"], secret: "some secret key" });

and it should only encrypt "references.author" not whole "references" field

caltuntas avatar Dec 11 '19 17:12 caltuntas

After having a look at the closed issues I realized there was a similar issue https://github.com/wheresvic/mongoose-field-encryption/issues/1 (and merge request at the end) closed due to inactivity. It would be useful to add this feature.

caltuntas avatar Dec 11 '19 18:12 caltuntas

@caltuntas I'll have a look at this again. The merge request you reference actually incorrectly mentioned the issue in question.

I cannot guarantee how fast I'll be able to develop this but let's say after the holidays would be a fair bet :)

wheresvic avatar Dec 13 '19 10:12 wheresvic

Fair enough :) If I can find some time before you I may add this and make a pull request. Thanks

caltuntas avatar Dec 13 '19 19:12 caltuntas

@caltuntas I had a look at this issue again and while implementing it would be possible, it would vastly complicate the code (and potentially break the current encrypted field naming convention).

I personally think that if you require nested field encryption, you could consider having sub-documents and apply the plugin on them and let mongoose itself handle the magic.

What do you think?

wheresvic avatar Jan 10 '20 10:01 wheresvic

@wheresvic's suggestion on using subdocuments works perfectly. For those of you looking for a simple code example, consider the following.

const mongoose = require('mongoose');
const mongooseFieldEncryption = require("mongoose-field-encryption").fieldEncryption;

const CredentialSchema = new mongoose.Schema({
    type: {
        required: true,
        type: String,
    },
    value: {
        required: true,
        type: String,
    },
});

CredentialSchema.plugin(mongooseFieldEncryption, {
    fields: ["value"],
    secret: process.env.MONGOOSE_ENCRYPTION_KEY,
});

const accountSchema = new mongoose.Schema({
    provider: {
        type: String,
        required: true,
        lowercase: true,
        trim: true,
    },
    credentials: [CredentialSchema],
    owner: {
        required: true,
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
    },
});

module.exports = mongoose.model('Account', accountSchema);

That's a stripped down version of what I'm using to store OAuth credentials for multiple service providers for multiple users on an open source project I'm currently working on.

Hopefully this code helps someone.

rickmacgillis avatar Jan 22 '20 21:01 rickmacgillis

  • 1 for this request. @rickmacgillis suggestion works, but I would be nice if implemented

mexusbg avatar Jul 09 '20 14:07 mexusbg

const mongoose = require("mongoose");
const schema = new mongoose.Schema({
    fieldA: { // 字段A
        type: String
    },
    fieldBArrays: [new mongoose.Schema({ // 字段B嵌套文档
        child: { // 子字段
            type: String
        }
    })]
});
const mongooseFieldEncryption = require('mongoose-field-encryption').fieldEncryption;
schema.plugin(mongooseFieldEncryption, {
    fields: ["fieldA","fieldBArrays.$.child"],
    secret: "秘钥",
    saltGenerator: function (secret) {
        return "1234567890123456"; //理想情况下,应使用该机密返回长度为16的字符串
    }
});

I did this

774649283 avatar Jul 10 '20 08:07 774649283

I experienced some issue with a similar structure like "Account schema has an array of Credential Schema, in which a field is marked as encrypted". After I use find() findOne() to get account(s), update some other fields but not the credentials, I will get and error of

"Cannot create field '-1' in element in { credentials: [...] }"

when I call account.save()

It seems like only happens to Array type, and I found using `account.markModified('credentials') solves this problem.

I'm not sure if this behaviour is mentioned anywhere but since it is kind of related to the implementation described in this issue, I though I'd just post it here.

Yzhibin avatar May 20 '21 04:05 Yzhibin

I tried following the suggestion proposed by @rickmacgillis but it's not working on my side.

I have an UserSchema where I need to encrypt the "name" and "surname" fields. Then this schema contains a field "extra" which is based on another schema (as it is a nested object). As suggested above, I have created the UserExtraSchema to allow nested fields encryption.

Unfortunately, I'm having some issues with this scenario. Therefore, the fields specified in the UserSchema are successfully encrypted, while the ones in the UserExtraSchema are not.

Below a snippet showing what I'm trying to do.

const UserExtraSchema: Schema = new Schema({
        city: {type: String},
        country: {type: String},
        address: {type: String},
        postalCode: {type: String}
});        

UserExtraSchema.plugin(mongooseFieldEncryption.fieldEncryption, {
    fields: ['address'],
    secret: process.env.MY_SECRET,
    saltGenerator(secret): string {
        return bcrypt.hashSync(secret, 10).substring(0, 16);
    }
});

const UserSchema: Schema = new Schema({
        _id: {type: String, required: true},
        name: {type: String, required: true},
        surname: {type: String, required: true},
        email: {type: String, required: true},
        extra: UserExtraSchema,
    }, {collection: 'users'}
);

UserSchema.plugin(mongooseFieldEncryption.fieldEncryption, {
    fields: ['name', 'surname],
    secret: process.env.MY_SECRET,
    saltGenerator(secret): string {
        return bcrypt.hashSync(secret, 10).substring(0, 16);
    }
});

limone-eth avatar May 27 '21 10:05 limone-eth

Added test: https://github.com/wheresvic/mongoose-field-encryption/blob/036917d580e0d43c28f3331d0025af439d6ee18c/test/test-db.js#L212

wheresvic avatar Jun 07 '21 10:06 wheresvic

I tried following the suggestion proposed by @rickmacgillis but it's not working on my side.

I have an UserSchema where I need to encrypt the "name" and "surname" fields. Then this schema contains a field "extra" which is based on another schema (as it is a nested object). As suggested above, I have created the UserExtraSchema to allow nested fields encryption.

Unfortunately, I'm having some issues with this scenario. Therefore, the fields specified in the UserSchema are successfully encrypted, while the ones in the UserExtraSchema are not.

Below a snippet showing what I'm trying to do.

const UserExtraSchema: Schema = new Schema({
        city: {type: String},
        country: {type: String},
        address: {type: String},
        postalCode: {type: String}
});        

UserExtraSchema.plugin(mongooseFieldEncryption.fieldEncryption, {
    fields: ['address'],
    secret: process.env.MY_SECRET,
    saltGenerator(secret): string {
        return bcrypt.hashSync(secret, 10).substring(0, 16);
    }
});

const UserSchema: Schema = new Schema({
        _id: {type: String, required: true},
        name: {type: String, required: true},
        surname: {type: String, required: true},
        email: {type: String, required: true},
        extra: UserExtraSchema,
    }, {collection: 'users'}
);

UserSchema.plugin(mongooseFieldEncryption.fieldEncryption, {
    fields: ['name', 'surname],
    secret: process.env.MY_SECRET,
    saltGenerator(secret): string {
        return bcrypt.hashSync(secret, 10).substring(0, 16);
    }
});

I have the same issue

mexusbg avatar Feb 11 '22 17:02 mexusbg

@mexusbg there is a test that uses exactly this example and it is working fine: https://github.com/wheresvic/mongoose-field-encryption/blob/036917d580e0d43c28f3331d0025af439d6ee18c/test/test-db.js#L212

the only difference I see from the example code is the way the saltGenerator function is setup (although I do not see anything wrong with the one that you are using). Maybe try to change the function to that provided in the test and see if it works?

The other point is that I do not see how you save and retrieve the document. Are you saving the main user document?

wheresvic avatar Feb 16 '22 10:02 wheresvic

@wheresvic I'm saving the main user document. User.create({}) User.findOneAndUpdate(findQuery,updateQuery)

mexusbg avatar Feb 16 '22 11:02 mexusbg

I have the same problem with subschema encryption when I use findOneAndUpdate() on the parent record, but when I switched to save(), it works.

ctranstrum avatar Sep 30 '22 17:09 ctranstrum

I'm encountering the same issue on Update/UpdateOne. Seems that mongoose only runs the hook on the top level schema.

I see that it's all over our project. I'll try to investigate. I think it's related to the way mongoose works

adidaslevy avatar May 30 '23 16:05 adidaslevy