Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nested field encryption support #34

Open
caltuntas opened this issue Dec 11, 2019 · 15 comments
Open

nested field encryption support #34

caltuntas opened this issue Dec 11, 2019 · 15 comments

Comments

@caltuntas
Copy link

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
Copy link
Author

After having a look at the closed issues I realized there was a similar issue #1 (and merge request at the end) closed due to inactivity. It would be useful to add this feature.

@wheresvic
Copy link
Owner

@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 wheresvic self-assigned this Dec 13, 2019
@caltuntas
Copy link
Author

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

@wheresvic
Copy link
Owner

@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
Copy link

@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
Copy link

mexusbg commented Jul 9, 2020

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

@774649283
Copy link

774649283 commented Jul 10, 2020

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
Copy link

Yzhibin commented May 20, 2021

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
Copy link

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
Copy link
Owner

Added test:

describe("subdocument encryption", function () {

@mexusbg
Copy link

mexusbg commented Feb 11, 2022

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
Copy link
Owner

wheresvic commented Feb 16, 2022

@mexusbg there is a test that uses exactly this example and it is working fine:

describe("subdocument encryption", function () {

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
Copy link

mexusbg commented Feb 16, 2022

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

@ctranstrum
Copy link

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
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants