Skip to content

Commit

Permalink
Fix infinite loop
Browse files Browse the repository at this point in the history
An infinite loop is caused by a loop reference when dump deep merge of schemas
happens in case of 2 tenant aware schemas used in a discriminator setup.
  • Loading branch information
baranga committed Mar 20, 2019
1 parent e1b10ca commit 35699ef
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 14 deletions.
35 changes: 21 additions & 14 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

'use strict';

// Private storage of plugin instances
const ns = new WeakMap();

/**
* MongoTenant is a class aimed for use in mongoose schema plugin scope.
* It adds support for multi-tenancy on document level (adding a tenant reference field and include this in unique indexes).
Expand All @@ -16,7 +19,6 @@ class MongoTenant {
/**
* Create a new mongo tenant from a given schema.
*
* @param {mongoose.Schema} schema
* @param {Object} [options] - A hash of configuration options.
* @param {boolean} [options.enabled] - Whether the mongo tenant plugin is enabled. Default: **true**.
* @param {string} [options.tenantIdKey] - The name of the tenant id field. Default: **tenantId**.
Expand All @@ -27,8 +29,13 @@ class MongoTenant {
*/
constructor(schema, options) {
this._modelCache = {};
this.schema = schema;
this.options = options || {};

// store schema in privately so we don't produce any infinite loops on dump deep merge of schemas
// (schema.plugins[] -> mongoTenant.schema -> ...)
ns[this] = {
schema
};
}

/**
Expand Down Expand Up @@ -131,7 +138,7 @@ class MongoTenant {
}
};

this.schema.add(tenantField);
ns[this].schema.add(tenantField);
}

return this;
Expand All @@ -146,7 +153,7 @@ class MongoTenant {
compoundIndexes() {
if (this.isEnabled()) {
// apply tenancy awareness to schema level unique indexes
this.schema._indexes.forEach((index) => {
ns[this].schema._indexes.forEach((index) => {
// extend uniqueness of indexes by tenant id field
// skip if perserveUniqueKey of the index is set to true
if (index[1].unique === true && index[1].preserveUniqueKey !== true) {
Expand All @@ -163,7 +170,7 @@ class MongoTenant {
});

// apply tenancy awareness to field level unique indexes
this.schema.eachPath((key, path) => {
ns[this].schema.eachPath((key, path) => {
let pathOptions = path.options;

// skip if perserveUniqueKey of an unique field is set to true
Expand All @@ -188,7 +195,7 @@ class MongoTenant {
}

// create a new one that includes the tenant id field
this.schema.index({
ns[this].schema.index({
[this.getTenantIdKey()]: 1,
[key]: 1
}, indexOptions);
Expand All @@ -208,7 +215,7 @@ class MongoTenant {
injectApi() {
let me = this;

Object.assign(this.schema.statics, {
Object.assign(ns[this].schema.statics, {
[this.getAccessorMethod()]: function (tenantId) {
if (!me.isEnabled()) {
return this.model(this.modelName);
Expand Down Expand Up @@ -403,55 +410,55 @@ class MongoTenant {
tenantIdGetter = this.getTenantIdGetter(),
tenantIdKey = this.getTenantIdKey();

this.schema.pre('count', function(next) {
ns[this].schema.pre('count', function(next) {
if (this.model.hasTenantContext) {
this._conditions[tenantIdKey] = this.model[tenantIdGetter]();
}

next();
});

this.schema.pre('find', function(next) {
ns[this].schema.pre('find', function(next) {
if (this.model.hasTenantContext) {
this._conditions[tenantIdKey] = this.model[tenantIdGetter]();
}

next();
});

this.schema.pre('findOne', function(next) {
ns[this].schema.pre('findOne', function(next) {
if (this.model.hasTenantContext) {
this._conditions[tenantIdKey] = this.model[tenantIdGetter]();
}

next();
});

this.schema.pre('findOneAndRemove', function(next) {
ns[this].schema.pre('findOneAndRemove', function(next) {
if (this.model.hasTenantContext) {
this._conditions[tenantIdKey] = this.model[tenantIdGetter]();
}

next();
});

this.schema.pre('findOneAndUpdate', function(next) {
ns[this].schema.pre('findOneAndUpdate', function(next) {
if (this.model.hasTenantContext) {
me._guardUpdateQuery(this);
}

next();
});

this.schema.pre('save', function(next) {
ns[this].schema.pre('save', function(next) {
if (this.constructor.hasTenantContext) {
this[tenantIdKey] = this.constructor[tenantIdGetter]();
}

next();
});

this.schema.pre('update', function(next) {
ns[this].schema.pre('update', function(next) {
if (this.model.hasTenantContext) {
me._guardUpdateQuery(this);
}
Expand Down
2 changes: 2 additions & 0 deletions release-notes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ releases:
changed:
- Models with tenant context (`Model.byTenant(...)`) will have a modified db connection object
returning Models bound to same context.
- Plugin instances do not have any longer a public reference to the schema they are created for. This is necessary to
avoid inifinite loops in case two tenant aware schemas are used in a discriminator setup.
- version: 1.5.0
date: 2018-07-06
fixed:
Expand Down

0 comments on commit 35699ef

Please sign in to comment.