Skip to content

Commit

Permalink
v1.0.0, removes subdomain specific code, improves tenantify querying …
Browse files Browse the repository at this point in the history
…logic
  • Loading branch information
flipace committed Apr 6, 2016
1 parent 2a9316b commit e0cc8cc
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 165 deletions.
23 changes: 8 additions & 15 deletions README.md
Expand Up @@ -2,10 +2,14 @@
tenantify
=========

tenantify is a meteor package which provides support
for (at least) subdomain multi-tenancy within one mongo database.
tenantify is a meteor package which allows you to easily
limit collection queries based on a given key-value pair in each document.

*i would like to extend its functionality to support multi-db setup too*
This allows you to easily setup multi-tenancy.

***WARNING: v1.0.0 removed all subdomain/header specific code so tenantify is now
only a query limiter package. If you want to have subdomain multi-tenancy, you'll have to
implement the functionality for handling different subdomains on your own now.***

Requirements
-------------
Expand All @@ -14,17 +18,6 @@ tenantify is only tested with nginx.

Configuration
-------------

Setup a nginx server to proxy_pass to your meteor application.

You can do this like that:

proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;

tenantify uses a very simple and straightforward way
to define collections which should be "tenantified".

Expand All @@ -34,7 +27,7 @@ First, tell tenantify in which collection you save your tenant ids. This is stra

`Teams` is the collection in which I have all my tenants. The `_id` field should be used as the tenant id.

When using the **subdomain-based** tenant system, you'll need to have a field with the key 'subdomain' which contains the `String` value of the subdomain for this tenant.
When using the **subdomain-based** tenant system, you'll want to have a field with the key 'subdomain' which contains the `String` value of the subdomain for this tenant.

For example:

Expand Down
3 changes: 0 additions & 3 deletions client.js

This file was deleted.

5 changes: 1 addition & 4 deletions package.js
@@ -1,6 +1,6 @@
Package.describe({
name: 'flipace:tenantify',
version: '0.1.1',
version: '1.0.0',
summary: 'easily setup multi tenancy for your meteor app',
git: 'https://github.com/flipace/meteor-tenantify',
documentation: 'README.md'
Expand All @@ -11,12 +11,9 @@ Package.onUse(function(api) {

api.use([
'underscore',
'gadicohen:headers',
'matb33:collection-hooks'
]);

api.addFiles('server.js', 'server');
api.addFiles('client.js', 'client');
api.addFiles('tenantify.js');

api.export('Tenantify');
Expand Down
18 changes: 0 additions & 18 deletions server.js

This file was deleted.

37 changes: 0 additions & 37 deletions tenantify-tests.js
@@ -1,5 +1,3 @@
// Write your tests here!
// Here is an example.
Teams = new Meteor.Collection('teams');
Projects = new Meteor.Collection('projects');

Expand Down Expand Up @@ -32,38 +30,3 @@ Tinytest.add('Tenantifies tenant collection should be the same as you provide as
Tenantify.setTenantCollection(Teams, '_id');
test.equal(Teams, Tenantify._tenantCollection);
});

if(Meteor.isServer) {
Tinytest.addAsync('Tenantifies headers should be the connection headers on the server.', function (test, onComplete) {
var connectionMock = {
httpHeaders: {
host: 'test.tenantify.io'
}
};

Meteor.call('flipace:tenantify/setHeaders', connectionMock, function(err, res) {
test.equal(res.host, connectionMock.httpHeaders.host);

onComplete();
});
});
}

Tinytest.addAsync('Only current tenant data should be found in tenantified collections.', function(test, onComplete) {
Tenantify.setTenantCollection(Teams, '_id');
Tenantify.collection(Projects, { tenantField: 'teamId' });

var connectionMock = {
httpHeaders: {
host: 'test.tenantify.io'
}
};

Meteor.call('flipace:tenantify/setHeaders', connectionMock, function(err, res) {
var testTeamProjects = Projects.find({}).fetch();

test.length(testTeamProjects, 1, 'There must be 1 project available for the team on subdomain "test.*".');

onComplete();
});
});
152 changes: 64 additions & 88 deletions tenantify.js
@@ -1,106 +1,82 @@
// constants
TENANTIFY_TYPE_SUBDOMAIN = 'subdomain';

// tenantify global
Tenantify = {};

Tenantify._headers = false;
Tenantify._tenantCollection = false;
Tenantify._tenantField = '_tenantId'; // the tenant field name in the tenant collection

Tenantify.tenantIdentifyMethod = TENANTIFY_TYPE_SUBDOMAIN;

Tenantify._currentTenantId = false;
Tenantify._tenantIdCache = {};

// functions
Tenantify.setTenantCollection = function(collection, field) {
this._tenantCollection = collection;
this._tenantField = field;
}

Tenantify.collection = function(collection, options) {
var defaults = {
tenantField: Tenantify._tenantField,
denyForNonTenant: true
};

var tenantifyOptions = options;

tenantifyOptions = _.extend(defaults,tenantifyOptions);
this._tenantCollection = collection;

collection.before.find(function(userId, selector, options) {
selector = selector || {};
options = options || {};

var tenantId = Tenantify._currentTenantId || Tenantify.getTenantId();

if(tenantId) {
selector[tenantifyOptions.tenantField] = tenantId;
} else if(tenantifyOptions.denyForNonTenant) {
selector[tenantifyOptions.tenantField] = 'disallowed';
}
});

collection.before.insert(function(userId, doc) {
var tenantId = Tenantify._currentTenantId || Tenantify.getTenantId();

if(tenantId) {
doc[tenantifyOptions.tenantField] = tenantId;
} else if(tenantifyOptions.denyForNonTenant) {
doc[tenantifyOptions.tenantField] = 'disallowed';
}
});
if (field) {
this._tenantField = field;
}
}

Tenantify.getTenantId = function(method) {
// if a tenantId is already set, return it
if(Tenantify._currentTenantId)
return Tenantify._currentTenantId;

// get the tenant id by using one of the given methods
switch(this.tenantIdentifyMethod) {
case TENANTIFY_TYPE_SUBDOMAIN:
if(this._headers) {
var host = splitHostname(this._headers.host);

if(typeof(host.subdomain) !== undefined && host.subdomain != '') {
// it's a subdomain. let's find the tenant id
var tenantData = Tenantify._tenantCollection.findOne({ subdomain: host.subdomain });

if(tenantData) {
Tenantify._currentTenantId = tenantData._id;
return tenantData._id;
}
}
}
break;
default:
return false;
break;
}

return false;
Tenantify.collection = function(collection, options) {
var defaults = {
tenantField: Tenantify._tenantField,
denyForNonTenant: true
};

var tenantifyOptions = options;

tenantifyOptions = _.extend(defaults,tenantifyOptions);

var indexObject = {};
indexObject[tenantifyOptions.tenantField] = 1;
collection._ensureIndex(indexObject);

collection.before.find(function(userId, selector, options) {
selector = selector || {};
options = options || {};

if (selector._ignoreTenantify) {
delete selector._ignoreTenantify;
return;
}

var tenantId = Tenantify.getTenantId(userId);

if(tenantId) {
selector[tenantifyOptions.tenantField] = tenantId;
} else if(tenantifyOptions.denyForNonTenant) {
selector[tenantifyOptions.tenantField] = 'disallowed';
}
});

collection.before.insert(function(userId, doc) {
var tenantId = Tenantify.getTenantId(userId);

if(tenantId) {
doc[tenantifyOptions.tenantField] = tenantId;
} else if(tenantifyOptions.denyForNonTenant) {
doc[tenantifyOptions.tenantField] = 'disallowed';
}
});

collection.before.update(function(userId, doc) {
var tenantId = Tenantify.getTenantId(userId);

if(tenantId) {
// do
} else if(tenantifyOptions.denyForNonTenant) {
modifier = {}
}
});
}

/**
* Common functions
*/

function splitHostname(hostname) {
var result = {};
var regexParse = new RegExp('([a-z\-0-9]{2,63})\.([a-z\.]{2,5})$');
var urlParts = regexParse.exec(hostname);

if(urlParts) {
result.domain = urlParts[1];
result.type = urlParts[2];
result.subdomain = hostname.replace(result.domain + '.' + result.type, '').slice(0, -1);
Tenantify.getTenantId = function(userId) {
// if a tenantId is already set, return it
if(Tenantify._tenantIdCache[userId])
return Tenantify._tenantIdCache[userId];

if(result.subdomain.indexOf('.') > -1) {
result.subdomain = result.subdomain.split('.')[0];
}
}
var userData = Meteor.users.findOne(userId);
var tenantId = userData.profile[Tenantify._tenantField];

Tenantify._tenantIdCache[userId] = tenantId;

return result;
return tenantId;
}

0 comments on commit e0cc8cc

Please sign in to comment.