joegoldbeck / mongoose-encryption

Simple encryption and authentication plugin for Mongoose
MIT License
215 stars 51 forks source link

Authentication failed on aggregation with subdocuments #92

Open philippewinter opened 4 years ago

philippewinter commented 4 years ago

Hi,

I'm trying to use aggregate on initial access to a document, I have encrypted the subdocument. Error: UnhandledPromiseRejectionWarning: Error: Authentication failed: Only some authenticated fields were selected by the query. Either all or none of the authenticated fields (quotes,_ct,_ac) should be selected for proper authentication.

My Code:

const companySchema = new mongoose.Schema({
    name: String,
    street: String,
    city: String,
    state: String,
    country: String,
    zip: String,
    base: String,
    taxID: String,
    phone: String,
    logo: String,
    csvImport: [{
        effDate: Date,
        prices: [{
            effDate: Date,
            expDate: Date,
            icao: String,
            obs: String,
            galFrom: Number,
            galTo: Number,
            price: Number,
        }]
    }],
    acfts: [acftSchema],
    uplifts: [upliftSchema],
    autoRelease: {
        type: Boolean,
        default: false
    },
    quotes: [quoteSchema],
    users: [userSchema],
    providers: [{
        type: mongoose.Schema.Types.ObjectId,
        ref: "fuelProvider"
    }],
    cards: [cardSchema],
    customerNumber: [customerNumberSchema],
    settings: [companySettingsSchema],
    active: {
        type: Boolean,
        default: true
    },
    stripe: {
        id: String,
        email: String
    },
    disabledProviders: [mongoose.Schema.Types.ObjectId]
}, { timestamps: { createdAt: 'created_at' } });

companySchema.plugin(encrypt, {
    secret: process.env.encKey,
    additionalAuthenticatedFields: ['quotes'],
    encryptedFields: []
});

--------------------------------

var QuoteSchema = new mongoose.Schema({
    registrationMark: {
        type: String,
        uppercase: true
    },
    flightDate: Date,
    origin: {
        type: String,
        uppercase: true
    },
    arrivalDate: Date,
    destination: {
        type: String,
        uppercase: true
    },
    fbo: String,
    fuelQuantity: String,
    bids: [bidSchema],
    crew: [userSchema],
    sent: {
        type: Number,
        default: 1
    },
    nextLeg: mongoose.Schema.Types.ObjectId,
    tanker: {
        type: Boolean,
        default: false
    }
}, { timestamps: { createdAt: 'created_at' } });

QuoteSchema.plugin(encrypt, {
    secret: process.env.encKey,
    encryptedFields: ['fuelQuantity'],
});

--------------------------------

let company = await Company.aggregate([{ $match: { _id: req.user.parent()._id } }, { $sort: { 'quote.flightDate': 1 } }, {
            $project: {
                quotes: {
                    $filter: {
                        input: "$quotes",
                        as: "quote",
                        cond: { $and: [{ $gt: ["$$quote.flightDate", new Date(moment().startOf('day'))] }, { $lt: ["$$quote.flightDate", new Date(moment().add(30, 'days'))] }, { $in: ['$$quote.registrationMark', acftTail] }] }
                    }
                }
            }
        }]);
karenpommeroy commented 3 years ago

Same here

import { Document, model, Schema } from "mongoose";
import encrypt from "mongoose-encryption";
import mongooseHidden from "mongoose-hidden";
`
export interface ISettings {
    userId: string;
    mapbox: {
        user: string;
        publicToken: string;
        privateToken: string;
    };
}

export type SettingsModel = ISettings & Document;

export const SettingsSchema = new Schema(
    {
        userId: { type: Schema.Types.ObjectId, ref: "User", required: true },
        mapbox: {
            user: String,
            publicToken: String,
            privateToken: String,
        },
    },
    {
        timestamps: true,
    },
);

SettingsSchema.set("toJSON", {
    virtuals: true,
});

SettingsSchema.plugin(encrypt, {
    secret: "dupa",
    encryptedFields: ["mapbox.privateToken"],
});

SettingsSchema.plugin(mongooseHidden({ hidden: { __t: true } }));

export const Settings = model<SettingsModel>("Settings", SettingsSchema);

Then simple mongoose.find gives me "Authentication failed".

I trakced that function authenticateSync gives me error because

var authentic = bufferEqual(basicAC, expectedHMAC);     // Here bufferEqual returns false
if (!authentic){
    throw new Error('Authentication failed');
}
joegoldbeck commented 3 years ago

@karenpommeroy I wonder if this is related to the mongooseHidden plugin you added. If you remove that, or perhaps simply reverse the order in which you add the plugins, does that solve the issue?

joegoldbeck commented 3 years ago

For the original issue cited, aggregations in general aren't a tested aspect of this package. In general, I'd expect they might work if the fields involved aren't encrypted. However, in this particular case, the entire quotes subdocument is marked to be authenticated, but then only some of its fields are returned in the aggregation, which means authentication cannot be performed. Hence, the error returned!