Move TOTP authenticator to typescript
parent
b54c181d27
commit
c98c07832d
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
|
||||
export class LdapSeachError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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>;
|
||||
}
|
||||
}
|
|
@ -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>;
|
||||
}
|
||||
}
|
|
@ -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>;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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";
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
|
||||
import sinon = require("sinon");
|
||||
|
||||
export = function () {
|
||||
export interface AccessControllerMock {
|
||||
isDomainAllowedForUser: sinon.SinonStub;
|
||||
}
|
||||
|
||||
export function AccessControllerMock() {
|
||||
return {
|
||||
isDomainAllowedForUser: sinon.stub()
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
import sinon = require("sinon");
|
||||
|
||||
export interface TOTPValidatorMock {
|
||||
validate: sinon.SinonStub;
|
||||
}
|
||||
|
||||
export function TOTPValidatorMock(): TOTPValidatorMock {
|
||||
return {
|
||||
validate: sinon.stub()
|
||||
};
|
||||
}
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
"allowJs": true,
|
||||
"paths": {
|
||||
"*": [
|
||||
"node_modules/@types/*",
|
||||
"src/types/*"
|
||||
"src/types/*",
|
||||
"node_modules/@types/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue