Skip to content

Commit

Permalink
Auto refresh of session.userDBs and user.personalDBs if the config.us…
Browse files Browse the repository at this point in the history
…erDBs.defaultDBs change.

Activated by the boolean session.checkUserDBsDiffFromDefault in the
config.
  • Loading branch information
Shocoben committed Apr 19, 2016
1 parent 3489808 commit b35b6f2
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 37 deletions.
19 changes: 18 additions & 1 deletion lib/configure.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ module.exports = function(data, defaults) {
return result;
};

var _signatures = [];

this.refreshSignature = function(key)
{
var item = this.getItem(key);
_signatures[key] = util.hashObjectOrVariable(item);
return _signatures[key];
};

this.getSignature = function(key)
{
if(!_signatures[key])
return this.refreshSignature(key);
return _signatures[key];
};


this.setItem = function(key, value) {
return util.setObjectRef(this.config, key, value);
};
Expand All @@ -22,4 +39,4 @@ module.exports = function(data, defaults) {
return util.delObjectRef(this.config, key);
};

};
};
36 changes: 6 additions & 30 deletions lib/dbauth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,10 @@ module.exports = function (config, userDB, couchAuthDB) {
adminRoles = adminRoles || [];
memberRoles = memberRoles || [];
// Create and the database and seed it if a designDoc is specified
var prefix = config.getItem('userDBs.privatePrefix') ? config.getItem('userDBs.privatePrefix') + '_' : '';
var finalDBName, newDB;
// Make sure we have a legal database name
var username = userDoc._id;
username = getLegalDBName(username);
if(type === 'shared') {
finalDBName = dbName;
} else {
finalDBName = prefix + dbName + '$' + username;
}
var prefix = config.getItem('userDBs.privatePrefix') ? config.getItem('userDBs.privatePrefix') + '_' : '';
finalDBName = util.getFinalDBName(prefix, dbName, type, userDoc);
return self.createDB(finalDBName)
.then(function() {
newDB = new PouchDB(util.getDBURL(config.getItem('dbServer')) + '/' + finalDBName);
Expand Down Expand Up @@ -98,6 +92,9 @@ module.exports = function (config, userDB, couchAuthDB) {
})
.then(function() {
return BPromise.resolve(finalDBName);
})
.catch(function(err){
throw err;
});
};

Expand Down Expand Up @@ -242,7 +239,7 @@ module.exports = function (config, userDB, couchAuthDB) {
if(err.status === 412) {
return BPromise.resolve(false);
} else {
return BPromise.reject(err.text);
return BPromise.reject(err.cause.response.text);
}
});
};
Expand All @@ -255,24 +252,3 @@ module.exports = function (config, userDB, couchAuthDB) {

return this;
};

// Escapes any characters that are illegal in a CouchDB database name using percent codes inside parenthesis
// Example: 'My.name@example.com' => 'my(2e)name(40)example(2e)com'
function getLegalDBName(input) {
input = input.toLowerCase();
var output = encodeURIComponent(input);
output = output.replace(/\./g, '%2E');
output = output.replace(/!/g, '%21');
output = output.replace(/~/g, '%7E');
output = output.replace(/\*/g, '%2A');
output = output.replace(/'/g, '%27');
output = output.replace(/\(/g, '%28');
output = output.replace(/\)/g, '%29');
output = output.replace(/\-/g, '%2D');
output = output.toLowerCase();
output = output.replace(/(%..)/g, function(esc) {
esc = esc.substr(1);
return '(' + esc + ')';
});
return output;
}
90 changes: 85 additions & 5 deletions lib/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -572,8 +572,9 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) {
if(!userAccess || !(userAccess instanceof Array))
userAccess = config.getItem('security.defaultUserAccess');

var defaultRequireUserAccess = config.getItem('userDBs.model._default.requireUserAccess');
for (var db_key in user.personalDBs) {
var requireUserAccess = config.getItem('userDBs.model.' + personalDBs[db_key].name + '.requireUserAccess') || config.getItem('userDBs.model._default.requireUserAccess');
var requireUserAccess = config.getItem('userDBs.model.' + personalDBs[db_key].name + '.requireUserAccess') || defaultRequireUserAccess;
if(requireUserAccess instanceof Array && requireUserAccess.length > 0) {
if(!userAccess || !(userAccess instanceof Array) || userAccess.length <= 0)
continue;
Expand All @@ -590,6 +591,79 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) {
return BPromise.resolve(allowedDBs);
};

this.checkPersonalDBsDiff = function(user, deletePrivate, deleteShared)
{
if(!config.getItem('session.checkUserDBsDiffFromDefault'))
return BPromise.resolve(user);

var promises = [];

var processUserDBs = function(defaultDBsList, type) {
Object.keys(user.personalDBs).forEach(function(userDBName){
var prefix = config.getItem('userDBs.privatePrefix') ? config.getItem('userDBs.privatePrefix') + '_' : '';
var defaultDBName = util.castFinalToDefaultDBName(prefix, userDBName, type, user);
var index_defaultDBName = defaultDBsList.indexOf(defaultDBName);
if(index_defaultDBName < 0)
{
//If the userDBName isn't in the defaultDBsList, it has to be removed
promises.push(
self.removeUserDB(user._id, userDBName, deletePrivate, deleteShared)
.then(function() {
delete user.personalDBs[userDBName];
}));
}
else
{
//It's already in, so remove it from the defaultDBsList, so the database won't be re-created
defaultDBsList.splice(index_defaultDBName, 1);
}
});
};

var finalPrivateDBsToAdd = null;
if (user.privateDBsSignature !== config.getSignature("userDBs.defaultDBs.private"))
{
var privateDBsToAdd = config.getItem("userDBs.defaultDBs.private");
// Just in case defaultDBs is not specified
if(Array.isArray(privateDBsToAdd) && privateDBsToAdd.length > 0) {
finalPrivateDBsToAdd = extend(true, [], privateDBsToAdd);
processUserDBs(finalPrivateDBsToAdd, 'private');
}
}

var finalSharedDBsToAdd = null;
if (user.sharedDBsSignature !== config.getSignature("userDBs.defaultDBs.shared"))
{
var sharedDBsToAdd = config.getItem("userDBs.defaultDBs.shared");
// Just in case defaultDBs is not specified
if(Array.isArray(sharedDBsToAdd) && sharedDBsToAdd.length > 0) {
finalSharedDBsToAdd = extend(true, [], sharedDBsToAdd);
processUserDBs(finalSharedDBsToAdd, 'shared');
}
}

if( (Array.isArray(finalPrivateDBsToAdd) && finalPrivateDBsToAdd.length > 0) || (Array.isArray(finalSharedDBsToAdd) && finalSharedDBsToAdd.length > 0))
{

return BPromise.all(promises)
.then(function() {
return addUserDBs(user, finalPrivateDBsToAdd, finalSharedDBsToAdd);
})
.then(function(newUser){
user = newUser;
return userDB.put(user);
})
.then(function(newUser){
return userDB.get(newUser.id);
});
}
else
{
return BPromise.resolve(user);
}
};


this.createSession = function(user_id, provider, req) {
var user;
var newToken;
Expand All @@ -601,6 +675,10 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) {
return userDB.get(user_id)
.then(function(record) {
user = record;
return self.checkPersonalDBsDiff(user, false, false); //Do not delete completely the removed DBs
})
.then(function(user_){
user = user_;
return self.getAllowedBDs(user);
})
.then(function(nAllowedDBs)
Expand Down Expand Up @@ -1282,7 +1360,8 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) {
});
}

function addUserDBs(newUser) {
//Add all the default DBs to a user, you can pass specific private and shared DBs names. Else the fuction is going to use the config's names (userDBs.defaultDBs)
function addUserDBs(newUser, privateDBs, sharedDBs) {
// Add personal DBs
if(!config.getItem('userDBs.defaultDBs')) {
return BPromise.resolve(newUser);
Expand All @@ -1308,22 +1387,23 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) {
};

// Just in case defaultDBs is not specified
var defaultPrivateDBs = config.getItem('userDBs.defaultDBs.private');
var defaultPrivateDBs = privateDBs || config.getItem('userDBs.defaultDBs.private');
if(!Array.isArray(defaultPrivateDBs)) {
defaultPrivateDBs = [];
}
processUserDBs(defaultPrivateDBs, 'private');
var defaultSharedDBs = config.getItem('userDBs.defaultDBs.shared');
var defaultSharedDBs = sharedDBs || config.getItem('userDBs.defaultDBs.shared');
if(!Array.isArray(defaultSharedDBs)) {
defaultSharedDBs = [];
}
processUserDBs(defaultSharedDBs, 'shared');

return BPromise.all(promises).then(function() {
newUser.privateDBsSignature = config.getSignature('userDBs.defaultDBs.private');
newUser.sharedDBsSignature = config.getSignature('userDBs.defaultDBs.shared');
return BPromise.resolve(newUser);
});
}

return this;

};
100 changes: 100 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var BPromise = require('bluebird');
var URLSafeBase64 = require('urlsafe-base64');
var uuid = require('node-uuid');
var pwd = require('couch-pwd');
var crypto = require('crypto');

exports.URLSafeUUID = function() {
return URLSafeBase64.encode(uuid.v4(null, new Buffer(16)));
Expand Down Expand Up @@ -251,3 +252,102 @@ exports.arrayUnion = function (a, b) {
}
return result;
};


/**
* Hash object to md5
*
* @param {array} an array / object
* @param {string} crypto's hash method (md5, sha1, ...)
* @return {string} resulting hash
*/
function hashObject(object, hashMethod) {
var cHashMethod = hashMethod || "md5";
var hash = crypto.createHash(cHashMethod)
.update(JSON.stringify(object, function (k, v) {
if (k[0] === "_") return undefined; // remove api stuff
else if (typeof v === "function") // consider functions
return v.toString();
else return v;
}))
.digest('hex');
return hash;
}

exports.hashObject = hashObject;

/**
* Automaticaly detect if it has to hash an object or a variable (string, int, ...) to md5
*
* @param {all} the item to hash (Array, Object, Int, String)
* @param {string} crypto's hash method (md5, sha1, ...)
* @return {string} resulting hash
*/
exports.hashObjectOrVariable = function(item, hashMethod)
{
if(!item)
{
return null;
}
var cHashMethod = hashMethod || "md5";
if(typeof item === "object" || item instanceof Array)
{
return hashObject(item, cHashMethod);
}
else
{
return crypto.createHash(cHashMethod).update(item).digest('hex');
}
};

// Escapes any characters that are illegal in a CouchDB database name using percent codes inside parenthesis
// Example: 'My.name@example.com' => 'my(2e)name(40)example(2e)com'
function getLegalDBName(input) {
input = input.toLowerCase();
var output = encodeURIComponent(input);
output = output.replace(/\./g, '%2E');
output = output.replace(/!/g, '%21');
output = output.replace(/~/g, '%7E');
output = output.replace(/\*/g, '%2A');
output = output.replace(/'/g, '%27');
output = output.replace(/\(/g, '%28');
output = output.replace(/\)/g, '%29');
output = output.replace(/\-/g, '%2D');
output = output.toLowerCase();
output = output.replace(/(%..)/g, function(esc) {
esc = esc.substr(1);
return '(' + esc + ')';
});
return output;
}

exports.getLegalDBName = getLegalDBName;

exports.getFinalDBName = function(prefix, dbName, type, userDoc)
{

var finalDBName;
// Make sure we have a legal database name
var username = userDoc._id;
username = getLegalDBName(username);
if(type === 'shared') {
return dbName;
} else {
finalDBName = prefix + dbName + '$' + username;
}
return finalDBName;
};

exports.castFinalToDefaultDBName = function(prefix, finalDBName, type, userDoc)
{
var defaultDBName;

var username = userDoc._id;
username = getLegalDBName(username);
if(type === 'shared') {
return finalDBName;
} else {
defaultDBName = finalDBName.replace(prefix,"").replace('$' + username, "");
}
return defaultDBName;
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"dependencies": {
"bluebird": "^3.3.4",
"couch-pwd": "0.0.1",
"crypto": "0.0.3",
"ejs": "^2.3.1",
"express": "^4.13.3",
"extend": "^3.0.0",
Expand Down
Loading

0 comments on commit b35b6f2

Please sign in to comment.