Validate first factor through a post request
parent
a1a3da650c
commit
d21164af58
|
@ -18,6 +18,7 @@
|
|||
"url": "https://github.com/clems4ever/two-factor-auth-server/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"bluebird": "^3.4.7",
|
||||
"body-parser": "^1.15.2",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"ejs": "^2.5.5",
|
||||
|
|
|
@ -1,52 +1,13 @@
|
|||
|
||||
module.exports = {
|
||||
'authenticate': authenticate,
|
||||
'verify': verify_authentication
|
||||
}
|
||||
|
||||
var objectPath = require('object-path');
|
||||
var ldap_checker = require('./ldap_checker');
|
||||
var totp_checker = require('./totp_checker');
|
||||
var replies = require('./replies');
|
||||
var Q = require('q');
|
||||
var utils = require('./utils');
|
||||
|
||||
|
||||
function authenticate(req, res) {
|
||||
var defer = Q.defer();
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
var token = req.body.token;
|
||||
console.log('Start authentication of user %s', username);
|
||||
|
||||
if(!username || !password || !token) {
|
||||
replies.authentication_failed(res);
|
||||
return;
|
||||
}
|
||||
|
||||
var jwt_engine = req.app.get('jwt engine');
|
||||
var ldap_client = req.app.get('ldap client');
|
||||
var totp_engine = req.app.get('totp engine');
|
||||
var config = req.app.get('config');
|
||||
|
||||
var totp_promise = totp_checker.validate(totp_engine, token, config.totp_secret);
|
||||
var credentials_promise = ldap_checker.validate(ldap_client, username, password, config.ldap_users_dn);
|
||||
|
||||
Q.all([totp_promise, credentials_promise])
|
||||
.then(function() {
|
||||
var token = jwt_engine.sign({ user: username }, config.jwt_expiration_time);
|
||||
replies.authentication_succeeded(res, username, token);
|
||||
console.log('Authentication succeeded');
|
||||
defer.resolve();
|
||||
})
|
||||
.fail(function(err1, err2) {
|
||||
console.log('Authentication failed', err1, err2);
|
||||
replies.authentication_failed(res);
|
||||
defer.reject();
|
||||
});
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
function verify_authentication(req, res) {
|
||||
console.log('Verify authentication');
|
||||
|
||||
|
|
|
@ -3,12 +3,11 @@ module.exports = {
|
|||
'validate': validateCredentials
|
||||
}
|
||||
|
||||
var Q = require('q');
|
||||
var util = require('util');
|
||||
var utils = require('./utils');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
function validateCredentials(ldap_client, username, password, users_dn) {
|
||||
var userDN = util.format("cn=%s,%s", username, users_dn);
|
||||
var bind_promised = utils.promisify(ldap_client.bind, ldap_client);
|
||||
var userDN = util.format("binding entry cn=%s,%s", username, users_dn);
|
||||
var bind_promised = Promise.promisify(ldap_client.bind, ldap_client);
|
||||
return bind_promised(userDN, password);
|
||||
}
|
|
@ -1,21 +1,19 @@
|
|||
|
||||
var first_factor = require('./routes/first_factor');
|
||||
|
||||
module.exports = {
|
||||
'auth': serveAuth,
|
||||
'login': serveLogin,
|
||||
'logout': serveLogout
|
||||
auth: serveAuth,
|
||||
login: serveLogin,
|
||||
logout: serveLogout,
|
||||
first_factor: first_factor
|
||||
}
|
||||
|
||||
var authentication = require('./authentication');
|
||||
var replies = require('./replies');
|
||||
|
||||
function serveAuth(req, res) {
|
||||
if(req.method == 'POST') {
|
||||
serveAuthPost(req, res);
|
||||
}
|
||||
else {
|
||||
serveAuthGet(req, res);
|
||||
}
|
||||
}
|
||||
|
||||
function serveAuthGet(req, res) {
|
||||
authentication.verify(req, res)
|
||||
|
@ -28,10 +26,6 @@ function serveAuthGet(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
function serveAuthPost(req, res) {
|
||||
authentication.authenticate(req, res);
|
||||
}
|
||||
|
||||
function serveLogin(req, res) {
|
||||
res.render('login');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
module.exports = first_factor;
|
||||
|
||||
var ldap = require('../ldap');
|
||||
|
||||
function first_factor(req, res) {
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
console.log('Start authentication of user %s', username);
|
||||
|
||||
if(!username || !password) {
|
||||
replies.authentication_failed(res);
|
||||
return;
|
||||
}
|
||||
|
||||
var ldap_client = req.app.get('ldap client');
|
||||
var config = req.app.get('config');
|
||||
|
||||
ldap.validate(ldap_client, username, password, config.ldap_users_dn)
|
||||
.then(function() {
|
||||
res.status(204);
|
||||
res.send();
|
||||
console.log('LDAP binding successful');
|
||||
})
|
||||
.error(function(err) {
|
||||
res.status(401);
|
||||
res.send();
|
||||
console.log('LDAP binding failed:', err);
|
||||
});
|
||||
}
|
|
@ -33,7 +33,8 @@ function run(config, ldap_client) {
|
|||
app.get ('/logout', routes.logout);
|
||||
|
||||
app.get ('/_auth', routes.auth);
|
||||
app.post ('/_auth', routes.auth);
|
||||
|
||||
app.post ('/_auth/1stfactor', routes.first_factor);
|
||||
|
||||
app.listen(config.port, function(err) {
|
||||
console.log('Listening on %d...', config.port);
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
|
||||
var sinon = require('sinon');
|
||||
var Promise = require('bluebird');
|
||||
var assert = require('assert');
|
||||
var first_factor = require('../../../src/lib/routes/first_factor');
|
||||
|
||||
describe('test the first factor validation route', function() {
|
||||
it('should return status code 204 when LDAP binding succeeds', function() {
|
||||
return test_first_factor_promised({ error: undefined, data: undefined }, 204);
|
||||
});
|
||||
|
||||
it('should return status code 401 when LDAP binding fails', function() {
|
||||
return test_first_factor_promised({ error: 'ldap failed', data: undefined }, 401);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function test_first_factor_promised(bind_params, statusCode) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
test_first_factor(bind_params, statusCode, resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
function test_first_factor(bind_params, statusCode, resolve, reject) {
|
||||
var send = sinon.spy(function(data) {
|
||||
resolve();
|
||||
});
|
||||
var status = sinon.spy(function(code) {
|
||||
assert.equal(code, statusCode);
|
||||
});
|
||||
|
||||
var bind_mock = sinon.stub().yields(bind_params.error, bind_params.data);
|
||||
var ldap_interface_mock = {
|
||||
bind: bind_mock
|
||||
}
|
||||
var config = {
|
||||
ldap_users_dn: 'dc=example,dc=com'
|
||||
}
|
||||
|
||||
var app_get = sinon.stub();
|
||||
app_get.withArgs('ldap client').returns(ldap_interface_mock);
|
||||
app_get.withArgs('config').returns(ldap_interface_mock);
|
||||
var req = {
|
||||
app: {
|
||||
get: app_get
|
||||
},
|
||||
body: {
|
||||
username: 'username',
|
||||
password: 'password'
|
||||
}
|
||||
}
|
||||
var res = {
|
||||
send: send,
|
||||
status: status
|
||||
}
|
||||
|
||||
first_factor(req, res);
|
||||
}
|
|
@ -75,35 +75,7 @@ function create_mocks() {
|
|||
}
|
||||
}
|
||||
|
||||
describe('test jwt', function() {
|
||||
describe('test authentication', function() {
|
||||
it('should authenticate user successfuly', function(done) {
|
||||
var jwt_token = 'jwt_token';
|
||||
var clock = sinon.useFakeTimers();
|
||||
var mocks = create_mocks();
|
||||
authentication.authenticate(mocks.req, mocks.res)
|
||||
.then(function() {
|
||||
clock.restore();
|
||||
assert(mocks.res.status.calledWith(200));
|
||||
assert(mocks.res.send.calledWith(jwt_token));
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should fail authentication', function(done) {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var mocks = create_mocks();
|
||||
mocks.totp.returns('wrong token');
|
||||
authentication.authenticate(mocks.req, mocks.res)
|
||||
.fail(function(err) {
|
||||
clock.restore();
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('test verify authentication', function() {
|
||||
describe('test authentication token verification', function() {
|
||||
it('should be already authenticated', function(done) {
|
||||
var mocks = create_mocks();
|
||||
var data = { user: 'username' };
|
||||
|
@ -130,5 +102,4 @@ describe('test jwt', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
var ldap_checker = require('../../src/lib/ldap_checker');
|
||||
var ldap = require('../../src/lib/ldap');
|
||||
var sinon = require('sinon');
|
||||
var sinonPromise = require('sinon-promise');
|
||||
|
||||
|
@ -17,10 +17,10 @@ function test_validate(bind_mock) {
|
|||
bind: bind_mock
|
||||
}
|
||||
|
||||
return ldap_checker.validate(ldap_client_mock, username, password, ldap_url, users_dn);
|
||||
return ldap.validate(ldap_client_mock, username, password, ldap_url, users_dn);
|
||||
}
|
||||
|
||||
describe('test ldap checker', function() {
|
||||
describe('test ldap validation', function() {
|
||||
it('should bind the user if good credentials provided', function() {
|
||||
var bind_mock = sinon.mock().yields();
|
||||
return test_validate(bind_mock);
|
||||
|
@ -29,7 +29,7 @@ describe('test ldap checker', function() {
|
|||
it('should not bind the user if wrong credentials provided', function() {
|
||||
var bind_mock = sinon.mock().yields('wrong credentials');
|
||||
var promise = test_validate(bind_mock);
|
||||
return promise.fail(autoResolving);
|
||||
return promise.error(autoResolving);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@ var request = require('request');
|
|||
var assert = require('assert');
|
||||
var speakeasy = require('speakeasy');
|
||||
var sinon = require('sinon');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
var request = Promise.promisifyAll(request);
|
||||
|
||||
var BASE_URL = 'http://localhost:8090';
|
||||
|
||||
|
@ -46,8 +49,8 @@ describe('test the server', function() {
|
|||
test_get_auth(jwt);
|
||||
});
|
||||
|
||||
describe('test POST /_auth', function() {
|
||||
test_post_auth(jwt);
|
||||
describe('test POST /_auth/1stfactor', function() {
|
||||
test_post_auth_1st_factor();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -95,50 +98,64 @@ function test_get_auth(jwt) {
|
|||
});
|
||||
}
|
||||
|
||||
function test_post_auth() {
|
||||
it('should return the JWT token when authentication is successful', function(done) {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var real_token = speakeasy.totp({
|
||||
secret: 'totp_secret',
|
||||
encoding: 'base32'
|
||||
});
|
||||
var expectedJwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdF9vayIsImlhdCI6MCwiZXhwIjozNjAwfQ.ihvaljGjO5h3iSO_h3PkNNSCYeePyB8Hr5lfVZZYyrQ';
|
||||
|
||||
request.post(BASE_URL + '/_auth', {
|
||||
function test_post_auth_1st_factor() {
|
||||
it('should return status code 204 when ldap bind is successful', function() {
|
||||
request.postAsync(BASE_URL + '/_auth/1stfactor', {
|
||||
form: {
|
||||
username: 'test_ok',
|
||||
password: 'password',
|
||||
token: real_token
|
||||
}
|
||||
},
|
||||
function (error, response, body) {
|
||||
if (!error && response.statusCode == 200) {
|
||||
assert.equal(body, expectedJwt);
|
||||
clock.restore();
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return invalid authentication status code', function(done) {
|
||||
var clock = sinon.useFakeTimers();
|
||||
var real_token = speakeasy.totp({
|
||||
secret: 'totp_secret',
|
||||
encoding: 'base32'
|
||||
});
|
||||
var data = {
|
||||
form: {
|
||||
username: 'test_nok',
|
||||
password: 'password',
|
||||
token: real_token
|
||||
}
|
||||
}
|
||||
|
||||
request.post(BASE_URL + '/_auth', data, function (error, response, body) {
|
||||
if(response.statusCode == 401) {
|
||||
clock.restore();
|
||||
done();
|
||||
username: 'username',
|
||||
password: 'password'
|
||||
}
|
||||
})
|
||||
.then(function(response) {
|
||||
assert.equal(response.statusCode, 204);
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
// function test_post_auth_totp() {
|
||||
// it('should return the JWT token when authentication is successful', function(done) {
|
||||
// var clock = sinon.useFakeTimers();
|
||||
// var real_token = speakeasy.totp({
|
||||
// secret: 'totp_secret',
|
||||
// encoding: 'base32'
|
||||
// });
|
||||
// var expectedJwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdF9vayIsImlhdCI6MCwiZXhwIjozNjAwfQ.ihvaljGjO5h3iSO_h3PkNNSCYeePyB8Hr5lfVZZYyrQ';
|
||||
//
|
||||
// request.post(BASE_URL + '/_auth/totp', {
|
||||
// form: {
|
||||
// username: 'test_ok',
|
||||
// password: 'password',
|
||||
// token: real_token
|
||||
// }
|
||||
// },
|
||||
// function (error, response, body) {
|
||||
// if (!error && response.statusCode == 200) {
|
||||
// assert.equal(body, expectedJwt);
|
||||
// clock.restore();
|
||||
// done();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// it('should return invalid authentication status code', function(done) {
|
||||
// var clock = sinon.useFakeTimers();
|
||||
// var real_token = speakeasy.totp({
|
||||
// secret: 'totp_secret',
|
||||
// encoding: 'base32'
|
||||
// });
|
||||
// var data = {
|
||||
// form: {
|
||||
// username: 'test_nok',
|
||||
// password: 'password',
|
||||
// token: real_token
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// request.post(BASE_URL + '/_auth/totp', data, function (error, response, body) {
|
||||
// if(response.statusCode == 401) {
|
||||
// clock.restore();
|
||||
// done();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
|
Loading…
Reference in New Issue