diff --git a/config_sample.json b/config_sample.json index 4f3869f..f3d6686 100644 --- a/config_sample.json +++ b/config_sample.json @@ -76,7 +76,15 @@ "clientSecret": "{{GOOGLE_CLIENT_SECRET}}", "callbackURL": "http://localhost:3000/auth/google/callback" }, - "aws": { + "facebook": { + "registerByToken": true, + "requestFields": "name,email,picture", + "fieldsMap": { + "name": "fullname", + "email": "email" + } + }, + "aws":{ "accessKeyId": "{{AWS_ACCESKEYID}}", "secretAccessKey": "{{AWS_SECRETACCESKEY}}", "region": "us-west-2", @@ -84,6 +92,12 @@ "avatars": "example-avatars" } }, + "validators": { + "profile": { + "path": "", + "filename": "profile_create.json" + } + }, "phoneVerification": { "pinSize": 4, "attempts": 3, @@ -104,6 +118,7 @@ }, "emailVerification": { "subject": "Example email verification", + "from": "hello@example.com", "body": "

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

here

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

", "compatibleEmailDevices": [ "*iPhone*", @@ -116,10 +131,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", @@ -133,9 +152,11 @@ "db": "mongodb://localhost/versionControl?w=1" }, "allowedDomains": [ - "*@a.com" + "*@a.com", + "*@facebook.com" ], - "password": { + "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}", @@ -178,5 +199,8 @@ }, "directProxyUrls": [ "\/upload$" + ], + "allowedHeaders": [ + "x-custom-header" ] } diff --git a/config_schema.json b/config_schema.json index ab5dbda..631b00c 100644 --- a/config_schema.json +++ b/config_schema.json @@ -40,6 +40,7 @@ "properties": { "path": {"type": "string", "required": true}, "username": {"type": "string", "required": true}, + "email": {"type": "string", "required": false}, "password": {"type": "string", "required": true} }, "required": true diff --git a/features/forgot_passwd.feature b/features/forgot_passwd.feature index a4eaeec..152ae85 100644 --- a/features/forgot_passwd.feature +++ b/features/forgot_passwd.feature @@ -6,9 +6,9 @@ Feature: client application requests recover password When the client makes a request to Then the response status code is 204 - Examples: - | METHOD | PATH | - | GET | /user/:email/password | + Examples: + | METHOD | PATH | + | GET | /user/:email/password | @service @@ -22,6 +22,6 @@ Feature: client application requests recover password And the response body contains json attribute "refreshToken" And the response body contains json attribute "expiresIn" - Examples: - | METHOD | PATH | - | GET | /user/:email/password | + Examples: + | METHOD | PATH | + | GET | /user/:email/password | diff --git a/features/proxy.feature b/features/proxy.feature index b8ee7d7..434594d 100644 --- a/features/proxy.feature +++ b/features/proxy.feature @@ -8,6 +8,18 @@ Feature: reverse proxy protects an applicacion behind cipherlayer Then the response status code is And the response body must be Examples: - | 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"} | + | 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"} | + + @service + 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/restrictedArea.feature b/features/restrictedArea.feature index 7b5b082..e678a50 100644 --- a/features/restrictedArea.feature +++ b/features/restrictedArea.feature @@ -17,9 +17,9 @@ Feature: client application logs in with admin role When the application makes a with to a protected Then the response status code is And the response body must be - Examples: - | PATH | METHOD | STATUS | REQUEST_PAYLOAD | RESPONSE_PAYLOAD | - | /api/profile | GET | 200 | {} | {"data":[]} | + Examples: + | PATH | METHOD | STATUS | REQUEST_PAYLOAD | RESPONSE_PAYLOAD | + | /api/profile | GET | 200 | {} | {"data":[]} | @service @@ -28,9 +28,9 @@ Feature: client application logs in with admin role And a protected service replies to a request with to with status and a body "" When the application makes a with to a protected Then the response status code is - Examples: - | PATH | METHOD | STATUS | REQUEST_PAYLOAD | - | /api/profile | PUT | 204 | {} | + Examples: + | PATH | METHOD | STATUS | REQUEST_PAYLOAD | + | /api/profile | PUT | 204 | {} | @service @@ -40,7 +40,7 @@ Feature: client application logs in with admin role When the application makes a with to a protected Then the response status code is And the response body must be - Examples: - | PATH | METHOD | STATUS | REQUEST_PAYLOAD | RESPONSE_PAYLOAD | - | /api/profile | GET | 401 | | {"err":"unauthorized"} | - | /api/profile | PUT | 401 | {"key":"value"} | {"err":"unauthorized"} | + Examples: + | PATH | METHOD | STATUS | REQUEST_PAYLOAD | RESPONSE_PAYLOAD | + | /api/profile | GET | 401 | | {"err":"unauthorized"} | + | /api/profile | PUT | 401 | {"key":"value"} | {"err":"unauthorized"} | 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/login_invalid_username.js b/features/step_definitions/login_invalid_username.js index 338aac6..1905ae4 100644 --- a/features/step_definitions/login_invalid_username.js +++ b/features/step_definitions/login_invalid_username.js @@ -6,6 +6,7 @@ var config = require('../../config.json'); module.exports = function(){ this.When(/^the client app requests log in the protected application with username substring/, function (callback) { var username = world.getUser().username; + console.log('Email', username); world.getUser().username = username.slice(0, username.length / 2); var options = { diff --git a/features/step_definitions/method_request_to_path.js b/features/step_definitions/method_request_to_path.js index db90069..71f554f 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) { @@ -20,12 +23,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; world.getResponse().headers = res.headers; 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/package.json b/package.json index 2ddb7ba..7c0f77b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "coverage-2": "cp coverage/lcov.info coverage/cucumber.lcov", "coverage-3": "node_modules/.bin/mocha tests --recursive --require blanket -R mocha-lcov-reporter > coverage/mocha.lcov", "coverage-4": "node_modules/.bin/lcov-result-merger 'coverage/*.lcov' 'coverage/merged.lcov'", - "coverage-5": "cat ./coverage/merged.lcov | ./node_modules/coveralls/bin/coveralls.js" + "coverage-5": "cat ./coverage/merged.lcov | ./node_modules/coveralls/bin/coveralls.js --verbose" }, "dependencies": { "async": "^0.9.0", 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/cipherlayer.js b/src/cipherlayer.js index b407367..1ea6802 100644 --- a/src/cipherlayer.js +++ b/src/cipherlayer.js @@ -115,7 +115,8 @@ function startListener(publicPort, internalPort, cbk){ "/auth/google", "/auth/google/*", "/user/activate*", - "/heartbeat" + "/heartbeat", + "/user/email/available" ]; publicServer.use(versionControl(versionControlOptions)); diff --git a/src/managers/dao.js b/src/managers/dao.js index 37060d4..1d89ca5 100644 --- a/src/managers/dao.js +++ b/src/managers/dao.js @@ -6,6 +6,7 @@ var escapeRegexp = require('escape-regexp'); var config = require(process.cwd() + '/config.json'); var mongoClient = require('mongodb').MongoClient; var ObjectID = require('mongodb').ObjectID; +var _ = require('lodash'); var ERROR_USER_NOT_FOUND = 'user_not_found'; var ERROR_USERNAME_ALREADY_EXISTS = 'username_already_exists'; @@ -117,6 +118,28 @@ function countUsers(cbk){ }); } +function findByEmail(email, callback) { + + var targetEmail = new RegExp("^"+escapeRegexp(email.toLowerCase())+"$", "i"); + + + usersCollection.find({username: targetEmail}, {password: 0}).toArray(function(error, foundUsers) { + + if (error) { + return callback({statusCode: 500, body: { + err: 'InternalError', + des: 'User lookup failed' + }}); + } + + if (_.isEmpty(foundUsers)) { + return callback(null, {available: true}); + } + + return callback(null, {available: false}); + }); +} + function getFromUsername(username, cbk){ if(!username){ return cbk({err:'invalid_username'}, null); @@ -346,6 +369,6 @@ module.exports = { getRealms: getRealms, resetRealmsVariables: resetRealmsVariables, deleteAllRealms: deleteAllRealms, - + findByEmail:findByEmail, getStatus: getStatus }; diff --git a/src/managers/email.js b/src/managers/email.js index a295f47..913a6ae 100644 --- a/src/managers/email.js +++ b/src/managers/email.js @@ -4,18 +4,21 @@ var ciphertoken = require('ciphertoken'); var crypto = require('crypto'); var redisMng = require('./redis'); +var config = require(process.cwd() + '/config.json'); + var _settings = {}; function sendEmailVerification(email, subject, emailBody, cbk){ - 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' }, @@ -47,7 +50,7 @@ function emailVerification(email, bodyData, cbk){ var transactionId = crypto.pseudoRandomBytes(12).toString('hex'); var redisKey = _settings.emailVerification.redis.key; - redisKey = redisKey.replace('{username}', bodyData.email); + redisKey = redisKey.replace('{username}', bodyData[config.passThroughEndpoint.email || 'email' ]); var redisExp = _settings.emailVerification.redis.expireInSec; redisMng.insertKeyValue(redisKey, transactionId, redisExp, function(err) { @@ -63,7 +66,7 @@ function emailVerification(email, bodyData, cbk){ tokenExpirationMinutes: redisExp }; - ciphertoken.createToken(tokenSettings, bodyData.email, null, bodyData, function(err, token){ + ciphertoken.createToken(tokenSettings, bodyData[config.passThroughEndpoint.email || 'email' ], null, bodyData, function(err, token){ if(err){ return cbk(err); } @@ -72,7 +75,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 +98,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' }, @@ -125,4 +127,4 @@ module.exports = function(settings) { emailVerification: emailVerification, sendEmailForgotPassword:sendEmailForgotPassword }; -}; \ No newline at end of file +}; 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/user.js b/src/managers/user.js index 2fab657..2d7dd6c 100644 --- a/src/managers/user.js +++ b/src/managers/user.js @@ -89,6 +89,16 @@ function createUser(body, pin, cbk) { }); } + if (body.fb) { + user.platforms = [{ + platform: 'fb', + accessToken: body.fb.accessToken + }]; + delete body.fb; + createUserPrivateCall(body, user, cbk); + return; + } + var phone = body.phone; var countryISO = body.country; phoneMng(_settings).verifyPhone(user.username, phone, countryISO, pin, function (err) { @@ -132,7 +142,6 @@ function createUser(body, pin, cbk) { }); }); }); - } function createUserByToken(token, cbk) { @@ -163,7 +172,7 @@ function createUserByToken(token, cbk) { 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 }); } @@ -362,6 +371,28 @@ function setPassword(id, body, cbk) { } } +function validateOldPassword(username, oldPassword, cbk) { + + daoMng.getAllUserFields(username, function (err, user) { + if (err) { + res.send(401, err); + return next(); + } + + cryptoMng.encrypt(oldPassword, function (encrypted) { + if (user.password !== encrypted) { + return cbk({ + err: 'invalid_old_password', + des: 'invalid password', + code: 401 + }); + } + + return cbk(); + }); + }); +} + //Aux functions function random(howMany, chars) { chars = chars || "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789"; @@ -436,6 +467,7 @@ module.exports = function (settings) { setPlatformData: setPlatformData, createUser: createUser, createUserByToken: createUserByToken, - setPassword: setPassword + setPassword: setPassword, + validateOldPassword: validateOldPassword }; }; diff --git a/src/middlewares/prepareOptions.js b/src/middlewares/prepareOptions.js index b8b292e..163869e 100644 --- a/src/middlewares/prepareOptions.js +++ b/src/middlewares/prepareOptions.js @@ -36,7 +36,9 @@ function prepareOptions (req, res, next){ options.formData = formData; } else { options.headers['Content-Type'] = req.header('Content-Type'); - options.body = JSON.stringify(req.body); + if(req.body) { + options.body = JSON.stringify(req.body); + } } req.options = options; return next(); diff --git a/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/public_routes/auth_facebook.js b/src/public_routes/auth_facebook.js new file mode 100644 index 0000000..a0290d1 --- /dev/null +++ b/src/public_routes/auth_facebook.js @@ -0,0 +1,202 @@ +var request = require('request'); +var crypto = require('crypto'); +var _ = require('lodash'); + +var log = require('../logger/service.js'); +var daoMng = require('../managers/dao'); +var userMng = require('../managers/user')(); +var tokenMng = require('../managers/token'); +var config = require(process.cwd() + '/config.json'); + +var defaultOptions = { + url: 'https://graph.facebook.com/v2.5/me', + json: true, + method: 'GET', + qs: { + fields: config.facebook ? config.facebook.requestFields : null, + format: 'json', + method: 'get', + pretty: 0, + suppress_http_code: 1 + } +}; + +function mapFacebookData(body, fieldsMap) { + var mappedData = {}; + + if(!fieldsMap) return mappedData; + + _.each(_.keys(fieldsMap), function(fb_key) { + var profile_key = fieldsMap[fb_key]; + if (fb_key === 'profile_picture') { + mappedData[profile_key] = body.picture ? body.picture.data.url : null; + return; + } + + if (fb_key === 'email' && !body[fb_key]) { + body[fb_key] = body.id + '@facebook.com'; + } + + if (!body[fb_key]) { + return; + } + + mappedData[profile_key] = body[fb_key]; + }); + + return mappedData; +} + + +function postAuthRegisterFacebook(req, res, next) { + + var options = _.clone(defaultOptions); + options.qs.access_token = req.body.accessToken; + + + if (!config.facebook) { + res.send(400, { + err: 'facebook_login_disabled', + des: 'Facebook login is not configured' + }); + return next(false); + } + + if (!req.body && !req.body.accessToken) { + res.send(400, { + err: 'missing_facebook_token', + des: 'Missing facebook access_token' + }); + return next(false); + } + + request(options, function(err, fb_res, fb_body) { + + if (err) { + res.send(409, {err: err.message}); + return next(); + } + + if (fb_body.error) { + res.send(409, {err: fb_body.error.type, des: fb_body.error.message}); + return next(); + } + + var fbUserProfile = mapFacebookData(fb_body, config.facebook.fieldsMap); + var fbUserProfileUsername = fbUserProfile[config.facebook.fieldsMap.email || 'email']; + + daoMng.getFromUsername(fbUserProfileUsername, function(err, foundUser) { + // RETURNING FACEBOOK USER + + if (!err) { + var platform = { + platform:'fb', + accessToken: req.body.accessToken + }; + + userMng.setPlatformData(foundUser._id, 'fb', platform, function(err) { + if(err){ + log.error({err:err}, 'error updating sf tokens into user '+foundUser._id+''); + } + + var data = {}; + if(foundUser.roles){ + data.roles = foundUser.roles; + } + + if(config.version){ + data.deviceVersion = req.headers[config.version.header]; + } + + tokenMng.createBothTokens(foundUser._id, data , function(err, tokens) { + if(err) { + res.send(409,{err: err.message}); + } else { + tokens.expiresIn = config.accessToken.expiration * 60; + res.send(200, tokens); + } + return next(false); + }); + + }); + return; + } + + // NEW FACEBOOK USER + + if (err && err.message === daoMng.ERROR_USER_NOT_FOUND) { + + if (!config.facebook.registerByToken) { + res.send(401, { + err: 'facebook_user_not_registered', + des: 'This user need registration before login' + }); + return next(false); + } + + fbUserProfile.fb = { + accessToken: req.body.accessToken + }; + + fbUserProfile.password = random(12); + + userMng.createUser(fbUserProfile, null, function (err, tokens) { + if (err) { + if (!err.code) { + res.send(500, err); + } else { + var errCode = err.code; + delete(err.code); + res.send(errCode, err); + } + return next(false); + } + + tokenMng.getRefreshTokenInfo(tokens.refreshToken, function (err, tokenSet) { + var userId = tokenSet.userId; + var tokenData = tokenSet.data; + + if (config.version) { + tokenData.deviceVersion = req.headers[config.version.header]; + } + + tokenMng.createBothTokens(userId, tokenData, function (err, tokens) { + tokens.expiresIn = config.accessToken.expiration * 60; + res.send(201, tokens); + return next(); + }); + }); + + }); + + return; + } + + if (err) { + res.send(500, {err:'internal_error', des:'There was an internal error checking facebook profile'}); + return next(false); + } + + }); + + }); +} + +// TODO: extract to common util file +function random (howMany, chars) { + chars = chars || "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789"; + var rnd = crypto.randomBytes(howMany), + value = new Array(howMany), + len = chars.length; + + for (var i = 0; i < howMany; i++) { + value[i] = chars[rnd[i] % len]; + } + return value.join(''); +} + +function addRoutes(service) { + service.post('/auth/login/facebook', postAuthRegisterFacebook); +} + +module.exports = addRoutes; diff --git a/src/public_routes/user.js b/src/public_routes/user.js index 7d89a49..3768150 100644 --- a/src/public_routes/user.js +++ b/src/public_routes/user.js @@ -13,6 +13,8 @@ var checkAccessTokenParam = require('../middlewares/accessTokenParam.js'); var checkAuthHeader = require('../middlewares/authHeader.js'); var decodeToken = require('../middlewares/decodeToken.js'); var findUser = require('../middlewares/findUser.js'); +var _ = require('lodash'); +var log = require('../logger/service.js'); function sendNewPassword(req, res, next) { if (!req.params.email) { @@ -189,12 +191,63 @@ function createUserByToken(req, res, next) { return next(false); } } + + if(req.method === 'POST') { + res.send(200, tokens); + return next(); + } + + 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); + } + + + 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, { @@ -221,13 +274,37 @@ function setPassword(req, res, next) { }); } +function checkEmailAvailable(req, res, next) { + var email = req.body.email; + + if (_.isEmpty(email)) { + res.send(400, { + err: 'BadRequestError', + des: 'Missing email in request body' + }); + return next(); + } + + + daoMng.findByEmail(email, function(error, output) { + if (error) { + res.send(error.statusCode, error.body); + return next(); + } + + res.send(200, output); + return next(); + }); +} + function addRoutes(service) { service.get('/user/:email/password', sendNewPassword); service.post(config.passThroughEndpoint.path, createUserEndpoint); service.get('/user/activate', createUserByToken); - - service.put('/user/me/password', checkAccessTokenParam, checkAuthHeader, decodeToken, findUser, setPassword); + service.post('/user/activate', createUserByToken); + service.post('/user/email/available', checkEmailAvailable); + 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 5483677..981389d 100644 --- a/tests/auth.js +++ b/tests/auth.js @@ -5,6 +5,7 @@ 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 describeFbToken = require('./auth/facebook_token.js'); var describeIn = require('./auth/in.js'); var describeGoogle = require('./auth/google.js'); var describeRenew = require('./auth/renew.js'); @@ -35,6 +36,7 @@ describe('/auth', function(){ describeUser.describe(); describeSf.describe(accessTokenSettings, refreshTokenSettings); describeIn.describe(); + describeFbToken.describe(); describeGoogle.describe(); describeRenew.describe(); }); diff --git a/tests/auth/facebook_token.js b/tests/auth/facebook_token.js new file mode 100644 index 0000000..8a556b9 --- /dev/null +++ b/tests/auth/facebook_token.js @@ -0,0 +1,156 @@ +var assert = require('assert'); +var request = require('request'); +var nock = require('nock'); +var clone = require('clone'); + +var config = require('../../config.json'); +var userDao = require('../../src/managers/dao.js'); + +var OPTIONS = { + url: 'http://localhost:' + config.public_port + '/auth/login/facebook', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + json: true, + body: { + accessToken: 'abcd1234' + }, + method: 'POST', + followRedirect: false +}; + +var baseUser = { + id: '1234', + email: 'test@a.com', + password: 'pass1' +}; + +var FB_PROFILE = { + name: "Test User", + email: "test@a.com", + id: "fb1234" +}; + +module.exports = { + describe: function() { + describe('/facebook_token', function() { + + beforeEach(function(done) { + userDao.deleteAllUsers(function(err){ + assert.equal(err, null); + done(); + }); + }); + + it('exchanges facebook token for an existing cipherlayer user', function(done) { + nockFBGraphCall(FB_PROFILE, OPTIONS.body.accessToken, config.facebook.requestFields); + + var options = clone(OPTIONS); + options.url ='http://localhost:' + config.public_port + '/auth/login/facebook'; + options.headers[config.version.header] = "test/1"; + + var existingUser = clone(baseUser); + existingUser.username = existingUser.email; + delete existingUser.email; + + userDao.addUser()(existingUser, function(error) { + assert.equal(error, null); + + request(options, function (err, res, body) { + assert.ok(body.accessToken); + assert.ok(body.refreshToken); + assert.ok(body.expiresIn); + + userDao.getFromUsername(baseUser.email, function(error, user) { + assert.ok(user); + assert.equal(user.username, existingUser.username); + assert.ok(user.platforms); + var fbPlatform = user.platforms[0]; + assert.equal(fbPlatform.platform, 'fb'); + assert.equal(fbPlatform.accessToken, OPTIONS.body.accessToken); + return done(); + }); + }); + }); + }); + + it('exchanges facebook token for new user', function(done) { + nockFBGraphCall(FB_PROFILE, OPTIONS.body.accessToken, config.facebook.requestFields); + nockPrivateCall(config, baseUser.id); + + var options = clone(OPTIONS); + options.url ='http://localhost:' + config.public_port + '/auth/login/facebook'; + options.headers[config.version.header] = "test/1"; + + request(options, function(err, res, body) { + assert.equal(err, null); + assert.ok(body.accessToken); + assert.ok(body.refreshToken); + assert.ok(body.expiresIn); + + userDao.getFromUsername(baseUser.email, function(err, foundUser) { + assert.equal(err, null); + assert.ok(foundUser); + assert.equal(foundUser.username, baseUser.email); + assert.ok(foundUser.platforms); + var fbPlatform = foundUser.platforms[0]; + assert.equal(fbPlatform.platform, 'fb'); + assert.equal(fbPlatform.accessToken, OPTIONS.body.accessToken); + done(); + }); + }); + }); + + it('creates a user with a facebook domain email when username field is missing', function(done) { + + var noEmailUser = clone(baseUser); + delete noEmailUser.email; + + var madeUpEmailFbProfile = clone(FB_PROFILE); + delete madeUpEmailFbProfile.email; + + var userEmail = 'fb' + noEmailUser.id + '@facebook.com'; + + nockFBGraphCall(madeUpEmailFbProfile, OPTIONS.body.accessToken, config.facebook.requestFields); + nockPrivateCall(config, noEmailUser.id); + + var options = clone(OPTIONS); + options.url ='http://localhost:' + config.public_port + '/auth/login/facebook'; + options.headers[config.version.header] = "test/1"; + + request(options, function(err, res, body) { + assert.equal(err, null); + assert.ok(body.accessToken); + assert.ok(body.refreshToken); + assert.ok(body.expiresIn); + + userDao.getFromUsername(userEmail, function (err, foundUser) { + assert.equal(err, null); + assert.ok(foundUser); + assert.equal(foundUser.username, userEmail); + assert.ok(foundUser.platforms); + var fbPlatform = foundUser.platforms[0]; + assert.equal(fbPlatform.platform, 'fb'); + assert.equal(fbPlatform.accessToken, OPTIONS.body.accessToken); + done(); + }); + }); + }); + }); + } +}; + + +function nockFBGraphCall(profile, access_token, fields) { + fields = encodeURIComponent(fields); + nock('https://graph.facebook.com') + .get('/v2.5/me?fields=' + fields + '&format=json&method=get&pretty=0&suppress_http_code=1' + '&access_token=' + access_token) + .reply(200, profile); +} + +function nockPrivateCall(config, userId) { + nock('http://' + config.private_host + ':' + config.private_port) + .post(config.passThroughEndpoint.path) + .reply(201, {id: userId}); +} + diff --git a/tests/auth/user.js b/tests/auth/user.js index 17472bf..8baa348 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/emailAvailable.js b/tests/emailAvailable.js new file mode 100644 index 0000000..1acfc4c --- /dev/null +++ b/tests/emailAvailable.js @@ -0,0 +1,97 @@ +var assert = require('assert'); +var request = require('request'); + +var cipherlayer = require('../src/cipherlayer'); +var config = require('../config.json'); +var userDao = require('../src/managers/dao'); + +var baseUser = { + id: 'a1b2c3d4e5f6', + username: 'user@example.com', + password: 'pass1' +}; + +describe('Check Email Available endpoint', function() { + + beforeEach(function(done) { + cipherlayer.start(config.public_port, config.internal_port, function(error) { + assert.equal(error, null); + userDao.deleteAllUsers(function(error) { + assert.equal(error, null); + return done(); + }); + }); + }); + + afterEach(function(done) { + cipherlayer.stop(done); + }); + + it('should indicate that requested email is available', function(done) { + + var requestOptions = { + url: 'http://localhost:' + config.public_port + '/user/email/available', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + method: 'POST', + json: true, + body: { + email: baseUser.username + } + }; + + request(requestOptions, function(err, res, body) { + assert.equal(err, null); + assert.equal(res.statusCode, 200); + assert.equal(body.available, true); + return done(); + }); + }); + + it('should indicate that requested email is unavailable', function(done) { + var requestOptions = { + url: 'http://localhost:' + config.public_port + '/user/email/available', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + method: 'POST', + json: true, + body: { + email: baseUser.username + } + }; + + userDao.addUser()(baseUser, function(error) { + + assert.equal(error, null); + + request(requestOptions, function(err, res, body) { + assert.equal(err, null); + assert.equal(res.statusCode, 200); + assert.equal(body.available, false); + return done(); + }); + }); + }); + + it('should return a BadRequestError on missing email component', function(done) { + var requestOptions = { + url: 'http://localhost:' + config.public_port + '/user/email/available', + headers: { + 'Content-Type': 'application/json; charset=utf-8' + }, + method: 'POST', + json: true, + body: {} + }; + + request(requestOptions, function(err, res) { + assert.equal(err, null); + assert.equal(res.statusCode, 400); + assert.equal(res.body.err, 'BadRequestError'); + assert.equal(res.body.des, 'Missing email in request body'); + return done(); + }); + }); +}); \ No newline at end of file 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/managerUser.js b/tests/managerUser.js index dcae2a5..03af34e 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/phone.js b/tests/phone.js index b94914a..2356b2f 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 e832306..038cb7c 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/prepareOptions.js b/tests/prepareOptions.js new file mode 100644 index 0000000..8fe6fc8 --- /dev/null +++ b/tests/prepareOptions.js @@ -0,0 +1,157 @@ +var assert = require('assert'); +var sinon = require('sinon'); +var prepareOptions = require('./../src/middlewares/prepareOptions'); +var _ = require('lodash'); +var fs = require('fs'); +var request; +var response; + +var fsStub; + +describe('prepareOptions middleware: ', function() { + + before(function(done) { + fsStub = sinon.stub(fs, 'createReadStream'); + return done(); + }); + + after(function(done) { + fsStub.restore(); + return done(); + }); + + beforeEach(function(done) { + + request = { + headers: {} + }; + response = {}; + response.body = {}; + response.send = function(status, message) { + response.body.status = status; + response.body.message = message; + return; + }; + request.header = function(item) { + return request.headers[item.toLowerCase()]; + }; + + return done(); + }); + + it('POST request content type is application/json', function(done) { + + _.extend(request, { + method: 'POST', + headers: { + 'content-type': 'application/json; charset=utf-8', + 'host': 'localhost:3000' + }, + tokenInfo: { + userId: '1234567890' + }, + connection: { + remoteAddress: '::ffff:127.0.0.1' + }, + body: { + item1: 'value1', + item2: 'value2' + }, + files: { + file1: 'stuff', + file2: 'moreStuff' + } + }); + + prepareOptions(request, response, function(error) { + assert.equal(error, undefined); + assert.notEqual(request.options, undefined); + assert.equal(request.options.headers['Content-Type'], request.headers['content-type']); + assert.ok(request.options.body); + assert.equal(request.options.body, JSON.stringify(request.body)); + return done(); + }); + }); + + it('GET request with content type application/json does not have a body in options', function(done) { + + _.extend(request, { + method: 'GET', + headers: { + 'content-type': 'application/json; charset=utf-8', + 'host': 'localhost:3000' + }, + tokenInfo: { + userId: '1234567890' + }, + connection: { + remoteAddress: '::ffff:127.0.0.1' + } + }); + + prepareOptions(request, response, function(error) { + assert.equal(error, undefined); + assert.notEqual(request.options, undefined); + assert.equal(request.options.headers['Content-Type'], request.headers['content-type']); + assert.equal(request.options.body, undefined); + return done(); + }); + }); + + it('DELETE request with content type application/json does not have a body in options', function(done) { + + _.extend(request, { + method: 'DELETE', + headers: { + 'content-type': 'application/json; charset=utf-8', + 'host': 'localhost:3000' + }, + tokenInfo: { + userId: '1234567890' + }, + connection: { + remoteAddress: '::ffff:127.0.0.1' + } + }); + + prepareOptions(request, response, function(error) { + assert.equal(error, undefined); + assert.notEqual(request.options, undefined); + assert.equal(request.options.headers['Content-Type'], request.headers['content-type']); + assert.equal(request.options.body, undefined); + return done(); + }); + }); + + it('request content type is multipart/form-data', function(done) { + + _.extend(request, { + headers: { + 'content-type': 'multipart/form-data', + 'host': 'localhost:3000' + }, + tokenInfo: { + userId: '1234567890' + }, + connection: { + remoteAddress: '::ffff:127.0.0.1' + }, + body: { + item1: 'value1', + item2: 'value2' + }, + files: { + file1: 'stuff', + file2: 'moreStuff' + } + }); + + prepareOptions(request, response, function(error) { + assert.equal(error, undefined); + assert.notEqual(request.options, undefined); + assert.notEqual(request.options.formData, undefined); + assert.equal(fsStub.calledTwice, true); + return done(); + }); + }); +}); \ No newline at end of file diff --git a/tests/proxy/protectedCallsPassThrough.js b/tests/proxy/protectedCallsPassThrough.js index fcef3ab..9e244ef 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 4f8be52..16638a6 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 0975bf5..272bec2 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 && config.allowedDomains[0] ? config.allowedDomains[0].replace('*','') : ''),