wheresvic / mongoose-field-encryption

A simple symmetric encryption plugin for individual fields. Dependency free, only mongoose peer dependency.
MIT License
74 stars 32 forks source link

nested field encryption support #34

Open caltuntas opened 4 years ago

caltuntas commented 4 years ago

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 commented 4 years ago

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.

wheresvic commented 4 years ago

@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 :)

caltuntas commented 4 years ago

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

wheresvic commented 4 years ago

@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?

rickmacgillis commented 4 years ago

@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.

mexusbg commented 4 years ago
774649283 commented 4 years ago
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

Yzhibin commented 3 years ago

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.

limone-eth commented 3 years ago

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);
    }
});
wheresvic commented 3 years ago

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

mexusbg commented 2 years ago

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

wheresvic commented 2 years ago

@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?

mexusbg commented 2 years ago

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

ctranstrum commented 2 years ago

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

adidaslevy commented 1 year ago

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