Use filesystem data store to save u2f meta info

pull/7/head
Clement Michaud 2017-01-21 20:24:35 +01:00
parent 9670b23a8b
commit 8b4339f8da
12 changed files with 433 additions and 109 deletions

View File

@ -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"]

View File

@ -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

View File

@ -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

View File

@ -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"
} }
} }

View File

@ -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({

View File

@ -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')),

View File

@ -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();
} });
} }

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}); });
} }
}); });

View File

@ -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 })
}
});

View File

@ -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();
});
});
});