diff --git a/config_sample.json b/config_sample.json
index e6da2d1..35bef81 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",
@@ -65,6 +65,12 @@
"avatars": "example-avatars"
}
},
+ "validators": {
+ "profile": {
+ "path": "",
+ "filename": "profile_create.json"
+ }
+ },
"phoneVerification": {
"pinSize": 4,
"attempts": 3,
@@ -83,8 +89,9 @@
}
]
},
- "emailVerification":{
+ "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.",
@@ -92,10 +99,14 @@
"key":"user.{username}.transaction",
"expireInSec": 86400
},
- "scheme":"mycomms"
+ "scheme":"mycomms",
+ "redirectUrl": "http://www.google.com"
},
"externalServices":{
- "notifications": "http://localhost:3002"
+ "notifications": {
+ "base": "http://localhost:3002",
+ "pathEmail": "/api/notification/email"
+ }
},
"version" : {
"header" : "x-example-version",
@@ -103,23 +114,24 @@
"test" : {
"link" : "http://testLink",
"1" : true
- }
- },
+ },
"installPath" : "/install",
"db":"mongodb://localhost/versionControl?w=1"
+ }
},
"allowedDomains":[
"*@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}",
"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"]
@@ -140,6 +152,9 @@
}
],
"directProxyUrls": [
- "\/upload$"
+ "\/upload$"
+ ],
+ "allowedHeaders": [
+ "x-custom-header"
]
}
diff --git a/features/post_profile.feature b/features/post_profile.feature
index a399da6..702d2dc 100644
--- a/features/post_profile.feature
+++ b/features/post_profile.feature
@@ -2,7 +2,6 @@ Feature: client application POST a profile to create
#TODO: update to validate the domain with the first item of the allowedDomains in the config (if it exists)
- @ignore
Scenario Outline: Client post data for a new profile
Given a protected service replies to a request with to with status and a body
When the client makes a pass through with the following in the body
@@ -15,4 +14,4 @@ Feature: client application POST a profile to create
Examples:
| METHOD | PATH | STATUS | PROTECTED_REQUEST_PAYLOAD | PUBLIC_REQUEST_PAYLOAD | PROTECTED_PAYLOAD |
- | POST | /api/profile | 201 | { "email" : "valid@vodafone.com" } | { "email":"valid@vodafone.com", "password":"i2E45678", "phone":"631014231", "country":"ES" } | { "id" : "a1b2c3d4" } |
+ | POST | /api/profile | 201 | { "email" : "valid@a.com" } | { "email":"valid@a.com", "password":"i2E45678", "phone":"631014231", "country":"ES" } | { "id" : "a1b2c3d4" } |
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/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/client_pass_through_pin.js b/features/step_definitions/client_pass_through_pin.js
index 7935304..077972a 100644
--- a/features/step_definitions/client_pass_through_pin.js
+++ b/features/step_definitions/client_pass_through_pin.js
@@ -26,6 +26,9 @@ var myStepDefinitionsWrapper = function () {
.post(config.passThroughEndpoint.path)
.reply(201, {id: "a1b2c3d4e5f6"});
+ // This is required to skip the email verification step and avoid a hanging request targeted at the email verification endpoint
+ config.emailVerification = null;
+
request(options, function(err,res,body) {
assert.equal(err,null);
world.getResponse().statusCode = res.statusCode;
@@ -34,7 +37,6 @@ var myStepDefinitionsWrapper = function () {
});
});
-
});
};
module.exports = myStepDefinitionsWrapper;
\ No newline at end of file
diff --git a/features/step_definitions/method_request_to_path.js b/features/step_definitions/method_request_to_path.js
index ad13c19..11d427c 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.pathEmail;
+
var myStepDefinitionsWrapper = function () {
this.When(/^the client makes a (.*) request to (.*)$/, function (METHOD, PATH, callback) {
@@ -18,12 +21,12 @@ 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) {
- assert.equal(err,null);
+ assert.equal(err,null);
world.getResponse().statusCode = res.statusCode;
callback();
});
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/scripts/add_users.js b/scripts/add_users.js
new file mode 100644
index 0000000..984aebe
--- /dev/null
+++ b/scripts/add_users.js
@@ -0,0 +1,98 @@
+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 || (process.env.DEFAULT_PASS ? process.env.DEFAULT_PASS : "qwerty")
+ };
+
+ 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/src/managers/email.js b/src/managers/email.js
index a295f47..d8a1f06 100644
--- a/src/managers/email.js
+++ b/src/managers/email.js
@@ -7,15 +7,16 @@ 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,
- html: emailBody
+ html: emailBody,
+ from: _settings.emailVerification.from
};
var options = {
- url: notifServiceURL + '/notification/email',
+ url: notifServiceURL + _settings.externalServices.notifications.pathEmail,
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
@@ -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) {
@@ -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.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..b7e726a
--- /dev/null
+++ b/src/managers/json_formats/profile_downloader.json
@@ -0,0 +1,20 @@
+{
+ "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/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/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/redis.js b/src/managers/redis.js
index 3846759..b64477f 100644
--- a/src/managers/redis.js
+++ b/src/managers/redis.js
@@ -24,7 +24,7 @@ function disconnect(cbk){
}
function insertKeyValue(key, value, expSeconds, cbk){
- if(!redisClient){
+ if(!isConnected){
return cbk({err:'redis_not_connected'});
}
@@ -47,7 +47,7 @@ function insertKeyValue(key, value, expSeconds, cbk){
}
function updateKeyValue(key, value, cbk){
- if(!redisClient){
+ if(!isConnected){
return cbk({err:'redis_not_connected'});
}
@@ -58,7 +58,7 @@ function updateKeyValue(key, value, cbk){
}
function getKeyValue(key, cbk){
- if(!redisClient){
+ if(!isConnected){
return cbk({err:'redis_not_connected'});
}
@@ -68,7 +68,7 @@ function getKeyValue(key, cbk){
}
function deleteKeyValue(key, cbk){
- if(!redisClient){
+ if(!isConnected){
return cbk({err:'redis_not_connected'});
}
@@ -78,7 +78,7 @@ function deleteKeyValue(key, cbk){
}
function deleteAllKeys(cbk){
- if(!redisClient){
+ if(!isConnected){
return cbk({err:'redis_not_connected'});
}
diff --git a/src/managers/token.js b/src/managers/token.js
index 9490abb..c12048a 100644
--- a/src/managers/token.js
+++ b/src/managers/token.js
@@ -15,6 +15,11 @@ var refreshTokenSettings = {
};
function createAccessToken(userId, data ,cbk){
+
+ if (!userId) {
+ return cbk({err: 'user_id_required'});
+ }
+
if(typeof data === 'function'){
cbk = data;
data = {};
@@ -31,6 +36,11 @@ function getRefreshTokenInfo(refreshToken, cbk){
}
function createRefreshToken(userId, data, cbk){
+
+ if (!userId) {
+ return cbk({err: 'user_id_required'});
+ }
+
if(typeof data === 'function'){
cbk = data;
data = {};
diff --git a/src/managers/user.js b/src/managers/user.js
index 21e2536..f2d0b70 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,12 +154,14 @@ function createUserByToken(token, cbk) {
}
var body = bodyData.data;
- var profileSchema = require('./json_formats/profile_create.json');
+ 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) {
+
return cbk({
err:'invalid_profile_data',
- des:'The data format provided is nor valid.',
+ des:'The data format provided is not valid.',
code: 400
});
}
@@ -224,6 +226,7 @@ function createUserPrivateCall(body, user, cbk){
log.info('=> POST ' + options.url);
request(options, function (err, private_res, body) {
+
if (err) {
log.error('<= error: ' + err);
return cbk({
@@ -309,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";
@@ -352,6 +377,7 @@ module.exports = function(settings) {
setPlatformData : setPlatformData,
createUser : createUser,
createUserByToken : createUserByToken,
- setPassword: setPassword
+ setPassword: setPassword,
+ validateOldPassword: validateOldPassword
};
};
diff --git a/src/middlewares/propagateRequest.js b/src/middlewares/propagateRequest.js
index f31cfff..1b1cc1c 100644
--- a/src/middlewares/propagateRequest.js
+++ b/src/middlewares/propagateRequest.js
@@ -34,6 +34,9 @@ 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
});
@@ -70,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);
@@ -83,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;
diff --git a/src/routes/user.js b/src/routes/user.js
index 7fdeab3..db5a86a 100644
--- a/src/routes/user.js
+++ b/src/routes/user.js
@@ -134,12 +134,58 @@ 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();
}
});
}
+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){
if(!req.body){
res.send(400, {
@@ -172,7 +218,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);
}
module.exports = addRoutes;
diff --git a/tests/auth.js b/tests/auth.js
index dd6d9d1..95dcbaf 100644
--- a/tests/auth.js
+++ b/tests/auth.js
@@ -2,6 +2,8 @@ var cipherlayer = require('../src/cipherlayer.js');
var config = require('../config.json');
var describeLogin = require('./auth/login.js');
+var describeLogout = require('./auth/logout.js');
+
var describeUser = require('./auth/user.js');
var describeSf = require('./auth/sf.js');
var describeIn = require('./auth/in.js');
@@ -30,6 +32,7 @@ describe('/auth', function(){
});
describeLogin.describe(accessTokenSettings, refreshTokenSettings);
+ describeLogout.describe(accessTokenSettings, refreshTokenSettings);
describeUser.describe();
describeSf.describe(accessTokenSettings, refreshTokenSettings);
describeIn.describe();
diff --git a/tests/auth/logout.js b/tests/auth/logout.js
index 98aa26b..499f346 100644
--- a/tests/auth/logout.js
+++ b/tests/auth/logout.js
@@ -4,12 +4,13 @@ var request = require('request');
var ciphertoken = require('ciphertoken');
var config = require('../../config.json');
var dao = require('../../src/managers/dao.js');
+var nock = require('nock');
var cryptoMng = require('../../src/managers/crypto')({ password : 'password' });
module.exports = {
describe: function(accessTokenSettings, refreshTokenSettings){
- describe('/login', function () {
+ describe('/logout', function () {
var baseUser = {
id: 'a1b2c3d4e5f6',
username: 'validuser' + (config.allowedDomains[0] ? config.allowedDomains[0] : ''),
@@ -43,6 +44,10 @@ module.exports = {
};
options.headers[config.version.header] = "test/1";
+ nock('http://localhost:'+ config.private_port)
+ .post('/api/me/session')
+ .reply(204);
+
request(options, function (err, res, body) {
assert.equal(err, null);
assert.equal(res.statusCode, 200, body);
@@ -61,8 +66,8 @@ module.exports = {
assert.equal(refreshTokenInfo.userId, user.id);
assert.equal(accessTokenInfo.data.deviceId, user.deviceId);
- options.url = 'http://localhost:' + config.public_port + '/auth/login';
- options.body({"userId":user.id, "deviceId": user.deviceId});
+ options.url = 'http://localhost:' + config.public_port + '/auth/logout';
+ options.body = JSON.stringify({"userId":user.id, "deviceId": user.deviceId});
request(options, function(err, res, body){
assert.equal(err, null);
assert.equal(res.statusCode, 204, body);
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'
diff --git a/tests/email.js b/tests/email.js
index 1a07965..2d6cf5e 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.pathEmail;
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/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
diff --git a/tests/managerToken.js b/tests/managerToken.js
index 4f5102e..85c3608 100644
--- a/tests/managerToken.js
+++ b/tests/managerToken.js
@@ -109,6 +109,14 @@ describe('token manager', function(){
});
});
});
+ it('empty, callback', function(done) {
+ tokenManager.createBothTokens(null, {}, function(error, tokens){
+ assert.notEqual(error, null);
+ assert.equal(error.err, 'user_id_required');
+ assert.equal(tokens, undefined);
+ done();
+ });
+ });
});
});
diff --git a/tests/managerUser.js b/tests/managerUser.js
index b62b280..1fa4061 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.pathEmail;
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/middlewares/accessTokenParams.js b/tests/middlewares/accessTokenParams.js
new file mode 100644
index 0000000..6eed2fb
--- /dev/null
+++ b/tests/middlewares/accessTokenParams.js
@@ -0,0 +1,37 @@
+var assert = require('assert');
+var accessTokenParams = require('./../../src/middlewares/accessTokenParam');
+var config = require('./../../config.json');
+
+var AT = 'abcdef123456789';
+var request;
+var response;
+
+describe('accessTokenParams middleware: ', function() {
+
+ beforeEach(function(done) {
+ request = {};
+ request.params = {};
+ request.headers = {};
+ response = {};
+ return done();
+ });
+
+ it('existing AT param', function(done) {
+
+ request.params.at = AT;
+
+ accessTokenParams(request, response, function() {
+ assert.notEqual(request.headers.authorization, undefined);
+ assert.equal(request.headers.authorization, config.authHeaderKey + AT);
+ return done();
+ });
+ });
+
+ it('non-existing AT param', function(done) {
+
+ accessTokenParams(request, response, function() {
+ assert.equal(request.headers.authorization, undefined);
+ return done();
+ });
+ });
+});
diff --git a/tests/middlewares/authHeader.js b/tests/middlewares/authHeader.js
new file mode 100644
index 0000000..d9dc8b9
--- /dev/null
+++ b/tests/middlewares/authHeader.js
@@ -0,0 +1,61 @@
+var assert = require('assert');
+var checkAuthHeader = require('./../../src/middlewares/authHeader');
+var config = require('./../../config.json');
+var _ = require('lodash');
+var request;
+var response;
+
+describe('authHeader middleware: ', function() {
+
+ beforeEach(function(done) {
+ request = {};
+ request.params = {};
+ request.headers = {};
+ request.header = function(item) {
+ return request.headers[item.toLowerCase()];
+ };
+
+ response = {};
+ response.body = {};
+ response.send = function(status, message) {
+ response.body.status = status;
+ response.body.message = message;
+ return;
+ };
+ return done();
+ });
+
+ it('has authentication header', function(done) {
+ request.headers.authorization = config.authHeaderKey + 'abcdef123456789';
+
+ checkAuthHeader(request, response, function(error) {
+ assert.deepEqual(response.body, {});
+ assert.equal(error, undefined);
+ return done();
+
+ });
+ });
+
+ it('has NOT authentication header', function(done) {
+
+ checkAuthHeader(request, response, function(error) {
+ assert.equal(response.body.status, 401);
+ assert.equal(response.body.message.err, 'unauthorized');
+ assert.equal(error, false);
+ return done();
+ });
+ });
+
+ it('has too short authentication header', function(done) {
+
+ request.headers.authorization = _.clone(config.authHeaderKey).slice(0,2);
+
+ checkAuthHeader(request, response, function(error) {
+ assert.equal(response.body.status, 401);
+ assert.equal(response.body.message.err, 'unauthorized');
+ assert.equal(error, false);
+ return done();
+ });
+ });
+
+});
diff --git a/tests/middlewares/decodeToken.js b/tests/middlewares/decodeToken.js
new file mode 100644
index 0000000..a68999d
--- /dev/null
+++ b/tests/middlewares/decodeToken.js
@@ -0,0 +1,99 @@
+var assert = require('assert');
+var sinon = require('sinon');
+
+var decodeToken = require('./../../src/middlewares/decodeToken');
+var tokenManager = require('./../../src/managers/token');
+var config = require('./../../config.json');
+
+var INVALID_AT = 'dsoiafobadjsbahof2345245boadsbkcbiilaSDGERTFGsdfn4302984252hds';
+var VALID_AT = '0MgdHFSlMFEHJo1173u62ovnj9cra8ZfEZSHFQVjpYEOu_p87W5wooN5BiayILoaHA_0X7I1QSHYKdQrFJy27pe3-RgTvw-IBqlgLgcvq7wrWRrA7gDMeSeMsr4MHye3rhtFM33Euterc0VKuN8TjTS5vafzbDtlyCk_1oVkjT4';
+
+var request;
+var response;
+var tokenMngStub;
+
+describe('decodeToken middleware: ', function() {
+
+ beforeEach(function(done) {
+ request = {};
+ request.params = {};
+ request.auth = {};
+ request.header = function(item) {
+ return request.headers[item.toLowerCase()];
+ };
+
+ response = {};
+ response.body = {};
+ response.send = function(status, message) {
+ response.body.status = status;
+ response.body.message = message;
+ return;
+ };
+
+ return done();
+ });
+
+ it('decodes token successfully', function(done) {
+
+ request.auth = config.authHeaderKey + VALID_AT;
+
+ decodeToken(request, response, function(error) {
+ assert.equal(error, null);
+ assert.notEqual(request.tokenInfo, undefined);
+ return done();
+ });
+ });
+
+ it('cannot decode a badly-formatted token', function(done) {
+
+ request.auth = config.authHeaderKey + INVALID_AT;
+
+ decodeToken(request, response, function(error) {
+ assert.equal(error, false);
+ assert.equal(response.body.status, 403);
+ assert.equal(response.body.message.err, 'invalid_token');
+ assert.equal(response.body.message.des, 'invalid authorization header');
+ return done();
+ });
+ });
+
+ it('cannot decode due to token expiration', function(done) {
+ request.auth = config.authHeaderKey + VALID_AT;
+
+ tokenMngStub = sinon.stub(tokenManager, 'getAccessTokenInfo', function(accessToken, callback) {
+ var error = {
+ err: 'accesstoken_expired'
+ };
+ return callback(error);
+ });
+
+ decodeToken(request, response, function(error) {
+ assert.equal(error, false);
+ assert.equal(response.body.status, 401);
+ assert.equal(response.body.message.err, 'expired_access_token');
+ assert.equal(response.body.message.des, 'access token expired');
+ tokenMngStub.restore();
+ return done();
+ });
+ });
+
+ it('fails to decode token', function(done) {
+ request.auth = config.authHeaderKey + VALID_AT;
+
+ tokenMngStub = sinon.stub(tokenManager, 'getAccessTokenInfo', function(accessToken, callback) {
+ var error = {
+ err: 'any_other_error'
+ };
+ return callback(error);
+ });
+
+ decodeToken(request, response, function(error) {
+ assert.equal(error, false);
+ assert.equal(response.body.status, 401);
+ assert.equal(response.body.message.err, 'invalid_access_token');
+ assert.equal(response.body.message.des, 'unable to read token info');
+ tokenMngStub.restore();
+ return done();
+ });
+ });
+});
\ No newline at end of file
diff --git a/tests/middlewares/findUser.js b/tests/middlewares/findUser.js
new file mode 100644
index 0000000..b022632
--- /dev/null
+++ b/tests/middlewares/findUser.js
@@ -0,0 +1,91 @@
+var assert = require('assert');
+var sinon = require('sinon');
+
+var config = require('./../../config.json');
+var findUser = require('./../../src/middlewares/findUser');
+var userDao = require('./../../src/managers/dao');
+var log = require('./../../src/logger/service.js');
+
+var VALID_AT = '0MgdHFSlMFEHJo1173u62ovnj9cra8ZfEZSHFQVjpYEOu_p87W5wooN5BiayILoaHA_0X7I1QSHYKdQrFJy27pe3-RgTvw-IBqlgLgcvq7wrWRrA7gDMeSeMsr4MHye3rhtFM33Euterc0VKuN8TjTS5vafzbDtlyCk_1oVkjT4';
+
+var request;
+var response;
+var findUserStub;
+var loggerSpy;
+
+var mockedUser = {
+ id:'a1b2c3d4e5f6',
+ username:'user1' + (config.allowedDomains[0] ? config.allowedDomains[0] : '') ,
+ password:'pass1'
+};
+
+describe('findUser middleware: ', function() {
+
+ before(function(done) {
+ findUserStub = sinon.stub(userDao, 'getFromId', function(userId, callback) {
+
+ if (userId === mockedUser.id) {
+ return callback(null, mockedUser);
+ }
+
+ return callback(true);
+ });
+
+ loggerSpy = sinon.spy(log, 'error');
+ return done();
+ });
+
+ after(function(done) {
+ findUserStub.restore();
+ loggerSpy.restore();
+ return done();
+ });
+
+ beforeEach(function(done) {
+ request = {
+ tokenInfo: {
+ data: {
+ roles: []
+ }
+ }
+ };
+ request._url = {};
+
+ response = {};
+ response.body = {};
+ response.send = function(status, message) {
+ response.body.status = status;
+ response.body.message = message;
+ return;
+ };
+
+ return done();
+ });
+
+ it('finds user successfully', function(done) {
+
+ request.tokenInfo.userId = mockedUser.id;
+
+ findUser(request, response, function(error) {
+ assert.equal(error, undefined);
+ assert.deepEqual(response.body, {});
+ assert.deepEqual(request.user, mockedUser);
+ return done();
+ });
+ });
+
+ it('cannot find user', function(done) {
+
+ request.tokenInfo.userId = 'invalid_user_id';
+ request.accessToken = VALID_AT;
+
+ findUser(request, response, function(error) {
+ assert.equal(error, false);
+ assert.equal(loggerSpy.calledOnce, true);
+ assert.equal(response.body.status, 401);
+ assert.equal(response.body.message.err, 'invalid_access_token');
+ assert.equal(response.body.message.des, 'unknown user inside token');
+ return done();
+ });
+ });
+});
\ No newline at end of file
diff --git a/tests/middlewares/permissions.js b/tests/middlewares/permissions.js
new file mode 100644
index 0000000..df17f93
--- /dev/null
+++ b/tests/middlewares/permissions.js
@@ -0,0 +1,70 @@
+var assert = require('assert');
+var config = require('./../../config.json');
+var checkPermissions = require('./../../src/middlewares/permissions');
+
+var request;
+var response;
+
+describe('Permissions middleware: ', function() {
+
+ beforeEach(function(done) {
+ request = {
+ tokenInfo: {
+ data: {
+ roles: []
+ }
+ }
+ };
+ request._url = {};
+
+ response = {};
+ response.body = {};
+ response.send = function(status, message) {
+ response.body.status = status;
+ response.body.message = message;
+ return;
+ };
+
+ return done();
+ });
+
+ it('passes through due to valid permissions', function(done) {
+
+ request.tokenInfo.data.roles = ['admin'];
+ request._url.pathname = '/api/profile';
+ request.method = 'PUT';
+
+ checkPermissions(request, response, function(error) {
+ assert.equal(error, undefined);
+ assert.deepEqual(response.body, {});
+ return done();
+ });
+ });
+
+ it('does not pass through due to invalid permissions', function(done) {
+ request.tokenInfo.data.roles = ['none'];
+ request._url.pathname = '/api/profile';
+ request.method = 'GET';
+
+ checkPermissions(request, response, function(error) {
+ assert.equal(error, false);
+ assert.equal(response.body.status, 401);
+ assert.equal(response.body.message.err, 'unauthorized');
+ return done();
+ });
+ });
+
+ it('skips permissions validations', function(done) {
+ config.endpoints = null;
+
+ request.tokenInfo.data.roles = ['admin'];
+ request._url.pathname = '/api/profile';
+ request.method = 'PUT';
+
+ checkPermissions(request, response, function(error) {
+ assert.equal(error, undefined);
+ assert.deepEqual(response.body, {});
+ return done();
+ });
+ });
+});
\ No newline at end of file
diff --git a/tests/middlewares/pinValidation.js b/tests/middlewares/pinValidation.js
new file mode 100644
index 0000000..bf2fc63
--- /dev/null
+++ b/tests/middlewares/pinValidation.js
@@ -0,0 +1,68 @@
+var assert = require('assert');
+var pinValidation = require('./../../src/middlewares/pinValidation');
+
+var request;
+var response;
+
+//TODO: cover remaining cases when refactoring phoneManager's module.exports as object instead of as function
+
+describe('pinValidation middleware: ', function() {
+
+ beforeEach(function(done) {
+
+ request = {
+ headers: {}
+ };
+ response = {};
+ response.body = {};
+ response.send = function(status, message) {
+ response.body.status = status;
+ response.body.message = message;
+ return;
+ };
+
+ return done();
+ });
+
+ it('skips pin validation', function(done) {
+
+ var settings = {
+ phoneVerification: null
+ };
+
+ pinValidation(settings)(request, response, function(error) {
+ assert.equal(error, undefined);
+ assert.deepEqual(response.body, {});
+ return done();
+ });
+ });
+
+ it('does not require pin validation', function(done) {
+ request.url = '/api/me/bla';
+ request.body = { country: 'ES', phone: '666666666' };
+ request.method = 'POST';
+ request.user = { id: 'mc_1a2b3c4d5e6f' };
+
+ pinValidation({})(request, response, function(error) {
+ assert.equal(error, undefined);
+ assert.deepEqual(response.body, {});
+ return done();
+ });
+ });
+
+ it('yields error due to missing user in request', function(done) {
+
+ request.url = '/api/me/phones';
+ request.headers['x-otp-pin'] = '7722';
+ request.body = { country: 'ES', phone: '666666666' };
+ request.method = 'POST';
+
+ pinValidation({})(request, response, function(error) {
+ assert.equal(error, false);
+ assert.equal(response.body.status, 401);
+ assert.equal(response.body.message.err, 'invalid_headers');
+ assert.equal(response.body.message.des, 'no user in headers');
+ return done();
+ });
+ });
+});
\ No newline at end of file
diff --git a/tests/middlewares/platformsSetUp.js b/tests/middlewares/platformsSetUp.js
new file mode 100644
index 0000000..cea841d
--- /dev/null
+++ b/tests/middlewares/platformsSetUp.js
@@ -0,0 +1,131 @@
+var assert = require('assert');
+var sinon = require('sinon');
+var platformsSetUp = require('./../../src/middlewares/platformsSetUp');
+var sfPlatform = require('./../../src/platforms/salesforce.js');
+
+var request;
+var response;
+var sfStub;
+
+describe('platformsSetUp middleware: ', function() {
+
+ before(function(done) {
+ sfStub = sinon.stub(sfPlatform, 'renewSFAccessTokenIfNecessary', function(user, platform, callback) {
+ if (user._id === '666') {
+ return callback(true);
+ }
+ return callback(null, 'some_access_token');
+ });
+ return done();
+ });
+
+ after(function(done) {
+ sfStub.restore();
+ return done();
+ });
+
+ beforeEach(function(done) {
+
+ request = {
+ headers: {},
+ options: {
+ headers: {}
+ },
+ user: {
+ _id: 'a1b2c3d4e5f6',
+ username: 'valid_user*@a.com',
+ roles: [ 'user' ],
+ signUpDate: 1446037518993,
+ platforms: [
+ {
+ platform:'sf',
+ accessToken: {
+ params: {
+ id: 'f6e5d4c3b2a1',
+ instance_url: 'http://instance.salesforce.com'
+ }
+ }
+
+ }]
+ }
+ };
+
+ request.header = function(item) {
+ return request.headers[item.toLowerCase()];
+ };
+
+ response = {};
+ response.body = {};
+ response.send = function(status, message) {
+ response.body.status = status;
+ response.body.message = message;
+ return;
+ };
+
+ return done();
+ });
+
+ it('skips the middleware for no user', function(done) {
+
+ request.user = {};
+
+ platformsSetUp(request, response, function(error) {
+ assert.equal(error, undefined);
+ assert.deepEqual(response.body, {});
+ return done();
+ });
+ });
+
+ it('gets setup for sf platform', function(done) {
+
+ var expectedHeaderContent = JSON.stringify({
+ userId: request.user.platforms[0].accessToken.params.id,
+ accessToken: 'some_access_token',
+ instanceUrl: request.user.platforms[0].accessToken.params.instance_url
+ });
+
+ platformsSetUp(request, response, function(error) {
+ assert.equal(error, undefined);
+ assert.notEqual(request.options.headers['x-sf-data'], undefined);
+ assert.equal(request.options.headers['x-in-data'], undefined);
+ var options = request.options.headers['x-sf-data'];
+ assert.equal(options, expectedHeaderContent);
+
+ return done();
+ });
+ });
+
+ it('yields error for sf setup', function(done) {
+
+ request.user._id = '666'; // User to force error in salesforce platform
+
+ platformsSetUp(request, response, function(error) {
+ assert.notEqual(error, undefined);
+ assert.equal(response.body.status, 401);
+ assert.equal(response.body.message.err, 'Could not renew SF token');
+
+ var errorStrMatch = response.body.message.des.match(/Unable to renew sales force access token/) ? true : false;
+ assert.equal(errorStrMatch, true);
+ return done();
+ });
+ });
+
+ it('gets setup for in platform', function(done) {
+
+ request.user.platforms[0].platform = 'in';
+ request.user.platforms[0].accessToken = 'some_access_token';
+
+ var expectedHeaderContent = JSON.stringify({
+ accessToken: 'some_access_token'
+ });
+
+ platformsSetUp(request, response, function(error) {
+ assert.equal(error, undefined);
+ assert.equal(request.options.headers['x-sf-data'], undefined);
+ assert.notEqual(request.options.headers['x-in-data'], undefined);
+ var options = request.options.headers['x-in-data'];
+ assert.equal(options, expectedHeaderContent);
+ return done();
+ });
+ });
+});
\ No newline at end of file
diff --git a/tests/middlewares/prepareOptions.js b/tests/middlewares/prepareOptions.js
new file mode 100644
index 0000000..c3c67e4
--- /dev/null
+++ b/tests/middlewares/prepareOptions.js
@@ -0,0 +1,105 @@
+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('request content type is application/json', function(done) {
+
+ _.extend(request, {
+ 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.equal(request.options.body, JSON.stringify(request.body));
+ 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();
+ });
+ });
+});
diff --git a/tests/middlewares/userAppVersion.js b/tests/middlewares/userAppVersion.js
new file mode 100644
index 0000000..48f6f1a
--- /dev/null
+++ b/tests/middlewares/userAppVersion.js
@@ -0,0 +1,92 @@
+var assert = require('assert');
+var sinon = require('sinon');
+var userAppVersion = require('./../../src/middlewares/userAppVersion');
+
+var request;
+var response;
+
+var userDao = require('./../../src/managers/dao');
+var userDaoStub;
+
+describe('userAppVersion middleware: ', function() {
+
+ before(function(done) {
+
+ userDaoStub = sinon.stub(userDao, 'updateField', function(userId, fieldName, fieldKey, callback) {
+
+ if (!userId) {
+ return callback(true);
+ }
+
+ return callback(null);
+ });
+
+ return done();
+ });
+
+ after(function(done) {
+ userDaoStub.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;
+ };
+
+ return done();
+ });
+
+ it('skips middleware due to missing version header', function(done) {
+
+ var settings = {
+ version: {
+ header: null
+ }
+ };
+
+ request.user = {
+ appVersion: 'stuff/1'
+ };
+
+ userAppVersion(settings)(request, response, function(error) {
+ assert.equal(error, undefined);
+ assert.deepEqual(response.body, {});
+ return done();
+ });
+ });
+
+ it('passes through the app version storage process', function(done) {
+
+ var settings = {
+ version: {
+ header: 'x-app-version'
+ }
+ };
+ request = {
+ headers: {
+ 'x-app-version': 'test/1'
+ },
+ user: {
+ _id: null,
+ appVersion: 'stuff/1'
+ }
+ };
+
+ userAppVersion(settings)(request, response, function(err) {
+ assert.notEqual(err, undefined);
+ assert.equal(response.body.status, 500);
+ assert.equal(response.body.message.err, 'proxy_error');
+ assert.equal(response.body.message.des, 'error updating user appVersion');
+ return done();
+ });
+ });
+});
\ No newline at end of file
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..a46a3dd 100644
--- a/tests/proxy/protectedCallsPassThrough.js
+++ b/tests/proxy/protectedCallsPassThrough.js
@@ -8,40 +8,38 @@ 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;
+var notificationsServicePath = config.externalServices.notifications.pathEmail;
module.exports = {
itCreated: function created(accessTokenSettings, refreshTokenSettings){
- it.skip('201 Created', function (done) {
+ it('201 Created', function (done) {
+
+ // This is required to skip the email verification step and avoid a hanging request targeted at the email verification endpoint
+ config.emailVerification = false;
+
var expectedUsername = 'valid' + (config.allowedDomains[0] ? config.allowedDomains[0] : '');
var expectedUserId = 'a1b2c3d4e5f6';
var expectedUserPhone = '111111111';
var expectedUserCountry = 'US';
var expectedPublicRequest = {};
expectedPublicRequest[config.passThroughEndpoint.username] = expectedUsername;
- expectedPublicRequest[config.passThroughEndpoint.password] = '12345678';
+ expectedPublicRequest[config.passThroughEndpoint.password] = 'P4ssword';
expectedPublicRequest.phone = expectedUserPhone;
expectedPublicRequest.country = expectedUserCountry;
- var expectedPrivateResponse = clone(expectedPublicRequest);
- delete(expectedPrivateResponse[config.passThroughEndpoint.password]);
-
- nock('http://' + config.private_host + ':' + config.private_port)
- .post(config.passThroughEndpoint.path, expectedPrivateResponse)
- .reply(201, {id: expectedUserId});
-
- var redisKey = config.redisKeys.user_phone_verify.key;
+ var redisKey = config.phoneVerification.redis.key;
redisKey = redisKey.replace('{userId}',expectedUsername).replace('{phone}','+1' + expectedUserPhone);
var pin = 'xxxx';
- redisMng.insertKeyValue(redisKey + '.pin', pin, config.redisKeys.user_phone_verify.expireInSec, function(err){
+ redisMng.insertKeyValue(redisKey + '.pin', pin, config.phoneVerification.redis.expireInSec, function(err){
assert.equal(err, null);
- redisMng.insertKeyValue(redisKey + '.attempts', config.userPIN.attempts , config.redisKeys.user_phone_verify.expireInSec, function(err){
+ redisMng.insertKeyValue(redisKey + '.attempts', config.phoneVerification.attempts , config.phoneVerification.redis.expireInSec, function(err){
assert.equal(err, null);
nock('http://' + config.private_host + ':' + config.private_port)
- .post(config.passThroughEndpoint.path, expectedPrivateResponse)
+ .post(config.passThroughEndpoint.path, expectedPublicRequest)
.reply(201, {id: expectedUserId});
var options = {
@@ -86,7 +84,10 @@ module.exports = {
});
},
itPlatformInfo: function platformInfo(accessTokenSettings, refreshTokenSettings){
- it.skip('203 Platform Info', function (done) {
+ it('203 Platform Info', function (done) {
+
+ // This is required to skip the email verification step and avoid a hanging request targeted at the email verification endpoint
+ config.emailVerification = false;
var expectedUsername = 'valid' + (config.allowedDomains[0] ? config.allowedDomains[0] : '');
var expectedUserId = 'a1b2c3d4e5f6';
@@ -111,14 +112,14 @@ module.exports = {
.post(config.passThroughEndpoint.path, expectedPrivateResponse)
.reply(203, {id: expectedUserId});
- var redisKey = config.redisKeys.user_phone_verify.key;
+ var redisKey = config.phoneVerification.redis.key;
redisKey = redisKey.replace('{userId}',expectedUsername).replace('{phone}','+1'+expectedUserPhone);
var pin = 'xxxx';
- redisMng.insertKeyValue(redisKey + '.pin', pin, config.redisKeys.user_phone_verify.expireInSec, function(err){
+ redisMng.insertKeyValue(redisKey + '.pin', pin, config.phoneVerification.redis.expireInSec, function(err){
assert.equal(err, null);
- redisMng.insertKeyValue(redisKey + '.attempts', config.userPIN.attempts , config.redisKeys.user_phone_verify.expireInSec, function(err){
+ redisMng.insertKeyValue(redisKey + '.attempts', config.phoneVerification.attempts , config.phoneVerification.redis.expireInSec, function(err){
assert.equal(err, null);
var options = {
@@ -172,32 +173,33 @@ module.exports = {
});
},
itAlreadyExists: function alreadyExists(accessTokenSettings, refreshTokenSettings){
- it.skip('409 already exists', function (done) {
+ it('409 already exists', function (done) {
+ // This is required to skip the email verification step and avoid a hanging request targeted at the email verification endpoint
+ config.emailVerification = false;
+
var expectedUsername = 'valid'+ (config.allowedDomains[0] ? config.allowedDomains[0] : '');
var expectedUserId = 'a1b2c3d4e5f6';
var expectedPublicRequest = {};
var expectedUserPhone = '222222222';
var expectedUserCountry = 'US';
+
expectedPublicRequest[config.passThroughEndpoint.username] = 'valid'+ (config.allowedDomains[0] ? config.allowedDomains[0] : '');
- expectedPublicRequest[config.passThroughEndpoint.password] = '12345678';
+ expectedPublicRequest[config.passThroughEndpoint.password] = 'P4ssword';
expectedPublicRequest.phone = expectedUserPhone;
expectedPublicRequest.country = expectedUserCountry;
- var expectedPrivateResponse = clone(expectedPublicRequest);
- delete(expectedPrivateResponse[config.passThroughEndpoint.password]);
-
nock('http://' + config.private_host + ':' + config.private_port)
- .post(config.passThroughEndpoint.path, expectedPrivateResponse)
+ .post(config.passThroughEndpoint.path, expectedPublicRequest)
.reply(201, {id: expectedUserId});
- var redisKey = config.redisKeys.user_phone_verify.key;
+ var redisKey = config.phoneVerification.redis.key;
redisKey = redisKey.replace('{userId}',expectedUsername).replace('{phone}','+1'+expectedUserPhone);
var pin = 'xxxx';
- redisMng.insertKeyValue(redisKey + '.pin', pin, config.redisKeys.user_phone_verify.expireInSec, function(err){
+ redisMng.insertKeyValue(redisKey + '.pin', pin, config.phoneVerification.redis.expireInSec, function(err){
assert.equal(err, null);
- redisMng.insertKeyValue(redisKey + '.attempts', config.userPIN.attempts , config.redisKeys.user_phone_verify.expireInSec, function(err){
+ redisMng.insertKeyValue(redisKey + '.attempts', config.phoneVerification.attempts , config.phoneVerification.redis.expireInSec, function(err){
assert.equal(err, null);
var options = {
@@ -240,6 +242,10 @@ module.exports = {
},
itNotSecurityToken: function notSecurityToken(){
it('400 not security token', function (done) {
+
+ // This is required to skip the email verification step and avoid a hanging request targeted at the email verification endpoint
+ config.emailVerification = false;
+
var expectedPublicRequest = {};
expectedPublicRequest[config.passThroughEndpoint.username] = 'valid' + (config.allowedDomains[0] ? config.allowedDomains[0] : '');
@@ -264,14 +270,29 @@ module.exports = {
});
},
itCreatedVerifyMail: function createdVerifyMail(){
- it.skip('201 Created (Verify email)', function (done) {
+ it('201 Created (Verify email)', function (done) {
+
+ config.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.",
+ "redis": {
+ "key":"user.{username}.transaction",
+ "expireInSec": 86400
+ },
+ "scheme":"mycomms",
+ "redirectUrl": "http://www.google.com"
+ };
+
var expectedUsername = 'valid' + (config.allowedDomains[0] ? config.allowedDomains[0] : '');
var expectedUserId = 'a1b2c3d4e5f6';
var expectedUserPhone = '111111111';
var expectedUserCountry = 'US';
var expectedPublicRequest = {};
expectedPublicRequest[config.passThroughEndpoint.username] = expectedUsername;
- expectedPublicRequest[config.passThroughEndpoint.password] = '12345678';
+ expectedPublicRequest[config.passThroughEndpoint.password] = 'P4ssword';
expectedPublicRequest.phone = expectedUserPhone;
expectedPublicRequest.country = expectedUserCountry;
@@ -279,22 +300,22 @@ module.exports = {
delete(expectedPrivateResponse[config.passThroughEndpoint.password]);
nock('http://' + config.private_host + ':' + config.private_port)
- .post(config.passThroughEndpoint.path, expectedPrivateResponse)
+ .post(config.passThroughEndpoint.path, expectedPublicRequest)
.times(2)
.reply(201, {id: expectedUserId});
nock(notificationsServiceURL)
- .post('/notification/email')
- .reply(204);
+ .post(notificationsServicePath)
+ .reply(200, {des: expectedUsername});
- var redisKey = config.redisKeys.user_phone_verify.key;
+ var redisKey = config.phoneVerification.redis.key;
redisKey = redisKey.replace('{userId}',expectedUsername).replace('{phone}','+1' + expectedUserPhone);
var pin = 'xxxx';
- redisMng.insertKeyValue(redisKey + '.pin', pin, config.redisKeys.user_phone_verify.expireInSec, function(err){
+ redisMng.insertKeyValue(redisKey + '.pin', pin, config.phoneVerification.redis.expireInSec, function(err){
assert.equal(err, null);
- redisMng.insertKeyValue(redisKey + '.attempts', config.userPIN.attempts, config.redisKeys.user_phone_verify.expireInSec, function(err){
+ redisMng.insertKeyValue(redisKey + '.attempts', config.phoneVerification.attempts, config.phoneVerification.redis.expireInSec, function(err){
assert.equal(err, null);
var options = {
@@ -309,13 +330,13 @@ module.exports = {
options.headers[config.version.header] = "test/1";
request(options, function (err, res, body) {
- assert.equal(err, null);
+ assert.equal(err, null);
assert.equal(res.statusCode, 200, body);
body = JSON.parse(body);
assert.deepEqual(body, {des: expectedUsername}, body);
//Check the redis transactionId for the user
- var redisKey = config.redisKeys.direct_login_transaction.key;
+ var redisKey = config.emailVerification.redis.key;
redisKey = redisKey.replace('{username}', expectedUsername);
redisMng.getKeyValue(redisKey, function(err, transactionId) {
diff --git a/tests/redis.js b/tests/redis.js
index d2f6147..d9b4755 100644
--- a/tests/redis.js
+++ b/tests/redis.js
@@ -14,13 +14,35 @@ describe('redis', function() {
var baseKey = 'key';
var baseValue = 'value';
- it('insert', function (done) {
+ it('insert: success', function (done) {
redisMng.insertKeyValue(baseKey, baseValue, 3, function(err){
assert.equal(err, null);
done();
});
});
+ it('insert: connection error', function(done) {
+ async.series([
+ disconnectRedis = function(miniDone) {
+ redisMng.disconnect(miniDone);
+ },
+ attemptInsert = function(miniDone) {
+ redisMng.insertKeyValue(baseKey, baseValue, 3, function(error) {
+ assert.notEqual(error, null);
+ assert.equal(error.err, 'redis_not_connected');
+ miniDone();
+ });
+ }
+ ], done);
+ });
+
+ it('insert: setKey error', function(done) {
+ redisMng.insertKeyValue(baseKey, null, 3, function(error) {
+ assert.notEqual(error, null);
+ done();
+ });
+ });
+
it('get', function (done) {
redisMng.getKeyValue(baseKey, function(err, value){
assert.equal(err, null);
@@ -29,7 +51,22 @@ describe('redis', function() {
});
});
- it('delete', function (done) {
+ it('get: connection error', function(done) {
+ async.series([
+ disconnectRedis = function(miniDone) {
+ redisMng.disconnect(miniDone);
+ },
+ attemptGet = function(miniDone) {
+ redisMng.getKeyValue(null, function(error, value) {
+ assert.equal(error.err, 'redis_not_connected');
+ assert.equal(value, undefined);
+ miniDone();
+ });
+ }
+ ], done);
+ });
+
+ it('delete: success', function (done) {
redisMng.deleteKeyValue(baseKey, function(err, deleted){
assert.equal(err, null);
assert.equal(deleted, true);
@@ -41,6 +78,22 @@ describe('redis', function() {
});
});
+ it('delete: connection error', function(done) {
+ async.series([
+ disconnectRedis = function(miniDone) {
+ redisMng.disconnect(miniDone);
+ },
+ attemptDelete = function(miniDone) {
+ redisMng.deleteKeyValue(baseKey, function(error, deleted) {
+ assert.notEqual(error, null);
+ assert.equal(error.err, 'redis_not_connected');
+ assert.equal(deleted, undefined);
+ miniDone();
+ });
+ }
+ ], done);
+ });
+
it('expire', function (done) {
this.timeout(4000);
async.series([
@@ -64,7 +117,7 @@ describe('redis', function() {
], done);
});
- it('update', function (done) {
+ it('update: success', function (done) {
this.timeout(4000);
var val = 'new value';
async.series([
@@ -102,7 +155,23 @@ describe('redis', function() {
], done);
});
- it('delete all', function (done) {
+ it('update: connection error', function(done) {
+ async.series([
+ disconnectRedis = function(miniDone) {
+ redisMng.disconnect(miniDone);
+ },
+ attemptUpdate = function(miniDone) {
+ redisMng.updateKeyValue(baseKey, baseValue, function(error, value) {
+ assert.notEqual(error, null);
+ assert.equal(error.err, 'redis_not_connected');
+ assert.equal(value, undefined);
+ miniDone();
+ });
+ }
+ ], done);
+ });
+
+ it('delete all: success', function (done) {
async.series([
createKey = function(done){
redisMng.insertKeyValue(baseKey, baseValue, 10, function(err){
@@ -126,4 +195,19 @@ describe('redis', function() {
], done);
});
+
+ it('delete all: connection error', function(done) {
+ async.series([
+ disconnectRedis = function(miniDone) {
+ redisMng.disconnect(miniDone);
+ },
+ attemptDeleteAll = function(miniDone) {
+ redisMng.deleteAllKeys(function(error) {
+ assert.notEqual(error, null);
+ assert.equal(error.err, 'redis_not_connected');
+ miniDone();
+ });
+ }
+ ], done);
+ });
});
diff --git a/tests/routesUser.js b/tests/routesUser.js
index b75932e..c315d56 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.pathEmail;
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] : ''),