From 1f08eafc3dbe49815477e93a344c2b36f3e64780 Mon Sep 17 00:00:00 2001 From: Nicolas Jaremek Date: Tue, 17 Mar 2015 11:45:46 +0100 Subject: [PATCH 01/31] Added fixtures script to add users to DB --- package.json | 3 +- scripts/add_users.js | 100 +++++++++++++++++++++++++++++++++++++++ tests/fixtures/User.json | 23 +++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 scripts/add_users.js create mode 100644 tests/fixtures/User.json diff --git a/package.json b/package.json index fc4eec5..0423d5a 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,8 @@ "int": "DEBUG=cipherlayer* node main.js", "pro": "DEBUG=cipherlayer* node main.js", "lint": "./node_modules/.bin/gulp jshint", - "lint-go": "./node_modules/.bin/gulp jshint-go" + "lint-go": "./node_modules/.bin/gulp jshint-go", + "fixtures": "node scripts/add_users.js" }, "repository": { "type": "git", diff --git a/scripts/add_users.js b/scripts/add_users.js new file mode 100644 index 0000000..daf65da --- /dev/null +++ b/scripts/add_users.js @@ -0,0 +1,100 @@ +var async = require('async'), + fs = require('fs'), + nock = require('nock'), + userMng = require('../src/managers/user'), + config = require('../config.json'), + userDao = require('../src/managers/dao.js'); +/* + * Objects for `async.eachSeries` + */ + +// Function to apply to each fixture +var addFixture = function(fixture, callback) { + + var data = fixture; + + // Define user object to be passed to userMng + var pin = null; + var profileBody = { + id: data._id.$oid || data._id, + email: data.email, + password: data.password, + phone: data.phone || '111111', + country: data.country || 'US' + }; + + if(!profileBody.id || !profileBody.email || !profileBody.password) { + console.log("Missing mandatory parameter(s)"); + return callback(); + } + // Nock the createUser URL + nock('http://' + config.private_host + ':' + config.private_port + config.passThroughEndpoint.path, { reqheaders: { + 'Content-Type': 'application/json; charset=utf-8' + }}) + .post(config.passThroughEndpoint.path) + .reply(201,profileBody); + + // Save user data to database + userMng().createUser(profileBody, pin, function(err) { + if(err) { + + if (err.err === 'auth_proxy_user_error') { + console.log(profileBody.email + " " + err.des); + return callback(); + } + return callback(err); + } + console.log(profileBody.email + " added"); + return callback(); + }); + +}; + +/* + * Main part of the script: + * - Exports the function, or + * - Executes the function if running from CLI + */ +var runLoadFixtures = module.exports = function(fixtureFile, callback) { + + console.log("running Load Fixtures"); + + + async.eachSeries(fixtureFile, addFixture, callback); + +}; + +if (!module.parent) { // Run as CLI command exec + async.series([ + + // Start cipherLayer components (mongodb, redis...) + function connect(done) { + userDao.connect(done); + }, + + function drop(done) { + if(!process.env.DROP_DB) return done(); + console.log("Dropping database"); + userDao.deleteAllUsers(done); + }, + + function load(done) { + fixtureFile = require(__dirname + '/' + '../tests/fixtures/' + 'User.json'); + runLoadFixtures(fixtureFile,done); + }, + + function disconnect(done) { + userDao.disconnect(done); + } + + ], function(err) { + if (err) { + console.error(err); + process.exit(1); + } + + console.info('Fixtures loaded'); + process.exit(); + }); + +} diff --git a/tests/fixtures/User.json b/tests/fixtures/User.json new file mode 100644 index 0000000..b2631f3 --- /dev/null +++ b/tests/fixtures/User.json @@ -0,0 +1,23 @@ +[ + { + "_id": {"$oid": "01f0000000000000003f0004"}, + "phone": "555-7891-2365", + "email": "nick@intelygenz.com", + "password": "1234", + "country": "PL" + }, + { + "_id": {"$oid": "01f0000000000000003f0002"}, + "phone": "555-8899-1324", + "email": "gustavo@intelygenz.com", + "password": "asdf", + "country": "AR" + }, + { + "_id": {"$oid": "01f0000000000000003f0003"}, + "phone": "555-0012-7453", + "email": "josemanuel@intelygenz.com", + "password": "abcd", + "country": "ES" + } +] \ No newline at end of file From 01f6def22b26a4b314668d9a7f583f005d557a20 Mon Sep 17 00:00:00 2001 From: Nicolas Jaremek Date: Tue, 17 Mar 2015 13:03:42 +0100 Subject: [PATCH 02/31] Added env var for default password. Added default password for users. --- scripts/add_users.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/add_users.js b/scripts/add_users.js index daf65da..984aebe 100644 --- a/scripts/add_users.js +++ b/scripts/add_users.js @@ -18,9 +18,7 @@ var addFixture = function(fixture, callback) { var profileBody = { id: data._id.$oid || data._id, email: data.email, - password: data.password, - phone: data.phone || '111111', - country: data.country || 'US' + password: data.password || (process.env.DEFAULT_PASS ? process.env.DEFAULT_PASS : "qwerty") }; if(!profileBody.id || !profileBody.email || !profileBody.password) { From 2fdd81e3ee9a328bc48881cb8433501a7d756edc Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Wed, 8 Apr 2015 13:23:09 +0200 Subject: [PATCH 03/31] added x-user-id header on directproxy request --- src/middlewares/propagateRequest.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/middlewares/propagateRequest.js b/src/middlewares/propagateRequest.js index af335cd..522645f 100644 --- a/src/middlewares/propagateRequest.js +++ b/src/middlewares/propagateRequest.js @@ -32,6 +32,8 @@ function propagateRequest(req, res, next){ // if url is a direct proxy request, use http-proxy if (useDirectProxy) { + // add user id to proxy request headers + req.headers['x-user-id'] = req.options.headers['x-user-id']; proxy.web(req, res, { target: 'http://'+ config.private_host + ':' + config.private_port }); From 50918ce4ed24b1692ec11baf8b3fc630d77f09ae Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Tue, 21 Apr 2015 13:32:08 +0200 Subject: [PATCH 04/31] added verify old password functionality --- config_sample.json | 1 + src/managers/user.js | 25 ++++++++++++++++++++++++- src/routes/user.js | 44 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/config_sample.json b/config_sample.json index a784791..b11bf89 100644 --- a/config_sample.json +++ b/config_sample.json @@ -104,6 +104,7 @@ "*@a.com" ], "password":{ + "validateOldPassword": false, "regexValidation": "(?=.*\\d)(?=.*[A-Z])(?=.*[a-z]).{8}", "message": "Your password must be at least 8 characters and must contain at least one capital, one lower and one number.", "generatedRegex": "([a-z][\\d][A-Z]){3,4}", diff --git a/src/managers/user.js b/src/managers/user.js index 380a32c..779cea6 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -312,6 +312,28 @@ function setPassword(id, body, cbk){ } } +function validateOldPassword(username, oldPassword, cbk) { + + userDao.getAllUserFields(username, function(err, user) { + if (err) { + res.send(401, err); + return next(); + } + + cryptoMng.encrypt(oldPassword, function(encrypted){ + if (user.password !== encrypted) { + return cbk({ + err: 'invalid_old_password', + des: 'invalid password', + code: 401 + }); + } + + return cbk(); + }); + }); +} + //Aux functions function random (howMany, chars) { chars = chars || "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789"; @@ -357,6 +379,7 @@ module.exports = function(settings) { setPlatformData : setPlatformData, createUser : createUser, createUserByToken : createUserByToken, - setPassword: setPassword + setPassword: setPassword, + validateOldPassword: validateOldPassword }; }; diff --git a/src/routes/user.js b/src/routes/user.js index d92ecb8..2bb1639 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -138,15 +138,49 @@ function createUserByToken(req, res, next) { }); } -function setPassword(req, res, next){ - if(!req.body){ - res.send(400, { +function checkBody(req, res, next) { + var err; + if (!req.body){ + err = { err: 'invalid_body', des: 'The call to this url must have body.' - } ); + }; + res.send(400, err); + return next(false); + } + + return next(); +} + +function validateOldPassword(req, res, next) { + var err; + if (!config.password.validateOldPassword) { return next(); } + if (!req.body.oldPassword) { + err = { + err: 'missing_password', + des: 'Missing old password validation' + }; + res.send(400, err); + return next(false); + } + + debug('validating old password', req.user.password, req.body); + + userMng().validateOldPassword(req.user.username, req.body.oldPassword, function(err){ + if (err) { + res.send(401, err); + return next(false); + } + return next(); + }); + +} + +function setPassword(req, res, next){ + userMng().setPassword(req.user._id, req.body, function(err, modified){ if (err) { if (!err.code ) { @@ -170,7 +204,7 @@ function addRoutes(service){ service.post(config.passThroughEndpoint.path, createUserEndpoint); service.get('/user/activate', createUserByToken); - service.put('/user/me/password', checkAccessTokenParam, checkAuthHeader, decodeToken, findUser, setPassword); + service.put('/user/me/password', checkAccessTokenParam, checkAuthHeader, decodeToken, checkBody, findUser, validateOldPassword, setPassword); debug('User routes added'); } From 1aca4bb589f33d9bd06f476a418108686fdbf116 Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Tue, 21 Apr 2015 16:13:33 +0200 Subject: [PATCH 05/31] temporally skipped tests userAppVersion --- tests/userAppVersion.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/userAppVersion.js b/tests/userAppVersion.js index 413325d..6817ecb 100644 --- a/tests/userAppVersion.js +++ b/tests/userAppVersion.js @@ -44,7 +44,7 @@ describe('middleware userAppVersion', function(){ ], done); }); - it('update (user has no appVersion)', function(done){ + it.skip('update (user has no appVersion)', function(done){ userDao.addUser()(baseUser, function(err, createdUser) { var req = { headers: {}, @@ -70,7 +70,7 @@ describe('middleware userAppVersion', function(){ }); }); - it('update (different appVersion)', function(done){ + it.skip('update (different appVersion)', function(done){ baseUser.appVersion = 'version 1.0.0'; userDao.addUser()(baseUser, function(err, createdUser) { var req = { @@ -141,4 +141,4 @@ describe('middleware userAppVersion', function(){ userAppVersion(settings)(req, res, next); }); -}); \ No newline at end of file +}); From 9ca1c3e86b606110a1f1e56536cac97f75274eb6 Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Tue, 21 Apr 2015 17:03:42 +0200 Subject: [PATCH 06/31] Revert "Added middleware "userAppVersion" (update user info with his app version)" This reverts commit fdcc89d76f3edda9a8baf17d8b2bbed1044c561f. Conflicts: src/cipherlayer.js src/middlewares/userAppVersion.js tests/userAppVersion.js --- src/cipherlayer.js | 11 ++- src/middlewares/userAppVersion.js | 36 -------- tests/userAppVersion.js | 144 ------------------------------ 3 files changed, 5 insertions(+), 186 deletions(-) delete mode 100644 src/middlewares/userAppVersion.js delete mode 100644 tests/userAppVersion.js diff --git a/src/cipherlayer.js b/src/cipherlayer.js index dc546f5..bb67970 100644 --- a/src/cipherlayer.js +++ b/src/cipherlayer.js @@ -23,8 +23,7 @@ var bodyParserWrapper = require('./middlewares/bodyParserWrapper.js'); var versionControl = require('version-control'); -var pinValidation = require('./middlewares/pinValidation.js')(); -var userAppVersion = require('./middlewares/userAppVersion.js')(); +var pinValidation = require('./middlewares/pinValidation.js'); var jsonValidator = require('./managers/json_validator'); var configSchema = require('../config_schema.json'); @@ -100,10 +99,10 @@ function startListener(publicPort, privatePort, cbk){ require(platformsPath + filename).addRoutes(server, passport); }); - server.get(/(.*)/, checkAccessTokenParam, checkAuthHeader, decodeToken, permissions, findUser, pinValidation, userAppVersion, prepareOptions, platformsSetUp, printTraces, propagateRequest); - server.post(/(.*)/, checkAccessTokenParam, checkAuthHeader, decodeToken, permissions, findUser, pinValidation, userAppVersion, prepareOptions, platformsSetUp, printTraces, propagateRequest); - server.del(/(.*)/, checkAccessTokenParam, checkAuthHeader, decodeToken, permissions, findUser, pinValidation, userAppVersion, prepareOptions, platformsSetUp, printTraces, propagateRequest); - server.put(/(.*)/, checkAccessTokenParam, checkAuthHeader, decodeToken, permissions, findUser, pinValidation, userAppVersion, prepareOptions, platformsSetUp, printTraces, propagateRequest); + server.get(/(.*)/, checkAccessTokenParam, checkAuthHeader, decodeToken, permissions, findUser, prepareOptions, platformsSetUp, printTraces, propagateRequest, pinValidation); + server.post(/(.*)/, checkAccessTokenParam, checkAuthHeader, decodeToken, permissions, findUser, prepareOptions, platformsSetUp, printTraces, propagateRequest, pinValidation); + server.del(/(.*)/, checkAccessTokenParam, checkAuthHeader, decodeToken, permissions, findUser,prepareOptions, platformsSetUp, printTraces, propagateRequest, pinValidation); + server.put(/(.*)/, checkAccessTokenParam, checkAuthHeader, decodeToken, permissions, findUser, prepareOptions, platformsSetUp, printTraces, propagateRequest, pinValidation); server.use(function(req, res, next){ debug('< ' + res.statusCode); diff --git a/src/middlewares/userAppVersion.js b/src/middlewares/userAppVersion.js deleted file mode 100644 index 926205e..0000000 --- a/src/middlewares/userAppVersion.js +++ /dev/null @@ -1,36 +0,0 @@ -var debug = require('debug')('cipherlayer:userAppVersion'); -var userDao = require('../managers/dao'); -var _ = require('lodash'); - -var config = require('../../config.json'); - -var updatingUserError = { - err:'proxy_error', - des:'error updating user appVersion' -}; - -var defaultSettings = config; -var _settings = {}; - -function storeUserAppVersion(req, res, next){ - if(!req.headers[_settings.version.header] || req.user.appVersion === req.headers[_settings.version.header]) { - return next(); - } else { - debug('appVersion [' + req.headers[_settings.version.header] + '] must be updated for the user [' + req.user._id + ']'); - userDao.updateField(req.user._id, 'appVersion', req.headers[_settings.version.header], function(err, updatedUsers){ - if(err){ - debug('error updating user appVersion ', err); - res.send(500, updatingUserError); - return next(false); - } else { - next(); - } - }); - } -} - -module.exports = function(settings){ - _.extend(_settings, defaultSettings, settings); - - return storeUserAppVersion; -}; diff --git a/tests/userAppVersion.js b/tests/userAppVersion.js deleted file mode 100644 index 6817ecb..0000000 --- a/tests/userAppVersion.js +++ /dev/null @@ -1,144 +0,0 @@ -var assert = require('assert'); -var async = require('async'); - -var userAppVersion = require('../src/middlewares/userAppVersion.js'); -var userDao = require('../src/managers/dao'); - -var config = require('../config.json'); - -describe('middleware userAppVersion', function(){ - var settings = { - "version" : { - "header" : "x-mycomms-version", - "platforms" : { - "test" : { - "link" : "http://testLink", - "1" : true - } - }, - "installPath" : "/install" - } - }; - - var baseUser = { - id:'a1b2c3d4e5f6', - username: 'username' + (config.allowedDomains[0] ? config.allowedDomains[0] : ''), - password: '12345678' - }; - - beforeEach(function(done){ - async.series([ - function(done){ - userDao.connect(function(){ - userDao.deleteAllUsers( done ); - }); - } - ], done); - }); - - afterEach(function(done){ - async.series([ - function(done){ - userDao.disconnect(done); - } - ], done); - }); - - it.skip('update (user has no appVersion)', function(done){ - userDao.addUser()(baseUser, function(err, createdUser) { - var req = { - headers: {}, - url: "/api/me", - method: "GET", - user: createdUser - }; - - req.headers[config.version.header] = 'version 1.0.0'; - - var res = {}; - var next = function(canContinue) { - if (canContinue === undefined || canContinue === true){ - userDao.getFromId(createdUser._id, function(err, foundUser){ - assert.equal(err, null); - assert.equal(foundUser.appVersion, 'version 1.0.0'); - done(); - }); - } - }; - - userAppVersion(settings)(req, res, next); - }); - }); - - it.skip('update (different appVersion)', function(done){ - baseUser.appVersion = 'version 1.0.0'; - userDao.addUser()(baseUser, function(err, createdUser) { - var req = { - headers: {}, - url: "/api/me", - method: "GET", - user: createdUser - }; - - req.headers[config.version.header] = 'version 2.0.0'; - - var res = {}; - var next = function(canContinue) { - if (canContinue === undefined || canContinue === true){ - userDao.getFromId(createdUser._id, function(err, foundUser){ - assert.equal(err, null); - assert.equal(foundUser.appVersion, 'version 2.0.0'); - done(); - }); - } - }; - - userAppVersion(settings)(req, res, next); - }); - }); - - it('continue (same appVersion)', function(done){ - baseUser.appVersion = 'version 1.0.0'; - userDao.addUser()(baseUser, function(err, createdUser) { - var req = { - headers: {}, - url: "/api/me", - method: "GET", - user: createdUser - }; - - req.headers[config.version.header] = 'version 1.0.0'; - - var res = {}; - var next = function(canContinue) { - if (canContinue === undefined || canContinue === true){ - userDao.getFromId(createdUser._id, function(err, foundUser){ - assert.equal(err, null); - assert.equal(foundUser.appVersion, 'version 1.0.0'); - done(); - }); - } - }; - - userAppVersion(settings)(req, res, next); - }); - }); - - it('continue (no version header)', function(done){ - var req = { - headers: {}, - url: "/api/me", - method: "GET", - user: baseUser - }; - - var res = {}; - var next = function(canContinue) { - if (canContinue === undefined || canContinue === true){ - done(); - } - }; - - userAppVersion(settings)(req, res, next); - }); -}); From fef9adf4e76a394efbb7259b5e2b09a585dc744c Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Wed, 9 Sep 2015 15:53:04 +0200 Subject: [PATCH 07/31] fixed package and removed log --- package.json | 2 +- src/cipherlayer.js | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/package.json b/package.json index 69f56e6..7fa36c3 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "redis": "^0.12.1", "request": "^2.47.0", "restify": "^2.8.3", - "version-control": "^1.2.1" + "version-control": "^1.2.4" }, "devDependencies": { "assert": "^1.1.2", diff --git a/src/cipherlayer.js b/src/cipherlayer.js index 27dab9f..d79b71d 100644 --- a/src/cipherlayer.js +++ b/src/cipherlayer.js @@ -88,11 +88,6 @@ function startListener(publicPort, privatePort, cbk){ server.use(restify.queryParser()); server.use(bodyParserWrapper(restify.bodyParser({maxBodySize: 1024 * 1024 * 3}))); - server.use(function(req,res,next){ - debug('> ' + req.method + ' ' + req.url); - next(); - }); - var versionControlOptions = clone(config.version); versionControlOptions.public = [ "/auth/sf", From 9fc9a9cf104c0aabbfbd32a6a1592bf6d6819621 Mon Sep 17 00:00:00 2001 From: Nicolas Jaremek Date: Thu, 24 Sep 2015 15:00:23 +0200 Subject: [PATCH 08/31] Added configuration parameter to define externalNotifications service email endpoint. --- src/managers/email.js | 6 +++--- src/managers/json_validator.js | 8 +++++--- src/managers/user.js | 6 ++++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/managers/email.js b/src/managers/email.js index a295f47..d97e96d 100644 --- a/src/managers/email.js +++ b/src/managers/email.js @@ -7,7 +7,7 @@ var redisMng = require('./redis'); var _settings = {}; function sendEmailVerification(email, subject, emailBody, cbk){ - var notifServiceURL = _settings.externalServices.notifications; + var notifServiceURL = _settings.externalServices.notifications.base; var emailOptions = { to: email, subject: subject, @@ -15,7 +15,7 @@ function sendEmailVerification(email, subject, emailBody, cbk){ }; var options = { - url: notifServiceURL + '/notification/email', + url: notifServiceURL + _settings.externalServices.notifications.email, headers: { 'Content-Type': 'application/json; charset=utf-8' }, @@ -96,7 +96,7 @@ function sendEmailForgotPassword(email, passwd, link, cbk){ }; var options = { - url: _settings.externalServices.notifications + '/notification/email', + url: _settings.externalServices.notifications.base + _settings.externalServices.notifications.email , headers: { 'Content-Type': 'application/json; charset=utf-8' }, diff --git a/src/managers/json_validator.js b/src/managers/json_validator.js index c276556..d23754a 100644 --- a/src/managers/json_validator.js +++ b/src/managers/json_validator.js @@ -5,12 +5,14 @@ module.exports = { if( !json || Object.keys(json).length === 0) { return false; } - if(schema) { + + if (!schema) { + return true; + } var result = (new Validator()).validate(json, schema); if (result.errors.length > 0) { return false; } - } - return true; + return true; } }; \ No newline at end of file diff --git a/src/managers/user.js b/src/managers/user.js index 6f8f616..a6b18c8 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -3,7 +3,7 @@ var request = require('request'); var crypto = require('crypto'); var _ = require('lodash'); var ciphertoken = require('ciphertoken'); - +var config = require(process.cwd() + '/config.json'); var userDao = require('./dao'); var tokenMng = require('./token'); var redisMng = require('./redis'); @@ -154,9 +154,11 @@ function createUserByToken(token, cbk) { } var body = bodyData.data; - var profileSchema = require('./json_formats/profile_create.json'); + var profileSchema = config.validators.profile.mustUse ? require('./json_formats/' + config.validators.profile) : null; + //Validate the current bodyData with the schema profile_create.json if( !jsonValidator.isValidJSON(body, profileSchema) || !body.transactionId) { + return cbk({ err:'invalid_profile_data', des:'The data format provided is nor valid.', From ca8d1ee945809c16c312ea18b50caa8e31099c01 Mon Sep 17 00:00:00 2001 From: Nicolas Jaremek Date: Thu, 24 Sep 2015 15:03:35 +0200 Subject: [PATCH 09/31] Updated tests for new configuration. --- config_sample.json | 52 ++++++++----------- .../method_request_to_path.js | 7 ++- src/managers/phone.js | 2 +- src/managers/user.js | 4 +- tests/email.js | 5 +- tests/managerUser.js | 9 ++-- tests/phone.js | 6 +-- tests/pinValidation.js | 2 +- tests/proxy/protectedCallsPassThrough.js | 2 +- tests/routesUser.js | 11 ++-- tests/verifyPhone.js | 2 +- 11 files changed, 51 insertions(+), 51 deletions(-) diff --git a/config_sample.json b/config_sample.json index 35ea54c..4577080 100644 --- a/config_sample.json +++ b/config_sample.json @@ -7,7 +7,7 @@ "accessToken" : { "cipherKey" : "unsecureKey1", "signKey" : "unsecureKey2", - "expiration" : 10 + "expiration" : 1000 }, "refreshToken" : { "cipherKey" : "unsecureKey3", @@ -19,10 +19,10 @@ "clientSecret": "unsecureKey5" }, "db": { - "conn": "mongodb://127.0.0.1:27017/cipherlayer?w=1" + "conn": "mongodb://mongodb:27017/cipherlayer?w=1" }, "redis":{ - "host":"127.0.0.1", + "host":"redis", "port":6379 }, "passThroughEndpoint" : { @@ -65,6 +65,12 @@ "avatars": "example-avatars" } }, + "validators": { + "profile": { + "mustUse": true, + "filename": "profile_create.json" + } + }, "phoneVerification": { "pinSize": 4, "attempts": 3, @@ -83,7 +89,7 @@ } ] }, - "emailVerification":{ + "emailVerification": { "subject": "Example email verification", "body": "

Thanks for register into Example, here is a link to activate your account click

here

If you have any problems on this process, please contact support@example.com and we will be pleased to help you.

", "compatibleEmailDevices": [ "*iPhone*", "*iPad*", "*iPod*" , "*Android*"], @@ -95,18 +101,25 @@ "scheme":"mycomms" }, "externalServices":{ - "notifications": "http://localhost:3002" + "notifications": { + "base": "http://localhost:3002", + "email": "/api/notification/email" + }, + "useResonator": false }, "version" : { - "header" : "x-example-version", + "header" : "x-app-version", "platforms" : { "test" : { "link" : "http://testLink", "1" : true + }, + "downloader" : { + "link" : "http://testLink", + "1" : true } }, - "installPath" : "/install", - "db":"mongodb://localhost/versionControl?w=1" + "installPath" : "/install" }, "allowedDomains":[ "*@a.com" @@ -119,28 +132,7 @@ "subject" :"Recover Example User Password", "body" : "Here is your new password for accessing to your Example account, if you want, you can update it anytime from your edit profile screen.

__PASSWD__

you can also click here from your mobile device to get in. If you receiver this email by error or you are sure you didn't requested it, please contact support@example.com" }, - "endpoints" : [ - { - "path" : "\/api\/profile", - "methods" : ["POST", "PUT"], - "roles" : ["admin"] - }, - { - "path": "\/.*", - "methods":["GET", "POST", "PUT"], - "roles":["user", "admin"] - } - ], - "accessControlAllow" : [ - { - "enabled" : true, - "methods" : "OPTION,GET,PUT,POST,HEAD", - "headers" : "x-mycomms-version,content-type,Authorization", - "credentials" : "true", - "origin": "http://192.168.1.116:9000" - } - ], "directProxyUrls": [ - "\/upload$" + "\/upload$" ] } diff --git a/features/step_definitions/method_request_to_path.js b/features/step_definitions/method_request_to_path.js index ad13c19..52dd180 100644 --- a/features/step_definitions/method_request_to_path.js +++ b/features/step_definitions/method_request_to_path.js @@ -5,6 +5,9 @@ var nock = require('nock'); var request = require('request'); var assert = require('assert'); +var NOTIFICATION_SERVICE_URL = config.externalServices.notifications.base; +var NOTIFICATION_EMAIL_SERVICE_PATH = config.externalServices.notifications.email; + var myStepDefinitionsWrapper = function () { this.When(/^the client makes a (.*) request to (.*)$/, function (METHOD, PATH, callback) { @@ -18,8 +21,8 @@ var myStepDefinitionsWrapper = function () { }; options.headers[config.version.header] = "test/1"; - nock(config.externalServices.notifications) - .post('/notification/email') + nock(NOTIFICATION_SERVICE_URL) + .post(NOTIFICATION_EMAIL_SERVICE_PATH) .reply(204); request(options, function(err,res) { diff --git a/src/managers/phone.js b/src/managers/phone.js index 0bd82ba..65caeca 100644 --- a/src/managers/phone.js +++ b/src/managers/phone.js @@ -34,7 +34,7 @@ function createPIN(redisKeyId, phone, cbk){ } function sendPIN(phone, pin, cbk){ - var notifServiceURL = _settings.externalServices.notifications; + var notifServiceURL = _settings.externalServices.notifications.base; var sms = { phone: phone, text: 'MyComms pin code: ' + pin diff --git a/src/managers/user.js b/src/managers/user.js index a6b18c8..bdb4878 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -154,14 +154,14 @@ function createUserByToken(token, cbk) { } var body = bodyData.data; - var profileSchema = config.validators.profile.mustUse ? require('./json_formats/' + config.validators.profile) : null; + var profileSchema = config.validators.profile.mustUse ? require('./json_formats/' + config.validators.profile.filename) : null; //Validate the current bodyData with the schema profile_create.json if( !jsonValidator.isValidJSON(body, profileSchema) || !body.transactionId) { return cbk({ err:'invalid_profile_data', - des:'The data format provided is nor valid.', + des:'The data format provided is not valid.', code: 400 }); } diff --git a/tests/email.js b/tests/email.js index 1a07965..58b2ece 100644 --- a/tests/email.js +++ b/tests/email.js @@ -5,7 +5,8 @@ var nock = require('nock'); var redisMng = require('../src/managers/redis'); var config = require('../config.json'); -var notifServiceURL = config.externalServices.notifications; +var notifServiceURL = config.externalServices.notifications.base; +var notifServicePath = config.externalServices.notifications.email; describe('email', function() { @@ -26,7 +27,7 @@ describe('email', function() { }); nock(notifServiceURL) - .post('/notification/email') + .post(notifServicePath) .reply(204); var email = "test@test.com"; diff --git a/tests/managerUser.js b/tests/managerUser.js index b62b280..27b6106 100644 --- a/tests/managerUser.js +++ b/tests/managerUser.js @@ -11,7 +11,8 @@ var cryptoMng = require('../src/managers/crypto')({ password : 'password' }); var config = require('../config.json'); -var notifServiceURL = config.externalServices.notifications; +var notifServiceURL = config.externalServices.notifications.base; +var notifServicePath = config.externalServices.notifications.email; var accessTokenSettings = { cipherKey: config.accessToken.cipherKey, @@ -195,7 +196,7 @@ describe('user Manager', function(){ .reply(201, {id: expectedUserId}); nock(notifServiceURL) - .post('/notification/email') + .post(notifServicePath) .reply(204); userMng(testsConfigSettings).createUser( profileBody, pin, function(err, tokens){ @@ -225,7 +226,7 @@ describe('user Manager', function(){ .reply(201, {id: expectedUserId}); nock(notifServiceURL) - .post('/notification/email') + .post(notifServicePath) .reply(204); userMng(testsConfigSettings).createUser( profileBody, pin, function(err, tokens){ @@ -545,7 +546,7 @@ describe('user Manager', function(){ var expectedError = { err:"invalid_profile_data", - des:"The data format provided is nor valid.", + des:"The data format provided is not valid.", code:400 }; diff --git a/tests/phone.js b/tests/phone.js index ecef7e5..1c3a707 100644 --- a/tests/phone.js +++ b/tests/phone.js @@ -36,8 +36,7 @@ describe('phone', function() { password : 'validpassword' }; - var notifServiceURL = config.externalServices.notifications; - + var notifServiceURL = config.externalServices.notifications.base; beforeEach(function(done){ async.parallel([ function(done){ @@ -68,7 +67,8 @@ describe('phone', function() { }); it('create pin', function(done){ - nock(notifServiceURL) + + nock(notifServiceURL) .post('/notification/sms') .reply(204); diff --git a/tests/pinValidation.js b/tests/pinValidation.js index 02a2585..e060feb 100644 --- a/tests/pinValidation.js +++ b/tests/pinValidation.js @@ -5,7 +5,7 @@ var config = require('../config.json'); var redisMng = require('../src/managers/redis'); var countries = require('countries-info'); -var notifServiceURL = config.externalServices.notifications; +var notifServiceURL = config.externalServices.notifications.base; describe('middleware pinValidation', function(){ diff --git a/tests/proxy/protectedCallsPassThrough.js b/tests/proxy/protectedCallsPassThrough.js index af9bcc5..12f2940 100644 --- a/tests/proxy/protectedCallsPassThrough.js +++ b/tests/proxy/protectedCallsPassThrough.js @@ -8,7 +8,7 @@ var redisMng = require('../../src/managers/redis'); var dao = require('../../src/managers/dao.js'); var config = require('../../config.json'); -var notificationsServiceURL = config.externalServices.notifications; +var notificationsServiceURL = config.externalServices.notifications.base; module.exports = { itCreated: function created(accessTokenSettings, refreshTokenSettings){ diff --git a/tests/routesUser.js b/tests/routesUser.js index b75932e..7ca13b8 100644 --- a/tests/routesUser.js +++ b/tests/routesUser.js @@ -18,6 +18,8 @@ var accessTokenSettings = { }; var AUTHORIZATION; +var NOTIFICATION_SERVICE_URL = config.externalServices.notifications.base; +var NOTIFICATION_EMAIL_SERVICE_PATH = config.externalServices.notifications.email; var createdUserId; @@ -73,10 +75,11 @@ describe('user', function () { }, method: 'GET' }; + options.headers[config.version.header] = "test/1"; - nock(config.externalServices.notifications) - .post('/notification/email') + nock(NOTIFICATION_SERVICE_URL) + .post(NOTIFICATION_EMAIL_SERVICE_PATH) .reply(201); request(options, function (err, res, body) { @@ -108,8 +111,8 @@ describe('user', function () { }; options.headers[config.version.header] = "test/1"; - nock(config.externalServices.notifications) - .post('/notification/email') + nock(NOTIFICATION_SERVICE_URL) + .post(NOTIFICATION_EMAIL_SERVICE_PATH) .times(2) .reply(204); diff --git a/tests/verifyPhone.js b/tests/verifyPhone.js index 68dfe75..d5cddc8 100644 --- a/tests/verifyPhone.js +++ b/tests/verifyPhone.js @@ -15,7 +15,7 @@ var redisMng = require('../src/managers/redis'); describe('/api/profile (verify phone)', function(){ - var notifServiceURL = config.externalServices.notifications; + var notifServiceURL = config.externalServices.notifications.base; var baseUser = { email : "valid" + (config.allowedDomains[0] ? config.allowedDomains[0] : ''), From ba61f47f8bc8abb4d83ca40928f71575c1bfb1d6 Mon Sep 17 00:00:00 2001 From: Nicolas Jaremek Date: Thu, 24 Sep 2015 16:26:47 +0200 Subject: [PATCH 10/31] Made JSON validator mandatory. Improved configuration file format. --- config_sample.json | 39 +++++++++++++------ .../step_definitions/client_pass_through.js | 2 +- .../method_request_to_path.js | 4 +- src/managers/email.js | 4 +- .../json_formats/profile_downloader.json | 10 +++++ src/managers/user.js | 2 +- tests/email.js | 2 +- tests/managerUser.js | 2 +- tests/routesUser.js | 2 +- 9 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 src/managers/json_formats/profile_downloader.json diff --git a/config_sample.json b/config_sample.json index 4577080..d3a5911 100644 --- a/config_sample.json +++ b/config_sample.json @@ -67,7 +67,7 @@ }, "validators": { "profile": { - "mustUse": true, + "path": "", "filename": "profile_create.json" } }, @@ -103,23 +103,19 @@ "externalServices":{ "notifications": { "base": "http://localhost:3002", - "email": "/api/notification/email" - }, - "useResonator": false + "pathEmail": "/api/notification/email" + } }, "version" : { - "header" : "x-app-version", + "header" : "x-example-version", "platforms" : { "test" : { "link" : "http://testLink", "1" : true }, - "downloader" : { - "link" : "http://testLink", - "1" : true - } - }, - "installPath" : "/install" + "installPath" : "/install", + "db":"mongodb://localhost/versionControl?w=1" + } }, "allowedDomains":[ "*@a.com" @@ -132,6 +128,27 @@ "subject" :"Recover Example User Password", "body" : "Here is your new password for accessing to your Example account, if you want, you can update it anytime from your edit profile screen.

__PASSWD__

you can also click here from your mobile device to get in. If you receiver this email by error or you are sure you didn't requested it, please contact support@example.com" }, + "endpoints" : [ + { + "path" : "\/api\/profile", + "methods" : ["POST", "PUT"], + "roles" : ["admin"] + }, + { + "path": "\/.*", + "methods":["GET", "POST", "PUT"], + "roles":["user", "admin"] + } + ], + "accessControlAllow" : [ + { + "enabled" : true, + "methods" : "OPTION,GET,PUT,POST,HEAD", + "headers" : "x-mycomms-version,content-type,Authorization", + "credentials" : "true", + "origin": "http://192.168.1.116:9000" + } + ], "directProxyUrls": [ "\/upload$" ] diff --git a/features/step_definitions/client_pass_through.js b/features/step_definitions/client_pass_through.js index 529d9ad..fbac0bc 100644 --- a/features/step_definitions/client_pass_through.js +++ b/features/step_definitions/client_pass_through.js @@ -7,7 +7,7 @@ var config = require('../../config.json'); module.exports = function(){ this.When(/^the client makes a pass through (.*) with the following (.*) in the body$/, function (METHOD, PUBLIC_PAYLOAD, callback) { - var notifServiceURL = config.externalServices.notifications; + var notifServiceURL = config.externalServices.notifications.base; var options = { url: 'http://localhost:' + config.public_port + config.passThroughEndpoint.path, diff --git a/features/step_definitions/method_request_to_path.js b/features/step_definitions/method_request_to_path.js index 52dd180..11d427c 100644 --- a/features/step_definitions/method_request_to_path.js +++ b/features/step_definitions/method_request_to_path.js @@ -6,7 +6,7 @@ var request = require('request'); var assert = require('assert'); var NOTIFICATION_SERVICE_URL = config.externalServices.notifications.base; -var NOTIFICATION_EMAIL_SERVICE_PATH = config.externalServices.notifications.email; +var NOTIFICATION_EMAIL_SERVICE_PATH = config.externalServices.notifications.pathEmail; var myStepDefinitionsWrapper = function () { this.When(/^the client makes a (.*) request to (.*)$/, function (METHOD, PATH, callback) { @@ -26,7 +26,7 @@ var myStepDefinitionsWrapper = function () { .reply(204); request(options, function(err,res) { - assert.equal(err,null); + assert.equal(err,null); world.getResponse().statusCode = res.statusCode; callback(); }); diff --git a/src/managers/email.js b/src/managers/email.js index d97e96d..a8c1e8f 100644 --- a/src/managers/email.js +++ b/src/managers/email.js @@ -15,7 +15,7 @@ function sendEmailVerification(email, subject, emailBody, cbk){ }; var options = { - url: notifServiceURL + _settings.externalServices.notifications.email, + url: notifServiceURL + _settings.externalServices.notifications.pathEmail, headers: { 'Content-Type': 'application/json; charset=utf-8' }, @@ -96,7 +96,7 @@ function sendEmailForgotPassword(email, passwd, link, cbk){ }; var options = { - url: _settings.externalServices.notifications.base + _settings.externalServices.notifications.email , + url: _settings.externalServices.notifications.base + _settings.externalServices.notifications.pathEmail , headers: { 'Content-Type': 'application/json; charset=utf-8' }, diff --git a/src/managers/json_formats/profile_downloader.json b/src/managers/json_formats/profile_downloader.json new file mode 100644 index 0000000..908ffe7 --- /dev/null +++ b/src/managers/json_formats/profile_downloader.json @@ -0,0 +1,10 @@ +{ + "id": "/Profile", + "type": "object", + "properties": { + "password": { "type": "string", "required": true }, + "email": { "type": "string", "format": "email", "required": true }, + "name": { "type": "string", "required": true } + }, + "additionalProperties": true +} \ No newline at end of file diff --git a/src/managers/user.js b/src/managers/user.js index bdb4878..e64d282 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -154,7 +154,7 @@ function createUserByToken(token, cbk) { } var body = bodyData.data; - var profileSchema = config.validators.profile.mustUse ? require('./json_formats/' + config.validators.profile.filename) : null; + var profileSchema = _.isEmpty(config.validators.profile.path) ? require('./json_formats/' + config.validators.profile.filename) : config.validators.profile.path; //Validate the current bodyData with the schema profile_create.json if( !jsonValidator.isValidJSON(body, profileSchema) || !body.transactionId) { diff --git a/tests/email.js b/tests/email.js index 58b2ece..2d6cf5e 100644 --- a/tests/email.js +++ b/tests/email.js @@ -6,7 +6,7 @@ var redisMng = require('../src/managers/redis'); var config = require('../config.json'); var notifServiceURL = config.externalServices.notifications.base; -var notifServicePath = config.externalServices.notifications.email; +var notifServicePath = config.externalServices.notifications.pathEmail; describe('email', function() { diff --git a/tests/managerUser.js b/tests/managerUser.js index 27b6106..1fa4061 100644 --- a/tests/managerUser.js +++ b/tests/managerUser.js @@ -12,7 +12,7 @@ var cryptoMng = require('../src/managers/crypto')({ password : 'password' }); var config = require('../config.json'); var notifServiceURL = config.externalServices.notifications.base; -var notifServicePath = config.externalServices.notifications.email; +var notifServicePath = config.externalServices.notifications.pathEmail; var accessTokenSettings = { cipherKey: config.accessToken.cipherKey, diff --git a/tests/routesUser.js b/tests/routesUser.js index 7ca13b8..c315d56 100644 --- a/tests/routesUser.js +++ b/tests/routesUser.js @@ -19,7 +19,7 @@ var accessTokenSettings = { var AUTHORIZATION; var NOTIFICATION_SERVICE_URL = config.externalServices.notifications.base; -var NOTIFICATION_EMAIL_SERVICE_PATH = config.externalServices.notifications.email; +var NOTIFICATION_EMAIL_SERVICE_PATH = config.externalServices.notifications.pathEmail; var createdUserId; From 16cc102150f25b370854619656e90d879c9d035b Mon Sep 17 00:00:00 2001 From: Nicolas Jaremek Date: Wed, 30 Sep 2015 11:36:50 +0200 Subject: [PATCH 11/31] Resetted hostnames for db and redis in config sample. --- config_sample.json | 4 ++-- .../json_formats/profile_downloader.json | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/config_sample.json b/config_sample.json index d3a5911..25cdae5 100644 --- a/config_sample.json +++ b/config_sample.json @@ -19,10 +19,10 @@ "clientSecret": "unsecureKey5" }, "db": { - "conn": "mongodb://mongodb:27017/cipherlayer?w=1" + "conn": "mongodb://127.0.0.1:27017/cipherlayer?w=1" }, "redis":{ - "host":"redis", + "host":"127.0.0.1", "port":6379 }, "passThroughEndpoint" : { diff --git a/src/managers/json_formats/profile_downloader.json b/src/managers/json_formats/profile_downloader.json index 908ffe7..b7e726a 100644 --- a/src/managers/json_formats/profile_downloader.json +++ b/src/managers/json_formats/profile_downloader.json @@ -2,9 +2,19 @@ "id": "/Profile", "type": "object", "properties": { - "password": { "type": "string", "required": true }, - "email": { "type": "string", "format": "email", "required": true }, - "name": { "type": "string", "required": true } + "password": { + "type": "string", + "required": true + }, + "email": { + "type": "string", + "format": "email", + "required": true + }, + "name": { + "type": "string", + "required": true + } }, "additionalProperties": true } \ No newline at end of file From 14cbf9fa2b5c23ff6b67f692e8e0e86f6e20604d Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Tue, 13 Oct 2015 13:49:04 +0200 Subject: [PATCH 12/31] add support to transfer a list of allowedHeaders in the configuration --- config_sample.json | 3 +++ features/proxy.feature | 12 ++++++++++++ .../step_definitions/protected_service_call.js | 2 ++ .../protected_service_definiton.js | 15 ++++++++++++++- .../step_definitions/response_header_content.js | 9 +++++++++ src/middlewares/propagateRequest.js | 17 +++++++++++++++++ 6 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 features/step_definitions/response_header_content.js diff --git a/config_sample.json b/config_sample.json index 25cdae5..ca5724a 100644 --- a/config_sample.json +++ b/config_sample.json @@ -151,5 +151,8 @@ ], "directProxyUrls": [ "\/upload$" + ], + "allowedHeaders": [ + "x-custom-header" ] } diff --git a/features/proxy.feature b/features/proxy.feature index 0f4d432..2704133 100644 --- a/features/proxy.feature +++ b/features/proxy.feature @@ -10,3 +10,15 @@ Feature: reverse proxy protects an applicacion behind cipherlayer | PATH | METHOD | STATUS | REQUEST_PAYLOAD | RESPONSE_PAYLOAD | | /test/get200 | GET | 200 | | {"m":"GET", "s":"200"} | | /test/post200 | POST | 200 | {"key":"value"} | {"m":"POST", "s":"200"} | + + @only + Scenario Outline: A protected service returns a response header + Given a user with role user and a valid access token + And a protected service replies to a request with to with status and a body and header and value + When the application makes a with to a protected + Then the response status code is + And the response body must be + And the response headers contains the with + Examples: + | PATH | METHOD | STATUS | REQUEST_PAYLOAD | RESPONSE_PAYLOAD | ALLOWED_HEADER | HEADER_VALUE | + | /test/get200 | GET | 200 | | {"m":"GET", "s":"200"} | x-custom-header | test | diff --git a/features/step_definitions/protected_service_call.js b/features/step_definitions/protected_service_call.js index 02022bd..9d26bb2 100644 --- a/features/step_definitions/protected_service_call.js +++ b/features/step_definitions/protected_service_call.js @@ -27,6 +27,8 @@ module.exports = function(){ } else { world.getResponse().body = null; } + + world.getResponse().headers = res.headers; callback(); }); }); diff --git a/features/step_definitions/protected_service_definiton.js b/features/step_definitions/protected_service_definiton.js index a252792..73828b2 100644 --- a/features/step_definitions/protected_service_definiton.js +++ b/features/step_definitions/protected_service_definiton.js @@ -14,11 +14,24 @@ module.exports = function(){ callback(); }); + this.Given(/^a protected service replies to a GET request with (.*) to (.*) with status (.*) and a body (.*) and header (.*) and value (.*)$/, function (REQUEST_PAYLOAD, PATH, STATUS, RESPONSE_PAYLOAD, ALLOWED_HEADER, HEADER_VALUE, callback){ + var headers = {}; + headers[ALLOWED_HEADER] = HEADER_VALUE; + nock('http://localhost:'+config.private_port, { + reqheaders: { + 'Content-Type': 'application/json; charset=utf-8', + 'x-user-id' : world.getUser().id + } + }).get(PATH).reply(Number(STATUS), JSON.parse(RESPONSE_PAYLOAD), headers); + + callback(); + }); + this.Given(/^a protected service replies to a POST request with (.*) to (.*) with status (.*) and a body (.*)$/, function (REQUEST_PAYLOAD, PATH, STATUS, RESPONSE_PAYLOAD, callback){ nock('http://localhost:'+config.private_port) .post(PATH, JSON.parse(REQUEST_PAYLOAD)) .reply(Number(STATUS), JSON.parse(RESPONSE_PAYLOAD)); - + callback(); }); diff --git a/features/step_definitions/response_header_content.js b/features/step_definitions/response_header_content.js new file mode 100644 index 0000000..79588de --- /dev/null +++ b/features/step_definitions/response_header_content.js @@ -0,0 +1,9 @@ +var world = require('../support/world'); +var assert = require('assert'); + +module.exports = function(){ + this.Given(/^the response headers contains the (.*) with (.*)$/, function (ALLOWEDHEADER, HEADERVALUE, callback) { + assert.equal(world.getResponse().headers[ALLOWEDHEADER], HEADERVALUE); + callback(); + }); +}; diff --git a/src/middlewares/propagateRequest.js b/src/middlewares/propagateRequest.js index 5fd99a1..1b1cc1c 100644 --- a/src/middlewares/propagateRequest.js +++ b/src/middlewares/propagateRequest.js @@ -73,6 +73,10 @@ function propagateRequest(req, res, next) { }, user: req.user }, 'proxy call'); + + + transferAllowedHeaders(config.allowedHeaders, private_res, res); + if (private_res.statusCode === 302) { res.header('Location', private_res.headers.location); res.send(302); @@ -86,4 +90,17 @@ function propagateRequest(req, res, next) { } } +function transferAllowedHeaders(headers, srcRes, dstRes) { + + if (!headers || !headers.length ) { + return; + } + + _.map(headers, function(header) { + if (srcRes.headers[header]) { + dstRes.header(header, srcRes.headers[header] ); + } + }); +} + module.exports = propagateRequest; From f1e01d8c6e3db01e5c4d20dbb53fd54846a93fb5 Mon Sep 17 00:00:00 2001 From: Nicolas Jaremek Date: Wed, 14 Oct 2015 13:43:18 +0200 Subject: [PATCH 13/31] ADDED optional configuration to redirect to URL on email confirmation endpoint. --- config_sample.json | 3 +- src/routes/user.js | 6 ++++ tests/auth/user.js | 69 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/config_sample.json b/config_sample.json index ca5724a..3cb0abb 100644 --- a/config_sample.json +++ b/config_sample.json @@ -98,7 +98,8 @@ "key":"user.{username}.transaction", "expireInSec": 86400 }, - "scheme":"mycomms" + "scheme":"mycomms", + "redirectUrl": "http://www.google.com" }, "externalServices":{ "notifications": { diff --git a/src/routes/user.js b/src/routes/user.js index 6fdc76e..db5a86a 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -134,6 +134,12 @@ function createUserByToken(req, res, next) { return next(false); } } + + if (config.emailVerification.redirectUrl) { + res.setHeader('Location', config.emailVerification.redirectUrl); + res.send(301); + return next(); + } res.send(200, { msg: config.emailVerification.nonCompatibleEmailMsg } ); return next(); } diff --git a/tests/auth/user.js b/tests/auth/user.js index c1c1a36..8d30111 100644 --- a/tests/auth/user.js +++ b/tests/auth/user.js @@ -4,6 +4,7 @@ var ciphertoken = require('ciphertoken'); var async = require('async'); var crypto = require('crypto'); var nock = require('nock'); +var _ = require('lodash'); var config = require('../../config.json'); var dao = require('../../src/managers/dao.js'); @@ -268,10 +269,12 @@ module.exports = { }); }); - it('Create OK (not an iOS or Android device) ', function(done) { + it('Create OK (not an iOS or Android device) without redirect option ', function(done) { var transactionId = crypto.pseudoRandomBytes(12).toString('hex'); + var thisConfig = _.clone(config); + thisConfig.emailVerification.redirectUrl = null; - var bodyData = { + var bodyData = { firstName: 'Firstname', lastName: 'Lastname', password: password, @@ -281,9 +284,9 @@ module.exports = { transactionId: transactionId }; - var redisKey = config.emailVerification.redis.key; + var redisKey = thisConfig.emailVerification.redis.key; redisKey = redisKey.replace('{username}', bodyData.email); - var redisExp = config.emailVerification.redis.expireInSec; + var redisExp = thisConfig.emailVerification.redis.expireInSec; redisMng.insertKeyValue(redisKey, transactionId, redisExp, function(err) { assert.equal(err, null); @@ -292,22 +295,22 @@ module.exports = { assert.equal(err, null); var options = { - url: 'http://' + config.private_host + ':' + config.public_port + '/user/activate?verifyToken=' + token , + url: 'http://' + thisConfig.private_host + ':' + thisConfig.public_port + '/user/activate?verifyToken=' + token , method:'GET', headers: {}, followRedirect: false }; options.headers['user-agent'] = "Mozilla/5.0"; - nock('http://' + config.private_host + ':' + config.private_port) - .post(config.passThroughEndpoint.path) + nock('http://' + thisConfig.private_host + ':' + thisConfig.private_port) + .post(thisConfig.passThroughEndpoint.path) .reply(201, {id: USER.id}); request(options, function(err, res, body){ assert.equal(err, null); assert.equal(res.statusCode, 200, body); body = JSON.parse(body); - assert.deepEqual(body, { msg : config.emailVerification.nonCompatibleEmailMsg } ); + assert.deepEqual(body, { msg : thisConfig.emailVerification.nonCompatibleEmailMsg } ); done(); }); }); @@ -315,7 +318,55 @@ module.exports = { }); }); - it('No verify token param', function(done) { + it('Create OK (not an iOS or Android device) with redirect option', function(done) { + var transactionId = crypto.pseudoRandomBytes(12).toString('hex'); + var thisConfig = _.clone(config); + thisConfig.emailVerification.redirectUrl = 'http://www.google.com'; + + var bodyData = { + firstName: 'Firstname', + lastName: 'Lastname', + password: password, + country: 'US', + phone: phone, + email: username, + transactionId: transactionId + }; + + var redisKey = thisConfig.emailVerification.redis.key; + redisKey = redisKey.replace('{username}', bodyData.email); + var redisExp = thisConfig.emailVerification.redis.expireInSec; + + redisMng.insertKeyValue(redisKey, transactionId, redisExp, function(err) { + assert.equal(err, null); + + ciphertoken.createToken(tokenSettings, username, null, bodyData, function(err, token){ + assert.equal(err, null); + + var options = { + url: 'http://' + thisConfig.private_host + ':' + thisConfig.public_port + '/user/activate?verifyToken=' + token , + method:'GET', + headers: {}, + followRedirect: false + }; + options.headers['user-agent'] = "Mozilla/5.0"; + + nock('http://' + thisConfig.private_host + ':' + thisConfig.private_port) + .post(thisConfig.passThroughEndpoint.path) + .reply(201, {id: USER.id}); + + request(options, function(err, res, body){ + assert.equal(err, null); + assert.equal(res.statusCode, 301, body); + assert.equal(res.headers.location, thisConfig.emailVerification.redirectUrl); + done(); + }); + }); + + }); + }); + + it('No verify token param', function(done) { var expectedResponseBody = { err: 'auth_proxy_error', des: 'empty param verifyToken' From 971abde12a85e4fe76e8b72eb8320d27ca72fff3 Mon Sep 17 00:00:00 2001 From: Nicolas Jaremek Date: Wed, 21 Oct 2015 16:39:53 +0200 Subject: [PATCH 14/31] Added from field to email endpoints. --- config_sample.json | 1 + src/managers/email.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config_sample.json b/config_sample.json index 3cb0abb..35bef81 100644 --- a/config_sample.json +++ b/config_sample.json @@ -91,6 +91,7 @@ }, "emailVerification": { "subject": "Example email verification", + "from": "hello@example.com", "body": "

Thanks for register into Example, here is a link to activate your account click

here

If you have any problems on this process, please contact support@example.com and we will be pleased to help you.

", "compatibleEmailDevices": [ "*iPhone*", "*iPad*", "*iPod*" , "*Android*"], "nonCompatibleEmailMsg": "Your user has been created correctly, try to access to Example app in your device.", diff --git a/src/managers/email.js b/src/managers/email.js index a8c1e8f..d8a1f06 100644 --- a/src/managers/email.js +++ b/src/managers/email.js @@ -11,7 +11,8 @@ function sendEmailVerification(email, subject, emailBody, cbk){ var emailOptions = { to: email, subject: subject, - html: emailBody + html: emailBody, + from: _settings.emailVerification.from }; var options = { @@ -72,7 +73,6 @@ function emailVerification(email, bodyData, cbk){ var emailText = (_settings.emailVerification.body).replace('{link}', link); var subject = _settings.emailVerification.subject; - //Send verify email sendEmailVerification(email, subject, emailText, function(err){ if (err) { From 3a6214d064f3bafba0e7a249926bf43cd9c22cfa Mon Sep 17 00:00:00 2001 From: Nick Jaremek Date: Thu, 3 Dec 2015 15:46:25 +0100 Subject: [PATCH 15/31] Removed buggy debug line causing a timeout error. --- src/routes/user.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/user.js b/src/routes/user.js index db5a86a..cb09b12 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -175,7 +175,6 @@ function validateOldPassword(req, res, next) { return next(false); } - debug('validating old password', req.user.password, req.body); userMng().validateOldPassword(req.user.username, req.body.oldPassword, function(err){ if (err) { From 4a075bd9caee05bc2547bba98edc2ad4e136b618 Mon Sep 17 00:00:00 2001 From: Nick Jaremek Date: Mon, 14 Dec 2015 12:34:22 +0100 Subject: [PATCH 16/31] Added begin/end characters to username lookup expression for exact match. --- src/managers/dao.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/managers/dao.js b/src/managers/dao.js index 3eac2b8..5567c58 100644 --- a/src/managers/dao.js +++ b/src/managers/dao.js @@ -116,7 +116,7 @@ function getFromUsername(username, cbk){ } function getFromUsernamePassword(username, password, cbk){ - username = new RegExp(escapeRegexp(username.toLowerCase()), "i"); + username = new RegExp("^"+escapeRegexp(username.toLowerCase())+"$", "i"); collection.find({username: username, password: password}, {password:0}, function(err, users){ if(err) { return cbk(err, null); @@ -138,7 +138,7 @@ function getAllUserFields(username, cbk){ if(!username){ return cbk({err:'invalid_username'}, null); } - username = new RegExp(escapeRegexp(username.toLowerCase()), "i"); + username = new RegExp("^"+escapeRegexp(username.toLowerCase())+"$", "i"); collection.find({username: username}, function(err, users){ if(err) { return cbk(err, null); From 6bddeb39ead05df8d63d4627a1d4ae9e468a502b Mon Sep 17 00:00:00 2001 From: Nick Jaremek Date: Mon, 14 Dec 2015 13:31:37 +0100 Subject: [PATCH 17/31] Added Cucumber and Mocha tests to ensure the exact match during user lookup. --- features/login.feature | 6 ++++ .../login_invalid_username.js | 30 +++++++++++++++++++ tests/auth/login.js | 23 ++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 features/step_definitions/login_invalid_username.js diff --git a/features/login.feature b/features/login.feature index a1de2f6..f7460e0 100644 --- a/features/login.feature +++ b/features/login.feature @@ -13,3 +13,9 @@ Feature: client application logs in into a protected backend When the client app requests log in the protected application with invalid credentials Then the response status code is 409 And the response body contains json attribute "err" + + Scenario: client app logs in with incorrect username + Given a user with valid credentials + When the client app requests log in the protected application with username substring + Then the response status code is 409 + And the response body contains json attribute "err" diff --git a/features/step_definitions/login_invalid_username.js b/features/step_definitions/login_invalid_username.js new file mode 100644 index 0000000..1905ae4 --- /dev/null +++ b/features/step_definitions/login_invalid_username.js @@ -0,0 +1,30 @@ +var world = require('../support/world'); +var request = require('request'); +var assert = require('assert'); +var config = require('../../config.json'); + +module.exports = function(){ + this.When(/^the client app requests log in the protected application with username substring/, function (callback) { + var username = world.getUser().username; + console.log('Email', username); + world.getUser().username = username.slice(0, username.length / 2); + + var options = { + url: 'http://localhost:'+config.public_port+'/auth/login', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + method:'POST', + body : JSON.stringify(world.getUser()) + }; + + options.headers[config.version.header] = "test/1"; + + request(options, function(err,res,body) { + assert.equal(err,null); + world.getResponse().statusCode = res.statusCode; + world.getResponse().body = JSON.parse(body); + callback(); + }); + }); +}; diff --git a/tests/auth/login.js b/tests/auth/login.js index 9516930..e1f3f44 100644 --- a/tests/auth/login.js +++ b/tests/auth/login.js @@ -89,6 +89,29 @@ module.exports = { done(); }); }); + + it('POST 409 username substring', function (done) { + var user = clone(baseUser); + var username = user.username; + user.username = username.slice(0, username.length / 2); + var options = { + url: 'http://localhost:' + config.public_port + '/auth/login', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + method: 'POST', + body: JSON.stringify(user) + }; + options.headers[config.version.header] = "test/1"; + + request(options, function (err, res, body) { + assert.equal(err, null); + assert.equal(res.statusCode, 409); + body = JSON.parse(body); + assert.notEqual(body.err, 'invalid_credentials'); + done(); + }); + }); }); describe('Admin /login', function () { From d2f9dac6f11c42b6a4fdb3d8681317f18b473f48 Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Thu, 28 Jan 2016 13:51:53 +0100 Subject: [PATCH 18/31] added configurable email field --- config_sample.json | 1 + config_schema.json | 1 + src/managers/user.js | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config_sample.json b/config_sample.json index d9c7233..fae82f1 100644 --- a/config_sample.json +++ b/config_sample.json @@ -30,6 +30,7 @@ "path": "/api/profile", "recoverPath": "/api/user/:email/password", "username":"email", + "email":"email", "password":"password" }, "salesforce" : { diff --git a/config_schema.json b/config_schema.json index ab5dbda..631b00c 100644 --- a/config_schema.json +++ b/config_schema.json @@ -40,6 +40,7 @@ "properties": { "path": {"type": "string", "required": true}, "username": {"type": "string", "required": true}, + "email": {"type": "string", "required": false}, "password": {"type": "string", "required": true} }, "required": true diff --git a/src/managers/user.js b/src/managers/user.js index 9c1ef99..fb394d9 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -117,7 +117,7 @@ function createUser(body, pin, cbk) { createUserPrivateCall(body, user, cbk); }); } else { - emailMng(_settings).emailVerification(body.email, body, function (err, destinationEmail) { + emailMng(_settings).emailVerification(body[config.passThroughEndpoint.email || 'email' ], body, function (err, destinationEmail) { if(err){ return cbk(err); } From 1ac06b6db6af723eb9d643c72eb9596314771446 Mon Sep 17 00:00:00 2001 From: Nick Jaremek Date: Thu, 28 Jan 2016 17:45:18 +0100 Subject: [PATCH 19/31] Fixed security vulnerability and profile schema loader. --- src/managers/user.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/managers/user.js b/src/managers/user.js index fb394d9..70e379c 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -127,6 +127,7 @@ function createUser(body, pin, cbk) { code: 200 }); } + delete(body[_settings.passThroughEndpoint.password]); createUserPrivateCall(body, user, cbk); }); } @@ -159,7 +160,13 @@ function createUserByToken(token, cbk) { } var body = bodyData.data; - var profileSchema = _.isEmpty(config.validators.profile.path) ? require('./json_formats/' + config.validators.profile.filename) : config.validators.profile.path; + var profileSchema; + + if (!config.validators) { + profileSchema = require('./json_formats/profile_create.json'); + } else { + profileSchema = require((config.validators.profile.path ? config.validators.profile.path : './json_formats/') + config.validators.profile.filename); + } //Validate the current bodyData with the schema profile_create.json if( !jsonValidator.isValidJSON(body, profileSchema) || !body.transactionId) { From 5bd13a6744069b584e967330fb63f6cde876d120 Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Fri, 29 Jan 2016 09:59:31 +0100 Subject: [PATCH 20/31] different approach to remove password from body --- src/managers/user.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/managers/user.js b/src/managers/user.js index 70e379c..603be45 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -127,7 +127,6 @@ function createUser(body, pin, cbk) { code: 200 }); } - delete(body[_settings.passThroughEndpoint.password]); createUserPrivateCall(body, user, cbk); }); } @@ -229,13 +228,15 @@ function createUserByToken(token, cbk) { } function createUserPrivateCall(body, user, cbk){ + var clonedBody = _.clone(body); + delete clonedBody.password; var options = { url: 'http://' + _settings.private_host + ':' + _settings.private_port + _settings.passThroughEndpoint.path, headers: { 'Content-Type': 'application/json; charset=utf-8' }, method: 'POST', - body: JSON.stringify(body) + body: JSON.stringify(clonedBody) }; log.info('=> POST ' + options.url); From a34e69422db52ece5a96c05730dd0d25d2d3b441 Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Fri, 29 Jan 2016 12:39:02 +0100 Subject: [PATCH 21/31] fixed redis key to avoid undefined values --- src/managers/email.js | 8 +++++--- src/managers/user.js | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/managers/email.js b/src/managers/email.js index d8a1f06..913a6ae 100644 --- a/src/managers/email.js +++ b/src/managers/email.js @@ -4,6 +4,8 @@ var ciphertoken = require('ciphertoken'); var crypto = require('crypto'); var redisMng = require('./redis'); +var config = require(process.cwd() + '/config.json'); + var _settings = {}; function sendEmailVerification(email, subject, emailBody, cbk){ @@ -48,7 +50,7 @@ function emailVerification(email, bodyData, cbk){ var transactionId = crypto.pseudoRandomBytes(12).toString('hex'); var redisKey = _settings.emailVerification.redis.key; - redisKey = redisKey.replace('{username}', bodyData.email); + redisKey = redisKey.replace('{username}', bodyData[config.passThroughEndpoint.email || 'email' ]); var redisExp = _settings.emailVerification.redis.expireInSec; redisMng.insertKeyValue(redisKey, transactionId, redisExp, function(err) { @@ -64,7 +66,7 @@ function emailVerification(email, bodyData, cbk){ tokenExpirationMinutes: redisExp }; - ciphertoken.createToken(tokenSettings, bodyData.email, null, bodyData, function(err, token){ + ciphertoken.createToken(tokenSettings, bodyData[config.passThroughEndpoint.email || 'email' ], null, bodyData, function(err, token){ if(err){ return cbk(err); } @@ -125,4 +127,4 @@ module.exports = function(settings) { emailVerification: emailVerification, sendEmailForgotPassword:sendEmailForgotPassword }; -}; \ No newline at end of file +}; diff --git a/src/managers/user.js b/src/managers/user.js index 603be45..5121abd 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -178,7 +178,7 @@ function createUserByToken(token, cbk) { } //Verify the transactionId var redisKey = _settings.emailVerification.redis.key; - redisKey = redisKey.replace('{username}', body.email); + redisKey = redisKey.replace('{username}', body[config.passThroughEndpoint.email || 'email' ]); redisMng.getKeyValue(redisKey, function(err, transactionId) { if(err){ From 8ed779b79c2cda7211690f0f55e08c963414d170 Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Fri, 29 Jan 2016 14:58:45 +0100 Subject: [PATCH 22/31] added autologin after email verification --- src/public_routes/user.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/public_routes/user.js b/src/public_routes/user.js index e93cd6f..23485e1 100644 --- a/src/public_routes/user.js +++ b/src/public_routes/user.js @@ -190,6 +190,11 @@ function createUserByToken(req, res, next) { } } + if(req.method === 'POST') { + res.send(200, tokens); + return next(); + } + if (config.emailVerification.redirectUrl) { res.setHeader('Location', config.emailVerification.redirectUrl); res.send(301); @@ -272,6 +277,7 @@ function addRoutes(service) { service.post(config.passThroughEndpoint.path, createUserEndpoint); service.get('/user/activate', createUserByToken); + service.post('/user/activate', createUserByToken); service.put('/user/me/password', checkAccessTokenParam, checkAuthHeader, decodeToken, checkBody, findUser, validateOldPassword, setPassword); } From 12566c35484e008ebfc46bb1f96431c394400dc7 Mon Sep 17 00:00:00 2001 From: Nick Jaremek Date: Mon, 1 Feb 2016 13:00:54 +0100 Subject: [PATCH 23/31] Stringify body in prepareOptions middleware only if body exists, in order to avoid sending a body with content equal to "" --- src/middlewares/prepareOptions.js | 4 +- tests/prepareOptions.js | 157 ++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 tests/prepareOptions.js diff --git a/src/middlewares/prepareOptions.js b/src/middlewares/prepareOptions.js index b8b292e..163869e 100644 --- a/src/middlewares/prepareOptions.js +++ b/src/middlewares/prepareOptions.js @@ -36,7 +36,9 @@ function prepareOptions (req, res, next){ options.formData = formData; } else { options.headers['Content-Type'] = req.header('Content-Type'); - options.body = JSON.stringify(req.body); + if(req.body) { + options.body = JSON.stringify(req.body); + } } req.options = options; return next(); diff --git a/tests/prepareOptions.js b/tests/prepareOptions.js new file mode 100644 index 0000000..8fe6fc8 --- /dev/null +++ b/tests/prepareOptions.js @@ -0,0 +1,157 @@ +var assert = require('assert'); +var sinon = require('sinon'); +var prepareOptions = require('./../src/middlewares/prepareOptions'); +var _ = require('lodash'); +var fs = require('fs'); +var request; +var response; + +var fsStub; + +describe('prepareOptions middleware: ', function() { + + before(function(done) { + fsStub = sinon.stub(fs, 'createReadStream'); + return done(); + }); + + after(function(done) { + fsStub.restore(); + return done(); + }); + + beforeEach(function(done) { + + request = { + headers: {} + }; + response = {}; + response.body = {}; + response.send = function(status, message) { + response.body.status = status; + response.body.message = message; + return; + }; + request.header = function(item) { + return request.headers[item.toLowerCase()]; + }; + + return done(); + }); + + it('POST request content type is application/json', function(done) { + + _.extend(request, { + method: 'POST', + headers: { + 'content-type': 'application/json; charset=utf-8', + 'host': 'localhost:3000' + }, + tokenInfo: { + userId: '1234567890' + }, + connection: { + remoteAddress: '::ffff:127.0.0.1' + }, + body: { + item1: 'value1', + item2: 'value2' + }, + files: { + file1: 'stuff', + file2: 'moreStuff' + } + }); + + prepareOptions(request, response, function(error) { + assert.equal(error, undefined); + assert.notEqual(request.options, undefined); + assert.equal(request.options.headers['Content-Type'], request.headers['content-type']); + assert.ok(request.options.body); + assert.equal(request.options.body, JSON.stringify(request.body)); + return done(); + }); + }); + + it('GET request with content type application/json does not have a body in options', function(done) { + + _.extend(request, { + method: 'GET', + headers: { + 'content-type': 'application/json; charset=utf-8', + 'host': 'localhost:3000' + }, + tokenInfo: { + userId: '1234567890' + }, + connection: { + remoteAddress: '::ffff:127.0.0.1' + } + }); + + prepareOptions(request, response, function(error) { + assert.equal(error, undefined); + assert.notEqual(request.options, undefined); + assert.equal(request.options.headers['Content-Type'], request.headers['content-type']); + assert.equal(request.options.body, undefined); + return done(); + }); + }); + + it('DELETE request with content type application/json does not have a body in options', function(done) { + + _.extend(request, { + method: 'DELETE', + headers: { + 'content-type': 'application/json; charset=utf-8', + 'host': 'localhost:3000' + }, + tokenInfo: { + userId: '1234567890' + }, + connection: { + remoteAddress: '::ffff:127.0.0.1' + } + }); + + prepareOptions(request, response, function(error) { + assert.equal(error, undefined); + assert.notEqual(request.options, undefined); + assert.equal(request.options.headers['Content-Type'], request.headers['content-type']); + assert.equal(request.options.body, undefined); + return done(); + }); + }); + + it('request content type is multipart/form-data', function(done) { + + _.extend(request, { + headers: { + 'content-type': 'multipart/form-data', + 'host': 'localhost:3000' + }, + tokenInfo: { + userId: '1234567890' + }, + connection: { + remoteAddress: '::ffff:127.0.0.1' + }, + body: { + item1: 'value1', + item2: 'value2' + }, + files: { + file1: 'stuff', + file2: 'moreStuff' + } + }); + + prepareOptions(request, response, function(error) { + assert.equal(error, undefined); + assert.notEqual(request.options, undefined); + assert.notEqual(request.options.formData, undefined); + assert.equal(fsStub.calledTwice, true); + return done(); + }); + }); +}); \ No newline at end of file From ee430d2e7ffe6abea8c28222070d963c4a4f8e5b Mon Sep 17 00:00:00 2001 From: Nick Jaremek Date: Wed, 3 Feb 2016 13:50:26 +0100 Subject: [PATCH 24/31] Added public endpoint to check email availability. --- src/cipherlayer.js | 3 +- src/managers/dao.js | 25 +++++++++- src/public_routes/user.js | 27 ++++++++++- tests/emailAvailable.js | 97 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 tests/emailAvailable.js diff --git a/src/cipherlayer.js b/src/cipherlayer.js index 6bb6611..7e31e66 100644 --- a/src/cipherlayer.js +++ b/src/cipherlayer.js @@ -102,7 +102,8 @@ function startListener(publicPort, internalPort, cbk){ "/auth/google", "/auth/google/*", "/user/activate*", - "/heartbeat" + "/heartbeat", + "/user/email/available" ]; publicServer.use(versionControl(versionControlOptions)); diff --git a/src/managers/dao.js b/src/managers/dao.js index 37060d4..1d89ca5 100644 --- a/src/managers/dao.js +++ b/src/managers/dao.js @@ -6,6 +6,7 @@ var escapeRegexp = require('escape-regexp'); var config = require(process.cwd() + '/config.json'); var mongoClient = require('mongodb').MongoClient; var ObjectID = require('mongodb').ObjectID; +var _ = require('lodash'); var ERROR_USER_NOT_FOUND = 'user_not_found'; var ERROR_USERNAME_ALREADY_EXISTS = 'username_already_exists'; @@ -117,6 +118,28 @@ function countUsers(cbk){ }); } +function findByEmail(email, callback) { + + var targetEmail = new RegExp("^"+escapeRegexp(email.toLowerCase())+"$", "i"); + + + usersCollection.find({username: targetEmail}, {password: 0}).toArray(function(error, foundUsers) { + + if (error) { + return callback({statusCode: 500, body: { + err: 'InternalError', + des: 'User lookup failed' + }}); + } + + if (_.isEmpty(foundUsers)) { + return callback(null, {available: true}); + } + + return callback(null, {available: false}); + }); +} + function getFromUsername(username, cbk){ if(!username){ return cbk({err:'invalid_username'}, null); @@ -346,6 +369,6 @@ module.exports = { getRealms: getRealms, resetRealmsVariables: resetRealmsVariables, deleteAllRealms: deleteAllRealms, - + findByEmail:findByEmail, getStatus: getStatus }; diff --git a/src/public_routes/user.js b/src/public_routes/user.js index 23485e1..3768150 100644 --- a/src/public_routes/user.js +++ b/src/public_routes/user.js @@ -13,6 +13,8 @@ var checkAccessTokenParam = require('../middlewares/accessTokenParam.js'); var checkAuthHeader = require('../middlewares/authHeader.js'); var decodeToken = require('../middlewares/decodeToken.js'); var findUser = require('../middlewares/findUser.js'); +var _ = require('lodash'); +var log = require('../logger/service.js'); function sendNewPassword(req, res, next) { if (!req.params.email) { @@ -272,13 +274,36 @@ function setPassword(req, res, next) { }); } +function checkEmailAvailable(req, res, next) { + var email = req.body.email; + + if (_.isEmpty(email)) { + res.send(400, { + err: 'BadRequestError', + des: 'Missing email in request body' + }); + return next(); + } + + + daoMng.findByEmail(email, function(error, output) { + if (error) { + res.send(error.statusCode, error.body); + return next(); + } + + res.send(200, output); + return next(); + }); +} + function addRoutes(service) { service.get('/user/:email/password', sendNewPassword); service.post(config.passThroughEndpoint.path, createUserEndpoint); service.get('/user/activate', createUserByToken); service.post('/user/activate', createUserByToken); - + service.post('/user/email/available', checkEmailAvailable); service.put('/user/me/password', checkAccessTokenParam, checkAuthHeader, decodeToken, checkBody, findUser, validateOldPassword, setPassword); } diff --git a/tests/emailAvailable.js b/tests/emailAvailable.js new file mode 100644 index 0000000..1acfc4c --- /dev/null +++ b/tests/emailAvailable.js @@ -0,0 +1,97 @@ +var assert = require('assert'); +var request = require('request'); + +var cipherlayer = require('../src/cipherlayer'); +var config = require('../config.json'); +var userDao = require('../src/managers/dao'); + +var baseUser = { + id: 'a1b2c3d4e5f6', + username: 'user@example.com', + password: 'pass1' +}; + +describe('Check Email Available endpoint', function() { + + beforeEach(function(done) { + cipherlayer.start(config.public_port, config.internal_port, function(error) { + assert.equal(error, null); + userDao.deleteAllUsers(function(error) { + assert.equal(error, null); + return done(); + }); + }); + }); + + afterEach(function(done) { + cipherlayer.stop(done); + }); + + it('should indicate that requested email is available', function(done) { + + var requestOptions = { + url: 'http://localhost:' + config.public_port + '/user/email/available', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + method: 'POST', + json: true, + body: { + email: baseUser.username + } + }; + + request(requestOptions, function(err, res, body) { + assert.equal(err, null); + assert.equal(res.statusCode, 200); + assert.equal(body.available, true); + return done(); + }); + }); + + it('should indicate that requested email is unavailable', function(done) { + var requestOptions = { + url: 'http://localhost:' + config.public_port + '/user/email/available', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + method: 'POST', + json: true, + body: { + email: baseUser.username + } + }; + + userDao.addUser()(baseUser, function(error) { + + assert.equal(error, null); + + request(requestOptions, function(err, res, body) { + assert.equal(err, null); + assert.equal(res.statusCode, 200); + assert.equal(body.available, false); + return done(); + }); + }); + }); + + it('should return a BadRequestError on missing email component', function(done) { + var requestOptions = { + url: 'http://localhost:' + config.public_port + '/user/email/available', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + method: 'POST', + json: true, + body: {} + }; + + request(requestOptions, function(err, res) { + assert.equal(err, null); + assert.equal(res.statusCode, 400); + assert.equal(res.body.err, 'BadRequestError'); + assert.equal(res.body.des, 'Missing email in request body'); + return done(); + }); + }); +}); \ No newline at end of file From 2ab164a9c4b961fbc6f50f58fbbee2219871b225 Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Thu, 4 Feb 2016 13:21:56 +0100 Subject: [PATCH 25/31] WIP login with facebook --- src/managers/user.js | 9 ++ src/public_routes/auth_facebook.js | 202 +++++++++++++++++++++++++++++ tests/auth.js | 2 + tests/auth/facebook_token.js | 78 +++++++++++ 4 files changed, 291 insertions(+) create mode 100644 src/public_routes/auth_facebook.js create mode 100644 tests/auth/facebook_token.js diff --git a/src/managers/user.js b/src/managers/user.js index 5121abd..6369db3 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -97,6 +97,15 @@ function createUser(body, pin, cbk) { return cbk(err); } + if (body.fb) { + user.platforms = [{ + platform: 'fb', + accessToken: body.fb.accessToken + }]; + delete body.fb; + createUserPrivateCall(body, user, cbk); + return; + } if (body.sf) { delete(body[_settings.passThroughEndpoint.password]); tokenMng.getAccessTokenInfo(body.sf, function (err, tokenInfo) { diff --git a/src/public_routes/auth_facebook.js b/src/public_routes/auth_facebook.js new file mode 100644 index 0000000..51f82d1 --- /dev/null +++ b/src/public_routes/auth_facebook.js @@ -0,0 +1,202 @@ +var request = require('request'); +var crypto = require('crypto'); +var _ = require('lodash'); + +var log = require('../logger/service.js'); +var daoMng = require('../managers/dao'); +var userMng = require('../managers/user')(); +var tokenMng = require('../managers/token'); +var config = require(process.cwd() + '/config.json'); + +var defaultOptions = { + url: 'https://graph.facebook.com/v2.5/me', + json: true, + method: 'GET', + qs: { + fields: config.facebook.requestFields, + format: 'json', + method: 'get', + pretty: 0, + suppress_http_code: 1 + } +}; + +function mapFacebookData(body, fieldsMap) { + var mappedData = {}; + + if(!fieldsMap) return mappedData; + + _.each(_.keys(fieldsMap), function(fb_key) { + var profile_key = fieldsMap[fb_key]; + if (fb_key === 'profile_picture') { + mappedData[profile_key] = body.picture ? body.picture.data.url : null; + return; + } + + if (fb_key === 'email' && !body[fb_key]) { + body[fb_key] = body.id + '@facebook.com'; + } + + if (!body[fb_key]) { + return; + } + + mappedData[profile_key] = body[fb_key]; + }); + + return mappedData; +} + + +function postAuthRegisterFacebook(req, res, next) { + + var options = _.clone(defaultOptions); + options.qs.access_token = req.body.accessToken; + + + if (!config.facebook) { + res.send(400, { + err: 'facebook_login_disabled', + des: 'Facebook login is not configured' + }); + return next(false); + } + + if (!req.body && !req.body.accessToken) { + res.send(400, { + err: 'missing_facebook_token', + des: 'Missing facebook access_token' + }); + return next(false); + } + + request(options, function(err, fb_res, fb_body) { + + if (err) { + res.send(409, {err: err.message}); + return next(); + } + + if (fb_body.error) { + res.send(409, {err: fb_body.error.type, des: fb_body.error.message}); + return next(); + } + + var fbUserProfile = mapFacebookData(fb_body, config.facebook.fieldsMap); + + daoMng.getFromUsername(fbUserProfile.username, function(err, foundUser) { + + // RETURNING FACEBOOK USER + + if (!err) { + var platform = { + platform:'fb', + accessToken: req.body.accessToken + }; + + userMng.setPlatformData(foundUser._id, 'fb', platform, function(err) { + if(err){ + log.error({err:err}, 'error updating sf tokens into user '+foundUser._id+''); + } + + var data = {}; + if(foundUser.roles){ + data.roles = foundUser.roles; + } + + if(config.version){ + data.deviceVersion = req.headers[config.version.header]; + } + + tokenMng.createBothTokens(foundUser._id, data , function(err, tokens) { + if(err) { + res.send(409,{err: err.message}); + } else { + tokens.expiresIn = config.accessToken.expiration * 60; + res.send(200, tokens); + } + return next(false); + }); + + }); + return; + } + + // NEW FACEBOOK USER + + if (err && err.message === daoMng.ERROR_USER_NOT_FOUND) { + + if (!config.facebook.registerByToken) { + res.send(401, { + err: 'facebook_user_not_registered', + des: 'This user need registration before login' + }); + return next(false); + } + + fbUserProfile.fb = { + accessToken: req.body.accessToken + }; + + fbUserProfile.password = random(12); + + userMng.createUser(fbUserProfile, null, function (err, tokens) { + if (err) { + if (!err.code) { + res.send(500, err); + } else { + var errCode = err.code; + delete(err.code); + res.send(errCode, err); + } + return next(false); + } + + tokenMng.getRefreshTokenInfo(tokens.refreshToken, function (err, tokenSet) { + var userId = tokenSet.userId; + var tokenData = tokenSet.data; + + if (config.version) { + tokenData.deviceVersion = req.headers[config.version.header]; + } + + tokenMng.createBothTokens(userId, tokenData, function (err, tokens) { + tokens.expiresIn = _settings.accessToken.expiration * 60; + res.send(201, tokens); + return next(); + }); + }); + + }); + + return; + } + + if (err) { + res.send(500, {err:'internal_error', des:'There was an internal error checking facebook profile'}); + return next(false); + } + + }); + + }); +} + +// TODO: extract to common util file +function random (howMany, chars) { + chars = chars || "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789"; + var rnd = crypto.randomBytes(howMany), + value = new Array(howMany), + len = chars.length; + + for (var i = 0; i < howMany; i++) { + value[i] = chars[rnd[i] % len]; + } + return value.join(''); +} + +function addRoutes(service) { + service.post('/auth/login/facebook', postAuthRegisterFacebook); +} + +module.exports = addRoutes; diff --git a/tests/auth.js b/tests/auth.js index 2f307f5..bca9d99 100644 --- a/tests/auth.js +++ b/tests/auth.js @@ -4,6 +4,7 @@ var config = require('../config.json'); var describeLogin = require('./auth/login.js'); var describeUser = require('./auth/user.js'); var describeSf = require('./auth/sf.js'); +var describeFbToken = require('./auth/facebook_token.js'); var describeIn = require('./auth/in.js'); var describeGoogle = require('./auth/google.js'); var describeRenew = require('./auth/renew.js'); @@ -33,6 +34,7 @@ describe('/auth', function(){ describeUser.describe(); describeSf.describe(accessTokenSettings, refreshTokenSettings); describeIn.describe(); + describeFbToken.describe(); describeGoogle.describe(); describeRenew.describe(); }); diff --git a/tests/auth/facebook_token.js b/tests/auth/facebook_token.js new file mode 100644 index 0000000..c2e1261 --- /dev/null +++ b/tests/auth/facebook_token.js @@ -0,0 +1,78 @@ +var assert = require('assert'); +var request = require('request'); +var nock = require('nock'); +var clone = require('clone'); + +var config = require('../../config.json'); +var dao = require('../../src/managers/dao.js'); + +var OPTIONS = { + url: 'http://localhost:' + config.public_port + '/auth/login/facebook', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + method: 'POST', + followRedirect: false +}; +var FB_PROFILE = { + name: "Test User", + email: "test@example.com", + id: "FB1234" +}; + +module.exports = { + describe: function() { + describe.only('/facebook_token', function() { + + beforeEach(function(done) { + dao.deleteAllUsers(function(err){ + assert.equal(err, null); + done(); + }); + }); + + it('exchanges facebook token for an existing cipherlayer user', function(done) { + nockFBGraphCall(FB_PROFILE, '1234', config.facebook.requestFields); + + var options = clone(OPTIONS); + options.url ='http://localhost:' + config.public_port + '/auth/login/facebook'; + + // TODO: create the user here + + request(options, function(err, res, body) { + console.log('REQUEST DONE', err, res, body); + done(); + }); + }); + + it('exchanges facebook token for new user', function(done) { + nockFBGraphCall(FB_PROFILE, '1234', config.facebook.requestFields); + nockPrivateCall(config, 'user1234'); + + var options = clone(OPTIONS); + options.url ='http://localhost:' + config.public_port + '/auth/login/facebook'; + request(options, function(err, res, body) { + console.log('REQUEST new DONE', err, res, body); + done(); + }); + + }); + + }); + } +}; + + +function nockFBGraphCall(profile, access_token, fields) { + fields = encodeURIComponent(fields); + nock('https://graph.facebook.com') + .get('/v2.5/me?access_token=' + access_token + '&fields=' + fields + '&format=json&method=get&pretty=0&suppress_http_code=1') + .reply(200, profile); +} + +function nockPrivateCall(config, userId) { + nock('http://' + config.private_host + ':' + config.private_port) + .post(config.passThroughEndpoint.path) + .reply(201, {id: userId}); +} + From 8886af143b3b41f56d09bb08b277968dc19fa51b Mon Sep 17 00:00:00 2001 From: Nick Jaremek Date: Thu, 4 Feb 2016 17:42:55 +0100 Subject: [PATCH 26/31] Added tests for facebook auth endpoint. Fixed auth endpoint. Updated config file to support facebook auth. --- config_sample.json | 20 +++++--- src/managers/user.js | 19 ++++---- src/public_routes/auth_facebook.js | 6 +-- tests/auth/facebook_token.js | 76 +++++++++++++++++++++++------- 4 files changed, 87 insertions(+), 34 deletions(-) diff --git a/config_sample.json b/config_sample.json index fae82f1..6814f80 100644 --- a/config_sample.json +++ b/config_sample.json @@ -59,6 +59,14 @@ "clientSecret": "{{GOOGLE_CLIENT_SECRET}}", "callbackURL": "http://localhost:3000/auth/google/callback" }, + "facebook": { + "registerByToken": true, + "requestFields": "name,email,picture", + "fieldsMap": { + "name": "fullname", + "email": "email" + } + }, "aws":{ "accessKeyId": "{{AWS_ACCESKEYID}}", "secretAccessKey": "{{AWS_SECRETACCESKEY}}", @@ -117,9 +125,9 @@ "link" : "http://testLink", "1" : true }, - "installPath" : "/install", - "db":"mongodb://localhost/versionControl?w=1" - } + "installPath" : "/install", + "db":"mongodb://localhost/versionControl?w=1" + } }, "allowedDomains":[ "*@a.com" @@ -132,8 +140,8 @@ "subject" :"Recover Example User Password", "body" : "Here is your new password for accessing to your Example account, if you want, you can update it anytime from your edit profile screen.

__PASSWD__

you can also click here from your mobile device to get in. If you receiver this email by error or you are sure you didn't requested it, please contact support@example.com" }, - "endpoints" : [ - { + "endpoints" : [ + { "path" : "\/api\/profile", "methods" : ["POST", "PUT"], "roles" : ["admin"] @@ -157,6 +165,6 @@ "\/upload$" ], "allowedHeaders": [ - "x-custom-header" + "x-custom-header" ] } diff --git a/src/managers/user.js b/src/managers/user.js index 6369db3..ee03ff6 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -90,6 +90,16 @@ function createUser(body, pin, cbk) { }); } + if (body.fb) { + user.platforms = [{ + platform: 'fb', + accessToken: body.fb.accessToken + }]; + delete body.fb; + createUserPrivateCall(body, user, cbk); + return; + } + var phone = body.phone; var countryISO = body.country; phoneMng(_settings).verifyPhone(user.username, phone, countryISO, pin, function (err) { @@ -97,15 +107,6 @@ function createUser(body, pin, cbk) { return cbk(err); } - if (body.fb) { - user.platforms = [{ - platform: 'fb', - accessToken: body.fb.accessToken - }]; - delete body.fb; - createUserPrivateCall(body, user, cbk); - return; - } if (body.sf) { delete(body[_settings.passThroughEndpoint.password]); tokenMng.getAccessTokenInfo(body.sf, function (err, tokenInfo) { diff --git a/src/public_routes/auth_facebook.js b/src/public_routes/auth_facebook.js index 51f82d1..a795970 100644 --- a/src/public_routes/auth_facebook.js +++ b/src/public_routes/auth_facebook.js @@ -83,9 +83,9 @@ function postAuthRegisterFacebook(req, res, next) { } var fbUserProfile = mapFacebookData(fb_body, config.facebook.fieldsMap); + var fbUserProfileUsername = fbUserProfile[config.facebook.fieldsMap.email || 'email']; - daoMng.getFromUsername(fbUserProfile.username, function(err, foundUser) { - + daoMng.getFromUsername(fbUserProfileUsername, function(err, foundUser) { // RETURNING FACEBOOK USER if (!err) { @@ -161,7 +161,7 @@ function postAuthRegisterFacebook(req, res, next) { } tokenMng.createBothTokens(userId, tokenData, function (err, tokens) { - tokens.expiresIn = _settings.accessToken.expiration * 60; + tokens.expiresIn = config.accessToken.expiration * 60; res.send(201, tokens); return next(); }); diff --git a/tests/auth/facebook_token.js b/tests/auth/facebook_token.js index c2e1261..9366b28 100644 --- a/tests/auth/facebook_token.js +++ b/tests/auth/facebook_token.js @@ -4,56 +4,100 @@ var nock = require('nock'); var clone = require('clone'); var config = require('../../config.json'); -var dao = require('../../src/managers/dao.js'); +var userDao = require('../../src/managers/dao.js'); var OPTIONS = { url: 'http://localhost:' + config.public_port + '/auth/login/facebook', headers: { 'Content-Type': 'application/json; charset=utf-8' }, + json: true, + body: { + accessToken: 'abcd1234' + }, method: 'POST', followRedirect: false }; + +var baseUser = { + id: '1234', + email: 'test@a.com', + password: 'pass1' +}; + var FB_PROFILE = { name: "Test User", - email: "test@example.com", + email: "test@a.com", id: "FB1234" }; module.exports = { describe: function() { - describe.only('/facebook_token', function() { + describe('/facebook_token', function() { beforeEach(function(done) { - dao.deleteAllUsers(function(err){ + userDao.deleteAllUsers(function(err){ assert.equal(err, null); done(); }); }); it('exchanges facebook token for an existing cipherlayer user', function(done) { - nockFBGraphCall(FB_PROFILE, '1234', config.facebook.requestFields); + nockFBGraphCall(FB_PROFILE, OPTIONS.body.accessToken, config.facebook.requestFields); var options = clone(OPTIONS); options.url ='http://localhost:' + config.public_port + '/auth/login/facebook'; - - // TODO: create the user here - - request(options, function(err, res, body) { - console.log('REQUEST DONE', err, res, body); - done(); + options.headers[config.version.header] = "test/1"; + + var existingUser = clone(baseUser); + existingUser.username = existingUser.email; + delete existingUser.email; + + userDao.addUser()(existingUser, function(error) { + assert.equal(error, null); + + request(options, function (err, res, body) { + assert.ok(body.accessToken); + assert.ok(body.refreshToken); + assert.ok(body.expiresIn); + + userDao.getFromUsername(baseUser.email, function(error, user) { + assert.ok(user); + assert.equal(user.username, existingUser.username); + assert.ok(user.platforms); + var fbPlatform = user.platforms[0]; + assert.equal(fbPlatform.platform, 'fb'); + assert.equal(fbPlatform.accessToken, OPTIONS.body.accessToken); + return done(); + }); + }); }); }); it('exchanges facebook token for new user', function(done) { - nockFBGraphCall(FB_PROFILE, '1234', config.facebook.requestFields); - nockPrivateCall(config, 'user1234'); + nockFBGraphCall(FB_PROFILE, OPTIONS.body.accessToken, config.facebook.requestFields); + nockPrivateCall(config, baseUser.id); var options = clone(OPTIONS); options.url ='http://localhost:' + config.public_port + '/auth/login/facebook'; + options.headers[config.version.header] = "test/1"; + request(options, function(err, res, body) { - console.log('REQUEST new DONE', err, res, body); - done(); + assert.equal(err, null); + assert.ok(body.accessToken); + assert.ok(body.refreshToken); + assert.ok(body.expiresIn); + + userDao.getFromUsername(baseUser.email, function(err, foundUser) { + assert.equal(err, null); + assert.ok(foundUser); + assert.equal(foundUser.username, baseUser.email); + assert.ok(foundUser.platforms); + var fbPlatform = foundUser.platforms[0]; + assert.equal(fbPlatform.platform, 'fb'); + assert.equal(fbPlatform.accessToken, OPTIONS.body.accessToken); + done(); + }); }); }); @@ -66,7 +110,7 @@ module.exports = { function nockFBGraphCall(profile, access_token, fields) { fields = encodeURIComponent(fields); nock('https://graph.facebook.com') - .get('/v2.5/me?access_token=' + access_token + '&fields=' + fields + '&format=json&method=get&pretty=0&suppress_http_code=1') + .get('/v2.5/me?fields=' + fields + '&format=json&method=get&pretty=0&suppress_http_code=1' + '&access_token=' + access_token) .reply(200, profile); } From 8e8811b7487a0c869ce91ed6719dfd71664456bc Mon Sep 17 00:00:00 2001 From: "gustavo.marin" Date: Thu, 4 Feb 2016 18:22:57 +0100 Subject: [PATCH 27/31] hotfix config facebook --- src/public_routes/auth_facebook.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public_routes/auth_facebook.js b/src/public_routes/auth_facebook.js index a795970..a0290d1 100644 --- a/src/public_routes/auth_facebook.js +++ b/src/public_routes/auth_facebook.js @@ -13,7 +13,7 @@ var defaultOptions = { json: true, method: 'GET', qs: { - fields: config.facebook.requestFields, + fields: config.facebook ? config.facebook.requestFields : null, format: 'json', method: 'get', pretty: 0, From 628a6e8905c72ef82b9a455229cbd054e77e32bf Mon Sep 17 00:00:00 2001 From: Nick Jaremek Date: Fri, 5 Feb 2016 09:33:16 +0100 Subject: [PATCH 28/31] Added test for facebook login in case email is missing from request body. --- config_sample.json | 3 ++- tests/auth/facebook_token.js | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/config_sample.json b/config_sample.json index 6814f80..d466fa9 100644 --- a/config_sample.json +++ b/config_sample.json @@ -130,7 +130,8 @@ } }, "allowedDomains":[ - "*@a.com" + "*@a.com", + "*@facebook.com" ], "password":{ "validateOldPassword": false, diff --git a/tests/auth/facebook_token.js b/tests/auth/facebook_token.js index 9366b28..0cd5368 100644 --- a/tests/auth/facebook_token.js +++ b/tests/auth/facebook_token.js @@ -99,9 +99,41 @@ module.exports = { done(); }); }); - }); + it('creates a user with a facebook domain email when username field is missing', function(done) { + + var noEmailUser = clone(baseUser); + delete noEmailUser.email; + + var madeUpEmailFbProfile = clone(FB_PROFILE); + madeUpEmailFbProfile.email = noEmailUser.id + '@facebook.com'; + + nockFBGraphCall(madeUpEmailFbProfile, OPTIONS.body.accessToken, config.facebook.requestFields); + nockPrivateCall(config, noEmailUser.id); + + var options = clone(OPTIONS); + options.url ='http://localhost:' + config.public_port + '/auth/login/facebook'; + options.headers[config.version.header] = "test/1"; + + request(options, function(err, res, body) { + assert.equal(err, null); + assert.ok(body.accessToken); + assert.ok(body.refreshToken); + assert.ok(body.expiresIn); + + userDao.getFromUsername(madeUpEmailFbProfile.email, function (err, foundUser) { + assert.equal(err, null); + assert.ok(foundUser); + assert.equal(foundUser.username, madeUpEmailFbProfile.email); + assert.ok(foundUser.platforms); + var fbPlatform = foundUser.platforms[0]; + assert.equal(fbPlatform.platform, 'fb'); + assert.equal(fbPlatform.accessToken, OPTIONS.body.accessToken); + done(); + }); + }); + }); }); } }; From 09542cee7fbe7a8630efc1a8616361154a9e5778 Mon Sep 17 00:00:00 2001 From: Nick Jaremek Date: Fri, 5 Feb 2016 10:16:06 +0100 Subject: [PATCH 29/31] Updated missing email facebook auth test case. --- tests/auth/facebook_token.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/auth/facebook_token.js b/tests/auth/facebook_token.js index 0cd5368..8a556b9 100644 --- a/tests/auth/facebook_token.js +++ b/tests/auth/facebook_token.js @@ -28,7 +28,7 @@ var baseUser = { var FB_PROFILE = { name: "Test User", email: "test@a.com", - id: "FB1234" + id: "fb1234" }; module.exports = { @@ -107,7 +107,9 @@ module.exports = { delete noEmailUser.email; var madeUpEmailFbProfile = clone(FB_PROFILE); - madeUpEmailFbProfile.email = noEmailUser.id + '@facebook.com'; + delete madeUpEmailFbProfile.email; + + var userEmail = 'fb' + noEmailUser.id + '@facebook.com'; nockFBGraphCall(madeUpEmailFbProfile, OPTIONS.body.accessToken, config.facebook.requestFields); nockPrivateCall(config, noEmailUser.id); @@ -122,10 +124,10 @@ module.exports = { assert.ok(body.refreshToken); assert.ok(body.expiresIn); - userDao.getFromUsername(madeUpEmailFbProfile.email, function (err, foundUser) { + userDao.getFromUsername(userEmail, function (err, foundUser) { assert.equal(err, null); assert.ok(foundUser); - assert.equal(foundUser.username, madeUpEmailFbProfile.email); + assert.equal(foundUser.username, userEmail); assert.ok(foundUser.platforms); var fbPlatform = foundUser.platforms[0]; assert.equal(fbPlatform.platform, 'fb'); From 4f55f22af46920b880045ecefbc56aa198eaa81b Mon Sep 17 00:00:00 2001 From: Luis Mesas Date: Mon, 8 Feb 2016 15:06:07 -0800 Subject: [PATCH 30/31] indent refactor --- src/managers/user.js | 865 +++++++++++++++++++++---------------------- 1 file changed, 432 insertions(+), 433 deletions(-) diff --git a/src/managers/user.js b/src/managers/user.js index ee03ff6..7e21a1f 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -9,15 +9,15 @@ var config = require(process.cwd() + '/config.json'); var daoMng = require('./dao'); var tokenMng = require('./token'); var redisMng = require('./redis'); -var cryptoMng = require('./crypto')({ password : 'password' }); +var cryptoMng = require('./crypto')({password: 'password'}); var phoneMng = require('./phone'); var emailMng = require('./email'); var jsonValidator = require('./json_validator'); var ERR_INVALID_PWD = { - err: 'invalid_password_format', - code: 400 + err: 'invalid_password_format', + code: 400 }; var _settings = {}; @@ -25,460 +25,459 @@ var _settings = {}; //This is Chris's contribution to the coding of this project!!! var ERR_INVALID_USER_DOMAIN = 'Sorry your email domain is not authorised for this service'; -function setPlatformData(userId, platform, data, cbk){ - daoMng.updateArrayItem(userId, 'platforms', 'platform', data, function(err, updates){ - if(err) { - return cbk(err); - } +function setPlatformData(userId, platform, data, cbk) { + daoMng.updateArrayItem(userId, 'platforms', 'platform', data, function (err, updates) { + if (err) { + return cbk(err); + } - if(updates<1) { - return cbk({err:'platform_not_updated', des:'updated command worked but no platform were updated'}); - } + if (updates < 1) { + return cbk({err: 'platform_not_updated', des: 'updated command worked but no platform were updated'}); + } - cbk(null); - }); + cbk(null); + }); } function createUser(body, pin, cbk) { - if (!body[_settings.passThroughEndpoint.username]) { - return cbk({ - err: 'auth_proxy_error', - des: 'invalid userinfo', - code: 400 - }); - } - body[_settings.passThroughEndpoint.username] = body[_settings.passThroughEndpoint.username].toLowerCase(); - - isValidDomain(body[_settings.passThroughEndpoint.username], function(isValid){ - if(!isValid) { - var err = { - err: 'user_domain_not_allowed', - des: ERR_INVALID_USER_DOMAIN, - code: 400 - }; - log.warn({err:err}); - return cbk(err, null); - } - - if (!body[_settings.passThroughEndpoint.password]) { - if (!body.sf) { - return cbk({ - err: 'invalid_security_token', - des: 'you must provide a password or a salesforce token to create the user', - code: 400 - }); - } - } else { - if(!validatePwd(body.password, _settings.password.regexValidation)) { - ERR_INVALID_PWD.des = _settings.password.message; - var invalidPasswordError = ERR_INVALID_PWD; - return cbk(invalidPasswordError); - } - } - - var user = { - username: body[_settings.passThroughEndpoint.username], - password: body[_settings.passThroughEndpoint.password] - }; - - daoMng.getFromUsername(user.username, function (err, foundUser) { - if (foundUser) { - return cbk({ - err: 'auth_proxy_user_error', - des: 'user already exists', - code: 403 - }); - } - - if (body.fb) { - user.platforms = [{ - platform: 'fb', - accessToken: body.fb.accessToken - }]; - delete body.fb; - createUserPrivateCall(body, user, cbk); - return; - } - - var phone = body.phone; - var countryISO = body.country; - phoneMng(_settings).verifyPhone(user.username, phone, countryISO, pin, function (err) { - if (err) { - return cbk(err); - } - - if (body.sf) { - delete(body[_settings.passThroughEndpoint.password]); - tokenMng.getAccessTokenInfo(body.sf, function (err, tokenInfo) { - if (err) { - return cbk({ - err: 'invalid_platform_token', - des: 'you must provide a valid salesforce token', - code: 400 - }); - } - - user.platforms = [{ - platform: 'sf', - accessToken: tokenInfo.data.accessToken, - refreshToken: tokenInfo.data.refreshToken, - expiry: new Date().getTime() + _settings.salesforce.expiration * 60 * 1000 - }]; - createUserPrivateCall(body, user, cbk); - }); - } else { - emailMng(_settings).emailVerification(body[config.passThroughEndpoint.email || 'email' ], body, function (err, destinationEmail) { - if(err){ - return cbk(err); - } - if(destinationEmail){ - return cbk({ - des: destinationEmail, - code: 200 - }); - } - createUserPrivateCall(body, user, cbk); - }); - } - }); - }); - }); + if (!body[_settings.passThroughEndpoint.username]) { + return cbk({ + err: 'auth_proxy_error', + des: 'invalid userinfo', + code: 400 + }); + } + body[_settings.passThroughEndpoint.username] = body[_settings.passThroughEndpoint.username].toLowerCase(); + + isValidDomain(body[_settings.passThroughEndpoint.username], function (isValid) { + if (!isValid) { + var err = { + err: 'user_domain_not_allowed', + des: ERR_INVALID_USER_DOMAIN, + code: 400 + }; + log.warn({err: err}); + return cbk(err, null); + } + + if (!body[_settings.passThroughEndpoint.password]) { + if (!body.sf) { + return cbk({ + err: 'invalid_security_token', + des: 'you must provide a password or a salesforce token to create the user', + code: 400 + }); + } + } else { + if (!validatePwd(body.password, _settings.password.regexValidation)) { + ERR_INVALID_PWD.des = _settings.password.message; + var invalidPasswordError = ERR_INVALID_PWD; + return cbk(invalidPasswordError); + } + } + + var user = { + username: body[_settings.passThroughEndpoint.username], + password: body[_settings.passThroughEndpoint.password] + }; + + daoMng.getFromUsername(user.username, function (err, foundUser) { + if (foundUser) { + return cbk({ + err: 'auth_proxy_user_error', + des: 'user already exists', + code: 403 + }); + } + + if (body.fb) { + user.platforms = [{ + platform: 'fb', + accessToken: body.fb.accessToken + }]; + delete body.fb; + createUserPrivateCall(body, user, cbk); + return; + } + + var phone = body.phone; + var countryISO = body.country; + phoneMng(_settings).verifyPhone(user.username, phone, countryISO, pin, function (err) { + if (err) { + return cbk(err); + } + + if (body.sf) { + delete(body[_settings.passThroughEndpoint.password]); + tokenMng.getAccessTokenInfo(body.sf, function (err, tokenInfo) { + if (err) { + return cbk({ + err: 'invalid_platform_token', + des: 'you must provide a valid salesforce token', + code: 400 + }); + } + + user.platforms = [{ + platform: 'sf', + accessToken: tokenInfo.data.accessToken, + refreshToken: tokenInfo.data.refreshToken, + expiry: new Date().getTime() + _settings.salesforce.expiration * 60 * 1000 + }]; + createUserPrivateCall(body, user, cbk); + }); + } else { + emailMng(_settings).emailVerification(body[config.passThroughEndpoint.email || 'email'], body, function (err, destinationEmail) { + if (err) { + return cbk(err); + } + if (destinationEmail) { + return cbk({ + des: destinationEmail, + code: 200 + }); + } + createUserPrivateCall(body, user, cbk); + }); + } + }); + }); + }); } function createUserByToken(token, cbk) { - if(!token){ - return cbk({ - err: 'auth_proxy_error', - des: 'empty param verifyToken', - code: 400 - }); - } - - //Decipher the body - var tokenSettings = { - cipherKey: _settings.accessToken.cipherKey, - firmKey: _settings.accessToken.signKey, - //Same expiration as the redisKey - tokenExpirationMinutes: _settings.emailVerification.redis.expireInSec - }; - - ciphertoken.getTokenSet(tokenSettings, token, function(err, bodyData){ - if(err){ - return cbk(err); - } - var body = bodyData.data; - - var profileSchema; - - if (!config.validators) { - profileSchema = require('./json_formats/profile_create.json'); - } else { - profileSchema = require((config.validators.profile.path ? config.validators.profile.path : './json_formats/') + config.validators.profile.filename); - } - - //Validate the current bodyData with the schema profile_create.json - if( !jsonValidator.isValidJSON(body, profileSchema) || !body.transactionId) { - - return cbk({ - err:'invalid_profile_data', - des:'The data format provided is not valid.', - code: 400 - }); - } - //Verify the transactionId - var redisKey = _settings.emailVerification.redis.key; - redisKey = redisKey.replace('{username}', body[config.passThroughEndpoint.email || 'email' ]); - - redisMng.getKeyValue(redisKey, function(err, transactionId) { - if(err){ - return cbk(err); - } - - if(body.transactionId !== transactionId){ - return cbk({ - err:'invalid_profile_data', - des:'Incorrect or expired transaction.', - code: 400 - }); - } - - var user = { - username: body[_settings.passThroughEndpoint.username], - password: body[_settings.passThroughEndpoint.password] - }; - delete(body[_settings.passThroughEndpoint.password]); - - isValidDomain(user.username, function(isValid){ - if(!isValid) { - var domainNotAllowedError = { - err:'user_domain_not_allowed', - des: ERR_INVALID_USER_DOMAIN, - code: 400 - }; - log.warn({err:domainNotAllowedError}); - return cbk(domainNotAllowedError, null); - } - - daoMng.getFromUsername(user.username, function (err, foundUser) { - if (foundUser) { - return cbk({ - err: 'auth_proxy_error', - des: 'user already exists', - code: 403 - }); - } - - delete(body[_settings.passThroughEndpoint.password]); - createUserPrivateCall(body, user, cbk); - }); - }); - }); - }); + if (!token) { + return cbk({ + err: 'auth_proxy_error', + des: 'empty param verifyToken', + code: 400 + }); + } + + //Decipher the body + var tokenSettings = { + cipherKey: _settings.accessToken.cipherKey, + firmKey: _settings.accessToken.signKey, + //Same expiration as the redisKey + tokenExpirationMinutes: _settings.emailVerification.redis.expireInSec + }; + + ciphertoken.getTokenSet(tokenSettings, token, function (err, bodyData) { + if (err) { + return cbk(err); + } + var body = bodyData.data; + + var profileSchema; + + if (!config.validators) { + profileSchema = require('./json_formats/profile_create.json'); + } else { + profileSchema = require((config.validators.profile.path ? config.validators.profile.path : './json_formats/') + config.validators.profile.filename); + } + + //Validate the current bodyData with the schema profile_create.json + if (!jsonValidator.isValidJSON(body, profileSchema) || !body.transactionId) { + + return cbk({ + err: 'invalid_profile_data', + des: 'The data format provided is not valid.', + code: 400 + }); + } + //Verify the transactionId + var redisKey = _settings.emailVerification.redis.key; + redisKey = redisKey.replace('{username}', body[config.passThroughEndpoint.email || 'email']); + + redisMng.getKeyValue(redisKey, function (err, transactionId) { + if (err) { + return cbk(err); + } + + if (body.transactionId !== transactionId) { + return cbk({ + err: 'invalid_profile_data', + des: 'Incorrect or expired transaction.', + code: 400 + }); + } + + var user = { + username: body[_settings.passThroughEndpoint.username], + password: body[_settings.passThroughEndpoint.password] + }; + delete(body[_settings.passThroughEndpoint.password]); + + isValidDomain(user.username, function (isValid) { + if (!isValid) { + var domainNotAllowedError = { + err: 'user_domain_not_allowed', + des: ERR_INVALID_USER_DOMAIN, + code: 400 + }; + log.warn({err: domainNotAllowedError}); + return cbk(domainNotAllowedError, null); + } + + daoMng.getFromUsername(user.username, function (err, foundUser) { + if (foundUser) { + return cbk({ + err: 'auth_proxy_error', + des: 'user already exists', + code: 403 + }); + } + + delete(body[_settings.passThroughEndpoint.password]); + createUserPrivateCall(body, user, cbk); + }); + }); + }); + }); } -function createUserPrivateCall(body, user, cbk){ - var clonedBody = _.clone(body); - delete clonedBody.password; - var options = { - url: 'http://' + _settings.private_host + ':' + _settings.private_port + _settings.passThroughEndpoint.path, - headers: { - 'Content-Type': 'application/json; charset=utf-8' - }, - method: 'POST', - body: JSON.stringify(clonedBody) - }; - - log.info('=> POST ' + options.url); - request(options, function (err, private_res, body) { - if (err) { - log.error('<= error: ' + err); - return cbk({ - err: 'auth_proxy_error', - des: 'there was an internal error when redirecting the call to protected service', - code: 500 - }); - } - - log.info('<= ' + private_res.statusCode); - body = JSON.parse(body); - user.id = body.id; - - if (!user.password) { - user.password = random(12); - } - - cryptoMng.encrypt(user.password, function(encrypted){ - user.password = encrypted; - - daoMng.addUser()(user, function (err, createdUser) { - if (err) { - log.error({err:err,des:'error adding user to DB'}); - return cbk({ - err: err.err, - des: 'error adding user to DB', - code: 409 - }); - } - - daoMng.getFromUsernamePassword(createdUser.username, createdUser.password, function (err, foundUser) { - if (err) { - log.error({err:err,des:'error obtaining user'}); - return cbk({ - err: err.message, - code: 409 - }); - } - - var data = {}; - if(foundUser.roles){ - data.roles = foundUser.roles; - } - - async.series([ - function(done){ - //Add "realms" & "capabilities" - daoMng.getRealms(function(err, realms){ - if(err){ - log.error({err:err,des:'error obtaining user realms'}); - return done(); - } - - if(!realms || !realms.length) { - log.info({des:'there are no REALMS in DB'}); - return done(); - } - - async.eachSeries(realms, function(realm, next){ - if(!realm.allowedDomains || !realm.allowedDomains.length){ - return next(); - } - async.eachSeries(realm.allowedDomains, function(domain, more){ - //wildcard - var check = domain.replace(/\*/g,'.*'); - var match = foundUser.username.match(check); - if(!match || foundUser.username !== match[0]){ - return more(); - } - - if(!data.realms){ - data.realms = []; - } - data.realms.push(realm.name); - - async.each(Object.keys(realm.capabilities), function(capName, added){ - if(!data.capabilities){ - data.capabilities = {}; - } - - data.capabilities[capName] = realm.capabilities[capName]; - added(); - }, more); - }, next); - }, done); - }); - } - ], function(){ - tokenMng.createBothTokens(foundUser._id, data, function (err, tokens) { - if (err) { - log.error({err:err, des:'error creating tokens'}); - return cbk({ - err: err.message, - code: 409 - }); - } - tokens.expiresIn = _settings.accessToken.expiration * 60; - cbk(null, tokens); - }); - }); - - }); - }); - }); - - }); +function createUserPrivateCall(body, user, cbk) { + var clonedBody = _.clone(body); + delete clonedBody.password; + var options = { + url: 'http://' + _settings.private_host + ':' + _settings.private_port + _settings.passThroughEndpoint.path, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + method: 'POST', + body: JSON.stringify(clonedBody) + }; + + log.info('=> POST ' + options.url); + request(options, function (err, private_res, body) { + if (err) { + log.error('<= error: ' + err); + return cbk({ + err: 'auth_proxy_error', + des: 'there was an internal error when redirecting the call to protected service', + code: 500 + }); + } + + log.info('<= ' + private_res.statusCode); + body = JSON.parse(body); + user.id = body.id; + + if (!user.password) { + user.password = random(12); + } + + cryptoMng.encrypt(user.password, function (encrypted) { + user.password = encrypted; + + daoMng.addUser()(user, function (err, createdUser) { + if (err) { + log.error({err: err, des: 'error adding user to DB'}); + return cbk({ + err: err.err, + des: 'error adding user to DB', + code: 409 + }); + } + + daoMng.getFromUsernamePassword(createdUser.username, createdUser.password, function (err, foundUser) { + if (err) { + log.error({err: err, des: 'error obtaining user'}); + return cbk({ + err: err.message, + code: 409 + }); + } + + var data = {}; + if (foundUser.roles) { + data.roles = foundUser.roles; + } + + async.series([ + function (done) { + //Add "realms" & "capabilities" + daoMng.getRealms(function (err, realms) { + if (err) { + log.error({err: err, des: 'error obtaining user realms'}); + return done(); + } + + if (!realms || !realms.length) { + log.info({des: 'there are no REALMS in DB'}); + return done(); + } + + async.eachSeries(realms, function (realm, next) { + if (!realm.allowedDomains || !realm.allowedDomains.length) { + return next(); + } + async.eachSeries(realm.allowedDomains, function (domain, more) { + //wildcard + var check = domain.replace(/\*/g, '.*'); + var match = foundUser.username.match(check); + if (!match || foundUser.username !== match[0]) { + return more(); + } + + if (!data.realms) { + data.realms = []; + } + data.realms.push(realm.name); + + async.each(Object.keys(realm.capabilities), function (capName, added) { + if (!data.capabilities) { + data.capabilities = {}; + } + + data.capabilities[capName] = realm.capabilities[capName]; + added(); + }, more); + }, next); + }, done); + }); + } + ], function () { + tokenMng.createBothTokens(foundUser._id, data, function (err, tokens) { + if (err) { + log.error({err: err, des: 'error creating tokens'}); + return cbk({ + err: err.message, + code: 409 + }); + } + tokens.expiresIn = _settings.accessToken.expiration * 60; + cbk(null, tokens); + }); + }); + + }); + }); + }); + + }); } -function setPassword(id, body, cbk){ - if(!body.password){ - return cbk({ - err: 'auth_proxy_error', - des: 'invalid body request', - code: 400 - }); - } - - if(!validatePwd(body.password, _settings.password.regexValidation)) { - ERR_INVALID_PWD.des = _settings.password.message; - var err = ERR_INVALID_PWD; - return cbk(err); - } else { - cryptoMng.encrypt(body.password, function(encryptedPwd){ - daoMng.updateField(id, 'password', encryptedPwd, function(err, result){ - return cbk(err, result); - }); - }); - } +function setPassword(id, body, cbk) { + if (!body.password) { + return cbk({ + err: 'auth_proxy_error', + des: 'invalid body request', + code: 400 + }); + } + + if (!validatePwd(body.password, _settings.password.regexValidation)) { + ERR_INVALID_PWD.des = _settings.password.message; + var err = ERR_INVALID_PWD; + return cbk(err); + } else { + cryptoMng.encrypt(body.password, function (encryptedPwd) { + daoMng.updateField(id, 'password', encryptedPwd, function (err, result) { + return cbk(err, result); + }); + }); + } } function validateOldPassword(username, oldPassword, cbk) { - daoMng.getAllUserFields(username, function(err, user) { - if (err) { - res.send(401, err); - return next(); - } - - cryptoMng.encrypt(oldPassword, function(encrypted){ - if (user.password !== encrypted) { - return cbk({ - err: 'invalid_old_password', - des: 'invalid password', - code: 401 - }); - } - - return cbk(); - }); - }); + daoMng.getAllUserFields(username, function (err, user) { + if (err) { + res.send(401, err); + return next(); + } + + cryptoMng.encrypt(oldPassword, function (encrypted) { + if (user.password !== encrypted) { + return cbk({ + err: 'invalid_old_password', + des: 'invalid password', + code: 401 + }); + } + + return cbk(); + }); + }); } //Aux functions -function random (howMany, chars) { - chars = chars || "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789"; - var rnd = crypto.randomBytes(howMany), - value = new Array(howMany), - len = chars.length; - - for (var i = 0; i < howMany; i++) { - value[i] = chars[rnd[i] % len]; - } - return value.join(''); +function random(howMany, chars) { + chars = chars || "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789"; + var rnd = crypto.randomBytes(howMany), + value = new Array(howMany), + len = chars.length; + + for (var i = 0; i < howMany; i++) { + value[i] = chars[rnd[i] % len]; + } + return value.join(''); } -function isValidDomain(email, cbk){ - var validDomain = false; - - var domainsInConfig = (_settings.allowedDomains && _settings.allowedDomains.length); - - daoMng.getRealms(function(err, realms){ - if(err){ - return cbk(false); - } - - if((!realms || !realms.length) && !domainsInConfig){ - return cbk(true); - } - - async.eachSeries(realms, function(realm, next){ - if(validDomain || !realm.allowedDomains || !realm.allowedDomains.length){ - return next(); - } - - async.eachSeries(realm.allowedDomains, function(domain, more){ - if(validDomain){ - return more(); - } - - //wildcard - var check = domain.replace(/\*/g,'.*'); - var match = email.match(check); - validDomain = (match !== null && email === match[0]); - more(); - }, next); - }, function(){ - if(!validDomain){ - //Check domains in config file - for(var i = 0; i < _settings.allowedDomains.length; i++){ - var domain = _settings.allowedDomains[i]; - - //wildcard - var check = domain.replace(/\*/g,'.*'); - var match = email.match(check); - validDomain = (match !== null && email === match[0]); - if(validDomain) break; - } - } - return cbk(validDomain); - }); - }); +function isValidDomain(email, cbk) { + var validDomain = false; + + var domainsInConfig = (_settings.allowedDomains && _settings.allowedDomains.length); + + daoMng.getRealms(function (err, realms) { + if (err) { + return cbk(false); + } + + if ((!realms || !realms.length) && !domainsInConfig) { + return cbk(true); + } + + async.eachSeries(realms, function (realm, next) { + if (validDomain || !realm.allowedDomains || !realm.allowedDomains.length) { + return next(); + } + + async.eachSeries(realm.allowedDomains, function (domain, more) { + if (validDomain) { + return more(); + } + + //wildcard + var check = domain.replace(/\*/g, '.*'); + var match = email.match(check); + validDomain = (match !== null && email === match[0]); + more(); + }, next); + }, function () { + if (!validDomain) { + //Check domains in config file + for (var i = 0; i < _settings.allowedDomains.length; i++) { + var domain = _settings.allowedDomains[i]; + + //wildcard + var check = domain.replace(/\*/g, '.*'); + var match = email.match(check); + validDomain = (match !== null && email === match[0]); + if (validDomain) break; + } + } + return cbk(validDomain); + }); + }); } -function validatePwd(pwd, regexp){ - var regex = new RegExp(regexp); - return regex.test(pwd); +function validatePwd(pwd, regexp) { + var regex = new RegExp(regexp); + return regex.test(pwd); } - -module.exports = function(settings) { - var config = require(process.cwd() + '/config.json'); - _settings = _.assign({}, config, settings); - - return { - setPlatformData : setPlatformData, - createUser : createUser, - createUserByToken : createUserByToken, - setPassword: setPassword, - validateOldPassword: validateOldPassword - }; +module.exports = function (settings) { + var config = require(process.cwd() + '/config.json'); + _settings = _.assign({}, config, settings); + + return { + setPlatformData: setPlatformData, + createUser: createUser, + createUserByToken: createUserByToken, + setPassword: setPassword, + validateOldPassword: validateOldPassword + }; }; From 097fc71a0a42fc309af8875426356ce9ee2ec787 Mon Sep 17 00:00:00 2001 From: Luis Mesas Date: Mon, 8 Feb 2016 15:48:23 -0800 Subject: [PATCH 31/31] added verbose option to coveralls script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2ddb7ba..7c0f77b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "coverage-2": "cp coverage/lcov.info coverage/cucumber.lcov", "coverage-3": "node_modules/.bin/mocha tests --recursive --require blanket -R mocha-lcov-reporter > coverage/mocha.lcov", "coverage-4": "node_modules/.bin/lcov-result-merger 'coverage/*.lcov' 'coverage/merged.lcov'", - "coverage-5": "cat ./coverage/merged.lcov | ./node_modules/coveralls/bin/coveralls.js" + "coverage-5": "cat ./coverage/merged.lcov | ./node_modules/coveralls/bin/coveralls.js --verbose" }, "dependencies": { "async": "^0.9.0",