Implement password reset
parent
320998ef78
commit
05046338ed
|
@ -4,6 +4,8 @@ debug_level: info
|
|||
ldap:
|
||||
url: ldap://ldap
|
||||
base_dn: ou=users,dc=example,dc=com
|
||||
user: cn=admin,dc=example,dc=com
|
||||
password: password
|
||||
|
||||
# Will be per user soon
|
||||
totp_secret: GRWGIJS6IRHVEODVNRCXCOBMJ5AGC6ZE
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<title>Home page</title>
|
||||
</head>
|
||||
<body>
|
||||
You need to <a href="/auth/login?redirect=/">log in</a> to access the <a href="/secret.html">secret</a>!<br/><br/>
|
||||
You can also log off by visiting the following <a href="/auth/logout?redirect=/">link</a>.
|
||||
You need to <a href="/authentication/login?redirect=/">log in</a> to access the <a href="/secret.html">secret</a>!<br/><br/>
|
||||
You can also log off by visiting the following <a href="/authentication/logout?redirect=/">link</a>.
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -34,19 +34,31 @@ http {
|
|||
|
||||
error_page 401 = @error401;
|
||||
location @error401 {
|
||||
return 302 https://localhost:8080/auth/login?redirect=$request_uri;
|
||||
return 302 https://localhost:8080/authentication/login?redirect=$request_uri;
|
||||
}
|
||||
|
||||
location /auth/ {
|
||||
location /authentication/ {
|
||||
proxy_set_header X-Original-URI $request_uri;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
proxy_pass http://auth/;
|
||||
proxy_pass http://auth/authentication/;
|
||||
}
|
||||
|
||||
location /authentication/js/ {
|
||||
proxy_pass http://auth/js/;
|
||||
}
|
||||
|
||||
location /authentication/img/ {
|
||||
proxy_pass http://auth/img/;
|
||||
}
|
||||
|
||||
location /authentication/css/ {
|
||||
proxy_pass http://auth/css/;
|
||||
}
|
||||
|
||||
location = /secret.html {
|
||||
auth_request /auth/verify;
|
||||
auth_request /authentication/verify;
|
||||
|
||||
auth_request_set $user $upstream_http_x_remote_user;
|
||||
proxy_set_header X-Forwarded-User $user;
|
||||
|
|
|
@ -23,9 +23,11 @@
|
|||
"authdog": "^0.1.1",
|
||||
"bluebird": "^3.4.7",
|
||||
"body-parser": "^1.15.2",
|
||||
"dovehash": "0.0.5",
|
||||
"ejs": "^2.5.5",
|
||||
"express": "^4.14.0",
|
||||
"express-session": "^1.14.2",
|
||||
"jshashes": "^1.0.6",
|
||||
"ldapjs": "^1.0.1",
|
||||
"nedb": "^1.8.0",
|
||||
"nodemailer": "^2.7.0",
|
||||
|
|
14
src/index.js
14
src/index.js
|
@ -1,8 +1,12 @@
|
|||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
var server = require('./lib/server');
|
||||
|
||||
var ldap = require('ldapjs');
|
||||
var u2f = require('authdog');
|
||||
var nodemailer = require('nodemailer');
|
||||
var nedb = require('nedb');
|
||||
var YAML = require('yamljs');
|
||||
|
||||
var config_path = process.argv[2];
|
||||
|
@ -15,6 +19,8 @@ var config = {
|
|||
totp_secret: yaml_config.totp_secret,
|
||||
ldap_url: yaml_config.ldap.url || 'ldap://127.0.0.1:389',
|
||||
ldap_users_dn: yaml_config.ldap.base_dn,
|
||||
ldap_user: yaml_config.ldap.user,
|
||||
ldap_password: yaml_config.ldap.password,
|
||||
session_secret: yaml_config.session.secret,
|
||||
session_max_age: yaml_config.session.expiration || 3600000, // in ms
|
||||
store_directory: yaml_config.store_directory,
|
||||
|
@ -30,4 +36,10 @@ var ldap_client = ldap.createClient({
|
|||
reconnect: true
|
||||
});
|
||||
|
||||
server.run(config, ldap_client, u2f);
|
||||
var deps = {};
|
||||
deps.u2f = u2f;
|
||||
deps.nedb = nedb;
|
||||
deps.nodemailer = nodemailer;
|
||||
deps.ldap = ldap;
|
||||
|
||||
server.run(config, ldap_client, deps);
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
module.exports = {
|
||||
LdapSearchError: LdapSearchError,
|
||||
LdapBindError: LdapBindError,
|
||||
IdentityError: IdentityError,
|
||||
AccessDeniedError: AccessDeniedError
|
||||
}
|
||||
|
||||
function LdapSearchError(message) {
|
||||
|
@ -15,3 +17,15 @@ function LdapBindError(message) {
|
|||
this.message = (message || "");
|
||||
}
|
||||
LdapBindError.prototype = Object.create(Error.prototype);
|
||||
|
||||
function IdentityError(message) {
|
||||
this.name = "IdentityError";
|
||||
this.message = (message || "");
|
||||
}
|
||||
IdentityError.prototype = Object.create(Error.prototype);
|
||||
|
||||
function AccessDeniedError(message) {
|
||||
this.name = "AccessDeniedError";
|
||||
this.message = (message || "");
|
||||
}
|
||||
AccessDeniedError.prototype = Object.create(Error.prototype);
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
|
||||
var objectPath = require('object-path');
|
||||
var randomstring = require('randomstring');
|
||||
var Promise = require('bluebird');
|
||||
var util = require('util');
|
||||
var exceptions = require('./exceptions');
|
||||
|
||||
module.exports = identity_check;
|
||||
|
||||
|
||||
// IdentityCheck class
|
||||
|
||||
function IdentityCheck(user_data_store, email_sender, logger) {
|
||||
this._user_data_store = user_data_store;
|
||||
this._email_sender = email_sender;
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
IdentityCheck.prototype.issue_token = function(userid, email, content, logger) {
|
||||
var five_minutes = 4 * 60 * 1000;
|
||||
var token = randomstring.generate({ length: 64 });
|
||||
var that = this;
|
||||
|
||||
this._logger.debug('identity_check: issue identity token %s for 5 minutes', token);
|
||||
return this._user_data_store.issue_identity_check_token(userid, token, content, five_minutes)
|
||||
.then(function() {
|
||||
that._logger.debug('identity_check: send email to %s', email);
|
||||
return that._send_identity_check_email(email, token);
|
||||
})
|
||||
}
|
||||
|
||||
IdentityCheck.prototype._send_identity_check_email = function(email, token) {
|
||||
var url = util.format('%s?identity_token=%s', email.hook_url, token);
|
||||
var email_content = util.format('<a href="%s">Register</a>', url);
|
||||
return this._email_sender.send(email.to, email.subject, email_content);
|
||||
}
|
||||
|
||||
IdentityCheck.prototype.consume_token = function(token, logger) {
|
||||
this._logger.debug('identity_check: consume token %s', token);
|
||||
return this._user_data_store.consume_identity_check_token(token)
|
||||
}
|
||||
|
||||
|
||||
// The identity_check middleware that allows the user two perform a two step validation
|
||||
// using the user email
|
||||
|
||||
function identity_check(app, endpoint, icheck_interface) {
|
||||
app.get(endpoint, identity_check_get(endpoint, icheck_interface));
|
||||
app.post(endpoint, identity_check_post(endpoint, icheck_interface));
|
||||
}
|
||||
|
||||
|
||||
function identity_check_get(endpoint, icheck_interface) {
|
||||
return function(req, res) {
|
||||
var logger = req.app.get('logger');
|
||||
var identity_token = objectPath.get(req, 'query.identity_token');
|
||||
logger.info('GET identity_check: identity token provided is %s', identity_token);
|
||||
|
||||
if(!identity_token) {
|
||||
res.status(403);
|
||||
res.send();
|
||||
return;
|
||||
}
|
||||
|
||||
var email_sender = req.app.get('email sender');
|
||||
var user_data_store = req.app.get('user data store');
|
||||
var identity_check = new IdentityCheck(user_data_store, email_sender, logger);
|
||||
|
||||
identity_check.consume_token(identity_token, logger)
|
||||
.then(function(content) {
|
||||
objectPath.set(req, 'session.auth_session.identity_check', {});
|
||||
req.session.auth_session.identity_check.challenge = icheck_interface.challenge;
|
||||
req.session.auth_session.identity_check.userid = content.userid;
|
||||
res.render(icheck_interface.render_template);
|
||||
}, function(err) {
|
||||
logger.error('GET identity_check: Error while consuming token %s', err);
|
||||
throw new exceptions.AccessDeniedError('Access denied');
|
||||
})
|
||||
.catch(exceptions.AccessDeniedError, function(err) {
|
||||
logger.error('GET identity_check: Access Denied %s', err);
|
||||
res.status(403);
|
||||
res.send();
|
||||
})
|
||||
.catch(function(err) {
|
||||
logger.error('GET identity_check: Internal error %s', err);
|
||||
res.status(500);
|
||||
res.send();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function identity_check_post(endpoint, icheck_interface) {
|
||||
return function(req, res) {
|
||||
var logger = req.app.get('logger');
|
||||
var email_sender = req.app.get('email sender');
|
||||
var user_data_store = req.app.get('user data store');
|
||||
var identity_check = new IdentityCheck(user_data_store, email_sender, logger);
|
||||
var userid, email_address;
|
||||
|
||||
icheck_interface.pre_check_callback(req)
|
||||
.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');
|
||||
}
|
||||
|
||||
var email = {};
|
||||
email.to = email_address;
|
||||
email.subject = 'Identity Verification';
|
||||
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');
|
||||
})
|
||||
.then(function() {
|
||||
res.status(204);
|
||||
res.send();
|
||||
})
|
||||
.catch(exceptions.IdentityError, function(err) {
|
||||
logger.error('POST identity_check: %s', err);
|
||||
res.status(400);
|
||||
res.send();
|
||||
return;
|
||||
})
|
||||
.catch(exceptions.AccessDeniedError, function(err) {
|
||||
logger.error('POST identity_check: %s', err);
|
||||
res.status(403);
|
||||
res.send();
|
||||
return;
|
||||
})
|
||||
.catch(function(err) {
|
||||
logger.error('POST identity_check: %s', err);
|
||||
res.status(500);
|
||||
res.send();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,18 +1,23 @@
|
|||
|
||||
module.exports = {
|
||||
validate: validateCredentials,
|
||||
get_email: retrieve_email
|
||||
get_email: retrieve_email,
|
||||
update_password: update_password
|
||||
}
|
||||
|
||||
var util = require('util');
|
||||
var Promise = require('bluebird');
|
||||
var exceptions = require('./exceptions');
|
||||
var Hashes = require('jshashes')
|
||||
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);
|
||||
throw new exceptions.LdapBindError(err.message);
|
||||
});
|
||||
}
|
||||
|
@ -33,14 +38,33 @@ function retrieve_email(ldap_client, username, users_dn) {
|
|||
doc = entry.object;
|
||||
});
|
||||
res.on('error', function(err) {
|
||||
reject(new exceptions.LdapSearchError(err.message));
|
||||
reject(new exceptions.LdapSearchError(err));
|
||||
});
|
||||
res.on('end', function(result) {
|
||||
resolve(doc);
|
||||
});
|
||||
})
|
||||
.catch(function(err) {
|
||||
reject(new exceptions.LdapSearchError(err.message));
|
||||
reject(new exceptions.LdapSearchError(err));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function update_password(ldap_client, ldap, username, new_password, config) {
|
||||
var userDN = util.format("cn=%s,%s", username, config.ldap_users_dn);
|
||||
var encoded_password = Dovehash.encode('SSHA', new_password);
|
||||
var change = new ldap.Change({
|
||||
operation: 'replace',
|
||||
modification: {
|
||||
userPassword: encoded_password
|
||||
}
|
||||
});
|
||||
|
||||
var modify_promised = Promise.promisify(ldap_client.modify, { context: ldap_client });
|
||||
var bind_promised = Promise.promisify(ldap_client.bind, { context: ldap_client });
|
||||
|
||||
return bind_promised(config.ldap_user, config.ldap_password)
|
||||
.then(function() {
|
||||
return modify_promised(userDN, change);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
|
||||
var first_factor = require('./routes/first_factor');
|
||||
var second_factor = require('./routes/second_factor');
|
||||
var reset_password = require('./routes/reset_password');
|
||||
var verify = require('./routes/verify');
|
||||
var u2f_register_handler = require('./routes/u2f_register_handler');
|
||||
var objectPath = require('object-path');
|
||||
|
||||
module.exports = {
|
||||
|
@ -9,7 +11,9 @@ module.exports = {
|
|||
logout: serveLogout,
|
||||
verify: verify,
|
||||
first_factor: first_factor,
|
||||
second_factor: second_factor
|
||||
second_factor: second_factor,
|
||||
reset_password: reset_password,
|
||||
u2f_register: u2f_register_handler
|
||||
}
|
||||
|
||||
function serveLogin(req, res) {
|
||||
|
@ -18,7 +22,6 @@ function serveLogin(req, res) {
|
|||
req.session.auth_session.first_factor = false;
|
||||
req.session.auth_session.second_factor = false;
|
||||
}
|
||||
|
||||
res.render('login');
|
||||
}
|
||||
|
||||
|
|
|
@ -12,15 +12,8 @@ function replyWithUnauthorized(res) {
|
|||
|
||||
function first_factor(req, res) {
|
||||
var logger = req.app.get('logger');
|
||||
if(!objectPath.has(req, 'session.auth_session.second_factor')) {
|
||||
logger.error('1st factor: Session is missing.');
|
||||
replyWithUnauthorized(res);
|
||||
}
|
||||
|
||||
var username = req.body.username;
|
||||
var password = req.body.password;
|
||||
console.log('Start authentication of user %s', username);
|
||||
|
||||
if(!username || !password) {
|
||||
replyWithUnauthorized(res);
|
||||
return;
|
||||
|
@ -34,29 +27,31 @@ function first_factor(req, res) {
|
|||
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)
|
||||
.then(function() {
|
||||
req.session.auth_session.userid = username;
|
||||
req.session.auth_session.first_factor = true;
|
||||
objectPath.set(req, 'session.auth_session.userid', username);
|
||||
objectPath.set(req, 'session.auth_session.first_factor', true);
|
||||
logger.info('1st factor: LDAP binding successful');
|
||||
logger.debug('1st factor: Retrieve email from LDAP');
|
||||
return ldap.get_email(ldap_client, username, config.ldap_users_dn)
|
||||
})
|
||||
.then(function(doc) {
|
||||
logger.debug('1st factor: document=%s', JSON.stringify(doc));
|
||||
var email = objectPath.get(doc, 'mail');
|
||||
logger.debug('1st factor: document=%s', JSON.stringify(doc));
|
||||
logger.debug('1st factor: Retrieved email is %s', email);
|
||||
req.session.auth_session.email = email;
|
||||
|
||||
objectPath.set(req, 'session.auth_session.email', email);
|
||||
res.status(204);
|
||||
res.send();
|
||||
})
|
||||
.catch(exceptions.LdapSearchError, function(err) {
|
||||
logger.info('1st factor: Unable to retrieve email from LDAP');
|
||||
logger.info('1st factor: Unable to retrieve email from LDAP', err);
|
||||
res.status(500);
|
||||
res.send();
|
||||
})
|
||||
.catch(exceptions.LdapBindError, function(err) {
|
||||
logger.info('1st factor: LDAP binding failed');
|
||||
logger.info('1st factor: LDAP binding failed', err);
|
||||
replyWithUnauthorized(res);
|
||||
})
|
||||
.catch(function(err) {
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
|
||||
var objectPath = require('object-path');
|
||||
var ldap = require('../ldap');
|
||||
var CHALLENGE = 'reset-password';
|
||||
|
||||
var icheck_interface = {
|
||||
challenge: CHALLENGE,
|
||||
render_template: 'reset-password',
|
||||
pre_check_callback: pre_check,
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
icheck_interface: icheck_interface,
|
||||
post: protect(post)
|
||||
}
|
||||
|
||||
function pre_check(req) {
|
||||
var userid = objectPath.get(req, 'body.userid');
|
||||
if(!userid) {
|
||||
return Promise.reject('No user id provided');
|
||||
}
|
||||
|
||||
var ldap_client = req.app.get('ldap client');
|
||||
var config = req.app.get('config');
|
||||
|
||||
return ldap.get_email(ldap_client, userid, config.ldap_users_dn)
|
||||
.then(function(doc) {
|
||||
var email = objectPath.get(doc, 'mail');
|
||||
|
||||
var identity = {}
|
||||
identity.email = email;
|
||||
identity.userid = userid;
|
||||
return Promise.resolve(identity);
|
||||
})
|
||||
}
|
||||
|
||||
function protect(fn) {
|
||||
return function(req, res) {
|
||||
var challenge = objectPath.get(req, 'session.auth_session.identity_check.challenge');
|
||||
if(challenge != CHALLENGE) {
|
||||
res.status(403);
|
||||
res.send();
|
||||
return;
|
||||
}
|
||||
fn(req, res);
|
||||
}
|
||||
}
|
||||
|
||||
function post(req, res) {
|
||||
var logger = req.app.get('logger');
|
||||
var ldapjs = req.app.get('ldap');
|
||||
var ldap_client = req.app.get('ldap client');
|
||||
var new_password = objectPath.get(req, 'body.password');
|
||||
var userid = objectPath.get(req, 'session.auth_session.identity_check.userid');
|
||||
var config = req.app.get('config');
|
||||
|
||||
logger.info('POST reset-password: User %s wants to reset his/her password', userid);
|
||||
|
||||
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', {});
|
||||
res.status(204);
|
||||
res.send();
|
||||
})
|
||||
.catch(function(err) {
|
||||
logger.error('POST reset-password: Error while resetting the password of user %s. %s', userid, err);
|
||||
res.status(500);
|
||||
res.send();
|
||||
});
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
var u2f_register = require('./u2f_register');
|
||||
var u2f_common = require('./u2f_common');
|
||||
var objectPath = require('object-path');
|
||||
|
||||
module.exports = {
|
||||
register_request: u2f_register.register_request,
|
||||
|
@ -12,41 +13,18 @@ module.exports = {
|
|||
sign: sign,
|
||||
}
|
||||
|
||||
var objectPath = require('object-path');
|
||||
|
||||
function retrieveU2fMeta(req, user_data_storage) {
|
||||
function retrieve_u2f_meta(req, user_data_storage) {
|
||||
var userid = req.session.auth_session.userid;
|
||||
var appid = u2f_common.extract_app_id(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) {
|
||||
resolve(authRequest);
|
||||
}, function(err) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function finishU2fAuthentication(u2f, authRequest, data, meta) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
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)
|
||||
retrieve_u2f_meta(req, user_data_storage)
|
||||
.then(function(doc) {
|
||||
if(!doc) {
|
||||
u2f_common.reply_with_missing_registration(res);
|
||||
|
@ -57,7 +35,7 @@ function sign_request(req, res) {
|
|||
var meta = doc.meta;
|
||||
var appid = u2f_common.extract_app_id(req);
|
||||
logger.info('U2F sign_request: Start authentication');
|
||||
return startU2fAuthentication(u2f, appid, meta);
|
||||
return u2f.startAuthentication(appid, [meta])
|
||||
})
|
||||
.then(function(authRequest) {
|
||||
logger.info('U2F sign_request: Store authentication request and reply');
|
||||
|
@ -67,7 +45,8 @@ function sign_request(req, res) {
|
|||
})
|
||||
.catch(function(err) {
|
||||
logger.info('U2F sign_request: %s', err);
|
||||
u2f_common.reply_with_unauthorized(res);
|
||||
res.status(500);
|
||||
res.send();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -81,14 +60,14 @@ function sign(req, res) {
|
|||
var logger = req.app.get('logger');
|
||||
var user_data_storage = req.app.get('user data store');
|
||||
|
||||
retrieveU2fMeta(req, user_data_storage)
|
||||
retrieve_u2f_meta(req, user_data_storage)
|
||||
.then(function(doc) {
|
||||
var appid = u2f_common.extract_app_id(req);
|
||||
var u2f = req.app.get('u2f');
|
||||
var authRequest = req.session.auth_session.sign_request;
|
||||
var meta = doc.meta;
|
||||
logger.info('U2F sign: Finish authentication');
|
||||
return finishU2fAuthentication(u2f, authRequest, req.body, meta);
|
||||
return u2f.finishAuthentication(authRequest, req.body, [meta])
|
||||
})
|
||||
.then(function(authenticationStatus) {
|
||||
logger.info('U2F sign: Authentication successful');
|
||||
|
@ -98,7 +77,7 @@ function sign(req, res) {
|
|||
})
|
||||
.catch(function(err) {
|
||||
logger.error('U2F sign: %s', err);
|
||||
res.status(401);
|
||||
res.status(500);
|
||||
res.send();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -13,8 +13,15 @@ var u2f_common = require('./u2f_common');
|
|||
var Promise = require('bluebird');
|
||||
|
||||
function register_request(req, res) {
|
||||
var u2f = req.app.get('u2f');
|
||||
var logger = req.app.get('logger');
|
||||
var challenge = objectPath.get(req, 'session.auth_session.identity_check.challenge');
|
||||
if(challenge != 'u2f-register') {
|
||||
res.status(403);
|
||||
res.send();
|
||||
return;
|
||||
}
|
||||
|
||||
var u2f = req.app.get('u2f');
|
||||
var appid = u2f_common.extract_app_id(req);
|
||||
|
||||
logger.debug('U2F register_request: headers=%s', JSON.stringify(req.headers));
|
||||
|
@ -28,19 +35,23 @@ function register_request(req, res) {
|
|||
})
|
||||
.catch(function(err) {
|
||||
logger.error('U2F register_request: %s', err);
|
||||
u2f_common.reply_with_internal_error(res, 'Unable to complete the registration');
|
||||
res.status(500);
|
||||
res.send('Unable to start registration request');
|
||||
});
|
||||
}
|
||||
|
||||
function register(req, res) {
|
||||
if(!objectPath.has(req, 'session.auth_session.register_request')) {
|
||||
u2f_common.reply_with_unauthorized(res);
|
||||
var registrationRequest = objectPath.get(req, 'session.auth_session.register_request');
|
||||
var challenge = objectPath.get(req, 'session.auth_session.identity_check.challenge');
|
||||
|
||||
if(!(registrationRequest && challenge == 'u2f-register')) {
|
||||
res.status(403);
|
||||
res.send();
|
||||
return;
|
||||
}
|
||||
|
||||
var user_data_storage = req.app.get('user data store');
|
||||
var u2f = req.app.get('u2f');
|
||||
var registrationRequest = req.session.auth_session.register_request;
|
||||
var userid = req.session.auth_session.userid;
|
||||
var appid = u2f_common.extract_app_id(req);
|
||||
var logger = req.app.get('logger');
|
||||
|
@ -65,7 +76,8 @@ function register(req, res) {
|
|||
})
|
||||
.catch(function(err) {
|
||||
logger.error('U2F register: %s', err);
|
||||
u2f_common.reply_with_unauthorized(res);
|
||||
res.status(500);
|
||||
res.send('Unable to register');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,93 +1,36 @@
|
|||
|
||||
module.exports = {
|
||||
get: register_handler_get,
|
||||
post: register_handler_post
|
||||
}
|
||||
|
||||
var objectPath = require('object-path');
|
||||
var randomstring = require('randomstring');
|
||||
var Promise = require('bluebird');
|
||||
var util = require('util');
|
||||
|
||||
var u2f_common = require('./u2f_common');
|
||||
var CHALLENGE = 'u2f-register';
|
||||
|
||||
function register_handler_get(req, res) {
|
||||
var logger = req.app.get('logger');
|
||||
logger.info('U2F register_handler: Continue registration process');
|
||||
var icheck_interface = {
|
||||
challenge: CHALLENGE,
|
||||
render_template: 'u2f-register',
|
||||
pre_check_callback: pre_check,
|
||||
}
|
||||
|
||||
var registration_token = objectPath.get(req, 'query.registration_token');
|
||||
logger.debug('U2F register_handler: registration_token=%s', registration_token);
|
||||
module.exports = {
|
||||
icheck_interface: icheck_interface,
|
||||
}
|
||||
|
||||
if(!registration_token) {
|
||||
res.status(403);
|
||||
res.send();
|
||||
return;
|
||||
|
||||
function pre_check(req) {
|
||||
var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor');
|
||||
if(!first_factor_passed) {
|
||||
return Promise.reject('Authentication required before issuing a u2f registration request');
|
||||
}
|
||||
|
||||
var user_data_store = req.app.get('user data store');
|
||||
|
||||
logger.debug('U2F register_handler: verify token validity and consume it');
|
||||
user_data_store.consume_u2f_registration_token(registration_token)
|
||||
.then(function() {
|
||||
res.render('u2f_register');
|
||||
})
|
||||
.catch(function(err) {
|
||||
res.status(403);
|
||||
res.send();
|
||||
});
|
||||
}
|
||||
|
||||
function send_u2f_registration_email(email_sender, original_url, email, token) {
|
||||
var url = util.format('%s?registration_token=%s', original_url, token);
|
||||
var email_content = util.format('<a href="%s">Register</a>', url);
|
||||
return email_sender.send(email, 'U2F Registration', email_content);
|
||||
}
|
||||
|
||||
function register_handler_post(req, res) {
|
||||
var logger = req.app.get('logger');
|
||||
logger.info('U2F register_handler: Starting registration process');
|
||||
logger.debug('U2F register_request: headers=%s', JSON.stringify(req.headers));
|
||||
|
||||
var userid = objectPath.get(req, 'session.auth_session.userid');
|
||||
var email = objectPath.get(req, 'session.auth_session.email');
|
||||
var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor');
|
||||
|
||||
// the user needs to have validated the first factor
|
||||
if(!(userid && first_factor_passed)) {
|
||||
var error = 'You need to be authenticated to register';
|
||||
logger.error('U2F register_handler: %s', error);
|
||||
res.status(403);
|
||||
res.send(error);
|
||||
return;
|
||||
|
||||
if(!(userid && email)) {
|
||||
return Promise.reject('User ID or email is missing');
|
||||
}
|
||||
|
||||
if(!email) {
|
||||
var error = util.format('No email has been found for user %s', userid);
|
||||
logger.error('U2F register_handler: %s', error);
|
||||
res.status(400);
|
||||
res.send(error);
|
||||
return;
|
||||
}
|
||||
|
||||
var five_minutes = 4 * 60 * 1000;
|
||||
var user_data_store = req.app.get('user data store');
|
||||
var token = randomstring.generate({ length: 64 });
|
||||
|
||||
logger.debug('U2F register_request: issue u2f registration token %s for 5 minutes', token);
|
||||
user_data_store.save_u2f_registration_token(userid, token, five_minutes)
|
||||
.then(function() {
|
||||
logger.debug('U2F register_request: Send u2f registration email to %s', email);
|
||||
var email_sender = req.app.get('email sender');
|
||||
var original_url = u2f_common.extract_original_url(req);
|
||||
return send_u2f_registration_email(email_sender, original_url, email, token);
|
||||
})
|
||||
.then(function() {
|
||||
res.status(204);
|
||||
res.send();
|
||||
})
|
||||
.catch(function(err) {
|
||||
logger.error('U2F register_handler: %s', err);
|
||||
res.status(500);
|
||||
res.send();
|
||||
});
|
||||
var identity = {};
|
||||
identity.email = email;
|
||||
identity.userid = userid;
|
||||
return Promise.resolve(identity);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,6 @@ function verify_filter(req, res) {
|
|||
}
|
||||
|
||||
function verify(req, res) {
|
||||
console.log('Verify authentication');
|
||||
|
||||
verify_filter(req, res)
|
||||
.then(function() {
|
||||
res.status(204);
|
||||
|
|
|
@ -11,12 +11,11 @@ var speakeasy = require('speakeasy');
|
|||
var path = require('path');
|
||||
var session = require('express-session');
|
||||
var winston = require('winston');
|
||||
var DataStore = require('nedb');
|
||||
var nodemailer = require('nodemailer');
|
||||
var UserDataStore = require('./user_data_store');
|
||||
var EmailSender = require('./email_sender');
|
||||
var identity_check = require('./identity_check');
|
||||
|
||||
function run(config, ldap_client, u2f, fn) {
|
||||
function run(config, ldap_client, deps, fn) {
|
||||
var view_directory = path.resolve(__dirname, '../views');
|
||||
var public_html_directory = path.resolve(__dirname, '../public_html');
|
||||
var datastore_options = {};
|
||||
|
@ -47,32 +46,39 @@ function run(config, ldap_client, u2f, fn) {
|
|||
winston.level = config.debug_level || 'info';
|
||||
|
||||
app.set('logger', winston);
|
||||
app.set('ldap', deps.ldap);
|
||||
app.set('ldap client', ldap_client);
|
||||
app.set('totp engine', speakeasy);
|
||||
app.set('u2f', u2f);
|
||||
app.set('user data store', new UserDataStore(DataStore, datastore_options));
|
||||
app.set('email sender', new EmailSender(nodemailer, email_options));
|
||||
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('config', config);
|
||||
|
||||
var base_endpoint = '/authentication';
|
||||
|
||||
// web pages
|
||||
app.get ('/login', routes.login);
|
||||
app.get ('/logout', routes.logout);
|
||||
app.get (base_endpoint + '/login', routes.login);
|
||||
app.get (base_endpoint + '/logout', routes.logout);
|
||||
|
||||
app.get ('/u2f-register', routes.second_factor.u2f.register_handler_get);
|
||||
app.post ('/u2f-register', routes.second_factor.u2f.register_handler_post);
|
||||
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
|
||||
app.post (base_endpoint + '/new-password', routes.reset_password.post);
|
||||
|
||||
// verify authentication
|
||||
app.get ('/verify', routes.verify);
|
||||
app.get (base_endpoint + '/verify', routes.verify);
|
||||
|
||||
// Authentication process
|
||||
app.post ('/1stfactor', routes.first_factor);
|
||||
app.post ('/2ndfactor/totp', routes.second_factor.totp);
|
||||
app.post (base_endpoint + '/1stfactor', routes.first_factor);
|
||||
app.post (base_endpoint + '/2ndfactor/totp', routes.second_factor.totp);
|
||||
|
||||
app.get ('/2ndfactor/u2f/register_request', routes.second_factor.u2f.register_request);
|
||||
app.post ('/2ndfactor/u2f/register', routes.second_factor.u2f.register);
|
||||
app.get (base_endpoint + '/2ndfactor/u2f/register_request', routes.second_factor.u2f.register_request);
|
||||
app.post (base_endpoint + '/2ndfactor/u2f/register', routes.second_factor.u2f.register);
|
||||
|
||||
app.get ('/2ndfactor/u2f/sign_request', routes.second_factor.u2f.sign_request);
|
||||
app.post ('/2ndfactor/u2f/sign', routes.second_factor.u2f.sign);
|
||||
app.get (base_endpoint + '/2ndfactor/u2f/sign_request', routes.second_factor.u2f.sign_request);
|
||||
app.post (base_endpoint + '/2ndfactor/u2f/sign', routes.second_factor.u2f.sign);
|
||||
|
||||
return app.listen(config.port, function(err) {
|
||||
console.log('Listening on %d...', config.port);
|
||||
|
|
|
@ -6,8 +6,8 @@ var path = require('path');
|
|||
|
||||
function UserDataStore(DataStore, options) {
|
||||
this._u2f_meta_collection = create_collection('u2f_meta', options, DataStore);
|
||||
this._u2f_registration_tokens_collection =
|
||||
create_collection('u2f_registration_tokens', options, DataStore);
|
||||
this._identity_check_tokens_collection =
|
||||
create_collection('identity_check_tokens', options, DataStore);
|
||||
}
|
||||
|
||||
function create_collection(name, options, DataStore) {
|
||||
|
@ -42,35 +42,41 @@ UserDataStore.prototype.get_u2f_meta = function(userid, app_id) {
|
|||
return this._u2f_meta_collection.findOneAsync(filter);
|
||||
}
|
||||
|
||||
UserDataStore.prototype.save_u2f_registration_token = function(userid, token, max_age) {
|
||||
UserDataStore.prototype.issue_identity_check_token = function(userid, token, data, max_age) {
|
||||
var newDocument = {};
|
||||
newDocument.userid = userid;
|
||||
newDocument.token = token;
|
||||
newDocument.content = { userid: userid, data: data };
|
||||
newDocument.max_date = new Date(new Date().getTime() + max_age);
|
||||
|
||||
return this._u2f_registration_tokens_collection.insertAsync(newDocument);
|
||||
return this._identity_check_tokens_collection.insertAsync(newDocument);
|
||||
}
|
||||
|
||||
UserDataStore.prototype.consume_u2f_registration_token = function(token) {
|
||||
UserDataStore.prototype.consume_identity_check_token = function(token) {
|
||||
var query = {};
|
||||
query.token = token;
|
||||
var that = this;
|
||||
var doc_content;
|
||||
|
||||
return this._u2f_registration_tokens_collection.findOneAsync(query)
|
||||
return this._identity_check_tokens_collection.findOneAsync(query)
|
||||
.then(function(doc) {
|
||||
if(!doc) {
|
||||
return Promise.reject('Registration token does not exist');
|
||||
}
|
||||
if(!doc) {
|
||||
return Promise.reject('Registration token does not exist');
|
||||
}
|
||||
|
||||
var max_date = doc.max_date;
|
||||
var current_date = new Date();
|
||||
if(current_date > max_date) {
|
||||
return Promise.reject('Registration token is not valid anymore');
|
||||
}
|
||||
var max_date = doc.max_date;
|
||||
var current_date = new Date();
|
||||
if(current_date > max_date) {
|
||||
return Promise.reject('Registration token is not valid anymore');
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
doc_content = doc.content;
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(function() {
|
||||
return that._u2f_registration_tokens_collection.removeAsync(query);
|
||||
});
|
||||
return that._identity_check_tokens_collection.removeAsync(query);
|
||||
})
|
||||
.then(function() {
|
||||
return Promise.resolve(doc_content);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ body {
|
|||
|
||||
.login h2 { color: #fff; text-shadow: 0 0 10px rgba(0,0,0,0.3); letter-spacing:1px; text-align:center; font-size: 1em; }
|
||||
|
||||
.login p { color: #fff; text-shadow: 0 0 10px rgba(0,0,0,0.3); letter-spacing:1px; text-align:center; }
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
|
@ -98,6 +100,6 @@ input:focus { box-shadow: inset 0 -5px 45px rgba(100,100,100,0.4), 0 1px 1px rgb
|
|||
float: right;
|
||||
}
|
||||
|
||||
#second-factor #u2f button {
|
||||
button {
|
||||
margin-top: 5px;
|
||||
}
|
|
@ -30,6 +30,11 @@ function onLoginButtonClicked() {
|
|||
});
|
||||
}
|
||||
|
||||
function onResetPasswordButtonClicked() {
|
||||
var r = '/authentication/reset-password-form';
|
||||
window.location.replace(r);
|
||||
}
|
||||
|
||||
function onTotpSignButtonClicked() {
|
||||
var token = $('#totp-token').val();
|
||||
validateSecondFactorTotp(token, function(err) {
|
||||
|
@ -64,7 +69,7 @@ function onU2fRegistrationButtonClicked() {
|
|||
function askForU2fRegistration(fn) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/auth/u2f-register'
|
||||
url: '/authentication/u2f-register'
|
||||
})
|
||||
.done(function(data) {
|
||||
fn(undefined, data);
|
||||
|
@ -91,7 +96,7 @@ function finishU2fAuthentication(url, responseData, fn) {
|
|||
}
|
||||
|
||||
function startU2fAuthentication(fn, timeout) {
|
||||
$.get('/auth/2ndfactor/u2f/sign_request', {}, null, 'json')
|
||||
$.get('/authentication/2ndfactor/u2f/sign_request', {}, null, 'json')
|
||||
.done(function(signResponse) {
|
||||
var registeredKeys = signResponse.registeredKeys;
|
||||
$.notify('Please touch the token', 'info');
|
||||
|
@ -104,7 +109,7 @@ function startU2fAuthentication(fn, timeout) {
|
|||
if (response.errorCode) {
|
||||
fn(response);
|
||||
} else {
|
||||
finishU2fAuthentication('/auth/2ndfactor/u2f/sign', response, fn);
|
||||
finishU2fAuthentication('/authentication/2ndfactor/u2f/sign', response, fn);
|
||||
}
|
||||
},
|
||||
timeout
|
||||
|
@ -116,7 +121,7 @@ function startU2fAuthentication(fn, timeout) {
|
|||
}
|
||||
|
||||
function validateSecondFactorTotp(token, fn) {
|
||||
$.post('/auth/2ndfactor/totp', {
|
||||
$.post('/authentication/2ndfactor/totp', {
|
||||
token: token,
|
||||
})
|
||||
.done(function() {
|
||||
|
@ -128,7 +133,7 @@ function validateSecondFactorTotp(token, fn) {
|
|||
}
|
||||
|
||||
function validateFirstFactor(username, password, fn) {
|
||||
$.post('/auth/1stfactor', {
|
||||
$.post('/authentication/1stfactor', {
|
||||
username: username,
|
||||
password: password,
|
||||
})
|
||||
|
@ -200,7 +205,6 @@ function hideSecondFactorLayout() {
|
|||
function setupFirstFactorLoginButton() {
|
||||
$('#first-factor #login-button').on('click', onLoginButtonClicked);
|
||||
setupEnterKeypressListener('#login-form', onLoginButtonClicked);
|
||||
$('#first-factor #information').hide();
|
||||
}
|
||||
|
||||
function cleanupFirstFactorLoginButton() {
|
||||
|
@ -221,10 +225,15 @@ function setupU2fRegistrationButton() {
|
|||
$('#second-factor #u2f-register-button').on('click', onU2fRegistrationButtonClicked);
|
||||
}
|
||||
|
||||
function setupResetPasswordButton() {
|
||||
$('#first-factor #reset-password-button').on('click', onResetPasswordButtonClicked);
|
||||
}
|
||||
|
||||
function enterFirstFactor() {
|
||||
showFirstFactorLayout();
|
||||
hideSecondFactorLayout();
|
||||
setupFirstFactorLoginButton();
|
||||
setupResetPasswordButton();
|
||||
}
|
||||
|
||||
function enterSecondFactor() {
|
|
@ -0,0 +1,47 @@
|
|||
(function() {
|
||||
|
||||
function setupEnterKeypressListener(filter, fn) {
|
||||
$(filter).on('keydown', 'input', function (e) {
|
||||
var key = e.which;
|
||||
switch (key) {
|
||||
case 13: // enter key code
|
||||
fn();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onResetPasswordButtonClicked() {
|
||||
var username = $('#username').val();
|
||||
|
||||
if(!username) {
|
||||
$.notify('You must provide your username to reset your password.', 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
$.post('/authentication/reset-password', {
|
||||
userid: username,
|
||||
})
|
||||
.done(function() {
|
||||
$.notify('An email has been sent. Click on the link to change your password', 'success');
|
||||
setTimeout(function() {
|
||||
window.location.replace('/authentication/login');
|
||||
}, 1000);
|
||||
})
|
||||
.fail(function() {
|
||||
$.notify('Are you sure this is your username?', 'warn');
|
||||
});
|
||||
}
|
||||
|
||||
function setupResetPasswordButton() {
|
||||
$('#reset-password-button').on('click', onResetPasswordButtonClicked);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
setupResetPasswordButton();
|
||||
setupEnterKeypressListener('#reset-password-form', onResetPasswordButtonClicked);
|
||||
});
|
||||
|
||||
})();
|
|
@ -0,0 +1,51 @@
|
|||
(function() {
|
||||
|
||||
function setupEnterKeypressListener(filter, fn) {
|
||||
$(filter).on('keydown', 'input', function (e) {
|
||||
var key = e.which;
|
||||
switch (key) {
|
||||
case 13: // enter key code
|
||||
fn();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onResetPasswordButtonClicked() {
|
||||
var password1 = $('#password1').val();
|
||||
var password2 = $('#password2').val();
|
||||
|
||||
if(!password1 || !password2) {
|
||||
$.notify('You must enter your new password twice.', 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
if(password1 != password2) {
|
||||
$.notify('The passwords are different', 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
$.post('/authentication/new-password', {
|
||||
password: password1,
|
||||
})
|
||||
.done(function() {
|
||||
$.notify('Your password has been changed. Please login again', 'success');
|
||||
window.location.replace('/authentication/login');
|
||||
})
|
||||
.fail(function() {
|
||||
$.notify('An error occurred during password change.', 'warn');
|
||||
});
|
||||
}
|
||||
|
||||
function setupResetPasswordButton() {
|
||||
$('#reset-password-button').on('click', onResetPasswordButtonClicked);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
setupResetPasswordButton();
|
||||
setupEnterKeypressListener('#reset-password-form', onResetPasswordButtonClicked);
|
||||
});
|
||||
|
||||
})();
|
|
@ -4,7 +4,6 @@ params={};
|
|||
location.search.replace(/[?&]+([^=&]+)=([^&]*)/gi,function(s,k,v){params[k]=v});
|
||||
|
||||
function finishRegister(url, responseData, fn) {
|
||||
console.log(responseData);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
|
@ -21,7 +20,7 @@ function finishRegister(url, responseData, fn) {
|
|||
}
|
||||
|
||||
function startRegister(fn, timeout) {
|
||||
$.get('/auth/2ndfactor/u2f/register_request', {}, null, 'json')
|
||||
$.get('/authentication/2ndfactor/u2f/register_request', {}, null, 'json')
|
||||
.done(function(startRegisterResponse) {
|
||||
u2f.register(
|
||||
startRegisterResponse.appId,
|
||||
|
@ -31,7 +30,7 @@ function startRegister(fn, timeout) {
|
|||
if (response.errorCode) {
|
||||
fn(response.errorCode);
|
||||
} else {
|
||||
finishRegister('/auth/2ndfactor/u2f/register', response, fn);
|
||||
finishRegister('/authentication/2ndfactor/u2f/register', response, fn);
|
||||
}
|
||||
},
|
||||
timeout
|
|
@ -0,0 +1 @@
|
|||
<link rel="stylesheet" type="text/css" href="css/login.css">
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>Login Portal</title>
|
||||
<link rel="stylesheet" type="text/css" href="login.css">
|
||||
<title>Login</title>
|
||||
<% include head %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="first-factor" class="login">
|
||||
|
@ -10,6 +10,7 @@
|
|||
<input type="text" name="username" id="username" placeholder="Username" required="required" />
|
||||
<input type="password" name="password" id="password" placeholder="Password" required="required" />
|
||||
<button type="button" id="login-button" class="btn btn-primary btn-block btn-large">Enter</button>
|
||||
<button type="button" id="reset-password-button" class="btn btn-primary btn-block btn-large">Reset password</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -27,8 +28,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="jquery.min.js"></script>
|
||||
<script src="notify.min.js"></script>
|
||||
<script src="u2f-api.js"></script>
|
||||
<script src="login.js"></script>
|
||||
<% include scripts %>
|
||||
<script src="js/u2f-api.js"></script>
|
||||
<script src="js/login.js"></script>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>Reset Password</title>
|
||||
<% include head %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="reset-password" class="login">
|
||||
<h1>Reset your password</h1>
|
||||
<p>What's your username? You will receive an email to change your password</p>
|
||||
<div id="reset-password-form">
|
||||
<input type="text" name="username" id="username" placeholder="Your username" required="required" />
|
||||
<button type="button" id="reset-password-button" class="btn btn-primary btn-block btn-large">Reset Password</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<% include scripts %>
|
||||
<script src="js/reset-password-form.js"></script>
|
||||
</html>
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>Reset Password</title>
|
||||
<% include head %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="reset-password" class="login">
|
||||
<h1>Reset your password</h1>
|
||||
<p>Please type your new password.</p>
|
||||
<div id="reset-password-form">
|
||||
<input type="password" name="password1" id="password1" placeholder="New password" required="required" />
|
||||
<input type="password" name="password2" id="password2" placeholder="Password confirmation" required="required" />
|
||||
<button type="button" id="reset-password-button" class="btn btn-primary btn-block btn-large">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<% include scripts %>
|
||||
<script src="js/reset-password.js"></script>
|
||||
</html>
|
|
@ -0,0 +1,2 @@
|
|||
<script src="js/jquery.min.js"></script>
|
||||
<script src="js/notify.min.js"></script>
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>FIDO U2F Registration</title>
|
||||
<link rel="stylesheet" type="text/css" href="login.css">
|
||||
<% include head %>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login">
|
||||
|
@ -10,8 +10,7 @@
|
|||
</div>
|
||||
</body>
|
||||
|
||||
<script src="jquery.min.js"></script>
|
||||
<script src="notify.min.js"></script>
|
||||
<script src="u2f-api.js"></script>
|
||||
<script src="u2f-register.js"></script>
|
||||
<% include scripts %>
|
||||
<script src="js/u2f-api.js"></script>
|
||||
<script src="js/u2f-register.js"></script>
|
||||
</html>
|
|
@ -0,0 +1,130 @@
|
|||
|
||||
var Promise = require('bluebird');
|
||||
var request = Promise.promisifyAll(require('request'));
|
||||
var assert = require('assert');
|
||||
|
||||
module.exports = function(port) {
|
||||
var PORT = port;
|
||||
var BASE_URL = 'http://localhost:' + PORT;
|
||||
|
||||
function execute_reset_password(jar, transporter, user, new_password) {
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/authentication/reset-password',
|
||||
jar: jar,
|
||||
form: { userid: user }
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204);
|
||||
var html_content = transporter.sendMail.getCall(0).args[0].html;
|
||||
var regexp = /identity_token=([a-zA-Z0-9]+)/;
|
||||
var token = regexp.exec(html_content)[1];
|
||||
// console.log(html_content, token);
|
||||
return request.getAsync({
|
||||
url: BASE_URL + '/authentication/reset-password?identity_token=' + token,
|
||||
jar: jar
|
||||
})
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/authentication/new-password',
|
||||
jar: jar,
|
||||
form: {
|
||||
password: new_password
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function execute_totp(jar, token) {
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/authentication/2ndfactor/totp',
|
||||
jar: jar,
|
||||
form: {
|
||||
token: token
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function execute_u2f_authentication(jar) {
|
||||
return request.getAsync({
|
||||
url: BASE_URL + '/authentication/2ndfactor/u2f/sign_request',
|
||||
jar: jar
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/authentication/2ndfactor/u2f/sign',
|
||||
jar: jar,
|
||||
form: {
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function execute_verification(jar) {
|
||||
return request.getAsync({ url: BASE_URL + '/authentication/verify', jar: jar })
|
||||
}
|
||||
|
||||
function execute_login(jar) {
|
||||
return request.getAsync({ url: BASE_URL + '/authentication/login', jar: jar })
|
||||
}
|
||||
|
||||
function execute_u2f_registration(jar, transporter) {
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/authentication/u2f-register',
|
||||
jar: jar
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204);
|
||||
var html_content = transporter.sendMail.getCall(0).args[0].html;
|
||||
var regexp = /identity_token=([a-zA-Z0-9]+)/;
|
||||
var token = regexp.exec(html_content)[1];
|
||||
// console.log(html_content, token);
|
||||
return request.getAsync({
|
||||
url: BASE_URL + '/authentication/u2f-register?identity_token=' + token,
|
||||
jar: jar
|
||||
})
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
return request.getAsync({
|
||||
url: BASE_URL + '/authentication/2ndfactor/u2f/register_request',
|
||||
jar: jar,
|
||||
});
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/authentication/2ndfactor/u2f/register',
|
||||
jar: jar,
|
||||
form: {
|
||||
s: 'test'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function execute_first_factor(jar) {
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/authentication/1stfactor',
|
||||
jar: jar,
|
||||
form: {
|
||||
username: 'test_ok',
|
||||
password: 'password'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
login: execute_login,
|
||||
verify: execute_verification,
|
||||
reset_password: execute_reset_password,
|
||||
u2f_authentication: execute_u2f_authentication,
|
||||
u2f_registration: execute_u2f_registration,
|
||||
first_factor: execute_first_factor,
|
||||
totp: execute_totp,
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
var sinon = require('sinon');
|
||||
var winston = require('winston');
|
||||
var reset_password = require('../../../src/lib/routes/reset_password');
|
||||
var assert = require('assert');
|
||||
|
||||
describe('test reset password', function() {
|
||||
var req, res;
|
||||
var user_data_store;
|
||||
var ldap_client;
|
||||
var ldap;
|
||||
|
||||
beforeEach(function() {
|
||||
req = {}
|
||||
req.body = {};
|
||||
req.body.userid = 'user';
|
||||
req.app = {};
|
||||
req.app.get = sinon.stub();
|
||||
req.app.get.withArgs('logger').returns(winston);
|
||||
req.session = {};
|
||||
req.session.auth_session = {};
|
||||
req.session.auth_session.userid = 'user';
|
||||
req.session.auth_session.email = 'user@example.com';
|
||||
req.session.auth_session.first_factor = true;
|
||||
req.session.auth_session.second_factor = false;
|
||||
req.headers = {};
|
||||
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({}));
|
||||
user_data_store.issue_identity_check_token = sinon.stub().returns(Promise.resolve({}));
|
||||
user_data_store.consume_identity_check_token = sinon.stub().returns(Promise.resolve({}));
|
||||
req.app.get.withArgs('user data store').returns(user_data_store);
|
||||
|
||||
ldap = {};
|
||||
ldap.Change = sinon.spy();
|
||||
req.app.get.withArgs('ldap').returns(ldap);
|
||||
|
||||
ldap_client = {};
|
||||
ldap_client.bind = sinon.stub();
|
||||
ldap_client.search = sinon.stub();
|
||||
ldap_client.modify = sinon.stub();
|
||||
req.app.get.withArgs('ldap client').returns(ldap_client);
|
||||
|
||||
config = {};
|
||||
config.ldap_users_dn = 'dc=example,dc=com';
|
||||
req.app.get.withArgs('config').returns(config);
|
||||
|
||||
res = {};
|
||||
res.send = sinon.spy();
|
||||
res.json = sinon.spy();
|
||||
res.status = sinon.spy();
|
||||
});
|
||||
|
||||
describe('test reset password identity pre check', test_reset_password_check);
|
||||
describe('test reset password post', test_reset_password_post);
|
||||
|
||||
function test_reset_password_check() {
|
||||
it('should fail when no userid is provided', function(done) {
|
||||
req.body.userid = undefined;
|
||||
reset_password.icheck_interface.pre_check_callback(req)
|
||||
.catch(function(err) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail if ldap fail', function(done) {
|
||||
ldap_client.search.yields('Internal error');
|
||||
reset_password.icheck_interface.pre_check_callback(req)
|
||||
.catch(function(err) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should returns identity when ldap replies', function(done) {
|
||||
var doc = {};
|
||||
doc.object = {};
|
||||
doc.object.email = 'test@example.com';
|
||||
doc.object.userid = 'user';
|
||||
|
||||
var res = {};
|
||||
res.on = sinon.stub();
|
||||
res.on.withArgs('searchEntry').yields(doc);
|
||||
res.on.withArgs('end').yields();
|
||||
|
||||
ldap_client.search.yields(undefined, res);
|
||||
reset_password.icheck_interface.pre_check_callback(req)
|
||||
.then(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_reset_password_post() {
|
||||
it('should update the password', 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';
|
||||
req.body = {};
|
||||
req.body.password = 'new-password';
|
||||
|
||||
ldap_client.modify.yields(undefined);
|
||||
ldap_client.bind.yields(undefined);
|
||||
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);
|
||||
done();
|
||||
});
|
||||
reset_password.post(req, res);
|
||||
});
|
||||
|
||||
it('should fail if identity_challenge does not exist', function(done) {
|
||||
req.session.auth_session.identity_check = {};
|
||||
req.session.auth_session.identity_check.challenge = undefined;
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(res.status.getCall(0).args[0], 403);
|
||||
done();
|
||||
});
|
||||
reset_password.post(req, res);
|
||||
});
|
||||
|
||||
it('should fail when ldap fails', function(done) {
|
||||
req.session.auth_session.identity_check = {};
|
||||
req.session.auth_session.identity_check.challenge = 'reset-password';
|
||||
req.body = {};
|
||||
req.body.password = 'new-password';
|
||||
|
||||
ldap_client.bind.yields(undefined);
|
||||
ldap_client.modify.yields('Internal error with LDAP');
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(res.status.getCall(0).args[0], 500);
|
||||
done();
|
||||
});
|
||||
reset_password.post(req, res);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -19,6 +19,9 @@ describe('test u2f routes', function() {
|
|||
req.session.auth_session.userid = 'user';
|
||||
req.session.auth_session.first_factor = true;
|
||||
req.session.auth_session.second_factor = false;
|
||||
req.session.auth_session.identity_check = {};
|
||||
req.session.auth_session.identity_check.challenge = 'u2f-register';
|
||||
req.session.auth_session.register_request = {};
|
||||
req.headers = {};
|
||||
req.headers.host = 'localhost';
|
||||
|
||||
|
@ -73,6 +76,15 @@ describe('test u2f routes', function() {
|
|||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||
u2f.register_request(req, res);
|
||||
});
|
||||
|
||||
it('should return forbidden if identity has not been verified', function(done) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(403, res.status.getCall(0).args[0]);
|
||||
done();
|
||||
});
|
||||
req.session.auth_session.identity_check = undefined;
|
||||
u2f.register_request(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
function test_registration() {
|
||||
|
@ -97,7 +109,7 @@ describe('test u2f routes', function() {
|
|||
|
||||
it('should return unauthorized on finishRegistration error', function(done) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(401, res.status.getCall(0).args[0]);
|
||||
assert.equal(500, res.status.getCall(0).args[0]);
|
||||
done();
|
||||
});
|
||||
var user_key_container = {};
|
||||
|
@ -110,9 +122,9 @@ describe('test u2f routes', function() {
|
|||
u2f.register(req, res);
|
||||
});
|
||||
|
||||
it('should return unauthorized error when no auth request has been initiated', function(done) {
|
||||
it('should return forbidden error when no auth request has been initiated', function(done) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(401, res.status.getCall(0).args[0]);
|
||||
assert.equal(403, res.status.getCall(0).args[0]);
|
||||
done();
|
||||
});
|
||||
var user_key_container = {};
|
||||
|
@ -120,9 +132,19 @@ describe('test u2f routes', function() {
|
|||
u2f_mock.finishRegistration = sinon.stub();
|
||||
u2f_mock.finishRegistration.returns(Promise.resolve());
|
||||
|
||||
req.session.auth_session.register_request = undefined;
|
||||
req.app.get.withArgs('u2f').returns(u2f_mock);
|
||||
u2f.register(req, res);
|
||||
});
|
||||
|
||||
it('should return forbidden error when identity has not been verified', function(done) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(403, res.status.getCall(0).args[0]);
|
||||
done();
|
||||
});
|
||||
req.session.auth_session.identity_check = undefined;
|
||||
u2f.register(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
function test_signing_request() {
|
||||
|
@ -148,7 +170,7 @@ describe('test u2f routes', function() {
|
|||
|
||||
it('should return unauthorized error on registration request error', function(done) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(401, res.status.getCall(0).args[0]);
|
||||
assert.equal(500, res.status.getCall(0).args[0]);
|
||||
done();
|
||||
});
|
||||
var user_key_container = {};
|
||||
|
@ -209,7 +231,7 @@ describe('test u2f routes', function() {
|
|||
|
||||
it('should return unauthorized error on registration request internal error', function(done) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(401, res.status.getCall(0).args[0]);
|
||||
assert.equal(500, res.status.getCall(0).args[0]);
|
||||
done();
|
||||
});
|
||||
var user_key_container = {};
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
var sinon = require('sinon');
|
||||
var winston = require('winston');
|
||||
var u2f_register = require('../../../src/lib/routes/u2f_register');
|
||||
var u2f_register = require('../../../src/lib/routes/u2f_register_handler');
|
||||
var assert = require('assert');
|
||||
|
||||
describe('test register handle', function() {
|
||||
describe('test register handler', function() {
|
||||
var req, res;
|
||||
var user_data_store;
|
||||
|
||||
|
@ -28,88 +27,52 @@ describe('test register handle', function() {
|
|||
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({}));
|
||||
user_data_store.save_u2f_registration_token = sinon.stub().returns(Promise.resolve({}));
|
||||
user_data_store.consume_u2f_registration_token = sinon.stub().returns(Promise.resolve({}));
|
||||
user_data_store.issue_identity_check_token = sinon.stub().returns(Promise.resolve({}));
|
||||
user_data_store.consume_identity_check_token = sinon.stub().returns(Promise.resolve({}));
|
||||
req.app.get.withArgs('user data store').returns(user_data_store);
|
||||
|
||||
res = {};
|
||||
res.send = sinon.spy();
|
||||
res.json = sinon.spy();
|
||||
res.status = sinon.spy();
|
||||
})
|
||||
});
|
||||
|
||||
describe('test u2f registration check', test_registration_check);
|
||||
|
||||
describe('test registration handler (POST)', test_registration_handler_post);
|
||||
describe('test registration handler (GET)', test_registration_handler_get);
|
||||
|
||||
function test_registration_handler_post() {
|
||||
it('should issue a registration token', function(done) {
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(204, res.status.getCall(0).args[0]);
|
||||
assert.equal('user', user_data_store.save_u2f_registration_token.getCall(0).args[0]);
|
||||
assert.equal(4 * 60 * 1000, user_data_store.save_u2f_registration_token.getCall(0).args[2]);
|
||||
function test_registration_check() {
|
||||
it('should fail if first_factor has not been passed', function(done) {
|
||||
req.session.auth_session.first_factor = false;
|
||||
u2f_register.icheck_interface.pre_check_callback(req)
|
||||
.catch(function(err) {
|
||||
done();
|
||||
});
|
||||
var email_sender = {};
|
||||
email_sender.send = sinon.stub().returns(Promise.resolve());
|
||||
req.app.get.withArgs('email sender').returns(email_sender);
|
||||
u2f_register.register_handler_post(req, res);
|
||||
});
|
||||
|
||||
it('should fail during issuance of a registration token', function(done) {
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(500, res.status.getCall(0).args[0]);
|
||||
it('should fail if userid is missing', function(done) {
|
||||
req.session.auth_session.first_factor = false;
|
||||
req.session.auth_session.userid = undefined;
|
||||
|
||||
u2f_register.icheck_interface.pre_check_callback(req)
|
||||
.catch(function(err) {
|
||||
done();
|
||||
});
|
||||
user_data_store.save_u2f_registration_token = sinon.stub().returns(Promise.reject('Error'));
|
||||
u2f_register.register_handler_post(req, res);
|
||||
});
|
||||
|
||||
it('should send bad request if no email has been found for the given user', function(done) {
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(400, res.status.getCall(0).args[0]);
|
||||
done();
|
||||
});
|
||||
it('should fail if email is missing', function(done) {
|
||||
req.session.auth_session.first_factor = false;
|
||||
req.session.auth_session.email = undefined;
|
||||
var email_sender = {};
|
||||
email_sender.send = sinon.stub().returns(Promise.resolve());
|
||||
req.app.get.withArgs('email sender').returns(email_sender);
|
||||
|
||||
u2f_register.register_handler_post(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
function test_registration_handler_get() {
|
||||
it('should send forbidden if no registration_token has been provided', function(done) {
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(403, res.status.getCall(0).args[0]);
|
||||
u2f_register.icheck_interface.pre_check_callback(req)
|
||||
.catch(function(err) {
|
||||
done();
|
||||
});
|
||||
u2f_register.register_handler_get(req, res);
|
||||
});
|
||||
|
||||
|
||||
it('should render the u2f-register view when registration token is still valid', function(done) {
|
||||
res.render = sinon.spy(function(data) {
|
||||
assert.equal('u2f_register', data);
|
||||
it('should succeed if first factor passed, userid and email are provided', function(done) {
|
||||
u2f_register.icheck_interface.pre_check_callback(req)
|
||||
.then(function(err) {
|
||||
done();
|
||||
});
|
||||
req.query = {};
|
||||
req.query.registration_token = 'token';
|
||||
u2f_register.register_handler_get(req, res);
|
||||
});
|
||||
|
||||
it('should send forbidden status when registration token is not valid', function(done) {
|
||||
res.send = sinon.spy(function(data) {
|
||||
assert.equal(403, res.status.getCall(0).args[0]);
|
||||
done();
|
||||
});
|
||||
|
||||
req.params = {};
|
||||
req.params.registration_token = 'token';
|
||||
user_data_store.consume_u2f_registration_token = sinon.stub().returns(Promise.reject('Not valid anymore'));
|
||||
|
||||
u2f_register.register_handler_get(req, res);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
|
||||
var server = require('../../src/lib/server');
|
||||
|
||||
var request = require('request');
|
||||
var Promise = require('bluebird');
|
||||
var request = Promise.promisifyAll(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 nedb = require('nedb');
|
||||
|
||||
var PORT = 8050;
|
||||
var BASE_URL = 'http://localhost:' + PORT;
|
||||
|
||||
var requests = require('./requests')(PORT);
|
||||
|
||||
|
||||
describe('test data persistence', function() {
|
||||
var u2f;
|
||||
var tmpDir;
|
||||
|
@ -73,38 +75,52 @@ describe('test data persistence', function() {
|
|||
u2f.finishRegistration.returns(Promise.resolve(sign_status));
|
||||
u2f.startAuthentication.returns(Promise.resolve(registration_request));
|
||||
u2f.finishAuthentication.returns(Promise.resolve(registration_status));
|
||||
|
||||
|
||||
var nodemailer = {};
|
||||
var transporter = {
|
||||
sendMail: sinon.stub().yields()
|
||||
};
|
||||
nodemailer.createTransport = sinon.spy(function() {
|
||||
return transporter;
|
||||
});
|
||||
|
||||
var deps = {};
|
||||
deps.u2f = u2f;
|
||||
deps.nedb = nedb;
|
||||
deps.nodemailer = nodemailer;
|
||||
|
||||
var j1 = request.jar();
|
||||
var j2 = request.jar();
|
||||
return start_server(config, ldap_client, u2f)
|
||||
|
||||
return start_server(config, ldap_client, deps)
|
||||
.then(function(s) {
|
||||
server = s;
|
||||
return execute_login(j1);
|
||||
return requests.login(j1);
|
||||
})
|
||||
.then(function(res) {
|
||||
return execute_first_factor(j1);
|
||||
return requests.first_factor(j1);
|
||||
})
|
||||
.then(function() {
|
||||
return execute_u2f_registration(j1);
|
||||
return requests.u2f_registration(j1, transporter);
|
||||
})
|
||||
.then(function() {
|
||||
return execute_u2f_authentication(j1);
|
||||
return requests.u2f_authentication(j1);
|
||||
})
|
||||
.then(function() {
|
||||
return stop_server(server);
|
||||
})
|
||||
.then(function() {
|
||||
return start_server(config, ldap_client, u2f)
|
||||
return start_server(config, ldap_client, deps)
|
||||
})
|
||||
.then(function(s) {
|
||||
server = s;
|
||||
return execute_login(j2);
|
||||
return requests.login(j2);
|
||||
})
|
||||
.then(function() {
|
||||
return execute_first_factor(j2);
|
||||
return requests.first_factor(j2);
|
||||
})
|
||||
.then(function() {
|
||||
return execute_u2f_authentication(j2);
|
||||
return requests.u2f_authentication(j2);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(204, res.statusCode);
|
||||
|
@ -117,9 +133,9 @@ describe('test data persistence', function() {
|
|||
});
|
||||
});
|
||||
|
||||
function start_server(config, ldap_client, u2f) {
|
||||
function start_server(config, ldap_client, deps) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var s = server.run(config, ldap_client, u2f);
|
||||
var s = server.run(config, ldap_client, deps);
|
||||
resolve(s);
|
||||
});
|
||||
}
|
||||
|
@ -130,55 +146,4 @@ describe('test data persistence', function() {
|
|||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function execute_first_factor(jar) {
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/1stfactor',
|
||||
jar: jar,
|
||||
form: {
|
||||
username: 'test_ok',
|
||||
password: 'password'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function execute_u2f_registration(jar) {
|
||||
return request.getAsync({
|
||||
url: BASE_URL + '/2ndfactor/u2f/register_request',
|
||||
jar: jar
|
||||
})
|
||||
.then(function(res) {
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/2ndfactor/u2f/register',
|
||||
jar: jar,
|
||||
form: {
|
||||
s: 'test'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function execute_u2f_authentication(jar) {
|
||||
return request.getAsync({
|
||||
url: BASE_URL + '/2ndfactor/u2f/sign_request',
|
||||
jar: jar
|
||||
})
|
||||
.then(function() {
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/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,208 @@
|
|||
|
||||
var sinon = require('sinon');
|
||||
var identity_check = require('../../src/lib/identity_check');
|
||||
var exceptions = require('../../src/lib/exceptions');
|
||||
var assert = require('assert');
|
||||
var winston = require('winston');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
describe('test identity check process', function() {
|
||||
var req, res, app, icheck_interface;
|
||||
var user_data_store;
|
||||
var email_sender;
|
||||
|
||||
beforeEach(function() {
|
||||
req = {};
|
||||
res = {};
|
||||
|
||||
app = {};
|
||||
icheck_interface = {};
|
||||
icheck_interface.pre_check_callback = sinon.stub();
|
||||
|
||||
user_data_store = {};
|
||||
user_data_store.issue_identity_check_token = sinon.stub();
|
||||
user_data_store.issue_identity_check_token.returns(Promise.resolve());
|
||||
user_data_store.consume_identity_check_token = sinon.stub();
|
||||
user_data_store.consume_identity_check_token.returns(Promise.resolve({ userid: 'user' }));
|
||||
|
||||
email_sender = {};
|
||||
email_sender.send = sinon.stub();
|
||||
email_sender.send = sinon.stub().returns(Promise.resolve());
|
||||
|
||||
req.headers = {};
|
||||
req.session = {};
|
||||
req.session.auth_session = {};
|
||||
|
||||
req.query = {};
|
||||
req.app = {};
|
||||
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('email sender').returns(email_sender);
|
||||
|
||||
res.status = sinon.spy();
|
||||
res.send = sinon.spy();
|
||||
res.redirect = sinon.spy();
|
||||
res.render = sinon.spy();
|
||||
|
||||
app.get = sinon.spy();
|
||||
app.post = sinon.spy();
|
||||
});
|
||||
|
||||
it('should register a POST and GET endpoint', function() {
|
||||
var app = {};
|
||||
app.get = sinon.spy();
|
||||
app.post = sinon.spy();
|
||||
var endpoint = '/test';
|
||||
var icheck_interface = {};
|
||||
|
||||
identity_check(app, endpoint, icheck_interface);
|
||||
|
||||
assert(app.get.calledOnce);
|
||||
assert(app.get.calledWith(endpoint));
|
||||
|
||||
assert(app.post.calledOnce);
|
||||
assert(app.post.calledWith(endpoint));
|
||||
});
|
||||
|
||||
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';
|
||||
|
||||
icheck_interface.pre_check_callback.returns(Promise.reject('No access'));
|
||||
identity_check(app, endpoint, icheck_interface);
|
||||
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(res.status.getCall(0).args[0], 403);
|
||||
done();
|
||||
});
|
||||
|
||||
var handler = app.post.getCall(0).args[1];
|
||||
handler(req, res);
|
||||
});
|
||||
|
||||
it('should send 400 if email is missing in provided identity', function(done) {
|
||||
var endpoint = '/protected';
|
||||
var identity = { userid: 'abc' };
|
||||
|
||||
icheck_interface.pre_check_callback.returns(Promise.resolve(identity));
|
||||
identity_check(app, endpoint, icheck_interface);
|
||||
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(res.status.getCall(0).args[0], 400);
|
||||
done();
|
||||
});
|
||||
|
||||
var handler = app.post.getCall(0).args[1];
|
||||
handler(req, res);
|
||||
});
|
||||
|
||||
it('should send 400 if userid is missing in provided identity', function(done) {
|
||||
var endpoint = '/protected';
|
||||
var identity = { email: 'abc@example.com' };
|
||||
|
||||
icheck_interface.pre_check_callback.returns(Promise.resolve(identity));
|
||||
identity_check(app, endpoint, icheck_interface);
|
||||
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(res.status.getCall(0).args[0], 400);
|
||||
done();
|
||||
});
|
||||
var handler = app.post.getCall(0).args[1];
|
||||
handler(req, res);
|
||||
});
|
||||
|
||||
it('should issue a token, send an email and return 204', function(done) {
|
||||
var endpoint = '/protected';
|
||||
var identity = { userid: 'user', email: 'abc@example.com' };
|
||||
req.headers.host = 'localhost';
|
||||
req.headers['x-original-uri'] = '/auth/test';
|
||||
|
||||
icheck_interface.pre_check_callback.returns(Promise.resolve(identity));
|
||||
identity_check(app, endpoint, icheck_interface);
|
||||
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(res.status.getCall(0).args[0], 204);
|
||||
assert(email_sender.send.calledOnce);
|
||||
assert(user_data_store.issue_identity_check_token.calledOnce);
|
||||
assert.equal(user_data_store.issue_identity_check_token.getCall(0).args[0], 'user');
|
||||
assert.equal(user_data_store.issue_identity_check_token.getCall(0).args[3], 240000);
|
||||
assert(email_sender.send.getCall(0).args[2].startsWith('<a href="https://localhost/auth/test?identity_token='));
|
||||
done();
|
||||
});
|
||||
var handler = app.post.getCall(0).args[1];
|
||||
handler(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
function test_get_handler() {
|
||||
it('should send 403 if no identity_token is provided', function(done) {
|
||||
var endpoint = '/protected';
|
||||
|
||||
identity_check(app, endpoint, icheck_interface);
|
||||
|
||||
res.send = sinon.spy(function() {
|
||||
assert.equal(res.status.getCall(0).args[0], 403);
|
||||
done();
|
||||
});
|
||||
var handler = app.get.getCall(0).args[1];
|
||||
handler(req, res);
|
||||
});
|
||||
|
||||
it('should render template if identity_token is provided and still valid', function(done) {
|
||||
req.query.identity_token = 'token';
|
||||
var endpoint = '/protected';
|
||||
|
||||
icheck_interface.render_template = 'template';
|
||||
|
||||
identity_check(app, endpoint, icheck_interface);
|
||||
|
||||
res.render = sinon.spy(function(template) {
|
||||
assert.equal(template, 'template');
|
||||
done();
|
||||
});
|
||||
var handler = app.get.getCall(0).args[1];
|
||||
handler(req, res);
|
||||
});
|
||||
|
||||
it('should return 403 if identity_token is provided but invalid', function(done) {
|
||||
req.query.identity_token = 'token';
|
||||
var endpoint = '/protected';
|
||||
|
||||
icheck_interface.render_template = 'template';
|
||||
user_data_store.consume_identity_check_token
|
||||
.returns(Promise.reject('Invalid token'));
|
||||
|
||||
identity_check(app, endpoint, icheck_interface);
|
||||
|
||||
res.send = sinon.spy(function(template) {
|
||||
assert.equal(res.status.getCall(0).args[0], 403);
|
||||
done();
|
||||
});
|
||||
var handler = app.get.getCall(0).args[1];
|
||||
handler(req, res);
|
||||
});
|
||||
|
||||
it('should set the identity_check session object even if session does not exist yet', function(done) {
|
||||
req.query.identity_token = 'token';
|
||||
var endpoint = '/protected';
|
||||
|
||||
req.session = {};
|
||||
icheck_interface.render_template = 'template';
|
||||
|
||||
identity_check(app, endpoint, icheck_interface);
|
||||
|
||||
res.render = sinon.spy(function(template) {
|
||||
assert.equal(req.session.auth_session.identity_check.userid, 'user');
|
||||
assert.equal(template, 'template');
|
||||
done();
|
||||
});
|
||||
var handler = app.get.getCall(0).args[1];
|
||||
handler(req, res);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -11,12 +11,15 @@ describe('test ldap validation', function() {
|
|||
beforeEach(function() {
|
||||
ldap_client = {
|
||||
bind: sinon.stub(),
|
||||
search: sinon.stub()
|
||||
search: sinon.stub(),
|
||||
modify: sinon.stub(),
|
||||
Change: sinon.spy()
|
||||
}
|
||||
});
|
||||
|
||||
describe('test binding', test_binding);
|
||||
describe('test get email', test_get_email);
|
||||
describe('test update password', test_update_password);
|
||||
|
||||
function test_binding() {
|
||||
function test_validate() {
|
||||
|
@ -85,5 +88,53 @@ describe('test ldap validation', function() {
|
|||
})
|
||||
});
|
||||
}
|
||||
|
||||
function test_update_password() {
|
||||
it('should update the password successfully', function(done) {
|
||||
var change = {};
|
||||
change.operation = 'replace';
|
||||
change.modification = {};
|
||||
change.modification.userPassword = 'new-password';
|
||||
|
||||
var config = {};
|
||||
config.ldap_users_dn = 'dc=example,dc=com';
|
||||
config.ldap_user = 'admin';
|
||||
|
||||
var userdn = 'cn=user,dc=example,dc=com';
|
||||
|
||||
var ldapjs = {};
|
||||
ldapjs.Change = sinon.spy();
|
||||
|
||||
ldap_client.bind.yields(undefined);
|
||||
ldap_client.modify.yields(undefined);
|
||||
|
||||
ldap.update_password(ldap_client, ldapjs, 'user', 'new-password', config)
|
||||
.then(function() {
|
||||
assert.deepEqual(ldap_client.modify.getCall(0).args[0], userdn);
|
||||
assert.deepEqual(ldapjs.Change.getCall(0).args[0].operation, change.operation);
|
||||
|
||||
var userPassword = ldapjs.Change.getCall(0).args[0].modification.userPassword;
|
||||
assert(/{SSHA}/.test(userPassword));
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should fail when ldap throws an error', function(done) {
|
||||
ldap_client.bind.yields(undefined);
|
||||
ldap_client.modify.yields('Error');
|
||||
|
||||
var config = {};
|
||||
config.ldap_users_dn = 'dc=example,dc=com';
|
||||
config.ldap_user = 'admin';
|
||||
|
||||
var ldapjs = {};
|
||||
ldapjs.Change = sinon.spy();
|
||||
|
||||
ldap.update_password(ldap_client, ldapjs, 'user', 'new-password', config)
|
||||
.catch(function() {
|
||||
done();
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,30 +1,39 @@
|
|||
|
||||
var server = require('../../src/lib/server');
|
||||
|
||||
var request = require('request');
|
||||
var Promise = require('bluebird');
|
||||
var request = Promise.promisifyAll(require('request'));
|
||||
var assert = require('assert');
|
||||
var speakeasy = require('speakeasy');
|
||||
var sinon = require('sinon');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
var request = Promise.promisifyAll(request);
|
||||
|
||||
var BASE_URL = 'http://localhost:8090';
|
||||
var PORT = 8090;
|
||||
var BASE_URL = 'http://localhost:' + PORT;
|
||||
var requests = require('./requests')(PORT);
|
||||
|
||||
describe('test the server', function() {
|
||||
var _server
|
||||
var u2f;
|
||||
var deps;
|
||||
var u2f, nedb;
|
||||
var transporter;
|
||||
var collection;
|
||||
var ldap_client = {
|
||||
bind: sinon.stub(),
|
||||
search: sinon.stub()
|
||||
search: sinon.stub(),
|
||||
modify: sinon.stub(),
|
||||
};
|
||||
var ldap = {
|
||||
Change: sinon.spy()
|
||||
}
|
||||
|
||||
beforeEach(function(done) {
|
||||
var config = {
|
||||
port: 8090,
|
||||
port: PORT,
|
||||
totp_secret: 'totp_secret',
|
||||
ldap_url: 'ldap://127.0.0.1:389',
|
||||
ldap_users_dn: 'ou=users,dc=example,dc=com',
|
||||
ldap_user: 'cn=admin,dc=example,dc=com',
|
||||
ldap_password: 'password',
|
||||
session_secret: 'session_secret',
|
||||
session_max_age: 50000,
|
||||
gmail: {
|
||||
|
@ -39,6 +48,23 @@ 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;
|
||||
});
|
||||
|
||||
transporter = {};
|
||||
transporter.sendMail = sinon.stub().yields();
|
||||
|
||||
var nodemailer = {};
|
||||
nodemailer.createTransport = sinon.spy(function() {
|
||||
return transporter;
|
||||
});
|
||||
|
||||
var search_doc = {
|
||||
object: {
|
||||
mail: 'test_ok@example.com'
|
||||
|
@ -52,10 +78,20 @@ describe('test the server', function() {
|
|||
|
||||
ldap_client.bind.withArgs('cn=test_ok,ou=users,dc=example,dc=com',
|
||||
'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');
|
||||
_server = server.run(config, ldap_client, u2f, function() {
|
||||
ldap_client.modify.yields(undefined);
|
||||
|
||||
var deps = {};
|
||||
deps.u2f = u2f;
|
||||
deps.nedb = nedb;
|
||||
deps.nodemailer = nodemailer;
|
||||
deps.ldap = ldap;
|
||||
|
||||
_server = server.run(config, ldap_client, deps, function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -74,11 +110,12 @@ describe('test the server', function() {
|
|||
|
||||
describe('test authentication and verification', function() {
|
||||
test_authentication();
|
||||
test_reset_password();
|
||||
});
|
||||
|
||||
function test_login() {
|
||||
it('should serve the login page', function(done) {
|
||||
request.getAsync(BASE_URL + '/login')
|
||||
request.getAsync(BASE_URL + '/authentication/login')
|
||||
.then(function(response) {
|
||||
assert.equal(response.statusCode, 200);
|
||||
done();
|
||||
|
@ -88,7 +125,7 @@ describe('test the server', function() {
|
|||
|
||||
function test_logout() {
|
||||
it('should logout and redirect to /', function(done) {
|
||||
request.getAsync(BASE_URL + '/logout')
|
||||
request.getAsync(BASE_URL + '/authentication/logout')
|
||||
.then(function(response) {
|
||||
assert.equal(response.req.path, '/');
|
||||
done();
|
||||
|
@ -98,7 +135,7 @@ describe('test the server', function() {
|
|||
|
||||
function test_authentication() {
|
||||
it('should return status code 401 when user is not authenticated', function() {
|
||||
return request.getAsync({ url: BASE_URL + '/verify' })
|
||||
return request.getAsync({ url: BASE_URL + '/authentication/verify' })
|
||||
.then(function(response) {
|
||||
assert.equal(response.statusCode, 401);
|
||||
return Promise.resolve();
|
||||
|
@ -111,31 +148,18 @@ describe('test the server', function() {
|
|||
encoding: 'base32'
|
||||
});
|
||||
var j = request.jar();
|
||||
return request.getAsync({ url: BASE_URL + '/login', jar: j })
|
||||
return requests.login(j)
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'get login page failed');
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/1stfactor',
|
||||
jar: j,
|
||||
form: {
|
||||
username: 'test_ok',
|
||||
password: 'password'
|
||||
}
|
||||
});
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'first factor failed');
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/2ndfactor/totp',
|
||||
jar: j,
|
||||
form: {
|
||||
token: real_token
|
||||
}
|
||||
});
|
||||
return requests.totp(j, real_token);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'second factor failed');
|
||||
return request.getAsync({ url: BASE_URL + '/verify', jar: j })
|
||||
return requests.verify(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'verify failed');
|
||||
|
@ -149,35 +173,22 @@ describe('test the server', function() {
|
|||
encoding: 'base32'
|
||||
});
|
||||
var j = request.jar();
|
||||
return request.getAsync({ url: BASE_URL + '/login', jar: j })
|
||||
return requests.login(j)
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'get login page failed');
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/1stfactor',
|
||||
jar: j,
|
||||
form: {
|
||||
username: 'test_ok',
|
||||
password: 'password'
|
||||
}
|
||||
});
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'first factor failed');
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/2ndfactor/totp',
|
||||
jar: j,
|
||||
form: {
|
||||
token: real_token
|
||||
}
|
||||
});
|
||||
return requests.totp(j, real_token);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'second factor failed');
|
||||
return request.getAsync({ url: BASE_URL + '/login', jar: j })
|
||||
return requests.login(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'login page loading failed');
|
||||
return request.getAsync({ url: BASE_URL + '/verify', jar: j })
|
||||
return requests.verify(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'verify failed');
|
||||
|
@ -197,57 +208,29 @@ describe('test the server', function() {
|
|||
u2f.finishRegistration.returns(Promise.resolve(sign_status));
|
||||
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 request.getAsync({ url: BASE_URL + '/login', jar: j })
|
||||
return requests.login(j)
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'get login page failed');
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/1stfactor',
|
||||
jar: j,
|
||||
form: {
|
||||
username: 'test_ok',
|
||||
password: 'password'
|
||||
}
|
||||
});
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'first factor failed');
|
||||
return request.getAsync({
|
||||
url: BASE_URL + '/2ndfactor/u2f/register_request',
|
||||
jar: j
|
||||
});
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'second factor, start register failed');
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/2ndfactor/u2f/register',
|
||||
jar: j,
|
||||
form: {
|
||||
s: 'test'
|
||||
}
|
||||
});
|
||||
return requests.u2f_registration(j, transporter);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'second factor, finish register failed');
|
||||
return request.getAsync({
|
||||
url: BASE_URL + '/2ndfactor/u2f/sign_request',
|
||||
jar: j
|
||||
});
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'second factor, start sign failed');
|
||||
return request.postAsync({
|
||||
url: BASE_URL + '/2ndfactor/u2f/sign',
|
||||
jar: j,
|
||||
form: {
|
||||
s: 'test'
|
||||
}
|
||||
});
|
||||
return requests.u2f_authentication(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'second factor, finish sign failed');
|
||||
return request.getAsync({ url: BASE_URL + '/verify', jar: j })
|
||||
return requests.verify(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'verify failed');
|
||||
|
@ -255,5 +238,29 @@ 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) {
|
||||
assert.equal(res.statusCode, 200, 'get login page failed');
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'first factor failed');
|
||||
return requests.reset_password(j, transporter, 'user', 'new-password');
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'second factor, finish register failed');
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -84,11 +84,13 @@ function test_u2f_registration_token() {
|
|||
var userid = 'user';
|
||||
var token = 'token';
|
||||
var max_age = 60;
|
||||
var content = 'abc';
|
||||
|
||||
return data_store.save_u2f_registration_token(userid, token, max_age)
|
||||
return data_store.issue_identity_check_token(userid, token, content, max_age)
|
||||
.then(function(document) {
|
||||
assert.equal(userid, document.userid);
|
||||
assert.equal(token, document.token);
|
||||
assert.equal(document.userid, userid);
|
||||
assert.equal(document.token, token);
|
||||
assert.deepEqual(document.content, { userid: 'user', data: content });
|
||||
assert('max_date' in document);
|
||||
assert('_id' in document);
|
||||
return Promise.resolve();
|
||||
|
@ -109,9 +111,9 @@ function test_u2f_registration_token() {
|
|||
var token = 'token';
|
||||
var max_age = 50;
|
||||
|
||||
data_store.save_u2f_registration_token(userid, token, max_age)
|
||||
data_store.issue_identity_check_token(userid, token, {}, max_age)
|
||||
.then(function(document) {
|
||||
return data_store.consume_u2f_registration_token(token);
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.then(function() {
|
||||
done();
|
||||
|
@ -131,12 +133,12 @@ function test_u2f_registration_token() {
|
|||
var token = 'token';
|
||||
var max_age = 50;
|
||||
|
||||
data_store.save_u2f_registration_token(userid, token, max_age)
|
||||
data_store.issue_identity_check_token(userid, token, {}, max_age)
|
||||
.then(function(document) {
|
||||
return data_store.consume_u2f_registration_token(token);
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.then(function(document) {
|
||||
return data_store.consume_u2f_registration_token(token);
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error(err);
|
||||
|
@ -152,7 +154,7 @@ function test_u2f_registration_token() {
|
|||
|
||||
var token = 'token';
|
||||
|
||||
return data_store.consume_u2f_registration_token(token)
|
||||
return data_store.consume_identity_check_token(token)
|
||||
.then(function(document) {
|
||||
return Promise.reject();
|
||||
})
|
||||
|
@ -172,14 +174,39 @@ function test_u2f_registration_token() {
|
|||
var max_age = 60;
|
||||
MockDate.set('1/1/2000');
|
||||
|
||||
data_store.save_u2f_registration_token(userid, token, max_age)
|
||||
data_store.issue_identity_check_token(userid, token, {}, max_age)
|
||||
.then(function() {
|
||||
MockDate.set('1/2/2000');
|
||||
return data_store.consume_u2f_registration_token(token);
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.catch(function(err) {
|
||||
MockDate.reset();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should save the userid and some data with the token', function(done) {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
|
||||
var userid = 'user';
|
||||
var token = 'token';
|
||||
var max_age = 60;
|
||||
MockDate.set('1/1/2000');
|
||||
var data = 'abc';
|
||||
|
||||
data_store.issue_identity_check_token(userid, token, data, max_age)
|
||||
.then(function() {
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.then(function(content) {
|
||||
var expected_content = {};
|
||||
expected_content.userid = 'user';
|
||||
expected_content.data = 'abc';
|
||||
assert.deepEqual(content, expected_content);
|
||||
done();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue