From 1f08eafc3dbe49815477e93a344c2b36f3e64780 Mon Sep 17 00:00:00 2001 From: Nicolas Jaremek Date: Tue, 17 Mar 2015 11:45:46 +0100 Subject: [PATCH 01/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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;