From 87aa27c7bd3887ab98b4668d4e5dedb53c23b9ea Mon Sep 17 00:00:00 2001 From: Peter Uithoven Date: Wed, 24 Aug 2016 16:25:43 +0200 Subject: [PATCH 01/17] Clarify adding additional fields I found the information on adding additional (profile) fields somewhat spread out over the documentation and lacking some examples. This is a proposal to clarify this. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a8a093c..53b752d9 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,7 @@ It's easy to add custom fields to user documents. When added to a `profile` fiel 2. Include the fields with [registrations](#post-register). 3. To also fill in custom fields after social authentications use the [superlogin.onCreate](#superloginoncreatefn) handler. Example: - ``` js + ``` js superlogin.onCreate(function(userDoc, provider) { if(userDoc.profile === undefined) { userDoc.profile = {}; From 069ff922e9dee042bd9afbb1594da3ad5e11bda2 Mon Sep 17 00:00:00 2001 From: Jesus Rabelo Date: Wed, 9 Nov 2016 15:35:24 -0500 Subject: [PATCH 02/17] Add custom data bases funcionality --- lib/user.js | 178 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 140 insertions(+), 38 deletions(-) diff --git a/lib/user.js b/lib/user.js index 5f6478b9..79877b61 100644 --- a/lib/user.js +++ b/lib/user.js @@ -1259,48 +1259,150 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); } - function addUserDBs(newUser) { - // Add personal DBs - if(!config.getItem('userDBs.defaultDBs')) { - return BPromise.resolve(newUser); + function addCustomUserDBs(approach, userdbList, userOpposedbList, customPrivateDBs, customSharedDBs, defaultPrivateDBs, defaultSharedDBs, type) { + var dbs; + var defaultOpposedDBs; + if(approach!=='override' && approach!=='rename'){ + throw new Error("Invalid value " + approach ); + } + if (type === 'private') { + dbs = customPrivateDBs; + defaultOpposedDBs = defaultSharedDBs; + } else { + dbs = customSharedDBs; + defaultOpposedDBs = defaultPrivateDBs; + } + dbs.forEach(function (nameDB) { + var indesDb = defaultOpposedDBs.indexOf(nameDB); + var indexOpposeDb = userOpposedbList.indexOf(nameDB); + + if (indesDb > -1 || indexOpposeDb > -1 ) { + if(approach === 'override'){ + if(indexOpposeDb > -1){ + userOpposedbList.splice(indexOpposeDb,1); + } + if(indesDb >-1){ + defaultOpposedDBs.splice(indesDb,1); + } + }else{ + var index; + if(type === 'private'){ + index = customPrivateDBs.indexOf(nameDB); + if (index > -1) { + customPrivateDBs.splice(index, 1); + } + index = defaultPrivateDBs.indexOf(nameDB); + if(index > -1){ + defaultPrivateDBs.splice(index); + } + index = userdbList.indexOf(nameDB); + if (index > -1) { + userdbList.splice(index,1); + } + }else{ + index = customSharedDBs.indexOf(nameDB); + if (index > -1) { + customSharedDBs.splice(index, 1); + } + index = defaultSharedDBs.indexOf(nameDB); + if(index > -1){ + defaultSharedDBs.splice(index); + } + index = userdbList.indexOf(nameDB); + if (index > -1) { + userdbList.splice(index,1); + } + } + nameDB=nameDB+"_"+type; + } + } + if(!userdbList.includes(nameDB)) { + userdbList.push(nameDB); + } + }); + if(type === 'private'){ + defaultSharedDBs.concat(customSharedDBs).filter(function (element) { + if(!userOpposedbList.includes(element) && !userOpposedbList.includes(element+"_shared")) { + userOpposedbList.push(element); + } + }); + }else{ + defaultPrivateDBs.concat(customPrivateDBs).filter(function (element) { + if(!userOpposedbList.includes(element) && !userOpposedbList.includes(element+"_private")) { + userOpposedbList.push(element); + } + }); + } } - var promises = []; - newUser.personalDBs = {}; - var processUserDBs = function(dbList, type) { - dbList.forEach(function(userDBName) { - var dbConfig = dbAuth.getDBConfig(userDBName); - promises.push( - dbAuth.addUserDB(newUser, userDBName, dbConfig.designDocs, type, dbConfig.permissions, dbConfig.adminRoles, - dbConfig.memberRoles) - .then(function(finalDBName) { - delete dbConfig.permissions; - delete dbConfig.adminRoles; - delete dbConfig.memberRoles; - delete dbConfig.designDocs; - dbConfig.type = type; - newUser.personalDBs[finalDBName] = dbConfig; - })); - }); - }; + function addUserDBs(newUser) { + // Add personal DBs + if (!config.getItem('userDBs.defaultDBs')) { + return BPromise.resolve(newUser); + } + var promises = []; + newUser.personalDBs = {}; - // Just in case defaultDBs is not specified - var defaultPrivateDBs = config.getItem('userDBs.defaultDBs.private'); - if(!Array.isArray(defaultPrivateDBs)) { - defaultPrivateDBs = []; - } - processUserDBs(defaultPrivateDBs, 'private'); - var defaultSharedDBs = config.getItem('userDBs.defaultDBs.shared'); - if(!Array.isArray(defaultSharedDBs)) { - defaultSharedDBs = []; - } - processUserDBs(defaultSharedDBs, 'shared'); + var processUserDBs = function (dbList, type) { + dbList.forEach(function (userDBName) { + var dbConfig = dbAuth.getDBConfig(userDBName); + promises.push( + dbAuth.addUserDB(newUser, userDBName, dbConfig.designDocs, type, dbConfig.permissions, dbConfig.adminRoles, + dbConfig.memberRoles) + .then(function (finalDBName) { + delete dbConfig.permissions; + delete dbConfig.adminRoles; + delete dbConfig.memberRoles; + delete dbConfig.designDocs; + dbConfig.type = type; + newUser.personalDBs[finalDBName] = dbConfig; + })); + }); + }; - return BPromise.all(promises).then(function() { - return BPromise.resolve(newUser); - }); - } + var customUserDBs = config.getItem('userDBs.customDBs'); + if (!Array.isArray(customUserDBs)) { + customUserDBs = []; + } + // Just in case defaultDBs is not specified + var defaultPrivateDBs = config.getItem('userDBs.defaultDBs.private'); + if (!Array.isArray(defaultPrivateDBs)) { + defaultPrivateDBs = []; + } - return this; + var defaultSharedDBs = config.getItem('userDBs.defaultDBs.shared'); + if (!Array.isArray(defaultSharedDBs)) { + defaultSharedDBs = []; + } + var userPrivateDBs= []; + var userSharedDBs = []; + var roleUserList = newUser.roles; + var customrol = false; + customUserDBs.forEach(function (element) { + if(roleUserList.includes(element.roles)) { + var customPrivateDBs = element.DBs.private || []; + var customSharedDBs = element.DBs.shared || []; + var approach = element.approach || 'override'; + addCustomUserDBs(approach, userPrivateDBs, userSharedDBs, customPrivateDBs, customSharedDBs, defaultPrivateDBs, defaultSharedDBs, 'private'); + addCustomUserDBs(approach, userSharedDBs, userPrivateDBs, customPrivateDBs, customSharedDBs, defaultPrivateDBs, defaultSharedDBs, 'shared'); + customrol = true; + } + }); + if(!customrol){ + userPrivateDBs = defaultPrivateDBs; + userSharedDBs = defaultSharedDBs; + } + + console.log(userPrivateDBs); + console.log(userSharedDBs); + + processUserDBs(userPrivateDBs, 'private'); + processUserDBs(userSharedDBs, 'shared'); + + return BPromise.all(promises).then(function () { + return BPromise.resolve(newUser); + }); + } + return this; }; From 8eaaf6856894721898479a08dbc7ed596591be8d Mon Sep 17 00:00:00 2001 From: Jesus Rabelo Date: Tue, 15 Nov 2016 00:39:58 -0500 Subject: [PATCH 03/17] Add customdbs unitary test --- dump.rdb | Bin 0 -> 76 bytes gulpfile.js | 11 +- lib/user.js | 4 +- test/customdbs.spec.js | 250 +++++++++++++++++++++++++++++++++++++++++ test/test.config.js | 2 +- 5 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 dump.rdb create mode 100644 test/customdbs.spec.js diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000000000000000000000000000000000000..ecd5c625d0ad45f9115dffc1862a30ec37371899 GIT binary patch literal 76 zcmWG?b@2=~FfcIx#aWb^l3A= Date: Tue, 15 Nov 2016 00:42:51 -0500 Subject: [PATCH 04/17] Add customdbs unitary test --- dump.rdb | Bin 76 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 dump.rdb diff --git a/dump.rdb b/dump.rdb deleted file mode 100644 index ecd5c625d0ad45f9115dffc1862a30ec37371899..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76 zcmWG?b@2=~FfcIx#aWb^l3A= Date: Tue, 15 Nov 2016 09:12:50 -0500 Subject: [PATCH 05/17] delete console debug --- lib/user.js | 846 ++++++++++++++++++++++++++++------------------------ 1 file changed, 459 insertions(+), 387 deletions(-) diff --git a/lib/user.js b/lib/user.js index 1cd806b6..be7c418e 100644 --- a/lib/user.js +++ b/lib/user.js @@ -37,13 +37,14 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { if (!username.match(USER_REGEXP)) { return BPromise.resolve('Invalid username'); } - return userDB.query('auth/username', {key: username}) + return userDB.query('auth/username', { + key: username + }) .then(function (result) { if (result.rows.length === 0) { // Pass! return BPromise.resolve(); - } - else { + } else { return BPromise.resolve('already in use'); } }, function (err) { @@ -58,13 +59,14 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { if (!email.match(EMAIL_REGEXP)) { return BPromise.resolve('invalid email'); } - return userDB.query('auth/email', {key: email}) + return userDB.query('auth/email', { + key: email + }) .then(function (result) { if (result.rows.length === 0) { // Pass! return BPromise.resolve(); - } - else { + } else { return BPromise.resolve('already in use'); } }, function (err) { @@ -79,12 +81,13 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { if (!email.match(EMAIL_REGEXP)) { return BPromise.resolve('invalid email'); } - return userDB.query('auth/emailUsername', {key: email}) + return userDB.query('auth/emailUsername', { + key: email + }) .then(function (result) { if (result.rows.length === 0) { return BPromise.resolve(); - } - else { + } else { return BPromise.resolve('already in use'); } }, function (err) { @@ -93,7 +96,7 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }; // Validation function for ensuring that two fields match - this.matches = function(value, option, key, attributes) { + this.matches = function (value, option, key, attributes) { if (attributes && attributes[option] !== value) { return "does not match " + option; } @@ -154,7 +157,7 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { } }; - if(emailUsername) { + if (emailUsername) { delete userModel.validate.username; delete userModel.validate.email.validateEmail; delete userModel.rename.username; @@ -190,16 +193,16 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { } }; - this.onCreate = function(fn) { - if(typeof fn === 'function') { + this.onCreate = function (fn) { + if (typeof fn === 'function') { onCreateActions.push(fn); } else { throw new TypeError('onCreate: You must pass in a function'); } }; - this.onLink = function(fn) { - if(typeof fn === 'function') { + this.onLink = function (fn) { + if (typeof fn === 'function') { onLinkActions.push(fn); } else { throw new TypeError('onLink: You must pass in a function'); @@ -208,19 +211,19 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { function processTransformations(fnArray, userDoc, provider) { var promise; - fnArray.forEach(function(fn) { - if(!promise) { + fnArray.forEach(function (fn) { + if (!promise) { promise = fn.call(null, userDoc, provider); } else { - if(!promise.then || typeof promise.then !== 'function') { + if (!promise.then || typeof promise.then !== 'function') { throw new Error('onCreate function must return a promise'); } - promise.then(function(newUserDoc) { + promise.then(function (newUserDoc) { return fn.call(null, newUserDoc, provider); }); } }); - if(!promise) { + if (!promise) { promise = BPromise.resolve(userDoc); } return promise; @@ -228,12 +231,15 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { this.get = function (login) { var query; - if(emailUsername) { + if (emailUsername) { query = 'emailUsername'; } else { query = EMAIL_REGEXP.test(login) ? 'email' : 'username'; } - return userDB.query('auth/' + query, {key: login, include_docs: true}) + return userDB.query('auth/' + query, { + key: login, + include_docs: true + }) .then(function (results) { if (results.rows.length > 0) { return BPromise.resolve(results.rows[0].doc); @@ -247,9 +253,9 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { req = req || {}; var finalUserModel = userModel; var newUserModel = config.getItem('userModel'); - if(typeof newUserModel === 'object') { + if (typeof newUserModel === 'object') { var whitelist; - if(newUserModel.whitelist) { + if (newUserModel.whitelist) { whitelist = util.arrayUnion(userModel.whitelist, newUserModel.whitelist); } finalUserModel = extend(true, {}, userModel, config.getItem('userModel')); @@ -261,10 +267,10 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { return user.process() .then(function (result) { newUser = result; - if(emailUsername) { + if (emailUsername) { newUser._id = newUser.email; } - if(config.getItem('local.sendConfirmEmail')) { + if (config.getItem('local.sendConfirmEmail')) { newUser.unverifiedEmail = { email: newUser.email, token: util.URLSafeUUID() @@ -272,8 +278,12 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { delete newUser.email; } return util.hashPassword(newUser.password); - }, function(err) { - return BPromise.reject({error: 'Validation failed', validationErrors: err, status: 400}); + }, function (err) { + return BPromise.reject({ + error: 'Validation failed', + validationErrors: err, + status: 400 + }); }) .then(function (hash) { // Store password hash @@ -289,21 +299,24 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }; return addUserDBs(newUser); }) - .then(function(newUser) { + .then(function (newUser) { return self.logActivity(newUser._id, 'signup', 'local', req, newUser); }) - .then(function(newUser) { + .then(function (newUser) { return processTransformations(onCreateActions, newUser, 'local'); }) - .then(function(finalNewUser) { + .then(function (finalNewUser) { return userDB.put(finalNewUser); }) - .then(function(result) { + .then(function (result) { newUser._rev = result.rev; - if(!config.getItem('local.sendConfirmEmail')) { + if (!config.getItem('local.sendConfirmEmail')) { return BPromise.resolve(); } - return mailer.sendEmail('confirmEmail', newUser.unverifiedEmail.email, {req: req, user: newUser}); + return mailer.sendEmail('confirmEmail', newUser.unverifiedEmail.email, { + req: req, + user: newUser + }); }) .then(function () { emitter.emit('signup', newUser, 'local'); @@ -311,7 +324,7 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); }; - this.socialAuth = function(provider, auth, profile, req) { + this.socialAuth = function (provider, auth, profile, req) { var user; var newAccount = false; var action; @@ -320,10 +333,13 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { var ip = req.ip; // It is important that we return a Bluebird promise so oauth.js can call .nodeify() return BPromise.resolve() - .then(function() { - return userDB.query('auth/' + provider, {key: profile.id, include_docs: true}); + .then(function () { + return userDB.query('auth/' + provider, { + key: profile.id, + include_docs: true + }); }) - .then(function(results) { + .then(function (results) { if (results.rows.length > 0) { user = results.rows[0].doc; return BPromise.resolve(); @@ -331,7 +347,7 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { newAccount = true; user = {}; user[provider] = {}; - if(profile.emails) { + if (profile.emails) { user.email = profile.emails[0].value; } user.providers = [provider]; @@ -342,7 +358,7 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { timestamp: new Date().toISOString(), ip: ip }; - var emailFail = function() { + var emailFail = function () { return BPromise.reject({ error: 'Email already in use', message: 'Your email is already in use. Try signing in first and then linking this account.', @@ -350,8 +366,8 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); }; // Now we need to generate a username - if(emailUsername) { - if(!user.email) { + if (emailUsername) { + if (!user.email) { return BPromise.reject({ error: 'No email provided', message: 'An email is required for registration, but ' + provider + ' didn\'t supply one.', @@ -359,29 +375,29 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); } return self.validateEmailUsername(user.email) - .then(function(err) { - if(err) { + .then(function (err) { + if (err) { return emailFail(); } return BPromise.resolve(user.email.toLowerCase()); }); } else { - if(profile.username) { + if (profile.username) { baseUsername = profile.username.toLowerCase(); } else { // If a username isn't specified we'll take it from the email - if(user.email) { + if (user.email) { var parseEmail = user.email.split('@'); baseUsername = parseEmail[0].toLowerCase(); - } else if(profile.displayName) { + } else if (profile.displayName) { baseUsername = profile.displayName.replace(/\s/g, '').toLowerCase(); } else { baseUsername = profile.id.toLowerCase(); } } return self.validateEmail(user.email) - .then(function(err) { - if(err) { + .then(function (err) { + if (err) { return emailFail(); } return generateUsername(baseUsername); @@ -389,57 +405,59 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { } } }) - .then(function(finalUsername) { - if(finalUsername) { + .then(function (finalUsername) { + if (finalUsername) { user._id = finalUsername; } user[provider].auth = auth; user[provider].profile = profile; - if(!user.name) { + if (!user.name) { user.name = profile.displayName; } delete user[provider].profile._raw; - if(newAccount) { + if (newAccount) { return addUserDBs(user); } else { return BPromise.resolve(user); } }) - .then(function(userDoc) { + .then(function (userDoc) { action = newAccount ? 'signup' : 'login'; return self.logActivity(userDoc._id, action, provider, req, userDoc); }) - .then(function(userDoc) { - if(newAccount) { + .then(function (userDoc) { + if (newAccount) { return processTransformations(onCreateActions, userDoc, provider); } else { return processTransformations(onLinkActions, userDoc, provider); } }) - .then(function(finalUser) { + .then(function (finalUser) { return userDB.put(finalUser); }) - .then(function() { - if(action === 'signup') { + .then(function () { + if (action === 'signup') { emitter.emit('signup', user, provider); } return BPromise.resolve(user); }); }; - this.linkSocial = function(user_id, provider, auth, profile, req) { + this.linkSocial = function (user_id, provider, auth, profile, req) { req = req || {}; var user; // Load user doc return BPromise.resolve() - .then(function() { - return userDB.query('auth/' + provider, {key: profile.id}); + .then(function () { + return userDB.query('auth/' + provider, { + key: profile.id + }); }) - .then(function(results) { - if(results.rows.length === 0) { + .then(function (results) { + if (results.rows.length === 0) { return BPromise.resolve(); } else { - if(results.rows[0].id !== user_id) { + if (results.rows[0].id !== user_id) { return BPromise.reject({ error: 'Conflict', message: 'This ' + provider + ' profile is already in use by another account.', @@ -448,13 +466,13 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { } } }) - .then(function() { + .then(function () { return userDB.get(user_id); }) - .then(function(theUser) { + .then(function (theUser) { user = theUser; // Check for conflicting provider - if(user[provider] && (user[provider].profile.id !== profile.id)) { + if (user[provider] && (user[provider].profile.id !== profile.id)) { return BPromise.reject({ error: 'Conflict', message: 'Your account is already linked with another ' + provider + 'profile.', @@ -462,28 +480,34 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); } // Check email for conflict - if(!profile.emails) { - return BPromise.resolve({rows: []}); + if (!profile.emails) { + return BPromise.resolve({ + rows: [] + }); } - if(emailUsername) { - return userDB.query('auth/emailUsername', {key: profile.emails[0].value}); + if (emailUsername) { + return userDB.query('auth/emailUsername', { + key: profile.emails[0].value + }); } else { - return userDB.query('auth/email', {key: profile.emails[0].value}); + return userDB.query('auth/email', { + key: profile.emails[0].value + }); } }) - .then(function(results) { + .then(function (results) { var passed; - if(results.rows.length === 0) { + if (results.rows.length === 0) { passed = true; } else { passed = true; - results.rows.forEach(function(row) { - if(row.id !== user_id) { + results.rows.forEach(function (row) { + if (row.id !== user_id) { passed = false; } }); } - if(!passed) { + if (!passed) { return BPromise.reject({ error: 'Conflict', message: 'The email ' + profile.emails[0].value + ' is already in use by another account.', @@ -493,40 +517,40 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { return BPromise.resolve(); } }) - .then(function() { + .then(function () { // Insert provider info user[provider] = {}; user[provider].auth = auth; user[provider].profile = profile; - if(!user.providers) { + if (!user.providers) { user.providers = []; } - if(user.providers.indexOf(provider) === -1) { + if (user.providers.indexOf(provider) === -1) { user.providers.push(provider); } - if(!user.name) { + if (!user.name) { user.name = profile.displayName; } delete user[provider].profile._raw; return self.logActivity(user._id, 'link', provider, req, user); }) - .then(function(userDoc) { + .then(function (userDoc) { return processTransformations(onLinkActions, userDoc, provider); }) - .then(function(finalUser) { + .then(function (finalUser) { return userDB.put(finalUser); }) - .then(function() { + .then(function () { return BPromise.resolve(user); }); }; - this.unlink = function(user_id, provider) { + this.unlink = function (user_id, provider) { var user; return userDB.get(user_id) - .then(function(theUser) { + .then(function (theUser) { user = theUser; - if(!provider) { + if (!provider) { return BPromise.reject({ error: 'Unlink failed', message: 'You must specify a provider to unlink.', @@ -534,7 +558,7 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); } // We can only unlink if there are at least two providers - if(!user.providers || !(user.providers instanceof Array) || user.providers.length < 2) { + if (!user.providers || !(user.providers instanceof Array) || user.providers.length < 2) { return BPromise.reject({ error: 'Unlink failed', message: 'You can\'t unlink your only provider!', @@ -542,7 +566,7 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); } // We cannot unlink local - if(provider === 'local') { + if (provider === 'local') { return BPromise.reject({ error: 'Unlink failed', message: 'You can\'t unlink local.', @@ -550,7 +574,7 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); } // Check that the provider exists - if(!user[provider] || typeof user[provider] !== 'object') { + if (!user[provider] || typeof user[provider] !== 'object') { return BPromise.reject({ error: 'Unlink failed', message: 'Provider: ' + util.capitalizeFirstLetter(provider) + ' not found.', @@ -562,12 +586,12 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { user.providers.splice(user.providers.indexOf(provider), 1); return userDB.put(user); }) - .then(function() { + .then(function () { return BPromise.resolve(user); }); }; - this.createSession = function(user_id, provider, req) { + this.createSession = function (user_id, provider, req) { var user; var newToken; var newSession; @@ -575,31 +599,31 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { req = req || {}; var ip = req.ip; return userDB.get(user_id) - .then(function(record) { + .then(function (record) { user = record; return generateSession(user._id, user.roles); }) - .then(function(token) { + .then(function (token) { password = token.password; newToken = token; newToken.provider = provider; return session.storeToken(newToken); }) - .then(function() { + .then(function () { return dbAuth.storeKey(user_id, newToken.key, password, newToken.expires, user.roles); }) - .then(function() { + .then(function () { // authorize the new session across all dbs - if(!user.personalDBs) { + if (!user.personalDBs) { return BPromise.resolve(); } return dbAuth.authorizeUserSessions(user_id, user.personalDBs, newToken.key, user.roles); }) - .then(function() { - if(!user.session) { + .then(function () { + if (!user.session) { user.session = {}; } - newSession = { + newSession = { issued: newToken.issued, expires: newToken.expires, provider: provider, @@ -607,31 +631,31 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }; user.session[newToken.key] = newSession; // Clear any failed login attempts - if(provider === 'local') { - if(!user.local) user.local = {}; + if (provider === 'local') { + if (!user.local) user.local = {}; user.local.failedLoginAttempts = 0; delete user.local.lockedUntil; } return self.logActivity(user._id, 'login', provider, req, user); }) - .then(function(userDoc) { + .then(function (userDoc) { // Clean out expired sessions on login return self.logoutUserSessions(userDoc, 'expired'); }) - .then(function(finalUser) { + .then(function (finalUser) { user = finalUser; return userDB.put(finalUser); }) - .then(function() { + .then(function () { newSession.token = newToken.key; newSession.password = password; newSession.user_id = user._id; newSession.roles = user.roles; // Inject the list of userDBs - if(typeof user.personalDBs === 'object') { + if (typeof user.personalDBs === 'object') { var userDBs = {}; var publicURL; - if(config.getItem('dbServer.publicURL')) { + if (config.getItem('dbServer.publicURL')) { var dbObj = url.parse(config.getItem('dbServer.publicURL')); dbObj.auth = newSession.token + ':' + newSession.password; publicURL = dbObj.format(); @@ -639,12 +663,12 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { publicURL = config.getItem('dbServer.protocol') + newSession.token + ':' + newSession.password + '@' + config.getItem('dbServer.host') + '/'; } - Object.keys(user.personalDBs).forEach(function(finalDBName) { + Object.keys(user.personalDBs).forEach(function (finalDBName) { userDBs[user.personalDBs[finalDBName].name] = publicURL + finalDBName; }); newSession.userDBs = userDBs; } - if(user.profile) { + if (user.profile) { newSession.profile = user.profile; } emitter.emit('login', newSession, provider); @@ -652,50 +676,50 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); }; - this.handleFailedLogin = function(user, req) { + this.handleFailedLogin = function (user, req) { req = req || {}; var maxFailedLogins = config.getItem('security.maxFailedLogins'); - if(!maxFailedLogins) { + if (!maxFailedLogins) { return BPromise.resolve(); } - if(!user.local) { + if (!user.local) { user.local = {}; } - if(!user.local.failedLoginAttempts) { + if (!user.local.failedLoginAttempts) { user.local.failedLoginAttempts = 0; } user.local.failedLoginAttempts++; - if(user.local.failedLoginAttempts > maxFailedLogins) { + if (user.local.failedLoginAttempts > maxFailedLogins) { user.local.failedLoginAttempts = 0; user.local.lockedUntil = Date.now() + config.getItem('security.lockoutTime') * 1000; } return self.logActivity(user._id, 'failed login', 'local', req, user) - .then(function(finalUser) { + .then(function (finalUser) { return userDB.put(finalUser); }) - .then(function() { + .then(function () { return BPromise.resolve(!!user.local.lockedUntil); }); }; - this.logActivity = function(user_id, action, provider, req, userDoc, saveDoc) { + this.logActivity = function (user_id, action, provider, req, userDoc, saveDoc) { var logSize = config.getItem('security.userActivityLogSize'); - if(!logSize) { + if (!logSize) { return BPromise.resolve(userDoc); } var promise; - if(userDoc) { + if (userDoc) { promise = BPromise.resolve(userDoc); } else { - if(saveDoc !== false) { + if (saveDoc !== false) { saveDoc = true; } promise = userDB.get(user_id); } return promise - .then(function(theUser) { + .then(function (theUser) { userDoc = theUser; - if(!userDoc.activity || !(userDoc.activity instanceof Array)) { + if (!userDoc.activity || !(userDoc.activity instanceof Array)) { userDoc.activity = []; } var entry = { @@ -705,12 +729,12 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { ip: req.ip }; userDoc.activity.unshift(entry); - while(userDoc.activity.length > logSize) { + while (userDoc.activity.length > logSize) { userDoc.activity.pop(); } - if(saveDoc) { + if (saveDoc) { return userDB.put(userDoc) - .then(function() { + .then(function () { return BPromise.resolve(userDoc); }); } else { @@ -722,7 +746,7 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { this.refreshSession = function (key) { var newSession; return session.fetchToken(key) - .then(function(oldToken) { + .then(function (oldToken) { newSession = oldToken; newSession.expires = Date.now() + sessionLife * 1000; return BPromise.all([ @@ -730,16 +754,16 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { session.storeToken(newSession) ]); }) - .then(function(results) { + .then(function (results) { var userDoc = results[0]; userDoc.session[key].expires = newSession.expires; // Clean out expired sessions on refresh return self.logoutUserSessions(userDoc, 'expired'); }) - .then(function(finalUser) { + .then(function (finalUser) { return userDB.put(finalUser); }) - .then(function() { + .then(function () { delete newSession.password; newSession.token = newSession.key; delete newSession.key; @@ -760,8 +784,11 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { return passwordResetForm.validate() .then(function () { var tokenHash = util.hashToken(form.token); - return userDB.query('auth/passwordReset', {key: tokenHash, include_docs: true}); - }, function(err) { + return userDB.query('auth/passwordReset', { + key: tokenHash, + include_docs: true + }); + }, function (err) { return BPromise.reject({ error: 'Validation failed', validationErrors: err, @@ -770,41 +797,47 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }) .then(function (results) { if (!results.rows.length) { - return BPromise.reject({status: 400, error: 'Invalid token'}); + return BPromise.reject({ + status: 400, + error: 'Invalid token' + }); } user = results.rows[0].doc; - if(user.forgotPassword.expires < Date.now()) { - return BPromise.reject({status: 400, error: 'Token expired'}); + if (user.forgotPassword.expires < Date.now()) { + return BPromise.reject({ + status: 400, + error: 'Token expired' + }); } return util.hashPassword(form.password); }) - .then(function(hash) { - if(!user.local) { + .then(function (hash) { + if (!user.local) { user.local = {}; } user.local.salt = hash.salt; user.local.derived_key = hash.derived_key; - if(user.providers.indexOf('local') === -1) { + if (user.providers.indexOf('local') === -1) { user.providers.push('local'); } // logout user completely return self.logoutUserSessions(user, 'all'); }) - .then(function(userDoc) { + .then(function (userDoc) { user = userDoc; delete user.forgotPassword; return self.logActivity(user._id, 'reset password', 'local', req, user); }) - .then(function(finalUser) { + .then(function (finalUser) { return userDB.put(finalUser); }) - .then(function() { + .then(function () { emitter.emit('password-reset', user); return BPromise.resolve(user); }); }; - this.changePasswordSecure = function(user_id, form, req) { + this.changePasswordSecure = function (user_id, form, req) { req = req || {}; var self = this; var ChangePasswordModel = new Model(changePasswordModel); @@ -813,31 +846,43 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { return changePasswordForm.validate() .then(function () { return userDB.get(user_id); - }, function(err) { - return BPromise.reject({error: 'Validation failed', validationErrors: err, status: 400}); + }, function (err) { + return BPromise.reject({ + error: 'Validation failed', + validationErrors: err, + status: 400 + }); }) - .then(function() { + .then(function () { return userDB.get(user_id); }) - .then(function(userDoc) { + .then(function (userDoc) { user = userDoc; - if(user.local && user.local.salt && user.local.derived_key) { + if (user.local && user.local.salt && user.local.derived_key) { // Password is required - if(!form.currentPassword){ - return BPromise.reject({error: 'Password change failed', message: 'You must supply your current password in order to change it.', status: 400}); + if (!form.currentPassword) { + return BPromise.reject({ + error: 'Password change failed', + message: 'You must supply your current password in order to change it.', + status: 400 + }); } return util.verifyPassword(user.local, form.currentPassword); } else { return BPromise.resolve(); } }) - .then(function() { + .then(function () { return self.changePassword(user._id, form.newPassword, user, req); - }, function(err) { - return BPromise.reject(err || {error: 'Password change failed', message: 'The current password you supplied is incorrect.', status: 400}); + }, function (err) { + return BPromise.reject(err || { + error: 'Password change failed', + message: 'The current password you supplied is incorrect.', + status: 400 + }); }) - .then(function() { - if(req.user && req.user.key) { + .then(function () { + if (req.user && req.user.key) { return self.logoutOthers(req.user.key); } else { return BPromise.resolve(); @@ -845,7 +890,7 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); }; - this.changePassword = function(user_id, newPassword, userDoc, req) { + this.changePassword = function (user_id, newPassword, userDoc, req) { req = req || {}; var promise, user; if (userDoc) { @@ -854,40 +899,43 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { promise = userDB.get(user_id); } return promise - .then(function(doc) { + .then(function (doc) { user = doc; return util.hashPassword(newPassword); - }, function(err) { + }, function (err) { return BPromise.reject({ error: 'User not found', status: 404 }); }) - .then(function(hash) { - if(!user.local) { + .then(function (hash) { + if (!user.local) { user.local = {}; } user.local.salt = hash.salt; user.local.derived_key = hash.derived_key; - if(user.providers.indexOf('local') === -1) { + if (user.providers.indexOf('local') === -1) { user.providers.push('local'); } return self.logActivity(user._id, 'changed password', 'local', req, user); }) - .then(function(finalUser) { + .then(function (finalUser) { return userDB.put(finalUser); }) - .then(function() { + .then(function () { emitter.emit('password-change', user); }); }; - this.forgotPassword = function(email, req) { + this.forgotPassword = function (email, req) { req = req || {}; var user, token, tokenHash; - return userDB.query('auth/email', {key: email, include_docs: true}) - .then(function(result) { - if(!result.rows.length) { + return userDB.query('auth/email', { + key: email, + include_docs: true + }) + .then(function (result) { + if (!result.rows.length) { return BPromise.reject({ error: 'User not found', status: 404 @@ -903,25 +951,34 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }; return self.logActivity(user._id, 'forgot password', 'local', req, user); }) - .then(function(finalUser) { + .then(function (finalUser) { return userDB.put(finalUser); }) - .then(function() { - return mailer.sendEmail('forgotPassword', user.email || user.unverifiedEmail.email, - {user: user, req: req, token: token}); // Send user the unhashed token - }).then(function() { + .then(function () { + return mailer.sendEmail('forgotPassword', user.email || user.unverifiedEmail.email, { + user: user, + req: req, + token: token + }); // Send user the unhashed token + }).then(function () { emitter.emit('forgot-password', user); return BPromise.resolve(user.forgotPassword); }); }; - this.verifyEmail = function(token, req) { + this.verifyEmail = function (token, req) { req = req || {}; var user; - return userDB.query('auth/verifyEmail', {key: token, include_docs: true}) - .then(function(result) { - if(!result.rows.length) { - return BPromise.reject({error: 'Invalid token', status: 400}); + return userDB.query('auth/verifyEmail', { + key: token, + include_docs: true + }) + .then(function (result) { + if (!result.rows.length) { + return BPromise.reject({ + error: 'Invalid token', + status: 400 + }); } user = result.rows[0].doc; user.email = user.unverifiedEmail.email; @@ -929,64 +986,69 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { emitter.emit('email-verified', user); return self.logActivity(user._id, 'verified email', 'local', req, user); }) - .then(function(finalUser) { + .then(function (finalUser) { return userDB.put(finalUser); }); }; - this.changeEmail = function(user_id, newEmail, req) { + this.changeEmail = function (user_id, newEmail, req) { req = req || {}; - if(!req.user) { - req.user = {provider: 'local'}; + if (!req.user) { + req.user = { + provider: 'local' + }; } var user; return self.validateEmail(newEmail) - .then(function(err) { - if(err) { + .then(function (err) { + if (err) { return BPromise.reject(err); } return userDB.get(user_id); }) - .then(function(userDoc) { + .then(function (userDoc) { user = userDoc; - if(config.getItem('local.sendConfirmEmail')) { + if (config.getItem('local.sendConfirmEmail')) { user.unverifiedEmail = { email: newEmail, token: util.URLSafeUUID() }; - return mailer.sendEmail('confirmEmail', user.unverifiedEmail.email, {req: req, user: user}); + return mailer.sendEmail('confirmEmail', user.unverifiedEmail.email, { + req: req, + user: user + }); } else { user.email = newEmail; return BPromise.resolve(); } }) - .then(function() { + .then(function () { emitter.emit('email-changed', user); return self.logActivity(user._id, 'changed email', req.user.provider, req, user); }) - .then(function(finalUser) { + .then(function (finalUser) { return userDB.put(finalUser); }); }; - this.addUserDB = function(user_id, dbName, type, designDocs, permissions) { + this.addUserDB = function (user_id, dbName, type, designDocs, permissions) { var userDoc; var dbConfig = dbAuth.getDBConfig(dbName, type || 'private'); dbConfig.designDocs = designDocs || dbConfig.designDocs || ''; dbConfig.permissions = permissions || dbConfig.permissions; return userDB.get(user_id) - .then(function(result) { + .then(function (result) { userDoc = result; return dbAuth.addUserDB(userDoc, dbName, dbConfig.designDocs, dbConfig.type, dbConfig.permissions, dbConfig.adminRoles, dbConfig.memberRoles); }) - .then(function(finalDBName) { - if(!userDoc.personalDBs) { + .then(function (finalDBName) { + if (!userDoc.personalDBs) { userDoc.personalDBs = {}; } delete dbConfig.designDocs; // If permissions is specified explicitly it will be saved, otherwise will be taken from defaults every session - if(!permissions) { + if (!permissions) { delete dbConfig.permissions; } delete dbConfig.adminRoles; @@ -997,22 +1059,22 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); }; - this.removeUserDB = function(user_id, dbName, deletePrivate, deleteShared) { + this.removeUserDB = function (user_id, dbName, deletePrivate, deleteShared) { var user; var update = false; return userDB.get(user_id) - .then(function(userDoc) { + .then(function (userDoc) { user = userDoc; - if(user.personalDBs && typeof user.personalDBs === 'object') { - Object.keys(user.personalDBs).forEach(function(db) { - if(user.personalDBs[db].name === dbName) { + if (user.personalDBs && typeof user.personalDBs === 'object') { + Object.keys(user.personalDBs).forEach(function (db) { + if (user.personalDBs[db].name === dbName) { var type = user.personalDBs[db].type; delete user.personalDBs[db]; update = true; - if(type === 'private' && deletePrivate) { + if (type === 'private' && deletePrivate) { return dbAuth.removeDB(dbName); } - if(type === 'shared' && deleteShared) { + if (type === 'shared' && deleteShared) { return dbAuth.removeDB(dbName); } } @@ -1020,8 +1082,8 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { } return BPromise.resolve(); }) - .then(function() { - if(update) { + .then(function () { + if (update) { emitter.emit('user-db-removed', user_id, dbName); return userDB.put(user); } @@ -1029,21 +1091,24 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); }; - this.logoutUser = function(user_id, session_id) { + this.logoutUser = function (user_id, session_id) { var promise, user; - if(user_id) { + if (user_id) { promise = userDB.get(user_id); } else { - if(!session_id) { + if (!session_id) { return BPromise.reject({ error: 'unauthorized', message: 'Either user_id or session_id must be specified', status: 401 }); } - promise = userDB.query('auth/session', {key: session_id, include_docs: true}) - .then(function(results) { - if(!results.rows.length) { + promise = userDB.query('auth/session', { + key: session_id, + include_docs: true + }) + .then(function (results) { + if (!results.rows.length) { return BPromise.reject({ error: 'unauthorized', status: 401 @@ -1053,56 +1118,59 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); } return promise - .then(function(record) { + .then(function (record) { user = record; user_id = record._id; return self.logoutUserSessions(user, 'all'); }) - .then(function() { + .then(function () { emitter.emit('logout', user_id); emitter.emit('logout-all', user_id); return userDB.put(user); }); }; - this.logoutSession = function(session_id) { + this.logoutSession = function (session_id) { var user; var startSessions = 0; var endSessions = 0; - return userDB.query('auth/session', {key: session_id, include_docs: true}) - .then(function(results) { - if(!results.rows.length) { + return userDB.query('auth/session', { + key: session_id, + include_docs: true + }) + .then(function (results) { + if (!results.rows.length) { return BPromise.reject({ error: 'unauthorized', status: 401 }); } user = results.rows[0].doc; - if(user.session) { + if (user.session) { startSessions = Object.keys(user.session).length; - if(user.session[session_id]) { + if (user.session[session_id]) { delete user.session[session_id]; } } var promises = []; promises.push(session.deleteTokens(session_id)); promises.push(dbAuth.removeKeys(session_id)); - if(user) { + if (user) { promises.push(dbAuth.deauthorizeUser(user, session_id)); } return BPromise.all(promises); }) - .then(function() { + .then(function () { // Clean out expired sessions return self.logoutUserSessions(user, 'expired'); }) - .then(function(finalUser) { + .then(function (finalUser) { user = finalUser; - if(user.session) { + if (user.session) { endSessions = Object.keys(user.session).length; } emitter.emit('logout', user._id); - if(startSessions !== endSessions) { + if (startSessions !== endSessions) { return userDB.put(user); } else { return BPromise.resolve(false); @@ -1110,20 +1178,23 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); }; - this.logoutOthers = function(session_id) { + this.logoutOthers = function (session_id) { var user; - return userDB.query('auth/session', {key: session_id, include_docs: true}) - .then(function(results) { - if(results.rows.length) { + return userDB.query('auth/session', { + key: session_id, + include_docs: true + }) + .then(function (results) { + if (results.rows.length) { user = results.rows[0].doc; - if(user.session && user.session[session_id]) { + if (user.session && user.session[session_id]) { return self.logoutUserSessions(user, 'other', session_id); } } return BPromise.resolve(); }) - .then(function(finalUser) { - if(finalUser) { + .then(function (finalUser) { + if (finalUser) { return userDB.put(finalUser); } else { return BPromise.resolve(false); @@ -1131,71 +1202,71 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); }; - this.logoutUserSessions = function(userDoc, op, currentSession) { + this.logoutUserSessions = function (userDoc, op, currentSession) { // When op is 'other' it will logout all sessions except for the specified 'currentSession' var promises = []; var sessions; - if(op === 'all' || op === 'other') { + if (op === 'all' || op === 'other') { sessions = util.getSessions(userDoc); - } else if(op === 'expired') { + } else if (op === 'expired') { sessions = util.getExpiredSessions(userDoc, Date.now()); } - if(op === 'other' && currentSession) { + if (op === 'other' && currentSession) { // Remove the current session from the list of sessions we are going to delete var index = sessions.indexOf(currentSession); - if(index > -1) { + if (index > -1) { sessions.splice(index, 1); } } - if(sessions.length) { + if (sessions.length) { // Delete the sessions from our session store promises.push(session.deleteTokens(sessions)); // Remove the keys from our couchDB auth database promises.push(dbAuth.removeKeys(sessions)); // Deauthorize keys from each personal database promises.push(dbAuth.deauthorizeUser(userDoc, sessions)); - if(op === 'expired' || op === 'other') { - sessions.forEach(function(session) { + if (op === 'expired' || op === 'other') { + sessions.forEach(function (session) { delete userDoc.session[session]; }); } } - if(op ==='all') { + if (op === 'all') { delete userDoc.session; } return BPromise.all(promises) - .then(function() { + .then(function () { return BPromise.resolve(userDoc); }); }; - this.remove = function(user_id, destroyDBs) { + this.remove = function (user_id, destroyDBs) { var user; var promises = []; return userDB.get(user_id) - .then(function(userDoc) { + .then(function (userDoc) { return self.logoutUserSessions(userDoc, 'all'); }) - .then(function(userDoc) { + .then(function (userDoc) { user = userDoc; - if(destroyDBs !== true || !user.personalDBs) { + if (destroyDBs !== true || !user.personalDBs) { return BPromise.resolve(); } - Object.keys(user.personalDBs).forEach(function(userdb) { - if(user.personalDBs[userdb].type === 'private') { + Object.keys(user.personalDBs).forEach(function (userdb) { + if (user.personalDBs[userdb].type === 'private') { promises.push(dbAuth.removeDB(userdb)); } }); return BPromise.all(promises); }) - .then(function() { + .then(function () { return userDB.remove(user); }); }; this.removeExpiredKeys = dbAuth.removeExpiredKeys.bind(dbAuth); - this.confirmSession = function(key, password) { + this.confirmSession = function (key, password) { return session.confirmToken(key, password); }; @@ -1205,12 +1276,12 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { function generateSession(username, roles) { var getKey; - if(config.getItem('dbServer.cloudant')) { + if (config.getItem('dbServer.cloudant')) { getKey = require('./dbauth/cloudant').getAPIKey(userDB); } else { var token = util.URLSafeUUID(); // Make sure our token doesn't start with illegal characters - while(token[0] === '_' || token[0] === '-') { + while (token[0] === '_' || token[0] === '-') { token = util.URLSafeUUID(); } getKey = BPromise.resolve({ @@ -1219,7 +1290,7 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { }); } return getKey - .then(function(key) { + .then(function (key) { var now = Date.now(); return BPromise.resolve({ _id: username, @@ -1237,21 +1308,25 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { base = base.toLowerCase(); var entries = []; var finalName; - return userDB.allDocs({startkey: base, endkey: base + '\uffff', include_docs: false}) - .then(function(results){ - if(results.rows.length === 0) { + return userDB.allDocs({ + startkey: base, + endkey: base + '\uffff', + include_docs: false + }) + .then(function (results) { + if (results.rows.length === 0) { return BPromise.resolve(base); } - for(var i=0; i -1 || indexOpposeDb > -1 ) { - if(approach === 'override'){ - if(indexOpposeDb > -1){ - userOpposedbList.splice(indexOpposeDb,1); - } - if(indesDb >-1){ - defaultOpposedDBs.splice(indesDb,1); - } - }else{ - var index; - if(type === 'private'){ - index = customPrivateDBs.indexOf(nameDB); - if (index > -1) { - customPrivateDBs.splice(index, 1); - } - index = defaultPrivateDBs.indexOf(nameDB); - if(index > -1){ - defaultPrivateDBs.splice(index); - } - index = userdbList.indexOf(nameDB); - if (index > -1) { - userdbList.splice(index,1); - } - }else{ - index = customSharedDBs.indexOf(nameDB); - if (index > -1) { - customSharedDBs.splice(index, 1); - } - index = defaultSharedDBs.indexOf(nameDB); - if(index > -1){ - defaultSharedDBs.splice(index); - } - index = userdbList.indexOf(nameDB); - if (index > -1) { - userdbList.splice(index,1); - } - } - nameDB=nameDB+"_"+type; - } + if (indesDb > -1 || indexOpposeDb > -1) { + if (approach === 'override') { + if (indexOpposeDb > -1) { + userOpposedbList.splice(indexOpposeDb, 1); + } + if (indesDb > -1) { + defaultOpposedDBs.splice(indesDb, 1); + } + } else { + var index; + if (type === 'private') { + index = customPrivateDBs.indexOf(nameDB); + if (index > -1) { + customPrivateDBs.splice(index, 1); } - if(!userdbList.includes(nameDB)) { - userdbList.push(nameDB); + index = defaultPrivateDBs.indexOf(nameDB); + if (index > -1) { + defaultPrivateDBs.splice(index); } - }); - if(type === 'private'){ - defaultSharedDBs.concat(customSharedDBs).filter(function (element) { - if(!userOpposedbList.includes(element) && !userOpposedbList.includes(element+"_shared")) { - userOpposedbList.push(element); - } - }); - }else{ - defaultPrivateDBs.concat(customPrivateDBs).filter(function (element) { - if(!userOpposedbList.includes(element) && !userOpposedbList.includes(element+"_private")) { - userOpposedbList.push(element); - } - }); + index = userdbList.indexOf(nameDB); + if (index > -1) { + userdbList.splice(index, 1); + } + } else { + index = customSharedDBs.indexOf(nameDB); + if (index > -1) { + customSharedDBs.splice(index, 1); + } + index = defaultSharedDBs.indexOf(nameDB); + if (index > -1) { + defaultSharedDBs.splice(index); + } + index = userdbList.indexOf(nameDB); + if (index > -1) { + userdbList.splice(index, 1); + } + } + nameDB = nameDB + "_" + type; + } + } + if (!userdbList.includes(nameDB)) { + userdbList.push(nameDB); + } + }); + if (type === 'private') { + defaultSharedDBs.concat(customSharedDBs).filter(function (element) { + if (!userOpposedbList.includes(element) && !userOpposedbList.includes(element + "_shared")) { + userOpposedbList.push(element); + } + }); + } else { + defaultPrivateDBs.concat(customPrivateDBs).filter(function (element) { + if (!userOpposedbList.includes(element) && !userOpposedbList.includes(element + "_private")) { + userOpposedbList.push(element); } + }); } + } function addUserDBs(newUser) { - // Add personal DBs - if (!config.getItem('userDBs.defaultDBs')) { - return BPromise.resolve(newUser); - } - var promises = []; - newUser.personalDBs = {}; - - var processUserDBs = function (dbList, type) { - dbList.forEach(function (userDBName) { - var dbConfig = dbAuth.getDBConfig(userDBName); - promises.push( - dbAuth.addUserDB(newUser, userDBName, dbConfig.designDocs, type, dbConfig.permissions, dbConfig.adminRoles, - dbConfig.memberRoles) - .then(function (finalDBName) { - delete dbConfig.permissions; - delete dbConfig.adminRoles; - delete dbConfig.memberRoles; - delete dbConfig.designDocs; - dbConfig.type = type; - newUser.personalDBs[finalDBName] = dbConfig; - })); - }); - }; + // Add personal DBs + if (!config.getItem('userDBs.defaultDBs')) { + return BPromise.resolve(newUser); + } + var promises = []; + newUser.personalDBs = {}; - var customUserDBs = config.getItem('userDBs.customDBs'); - if (!Array.isArray(customUserDBs)) { - customUserDBs = []; - } - // Just in case defaultDBs is not specified - var defaultPrivateDBs = config.getItem('userDBs.defaultDBs.private'); - if (!Array.isArray(defaultPrivateDBs)) { - defaultPrivateDBs = []; - } + var processUserDBs = function (dbList, type) { + dbList.forEach(function (userDBName) { + var dbConfig = dbAuth.getDBConfig(userDBName); + promises.push( + dbAuth.addUserDB(newUser, userDBName, dbConfig.designDocs, type, dbConfig.permissions, dbConfig.adminRoles, + dbConfig.memberRoles) + .then(function (finalDBName) { + delete dbConfig.permissions; + delete dbConfig.adminRoles; + delete dbConfig.memberRoles; + delete dbConfig.designDocs; + dbConfig.type = type; + newUser.personalDBs[finalDBName] = dbConfig; + })); + }); + }; - var defaultSharedDBs = config.getItem('userDBs.defaultDBs.shared'); - if (!Array.isArray(defaultSharedDBs)) { - defaultSharedDBs = []; - } + var customUserDBs = config.getItem('userDBs.customDBs'); + if (!Array.isArray(customUserDBs)) { + customUserDBs = []; + } + // Just in case defaultDBs is not specified + var defaultPrivateDBs = config.getItem('userDBs.defaultDBs.private'); + if (!Array.isArray(defaultPrivateDBs)) { + defaultPrivateDBs = []; + } - var userPrivateDBs= []; - var userSharedDBs = []; - var roleUserList = newUser.roles; - var customrol = false; - customUserDBs.forEach(function (element) { - if(roleUserList.includes(element.roles)) { - var customPrivateDBs = element.DBs.private || []; - var customSharedDBs = element.DBs.shared || []; - var approach = element.approach || 'override'; - addCustomUserDBs(approach, userPrivateDBs, userSharedDBs, customPrivateDBs, customSharedDBs, defaultPrivateDBs, defaultSharedDBs, 'private'); - addCustomUserDBs(approach, userSharedDBs, userPrivateDBs, customPrivateDBs, customSharedDBs, defaultPrivateDBs, defaultSharedDBs, 'shared'); - customrol = true; - } - }); - if(!customrol){ - userPrivateDBs = defaultPrivateDBs; - userSharedDBs = defaultSharedDBs; - } + var defaultSharedDBs = config.getItem('userDBs.defaultDBs.shared'); + if (!Array.isArray(defaultSharedDBs)) { + defaultSharedDBs = []; + } - //console.log(userPrivateDBs); - //console.log(userSharedDBs); + var userPrivateDBs = []; + var userSharedDBs = []; + var roleUserList = newUser.roles; + var customrol = false; + customUserDBs.forEach(function (element) { + if (roleUserList.includes(element.roles)) { + var customPrivateDBs = element.DBs.private || []; + var customSharedDBs = element.DBs.shared || []; + var approach = element.approach || 'override'; + addCustomUserDBs(approach, userPrivateDBs, userSharedDBs, customPrivateDBs, customSharedDBs, defaultPrivateDBs, defaultSharedDBs, 'private'); + addCustomUserDBs(approach, userSharedDBs, userPrivateDBs, customPrivateDBs, customSharedDBs, defaultPrivateDBs, defaultSharedDBs, 'shared'); + customrol = true; + } + }); + if (!customrol) { + userPrivateDBs = defaultPrivateDBs; + userSharedDBs = defaultSharedDBs; + } - processUserDBs(userPrivateDBs, 'private'); - processUserDBs(userSharedDBs, 'shared'); + processUserDBs(userPrivateDBs, 'private'); + processUserDBs(userSharedDBs, 'shared'); - return BPromise.all(promises).then(function () { - return BPromise.resolve(newUser); - }); - } + return BPromise.all(promises).then(function () { + return BPromise.resolve(newUser); + }); + } return this; -}; +}; \ No newline at end of file From 872160385b81364d7a8263065eec8106a89e9347 Mon Sep 17 00:00:00 2001 From: Jesus Rabelo Date: Tue, 15 Nov 2016 09:21:22 -0500 Subject: [PATCH 06/17] add final-test to gulpfile --- gulpfile.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index a570d460..0bba7e65 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -40,9 +40,9 @@ gulp.task('customdbs-test', ['user-test'], function () { .pipe(mocha({timeout: 2000})); }); -//gulp.task('final-test', ['user-test'], function () { -// return gulp.src(['test/test.js'], {read: false}) -// .pipe(mocha({timeout: 2000})); -//}); +gulp.task('final-test', ['user-test'], function () { + return gulp.src(['test/test.js'], {read: false}) + .pipe(mocha({timeout: 2000})); +}); -gulp.task('default', ['customdbs-test','user-test', 'mailer-test', 'session-test', 'middleware-test', 'lint']); +gulp.task('default', ['final-test','customdbs-test','user-test', 'mailer-test', 'session-test', 'middleware-test', 'lint']); From 72c7f3f2a0985f7e4ff4b4560edf85fe19be9fc4 Mon Sep 17 00:00:00 2001 From: Martin Fraser Date: Wed, 13 Sep 2017 14:45:16 +0100 Subject: [PATCH 07/17] Added example config and explaination for using random UID --- config.example.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.example.js b/config.example.js index 83d98428..98574996 100644 --- a/config.example.js +++ b/config.example.js @@ -41,6 +41,9 @@ module.exports = { confirmEmailRedirectURL: '/', // Set this to true to disable usernames and use emails instead emailUsername: false, + // If this is more then zero a random ID of that length will be used in place of the username. This number must + // be even and must be big enough to allow it to be unique. I would suggest at least 16. + randomUIDLength: 0, // Custom names for the username and password fields in your sign-in form usernameField: 'user', passwordField: 'pass', From bcfe6ae112ed966bae1d6302b719e8f3e1ebc2c7 Mon Sep 17 00:00:00 2001 From: Martin Fraser Date: Wed, 13 Sep 2017 14:45:20 +0100 Subject: [PATCH 08/17] Add option to create random _id for users --- lib/user.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/user.js b/lib/user.js index be7c418e..7b334013 100644 --- a/lib/user.js +++ b/lib/user.js @@ -29,6 +29,8 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { var emailUsername = config.getItem('local.emailUsername'); + var randomUsername = config.getItem('local.randomUsername'); + this.validateUsername = function (username) { if (!username) { @@ -270,6 +272,9 @@ module.exports = function (config, userDB, couchAuthDB, mailer, emitter) { if (emailUsername) { newUser._id = newUser.email; } + if (typeof (randomUsername) === 'number') { + newUser._id = require("crypto").randomBytes(Math.floor(randomUsername / 2)).toString('hex'); + } if (config.getItem('local.sendConfirmEmail')) { newUser.unverifiedEmail = { email: newUser.email, From d3980f2db07292c7b0bcf3d77c42f1aff0be48ba Mon Sep 17 00:00:00 2001 From: Jason Jarrett Date: Sun, 8 Jan 2017 13:25:12 -0800 Subject: [PATCH 09/17] add window message listener to auth-callback template --- templates/oauth/auth-callback.ejs | 35 +++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/templates/oauth/auth-callback.ejs b/templates/oauth/auth-callback.ejs index 44b65639..28fc6b29 100644 --- a/templates/oauth/auth-callback.ejs +++ b/templates/oauth/auth-callback.ejs @@ -7,16 +7,39 @@ \ No newline at end of file From ef420d0083408d551a32f98438276862fc9963de Mon Sep 17 00:00:00 2001 From: Anielkis Herrera Date: Sat, 13 Jan 2018 19:02:57 -0500 Subject: [PATCH 10/17] Uses versioned version of couch-pwd-updated instead of pointing directly to master of couch-pwd. --- lib/util.js | 78 +++++++++++++++++++++++++++------------------------- package.json | 4 +-- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/lib/util.js b/lib/util.js index 9a4db6ac..bab25263 100644 --- a/lib/util.js +++ b/lib/util.js @@ -3,14 +3,14 @@ var BPromise = require('bluebird'); var URLSafeBase64 = require('urlsafe-base64'); var uuid = require('uuid'); -var pwd = require('couch-pwd'); +var pwd = require('couch-pwd-updated'); var crypto = require('crypto'); -exports.URLSafeUUID = function() { +exports.URLSafeUUID = function () { return URLSafeBase64.encode(uuid.v4(null, new Buffer(16))); }; -exports.hashToken = function(token) { +exports.hashToken = function (token) { return crypto.createHash('sha256').update(token).digest('hex'); }; @@ -29,14 +29,16 @@ exports.hashPassword = function (password) { }; exports.verifyPassword = function (hashObj, password) { - var getHash = BPromise.promisify(pwd.hash, {context: pwd}); + var getHash = BPromise.promisify(pwd.hash, { + context: pwd + }); var iterations = hashObj.iterations; var salt = hashObj.salt; var derived_key = hashObj.derived_key; if (iterations) { pwd.iterations(iterations); } - if(!salt || !derived_key) { + if (!salt || !derived_key) { return BPromise.reject(false); } return getHash(password, salt) @@ -49,9 +51,9 @@ exports.verifyPassword = function (hashObj, password) { }); }; -exports.getDBURL = function(db) { +exports.getDBURL = function (db) { var url; - if(db.user) { + if (db.user) { url = db.protocol + encodeURIComponent(db.user) + ':' + encodeURIComponent(db.password) + '@' + db.host; } else { url = db.protocol + db.host; @@ -59,32 +61,32 @@ exports.getDBURL = function(db) { return url; }; -exports.getFullDBURL = function(dbConfig, dbName) { +exports.getFullDBURL = function (dbConfig, dbName) { return exports.getDBURL(dbConfig) + '/' + dbName; }; -exports.toArray = function(obj) { - if(!(obj instanceof Array)) { +exports.toArray = function (obj) { + if (!(obj instanceof Array)) { obj = [obj]; } return obj; }; -exports.getSessions = function(userDoc) { +exports.getSessions = function (userDoc) { var sessions = []; - if(userDoc.session) { - Object.keys(userDoc.session).forEach(function(mySession) { + if (userDoc.session) { + Object.keys(userDoc.session).forEach(function (mySession) { sessions.push(mySession); }); } return sessions; }; -exports.getExpiredSessions = function(userDoc, now) { +exports.getExpiredSessions = function (userDoc, now) { var sessions = []; - if(userDoc.session) { - Object.keys(userDoc.session).forEach(function(mySession) { - if(userDoc.session[mySession].expires <= now) { + if (userDoc.session) { + Object.keys(userDoc.session).forEach(function (mySession) { + if (userDoc.session[mySession].expires <= now) { sessions.push(mySession); } }); @@ -93,7 +95,7 @@ exports.getExpiredSessions = function(userDoc, now) { }; // Takes a req object and returns the bearer token, or undefined if it is not found -exports.getSessionToken = function(req) { +exports.getSessionToken = function (req) { if (req.headers && req.headers.authorization) { var parts = req.headers.authorization.split(' '); if (parts.length == 2) { @@ -101,7 +103,7 @@ exports.getSessionToken = function(req) { var credentials = parts[1]; if (/^Bearer$/i.test(scheme)) { var parse = credentials.split(':'); - if(parse.length < 2) { + if (parse.length < 2) { return; } return parse[0]; @@ -111,9 +113,9 @@ exports.getSessionToken = function(req) { }; // Generates views for each registered provider in the user design doc -exports.addProvidersToDesignDoc = function(config, ddoc) { +exports.addProvidersToDesignDoc = function (config, ddoc) { var providers = config.getItem('providers'); - if(!providers) { + if (!providers) { return ddoc; } var ddocTemplate = @@ -122,14 +124,14 @@ exports.addProvidersToDesignDoc = function(config, ddoc) { " emit(doc.%PROVIDER%.profile.id, null);\n" + " }\n" + "}"; - Object.keys(providers).forEach(function(provider) { + Object.keys(providers).forEach(function (provider) { ddoc.auth.views[provider] = ddocTemplate.replace(new RegExp('%PROVIDER%', 'g'), provider); }); return ddoc; }; // Capitalizes the first letter of a string -exports.capitalizeFirstLetter = function(string) { +exports.capitalizeFirstLetter = function (string) { return string.charAt(0).toUpperCase() + string.slice(1); }; @@ -142,9 +144,9 @@ exports.capitalizeFirstLetter = function(string) { * @return {object|undefined} a reference to the requested key or undefined if not found */ -exports.getObjectRef = function(obj, str) { +exports.getObjectRef = function (obj, str) { str = str.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties - str = str.replace(/^\./, ''); // strip a leading dot + str = str.replace(/^\./, ''); // strip a leading dot var pList = str.split('.'); while (pList.length) { var n = pList.shift(); @@ -167,19 +169,19 @@ exports.getObjectRef = function(obj, str) { * @return {*} the value the reference was set to */ -exports.setObjectRef = function(obj, str, val) { +exports.setObjectRef = function (obj, str, val) { str = str.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties - str = str.replace(/^\./, ''); // strip a leading dot + str = str.replace(/^\./, ''); // strip a leading dot var pList = str.split('.'); var len = pList.length; - for(var i = 0; i < len-1; i++) { + for (var i = 0; i < len - 1; i++) { var elem = pList[i]; - if( !obj[elem] ) { + if (!obj[elem]) { obj[elem] = {}; } obj = obj[elem]; } - obj[pList[len-1]] = val; + obj[pList[len - 1]] = val; return val; }; @@ -191,19 +193,19 @@ exports.setObjectRef = function(obj, str, val) { * @return {boolean} true if successful */ -exports.delObjectRef = function(obj, str) { +exports.delObjectRef = function (obj, str) { str = str.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties - str = str.replace(/^\./, ''); // strip a leading dot + str = str.replace(/^\./, ''); // strip a leading dot var pList = str.split('.'); var len = pList.length; - for(var i = 0; i < len-1; i++) { + for (var i = 0; i < len - 1; i++) { var elem = pList[i]; - if( !obj[elem] ) { + if (!obj[elem]) { return false; } obj = obj[elem]; } - delete obj[pList[len-1]]; + delete obj[pList[len - 1]]; return true; }; @@ -217,9 +219,9 @@ exports.delObjectRef = function(obj, str) { exports.arrayUnion = function (a, b) { var result = a.concat(b); - for(var i=0; i Date: Sat, 13 Jan 2018 19:07:19 -0500 Subject: [PATCH 11/17] fix --- config.example.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.example.js b/config.example.js index 98574996..eeec32c4 100644 --- a/config.example.js +++ b/config.example.js @@ -48,7 +48,7 @@ module.exports = { usernameField: 'user', passwordField: 'pass', // Override default constraints - passwordConstraints = { + passwordConstraints: { length: { minimum: 6, message: "must be at least 6 characters" @@ -133,7 +133,7 @@ module.exports = { }, // These are settings for each personal database model: { - // If your database is not listed below, these default settings will be applied + // If your database is not listed below, these default settings will be applied _default: { // Array containing name of the design doc files (omitting .js extension), in the directory configured below designDocs: ['mydesign'], @@ -194,4 +194,4 @@ module.exports = { } } } -}; +}; \ No newline at end of file From 47d7cfed5ffe61961003d1559406f54d1c7b9d76 Mon Sep 17 00:00:00 2001 From: Peter Uithoven Date: Fri, 12 May 2017 12:30:38 +0200 Subject: [PATCH 12/17] design doc with validate_doc_update example I was having a hard time figuring out how to add a `validate_doc_update` to a design doc, this would have answered my question. --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 53b752d9..82e36beb 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,19 @@ SuperLogin also allows you to specify default `_security` roles for members and CouchDB can save your API a lot of traffic by handling both reads and writes. CouchDB provides the [validate_doc_update function](http://guide.couchdb.org/draft/validation.html) to approve or disapprove what gets written. However, since your CouchDB users are temporary random API keys, you have no idea which user is requesting to write. SuperLogin has inserted the original `user_id` into `userCtx.roles[0]`, prefixed by `user:` (e.g. `user:superman`). +Example design doc: +``` js +module.exports = { + validator: { + validate_doc_update: function (newDoc, oldDoc, userCtx) { + if (!newDoc.name) { + throw({forbidden: 'doc.name is required'}); + } + }.toString() + } +}; +``` + If you are using Cloudant authentication, the prefixed `user_id` is inserted as the first item on the `permissions` array, which will also appear inside `roles` in your `userCtx` object. You will also find all the `roles` from your user doc here. If you wish to give a user special Cloudant permissions other than the ones specified in your config, you can edit the user doc from the `sl-users` database and under `personalDBs` add an array called `permissions` under the corresponding DB for that user. From 226998add79cc3e5c3e74893394c2065c596c091 Mon Sep 17 00:00:00 2001 From: Anielkis Herrera Date: Sat, 13 Jan 2018 19:22:02 -0500 Subject: [PATCH 13/17] Update api to pouchdb >= 6.0 from https://github.com/pouchdb-community/pouchdb-authentication/commit/ef607b507eb5426aab0dbdf98bcdcfd1f173a4d3 --- lib/dbauth/cloudant.js | 91 +++++++++++++++++++++++------------------- package.json | 3 +- 2 files changed, 53 insertions(+), 41 deletions(-) diff --git a/lib/dbauth/cloudant.js b/lib/dbauth/cloudant.js index 14b08eb2..82c53ed4 100644 --- a/lib/dbauth/cloudant.js +++ b/lib/dbauth/cloudant.js @@ -1,27 +1,28 @@ 'use strict'; var url = require('url'); +var urlParse = require('url-parse'); var BPromise = require('bluebird'); var request = require('superagent'); var util = require('./../util'); // This is not needed with Cloudant -exports.storeKey = function() { +exports.storeKey = function () { return BPromise.resolve(); }; // This is not needed with Cloudant -exports.removeKeys = function() { +exports.removeKeys = function () { return BPromise.resolve(); }; // This is not needed with Cloudant -exports.initSecurity = function() { +exports.initSecurity = function () { return BPromise.resolve(); }; -exports.authorizeKeys = function(user_id, db, keys, permissions, roles) { +exports.authorizeKeys = function (user_id, db, keys, permissions, roles) { var keysObj = {}; - if(!permissions) { + if (!permissions) { permissions = ['_reader', '_replicator']; } permissions = permissions.concat(roles || []); @@ -29,8 +30,8 @@ exports.authorizeKeys = function(user_id, db, keys, permissions, roles) { // If keys is a single value convert it to an Array keys = util.toArray(keys); // Check if keys is an array and convert it to an object - if(keys instanceof Array) { - keys.forEach(function(key) { + if (keys instanceof Array) { + keys.forEach(function (key) { keysObj[key] = permissions; }); } else { @@ -38,36 +39,36 @@ exports.authorizeKeys = function(user_id, db, keys, permissions, roles) { } // Pull the current _security doc return getSecurityCloudant(db) - .then(function(secDoc) { - if(!secDoc._id) { + .then(function (secDoc) { + if (!secDoc._id) { secDoc._id = '_security'; } - if(!secDoc.cloudant) { + if (!secDoc.cloudant) { secDoc.cloudant = {}; } - Object.keys(keysObj).forEach(function(key) { + Object.keys(keysObj).forEach(function (key) { secDoc.cloudant[key] = keysObj[key]; }); return putSecurityCloudant(db, secDoc); }); }; -exports.deauthorizeKeys = function(db, keys) { +exports.deauthorizeKeys = function (db, keys) { // cast keys to an Array keys = util.toArray(keys); return getSecurityCloudant(db) - .then(function(secDoc) { + .then(function (secDoc) { var changes = false; - if(!secDoc.cloudant) { + if (!secDoc.cloudant) { return BPromise.resolve(false); } - keys.forEach(function(key) { - if(secDoc.cloudant[key]) { + keys.forEach(function (key) { + if (secDoc.cloudant[key]) { changes = true; delete secDoc.cloudant[key]; } }); - if(changes) { + if (changes) { return putSecurityCloudant(db, secDoc); } else { return BPromise.resolve(false); @@ -75,18 +76,18 @@ exports.deauthorizeKeys = function(db, keys) { }); }; -exports.getAPIKey = function(db) { - var parsedUrl = url.parse(db.getUrl()); +exports.getAPIKey = function (db) { + var parsedUrl = url.parse(getBaseUrl(db)); parsedUrl.pathname = '/_api/v2/api_keys'; var finalUrl = url.format(parsedUrl); - return BPromise.fromNode(function(callback) { - request.post(finalUrl) - .set(db.getHeaders()) - .end(callback); - }) - .then(function(res) { + return BPromise.fromNode(function (callback) { + request.post(finalUrl) + .set(db.getHeaders()) + .end(callback); + }) + .then(function (res) { var result = JSON.parse(res.text); - if(result.key && result.password && result.ok === true) { + if (result.key && result.password && result.ok === true) { return BPromise.resolve(result); } else { return BPromise.reject(result); @@ -96,31 +97,41 @@ exports.getAPIKey = function(db) { var getSecurityCloudant = exports.getSecurityCloudant = function (db) { var finalUrl = getSecurityUrl(db); - return BPromise.fromNode(function(callback) { - request.get(finalUrl) - .set(db.getHeaders()) - .end(callback); - }) - .then(function(res) { + return BPromise.fromNode(function (callback) { + request.get(finalUrl) + .set(db.getHeaders()) + .end(callback); + }) + .then(function (res) { return BPromise.resolve(JSON.parse(res.text)); }); }; var putSecurityCloudant = exports.putSecurityCloudant = function (db, doc) { var finalUrl = getSecurityUrl(db); - return BPromise.fromNode(function(callback) { - request.put(finalUrl) - .set(db.getHeaders()) - .send(doc) - .end(callback); - }) - .then(function(res) { + return BPromise.fromNode(function (callback) { + request.put(finalUrl) + .set(db.getHeaders()) + .send(doc) + .end(callback); + }) + .then(function (res) { return BPromise.resolve(JSON.parse(res.text)); }); }; function getSecurityUrl(db) { - var parsedUrl = url.parse(db.getUrl()); + var parsedUrl = url.parse(getBaseUrl(db)); parsedUrl.pathname = parsedUrl.pathname + '_security'; return url.format(parsedUrl); +} + +function getBaseUrl(db) { + if (typeof db.getUrl === 'function') { // pouchdb pre-6.0.0 + return urlParse(db.getUrl()).origin; + } else if (db.__opts && db.__opts.prefix) { // PouchDB.defaults + return db.__opts.prefix; + } else { // pouchdb post-6.0.0 + return urlParse(db.name).origin; + } } \ No newline at end of file diff --git a/package.json b/package.json index c198182f..0c39f308 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "redis": "^2.5.2", "sofa-model": "^0.2.0", "superagent": "^1.2.0", + "url-parse": "^1.2.0", "urlsafe-base64": "1.0.0", "uuid": "^3.0.0" }, @@ -66,4 +67,4 @@ "sinon": "^1.17.4", "sinon-chai": "^2.8.0" } -} \ No newline at end of file +} From 3dabb295203a04d19a41459a921133ffea61e7dc Mon Sep 17 00:00:00 2001 From: Anielkis Herrera Date: Sat, 13 Jan 2018 19:30:23 -0500 Subject: [PATCH 14/17] pouchdb 6.3.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0c39f308..f8030bc7 100644 --- a/package.json +++ b/package.json @@ -67,4 +67,4 @@ "sinon": "^1.17.4", "sinon-chai": "^2.8.0" } -} +} \ No newline at end of file From 190e033e34a9dafaf6cba9a413115950ea140702 Mon Sep 17 00:00:00 2001 From: Anielkis Herrera Date: Sat, 13 Jan 2018 20:23:44 -0500 Subject: [PATCH 15/17] pouchdb 5.4.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8030bc7..cee35a13 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "passport": "^0.3.2", "passport-http-bearer-sl": "^1.0.1", "passport-local": "^1.0.0", - "pouchdb": "~6.3.4", + "pouchdb": "~5.4.5", "pouchdb-seed-design": "^0.2.0", "redis": "^2.5.2", "sofa-model": "^0.2.0", From 575a525693c75b805792cdaaa1e7567a9114dacf Mon Sep 17 00:00:00 2001 From: Anielkis Herrera Date: Sat, 13 Jan 2018 21:15:49 -0500 Subject: [PATCH 16/17] fix --- lib/dbauth/cloudant.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/dbauth/cloudant.js b/lib/dbauth/cloudant.js index 82c53ed4..57509f3b 100644 --- a/lib/dbauth/cloudant.js +++ b/lib/dbauth/cloudant.js @@ -128,10 +128,13 @@ function getSecurityUrl(db) { function getBaseUrl(db) { if (typeof db.getUrl === 'function') { // pouchdb pre-6.0.0 - return urlParse(db.getUrl()).origin; + // console.log("db.getUrl() = " + JSON.stringify(db.getUrl())); + return db.getUrl(); } else if (db.__opts && db.__opts.prefix) { // PouchDB.defaults + // console.log("db.__opts.prefix = " + JSON.stringify(db.__opts.prefix)); return db.__opts.prefix; } else { // pouchdb post-6.0.0 + // console.log("urlParse(db.name(" + db.name + ")).origin = " + JSON.stringify(urlParse(db.name).origin)); return urlParse(db.name).origin; } } \ No newline at end of file From 533c3f43ee6ba0080e1da1bf8356ebd1a00f53e0 Mon Sep 17 00:00:00 2001 From: Anielkis Herrera Date: Sat, 13 Jan 2018 22:08:34 -0500 Subject: [PATCH 17/17] debug --- lib/dbauth/cloudant.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/dbauth/cloudant.js b/lib/dbauth/cloudant.js index 57509f3b..e22143d8 100644 --- a/lib/dbauth/cloudant.js +++ b/lib/dbauth/cloudant.js @@ -92,6 +92,8 @@ exports.getAPIKey = function (db) { } else { return BPromise.reject(result); } + }, function (err) { + console.log("Error getAPIKey(" + finalUrl + "): " + JSON.stringify(err)); }); }; @@ -104,6 +106,8 @@ var getSecurityCloudant = exports.getSecurityCloudant = function (db) { }) .then(function (res) { return BPromise.resolve(JSON.parse(res.text)); + }, function (err) { + console.log("Error getSecurityCloudant(" + finalUrl + "): " + JSON.stringify(err)); }); }; @@ -117,6 +121,8 @@ var putSecurityCloudant = exports.putSecurityCloudant = function (db, doc) { }) .then(function (res) { return BPromise.resolve(JSON.parse(res.text)); + }, function (err) { + console.log("Error putSecurityCloudant(" + finalUrl + "): " + JSON.stringify(err)); }); };