2016-12-17 01:06:40 +00:00
|
|
|
|
|
2016-12-17 23:07:56 +00:00
|
|
|
|
var server = require('../../src/lib/server');
|
2017-03-25 14:17:21 +00:00
|
|
|
|
var Ldap = require('../../src/lib/ldap');
|
2016-12-17 23:07:56 +00:00
|
|
|
|
|
2017-01-27 00:20:03 +00:00
|
|
|
|
var Promise = require('bluebird');
|
|
|
|
|
var request = Promise.promisifyAll(require('request'));
|
2016-12-17 01:06:40 +00:00
|
|
|
|
var assert = require('assert');
|
|
|
|
|
var speakeasy = require('speakeasy');
|
|
|
|
|
var sinon = require('sinon');
|
2017-01-28 00:32:25 +00:00
|
|
|
|
var MockDate = require('mockdate');
|
2017-03-15 22:07:57 +00:00
|
|
|
|
var session = require('express-session');
|
2017-03-25 14:17:21 +00:00
|
|
|
|
var winston = require('winston');
|
|
|
|
|
var speakeasy = require('speakeasy');
|
|
|
|
|
var ldapjs = require('ldapjs');
|
2016-12-17 01:06:40 +00:00
|
|
|
|
|
2017-01-27 00:20:03 +00:00
|
|
|
|
var PORT = 8090;
|
|
|
|
|
var BASE_URL = 'http://localhost:' + PORT;
|
|
|
|
|
var requests = require('./requests')(PORT);
|
2016-12-17 23:07:56 +00:00
|
|
|
|
|
2016-12-17 01:06:40 +00:00
|
|
|
|
describe('test the server', function() {
|
2017-01-21 16:41:06 +00:00
|
|
|
|
var _server
|
2017-01-27 00:20:03 +00:00
|
|
|
|
var deps;
|
|
|
|
|
var u2f, nedb;
|
|
|
|
|
var transporter;
|
|
|
|
|
var collection;
|
2016-12-17 01:06:40 +00:00
|
|
|
|
|
2017-01-21 16:41:06 +00:00
|
|
|
|
beforeEach(function(done) {
|
2016-12-17 01:06:40 +00:00
|
|
|
|
var config = {
|
2017-01-27 00:20:03 +00:00
|
|
|
|
port: PORT,
|
2016-12-17 01:06:40 +00:00
|
|
|
|
totp_secret: 'totp_secret',
|
2017-03-21 19:57:03 +00:00
|
|
|
|
ldap: {
|
|
|
|
|
url: 'ldap://127.0.0.1:389',
|
2017-03-25 14:17:21 +00:00
|
|
|
|
base_dn: 'ou=users,dc=example,dc=com',
|
|
|
|
|
user_name_attribute: 'cn',
|
2017-03-21 19:57:03 +00:00
|
|
|
|
user: 'cn=admin,dc=example,dc=com',
|
|
|
|
|
password: 'password',
|
|
|
|
|
},
|
|
|
|
|
session: {
|
|
|
|
|
secret: 'session_secret',
|
|
|
|
|
expiration: 50000,
|
|
|
|
|
},
|
2017-01-28 00:32:25 +00:00
|
|
|
|
store_in_memory: true,
|
2017-01-28 18:59:15 +00:00
|
|
|
|
notifier: {
|
|
|
|
|
gmail: {
|
|
|
|
|
user: 'user@example.com',
|
|
|
|
|
pass: 'password'
|
|
|
|
|
}
|
2017-01-22 16:54:45 +00:00
|
|
|
|
}
|
2016-12-17 01:06:40 +00:00
|
|
|
|
};
|
|
|
|
|
|
2017-03-25 14:17:21 +00:00
|
|
|
|
var ldap_client = {
|
|
|
|
|
bind: sinon.stub(),
|
|
|
|
|
search: sinon.stub(),
|
|
|
|
|
modify: sinon.stub(),
|
|
|
|
|
on: sinon.spy()
|
|
|
|
|
};
|
|
|
|
|
var ldap = {
|
|
|
|
|
Change: sinon.spy(),
|
|
|
|
|
createClient: sinon.spy(function() {
|
|
|
|
|
return ldap_client;
|
|
|
|
|
})
|
|
|
|
|
};
|
|
|
|
|
|
2017-01-21 16:41:06 +00:00
|
|
|
|
u2f = {};
|
|
|
|
|
u2f.startRegistration = sinon.stub();
|
|
|
|
|
u2f.finishRegistration = sinon.stub();
|
|
|
|
|
u2f.startAuthentication = sinon.stub();
|
|
|
|
|
u2f.finishAuthentication = sinon.stub();
|
|
|
|
|
|
2017-01-28 00:32:25 +00:00
|
|
|
|
nedb = require('nedb');
|
|
|
|
|
|
2017-01-27 00:20:03 +00:00
|
|
|
|
transporter = {};
|
|
|
|
|
transporter.sendMail = sinon.stub().yields();
|
|
|
|
|
|
|
|
|
|
var nodemailer = {};
|
|
|
|
|
nodemailer.createTransport = sinon.spy(function() {
|
|
|
|
|
return transporter;
|
|
|
|
|
});
|
|
|
|
|
|
2017-03-25 14:17:21 +00:00
|
|
|
|
ldap_document = {
|
2017-01-22 16:54:45 +00:00
|
|
|
|
object: {
|
2017-03-25 14:17:21 +00:00
|
|
|
|
mail: 'test_ok@example.com',
|
2017-01-22 16:54:45 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var search_res = {};
|
|
|
|
|
search_res.on = sinon.spy(function(event, fn) {
|
2017-03-25 14:17:21 +00:00
|
|
|
|
if(event != 'error') fn(ldap_document);
|
2017-01-22 16:54:45 +00:00
|
|
|
|
});
|
|
|
|
|
|
2016-12-17 01:06:40 +00:00
|
|
|
|
ldap_client.bind.withArgs('cn=test_ok,ou=users,dc=example,dc=com',
|
|
|
|
|
'password').yields(undefined);
|
2017-01-27 00:20:03 +00:00
|
|
|
|
ldap_client.bind.withArgs('cn=admin,dc=example,dc=com',
|
|
|
|
|
'password').yields(undefined);
|
2017-01-28 00:32:25 +00:00
|
|
|
|
|
2016-12-17 18:36:41 +00:00
|
|
|
|
ldap_client.bind.withArgs('cn=test_nok,ou=users,dc=example,dc=com',
|
|
|
|
|
'password').yields('error');
|
2017-01-28 00:32:25 +00:00
|
|
|
|
|
2017-01-27 00:20:03 +00:00
|
|
|
|
ldap_client.modify.yields(undefined);
|
2017-01-28 00:32:25 +00:00
|
|
|
|
ldap_client.search.yields(undefined, search_res);
|
2017-01-27 00:20:03 +00:00
|
|
|
|
|
|
|
|
|
var deps = {};
|
|
|
|
|
deps.u2f = u2f;
|
|
|
|
|
deps.nedb = nedb;
|
|
|
|
|
deps.nodemailer = nodemailer;
|
2017-03-25 14:17:21 +00:00
|
|
|
|
deps.ldapjs = ldap;
|
2017-03-15 22:07:57 +00:00
|
|
|
|
deps.session = session;
|
2017-03-25 14:17:21 +00:00
|
|
|
|
deps.winston = winston;
|
|
|
|
|
deps.speakeasy = speakeasy;
|
2017-01-27 00:20:03 +00:00
|
|
|
|
|
2017-03-25 14:17:21 +00:00
|
|
|
|
_server = server.run(config, deps, function() {
|
2017-01-21 16:41:06 +00:00
|
|
|
|
done();
|
|
|
|
|
});
|
2016-12-17 01:06:40 +00:00
|
|
|
|
});
|
|
|
|
|
|
2017-01-21 16:41:06 +00:00
|
|
|
|
afterEach(function() {
|
|
|
|
|
_server.close();
|
|
|
|
|
});
|
2016-12-17 18:36:41 +00:00
|
|
|
|
|
|
|
|
|
describe('test GET /login', function() {
|
2017-01-28 00:32:25 +00:00
|
|
|
|
test_login();
|
2016-12-17 18:36:41 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('test GET /logout', function() {
|
2017-01-28 00:32:25 +00:00
|
|
|
|
test_logout();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('test GET /reset-password-form', function() {
|
|
|
|
|
test_reset_password_form();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('test endpoints locks', function() {
|
|
|
|
|
function should_post_and_reply_with(url, status_code) {
|
|
|
|
|
return request.postAsync(url).then(function(response) {
|
|
|
|
|
assert.equal(response.statusCode, status_code);
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function should_get_and_reply_with(url, status_code) {
|
|
|
|
|
return request.getAsync(url).then(function(response) {
|
|
|
|
|
assert.equal(response.statusCode, status_code);
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function should_post_and_reply_with_403(url) {
|
|
|
|
|
return should_post_and_reply_with(url, 403);
|
|
|
|
|
}
|
|
|
|
|
function should_get_and_reply_with_403(url) {
|
|
|
|
|
return should_get_and_reply_with(url, 403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function should_post_and_reply_with_401(url) {
|
|
|
|
|
return should_post_and_reply_with(url, 401);
|
|
|
|
|
}
|
|
|
|
|
function should_get_and_reply_with_401(url) {
|
|
|
|
|
return should_get_and_reply_with(url, 401);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function should_get_and_post_reply_with_403(url) {
|
|
|
|
|
var p1 = should_post_and_reply_with_403(url);
|
|
|
|
|
var p2 = should_get_and_reply_with_403(url);
|
|
|
|
|
return Promise.all([p1, p2]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it('should block /authentication/new-password', function() {
|
|
|
|
|
return should_post_and_reply_with_403(BASE_URL + '/authentication/new-password')
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should block /authentication/u2f-register', function() {
|
|
|
|
|
return should_get_and_post_reply_with_403(BASE_URL + '/authentication/u2f-register');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should block /authentication/reset-password', function() {
|
|
|
|
|
return should_get_and_post_reply_with_403(BASE_URL + '/authentication/reset-password');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should block /authentication/2ndfactor/u2f/register_request', function() {
|
|
|
|
|
return should_get_and_reply_with_403(BASE_URL + '/authentication/2ndfactor/u2f/register_request');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should block /authentication/2ndfactor/u2f/register', function() {
|
|
|
|
|
return should_post_and_reply_with_403(BASE_URL + '/authentication/2ndfactor/u2f/register');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should block /authentication/2ndfactor/u2f/sign_request', function() {
|
|
|
|
|
return should_get_and_reply_with_403(BASE_URL + '/authentication/2ndfactor/u2f/sign_request');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should block /authentication/2ndfactor/u2f/sign', function() {
|
|
|
|
|
return should_post_and_reply_with_403(BASE_URL + '/authentication/2ndfactor/u2f/sign');
|
|
|
|
|
});
|
2016-12-17 18:36:41 +00:00
|
|
|
|
});
|
|
|
|
|
|
2017-01-21 16:41:06 +00:00
|
|
|
|
describe('test authentication and verification', function() {
|
|
|
|
|
test_authentication();
|
2017-01-27 00:20:03 +00:00
|
|
|
|
test_reset_password();
|
2017-01-28 00:32:25 +00:00
|
|
|
|
test_regulation();
|
2016-12-17 01:06:40 +00:00
|
|
|
|
});
|
2017-01-19 00:44:24 +00:00
|
|
|
|
|
2017-01-28 00:32:25 +00:00
|
|
|
|
function test_reset_password_form() {
|
|
|
|
|
it('should serve the reset password form page', function(done) {
|
|
|
|
|
request.getAsync(BASE_URL + '/authentication/reset-password-form')
|
|
|
|
|
.then(function(response) {
|
|
|
|
|
assert.equal(response.statusCode, 200);
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-21 16:41:06 +00:00
|
|
|
|
function test_login() {
|
|
|
|
|
it('should serve the login page', function(done) {
|
2017-01-27 00:20:03 +00:00
|
|
|
|
request.getAsync(BASE_URL + '/authentication/login')
|
2017-01-21 16:41:06 +00:00
|
|
|
|
.then(function(response) {
|
|
|
|
|
assert.equal(response.statusCode, 200);
|
2017-01-19 00:44:24 +00:00
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-01-21 16:41:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function test_logout() {
|
|
|
|
|
it('should logout and redirect to /', function(done) {
|
2017-01-27 00:20:03 +00:00
|
|
|
|
request.getAsync(BASE_URL + '/authentication/logout')
|
2017-01-21 16:41:06 +00:00
|
|
|
|
.then(function(response) {
|
|
|
|
|
assert.equal(response.req.path, '/');
|
|
|
|
|
done();
|
|
|
|
|
});
|
2016-12-17 18:36:41 +00:00
|
|
|
|
});
|
2017-01-21 16:41:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function test_authentication() {
|
|
|
|
|
it('should return status code 401 when user is not authenticated', function() {
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return request.getAsync({ url: BASE_URL + '/authentication/verify' })
|
2017-01-21 16:41:06 +00:00
|
|
|
|
.then(function(response) {
|
|
|
|
|
assert.equal(response.statusCode, 401);
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return status code 204 when user is authenticated using totp', function() {
|
|
|
|
|
var j = request.jar();
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.login(j)
|
2017-01-21 16:41:06 +00:00
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 200, 'get login page failed');
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.first_factor(j);
|
2017-01-21 16:41:06 +00:00
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'first factor failed');
|
2017-01-28 17:27:54 +00:00
|
|
|
|
return requests.register_totp(j, transporter);
|
|
|
|
|
})
|
|
|
|
|
.then(function(secret) {
|
|
|
|
|
var sec = JSON.parse(secret);
|
|
|
|
|
var real_token = speakeasy.totp({
|
|
|
|
|
secret: sec.base32,
|
|
|
|
|
encoding: 'base32'
|
|
|
|
|
});
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.totp(j, real_token);
|
2017-01-21 16:41:06 +00:00
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'second factor failed');
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.verify(j);
|
2017-01-21 16:41:06 +00:00
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'verify failed');
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2017-01-22 16:54:45 +00:00
|
|
|
|
it('should keep session variables when login page is reloaded', function() {
|
|
|
|
|
var real_token = speakeasy.totp({
|
|
|
|
|
secret: 'totp_secret',
|
|
|
|
|
encoding: 'base32'
|
|
|
|
|
});
|
|
|
|
|
var j = request.jar();
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.login(j)
|
2017-01-22 16:54:45 +00:00
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 200, 'get login page failed');
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.first_factor(j);
|
2017-01-22 16:54:45 +00:00
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'first factor failed');
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.totp(j, real_token);
|
2017-01-22 16:54:45 +00:00
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'second factor failed');
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.login(j);
|
2017-01-22 16:54:45 +00:00
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 200, 'login page loading failed');
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.verify(j);
|
2017-01-22 16:54:45 +00:00
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'verify failed');
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
})
|
|
|
|
|
.catch(function(err) {
|
|
|
|
|
console.error(err);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2017-01-21 16:41:06 +00:00
|
|
|
|
it('should return status code 204 when user is authenticated using u2f', function() {
|
|
|
|
|
var sign_request = {};
|
|
|
|
|
var sign_status = {};
|
|
|
|
|
var registration_request = {};
|
|
|
|
|
var registration_status = {};
|
|
|
|
|
u2f.startRegistration.returns(Promise.resolve(sign_request));
|
|
|
|
|
u2f.finishRegistration.returns(Promise.resolve(sign_status));
|
|
|
|
|
u2f.startAuthentication.returns(Promise.resolve(registration_request));
|
|
|
|
|
u2f.finishAuthentication.returns(Promise.resolve(registration_status));
|
2017-01-27 00:20:03 +00:00
|
|
|
|
|
2017-01-21 16:41:06 +00:00
|
|
|
|
var j = request.jar();
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.login(j)
|
2017-01-21 16:41:06 +00:00
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 200, 'get login page failed');
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.first_factor(j);
|
2017-01-21 16:41:06 +00:00
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'first factor failed');
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.u2f_registration(j, transporter);
|
2017-01-21 16:41:06 +00:00
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'second factor, finish register failed');
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.u2f_authentication(j);
|
2017-01-21 16:41:06 +00:00
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'second factor, finish sign failed');
|
2017-01-27 00:20:03 +00:00
|
|
|
|
return requests.verify(j);
|
2017-01-21 16:41:06 +00:00
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'verify failed');
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
2017-01-27 00:20:03 +00:00
|
|
|
|
|
|
|
|
|
function test_reset_password() {
|
|
|
|
|
it('should reset the password', function() {
|
|
|
|
|
var j = request.jar();
|
|
|
|
|
return requests.login(j)
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 200, 'get login page failed');
|
|
|
|
|
return requests.first_factor(j);
|
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'first factor failed');
|
|
|
|
|
return requests.reset_password(j, transporter, 'user', 'new-password');
|
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 204, 'second factor, finish register failed');
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
2017-01-28 00:32:25 +00:00
|
|
|
|
|
|
|
|
|
function test_regulation() {
|
|
|
|
|
it('should regulate authentication', function() {
|
|
|
|
|
var j = request.jar();
|
|
|
|
|
MockDate.set('1/2/2017 00:00:00');
|
|
|
|
|
return requests.login(j)
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 200, 'get login page failed');
|
|
|
|
|
return requests.failing_first_factor(j);
|
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 401, 'first factor failed');
|
|
|
|
|
return requests.failing_first_factor(j);
|
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 401, 'first factor failed');
|
|
|
|
|
return requests.failing_first_factor(j);
|
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 401, 'first factor failed');
|
|
|
|
|
return requests.failing_first_factor(j);
|
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 403, 'first factor failed');
|
|
|
|
|
MockDate.set('1/2/2017 00:30:00');
|
|
|
|
|
return requests.failing_first_factor(j);
|
|
|
|
|
})
|
|
|
|
|
.then(function(res) {
|
|
|
|
|
assert.equal(res.statusCode, 401, 'first factor failed');
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
}
|
2017-01-21 16:41:06 +00:00
|
|
|
|
});
|
|
|
|
|
|