Use filesystem data store to save u2f meta info
parent
9670b23a8b
commit
8b4339f8da
|
@ -10,4 +10,6 @@ COPY src /usr/src
|
||||||
ENV PORT=80
|
ENV PORT=80
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
VOLUME /var/lib/auth-server
|
||||||
|
|
||||||
CMD ["node", "index.js"]
|
CMD ["node", "index.js"]
|
||||||
|
|
|
@ -3,6 +3,7 @@ version: '2'
|
||||||
services:
|
services:
|
||||||
auth:
|
auth:
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./test:/usr/src/test
|
||||||
- ./src/views:/usr/src/views
|
- ./src/views:/usr/src/views
|
||||||
- ./src/public_html:/usr/src/public_html
|
- ./src/public_html:/usr/src/public_html
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ services:
|
||||||
- TOTP_SECRET=GRWGIJS6IRHVEODVNRCXCOBMJ5AGC6ZE
|
- TOTP_SECRET=GRWGIJS6IRHVEODVNRCXCOBMJ5AGC6ZE
|
||||||
- SESSION_SECRET=unsecure_secret
|
- SESSION_SECRET=unsecure_secret
|
||||||
- SESSION_EXPIRATION_TIME=3600000
|
- SESSION_EXPIRATION_TIME=3600000
|
||||||
|
- STORE_DIRECTORY=/var/lib/auth-server
|
||||||
depends_on:
|
depends_on:
|
||||||
- ldap
|
- ldap
|
||||||
restart: always
|
restart: always
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"unit-test": "./node_modules/.bin/mocha --recursive test/unitary",
|
"unit-test": "./node_modules/.bin/mocha --recursive test/unitary",
|
||||||
"int-test": "./node_modules/.bin/mocha --recursive test/integration",
|
"int-test": "./node_modules/.bin/mocha --recursive test/integration",
|
||||||
"all-test": "./node_modules/.bin/mocha --recursive test",
|
"all-test": "./node_modules/.bin/mocha --recursive test",
|
||||||
"coverage": "./node_modules/.bin/istanbul cover _mocha -- -R spec"
|
"coverage": "./node_modules/.bin/istanbul cover _mocha -- -R spec --recursive test"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -27,6 +27,7 @@
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
"express-session": "^1.14.2",
|
"express-session": "^1.14.2",
|
||||||
"ldapjs": "^1.0.1",
|
"ldapjs": "^1.0.1",
|
||||||
|
"nedb": "^1.8.0",
|
||||||
"object-path": "^0.11.3",
|
"object-path": "^0.11.3",
|
||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
"winston": "^2.3.1"
|
"winston": "^2.3.1"
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
"request": "^2.79.0",
|
"request": "^2.79.0",
|
||||||
"should": "^11.1.1",
|
"should": "^11.1.1",
|
||||||
"sinon": "^1.17.6",
|
"sinon": "^1.17.6",
|
||||||
"sinon-promise": "^0.1.3"
|
"sinon-promise": "^0.1.3",
|
||||||
|
"tmp": "0.0.31"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ var config = {
|
||||||
ldap_url: process.env.LDAP_URL || 'ldap://127.0.0.1:389',
|
ldap_url: process.env.LDAP_URL || 'ldap://127.0.0.1:389',
|
||||||
ldap_users_dn: process.env.LDAP_USERS_DN,
|
ldap_users_dn: process.env.LDAP_USERS_DN,
|
||||||
session_secret: process.env.SESSION_SECRET,
|
session_secret: process.env.SESSION_SECRET,
|
||||||
session_max_age: process.env.SESSION_MAX_AGE || 3600000 // in ms
|
session_max_age: process.env.SESSION_MAX_AGE || 3600000, // in ms
|
||||||
|
store_directory: process.env.STORE_DIRECTORY
|
||||||
}
|
}
|
||||||
|
|
||||||
var ldap_client = ldap.createClient({
|
var ldap_client = ldap.createClient({
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
|
|
||||||
var user_key_container = {};
|
|
||||||
var denyNotLogged = require('./deny_not_logged');
|
var denyNotLogged = require('./deny_not_logged');
|
||||||
var u2f = require('./u2f')(user_key_container); // create a u2f handler bound to
|
var u2f = require('./u2f');
|
||||||
// user key container
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
totp: denyNotLogged(require('./totp')),
|
totp: denyNotLogged(require('./totp')),
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
|
|
||||||
module.exports = function(user_key_container) {
|
module.exports = {
|
||||||
return {
|
register_request: register_request,
|
||||||
register_request: register_request,
|
register: register,
|
||||||
register: register(user_key_container),
|
sign_request: sign_request,
|
||||||
sign_request: sign_request(user_key_container),
|
sign: sign,
|
||||||
sign: sign(user_key_container),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var objectPath = require('object-path');
|
var objectPath = require('object-path');
|
||||||
|
@ -26,15 +24,18 @@ function replyWithUnauthorized(res) {
|
||||||
res.send();
|
res.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractAppId(req) {
|
||||||
|
return util.format('https://%s', req.headers.host);
|
||||||
|
}
|
||||||
|
|
||||||
function register_request(req, res) {
|
function register_request(req, res) {
|
||||||
var u2f = req.app.get('u2f');
|
var u2f = req.app.get('u2f');
|
||||||
var logger = req.app.get('logger');
|
var logger = req.app.get('logger');
|
||||||
var app_id = util.format('https://%s', req.headers.host);
|
var appid = extractAppId(req);
|
||||||
|
|
||||||
logger.debug('U2F register_request: headers=%s', JSON.stringify(req.headers));
|
logger.debug('U2F register_request: headers=%s', JSON.stringify(req.headers));
|
||||||
logger.info('U2F register_request: Starting registration');
|
logger.info('U2F register_request: Starting registration');
|
||||||
u2f.startRegistration(app_id, [])
|
u2f.startRegistration(appid, [])
|
||||||
.then(function(registrationRequest) {
|
.then(function(registrationRequest) {
|
||||||
logger.info('U2F register_request: Sending back registration request');
|
logger.info('U2F register_request: Sending back registration request');
|
||||||
req.session.auth_session.register_request = registrationRequest;
|
req.session.auth_session.register_request = registrationRequest;
|
||||||
|
@ -46,99 +47,125 @@ function register_request(req, res) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function register(user_key_container) {
|
function register(req, res) {
|
||||||
return function(req, res) {
|
if(!objectPath.has(req, 'session.auth_session.register_request')) {
|
||||||
if(!objectPath.has(req, 'session.auth_session.register_request')) {
|
replyWithUnauthorized(res);
|
||||||
replyWithUnauthorized(res);
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var u2f = req.app.get('u2f');
|
|
||||||
var registrationRequest = req.session.auth_session.register_request;
|
|
||||||
var logger = req.app.get('logger');
|
|
||||||
|
|
||||||
logger.info('U2F register: Finishing registration');
|
|
||||||
logger.debug('U2F register: register_request=%s', JSON.stringify(registrationRequest));
|
|
||||||
logger.debug('U2F register: body=%s', JSON.stringify(req.body));
|
|
||||||
|
|
||||||
u2f.finishRegistration(registrationRequest, req.body)
|
|
||||||
.then(function(registrationStatus) {
|
|
||||||
logger.info('U2F register: Store registration and reply');
|
|
||||||
var meta = {
|
|
||||||
keyHandle: registrationStatus.keyHandle,
|
|
||||||
publicKey: registrationStatus.publicKey,
|
|
||||||
certificate: registrationStatus.certificate
|
|
||||||
}
|
|
||||||
user_key_container[req.session.auth_session.userid] = meta;
|
|
||||||
res.status(204);
|
|
||||||
res.send();
|
|
||||||
}, function(err) {
|
|
||||||
logger.error('U2F register: %s', err);
|
|
||||||
replyWithInternalError(res, 'Unable to complete the registration');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function userKeyExists(req, user_key_container) {
|
var user_data_storage = req.app.get('user data store');
|
||||||
return req.session.auth_session.userid in user_key_container;
|
var u2f = req.app.get('u2f');
|
||||||
}
|
var registrationRequest = req.session.auth_session.register_request;
|
||||||
|
var userid = req.session.auth_session.userid;
|
||||||
|
var appid = extractAppId(req);
|
||||||
|
var logger = req.app.get('logger');
|
||||||
|
|
||||||
|
logger.info('U2F register: Finishing registration');
|
||||||
|
logger.debug('U2F register: register_request=%s', JSON.stringify(registrationRequest));
|
||||||
|
logger.debug('U2F register: body=%s', JSON.stringify(req.body));
|
||||||
|
|
||||||
|
u2f.finishRegistration(registrationRequest, req.body)
|
||||||
function sign_request(user_key_container) {
|
.then(function(registrationStatus) {
|
||||||
return function(req, res) {
|
logger.info('U2F register: Store registration and reply');
|
||||||
if(!userKeyExists(req, user_key_container)) {
|
var meta = {
|
||||||
replyWithMissingRegistration(res);
|
keyHandle: registrationStatus.keyHandle,
|
||||||
return;
|
publicKey: registrationStatus.publicKey,
|
||||||
|
certificate: registrationStatus.certificate
|
||||||
}
|
}
|
||||||
|
user_data_storage.set_u2f_meta(userid, appid, meta);
|
||||||
var logger = req.app.get('logger');
|
res.status(204);
|
||||||
var u2f = req.app.get('u2f');
|
res.send();
|
||||||
var key = user_key_container[req.session.auth_session.userid];
|
}, function(err) {
|
||||||
var app_id = util.format('https://%s', req.headers.host);
|
logger.error('U2F register: %s', err);
|
||||||
|
replyWithInternalError(res, 'Unable to complete the registration');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('U2F sign_request: Start authentication');
|
function retrieveU2fMeta(req, user_data_storage) {
|
||||||
u2f.startAuthentication(app_id, [key])
|
var userid = req.session.auth_session.userid;
|
||||||
|
var appid = extractAppId(req);
|
||||||
|
return user_data_storage.get_u2f_meta(userid, appid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startU2fAuthentication(u2f, appid, meta) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
u2f.startAuthentication(appid, [meta])
|
||||||
.then(function(authRequest) {
|
.then(function(authRequest) {
|
||||||
logger.info('U2F sign_request: Store authentication request and reply');
|
resolve(authRequest);
|
||||||
req.session.auth_session.sign_request = authRequest;
|
|
||||||
res.status(200);
|
|
||||||
res.json(authRequest);
|
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
logger.info('U2F sign_request: %s', err);
|
reject(err);
|
||||||
replyWithUnauthorized(res);
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sign(user_key_container) {
|
function finishU2fAuthentication(u2f, authRequest, data, meta) {
|
||||||
return function(req, res) {
|
return new Promise(function(resolve, reject) {
|
||||||
if(!userKeyExists(req, user_key_container)) {
|
u2f.finishAuthentication(authRequest, data, [meta])
|
||||||
|
.then(function(authenticationStatus) {
|
||||||
|
resolve(authenticationStatus);
|
||||||
|
}, function(err) {
|
||||||
|
reject(err);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sign_request(req, res) {
|
||||||
|
var logger = req.app.get('logger');
|
||||||
|
var user_data_storage = req.app.get('user data store');
|
||||||
|
|
||||||
|
retrieveU2fMeta(req, user_data_storage)
|
||||||
|
.then(function(doc) {
|
||||||
|
if(!doc) {
|
||||||
replyWithMissingRegistration(res);
|
replyWithMissingRegistration(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!objectPath.has(req, 'session.auth_session.sign_request')) {
|
var u2f = req.app.get('u2f');
|
||||||
replyWithUnauthorized(res);
|
var meta = doc.meta;
|
||||||
return;
|
var appid = extractAppId(req);
|
||||||
}
|
logger.info('U2F sign_request: Start authentication');
|
||||||
|
return startU2fAuthentication(u2f, appid, meta);
|
||||||
var logger = req.app.get('logger');
|
})
|
||||||
|
.then(function(authRequest) {
|
||||||
|
logger.info('U2F sign_request: Store authentication request and reply');
|
||||||
|
req.session.auth_session.sign_request = authRequest;
|
||||||
|
res.status(200);
|
||||||
|
res.json(authRequest);
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
logger.info('U2F sign_request: %s', err);
|
||||||
|
replyWithUnauthorized(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function sign(req, res) {
|
||||||
|
if(!objectPath.has(req, 'session.auth_session.sign_request')) {
|
||||||
|
replyWithUnauthorized(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var logger = req.app.get('logger');
|
||||||
|
var user_data_storage = req.app.get('user data store');
|
||||||
|
|
||||||
|
retrieveU2fMeta(req, user_data_storage)
|
||||||
|
.then(function(doc) {
|
||||||
|
var appid = extractAppId(req);
|
||||||
var u2f = req.app.get('u2f');
|
var u2f = req.app.get('u2f');
|
||||||
var authRequest = req.session.auth_session.sign_request;
|
var authRequest = req.session.auth_session.sign_request;
|
||||||
var key = user_key_container[req.session.auth_session.userid];
|
var meta = doc.meta;
|
||||||
|
|
||||||
logger.info('U2F sign: Finish authentication');
|
logger.info('U2F sign: Finish authentication');
|
||||||
u2f.finishAuthentication(authRequest, req.body, [key])
|
return finishU2fAuthentication(u2f, authRequest, req.body, meta);
|
||||||
.then(function(authenticationStatus) {
|
})
|
||||||
logger.info('U2F sign: Authentication successful');
|
.then(function(authenticationStatus) {
|
||||||
req.session.auth_session.second_factor = true;
|
logger.info('U2F sign: Authentication successful');
|
||||||
res.status(204);
|
req.session.auth_session.second_factor = true;
|
||||||
res.send();
|
res.status(204);
|
||||||
}, function(err) {
|
res.send();
|
||||||
logger.error('U2F sign: %s', err);
|
}, function(err) {
|
||||||
res.status(401);
|
logger.error('U2F sign: %s', err);
|
||||||
res.send();
|
res.status(401);
|
||||||
});
|
res.send();
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,14 @@ var speakeasy = require('speakeasy');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var session = require('express-session');
|
var session = require('express-session');
|
||||||
var winston = require('winston');
|
var winston = require('winston');
|
||||||
|
var DataStore = require('nedb');
|
||||||
|
var UserDataStore = require('./user_data_store');
|
||||||
|
|
||||||
function run(config, ldap_client, u2f, fn) {
|
function run(config, ldap_client, u2f, fn) {
|
||||||
var view_directory = path.resolve(__dirname, '../views');
|
var view_directory = path.resolve(__dirname, '../views');
|
||||||
var public_html_directory = path.resolve(__dirname, '../public_html');
|
var public_html_directory = path.resolve(__dirname, '../public_html');
|
||||||
|
var datastore_options = {};
|
||||||
|
datastore_options.directory = config.store_directory;
|
||||||
|
|
||||||
var app = express();
|
var app = express();
|
||||||
app.use(express.static(public_html_directory));
|
app.use(express.static(public_html_directory));
|
||||||
|
@ -41,6 +45,7 @@ function run(config, ldap_client, u2f, fn) {
|
||||||
app.set('ldap client', ldap_client);
|
app.set('ldap client', ldap_client);
|
||||||
app.set('totp engine', speakeasy);
|
app.set('totp engine', speakeasy);
|
||||||
app.set('u2f', u2f);
|
app.set('u2f', u2f);
|
||||||
|
app.set('user data store', new UserDataStore(DataStore, datastore_options));
|
||||||
app.set('config', config);
|
app.set('config', config);
|
||||||
|
|
||||||
app.get ('/login', routes.login);
|
app.get ('/login', routes.login);
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
module.exports = UserDataStore;
|
||||||
|
|
||||||
|
var Promise = require('bluebird');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
function UserDataStore(DataStore, options) {
|
||||||
|
var datastore_options = {};
|
||||||
|
if(options.directory)
|
||||||
|
datastore_options.filename = path.resolve(options.directory, 'u2f_meta');
|
||||||
|
|
||||||
|
datastore_options.inMemoryOnly = options.inMemoryOnly || false;
|
||||||
|
datastore_options.autoload = true;
|
||||||
|
console.log(datastore_options);
|
||||||
|
|
||||||
|
this._u2f_meta_collection = Promise.promisifyAll(new DataStore(datastore_options));
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDataStore.prototype.set_u2f_meta = function(userid, app_id, meta) {
|
||||||
|
var newDocument = {};
|
||||||
|
newDocument.userid = userid;
|
||||||
|
newDocument.appid = app_id;
|
||||||
|
newDocument.meta = meta;
|
||||||
|
|
||||||
|
var filter = {};
|
||||||
|
filter.userid = userid;
|
||||||
|
filter.appid = app_id;
|
||||||
|
|
||||||
|
return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDataStore.prototype.get_u2f_meta = function(userid, app_id) {
|
||||||
|
var filter = {};
|
||||||
|
filter.userid = userid;
|
||||||
|
filter.appid = app_id;
|
||||||
|
|
||||||
|
return this._u2f_meta_collection.findOneAsync(filter);
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ var winston = require('winston');
|
||||||
|
|
||||||
describe('test u2f routes', function() {
|
describe('test u2f routes', function() {
|
||||||
var req, res;
|
var req, res;
|
||||||
|
var user_data_store;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
req = {}
|
req = {}
|
||||||
|
@ -21,8 +22,17 @@ describe('test u2f routes', function() {
|
||||||
req.headers = {};
|
req.headers = {};
|
||||||
req.headers.host = 'localhost';
|
req.headers.host = 'localhost';
|
||||||
|
|
||||||
|
var options = {};
|
||||||
|
options.inMemoryOnly = true;
|
||||||
|
|
||||||
|
user_data_store = {};
|
||||||
|
user_data_store.set_u2f_meta = sinon.stub().returns(Promise.resolve({}));
|
||||||
|
user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve({}));
|
||||||
|
req.app.get.withArgs('user data store').returns(user_data_store);
|
||||||
|
|
||||||
res = {};
|
res = {};
|
||||||
res.send = sinon.spy();
|
res.send = sinon.spy();
|
||||||
|
res.json = sinon.spy();
|
||||||
res.status = sinon.spy();
|
res.status = sinon.spy();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -31,8 +41,6 @@ describe('test u2f routes', function() {
|
||||||
describe('test signing request', test_signing_request);
|
describe('test signing request', test_signing_request);
|
||||||
describe('test signing', test_signing);
|
describe('test signing', test_signing);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function test_registration_request() {
|
function test_registration_request() {
|
||||||
it('should send back the registration request and save it in the session', function(done) {
|
it('should send back the registration request and save it in the session', function(done) {
|
||||||
var expectedRequest = {
|
var expectedRequest = {
|
||||||
|
@ -49,7 +57,7 @@ describe('test u2f routes', function() {
|
||||||
u2f_mock.startRegistration.returns(Promise.resolve(expectedRequest));
|
u2f_mock.startRegistration.returns(Promise.resolve(expectedRequest));
|
||||||
|
|
||||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||||
u2f(user_key_container).register_request(req, res);
|
u2f.register_request(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return internal error on registration request', function(done) {
|
it('should return internal error on registration request', function(done) {
|
||||||
|
@ -63,21 +71,19 @@ describe('test u2f routes', function() {
|
||||||
u2f_mock.startRegistration.returns(Promise.reject('Internal error'));
|
u2f_mock.startRegistration.returns(Promise.reject('Internal error'));
|
||||||
|
|
||||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||||
u2f(user_key_container).register_request(req, res);
|
u2f.register_request(req, res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_registration() {
|
function test_registration() {
|
||||||
it('should return status code 200', function(done) {
|
it('should save u2f meta and return status code 200', function(done) {
|
||||||
var user_key_container = {};
|
|
||||||
var expectedStatus = {
|
var expectedStatus = {
|
||||||
keyHandle: 'keyHandle',
|
keyHandle: 'keyHandle',
|
||||||
publicKey: 'pbk',
|
publicKey: 'pbk',
|
||||||
certificate: 'cert'
|
certificate: 'cert'
|
||||||
};
|
};
|
||||||
res.send = sinon.spy(function(data) {
|
res.send = sinon.spy(function(data) {
|
||||||
assert('user' in user_key_container);
|
assert('user', user_data_store.set_u2f_meta.getCall(0).args[0])
|
||||||
assert.deepEqual(expectedStatus, user_key_container['user']);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
var u2f_mock = {};
|
var u2f_mock = {};
|
||||||
|
@ -86,7 +92,7 @@ describe('test u2f routes', function() {
|
||||||
|
|
||||||
req.session.auth_session.register_request = {};
|
req.session.auth_session.register_request = {};
|
||||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||||
u2f(user_key_container).register(req, res);
|
u2f.register(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return unauthorized error on registration request', function(done) {
|
it('should return unauthorized error on registration request', function(done) {
|
||||||
|
@ -100,7 +106,7 @@ describe('test u2f routes', function() {
|
||||||
u2f_mock.finishRegistration.returns(Promise.reject('Internal error'));
|
u2f_mock.finishRegistration.returns(Promise.reject('Internal error'));
|
||||||
|
|
||||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||||
u2f(user_key_container).register(req, res);
|
u2f.register(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return unauthorized error when no auth request has been initiated', function(done) {
|
it('should return unauthorized error when no auth request has been initiated', function(done) {
|
||||||
|
@ -114,7 +120,7 @@ describe('test u2f routes', function() {
|
||||||
u2f_mock.finishRegistration.returns(Promise.resolve());
|
u2f_mock.finishRegistration.returns(Promise.resolve());
|
||||||
|
|
||||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||||
u2f(user_key_container).register(req, res);
|
u2f.register(req, res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +142,7 @@ describe('test u2f routes', function() {
|
||||||
u2f_mock.startAuthentication.returns(Promise.resolve(expectedRequest));
|
u2f_mock.startAuthentication.returns(Promise.resolve(expectedRequest));
|
||||||
|
|
||||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||||
u2f(user_key_container).sign_request(req, res);
|
u2f.sign_request(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return unauthorized error on registration request error', function(done) {
|
it('should return unauthorized error on registration request error', function(done) {
|
||||||
|
@ -151,7 +157,7 @@ describe('test u2f routes', function() {
|
||||||
u2f_mock.startAuthentication.returns(Promise.reject('Internal error'));
|
u2f_mock.startAuthentication.returns(Promise.reject('Internal error'));
|
||||||
|
|
||||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||||
u2f(user_key_container).sign_request(req, res);
|
u2f.sign_request(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send unauthorized error when no registration exists', function(done) {
|
it('should send unauthorized error when no registration exists', function(done) {
|
||||||
|
@ -167,8 +173,13 @@ describe('test u2f routes', function() {
|
||||||
u2f_mock.startAuthentication = sinon.stub();
|
u2f_mock.startAuthentication = sinon.stub();
|
||||||
u2f_mock.startAuthentication.returns(Promise.resolve(expectedRequest));
|
u2f_mock.startAuthentication.returns(Promise.resolve(expectedRequest));
|
||||||
|
|
||||||
|
user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve());
|
||||||
|
|
||||||
|
req.app.get = sinon.stub();
|
||||||
|
req.app.get.withArgs('logger').returns(winston);
|
||||||
|
req.app.get.withArgs('user data store').returns(user_data_store);
|
||||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||||
u2f(user_key_container).sign_request(req, res);
|
u2f.sign_request(req, res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +203,7 @@ describe('test u2f routes', function() {
|
||||||
|
|
||||||
req.session.auth_session.sign_request = {};
|
req.session.auth_session.sign_request = {};
|
||||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||||
u2f(user_key_container).sign(req, res);
|
u2f.sign(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return unauthorized error on registration request internal error', function(done) {
|
it('should return unauthorized error on registration request internal error', function(done) {
|
||||||
|
@ -209,7 +220,7 @@ describe('test u2f routes', function() {
|
||||||
|
|
||||||
req.session.auth_session.sign_request = {};
|
req.session.auth_session.sign_request = {};
|
||||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||||
u2f(user_key_container).register(req, res);
|
u2f.register(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return unauthorized error when no sign request has been initiated', function(done) {
|
it('should return unauthorized error when no sign request has been initiated', function(done) {
|
||||||
|
@ -223,7 +234,7 @@ describe('test u2f routes', function() {
|
||||||
u2f_mock.finishAuthentication.returns(Promise.resolve());
|
u2f_mock.finishAuthentication.returns(Promise.resolve());
|
||||||
|
|
||||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||||
u2f(user_key_container).register(req, res);
|
u2f.register(req, res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
|
||||||
|
var server = require('../../src/lib/server');
|
||||||
|
|
||||||
|
var request = require('request');
|
||||||
|
var assert = require('assert');
|
||||||
|
var speakeasy = require('speakeasy');
|
||||||
|
var sinon = require('sinon');
|
||||||
|
var Promise = require('bluebird');
|
||||||
|
var tmp = require('tmp');
|
||||||
|
|
||||||
|
var request = Promise.promisifyAll(request);
|
||||||
|
|
||||||
|
var PORT = 8050;
|
||||||
|
var BASE_URL = 'http://localhost:' + PORT;
|
||||||
|
|
||||||
|
describe('test data persistence', function() {
|
||||||
|
var u2f;
|
||||||
|
var tmpDir;
|
||||||
|
var ldap_client = {
|
||||||
|
bind: sinon.stub()
|
||||||
|
};
|
||||||
|
var config;
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
u2f = {};
|
||||||
|
u2f.startRegistration = sinon.stub();
|
||||||
|
u2f.finishRegistration = sinon.stub();
|
||||||
|
u2f.startAuthentication = sinon.stub();
|
||||||
|
u2f.finishAuthentication = sinon.stub();
|
||||||
|
|
||||||
|
ldap_client.bind.withArgs('cn=test_ok,ou=users,dc=example,dc=com',
|
||||||
|
'password').yields(undefined);
|
||||||
|
ldap_client.bind.withArgs('cn=test_nok,ou=users,dc=example,dc=com',
|
||||||
|
'password').yields('error');
|
||||||
|
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||||
|
config = {
|
||||||
|
port: PORT,
|
||||||
|
totp_secret: 'totp_secret',
|
||||||
|
ldap_url: 'ldap://127.0.0.1:389',
|
||||||
|
ldap_users_dn: 'ou=users,dc=example,dc=com',
|
||||||
|
session_secret: 'session_secret',
|
||||||
|
session_max_age: 50000,
|
||||||
|
store_directory: tmpDir.name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function() {
|
||||||
|
tmpDir.removeCallback();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save a u2f meta and reload it after a restart of the server', function() {
|
||||||
|
var server;
|
||||||
|
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));
|
||||||
|
|
||||||
|
var j1 = request.jar();
|
||||||
|
var j2 = request.jar();
|
||||||
|
return start_server(config, ldap_client, u2f)
|
||||||
|
.then(function(s) {
|
||||||
|
server = s;
|
||||||
|
return execute_login(j1);
|
||||||
|
})
|
||||||
|
.then(function(res) {
|
||||||
|
return execute_first_factor(j1);
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return execute_u2f_registration(j1);
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return execute_u2f_authentication(j1);
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return stop_server(server);
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return start_server(config, ldap_client, u2f)
|
||||||
|
})
|
||||||
|
.then(function(s) {
|
||||||
|
server = s;
|
||||||
|
return execute_login(j2);
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return execute_first_factor(j2);
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return execute_u2f_authentication(j2);
|
||||||
|
})
|
||||||
|
.then(function(res) {
|
||||||
|
assert.equal(204, res.statusCode);
|
||||||
|
server.close();
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.error(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function start_server(config, ldap_client, u2f) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var s = server.run(config, ldap_client, u2f);
|
||||||
|
resolve(s);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop_server(s) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
s.close();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute_first_factor(jar) {
|
||||||
|
return request.postAsync({
|
||||||
|
url: BASE_URL + '/_auth/1stfactor',
|
||||||
|
jar: jar,
|
||||||
|
form: {
|
||||||
|
username: 'test_ok',
|
||||||
|
password: 'password'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute_u2f_registration(jar) {
|
||||||
|
return request.getAsync({
|
||||||
|
url: BASE_URL + '/_auth/2ndfactor/u2f/register_request',
|
||||||
|
jar: jar
|
||||||
|
})
|
||||||
|
.then(function(res) {
|
||||||
|
return request.postAsync({
|
||||||
|
url: BASE_URL + '/_auth/2ndfactor/u2f/register',
|
||||||
|
jar: jar,
|
||||||
|
form: {
|
||||||
|
s: 'test'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute_u2f_authentication(jar) {
|
||||||
|
return request.getAsync({
|
||||||
|
url: BASE_URL + '/_auth/2ndfactor/u2f/sign_request',
|
||||||
|
jar: jar
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return request.postAsync({
|
||||||
|
url: BASE_URL + '/_auth/2ndfactor/u2f/sign',
|
||||||
|
jar: jar,
|
||||||
|
form: {
|
||||||
|
s: 'test'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute_verification(jar) {
|
||||||
|
return request.getAsync({ url: BASE_URL + '/_verify', jar: jar })
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute_login(jar) {
|
||||||
|
return request.getAsync({ url: BASE_URL + '/login', jar: jar })
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,68 @@
|
||||||
|
|
||||||
|
var UserDataStore = require('../../src/lib/user_data_store');
|
||||||
|
var DataStore = require('nedb');
|
||||||
|
var assert = require('assert');
|
||||||
|
var Promise = require('bluebird');
|
||||||
|
|
||||||
|
describe('test user data store', function() {
|
||||||
|
it('should save a u2f meta', function() {
|
||||||
|
var options = {};
|
||||||
|
options.inMemoryOnly = true;
|
||||||
|
|
||||||
|
var data_store = new UserDataStore(DataStore, options);
|
||||||
|
|
||||||
|
var userid = 'user';
|
||||||
|
var app_id = 'https://localhost';
|
||||||
|
var meta = {};
|
||||||
|
meta.publicKey = 'pbk';
|
||||||
|
|
||||||
|
return data_store.set_u2f_meta(userid, app_id, meta)
|
||||||
|
.then(function(numUpdated) {
|
||||||
|
assert.equal(1, numUpdated);
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve no u2f meta', function() {
|
||||||
|
var options = {};
|
||||||
|
options.inMemoryOnly = true;
|
||||||
|
|
||||||
|
var data_store = new UserDataStore(DataStore, options);
|
||||||
|
|
||||||
|
var userid = 'user';
|
||||||
|
var app_id = 'https://localhost';
|
||||||
|
var meta = {};
|
||||||
|
meta.publicKey = 'pbk';
|
||||||
|
|
||||||
|
return data_store.get_u2f_meta(userid, app_id)
|
||||||
|
.then(function(doc) {
|
||||||
|
assert.equal(undefined, doc);
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert and retrieve a u2f meta', function() {
|
||||||
|
var options = {};
|
||||||
|
options.inMemoryOnly = true;
|
||||||
|
|
||||||
|
var data_store = new UserDataStore(DataStore, options);
|
||||||
|
|
||||||
|
var userid = 'user';
|
||||||
|
var app_id = 'https://localhost';
|
||||||
|
var meta = {};
|
||||||
|
meta.publicKey = 'pbk';
|
||||||
|
|
||||||
|
return data_store.set_u2f_meta(userid, app_id, meta)
|
||||||
|
.then(function(numUpdated, data) {
|
||||||
|
assert.equal(1, numUpdated);
|
||||||
|
return data_store.get_u2f_meta(userid, app_id)
|
||||||
|
})
|
||||||
|
.then(function(doc) {
|
||||||
|
assert.deepEqual(meta, doc.meta);
|
||||||
|
assert.deepEqual(userid, doc.userid);
|
||||||
|
assert.deepEqual(app_id, doc.appid);
|
||||||
|
assert('_id' in doc);
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue