Move TOTP authenticator to typescript
parent
b54c181d27
commit
c98c07832d
|
@ -47,7 +47,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/assert": "0.0.31",
|
"@types/assert": "0.0.31",
|
||||||
"@types/bluebird": "^3.5.3",
|
"@types/bluebird": "^3.5.4",
|
||||||
"@types/body-parser": "^1.16.3",
|
"@types/body-parser": "^1.16.3",
|
||||||
"@types/ejs": "^2.3.33",
|
"@types/ejs": "^2.3.33",
|
||||||
"@types/express": "^4.0.35",
|
"@types/express": "^4.0.35",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
import * as Promise from "bluebird";
|
import * as BluebirdPromise from "bluebird";
|
||||||
import exceptions = require("./Exceptions");
|
import exceptions = require("./Exceptions");
|
||||||
|
|
||||||
const REGULATION_TRACE_TYPE = "regulation";
|
const REGULATION_TRACE_TYPE = "regulation";
|
||||||
|
@ -19,16 +19,16 @@ export default class AuthenticationRegulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark authentication
|
// 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);
|
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)
|
return this._user_data_store.get_last_authentication_traces(userid, REGULATION_TRACE_TYPE, false, 3)
|
||||||
.then((docs: Array<DatedDocument>) => {
|
.then((docs: Array<DatedDocument>) => {
|
||||||
if (docs.length < MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE) {
|
if (docs.length < MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE) {
|
||||||
// less than the max authorized number of authentication in time range, thus authorizing access
|
// 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];
|
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.");
|
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 {
|
export class LdapSeachError extends Error {
|
||||||
constructor(message?: string) {
|
constructor(message?: string) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
|
@ -115,7 +115,7 @@ export class LdapClient {
|
||||||
groups.push(docs[i].cn);
|
groups.push(docs[i].cn);
|
||||||
}
|
}
|
||||||
that.logger.debug("LDAP: got groups %s", groups);
|
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);
|
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 TOTPGenerator from "./TOTPGenerator";
|
||||||
import RestApi from "./RestApi";
|
import RestApi from "./RestApi";
|
||||||
import { LdapClient } from "./LdapClient";
|
import { LdapClient } from "./LdapClient";
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
|
||||||
import * as Express from "express";
|
import * as Express from "express";
|
||||||
import * as BodyParser from "body-parser";
|
import * as BodyParser from "body-parser";
|
||||||
|
@ -20,7 +21,7 @@ import AccessController from "./access_control/AccessController";
|
||||||
export default class Server {
|
export default class Server {
|
||||||
private httpServer: http.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 config = ConfigurationAdapter.adapt(yaml_configuration);
|
||||||
|
|
||||||
const view_directory = Path.resolve(__dirname, "../views");
|
const view_directory = Path.resolve(__dirname, "../views");
|
||||||
|
@ -54,7 +55,7 @@ export default class Server {
|
||||||
deps.winston.level = config.logs_level || "info";
|
deps.winston.level = config.logs_level || "info";
|
||||||
|
|
||||||
const five_minutes = 5 * 60;
|
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 regulator = new AuthenticationRegulator(data_store, five_minutes);
|
||||||
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
|
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
|
||||||
const ldap = new LdapClient(config.ldap, deps.ldapjs, deps.winston);
|
const ldap = new LdapClient(config.ldap, deps.ldapjs, deps.winston);
|
||||||
|
@ -75,7 +76,7 @@ export default class Server {
|
||||||
|
|
||||||
RestApi.setup(app);
|
RestApi.setup(app);
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new BluebirdPromise<void>((resolve, reject) => {
|
||||||
this.httpServer = app.listen(config.port, function (err: string) {
|
this.httpServer = app.listen(config.port, function (err: string) {
|
||||||
console.log("Listening on %d...", config.port);
|
console.log("Listening on %d...", config.port);
|
||||||
resolve();
|
resolve();
|
||||||
|
|
|
@ -18,6 +18,6 @@ export default class TOTPValidator {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (token == real_token) return BluebirdPromise.resolve();
|
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 * as path from "path";
|
||||||
import Nedb = require("nedb");
|
|
||||||
import { NedbAsync } from "nedb";
|
import { NedbAsync } from "nedb";
|
||||||
import { TOTPSecret } from "../types/TOTPSecret";
|
import { TOTPSecret } from "../types/TOTPSecret";
|
||||||
|
import { Nedb } from "../types/Dependencies";
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
|
|
||||||
|
@ -36,18 +36,20 @@ export default class UserDataStore {
|
||||||
private _identity_check_tokens_collection: NedbAsync;
|
private _identity_check_tokens_collection: NedbAsync;
|
||||||
private _authentication_traces_collection: NedbAsync;
|
private _authentication_traces_collection: NedbAsync;
|
||||||
private _totp_secret_collection: NedbAsync;
|
private _totp_secret_collection: NedbAsync;
|
||||||
|
private nedb: Nedb;
|
||||||
|
|
||||||
constructor(options?: Options) {
|
constructor(options: Options, nedb: Nedb) {
|
||||||
this._u2f_meta_collection = create_collection(U2F_META_COLLECTION_NAME, options);
|
this.nedb = nedb;
|
||||||
|
this._u2f_meta_collection = this.create_collection(U2F_META_COLLECTION_NAME, options);
|
||||||
this._identity_check_tokens_collection =
|
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 =
|
this._authentication_traces_collection =
|
||||||
create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options);
|
this.create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options);
|
||||||
this._totp_secret_collection =
|
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 = {
|
const newDocument = {
|
||||||
userid: userid,
|
userid: userid,
|
||||||
appid: appid,
|
appid: appid,
|
||||||
|
@ -62,7 +64,7 @@ export default class UserDataStore {
|
||||||
return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true });
|
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 = {
|
const filter = {
|
||||||
userid: userid,
|
userid: userid,
|
||||||
appid: appid
|
appid: appid
|
||||||
|
@ -81,7 +83,7 @@ export default class UserDataStore {
|
||||||
return this._authentication_traces_collection.insertAsync(newDocument);
|
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 = {
|
const q = {
|
||||||
userid: userid,
|
userid: userid,
|
||||||
type: type,
|
type: type,
|
||||||
|
@ -90,11 +92,11 @@ export default class UserDataStore {
|
||||||
|
|
||||||
const query = this._authentication_traces_collection.find(q)
|
const query = this._authentication_traces_collection.find(q)
|
||||||
.sort({ date: -1 }).limit(count);
|
.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();
|
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 = {
|
const newDocument = {
|
||||||
userid: userid,
|
userid: userid,
|
||||||
token: token,
|
token: token,
|
||||||
|
@ -108,7 +110,7 @@ export default class UserDataStore {
|
||||||
return this._identity_check_tokens_collection.insertAsync(newDocument);
|
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 = {
|
const query = {
|
||||||
token: token
|
token: token
|
||||||
};
|
};
|
||||||
|
@ -116,26 +118,26 @@ export default class UserDataStore {
|
||||||
return this._identity_check_tokens_collection.findOneAsync(query)
|
return this._identity_check_tokens_collection.findOneAsync(query)
|
||||||
.then(function (doc) {
|
.then(function (doc) {
|
||||||
if (!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 max_date = doc.max_date;
|
||||||
const current_date = new Date();
|
const current_date = new Date();
|
||||||
if (current_date > max_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) => {
|
.then((content) => {
|
||||||
return Promise.join(this._identity_check_tokens_collection.removeAsync(query),
|
return BluebirdPromise.join(this._identity_check_tokens_collection.removeAsync(query),
|
||||||
Promise.resolve(content));
|
BluebirdPromise.resolve(content));
|
||||||
})
|
})
|
||||||
.then((v) => {
|
.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 = {
|
const doc = {
|
||||||
userid: userid,
|
userid: userid,
|
||||||
secret: secret
|
secret: secret
|
||||||
|
@ -147,15 +149,14 @@ export default class UserDataStore {
|
||||||
return this._totp_secret_collection.updateAsync(query, doc, { upsert: true });
|
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 = {
|
const query = {
|
||||||
userid: userid
|
userid: userid
|
||||||
};
|
};
|
||||||
return this._totp_secret_collection.findOneAsync(query);
|
return this._totp_secret_collection.findOneAsync(query);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function create_collection(name: string, options: any): NedbAsync {
|
private create_collection(name: string, options: any): NedbAsync {
|
||||||
const datastore_options = {
|
const datastore_options = {
|
||||||
inMemoryOnly: options.inMemoryOnly || false,
|
inMemoryOnly: options.inMemoryOnly || false,
|
||||||
autoload: true,
|
autoload: true,
|
||||||
|
@ -165,5 +166,6 @@ function create_collection(name: string, options: any): NedbAsync {
|
||||||
if (options.directory)
|
if (options.directory)
|
||||||
datastore_options.filename = path.resolve(options.directory, name);
|
datastore_options.filename = path.resolve(options.directory, name);
|
||||||
|
|
||||||
return Promise.promisifyAll(new Nedb(datastore_options)) as NedbAsync;
|
return BluebirdPromise.promisifyAll(new this.nedb(datastore_options)) as NedbAsync;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
var objectPath = require('object-path');
|
var objectPath = require('object-path');
|
||||||
var randomstring = require('randomstring');
|
var randomstring = require('randomstring');
|
||||||
var Promise = require('bluebird');
|
var BluebirdPromise = require('bluebird');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var exceptions = require('./Exceptions');
|
var exceptions = require('./Exceptions');
|
||||||
var fs = require('fs');
|
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);
|
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)
|
return this._user_data_store.issue_identity_check_token(userid, token, content, five_minutes)
|
||||||
.then(function() {
|
.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 fs from "fs";
|
||||||
import * as ejs from "ejs";
|
import * as ejs from "ejs";
|
||||||
import nodemailer = require("nodemailer");
|
import nodemailer = require("nodemailer");
|
||||||
|
@ -23,10 +23,10 @@ export class GMailNotifier extends INotifier {
|
||||||
pass: options.password
|
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 = {
|
const d = {
|
||||||
url: link,
|
url: link,
|
||||||
button_title: "Continue",
|
button_title: "Continue",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
import exceptions = require("../Exceptions");
|
import exceptions = require("../Exceptions");
|
||||||
import objectPath = require("object-path");
|
import objectPath = require("object-path");
|
||||||
import Promise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import express = require("express");
|
import express = require("express");
|
||||||
|
|
||||||
export = function (req: express.Request, res: express.Response) {
|
export = function (req: express.Request, res: express.Response) {
|
||||||
|
@ -32,7 +32,7 @@ export = function(req: express.Request, res: express.Response) {
|
||||||
objectPath.set(req, "session.auth_session.first_factor", true);
|
objectPath.set(req, "session.auth_session.first_factor", true);
|
||||||
logger.info("1st factor: LDAP binding successful");
|
logger.info("1st factor: LDAP binding successful");
|
||||||
logger.debug("1st factor: Retrieve email from LDAP");
|
logger.debug("1st factor: Retrieve email from LDAP");
|
||||||
return Promise.join(ldap.get_emails(username), ldap.get_groups(username));
|
return BluebirdPromise.join(ldap.get_emails(username), ldap.get_groups(username));
|
||||||
})
|
})
|
||||||
.then(function (data: string[2]) {
|
.then(function (data: string[2]) {
|
||||||
const emails = data[0];
|
const emails = data[0];
|
||||||
|
|
|
@ -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 objectPath = require('object-path');
|
||||||
var exceptions = require('../Exceptions');
|
var exceptions = require('../Exceptions');
|
||||||
var CHALLENGE = 'reset-password';
|
var CHALLENGE = 'reset-password';
|
||||||
|
@ -19,7 +19,7 @@ module.exports = {
|
||||||
function pre_check(req) {
|
function pre_check(req) {
|
||||||
var userid = objectPath.get(req, 'body.userid');
|
var userid = objectPath.get(req, 'body.userid');
|
||||||
if(!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');
|
var ldap = req.app.get('ldap');
|
||||||
|
@ -30,7 +30,7 @@ function pre_check(req) {
|
||||||
var identity = {}
|
var identity = {}
|
||||||
identity.email = emails[0];
|
identity.email = emails[0];
|
||||||
identity.userid = userid;
|
identity.userid = userid;
|
||||||
return Promise.resolve(identity);
|
return BluebirdPromise.resolve(identity);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
|
||||||
var denyNotLogged = require('./deny_not_logged');
|
var denyNotLogged = require('./deny_not_logged');
|
||||||
var u2f = require('./u2f');
|
var u2f = require('./u2f');
|
||||||
|
var TOTPAuthenticator = require("./TOTPAuthenticator");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
totp: denyNotLogged(require('./totp')),
|
totp: denyNotLogged(TOTPAuthenticator),
|
||||||
u2f: {
|
u2f: {
|
||||||
register_request: u2f.register_request,
|
register_request: u2f.register_request,
|
||||||
register: u2f.register,
|
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 objectPath = require('object-path');
|
||||||
var Promise = require('bluebird');
|
var BluebirdPromise = require('bluebird');
|
||||||
|
|
||||||
var CHALLENGE = 'totp-register';
|
var CHALLENGE = 'totp-register';
|
||||||
|
|
||||||
|
@ -18,20 +18,20 @@ module.exports = {
|
||||||
function pre_check(req) {
|
function pre_check(req) {
|
||||||
var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor');
|
var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor');
|
||||||
if(!first_factor_passed) {
|
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 userid = objectPath.get(req, 'session.auth_session.userid');
|
||||||
var email = objectPath.get(req, 'session.auth_session.email');
|
var email = objectPath.get(req, 'session.auth_session.email');
|
||||||
|
|
||||||
if(!(userid && email)) {
|
if(!(userid && email)) {
|
||||||
return Promise.reject('User ID or email is missing');
|
return BluebirdPromise.reject('User ID or email is missing');
|
||||||
}
|
}
|
||||||
|
|
||||||
var identity = {};
|
var identity = {};
|
||||||
identity.email = email;
|
identity.email = email;
|
||||||
identity.userid = userid;
|
identity.userid = userid;
|
||||||
return Promise.resolve(identity);
|
return BluebirdPromise.resolve(identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a secret and send it to the user
|
// Generate a secret and send it to the user
|
||||||
|
|
|
@ -10,7 +10,7 @@ module.exports = {
|
||||||
|
|
||||||
var objectPath = require('object-path');
|
var objectPath = require('object-path');
|
||||||
var u2f_common = require('./u2f_common');
|
var u2f_common = require('./u2f_common');
|
||||||
var Promise = require('bluebird');
|
var BluebirdPromise = require('bluebird');
|
||||||
|
|
||||||
function register_request(req, res) {
|
function register_request(req, res) {
|
||||||
var logger = req.app.get('logger');
|
var logger = req.app.get('logger');
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
var objectPath = require('object-path');
|
var objectPath = require('object-path');
|
||||||
var Promise = require('bluebird');
|
var BluebirdPromise = require('bluebird');
|
||||||
|
|
||||||
var CHALLENGE = 'u2f-register';
|
var CHALLENGE = 'u2f-register';
|
||||||
|
|
||||||
|
@ -19,19 +19,19 @@ module.exports = {
|
||||||
function pre_check(req) {
|
function pre_check(req) {
|
||||||
var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor');
|
var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor');
|
||||||
if(!first_factor_passed) {
|
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 userid = objectPath.get(req, 'session.auth_session.userid');
|
||||||
var email = objectPath.get(req, 'session.auth_session.email');
|
var email = objectPath.get(req, 'session.auth_session.email');
|
||||||
|
|
||||||
if(!(userid && email)) {
|
if(!(userid && email)) {
|
||||||
return Promise.reject('User ID or email is missing');
|
return BluebirdPromise.reject('User ID or email is missing');
|
||||||
}
|
}
|
||||||
|
|
||||||
var identity = {};
|
var identity = {};
|
||||||
identity.email = email;
|
identity.email = email;
|
||||||
identity.userid = userid;
|
identity.userid = userid;
|
||||||
return Promise.resolve(identity);
|
return BluebirdPromise.resolve(identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,31 +2,31 @@
|
||||||
module.exports = verify;
|
module.exports = verify;
|
||||||
|
|
||||||
var objectPath = require('object-path');
|
var objectPath = require('object-path');
|
||||||
var Promise = require('bluebird');
|
var BluebirdPromise = require('bluebird');
|
||||||
|
|
||||||
function verify_filter(req, res) {
|
function verify_filter(req, res) {
|
||||||
var logger = req.app.get('logger');
|
var logger = req.app.get('logger');
|
||||||
|
|
||||||
if(!objectPath.has(req, 'session.auth_session'))
|
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'))
|
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'))
|
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'))
|
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 host = objectPath.get(req, 'headers.host');
|
||||||
var domain = host.split(':')[0];
|
var domain = host.split(':')[0];
|
||||||
|
|
||||||
if(!req.session.auth_session.first_factor ||
|
if(!req.session.auth_session.first_factor ||
|
||||||
!req.session.auth_session.second_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) {
|
function verify(req, res) {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
|
||||||
declare module "authdog" {
|
declare module "authdog" {
|
||||||
interface RegisterRequest {
|
interface RegisterRequest {
|
||||||
challenge: string;
|
challenge: string;
|
||||||
|
@ -60,8 +62,8 @@ declare module "authdog" {
|
||||||
counter: Uint32Array
|
counter: Uint32Array
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startRegistration(appId: AppId, registeredKeys: RegisteredKeys, options?: Options): Promise<RegistrationRequest>;
|
export function startRegistration(appId: AppId, registeredKeys: RegisteredKeys, options?: Options): BluebirdPromise<RegistrationRequest>;
|
||||||
export function finishRegistration(registrationRequest: RegistrationRequest, registrationResponse: RegistrationResponse): Promise<Registration>;
|
export function finishRegistration(registrationRequest: RegistrationRequest, registrationResponse: RegistrationResponse): BluebirdPromise<Registration>;
|
||||||
export function startAuthentication(appId: AppId, registeredKeys: RegisteredKeys, options: Options): Promise<AuthenticationRequest>;
|
export function startAuthentication(appId: AppId, registeredKeys: RegisteredKeys, options: Options): BluebirdPromise<AuthenticationRequest>;
|
||||||
export function finishAuthentication(challenge: string, deviceResponse: AuthenticationResponse, registeredKeys: RegisteredKeys): Promise<Authentication>;
|
export function finishAuthentication(challenge: string, deviceResponse: AuthenticationResponse, registeredKeys: RegisteredKeys): BluebirdPromise<Authentication>;
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
import ldapjs = require("ldapjs");
|
import ldapjs = require("ldapjs");
|
||||||
import * as Promise from "bluebird";
|
import * as BluebirdPromise from "bluebird";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
declare module "ldapjs" {
|
declare module "ldapjs" {
|
||||||
export interface ClientAsync {
|
export interface ClientAsync {
|
||||||
bindAsync(username: string, password: string): Promise<void>;
|
bindAsync(username: string, password: string): BluebirdPromise<void>;
|
||||||
searchAsync(base: string, query: ldapjs.SearchOptions): Promise<EventEmitter>;
|
searchAsync(base: string, query: ldapjs.SearchOptions): BluebirdPromise<EventEmitter>;
|
||||||
modifyAsync(userdn: string, change: ldapjs.Change): Promise<void>;
|
modifyAsync(userdn: string, change: ldapjs.Change): BluebirdPromise<void>;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
import Nedb = require("nedb");
|
import Nedb = require("nedb");
|
||||||
import * as Promise from "bluebird";
|
import BluebirdPromise = require("bluebird");
|
||||||
|
|
||||||
declare module "nedb" {
|
declare module "nedb" {
|
||||||
export class NedbAsync extends Nedb {
|
export class NedbAsync extends Nedb {
|
||||||
constructor(pathOrOptions?: string | Nedb.DataStoreOptions);
|
constructor(pathOrOptions?: string | Nedb.DataStoreOptions);
|
||||||
updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): Promise<any>;
|
updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): BluebirdPromise<any>;
|
||||||
findOneAsync(query: any): Promise<any>;
|
findOneAsync(query: any): BluebirdPromise<any>;
|
||||||
insertAsync<T>(newDoc: T): Promise<any>;
|
insertAsync<T>(newDoc: T): BluebirdPromise<any>;
|
||||||
removeAsync(query: any): Promise<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";
|
import * as request from "request";
|
||||||
|
|
||||||
declare module "request" {
|
declare module "request" {
|
||||||
export interface RequestAsync extends RequestAPI<Request, CoreOptions, RequiredUriUrl> {
|
export interface RequestAsync extends RequestAPI<Request, CoreOptions, RequiredUriUrl> {
|
||||||
getAsync(uri: string, options?: RequiredUriUrl): Promise<RequestResponse>;
|
getAsync(uri: string, options?: RequiredUriUrl): BluebirdPromise<RequestResponse>;
|
||||||
getAsync(uri: string): Promise<RequestResponse>;
|
getAsync(uri: string): BluebirdPromise<RequestResponse>;
|
||||||
getAsync(options: RequiredUriUrl & CoreOptions): Promise<RequestResponse>;
|
getAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise<RequestResponse>;
|
||||||
|
|
||||||
postAsync(uri: string, options?: CoreOptions): Promise<RequestResponse>;
|
postAsync(uri: string, options?: CoreOptions): BluebirdPromise<RequestResponse>;
|
||||||
postAsync(uri: string): Promise<RequestResponse>;
|
postAsync(uri: string): BluebirdPromise<RequestResponse>;
|
||||||
postAsync(options: RequiredUriUrl & CoreOptions): Promise<RequestResponse>;
|
postAsync(options: RequiredUriUrl & CoreOptions): BluebirdPromise<RequestResponse>;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,13 +3,14 @@ import AuthenticationRegulator from "../../src/lib/AuthenticationRegulator";
|
||||||
import UserDataStore from "../../src/lib/UserDataStore";
|
import UserDataStore from "../../src/lib/UserDataStore";
|
||||||
import MockDate = require("mockdate");
|
import MockDate = require("mockdate");
|
||||||
import exceptions = require("../../src/lib/Exceptions");
|
import exceptions = require("../../src/lib/Exceptions");
|
||||||
|
import nedb = require("nedb");
|
||||||
|
|
||||||
describe("test authentication regulator", function() {
|
describe("test authentication regulator", function() {
|
||||||
it("should mark 2 authentication and regulate (resolve)", function() {
|
it("should mark 2 authentication and regulate (resolve)", function() {
|
||||||
const options = {
|
const options = {
|
||||||
inMemoryOnly: true
|
inMemoryOnly: true
|
||||||
};
|
};
|
||||||
const data_store = new UserDataStore(options);
|
const data_store = new UserDataStore(options, nedb);
|
||||||
const regulator = new AuthenticationRegulator(data_store, 10);
|
const regulator = new AuthenticationRegulator(data_store, 10);
|
||||||
const user = "user";
|
const user = "user";
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ describe("test authentication regulator", function() {
|
||||||
const options = {
|
const options = {
|
||||||
inMemoryOnly: true
|
inMemoryOnly: true
|
||||||
};
|
};
|
||||||
const data_store = new UserDataStore(options);
|
const data_store = new UserDataStore(options, nedb);
|
||||||
const regulator = new AuthenticationRegulator(data_store, 10);
|
const regulator = new AuthenticationRegulator(data_store, 10);
|
||||||
const user = "user";
|
const user = "user";
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@ describe("test authentication regulator", function() {
|
||||||
const options = {
|
const options = {
|
||||||
inMemoryOnly: true
|
inMemoryOnly: true
|
||||||
};
|
};
|
||||||
const data_store = new UserDataStore(options);
|
const data_store = new UserDataStore(options, nedb);
|
||||||
const regulator = new AuthenticationRegulator(data_store, 10);
|
const regulator = new AuthenticationRegulator(data_store, 10);
|
||||||
const user = "user";
|
const user = "user";
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,12 @@ describe("test TOTP validation", function() {
|
||||||
return totpValidator.validate(token, totp_secret);
|
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 totp_secret = "NBD2ZV64R9UV1O7K";
|
||||||
const token = "wrong token";
|
const token = "wrong token";
|
||||||
return totpValidator.validate(token, totp_secret)
|
totpValidator.validate(token, totp_secret)
|
||||||
.catch(function() {
|
.catch(function() {
|
||||||
return Promise.resolve();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -2,7 +2,7 @@
|
||||||
import UserDataStore from "../../src/lib/UserDataStore";
|
import UserDataStore from "../../src/lib/UserDataStore";
|
||||||
import { U2FMetaDocument, Options } from "../../src/lib/UserDataStore";
|
import { U2FMetaDocument, Options } from "../../src/lib/UserDataStore";
|
||||||
|
|
||||||
import DataStore = require("nedb");
|
import nedb = require("nedb");
|
||||||
import assert = require("assert");
|
import assert = require("assert");
|
||||||
import Promise = require("bluebird");
|
import Promise = require("bluebird");
|
||||||
import sinon = require("sinon");
|
import sinon = require("sinon");
|
||||||
|
@ -20,7 +20,7 @@ describe("test user data store", () => {
|
||||||
|
|
||||||
describe("test u2f meta", () => {
|
describe("test u2f meta", () => {
|
||||||
it("should save a u2f meta", function () {
|
it("should save a u2f meta", function () {
|
||||||
const data_store = new UserDataStore(options);
|
const data_store = new UserDataStore(options, nedb);
|
||||||
|
|
||||||
const userid = "user";
|
const userid = "user";
|
||||||
const app_id = "https://localhost";
|
const app_id = "https://localhost";
|
||||||
|
@ -40,7 +40,7 @@ describe("test user data store", () => {
|
||||||
inMemoryOnly: true
|
inMemoryOnly: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const data_store = new UserDataStore(options);
|
const data_store = new UserDataStore(options, nedb);
|
||||||
|
|
||||||
const userid = "user";
|
const userid = "user";
|
||||||
const app_id = "https://localhost";
|
const app_id = "https://localhost";
|
||||||
|
@ -60,7 +60,7 @@ describe("test user data store", () => {
|
||||||
inMemoryOnly: true
|
inMemoryOnly: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const data_store = new UserDataStore(options);
|
const data_store = new UserDataStore(options, nedb);
|
||||||
|
|
||||||
const userid = "user";
|
const userid = "user";
|
||||||
const app_id = "https://localhost";
|
const app_id = "https://localhost";
|
||||||
|
@ -86,7 +86,7 @@ describe("test user data store", () => {
|
||||||
|
|
||||||
describe("test u2f registration token", () => {
|
describe("test u2f registration token", () => {
|
||||||
it("should save u2f registration token", function () {
|
it("should save u2f registration token", function () {
|
||||||
const data_store = new UserDataStore(options);
|
const data_store = new UserDataStore(options, nedb);
|
||||||
|
|
||||||
const userid = "user";
|
const userid = "user";
|
||||||
const token = "token";
|
const token = "token";
|
||||||
|
@ -109,7 +109,7 @@ describe("test user data store", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should save u2f registration token and consume it", function (done) {
|
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 userid = "user";
|
||||||
const token = "token";
|
const token = "token";
|
||||||
|
@ -128,7 +128,7 @@ describe("test user data store", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not be able to consume registration token twice", function (done) {
|
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 userid = "user";
|
||||||
const token = "token";
|
const token = "token";
|
||||||
|
@ -148,7 +148,7 @@ describe("test user data store", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail when token does not exist", function () {
|
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";
|
const token = "token";
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ describe("test user data store", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail when token expired", function (done) {
|
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 userid = "user";
|
||||||
const token = "token";
|
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) {
|
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 userid = "user";
|
||||||
const token = "token";
|
const token = "token";
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
|
||||||
import sinon = require("sinon");
|
import sinon = require("sinon");
|
||||||
|
|
||||||
export = function () {
|
export interface AccessControllerMock {
|
||||||
|
isDomainAllowedForUser: sinon.SinonStub;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AccessControllerMock() {
|
||||||
return {
|
return {
|
||||||
isDomainAllowedForUser: sinon.stub()
|
isDomainAllowedForUser: sinon.stub()
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
|
|
||||||
import sinon = require("sinon");
|
import sinon = require("sinon");
|
||||||
|
|
||||||
export = function () {
|
|
||||||
|
export interface AuthenticationRegulatorMock {
|
||||||
|
mark: sinon.SinonStub;
|
||||||
|
regulate: sinon.SinonStub;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AuthenticationRegulatorMock() {
|
||||||
return {
|
return {
|
||||||
mark: sinon.stub(),
|
mark: sinon.stub(),
|
||||||
regulate: 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;
|
get_u2f_meta: sinon.SinonStub;
|
||||||
issue_identity_check_token: sinon.SinonStub;
|
issue_identity_check_token: sinon.SinonStub;
|
||||||
consume_identity_check_token: sinon.SinonStub;
|
consume_identity_check_token: sinon.SinonStub;
|
||||||
|
get_totp_secret: sinon.SinonStub;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserDataStore(): UserDataStore {
|
export function UserDataStore(): UserDataStore {
|
||||||
|
@ -13,6 +14,7 @@ export function UserDataStore(): UserDataStore {
|
||||||
set_u2f_meta: sinon.stub(),
|
set_u2f_meta: sinon.stub(),
|
||||||
get_u2f_meta: sinon.stub(),
|
get_u2f_meta: sinon.stub(),
|
||||||
issue_identity_check_token: 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");
|
import ExpressMock = require("../mocks/express");
|
||||||
|
|
||||||
describe("test the first factor validation route", function() {
|
describe("test the first factor validation route", function() {
|
||||||
let req: any;
|
let req: ExpressMock.RequestMock;
|
||||||
let res: any;
|
let res: ExpressMock.ResponseMock;
|
||||||
let emails: string[];
|
let emails: string[];
|
||||||
let groups: string[];
|
let groups: string[];
|
||||||
let configuration;
|
let configuration;
|
||||||
let ldapMock: LdapClientMock;
|
let ldapMock: LdapClientMock;
|
||||||
let regulator: any;
|
let regulator: AuthenticationRegulatorMock.AuthenticationRegulatorMock;
|
||||||
let accessController: any;
|
let accessController: AccessControllerMock.AccessControllerMock;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
configuration = {
|
configuration = {
|
||||||
|
@ -34,10 +34,10 @@ describe("test the first factor validation route", function() {
|
||||||
|
|
||||||
ldapMock = LdapClientMock();
|
ldapMock = LdapClientMock();
|
||||||
|
|
||||||
accessController = AccessControllerMock();
|
accessController = AccessControllerMock.AccessControllerMock();
|
||||||
accessController.isDomainAllowedForUser.returns(true);
|
accessController.isDomainAllowedForUser.returns(true);
|
||||||
|
|
||||||
regulator = AuthenticationRegulatorMock();
|
regulator = AuthenticationRegulatorMock.AuthenticationRegulatorMock();
|
||||||
regulator.regulate.returns(BluebirdPromise.resolve());
|
regulator.regulate.returns(BluebirdPromise.resolve());
|
||||||
regulator.mark.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.bind.withArgs("username").returns(BluebirdPromise.resolve());
|
||||||
ldapMock.get_emails.returns(BluebirdPromise.resolve(emails));
|
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) {
|
it("should retrieve email from LDAP", function(done) {
|
||||||
res.send = sinon.spy(function() { done(); });
|
res.send = sinon.spy(function() { done(); });
|
||||||
ldapMock.bind.returns(BluebirdPromise.resolve());
|
ldapMock.bind.returns(BluebirdPromise.resolve());
|
||||||
ldapMock.get_emails = sinon.stub().withArgs("usernam").returns(BluebirdPromise.resolve([{mail: ["test@example.com"] }]));
|
ldapMock.get_emails = sinon.stub().withArgs("username").returns(BluebirdPromise.resolve([{mail: ["test@example.com"] }]));
|
||||||
FirstFactor(req, res);
|
FirstFactor(req as any, res as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set email as session variables", function() {
|
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" ];
|
const emails = [ "test_ok@example.com" ];
|
||||||
ldapMock.bind.returns(BluebirdPromise.resolve());
|
ldapMock.bind.returns(BluebirdPromise.resolve());
|
||||||
ldapMock.get_emails.returns(BluebirdPromise.resolve(emails));
|
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");
|
assert.equal(regulator.mark.getCall(0).args[0], "username");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
ldapMock.bind.throws(new exceptions.LdapBindError("Bad credentials"));
|
ldapMock.bind.returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials")));
|
||||||
FirstFactor(req, res);
|
FirstFactor(req as any, res as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return status code 500 when LDAP search throws", function(done) {
|
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();
|
done();
|
||||||
});
|
});
|
||||||
ldapMock.bind.returns(BluebirdPromise.resolve());
|
ldapMock.bind.returns(BluebirdPromise.resolve());
|
||||||
ldapMock.get_emails.throws(new exceptions.LdapSeachError("error while retrieving emails"));
|
ldapMock.get_emails.returns(BluebirdPromise.reject(new exceptions.LdapSeachError("error while retrieving emails")));
|
||||||
FirstFactor(req, res);
|
FirstFactor(req as any, res as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return status code 403 when regulator rejects authentication", function(done) {
|
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.bind.returns(BluebirdPromise.resolve());
|
||||||
ldapMock.get_emails.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 sinon from "sinon";
|
||||||
import * as MockDate from "mockdate";
|
import * as MockDate from "mockdate";
|
||||||
import UserDataStore from "../../../src/lib/UserDataStore";
|
import UserDataStore from "../../../src/lib/UserDataStore";
|
||||||
|
import nedb = require("nedb");
|
||||||
|
|
||||||
describe("test user data store", function() {
|
describe("test user data store", function() {
|
||||||
describe("test authentication traces", test_authentication_traces);
|
describe("test authentication traces", test_authentication_traces);
|
||||||
|
@ -15,7 +16,7 @@ function test_authentication_traces() {
|
||||||
inMemoryOnly: true
|
inMemoryOnly: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const data_store = new UserDataStore(options);
|
const data_store = new UserDataStore(options, nedb);
|
||||||
const userid = "user";
|
const userid = "user";
|
||||||
const type = "1stfactor";
|
const type = "1stfactor";
|
||||||
const is_success = false;
|
const is_success = false;
|
||||||
|
@ -34,7 +35,7 @@ function test_authentication_traces() {
|
||||||
inMemoryOnly: true
|
inMemoryOnly: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const data_store = new UserDataStore(options);
|
const data_store = new UserDataStore(options, nedb);
|
||||||
const userid = "user";
|
const userid = "user";
|
||||||
const type = "1stfactor";
|
const type = "1stfactor";
|
||||||
const is_success = false;
|
const is_success = false;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as Promise from "bluebird";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
import * as MockDate from "mockdate";
|
import * as MockDate from "mockdate";
|
||||||
import UserDataStore from "../../../src/lib/UserDataStore";
|
import UserDataStore from "../../../src/lib/UserDataStore";
|
||||||
|
import nedb = require("nedb");
|
||||||
|
|
||||||
describe("test user data store", function() {
|
describe("test user data store", function() {
|
||||||
describe("test totp secrets store", test_totp_secrets);
|
describe("test totp secrets store", test_totp_secrets);
|
||||||
|
@ -15,7 +16,7 @@ function test_totp_secrets() {
|
||||||
inMemoryOnly: true
|
inMemoryOnly: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const data_store = new UserDataStore(options);
|
const data_store = new UserDataStore(options, nedb);
|
||||||
const userid = "user";
|
const userid = "user";
|
||||||
const secret = {
|
const secret = {
|
||||||
ascii: "abc",
|
ascii: "abc",
|
||||||
|
@ -41,7 +42,7 @@ function test_totp_secrets() {
|
||||||
inMemoryOnly: true
|
inMemoryOnly: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const data_store = new UserDataStore(options);
|
const data_store = new UserDataStore(options, nedb);
|
||||||
const userid = "user";
|
const userid = "user";
|
||||||
const secret1 = {
|
const secret1 = {
|
||||||
ascii: "abc",
|
ascii: "abc",
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"*": [
|
"*": [
|
||||||
"node_modules/@types/*",
|
"src/types/*",
|
||||||
"src/types/*"
|
"node_modules/@types/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue