mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Validate first factor through a post request
This commit is contained in:
parent
a1a3da650c
commit
d21164af58
|
@ -18,6 +18,7 @@
|
||||||
"url": "https://github.com/clems4ever/two-factor-auth-server/issues"
|
"url": "https://github.com/clems4ever/two-factor-auth-server/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bluebird": "^3.4.7",
|
||||||
"body-parser": "^1.15.2",
|
"body-parser": "^1.15.2",
|
||||||
"cookie-parser": "^1.4.3",
|
"cookie-parser": "^1.4.3",
|
||||||
"ejs": "^2.5.5",
|
"ejs": "^2.5.5",
|
||||||
|
|
|
@ -1,52 +1,13 @@
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'authenticate': authenticate,
|
|
||||||
'verify': verify_authentication
|
'verify': verify_authentication
|
||||||
}
|
}
|
||||||
|
|
||||||
var objectPath = require('object-path');
|
var objectPath = require('object-path');
|
||||||
var ldap_checker = require('./ldap_checker');
|
|
||||||
var totp_checker = require('./totp_checker');
|
var totp_checker = require('./totp_checker');
|
||||||
var replies = require('./replies');
|
var replies = require('./replies');
|
||||||
var Q = require('q');
|
|
||||||
var utils = require('./utils');
|
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) {
|
function verify_authentication(req, res) {
|
||||||
console.log('Verify authentication');
|
console.log('Verify authentication');
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,11 @@ module.exports = {
|
||||||
'validate': validateCredentials
|
'validate': validateCredentials
|
||||||
}
|
}
|
||||||
|
|
||||||
var Q = require('q');
|
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var utils = require('./utils');
|
var Promise = require('bluebird');
|
||||||
|
|
||||||
function validateCredentials(ldap_client, username, password, users_dn) {
|
function validateCredentials(ldap_client, username, password, users_dn) {
|
||||||
var userDN = util.format("cn=%s,%s", username, users_dn);
|
var userDN = util.format("binding entry cn=%s,%s", username, users_dn);
|
||||||
var bind_promised = utils.promisify(ldap_client.bind, ldap_client);
|
var bind_promised = Promise.promisify(ldap_client.bind, ldap_client);
|
||||||
return bind_promised(userDN, password);
|
return bind_promised(userDN, password);
|
||||||
}
|
}
|
|
@ -1,20 +1,18 @@
|
||||||
|
|
||||||
|
var first_factor = require('./routes/first_factor');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'auth': serveAuth,
|
auth: serveAuth,
|
||||||
'login': serveLogin,
|
login: serveLogin,
|
||||||
'logout': serveLogout
|
logout: serveLogout,
|
||||||
|
first_factor: first_factor
|
||||||
}
|
}
|
||||||
|
|
||||||
var authentication = require('./authentication');
|
var authentication = require('./authentication');
|
||||||
var replies = require('./replies');
|
var replies = require('./replies');
|
||||||
|
|
||||||
function serveAuth(req, res) {
|
function serveAuth(req, res) {
|
||||||
if(req.method == 'POST') {
|
serveAuthGet(req, res);
|
||||||
serveAuthPost(req, res);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
serveAuthGet(req, res);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function serveAuthGet(req, res) {
|
function serveAuthGet(req, res) {
|
||||||
|
@ -28,10 +26,6 @@ function serveAuthGet(req, res) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function serveAuthPost(req, res) {
|
|
||||||
authentication.authenticate(req, res);
|
|
||||||
}
|
|
||||||
|
|
||||||
function serveLogin(req, res) {
|
function serveLogin(req, res) {
|
||||||
res.render('login');
|
res.render('login');
|
||||||
}
|
}
|
||||||
|
|
30
src/lib/routes/first_factor.js
Normal file
30
src/lib/routes/first_factor.js
Normal file
|
@ -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 ('/logout', routes.logout);
|
||||||
|
|
||||||
app.get ('/_auth', routes.auth);
|
app.get ('/_auth', routes.auth);
|
||||||
app.post ('/_auth', routes.auth);
|
|
||||||
|
app.post ('/_auth/1stfactor', routes.first_factor);
|
||||||
|
|
||||||
app.listen(config.port, function(err) {
|
app.listen(config.port, function(err) {
|
||||||
console.log('Listening on %d...', config.port);
|
console.log('Listening on %d...', config.port);
|
||||||
|
|
58
test/unitary/routes/test_first_factor.js
Normal file
58
test/unitary/routes/test_first_factor.js
Normal file
|
@ -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,59 +75,30 @@ function create_mocks() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('test jwt', function() {
|
describe('test authentication token verification', function() {
|
||||||
describe('test authentication', function() {
|
it('should be already authenticated', function(done) {
|
||||||
it('should authenticate user successfuly', function(done) {
|
var mocks = create_mocks();
|
||||||
var jwt_token = 'jwt_token';
|
var data = { user: 'username' };
|
||||||
var clock = sinon.useFakeTimers();
|
mocks.req.app.get.withArgs('jwt engine').returns({
|
||||||
var mocks = create_mocks();
|
verify: sinon.promise().resolves(data)
|
||||||
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) {
|
authentication.verify(mocks.req, mocks.res)
|
||||||
var clock = sinon.useFakeTimers();
|
.then(function(actual_data) {
|
||||||
var mocks = create_mocks();
|
assert.equal(actual_data, data);
|
||||||
mocks.totp.returns('wrong token');
|
done();
|
||||||
authentication.authenticate(mocks.req, mocks.res)
|
|
||||||
.fail(function(err) {
|
|
||||||
clock.restore();
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not be already authenticated', function(done) {
|
||||||
describe('test verify authentication', function() {
|
var mocks = create_mocks();
|
||||||
it('should be already authenticated', function(done) {
|
var data = { user: 'username' };
|
||||||
var mocks = create_mocks();
|
mocks.req.app.get.withArgs('jwt engine').returns({
|
||||||
var data = { user: 'username' };
|
verify: sinon.promise().rejects('Error with JWT token')
|
||||||
mocks.req.app.get.withArgs('jwt engine').returns({
|
|
||||||
verify: sinon.promise().resolves(data)
|
|
||||||
});
|
|
||||||
|
|
||||||
authentication.verify(mocks.req, mocks.res)
|
|
||||||
.then(function(actual_data) {
|
|
||||||
assert.equal(actual_data, data);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
return authentication.verify(mocks.req, mocks.res, mocks.args)
|
||||||
it('should not be already authenticated', function(done) {
|
.fail(function() {
|
||||||
var mocks = create_mocks();
|
done();
|
||||||
var data = { user: 'username' };
|
|
||||||
mocks.req.app.get.withArgs('jwt engine').returns({
|
|
||||||
verify: sinon.promise().rejects('Error with JWT token')
|
|
||||||
});
|
|
||||||
return authentication.verify(mocks.req, mocks.res, mocks.args)
|
|
||||||
.fail(function() {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
var ldap_checker = require('../../src/lib/ldap_checker');
|
var ldap = require('../../src/lib/ldap');
|
||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
var sinonPromise = require('sinon-promise');
|
var sinonPromise = require('sinon-promise');
|
||||||
|
|
||||||
|
@ -17,10 +17,10 @@ function test_validate(bind_mock) {
|
||||||
bind: 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() {
|
it('should bind the user if good credentials provided', function() {
|
||||||
var bind_mock = sinon.mock().yields();
|
var bind_mock = sinon.mock().yields();
|
||||||
return test_validate(bind_mock);
|
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() {
|
it('should not bind the user if wrong credentials provided', function() {
|
||||||
var bind_mock = sinon.mock().yields('wrong credentials');
|
var bind_mock = sinon.mock().yields('wrong credentials');
|
||||||
var promise = test_validate(bind_mock);
|
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 assert = require('assert');
|
||||||
var speakeasy = require('speakeasy');
|
var speakeasy = require('speakeasy');
|
||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
|
var Promise = require('bluebird');
|
||||||
|
|
||||||
|
var request = Promise.promisifyAll(request);
|
||||||
|
|
||||||
var BASE_URL = 'http://localhost:8090';
|
var BASE_URL = 'http://localhost:8090';
|
||||||
|
|
||||||
|
@ -46,8 +49,8 @@ describe('test the server', function() {
|
||||||
test_get_auth(jwt);
|
test_get_auth(jwt);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('test POST /_auth', function() {
|
describe('test POST /_auth/1stfactor', function() {
|
||||||
test_post_auth(jwt);
|
test_post_auth_1st_factor();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -95,50 +98,64 @@ function test_get_auth(jwt) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_post_auth() {
|
function test_post_auth_1st_factor() {
|
||||||
it('should return the JWT token when authentication is successful', function(done) {
|
it('should return status code 204 when ldap bind is successful', function() {
|
||||||
var clock = sinon.useFakeTimers();
|
request.postAsync(BASE_URL + '/_auth/1stfactor', {
|
||||||
var real_token = speakeasy.totp({
|
|
||||||
secret: 'totp_secret',
|
|
||||||
encoding: 'base32'
|
|
||||||
});
|
|
||||||
var expectedJwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdF9vayIsImlhdCI6MCwiZXhwIjozNjAwfQ.ihvaljGjO5h3iSO_h3PkNNSCYeePyB8Hr5lfVZZYyrQ';
|
|
||||||
|
|
||||||
request.post(BASE_URL + '/_auth', {
|
|
||||||
form: {
|
form: {
|
||||||
username: 'test_ok',
|
username: 'username',
|
||||||
password: 'password',
|
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();
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.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
Block a user