mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Implement authentication regulation
This commit is contained in:
parent
05046338ed
commit
cb98f0454a
35
src/lib/authentication_regulator.js
Normal file
35
src/lib/authentication_regulator.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
|
||||
module.exports = AuthenticationRegulator;
|
||||
|
||||
var exceptions = require('./exceptions');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
function AuthenticationRegulator(user_data_store, lock_time_in_seconds) {
|
||||
this._user_data_store = user_data_store;
|
||||
this._lock_time_in_seconds = lock_time_in_seconds;
|
||||
}
|
||||
|
||||
// Mark authentication
|
||||
AuthenticationRegulator.prototype.mark = function(userid, is_success) {
|
||||
return this._user_data_store.save_authentication_trace(userid, '1stfactor', is_success);
|
||||
}
|
||||
|
||||
AuthenticationRegulator.prototype.regulate = function(userid) {
|
||||
var that = this;
|
||||
return this._user_data_store.get_last_authentication_traces(userid, '1stfactor', false, 3)
|
||||
.then(function(docs) {
|
||||
if(docs.length < 3) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
var oldest_doc = docs[2];
|
||||
var no_lock_min_date = new Date(new Date().getTime() -
|
||||
that._lock_time_in_seconds * 1000);
|
||||
|
||||
if(oldest_doc.date > no_lock_min_date) {
|
||||
throw new exceptions.AuthenticationRegulationError();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
|
@ -3,7 +3,8 @@ module.exports = {
|
|||
LdapSearchError: LdapSearchError,
|
||||
LdapBindError: LdapBindError,
|
||||
IdentityError: IdentityError,
|
||||
AccessDeniedError: AccessDeniedError
|
||||
AccessDeniedError: AccessDeniedError,
|
||||
AuthenticationRegulationError: AuthenticationRegulationError,
|
||||
}
|
||||
|
||||
function LdapSearchError(message) {
|
||||
|
@ -29,3 +30,9 @@ function AccessDeniedError(message) {
|
|||
this.message = (message || "");
|
||||
}
|
||||
AccessDeniedError.prototype = Object.create(Error.prototype);
|
||||
|
||||
function AuthenticationRegulationError(message) {
|
||||
this.name = "AuthenticationRegulationError";
|
||||
this.message = (message || "");
|
||||
}
|
||||
AuthenticationRegulationError.prototype = Object.create(Error.prototype);
|
||||
|
|
|
@ -102,6 +102,7 @@ function identity_check_post(endpoint, icheck_interface) {
|
|||
.then(function(identity) {
|
||||
email_address = objectPath.get(identity, 'email');
|
||||
userid = objectPath.get(identity, 'userid');
|
||||
|
||||
if(!(email_address && userid)) {
|
||||
throw new exceptions.IdentityError('Missing user id or email address');
|
||||
}
|
||||
|
@ -112,26 +113,24 @@ function identity_check_post(endpoint, icheck_interface) {
|
|||
email.hook_url = util.format('https://%s%s', req.headers.host, req.headers['x-original-uri']);
|
||||
return identity_check.issue_token(userid, email, undefined, logger);
|
||||
}, function(err) {
|
||||
throw new exceptions.AccessDeniedError('Access denied');
|
||||
throw new exceptions.AccessDeniedError();
|
||||
})
|
||||
.then(function() {
|
||||
res.status(204);
|
||||
res.send();
|
||||
})
|
||||
.catch(exceptions.IdentityError, function(err) {
|
||||
logger.error('POST identity_check: %s', err);
|
||||
logger.error('POST identity_check: IdentityError %s', err);
|
||||
res.status(400);
|
||||
res.send();
|
||||
return;
|
||||
})
|
||||
.catch(exceptions.AccessDeniedError, function(err) {
|
||||
logger.error('POST identity_check: %s', err);
|
||||
logger.error('POST identity_check: AccessDeniedError %s', err);
|
||||
res.status(403);
|
||||
res.send();
|
||||
return;
|
||||
})
|
||||
.catch(function(err) {
|
||||
logger.error('POST identity_check: %s', err);
|
||||
logger.error('POST identity_check: Error %s', err);
|
||||
res.status(500);
|
||||
res.send();
|
||||
});
|
||||
|
|
|
@ -14,7 +14,6 @@ var Dovehash = require('dovehash');
|
|||
function validateCredentials(ldap_client, username, password, users_dn) {
|
||||
var userDN = util.format("cn=%s,%s", username, users_dn);
|
||||
var bind_promised = Promise.promisify(ldap_client.bind, { context: ldap_client });
|
||||
console.log(username, password);
|
||||
return bind_promised(userDN, password)
|
||||
.error(function(err) {
|
||||
console.error(err);
|
||||
|
|
|
@ -3,19 +3,14 @@ module.exports = denyNotLogged;
|
|||
|
||||
var objectPath = require('object-path');
|
||||
|
||||
function replyWithUnauthorized(res) {
|
||||
res.status(401);
|
||||
res.send('Unauthorized access');
|
||||
}
|
||||
|
||||
function denyNotLogged(next) {
|
||||
return function(req, res) {
|
||||
var auth_session = req.session.auth_session;
|
||||
var first_factor = objectPath.has(req, 'session.auth_session.first_factor')
|
||||
&& req.session.auth_session.first_factor;
|
||||
if(!first_factor) {
|
||||
replyWithUnauthorized(res);
|
||||
console.log('Access to this route is denied');
|
||||
res.status(403);
|
||||
res.send();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,17 +5,13 @@ var exceptions = require('../exceptions');
|
|||
var ldap = require('../ldap');
|
||||
var objectPath = require('object-path');
|
||||
|
||||
function replyWithUnauthorized(res) {
|
||||
res.status(401);
|
||||
res.send();
|
||||
}
|
||||
|
||||
function first_factor(req, res) {
|
||||
var logger = req.app.get('logger');
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
if(!username || !password) {
|
||||
replyWithUnauthorized(res);
|
||||
res.status(401);
|
||||
res.send();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -23,12 +19,16 @@ function first_factor(req, res) {
|
|||
|
||||
var ldap_client = req.app.get('ldap client');
|
||||
var config = req.app.get('config');
|
||||
var regulator = req.app.get('authentication regulator');
|
||||
|
||||
logger.debug('1st factor: Start bind operation against LDAP');
|
||||
logger.debug('1st factor: username=%s', username);
|
||||
logger.debug('1st factor: base_dn=%s', config.ldap_users_dn);
|
||||
|
||||
ldap.validate(ldap_client, username, password, config.ldap_users_dn)
|
||||
regulator.regulate(username)
|
||||
.then(function() {
|
||||
return ldap.validate(ldap_client, username, password, config.ldap_users_dn);
|
||||
})
|
||||
.then(function() {
|
||||
objectPath.set(req, 'session.auth_session.userid', username);
|
||||
objectPath.set(req, 'session.auth_session.first_factor', true);
|
||||
|
@ -42,6 +42,7 @@ function first_factor(req, res) {
|
|||
logger.debug('1st factor: Retrieved email is %s', email);
|
||||
|
||||
objectPath.set(req, 'session.auth_session.email', email);
|
||||
regulator.mark(username, true);
|
||||
res.status(204);
|
||||
res.send();
|
||||
})
|
||||
|
@ -51,10 +52,21 @@ function first_factor(req, res) {
|
|||
res.send();
|
||||
})
|
||||
.catch(exceptions.LdapBindError, function(err) {
|
||||
logger.info('1st factor: LDAP binding failed', err);
|
||||
replyWithUnauthorized(res);
|
||||
logger.info('1st factor: LDAP binding failed');
|
||||
logger.debug('1st factor: LDAP binding failed due to ', err);
|
||||
regulator.mark(username, false);
|
||||
res.status(401);
|
||||
res.send('Bad credentials');
|
||||
})
|
||||
.catch(exceptions.AuthenticationRegulationError, function(err) {
|
||||
logger.info('1st factor: the regulator rejected the authentication of user %s', username);
|
||||
logger.debug('1st factor: authentication rejected due to %s', err);
|
||||
res.status(403);
|
||||
res.send('Access has been restricted for a few minutes...');
|
||||
})
|
||||
.catch(function(err) {
|
||||
logger.debug('1st factor: Unhandled error %s', err);
|
||||
res.status(500);
|
||||
res.send('Internal error');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
var Promise = require('bluebird');
|
||||
var objectPath = require('object-path');
|
||||
var ldap = require('../ldap');
|
||||
var exceptions = require('../exceptions');
|
||||
var CHALLENGE = 'reset-password';
|
||||
|
||||
var icheck_interface = {
|
||||
|
@ -17,7 +19,8 @@ module.exports = {
|
|||
function pre_check(req) {
|
||||
var userid = objectPath.get(req, 'body.userid');
|
||||
if(!userid) {
|
||||
return Promise.reject('No user id provided');
|
||||
var err = new exceptions.AccessDeniedError();
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
var ldap_client = req.app.get('ldap client');
|
||||
|
@ -31,7 +34,7 @@ function pre_check(req) {
|
|||
identity.email = email;
|
||||
identity.userid = userid;
|
||||
return Promise.resolve(identity);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function protect(fn) {
|
||||
|
@ -59,7 +62,7 @@ function post(req, res) {
|
|||
ldap.update_password(ldap_client, ldapjs, userid, new_password, config)
|
||||
.then(function() {
|
||||
logger.info('POST reset-password: Password reset for user %s', userid);
|
||||
objectPath.set(req, 'session.auth_session', {});
|
||||
objectPath.set(req, 'session.auth_session', undefined);
|
||||
res.status(204);
|
||||
res.send();
|
||||
})
|
||||
|
|
|
@ -20,6 +20,7 @@ function retrieve_u2f_meta(req, user_data_storage) {
|
|||
return user_data_storage.get_u2f_meta(userid, appid);
|
||||
}
|
||||
|
||||
|
||||
function sign_request(req, res) {
|
||||
var logger = req.app.get('logger');
|
||||
var user_data_storage = req.app.get('user data store');
|
||||
|
|
|
@ -71,6 +71,7 @@ function register(req, res) {
|
|||
return user_data_storage.set_u2f_meta(userid, appid, meta);
|
||||
})
|
||||
.then(function() {
|
||||
objectPath.set(req, 'session.auth_session.identity_check', undefined);
|
||||
res.status(204);
|
||||
res.send();
|
||||
})
|
||||
|
|
|
@ -13,6 +13,7 @@ var session = require('express-session');
|
|||
var winston = require('winston');
|
||||
var UserDataStore = require('./user_data_store');
|
||||
var EmailSender = require('./email_sender');
|
||||
var AuthenticationRegulator = require('./authentication_regulator');
|
||||
var identity_check = require('./identity_check');
|
||||
|
||||
function run(config, ldap_client, deps, fn) {
|
||||
|
@ -20,6 +21,8 @@ function run(config, ldap_client, deps, fn) {
|
|||
var public_html_directory = path.resolve(__dirname, '../public_html');
|
||||
var datastore_options = {};
|
||||
datastore_options.directory = config.store_directory;
|
||||
if(config.store_in_memory)
|
||||
datastore_options.inMemory = true;
|
||||
|
||||
var email_options = {};
|
||||
email_options.gmail = config.gmail;
|
||||
|
@ -45,13 +48,19 @@ function run(config, ldap_client, deps, fn) {
|
|||
|
||||
winston.level = config.debug_level || 'info';
|
||||
|
||||
var five_minutes = 5 * 60;
|
||||
var data_store = new UserDataStore(deps.nedb, datastore_options);
|
||||
var regulator = new AuthenticationRegulator(data_store, five_minutes);
|
||||
var notifier = new EmailSender(deps.nodemailer, email_options);
|
||||
|
||||
app.set('logger', winston);
|
||||
app.set('ldap', deps.ldap);
|
||||
app.set('ldap client', ldap_client);
|
||||
app.set('totp engine', speakeasy);
|
||||
app.set('u2f', deps.u2f);
|
||||
app.set('user data store', new UserDataStore(deps.nedb, datastore_options));
|
||||
app.set('email sender', new EmailSender(deps.nodemailer, email_options));
|
||||
app.set('user data store', data_store);
|
||||
app.set('email sender', notifier);
|
||||
app.set('authentication regulator', regulator);
|
||||
app.set('config', config);
|
||||
|
||||
var base_endpoint = '/authentication';
|
||||
|
@ -62,6 +71,7 @@ function run(config, ldap_client, deps, fn) {
|
|||
|
||||
identity_check(app, base_endpoint + '/u2f-register', routes.u2f_register.icheck_interface);
|
||||
identity_check(app, base_endpoint + '/reset-password', routes.reset_password.icheck_interface);
|
||||
|
||||
app.get (base_endpoint + '/reset-password-form', function(req, res) { res.render('reset-password-form'); });
|
||||
|
||||
// Reset the password
|
||||
|
|
|
@ -8,6 +8,8 @@ function UserDataStore(DataStore, options) {
|
|||
this._u2f_meta_collection = create_collection('u2f_meta', options, DataStore);
|
||||
this._identity_check_tokens_collection =
|
||||
create_collection('identity_check_tokens', options, DataStore);
|
||||
this._authentication_traces_collection =
|
||||
create_collection('authentication_traces', options, DataStore);
|
||||
}
|
||||
|
||||
function create_collection(name, options, DataStore) {
|
||||
|
@ -42,6 +44,28 @@ UserDataStore.prototype.get_u2f_meta = function(userid, app_id) {
|
|||
return this._u2f_meta_collection.findOneAsync(filter);
|
||||
}
|
||||
|
||||
UserDataStore.prototype.save_authentication_trace = function(userid, type, is_success) {
|
||||
var newDocument = {};
|
||||
newDocument.userid = userid;
|
||||
newDocument.date = new Date();
|
||||
newDocument.is_success = is_success;
|
||||
newDocument.type = type;
|
||||
|
||||
return this._authentication_traces_collection.insertAsync(newDocument);
|
||||
}
|
||||
|
||||
UserDataStore.prototype.get_last_authentication_traces = function(userid, type, is_success, count) {
|
||||
var query = {};
|
||||
query.userid = userid;
|
||||
query.type = type;
|
||||
query.is_success = is_success;
|
||||
|
||||
var query = this._authentication_traces_collection.find(query)
|
||||
.sort({ date: -1 }).limit(count);
|
||||
var query_promisified = Promise.promisify(query.exec, { context: query });
|
||||
return query_promisified();
|
||||
}
|
||||
|
||||
UserDataStore.prototype.issue_identity_check_token = function(userid, token, data, max_age) {
|
||||
var newDocument = {};
|
||||
newDocument.userid = userid;
|
||||
|
|
|
@ -23,7 +23,7 @@ function onLoginButtonClicked() {
|
|||
|
||||
validateFirstFactor(username, password, function(err) {
|
||||
if(err) {
|
||||
onFirstFactorFailure();
|
||||
onFirstFactorFailure(err);
|
||||
return;
|
||||
}
|
||||
onFirstFactorSuccess();
|
||||
|
@ -91,7 +91,7 @@ function finishU2fAuthentication(url, responseData, fn) {
|
|||
fn(undefined, data);
|
||||
})
|
||||
.fail(function(xhr, status) {
|
||||
$.notify('Error when finish U2F transaction' + status);
|
||||
$.notify('Error when finish U2F transaction', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -159,10 +159,10 @@ function onFirstFactorSuccess() {
|
|||
enterSecondFactor();
|
||||
}
|
||||
|
||||
function onFirstFactorFailure() {
|
||||
function onFirstFactorFailure(err) {
|
||||
$('#password').val('');
|
||||
$('#token').val('');
|
||||
$.notify('Wrong credentials', 'error');
|
||||
$.notify('Error during authentication: ' + err, 'error');
|
||||
}
|
||||
|
||||
function onAuthenticationSuccess() {
|
||||
|
@ -183,7 +183,7 @@ function onU2fAuthenticationSuccess() {
|
|||
}
|
||||
|
||||
function onU2fAuthenticationFailure(err) {
|
||||
$.notify('Problem authenticating with U2F.', 'error');
|
||||
$.notify('Problem with U2F authentication. Did you register before authenticating?', 'warn');
|
||||
}
|
||||
|
||||
function showFirstFactorLayout() {
|
||||
|
|
|
@ -28,7 +28,7 @@ describe('test the server', function() {
|
|||
});
|
||||
|
||||
it('should serve the login page', function(done) {
|
||||
getPromised(BASE_URL + '/auth/login?redirect=/')
|
||||
getPromised(BASE_URL + '/authentication/login?redirect=/')
|
||||
.then(function(data) {
|
||||
assert.equal(data.statusCode, 200);
|
||||
done();
|
||||
|
@ -44,7 +44,7 @@ describe('test the server', function() {
|
|||
});
|
||||
|
||||
it('should redirect when logout', function(done) {
|
||||
getPromised(BASE_URL + '/auth/logout?redirect=/')
|
||||
getPromised(BASE_URL + '/authentication/logout?redirect=/')
|
||||
.then(function(data) {
|
||||
assert.equal(data.statusCode, 200);
|
||||
assert.equal(data.body, home_page);
|
||||
|
@ -62,7 +62,7 @@ describe('test the server', function() {
|
|||
});
|
||||
|
||||
it('should fail the first_factor login', function() {
|
||||
return postPromised(BASE_URL + '/auth/1stfactor', {
|
||||
return postPromised(BASE_URL + '/authentication/1stfactor', {
|
||||
form: {
|
||||
username: 'user',
|
||||
password: 'bad_password'
|
||||
|
@ -80,7 +80,7 @@ describe('test the server', function() {
|
|||
encoding: 'base32'
|
||||
});
|
||||
|
||||
return postPromised(BASE_URL + '/auth/1stfactor', {
|
||||
return postPromised(BASE_URL + '/authentication/1stfactor', {
|
||||
form: {
|
||||
username: 'user',
|
||||
password: 'password',
|
||||
|
@ -88,7 +88,7 @@ describe('test the server', function() {
|
|||
})
|
||||
.then(function(response) {
|
||||
assert.equal(response.statusCode, 204);
|
||||
return postPromised(BASE_URL + '/auth/2ndfactor/totp', {
|
||||
return postPromised(BASE_URL + '/authentication/2ndfactor/totp', {
|
||||
form: { token: token }
|
||||
});
|
||||
})
|
||||
|
@ -116,7 +116,7 @@ describe('test the server', function() {
|
|||
var is_secret_page_content =
|
||||
(content.indexOf('This is a very important secret!') > -1);
|
||||
assert(is_secret_page_content);
|
||||
return getPromised(BASE_URL + '/auth/logout')
|
||||
return getPromised(BASE_URL + '/authentication/logout')
|
||||
})
|
||||
.then(function(data) {
|
||||
assert.equal(data.statusCode, 200);
|
||||
|
@ -164,5 +164,5 @@ function getHomePage() {
|
|||
}
|
||||
|
||||
function getLoginPage() {
|
||||
return getPromised(BASE_URL + '/auth/login');
|
||||
return getPromised(BASE_URL + '/authentication/login');
|
||||
}
|
||||
|
|
|
@ -116,6 +116,17 @@ module.exports = function(port) {
|
|||
});
|
||||
}
|
||||
|
||||
function execute_failing_first_factor(jar) {
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/authentication/1stfactor',
|
||||
jar: jar,
|
||||
form: {
|
||||
username: 'test_nok',
|
||||
password: 'password'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
login: execute_login,
|
||||
verify: execute_verification,
|
||||
|
@ -123,6 +134,7 @@ module.exports = function(port) {
|
|||
u2f_authentication: execute_u2f_authentication,
|
||||
u2f_registration: execute_u2f_registration,
|
||||
first_factor: execute_first_factor,
|
||||
failing_first_factor: execute_failing_first_factor,
|
||||
totp: execute_totp,
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,11 @@ var assert = require('assert');
|
|||
var denyNotLogged = require('../../../src/lib/routes/deny_not_logged');
|
||||
|
||||
describe('test not logged', function() {
|
||||
it('should return status code 401 when auth_session has not been previously created', function() {
|
||||
it('should return status code 403 when auth_session has not been previously created', function() {
|
||||
return test_auth_session_not_created();
|
||||
});
|
||||
|
||||
it('should return status code 401 when auth_session has failed first factor', function() {
|
||||
it('should return status code 403 when auth_session has failed first factor', function() {
|
||||
return test_auth_first_factor_not_validated();
|
||||
});
|
||||
|
||||
|
@ -23,7 +23,7 @@ function test_auth_session_not_created() {
|
|||
return new Promise(function(resolve, reject) {
|
||||
var send = sinon.spy(resolve);
|
||||
var status = sinon.spy(function(code) {
|
||||
assert.equal(401, code);
|
||||
assert.equal(403, code);
|
||||
});
|
||||
var req = {
|
||||
session: {}
|
||||
|
@ -42,7 +42,7 @@ function test_auth_first_factor_not_validated() {
|
|||
return new Promise(function(resolve, reject) {
|
||||
var send = sinon.spy(resolve);
|
||||
var status = sinon.spy(function(code) {
|
||||
assert.equal(401, code);
|
||||
assert.equal(403, code);
|
||||
});
|
||||
var req = {
|
||||
session: {
|
||||
|
|
|
@ -4,11 +4,13 @@ var Promise = require('bluebird');
|
|||
var assert = require('assert');
|
||||
var winston = require('winston');
|
||||
var first_factor = require('../../../src/lib/routes/first_factor');
|
||||
var exceptions = require('../../../src/lib/exceptions');
|
||||
|
||||
describe('test the first factor validation route', function() {
|
||||
var req, res;
|
||||
var ldap_interface_mock;
|
||||
var search_res_ok;
|
||||
var regulator;
|
||||
|
||||
beforeEach(function() {
|
||||
ldap_interface_mock = {
|
||||
|
@ -31,10 +33,18 @@ describe('test the first factor validation route', function() {
|
|||
});
|
||||
ldap_interface_mock.search.yields(undefined, search_res_ok);
|
||||
|
||||
regulator = {};
|
||||
regulator.mark = sinon.stub();
|
||||
regulator.regulate = sinon.stub();
|
||||
|
||||
regulator.mark.returns(Promise.resolve());
|
||||
regulator.regulate.returns(Promise.resolve());
|
||||
|
||||
var app_get = sinon.stub();
|
||||
app_get.withArgs('ldap client').returns(ldap_interface_mock);
|
||||
app_get.withArgs('config').returns(ldap_interface_mock);
|
||||
app_get.withArgs('logger').returns(winston);
|
||||
app_get.withArgs('authentication regulator').returns(regulator);
|
||||
|
||||
req = {
|
||||
app: {
|
||||
|
@ -69,27 +79,36 @@ describe('test the first factor validation route', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('should return status code 401 when LDAP binding fails', function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
it('should return status code 401 when LDAP binding fails', function(done) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(401, res.status.getCall(0).args[0]);
|
||||
resolve();
|
||||
assert.equal(regulator.mark.getCall(0).args[0], 'username');
|
||||
done();
|
||||
});
|
||||
ldap_interface_mock.bind.yields('Bad credentials');
|
||||
first_factor(req, res);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return status code 500 when LDAP binding fails', function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
it('should return status code 500 when LDAP binding throws', function(done) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(500, res.status.getCall(0).args[0]);
|
||||
resolve();
|
||||
done();
|
||||
});
|
||||
ldap_interface_mock.bind.yields(undefined);
|
||||
ldap_interface_mock.search.yields('error');
|
||||
first_factor(req, res);
|
||||
});
|
||||
|
||||
it('should return status code 403 when regulator rejects authentication', function(done) {
|
||||
var err = new exceptions.AuthenticationRegulationError();
|
||||
regulator.regulate.returns(Promise.reject(err));
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(403, res.status.getCall(0).args[0]);
|
||||
done();
|
||||
});
|
||||
ldap_interface_mock.bind.yields(undefined);
|
||||
ldap_interface_mock.search.yields(undefined);
|
||||
first_factor(req, res);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ describe('test reset password', function() {
|
|||
}
|
||||
|
||||
function test_reset_password_post() {
|
||||
it('should update the password', function(done) {
|
||||
it('should update the password and reset auth_session for reauthentication', function(done) {
|
||||
req.session.auth_session.identity_check = {};
|
||||
req.session.auth_session.identity_check.userid = 'user';
|
||||
req.session.auth_session.identity_check.challenge = 'reset-password';
|
||||
|
@ -107,6 +107,7 @@ describe('test reset password', function() {
|
|||
res.send = sinon.spy(function() {
|
||||
assert.equal(ldap_client.modify.getCall(0).args[0], 'cn=user,dc=example,dc=com');
|
||||
assert.equal(res.status.getCall(0).args[0], 204);
|
||||
assert.equal(req.session.auth_session, undefined);
|
||||
done();
|
||||
});
|
||||
reset_password.post(req, res);
|
||||
|
|
|
@ -95,7 +95,8 @@ describe('test u2f routes', function() {
|
|||
certificate: 'cert'
|
||||
};
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert('user', user_data_store.set_u2f_meta.getCall(0).args[0])
|
||||
assert.equal('user', user_data_store.set_u2f_meta.getCall(0).args[0])
|
||||
assert.equal(req.session.auth_session.identity_check, undefined);
|
||||
done();
|
||||
});
|
||||
var u2f_mock = {};
|
||||
|
|
70
test/unitary/test_authentication_regulator.js
Normal file
70
test/unitary/test_authentication_regulator.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
|
||||
var AuthenticationRegulator = require('../../src/lib/authentication_regulator');
|
||||
var UserDataStore = require('../../src/lib/user_data_store');
|
||||
var DataStore = require('nedb');
|
||||
var exceptions = require('../../src/lib/exceptions');
|
||||
var MockDate = require('mockdate');
|
||||
|
||||
describe('test authentication regulator', function() {
|
||||
it('should mark 2 authentication and regulate (resolve)', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var regulator = new AuthenticationRegulator(data_store, 10);
|
||||
var user = 'user';
|
||||
|
||||
return regulator.mark(user, false)
|
||||
.then(function() {
|
||||
return regulator.mark(user, true);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.regulate(user);
|
||||
});
|
||||
});
|
||||
|
||||
it('should mark 3 authentications and regulate (reject)', function(done) {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var regulator = new AuthenticationRegulator(data_store, 10);
|
||||
var user = 'user';
|
||||
|
||||
regulator.mark(user, false)
|
||||
.then(function() {
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.regulate(user);
|
||||
})
|
||||
.catch(exceptions.AuthenticationRegulationError, function() {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should mark 3 authentications and regulate (resolve)', function(done) {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var regulator = new AuthenticationRegulator(data_store, 10);
|
||||
var user = 'user';
|
||||
|
||||
MockDate.set('1/2/2000 00:00:00');
|
||||
regulator.mark(user, false)
|
||||
.then(function() {
|
||||
MockDate.set('1/2/2000 00:00:15');
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.regulate(user);
|
||||
})
|
||||
.then(function() {
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
|
@ -68,7 +68,6 @@ describe('test identity check process', function() {
|
|||
describe('test POST', test_post_handler);
|
||||
describe('test GET', test_get_handler);
|
||||
|
||||
|
||||
function test_post_handler() {
|
||||
it('should send 403 if pre check rejects', function(done) {
|
||||
var endpoint = '/protected';
|
||||
|
|
|
@ -6,6 +6,7 @@ var request = Promise.promisifyAll(require('request'));
|
|||
var assert = require('assert');
|
||||
var speakeasy = require('speakeasy');
|
||||
var sinon = require('sinon');
|
||||
var MockDate = require('mockdate');
|
||||
|
||||
var PORT = 8090;
|
||||
var BASE_URL = 'http://localhost:' + PORT;
|
||||
|
@ -36,6 +37,7 @@ describe('test the server', function() {
|
|||
ldap_password: 'password',
|
||||
session_secret: 'session_secret',
|
||||
session_max_age: 50000,
|
||||
store_in_memory: true,
|
||||
gmail: {
|
||||
user: 'user@example.com',
|
||||
pass: 'password'
|
||||
|
@ -48,14 +50,7 @@ describe('test the server', function() {
|
|||
u2f.startAuthentication = sinon.stub();
|
||||
u2f.finishAuthentication = sinon.stub();
|
||||
|
||||
collection = {};
|
||||
collection.insert = sinon.stub().yields(undefined, 1);
|
||||
collection.findOne = sinon.stub().yields(undefined, {});
|
||||
collection.update = sinon.stub().yields(undefined, {});
|
||||
collection.remove = sinon.stub().yields(undefined, 1);
|
||||
nedb = sinon.spy(function() {
|
||||
return collection;
|
||||
});
|
||||
nedb = require('nedb');
|
||||
|
||||
transporter = {};
|
||||
transporter.sendMail = sinon.stub().yields();
|
||||
|
@ -80,10 +75,12 @@ describe('test the server', function() {
|
|||
'password').yields(undefined);
|
||||
ldap_client.bind.withArgs('cn=admin,dc=example,dc=com',
|
||||
'password').yields(undefined);
|
||||
ldap_client.search.yields(undefined, search_res);
|
||||
|
||||
ldap_client.bind.withArgs('cn=test_nok,ou=users,dc=example,dc=com',
|
||||
'password').yields('error');
|
||||
|
||||
ldap_client.modify.yields(undefined);
|
||||
ldap_client.search.yields(undefined, search_res);
|
||||
|
||||
var deps = {};
|
||||
deps.u2f = u2f;
|
||||
|
@ -101,18 +98,97 @@ describe('test the server', function() {
|
|||
});
|
||||
|
||||
describe('test GET /login', function() {
|
||||
test_login()
|
||||
test_login();
|
||||
});
|
||||
|
||||
describe('test GET /logout', function() {
|
||||
test_logout()
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('test authentication and verification', function() {
|
||||
test_authentication();
|
||||
test_reset_password();
|
||||
test_regulation();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_login() {
|
||||
it('should serve the login page', function(done) {
|
||||
request.getAsync(BASE_URL + '/authentication/login')
|
||||
|
@ -209,11 +285,6 @@ describe('test the server', function() {
|
|||
u2f.startAuthentication.returns(Promise.resolve(registration_request));
|
||||
u2f.finishAuthentication.returns(Promise.resolve(registration_status));
|
||||
|
||||
collection.insert = sinon.spy(function(data, fn) {
|
||||
collection.findOne.yields(undefined, data);
|
||||
fn();
|
||||
});
|
||||
|
||||
var j = request.jar();
|
||||
return requests.login(j)
|
||||
.then(function(res) {
|
||||
|
@ -241,11 +312,6 @@ describe('test the server', function() {
|
|||
|
||||
function test_reset_password() {
|
||||
it('should reset the password', function() {
|
||||
collection.insert = sinon.spy(function(data, fn) {
|
||||
collection.findOne.yields(undefined, data);
|
||||
fn();
|
||||
});
|
||||
|
||||
var j = request.jar();
|
||||
return requests.login(j)
|
||||
.then(function(res) {
|
||||
|
@ -262,5 +328,39 @@ describe('test the server', function() {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
console.log('coucou');
|
||||
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();
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
69
test/unitary/user_data_store/test_authentication_audit.js
Normal file
69
test/unitary/user_data_store/test_authentication_audit.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
|
||||
var assert = require('assert');
|
||||
var Promise = require('bluebird');
|
||||
var sinon = require('sinon');
|
||||
var MockDate = require('mockdate');
|
||||
var UserDataStore = require('../../../src/lib/user_data_store');
|
||||
var DataStore = require('nedb');
|
||||
|
||||
describe('test user data store', function() {
|
||||
describe('test authentication traces', test_authentication_traces);
|
||||
});
|
||||
|
||||
function test_authentication_traces() {
|
||||
it('should save an authentication trace in db', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var userid = 'user';
|
||||
var type = '1stfactor';
|
||||
var is_success = false;
|
||||
return data_store.save_authentication_trace(userid, type, is_success)
|
||||
.then(function(doc) {
|
||||
assert('_id' in doc);
|
||||
assert.equal(doc.userid, 'user');
|
||||
assert.equal(doc.is_success, false);
|
||||
assert.equal(doc.type, '1stfactor');
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 3 last authentication traces', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var userid = 'user';
|
||||
var type = '1stfactor';
|
||||
var is_success = false;
|
||||
MockDate.set('2/1/2000');
|
||||
return data_store.save_authentication_trace(userid, type, false)
|
||||
.then(function(doc) {
|
||||
MockDate.set('1/2/2000');
|
||||
return data_store.save_authentication_trace(userid, type, true);
|
||||
})
|
||||
.then(function(doc) {
|
||||
MockDate.set('1/7/2000');
|
||||
return data_store.save_authentication_trace(userid, type, false);
|
||||
})
|
||||
.then(function(doc) {
|
||||
MockDate.set('1/2/2000');
|
||||
return data_store.save_authentication_trace(userid, type, false);
|
||||
})
|
||||
.then(function(doc) {
|
||||
MockDate.set('1/5/2000');
|
||||
return data_store.save_authentication_trace(userid, type, false);
|
||||
})
|
||||
.then(function(doc) {
|
||||
return data_store.get_last_authentication_traces(userid, type, false, 3);
|
||||
})
|
||||
.then(function(docs) {
|
||||
assert.equal(docs.length, 3);
|
||||
assert.deepEqual(docs[0].date, new Date('2/1/2000'));
|
||||
assert.deepEqual(docs[1].date, new Date('1/7/2000'));
|
||||
assert.deepEqual(docs[2].date, new Date('1/5/2000'));
|
||||
return Promise.resolve();
|
||||
})
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user