Move TOTP authenticator to typescript

This commit is contained in:
Clement Michaud 2017-05-21 12:14:59 +02:00
parent b54c181d27
commit c98c07832d
35 changed files with 354 additions and 319 deletions

View File

@ -47,7 +47,7 @@
},
"devDependencies": {
"@types/assert": "0.0.31",
"@types/bluebird": "^3.5.3",
"@types/bluebird": "^3.5.4",
"@types/body-parser": "^1.16.3",
"@types/ejs": "^2.3.33",
"@types/express": "^4.0.35",

View File

@ -1,5 +1,5 @@
import * as Promise from "bluebird";
import * as BluebirdPromise from "bluebird";
import exceptions = require("./Exceptions");
const REGULATION_TRACE_TYPE = "regulation";
@ -19,16 +19,16 @@ export default class AuthenticationRegulator {
}
// Mark authentication
mark(userid: string, is_success: boolean): Promise<void> {
mark(userid: string, is_success: boolean): BluebirdPromise<void> {
return this._user_data_store.save_authentication_trace(userid, REGULATION_TRACE_TYPE, is_success);
}
regulate(userid: string): Promise<void> {
regulate(userid: string): BluebirdPromise<void> {
return this._user_data_store.get_last_authentication_traces(userid, REGULATION_TRACE_TYPE, false, 3)
.then((docs: Array<DatedDocument>) => {
if (docs.length < MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE) {
// less than the max authorized number of authentication in time range, thus authorizing access
return Promise.resolve();
return BluebirdPromise.resolve();
}
const oldest_doc = docs[MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE - 1];
@ -37,7 +37,7 @@ export default class AuthenticationRegulator {
throw new exceptions.AuthenticationRegulationError("Max number of authentication. Please retry in few minutes.");
}
return Promise.resolve();
return BluebirdPromise.resolve();
});
}
}

View File

@ -1,5 +1,4 @@
export class LdapSeachError extends Error {
constructor(message?: string) {
super(message);

View File

@ -115,7 +115,7 @@ export class LdapClient {
groups.push(docs[i].cn);
}
that.logger.debug("LDAP: got groups %s", groups);
return Promise.resolve(groups);
return BluebirdPromise.resolve(groups);
});
}
@ -141,7 +141,7 @@ export class LdapClient {
}
}
that.logger.debug("LDAP: got emails %s", emails);
return Promise.resolve(emails);
return BluebirdPromise.resolve(emails);
});
}

View File

@ -9,6 +9,7 @@ import TOTPValidator from "./TOTPValidator";
import TOTPGenerator from "./TOTPGenerator";
import RestApi from "./RestApi";
import { LdapClient } from "./LdapClient";
import BluebirdPromise = require("bluebird");
import * as Express from "express";
import * as BodyParser from "body-parser";
@ -20,7 +21,7 @@ import AccessController from "./access_control/AccessController";
export default class Server {
private httpServer: http.Server;
start(yaml_configuration: UserConfiguration, deps: GlobalDependencies): Promise<void> {
start(yaml_configuration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
const config = ConfigurationAdapter.adapt(yaml_configuration);
const view_directory = Path.resolve(__dirname, "../views");
@ -54,7 +55,7 @@ export default class Server {
deps.winston.level = config.logs_level || "info";
const five_minutes = 5 * 60;
const data_store = new UserDataStore(datastore_options);
const data_store = new UserDataStore(datastore_options, deps.nedb);
const regulator = new AuthenticationRegulator(data_store, five_minutes);
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
const ldap = new LdapClient(config.ldap, deps.ldapjs, deps.winston);
@ -75,7 +76,7 @@ export default class Server {
RestApi.setup(app);
return new Promise<void>((resolve, reject) => {
return new BluebirdPromise<void>((resolve, reject) => {
this.httpServer = app.listen(config.port, function (err: string) {
console.log("Listening on %d...", config.port);
resolve();

View File

@ -18,6 +18,6 @@ export default class TOTPValidator {
});
if (token == real_token) return BluebirdPromise.resolve();
return BluebirdPromise.reject("Wrong challenge");
return BluebirdPromise.reject(new Error("Wrong challenge"));
}
}

View File

@ -1,8 +1,8 @@
import * as Promise from "bluebird";
import * as BluebirdPromise from "bluebird";
import * as path from "path";
import Nedb = require("nedb");
import { NedbAsync } from "nedb";
import { TOTPSecret } from "../types/TOTPSecret";
import { Nedb } from "../types/Dependencies";
// Constants
@ -36,18 +36,20 @@ export default class UserDataStore {
private _identity_check_tokens_collection: NedbAsync;
private _authentication_traces_collection: NedbAsync;
private _totp_secret_collection: NedbAsync;
private nedb: Nedb;
constructor(options?: Options) {
this._u2f_meta_collection = create_collection(U2F_META_COLLECTION_NAME, options);
constructor(options: Options, nedb: Nedb) {
this.nedb = nedb;
this._u2f_meta_collection = this.create_collection(U2F_META_COLLECTION_NAME, options);
this._identity_check_tokens_collection =
create_collection(IDENTITY_CHECK_TOKENS_COLLECTION_NAME, options);
this.create_collection(IDENTITY_CHECK_TOKENS_COLLECTION_NAME, options);
this._authentication_traces_collection =
create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options);
this.create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options);
this._totp_secret_collection =
create_collection(TOTP_SECRETS_COLLECTION_NAME, options);
this.create_collection(TOTP_SECRETS_COLLECTION_NAME, options);
}
set_u2f_meta(userid: string, appid: string, meta: Object): Promise<any> {
set_u2f_meta(userid: string, appid: string, meta: Object): BluebirdPromise<any> {
const newDocument = {
userid: userid,
appid: appid,
@ -62,7 +64,7 @@ export default class UserDataStore {
return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true });
}
get_u2f_meta(userid: string, appid: string): Promise<U2FMetaDocument> {
get_u2f_meta(userid: string, appid: string): BluebirdPromise<U2FMetaDocument> {
const filter = {
userid: userid,
appid: appid
@ -81,7 +83,7 @@ export default class UserDataStore {
return this._authentication_traces_collection.insertAsync(newDocument);
}
get_last_authentication_traces(userid: string, type: string, is_success: boolean, count: number): Promise<any> {
get_last_authentication_traces(userid: string, type: string, is_success: boolean, count: number): BluebirdPromise<any> {
const q = {
userid: userid,
type: type,
@ -90,11 +92,11 @@ export default class UserDataStore {
const query = this._authentication_traces_collection.find(q)
.sort({ date: -1 }).limit(count);
const query_promisified = Promise.promisify(query.exec, { context: query });
const query_promisified = BluebirdPromise.promisify(query.exec, { context: query });
return query_promisified();
}
issue_identity_check_token(userid: string, token: string, data: string | object, max_age: number): Promise<any> {
issue_identity_check_token(userid: string, token: string, data: string | object, max_age: number): BluebirdPromise<any> {
const newDocument = {
userid: userid,
token: token,
@ -108,7 +110,7 @@ export default class UserDataStore {
return this._identity_check_tokens_collection.insertAsync(newDocument);
}
consume_identity_check_token(token: string): Promise<any> {
consume_identity_check_token(token: string): BluebirdPromise<any> {
const query = {
token: token
};
@ -116,26 +118,26 @@ export default class UserDataStore {
return this._identity_check_tokens_collection.findOneAsync(query)
.then(function (doc) {
if (!doc) {
return Promise.reject("Registration token does not exist");
return BluebirdPromise.reject("Registration token does not exist");
}
const max_date = doc.max_date;
const current_date = new Date();
if (current_date > max_date) {
return Promise.reject("Registration token is not valid anymore");
return BluebirdPromise.reject("Registration token is not valid anymore");
}
return Promise.resolve(doc.content);
return BluebirdPromise.resolve(doc.content);
})
.then((content) => {
return Promise.join(this._identity_check_tokens_collection.removeAsync(query),
Promise.resolve(content));
return BluebirdPromise.join(this._identity_check_tokens_collection.removeAsync(query),
BluebirdPromise.resolve(content));
})
.then((v) => {
return Promise.resolve(v[1]);
return BluebirdPromise.resolve(v[1]);
});
}
set_totp_secret(userid: string, secret: TOTPSecret): Promise<any> {
set_totp_secret(userid: string, secret: TOTPSecret): BluebirdPromise<any> {
const doc = {
userid: userid,
secret: secret
@ -147,23 +149,23 @@ export default class UserDataStore {
return this._totp_secret_collection.updateAsync(query, doc, { upsert: true });
}
get_totp_secret(userid: string): Promise<TOTPSecretDocument> {
get_totp_secret(userid: string): BluebirdPromise<TOTPSecretDocument> {
const query = {
userid: userid
};
return this._totp_secret_collection.findOneAsync(query);
}
}
function create_collection(name: string, options: any): NedbAsync {
const datastore_options = {
inMemoryOnly: options.inMemoryOnly || false,
autoload: true,
filename: ""
};
if (options.directory)
datastore_options.filename = path.resolve(options.directory, name);
return Promise.promisifyAll(new Nedb(datastore_options)) as NedbAsync;
private create_collection(name: string, options: any): NedbAsync {
const datastore_options = {
inMemoryOnly: options.inMemoryOnly || false,
autoload: true,
filename: ""
};
if (options.directory)
datastore_options.filename = path.resolve(options.directory, name);
return BluebirdPromise.promisifyAll(new this.nedb(datastore_options)) as NedbAsync;
}
}

View File

@ -1,7 +1,7 @@
var objectPath = require('object-path');
var randomstring = require('randomstring');
var Promise = require('bluebird');
var BluebirdPromise = require('bluebird');
var util = require('util');
var exceptions = require('./Exceptions');
var fs = require('fs');
@ -27,7 +27,7 @@ IdentityCheck.prototype.issue_token = function(userid, content, logger) {
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() {
return Promise.resolve(token);
return BluebirdPromise.resolve(token);
});
}

View File

@ -1,5 +1,5 @@
import * as Promise from "bluebird";
import * as BluebirdPromise from "bluebird";
import * as fs from "fs";
import * as ejs from "ejs";
import nodemailer = require("nodemailer");
@ -23,10 +23,10 @@ export class GMailNotifier extends INotifier {
pass: options.password
}
});
this.transporter = Promise.promisifyAll(transporter);
this.transporter = BluebirdPromise.promisifyAll(transporter);
}
notify(identity: Identity, subject: string, link: string): Promise<void> {
notify(identity: Identity, subject: string, link: string): BluebirdPromise<void> {
const d = {
url: link,
button_title: "Continue",

View File

@ -1,10 +1,10 @@
import exceptions = require("../Exceptions");
import objectPath = require("object-path");
import Promise = require("bluebird");
import BluebirdPromise = require("bluebird");
import express = require("express");
export = function(req: express.Request, res: express.Response) {
export = function (req: express.Request, res: express.Response) {
const username = req.body.username;
const password = req.body.password;
if (!username || !password) {
@ -24,53 +24,53 @@ export = function(req: express.Request, res: express.Response) {
logger.debug("1st factor: username=%s", username);
regulator.regulate(username)
.then(function() {
return ldap.bind(username, password);
})
.then(function() {
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 Promise.join(ldap.get_emails(username), ldap.get_groups(username));
})
.then(function(data: string[2]) {
const emails = data[0];
const groups = data[1];
.then(function () {
return ldap.bind(username, password);
})
.then(function () {
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 BluebirdPromise.join(ldap.get_emails(username), ldap.get_groups(username));
})
.then(function (data: string[2]) {
const emails = data[0];
const groups = data[1];
if (!emails && emails.length <= 0) throw new Error("No email found");
logger.debug("1st factor: Retrieved email are %s", emails);
objectPath.set(req, "session.auth_session.email", emails[0]);
if (!emails && emails.length <= 0) throw new Error("No email found");
logger.debug("1st factor: Retrieved email are %s", emails);
objectPath.set(req, "session.auth_session.email", emails[0]);
const isAllowed = accessController.isDomainAllowedForUser(username, groups);
if (!isAllowed) throw new Error("User not allowed to visit this domain");
const isAllowed = accessController.isDomainAllowedForUser(username, groups);
if (!isAllowed) throw new Error("User not allowed to visit this domain");
regulator.mark(username, true);
res.status(204);
res.send();
})
.catch(exceptions.LdapSeachError, function(err: Error) {
logger.error("1st factor: Unable to retrieve email from LDAP", err);
res.status(500);
res.send();
})
.catch(exceptions.LdapBindError, function(err: Error) {
logger.error("1st factor: LDAP binding failed");
logger.debug("1st factor: LDAP binding failed due to ", err);
regulator.mark(username, false);
res.status(401);
res.send("Bad credentials");
})
.catch(exceptions.AuthenticationRegulationError, function(err: Error) {
logger.error("1st factor: the regulator rejected the authentication of user %s", username);
logger.debug("1st factor: authentication rejected due to %s", err);
res.status(403);
res.send("Access has been restricted for a few minutes...");
})
.catch(function(err: Error) {
console.log(err.stack);
logger.error("1st factor: Unhandled error %s", err);
res.status(500);
res.send("Internal error");
});
regulator.mark(username, true);
res.status(204);
res.send();
})
.catch(exceptions.LdapSeachError, function (err: Error) {
logger.error("1st factor: Unable to retrieve email from LDAP", err);
res.status(500);
res.send();
})
.catch(exceptions.LdapBindError, function (err: Error) {
logger.error("1st factor: LDAP binding failed");
logger.debug("1st factor: LDAP binding failed due to ", err);
regulator.mark(username, false);
res.status(401);
res.send("Bad credentials");
})
.catch(exceptions.AuthenticationRegulationError, function (err: Error) {
logger.error("1st factor: the regulator rejected the authentication of user %s", username);
logger.debug("1st factor: authentication rejected due to %s", err);
res.status(403);
res.send("Access has been restricted for a few minutes...");
})
.catch(function (err: Error) {
console.log(err.stack);
logger.error("1st factor: Unhandled error %s", err);
res.status(500);
res.send("Internal error");
});
};

View File

@ -0,0 +1,49 @@
import exceptions = require("../Exceptions");
import objectPath = require("object-path");
import express = require("express");
import { TOTPSecretDocument } from "../UserDataStore";
import BluebirdPromise = require("bluebird");
const UNAUTHORIZED_MESSAGE = "Unauthorized access";
export = function(req: express.Request, res: express.Response) {
const logger = req.app.get("logger");
const userid = objectPath.get(req, "session.auth_session.userid");
logger.info("POST 2ndfactor totp: Initiate TOTP validation for user %s", userid);
if (!userid) {
logger.error("POST 2ndfactor totp: No user id in the session");
res.status(403);
res.send();
return;
}
const token = req.body.token;
const totpValidator = req.app.get("totp validator");
const userDataStore = req.app.get("user data store");
logger.debug("POST 2ndfactor totp: Fetching secret for user %s", userid);
userDataStore.get_totp_secret(userid)
.then(function (doc: TOTPSecretDocument) {
logger.debug("POST 2ndfactor totp: TOTP secret is %s", JSON.stringify(doc));
return totpValidator.validate(token, doc.secret.base32);
})
.then(function () {
logger.debug("POST 2ndfactor totp: TOTP validation succeeded");
objectPath.set(req, "session.auth_session.second_factor", true);
res.status(204);
res.send();
})
.catch(exceptions.InvalidTOTPError, function (err: Error) {
logger.error("POST 2ndfactor totp: Invalid TOTP token %s", err.message);
res.status(401);
res.send("Invalid TOTP token");
})
.catch(function (err: Error) {
console.log(err.stack);
logger.error("POST 2ndfactor totp: Internal error %s", err.message);
res.status(500);
res.send("Internal error");
});
};

View File

@ -1,5 +1,5 @@
var Promise = require('bluebird');
var BluebirdPromise = require('bluebird');
var objectPath = require('object-path');
var exceptions = require('../Exceptions');
var CHALLENGE = 'reset-password';
@ -19,7 +19,7 @@ module.exports = {
function pre_check(req) {
var userid = objectPath.get(req, 'body.userid');
if(!userid) {
return Promise.reject(new exceptions.AccessDeniedError("No user id provided"));
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No user id provided"));
}
var ldap = req.app.get('ldap');
@ -30,7 +30,7 @@ function pre_check(req) {
var identity = {}
identity.email = emails[0];
identity.userid = userid;
return Promise.resolve(identity);
return BluebirdPromise.resolve(identity);
});
}

View File

@ -1,9 +1,10 @@
var denyNotLogged = require('./deny_not_logged');
var u2f = require('./u2f');
var u2f = require('./u2f');
var TOTPAuthenticator = require("./TOTPAuthenticator");
module.exports = {
totp: denyNotLogged(require('./totp')),
totp: denyNotLogged(TOTPAuthenticator),
u2f: {
register_request: u2f.register_request,
register: u2f.register,

View File

@ -1,49 +0,0 @@
module.exports = totp_fn;
var objectPath = require('object-path');
var exceptions = require('../../../src/lib/Exceptions');
var UNAUTHORIZED_MESSAGE = 'Unauthorized access';
function totp_fn(req, res) {
var logger = req.app.get('logger');
var userid = objectPath.get(req, 'session.auth_session.userid');
logger.info('POST 2ndfactor totp: Initiate TOTP validation for user %s', userid);
if(!userid) {
logger.error('POST 2ndfactor totp: No user id in the session');
res.status(403);
res.send();
return;
}
var token = req.body.token;
var totpValidator = req.app.get('totp validator');
var data_store = req.app.get('user data store');
logger.debug('POST 2ndfactor totp: Fetching secret for user %s', userid);
data_store.get_totp_secret(userid)
.then(function(doc) {
logger.debug('POST 2ndfactor totp: TOTP secret is %s', JSON.stringify(doc));
return totpValidator.validate(token, doc.secret.base32);
})
.then(function() {
logger.debug('POST 2ndfactor totp: TOTP validation succeeded');
objectPath.set(req, 'session.auth_session.second_factor', true);
res.status(204);
res.send();
}, function(err) {
throw new exceptions.InvalidTOTPError();
})
.catch(exceptions.InvalidTOTPError, function(err) {
logger.error('POST 2ndfactor totp: Invalid TOTP token %s', err);
res.status(401);
res.send('Invalid TOTP token');
})
.catch(function(err) {
logger.error('POST 2ndfactor totp: Internal error %s', err);
res.status(500);
res.send('Internal error');
});
}

View File

@ -1,5 +1,5 @@
var objectPath = require('object-path');
var Promise = require('bluebird');
var BluebirdPromise = require('bluebird');
var CHALLENGE = 'totp-register';
@ -18,20 +18,20 @@ module.exports = {
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 registering TOTP secret key');
return BluebirdPromise.reject('Authentication required before registering TOTP secret key');
}
var userid = objectPath.get(req, 'session.auth_session.userid');
var email = objectPath.get(req, 'session.auth_session.email');
if(!(userid && email)) {
return Promise.reject('User ID or email is missing');
return BluebirdPromise.reject('User ID or email is missing');
}
var identity = {};
identity.email = email;
identity.userid = userid;
return Promise.resolve(identity);
return BluebirdPromise.resolve(identity);
}
// Generate a secret and send it to the user

View File

@ -10,7 +10,7 @@ module.exports = {
var objectPath = require('object-path');
var u2f_common = require('./u2f_common');
var Promise = require('bluebird');
var BluebirdPromise = require('bluebird');
function register_request(req, res) {
var logger = req.app.get('logger');

View File

@ -1,6 +1,6 @@
var objectPath = require('object-path');
var Promise = require('bluebird');
var BluebirdPromise = require('bluebird');
var CHALLENGE = 'u2f-register';
@ -19,19 +19,19 @@ module.exports = {
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');
return BluebirdPromise.reject('Authentication required before issuing a u2f registration request');
}
var userid = objectPath.get(req, 'session.auth_session.userid');
var email = objectPath.get(req, 'session.auth_session.email');
if(!(userid && email)) {
return Promise.reject('User ID or email is missing');
return BluebirdPromise.reject('User ID or email is missing');
}
var identity = {};
identity.email = email;
identity.userid = userid;
return Promise.resolve(identity);
return BluebirdPromise.resolve(identity);
}

View File

@ -2,31 +2,31 @@
module.exports = verify;
var objectPath = require('object-path');
var Promise = require('bluebird');
var BluebirdPromise = require('bluebird');
function verify_filter(req, res) {
var logger = req.app.get('logger');
if(!objectPath.has(req, 'session.auth_session'))
return Promise.reject('No auth_session variable');
return BluebirdPromise.reject('No auth_session variable');
if(!objectPath.has(req, 'session.auth_session.first_factor'))
return Promise.reject('No first factor variable');
return BluebirdPromise.reject('No first factor variable');
if(!objectPath.has(req, 'session.auth_session.second_factor'))
return Promise.reject('No second factor variable');
return BluebirdPromise.reject('No second factor variable');
if(!objectPath.has(req, 'session.auth_session.userid'))
return Promise.reject('No userid variable');
return BluebirdPromise.reject('No userid variable');
var host = objectPath.get(req, 'headers.host');
var domain = host.split(':')[0];
if(!req.session.auth_session.first_factor ||
!req.session.auth_session.second_factor)
return Promise.reject('First or second factor not validated');
return BluebirdPromise.reject('First or second factor not validated');
return Promise.resolve();
return BluebirdPromise.resolve();
}
function verify(req, res) {

View File

@ -1,4 +1,6 @@
import BluebirdPromise = require("bluebird");
declare module "authdog" {
interface RegisterRequest {
challenge: string;
@ -60,8 +62,8 @@ declare module "authdog" {
counter: Uint32Array
}
export function startRegistration(appId: AppId, registeredKeys: RegisteredKeys, options?: Options): Promise<RegistrationRequest>;
export function finishRegistration(registrationRequest: RegistrationRequest, registrationResponse: RegistrationResponse): Promise<Registration>;
export function startAuthentication(appId: AppId, registeredKeys: RegisteredKeys, options: Options): Promise<AuthenticationRequest>;
export function finishAuthentication(challenge: string, deviceResponse: AuthenticationResponse, registeredKeys: RegisteredKeys): Promise<Authentication>;
export function startRegistration(appId: AppId, registeredKeys: RegisteredKeys, options?: Options): BluebirdPromise<RegistrationRequest>;
export function finishRegistration(registrationRequest: RegistrationRequest, registrationResponse: RegistrationResponse): BluebirdPromise<Registration>;
export function startAuthentication(appId: AppId, registeredKeys: RegisteredKeys, options: Options): BluebirdPromise<AuthenticationRequest>;
export function finishAuthentication(challenge: string, deviceResponse: AuthenticationResponse, registeredKeys: RegisteredKeys): BluebirdPromise<Authentication>;
}

View File

@ -1,11 +1,11 @@
import ldapjs = require("ldapjs");
import * as Promise from "bluebird";
import * as BluebirdPromise from "bluebird";
import { EventEmitter } from "events";
declare module "ldapjs" {
export interface ClientAsync {
bindAsync(username: string, password: string): Promise<void>;
searchAsync(base: string, query: ldapjs.SearchOptions): Promise<EventEmitter>;
modifyAsync(userdn: string, change: ldapjs.Change): Promise<void>;
bindAsync(username: string, password: string): BluebirdPromise<void>;
searchAsync(base: string, query: ldapjs.SearchOptions): BluebirdPromise<EventEmitter>;
modifyAsync(userdn: string, change: ldapjs.Change): BluebirdPromise<void>;
}
}

View File

@ -1,12 +1,12 @@
import Nedb = require("nedb");
import * as Promise from "bluebird";
import BluebirdPromise = require("bluebird");
declare module "nedb" {
export class NedbAsync extends Nedb {
constructor(pathOrOptions?: string | Nedb.DataStoreOptions);
updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): Promise<any>;
findOneAsync(query: any): Promise<any>;
insertAsync<T>(newDoc: T): Promise<any>;
removeAsync(query: any): Promise<any>;
updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): BluebirdPromise<any>;
findOneAsync(query: any): BluebirdPromise<any>;
insertAsync<T>(newDoc: T): BluebirdPromise<any>;
removeAsync(query: any): BluebirdPromise<any>;
}
}

View File

@ -1,14 +1,14 @@
import * as Promise from "bluebird";
import * as BluebirdPromise from "bluebird";
import * as request from "request";
declare module "request" {
export interface RequestAsync extends RequestAPI<Request, CoreOptions, RequiredUriUrl> {
getAsync(uri: string, options?: RequiredUriUrl): Promise<RequestResponse>;
getAsync(uri: string): Promise<RequestResponse>;
getAsync(options: RequiredUriUrl & CoreOptions): Promise<RequestResponse>;
getAsync(uri: string, options?: RequiredUriUrl): BluebirdPromise<RequestResponse>;
getAsync(uri: string): BluebirdPromise<RequestResponse>;
getAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise<RequestResponse>;
postAsync(uri: string, options?: CoreOptions): Promise<RequestResponse>;
postAsync(uri: string): Promise<RequestResponse>;
postAsync(options: RequiredUriUrl & CoreOptions): Promise<RequestResponse>;
postAsync(uri: string, options?: CoreOptions): BluebirdPromise<RequestResponse>;
postAsync(uri: string): BluebirdPromise<RequestResponse>;
postAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise<RequestResponse>;
}
}

View File

@ -3,13 +3,14 @@ import AuthenticationRegulator from "../../src/lib/AuthenticationRegulator";
import UserDataStore from "../../src/lib/UserDataStore";
import MockDate = require("mockdate");
import exceptions = require("../../src/lib/Exceptions");
import nedb = require("nedb");
describe("test authentication regulator", function() {
it("should mark 2 authentication and regulate (resolve)", function() {
const options = {
inMemoryOnly: true
};
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const regulator = new AuthenticationRegulator(data_store, 10);
const user = "user";
@ -26,7 +27,7 @@ describe("test authentication regulator", function() {
const options = {
inMemoryOnly: true
};
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const regulator = new AuthenticationRegulator(data_store, 10);
const user = "user";
@ -49,7 +50,7 @@ describe("test authentication regulator", function() {
const options = {
inMemoryOnly: true
};
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const regulator = new AuthenticationRegulator(data_store, 10);
const user = "user";

View File

@ -18,12 +18,12 @@ describe("test TOTP validation", function() {
return totpValidator.validate(token, totp_secret);
});
it("should not validate a wrong TOTP token", function() {
it("should not validate a wrong TOTP token", function(done) {
const totp_secret = "NBD2ZV64R9UV1O7K";
const token = "wrong token";
return totpValidator.validate(token, totp_secret)
totpValidator.validate(token, totp_secret)
.catch(function() {
return Promise.resolve();
done();
});
});
});

View File

@ -2,7 +2,7 @@
import UserDataStore from "../../src/lib/UserDataStore";
import { U2FMetaDocument, Options } from "../../src/lib/UserDataStore";
import DataStore = require("nedb");
import nedb = require("nedb");
import assert = require("assert");
import Promise = require("bluebird");
import sinon = require("sinon");
@ -20,7 +20,7 @@ describe("test user data store", () => {
describe("test u2f meta", () => {
it("should save a u2f meta", function () {
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const app_id = "https://localhost";
@ -40,7 +40,7 @@ describe("test user data store", () => {
inMemoryOnly: true
};
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const app_id = "https://localhost";
@ -60,7 +60,7 @@ describe("test user data store", () => {
inMemoryOnly: true
};
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const app_id = "https://localhost";
@ -86,7 +86,7 @@ describe("test user data store", () => {
describe("test u2f registration token", () => {
it("should save u2f registration token", function () {
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const token = "token";
@ -109,7 +109,7 @@ describe("test user data store", () => {
});
it("should save u2f registration token and consume it", function (done) {
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const token = "token";
@ -128,7 +128,7 @@ describe("test user data store", () => {
});
it("should not be able to consume registration token twice", function (done) {
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const token = "token";
@ -148,7 +148,7 @@ describe("test user data store", () => {
});
it("should fail when token does not exist", function () {
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const token = "token";
@ -162,7 +162,7 @@ describe("test user data store", () => {
});
it("should fail when token expired", function (done) {
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const token = "token";
@ -181,7 +181,7 @@ describe("test user data store", () => {
});
it("should save the userid and some data with the token", function (done) {
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const token = "token";

View File

@ -1,8 +1,12 @@
import sinon = require("sinon");
export = function () {
export interface AccessControllerMock {
isDomainAllowedForUser: sinon.SinonStub;
}
export function AccessControllerMock() {
return {
isDomainAllowedForUser: sinon.stub()
};
};
}

View File

@ -1,9 +1,15 @@
import sinon = require("sinon");
export = function () {
export interface AuthenticationRegulatorMock {
mark: sinon.SinonStub;
regulate: sinon.SinonStub;
}
export function AuthenticationRegulatorMock() {
return {
mark: sinon.stub(),
regulate: sinon.stub()
};
};
}

View File

@ -0,0 +1,12 @@
import sinon = require("sinon");
export interface TOTPValidatorMock {
validate: sinon.SinonStub;
}
export function TOTPValidatorMock(): TOTPValidatorMock {
return {
validate: sinon.stub()
};
}

View File

@ -6,6 +6,7 @@ export interface UserDataStore {
get_u2f_meta: sinon.SinonStub;
issue_identity_check_token: sinon.SinonStub;
consume_identity_check_token: sinon.SinonStub;
get_totp_secret: sinon.SinonStub;
}
export function UserDataStore(): UserDataStore {
@ -13,6 +14,7 @@ export function UserDataStore(): UserDataStore {
set_u2f_meta: sinon.stub(),
get_u2f_meta: sinon.stub(),
issue_identity_check_token: sinon.stub(),
consume_identity_check_token: sinon.stub()
consume_identity_check_token: sinon.stub(),
get_totp_secret: sinon.stub()
};
}

View File

@ -12,14 +12,14 @@ import { LdapClientMock } from "../mocks/LdapClient";
import ExpressMock = require("../mocks/express");
describe("test the first factor validation route", function() {
let req: any;
let res: any;
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let emails: string[];
let groups: string[];
let configuration;
let ldapMock: LdapClientMock;
let regulator: any;
let accessController: any;
let regulator: AuthenticationRegulatorMock.AuthenticationRegulatorMock;
let accessController: AccessControllerMock.AccessControllerMock;
beforeEach(function() {
configuration = {
@ -34,10 +34,10 @@ describe("test the first factor validation route", function() {
ldapMock = LdapClientMock();
accessController = AccessControllerMock();
accessController = AccessControllerMock.AccessControllerMock();
accessController.isDomainAllowedForUser.returns(true);
regulator = AuthenticationRegulatorMock();
regulator = AuthenticationRegulatorMock.AuthenticationRegulatorMock();
regulator.regulate.returns(BluebirdPromise.resolve());
regulator.mark.returns(BluebirdPromise.resolve());
@ -75,15 +75,15 @@ describe("test the first factor validation route", function() {
});
ldapMock.bind.withArgs("username").returns(BluebirdPromise.resolve());
ldapMock.get_emails.returns(BluebirdPromise.resolve(emails));
FirstFactor(req, res);
FirstFactor(req as any, res as any);
});
});
it("should retrieve email from LDAP", function(done) {
res.send = sinon.spy(function() { done(); });
ldapMock.bind.returns(BluebirdPromise.resolve());
ldapMock.get_emails = sinon.stub().withArgs("usernam").returns(BluebirdPromise.resolve([{mail: ["test@example.com"] }]));
FirstFactor(req, res);
ldapMock.get_emails = sinon.stub().withArgs("username").returns(BluebirdPromise.resolve([{mail: ["test@example.com"] }]));
FirstFactor(req as any, res as any);
});
it("should set email as session variables", function() {
@ -95,7 +95,7 @@ describe("test the first factor validation route", function() {
const emails = [ "test_ok@example.com" ];
ldapMock.bind.returns(BluebirdPromise.resolve());
ldapMock.get_emails.returns(BluebirdPromise.resolve(emails));
FirstFactor(req, res);
FirstFactor(req as any, res as any);
});
});
@ -105,8 +105,8 @@ describe("test the first factor validation route", function() {
assert.equal(regulator.mark.getCall(0).args[0], "username");
done();
});
ldapMock.bind.throws(new exceptions.LdapBindError("Bad credentials"));
FirstFactor(req, res);
ldapMock.bind.returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials")));
FirstFactor(req as any, res as any);
});
it("should return status code 500 when LDAP search throws", function(done) {
@ -115,8 +115,8 @@ describe("test the first factor validation route", function() {
done();
});
ldapMock.bind.returns(BluebirdPromise.resolve());
ldapMock.get_emails.throws(new exceptions.LdapSeachError("error while retrieving emails"));
FirstFactor(req, res);
ldapMock.get_emails.returns(BluebirdPromise.reject(new exceptions.LdapSeachError("error while retrieving emails")));
FirstFactor(req as any, res as any);
});
it("should return status code 403 when regulator rejects authentication", function(done) {
@ -129,7 +129,7 @@ describe("test the first factor validation route", function() {
});
ldapMock.bind.returns(BluebirdPromise.resolve());
ldapMock.get_emails.returns(BluebirdPromise.resolve());
FirstFactor(req, res);
FirstFactor(req as any, res as any);
});
});

View File

@ -0,0 +1,90 @@
import BluebirdPromise = require("bluebird");
import sinon = require("sinon");
import assert = require("assert");
import winston = require("winston");
import exceptions = require("../../../src/lib/Exceptions");
import TOTPAuthenticator = require("../../../src/lib/routes/TOTPAuthenticator");
import ExpressMock = require("../mocks/express");
import UserDataStoreMock = require("../mocks/UserDataStore");
import TOTPValidatorMock = require("../mocks/TOTPValidator");
describe("test totp route", function() {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let totpValidator: TOTPValidatorMock.TOTPValidatorMock;
let userDataStore: UserDataStoreMock.UserDataStore;
beforeEach(function() {
const app_get = sinon.stub();
req = {
app: {
get: app_get
},
body: {
token: "abc"
},
session: {
auth_session: {
userid: "user",
first_factor: false,
second_factor: false
}
}
};
res = ExpressMock.ResponseMock();
const config = { totp_secret: "secret" };
totpValidator = TOTPValidatorMock.TOTPValidatorMock();
userDataStore = UserDataStoreMock.UserDataStore();
const doc = {
userid: "user",
secret: {
base32: "ABCDEF"
}
};
userDataStore.get_totp_secret.returns(BluebirdPromise.resolve(doc));
app_get.withArgs("logger").returns(winston);
app_get.withArgs("totp validator").returns(totpValidator);
app_get.withArgs("config").returns(config);
app_get.withArgs("user data store").returns(userDataStore);
});
it("should send status code 204 when totp is valid", function(done) {
totpValidator.validate.returns(Promise.resolve("ok"));
res.send = sinon.spy(function() {
// Second factor passed
assert.equal(true, req.session.auth_session.second_factor);
assert.equal(204, res.status.getCall(0).args[0]);
done();
});
TOTPAuthenticator(req as any, res as any);
});
it("should send status code 401 when totp is not valid", function(done) {
totpValidator.validate.returns(Promise.reject(new exceptions.InvalidTOTPError("Bad TOTP token")));
res.send = sinon.spy(function() {
assert.equal(false, req.session.auth_session.second_factor);
assert.equal(401, res.status.getCall(0).args[0]);
done();
});
TOTPAuthenticator(req as any, res as any);
});
it("should send status code 401 when session has not been initiated", function(done) {
totpValidator.validate.returns(Promise.resolve("abc"));
res.send = sinon.spy(function() {
assert.equal(403, res.status.getCall(0).args[0]);
done();
});
req.session = {};
TOTPAuthenticator(req as any, res as any);
});
});

View File

@ -1,87 +0,0 @@
var totp = require('../../../src/lib/routes/totp');
var Promise = require('bluebird');
var sinon = require('sinon');
var assert = require('assert');
var winston = require('winston');
describe('test totp route', function() {
var req, res;
var totpValidator;
var user_data_store;
beforeEach(function() {
var app_get = sinon.stub();
req = {
app: {
get: app_get
},
body: {
token: 'abc'
},
session: {
auth_session: {
userid: 'user',
first_factor: false,
second_factor: false
}
}
};
res = {
send: sinon.spy(),
status: sinon.spy()
};
var config = { totp_secret: 'secret' };
totpValidator = {
validate: sinon.stub()
}
user_data_store = {};
user_data_store.get_totp_secret = sinon.stub();
var doc = {};
doc.userid = 'user';
doc.secret = {};
doc.secret.base32 = 'ABCDEF';
user_data_store.get_totp_secret.returns(Promise.resolve(doc));
app_get.withArgs('logger').returns(winston);
app_get.withArgs('totp validator').returns(totpValidator);
app_get.withArgs('config').returns(config);
app_get.withArgs('user data store').returns(user_data_store);
});
it('should send status code 204 when totp is valid', function(done) {
totpValidator.validate.returns(Promise.resolve("ok"));
res.send = sinon.spy(function() {
// Second factor passed
assert.equal(true, req.session.auth_session.second_factor)
assert.equal(204, res.status.getCall(0).args[0]);
done();
});
totp(req, res);
});
it('should send status code 401 when totp is not valid', function(done) {
totpValidator.validate.returns(Promise.reject('bad_token'));
res.send = sinon.spy(function() {
assert.equal(false, req.session.auth_session.second_factor)
assert.equal(401, res.status.getCall(0).args[0]);
done();
});
totp(req, res);
});
it('should send status code 401 when session has not been initiated', function(done) {
totpValidator.validate.returns(Promise.resolve('abc'));
res.send = sinon.spy(function() {
assert.equal(403, res.status.getCall(0).args[0]);
done();
});
req.session = {};
totp(req, res);
});
});

View File

@ -4,6 +4,7 @@ import * as Promise from "bluebird";
import * as sinon from "sinon";
import * as MockDate from "mockdate";
import UserDataStore from "../../../src/lib/UserDataStore";
import nedb = require("nedb");
describe("test user data store", function() {
describe("test authentication traces", test_authentication_traces);
@ -15,7 +16,7 @@ function test_authentication_traces() {
inMemoryOnly: true
};
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const type = "1stfactor";
const is_success = false;
@ -34,7 +35,7 @@ function test_authentication_traces() {
inMemoryOnly: true
};
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const type = "1stfactor";
const is_success = false;

View File

@ -4,6 +4,7 @@ import * as Promise from "bluebird";
import * as sinon from "sinon";
import * as MockDate from "mockdate";
import UserDataStore from "../../../src/lib/UserDataStore";
import nedb = require("nedb");
describe("test user data store", function() {
describe("test totp secrets store", test_totp_secrets);
@ -15,7 +16,7 @@ function test_totp_secrets() {
inMemoryOnly: true
};
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const secret = {
ascii: "abc",
@ -41,7 +42,7 @@ function test_totp_secrets() {
inMemoryOnly: true
};
const data_store = new UserDataStore(options);
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const secret1 = {
ascii: "abc",

View File

@ -10,8 +10,8 @@
"allowJs": true,
"paths": {
"*": [
"node_modules/@types/*",
"src/types/*"
"src/types/*",
"node_modules/@types/*"
]
}
},