Add Mongo as scalable and resilient storage backend

pull/61/head
Clement Michaud 2017-07-19 21:06:12 +02:00
parent b0e3038aa6
commit e45ac39c8f
113 changed files with 2056 additions and 1200 deletions

View File

@ -77,9 +77,13 @@ session:
host: redis
port: 6379
# The directory where the DB files will be saved
store_directory: /var/lib/authelia/store
storage:
# The directory where the DB files will be saved
# local: /var/lib/authelia/store
# Settings to connect to mongo server
mongo:
url: mongodb://mongo/authelia
# Notifications are sent to users when they require a password reset, a u2f
# registration or a TOTP registration.

View File

@ -0,0 +1,6 @@
version: '2'
services:
mongo:
image: mongo:3.4
networks:
- example-network

View File

@ -1,6 +1,6 @@
version: '2'
services:
redis:
image: redis
image: redis:4.0-alpine
networks:
- example-network

View File

@ -24,7 +24,6 @@
"title": "Authelia API documentation"
},
"dependencies": {
"@types/cors": "^2.8.1",
"bluebird": "^3.4.7",
"body-parser": "^1.15.2",
"connect-redis": "^3.3.0",
@ -33,6 +32,7 @@
"express": "^4.14.0",
"express-session": "^1.14.2",
"ldapjs": "^1.0.1",
"mongodb": "^2.2.30",
"nedb": "^1.8.0",
"nodemailer": "^4.0.1",
"object-path": "^0.11.3",
@ -47,6 +47,7 @@
"@types/bluebird": "^3.5.4",
"@types/body-parser": "^1.16.3",
"@types/connect-redis": "0.0.6",
"@types/cors": "^2.8.1",
"@types/ejs": "^2.3.33",
"@types/express": "^4.0.35",
"@types/express-session": "0.0.32",
@ -55,6 +56,7 @@
"@types/ldapjs": "^1.0.0",
"@types/mocha": "^2.2.41",
"@types/mockdate": "^2.0.0",
"@types/mongodb": "^2.2.7",
"@types/nedb": "^1.8.3",
"@types/nodemailer": "^1.3.32",
"@types/object-path": "^0.9.28",
@ -89,7 +91,7 @@
"query-string": "^4.3.4",
"request": "^2.81.0",
"should": "^11.1.1",
"sinon": "^1.17.6",
"sinon": "^2.3.8",
"sinon-promise": "^0.1.3",
"tmp": "0.0.31",
"ts-node": "^3.0.4",
@ -107,7 +109,8 @@
"doc",
"src/types",
"dist",
"test"
"test",
"src/**/*.d.ts"
],
"extension": [
".ts"

View File

@ -2,4 +2,12 @@
set -e
docker-compose -f docker-compose.base.yml -f docker-compose.yml -f docker-compose.dev.yml -f example/redis/docker-compose.yml -f example/nginx/docker-compose.yml -f example/ldap/docker-compose.yml -f test/integration/docker-compose.yml $*
docker-compose \
-f docker-compose.base.yml \
-f docker-compose.yml \
-f docker-compose.dev.yml \
-f example/mongo/docker-compose.yml \
-f example/redis/docker-compose.yml \
-f example/nginx/docker-compose.yml \
-f example/ldap/docker-compose.yml \
-f test/integration/docker-compose.yml $*

View File

@ -2,4 +2,11 @@
set -e
docker-compose -f docker-compose.base.yml -f docker-compose.yml -f example/redis/docker-compose.yml -f example/nginx/docker-compose.yml -f example/ldap/docker-compose.yml -f test/integration/docker-compose.yml $*
docker-compose \
-f docker-compose.base.yml \
-f docker-compose.yml \
-f example/mongo/docker-compose.yml \
-f example/redis/docker-compose.yml \
-f example/nginx/docker-compose.yml \
-f example/ldap/docker-compose.yml \
-f test/integration/docker-compose.yml $*

View File

@ -3,4 +3,4 @@
DC_SCRIPT=./scripts/example/dc-example.sh
$DC_SCRIPT build
$DC_SCRIPT up -d redis openldap authelia nginx
$DC_SCRIPT up -d mongo redis openldap authelia nginx

View File

@ -1,9 +1,10 @@
#!/bin/bash
DC_SCRIPT=./scripts/example/dc-example.sh
EXPECTED_SERVICES_COUNT=6
start_services() {
$DC_SCRIPT up -d redis openldap authelia nginx nginx-tests
$DC_SCRIPT up -d mongo redis openldap authelia nginx nginx-tests
sleep 3
}
@ -44,7 +45,7 @@ run_integration_tests() {
$DC_SCRIPT logs authelia
echo "Check number of services"
expect_services_count 5
expect_services_count $EXPECTED_SERVICES_COUNT
echo "Run integration tests..."
$DC_SCRIPT run --rm integration-tests
@ -56,7 +57,7 @@ run_integration_tests() {
run_system_tests() {
echo "Start services..."
start_services
expect_services_count 5
expect_services_count $EXPECTED_SERVICES_COUNT
./node_modules/.bin/mocha --compilers ts:ts-node/register --recursive test/system
shut_services
@ -67,7 +68,7 @@ run_other_tests() {
npm install --only=dev
./node_modules/.bin/grunt build-dist
./scripts/example/deploy-example.sh
expect_services_count 4
expect_services_count 5
}

View File

@ -1,39 +1,36 @@
import * as BluebirdPromise from "bluebird";
import exceptions = require("./Exceptions");
import { UserDataStore } from "./storage/UserDataStore";
import {AuthenticationTraceDocument} from "./storage/AuthenticationTraceDocument";
const REGULATION_TRACE_TYPE = "regulation";
const MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE = 3;
interface DatedDocument {
date: Date;
}
export class AuthenticationRegulator {
private _user_data_store: any;
private _lock_time_in_seconds: number;
private userDataStore: UserDataStore;
private lockTimeInSeconds: number;
constructor(user_data_store: any, lock_time_in_seconds: number) {
this._user_data_store = user_data_store;
this._lock_time_in_seconds = lock_time_in_seconds;
constructor(userDataStore: any, lockTimeInSeconds: number) {
this.userDataStore = userDataStore;
this.lockTimeInSeconds = lockTimeInSeconds;
}
// Mark authentication
mark(userid: string, is_success: boolean): BluebirdPromise<void> {
return this._user_data_store.save_authentication_trace(userid, REGULATION_TRACE_TYPE, is_success);
mark(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
return this.userDataStore.saveAuthenticationTrace(userId, isAuthenticationSuccessful);
}
regulate(userid: string): BluebirdPromise<void> {
return this._user_data_store.get_last_authentication_traces(userid, REGULATION_TRACE_TYPE, false, 3)
.then((docs: Array<DatedDocument>) => {
regulate(userId: string): BluebirdPromise<void> {
return this.userDataStore.retrieveLatestAuthenticationTraces(userId, false, 3)
.then((docs: AuthenticationTraceDocument[]) => {
if (docs.length < MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE) {
// less than the max authorized number of authentication in time range, thus authorizing access
return BluebirdPromise.resolve();
}
const oldest_doc = docs[MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE - 1];
const no_lock_min_date = new Date(new Date().getTime() - this._lock_time_in_seconds * 1000);
if (oldest_doc.date > no_lock_min_date) {
const oldestDocument = docs[MAX_AUTHENTICATION_COUNT_IN_TIME_RANGE - 1];
const noLockMinDate = new Date(new Date().getTime() - this.lockTimeInSeconds * 1000);
if (oldestDocument.date > noLockMinDate) {
throw new exceptions.AuthenticationRegulationError("Max number of authentication. Please retry in few minutes.");
}

View File

@ -6,9 +6,9 @@ import Exceptions = require("./Exceptions");
import AuthenticationSession = require("./AuthenticationSession");
export function validate(req: express.Request): BluebirdPromise<void> {
const authSession = AuthenticationSession.get(req);
if (!authSession.userid || !authSession.first_factor)
return BluebirdPromise.reject(new Exceptions.FirstFactorValidationError("First factor has not been validated yet."));
const authSession = AuthenticationSession.get(req);
if (!authSession.userid || !authSession.first_factor)
return BluebirdPromise.reject(new Exceptions.FirstFactorValidationError("First factor has not been validated yet."));
return BluebirdPromise.resolve();
return BluebirdPromise.resolve();
}

View File

@ -6,15 +6,15 @@ import util = require("util");
import Exceptions = require("./Exceptions");
import fs = require("fs");
import ejs = require("ejs");
import UserDataStore from "./UserDataStore";
import { IUserDataStore } from "./storage/IUserDataStore";
import { Winston } from "../../types/Dependencies";
import express = require("express");
import ErrorReplies = require("./ErrorReplies");
import ServerVariables = require("./ServerVariables");
import { ServerVariablesHandler } from "./ServerVariablesHandler";
import AuthenticationSession = require("./AuthenticationSession");
import Identity = require("../../types/Identity");
import { IdentityValidationRequestContent } from "./UserDataStore";
import { IdentityValidationDocument } from "./storage/IdentityValidationDocument";
const filePath = __dirname + "/../resources/email-template.ejs";
const email_template = fs.readFileSync(filePath, "utf8");
@ -33,21 +33,21 @@ export interface IdentityValidable {
mailSubject(): string;
}
function issue_token(userid: string, content: Object, userDataStore: UserDataStore, logger: Winston): BluebirdPromise<string> {
function createAndSaveToken(userid: string, challenge: string, userDataStore: IUserDataStore, logger: Winston): BluebirdPromise<string> {
const five_minutes = 4 * 60 * 1000;
const token = randomstring.generate({ length: 64 });
const that = this;
logger.debug("identity_check: issue identity token %s for 5 minutes", token);
return userDataStore.issue_identity_check_token(userid, token, content, five_minutes)
return userDataStore.produceIdentityValidationToken(userid, token, challenge, five_minutes)
.then(function () {
return BluebirdPromise.resolve(token);
});
}
function consume_token(token: string, userDataStore: UserDataStore, logger: Winston): BluebirdPromise<IdentityValidationRequestContent> {
function consumeToken(token: string, challenge: string, userDataStore: IUserDataStore, logger: Winston): BluebirdPromise<IdentityValidationDocument> {
logger.debug("identity_check: consume token %s", token);
return userDataStore.consume_identity_check_token(token);
return userDataStore.consumeIdentityValidationToken(token, challenge);
}
export function register(app: express.Application, pre_validation_endpoint: string, post_validation_endpoint: string, handler: IdentityValidable) {
@ -63,8 +63,8 @@ function checkIdentityToken(req: express.Request, identityToken: string): Bluebi
export function get_finish_validation(handler: IdentityValidable): express.RequestHandler {
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariables.getLogger(req.app);
const userDataStore = ServerVariables.getUserDataStore(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
const authSession = AuthenticationSession.get(req);
const identityToken = objectPath.get<express.Request, string>(req, "query.identity_token");
@ -75,12 +75,12 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
return handler.postValidationInit(req);
})
.then(function () {
return consume_token(identityToken, userDataStore, logger);
return consumeToken(identityToken, handler.challenge(), userDataStore, logger);
})
.then(function (content: IdentityValidationRequestContent) {
.then(function (doc: IdentityValidationDocument) {
authSession.identity_check = {
challenge: handler.challenge(),
userid: content.userid
userid: doc.userId
};
handler.postValidationResponse(req, res);
return BluebirdPromise.resolve();
@ -94,9 +94,9 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
export function get_start_validation(handler: IdentityValidable, postValidationEndpoint: string): express.RequestHandler {
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariables.getLogger(req.app);
const notifier = ServerVariables.getNotifier(req.app);
const userDataStore = ServerVariables.getUserDataStore(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const notifier = ServerVariablesHandler.getNotifier(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
let identity: Identity.Identity;
logger.info("Identity Validation: Start identity validation");
@ -104,13 +104,13 @@ export function get_start_validation(handler: IdentityValidable, postValidationE
.then(function (id: Identity.Identity) {
logger.debug("Identity Validation: retrieved identity is %s", JSON.stringify(id));
identity = id;
const email_address = objectPath.get<Identity.Identity, string>(identity, "email");
const userid = objectPath.get<Identity.Identity, string>(identity, "userid");
const email = identity.email;
const userid = identity.userid;
if (!(email_address && userid))
if (!(email && userid))
return BluebirdPromise.reject(new Exceptions.IdentityError("Missing user id or email address"));
return issue_token(userid, undefined, userDataStore, logger);
return createAndSaveToken(userid, handler.challenge(), userDataStore, logger);
})
.then(function (token: string) {
const host = req.get("Host");

View File

@ -1,6 +1,6 @@
import express = require("express");
import UserDataStore from "./UserDataStore";
import { UserDataStore } from "./storage/UserDataStore";
import { Winston } from "../../types/Dependencies";
import FirstFactorGet = require("./routes/firstfactor/get");
@ -34,7 +34,7 @@ import Error404Get = require("./routes/error/404/get");
import Endpoints = require("../endpoints");
export default class RestApi {
export class RestApi {
static setup(app: express.Application): void {
app.get(Endpoints.FIRST_FACTOR_GET, FirstFactorGet.default);
app.get(Endpoints.SECOND_FACTOR_GET, SecondFactorGet.default);

View File

@ -1,43 +1,54 @@
import BluebirdPromise = require("bluebird");
import { AccessController } from "./access_control/AccessController";
import { UserConfiguration } from "./../../types/Configuration";
import { AppConfiguration, UserConfiguration } from "./configuration/Configuration";
import { GlobalDependencies } from "../../types/Dependencies";
import { AuthenticationRegulator } from "./AuthenticationRegulator";
import UserDataStore from "./UserDataStore";
import ConfigurationAdapter from "./ConfigurationAdapter";
import { UserDataStore } from "./storage/UserDataStore";
import { ConfigurationAdapter } from "./configuration/ConfigurationAdapter";
import { TOTPValidator } from "./TOTPValidator";
import { TOTPGenerator } from "./TOTPGenerator";
import RestApi from "./RestApi";
import { RestApi } from "./RestApi";
import { Client } from "./ldap/Client";
import BluebirdPromise = require("bluebird");
import ServerVariables = require("./ServerVariables");
import SessionConfigurationBuilder from "./SessionConfigurationBuilder";
import { ServerVariablesHandler } from "./ServerVariablesHandler";
import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder";
import * as Express from "express";
import * as BodyParser from "body-parser";
import * as Path from "path";
import * as http from "http";
// Constants
const TRUST_PROXY = "trust proxy";
const VIEWS = "views";
const VIEW_ENGINE = "view engine";
const PUG = "pug";
export default class Server {
private httpServer: http.Server;
start(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
const config = ConfigurationAdapter.adapt(yamlConfiguration);
private setupExpressApplication(config: AppConfiguration, app: Express.Application, deps: GlobalDependencies): void {
const viewsDirectory = Path.resolve(__dirname, "../views");
const publicHtmlDirectory = Path.resolve(__dirname, "../public_html");
const expressSessionOptions = SessionConfigurationBuilder.build(config, deps);
const app = Express();
app.use(Express.static(publicHtmlDirectory));
app.use(BodyParser.urlencoded({ extended: false }));
app.use(BodyParser.json());
app.use(deps.session(expressSessionOptions));
app.set("trust proxy", 1);
app.set("views", viewsDirectory);
app.set("view engine", "pug");
app.set(TRUST_PROXY, 1);
app.set(VIEWS, viewsDirectory);
app.set(VIEW_ENGINE, PUG);
RestApi.setup(app);
}
private transformConfiguration(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): AppConfiguration {
const config = ConfigurationAdapter.adapt(yamlConfiguration);
// by default the level of logs is info
deps.winston.level = config.logs_level;
@ -45,18 +56,33 @@ export default class Server {
deps.winston.debug("Content of YAML configuration file is %s", JSON.stringify(yamlConfiguration, undefined, 2));
deps.winston.debug("Authelia configuration is %s", JSON.stringify(config, undefined, 2));
return config;
}
ServerVariables.fill(app, config, deps);
RestApi.setup(app);
private setup(config: AppConfiguration, app: Express.Application, deps: GlobalDependencies): BluebirdPromise<void> {
this.setupExpressApplication(config, app, deps);
return ServerVariablesHandler.initialize(app, config, deps);
}
private startServer(app: Express.Application, port: number) {
return new BluebirdPromise<void>((resolve, reject) => {
this.httpServer = app.listen(config.port, function (err: string) {
console.log("Listening on %d...", config.port);
this.httpServer = app.listen(port, function (err: string) {
console.log("Listening on %d...", port);
resolve();
});
});
}
start(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
const that = this;
const app = Express();
const config = this.transformConfiguration(yamlConfiguration, deps);
return this.setup(config, app, deps)
.then(function () {
return that.startServer(app, config.port);
});
}
stop() {
this.httpServer.close();
}

View File

@ -1,120 +0,0 @@
import winston = require("winston");
import { Authenticator } from "./ldap/Authenticator";
import { PasswordUpdater } from "./ldap/PasswordUpdater";
import { EmailsRetriever } from "./ldap/EmailsRetriever";
import { TOTPValidator } from "./TOTPValidator";
import { TOTPGenerator } from "./TOTPGenerator";
import U2F = require("u2f");
import UserDataStore from "./UserDataStore";
import { INotifier } from "./notifiers/INotifier";
import { AuthenticationRegulator } from "./AuthenticationRegulator";
import Configuration = require("../../types/Configuration");
import { AccessController } from "./access_control/AccessController";
import { NotifierFactory } from "./notifiers/NotifierFactory";
import { GlobalDependencies } from "../../types/Dependencies";
import express = require("express");
export const VARIABLES_KEY = "authelia-variables";
export interface ServerVariables {
logger: typeof winston;
ldapAuthenticator: Authenticator;
ldapPasswordUpdater: PasswordUpdater;
ldapEmailsRetriever: EmailsRetriever;
totpValidator: TOTPValidator;
totpGenerator: TOTPGenerator;
u2f: typeof U2F;
userDataStore: UserDataStore;
notifier: INotifier;
regulator: AuthenticationRegulator;
config: Configuration.AppConfiguration;
accessController: AccessController;
}
export function fill(app: express.Application, config: Configuration.AppConfiguration, deps: GlobalDependencies) {
const five_minutes = 5 * 60;
const datastore_options = {
directory: config.store_directory,
inMemory: config.store_in_memory
};
const userDataStore = new UserDataStore(datastore_options, deps.nedb);
const regulator = new AuthenticationRegulator(userDataStore, five_minutes);
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
const ldapAuthenticator = new Authenticator(config.ldap, deps.ldapjs, deps.winston);
const ldapPasswordUpdater = new PasswordUpdater(config.ldap, deps.ldapjs, deps.dovehash, deps.winston);
const ldapEmailsRetriever = new EmailsRetriever(config.ldap, deps.ldapjs, deps.winston);
const accessController = new AccessController(config.access_control, deps.winston);
const totpValidator = new TOTPValidator(deps.speakeasy);
const totpGenerator = new TOTPGenerator(deps.speakeasy);
const variables: ServerVariables = {
accessController: accessController,
config: config,
ldapAuthenticator: ldapAuthenticator,
ldapPasswordUpdater: ldapPasswordUpdater,
ldapEmailsRetriever: ldapEmailsRetriever,
logger: deps.winston,
notifier: notifier,
regulator: regulator,
totpGenerator: totpGenerator,
totpValidator: totpValidator,
u2f: deps.u2f,
userDataStore: userDataStore
};
app.set(VARIABLES_KEY, variables);
}
export function getLogger(app: express.Application): typeof winston {
return (app.get(VARIABLES_KEY) as ServerVariables).logger;
}
export function getUserDataStore(app: express.Application): UserDataStore {
return (app.get(VARIABLES_KEY) as ServerVariables).userDataStore;
}
export function getNotifier(app: express.Application): INotifier {
return (app.get(VARIABLES_KEY) as ServerVariables).notifier;
}
export function getLdapAuthenticator(app: express.Application): Authenticator {
return (app.get(VARIABLES_KEY) as ServerVariables).ldapAuthenticator;
}
export function getLdapPasswordUpdater(app: express.Application): PasswordUpdater {
return (app.get(VARIABLES_KEY) as ServerVariables).ldapPasswordUpdater;
}
export function getLdapEmailsRetriever(app: express.Application): EmailsRetriever {
return (app.get(VARIABLES_KEY) as ServerVariables).ldapEmailsRetriever;
}
export function getConfiguration(app: express.Application): Configuration.AppConfiguration {
return (app.get(VARIABLES_KEY) as ServerVariables).config;
}
export function getAuthenticationRegulator(app: express.Application): AuthenticationRegulator {
return (app.get(VARIABLES_KEY) as ServerVariables).regulator;
}
export function getAccessController(app: express.Application): AccessController {
return (app.get(VARIABLES_KEY) as ServerVariables).accessController;
}
export function getTOTPGenerator(app: express.Application): TOTPGenerator {
return (app.get(VARIABLES_KEY) as ServerVariables).totpGenerator;
}
export function getTOTPValidator(app: express.Application): TOTPValidator {
return (app.get(VARIABLES_KEY) as ServerVariables).totpValidator;
}
export function getU2F(app: express.Application): typeof U2F {
return (app.get(VARIABLES_KEY) as ServerVariables).u2f;
}

View File

@ -0,0 +1,151 @@
import winston = require("winston");
import BluebirdPromise = require("bluebird");
import { Authenticator } from "./ldap/Authenticator";
import { PasswordUpdater } from "./ldap/PasswordUpdater";
import { EmailsRetriever } from "./ldap/EmailsRetriever";
import { TOTPValidator } from "./TOTPValidator";
import { TOTPGenerator } from "./TOTPGenerator";
import U2F = require("u2f");
import { IUserDataStore } from "./storage/IUserDataStore";
import { UserDataStore } from "./storage/UserDataStore";
import { INotifier } from "./notifiers/INotifier";
import { AuthenticationRegulator } from "./AuthenticationRegulator";
import Configuration = require("./configuration/Configuration");
import { AccessController } from "./access_control/AccessController";
import { NotifierFactory } from "./notifiers/NotifierFactory";
import { CollectionFactoryFactory } from "./storage/CollectionFactoryFactory";
import { ICollectionFactory } from "./storage/ICollectionFactory";
import { MongoCollectionFactory } from "./storage/mongo/MongoCollectionFactory";
import { MongoConnectorFactory } from "./connectors/mongo/MongoConnectorFactory";
import { IMongoClient } from "./connectors/mongo/IMongoClient";
import { GlobalDependencies } from "../../types/Dependencies";
import express = require("express");
export const VARIABLES_KEY = "authelia-variables";
export interface ServerVariables {
logger: typeof winston;
ldapAuthenticator: Authenticator;
ldapPasswordUpdater: PasswordUpdater;
ldapEmailsRetriever: EmailsRetriever;
totpValidator: TOTPValidator;
totpGenerator: TOTPGenerator;
u2f: typeof U2F;
userDataStore: IUserDataStore;
notifier: INotifier;
regulator: AuthenticationRegulator;
config: Configuration.AppConfiguration;
accessController: AccessController;
}
class UserDataStoreFactory {
static create(config: Configuration.AppConfiguration): BluebirdPromise<UserDataStore> {
if (config.storage.local) {
const nedbOptions = {
directory: config.storage.local.path,
inMemory: config.storage.local.in_memory
};
const collectionFactory = CollectionFactoryFactory.createNedb(nedbOptions);
return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
}
else if (config.storage.mongo) {
const mongoConnectorFactory = new MongoConnectorFactory();
const mongoConnector = mongoConnectorFactory.create(config.storage.mongo.url);
return mongoConnector.connect()
.then(function (client: IMongoClient) {
const collectionFactory = CollectionFactoryFactory.createMongo(client);
return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
});
}
return BluebirdPromise.reject(new Error("Storage backend incorrectly configured."));
}
}
export class ServerVariablesHandler {
static initialize(app: express.Application, config: Configuration.AppConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
const five_minutes = 5 * 60;
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
const ldapAuthenticator = new Authenticator(config.ldap, deps.ldapjs, deps.winston);
const ldapPasswordUpdater = new PasswordUpdater(config.ldap, deps.ldapjs, deps.dovehash, deps.winston);
const ldapEmailsRetriever = new EmailsRetriever(config.ldap, deps.ldapjs, deps.winston);
const accessController = new AccessController(config.access_control, deps.winston);
const totpValidator = new TOTPValidator(deps.speakeasy);
const totpGenerator = new TOTPGenerator(deps.speakeasy);
return UserDataStoreFactory.create(config)
.then(function (userDataStore: UserDataStore) {
const regulator = new AuthenticationRegulator(userDataStore, five_minutes);
const variables: ServerVariables = {
accessController: accessController,
config: config,
ldapAuthenticator: ldapAuthenticator,
ldapPasswordUpdater: ldapPasswordUpdater,
ldapEmailsRetriever: ldapEmailsRetriever,
logger: deps.winston,
notifier: notifier,
regulator: regulator,
totpGenerator: totpGenerator,
totpValidator: totpValidator,
u2f: deps.u2f,
userDataStore: userDataStore
};
app.set(VARIABLES_KEY, variables);
});
}
static getLogger(app: express.Application): typeof winston {
return (app.get(VARIABLES_KEY) as ServerVariables).logger;
}
static getUserDataStore(app: express.Application): IUserDataStore {
return (app.get(VARIABLES_KEY) as ServerVariables).userDataStore;
}
static getNotifier(app: express.Application): INotifier {
return (app.get(VARIABLES_KEY) as ServerVariables).notifier;
}
static getLdapAuthenticator(app: express.Application): Authenticator {
return (app.get(VARIABLES_KEY) as ServerVariables).ldapAuthenticator;
}
static getLdapPasswordUpdater(app: express.Application): PasswordUpdater {
return (app.get(VARIABLES_KEY) as ServerVariables).ldapPasswordUpdater;
}
static getLdapEmailsRetriever(app: express.Application): EmailsRetriever {
return (app.get(VARIABLES_KEY) as ServerVariables).ldapEmailsRetriever;
}
static getConfiguration(app: express.Application): Configuration.AppConfiguration {
return (app.get(VARIABLES_KEY) as ServerVariables).config;
}
static getAuthenticationRegulator(app: express.Application): AuthenticationRegulator {
return (app.get(VARIABLES_KEY) as ServerVariables).regulator;
}
static getAccessController(app: express.Application): AccessController {
return (app.get(VARIABLES_KEY) as ServerVariables).accessController;
}
static getTOTPGenerator(app: express.Application): TOTPGenerator {
return (app.get(VARIABLES_KEY) as ServerVariables).totpGenerator;
}
static getTOTPValidator(app: express.Application): TOTPValidator {
return (app.get(VARIABLES_KEY) as ServerVariables).totpValidator;
}
static getU2F(app: express.Application): typeof U2F {
return (app.get(VARIABLES_KEY) as ServerVariables).u2f;
}
}

View File

@ -1,37 +0,0 @@
import ExpressSession = require("express-session");
import { AppConfiguration } from "../../types/Configuration";
import { GlobalDependencies } from "../../types/Dependencies";
export default class SessionConfigurationBuilder {
static build(configuration: AppConfiguration, deps: GlobalDependencies): ExpressSession.SessionOptions {
const sessionOptions: ExpressSession.SessionOptions = {
secret: configuration.session.secret,
resave: false,
saveUninitialized: true,
cookie: {
secure: false,
maxAge: configuration.session.expiration,
domain: configuration.session.domain
},
};
if (configuration.session.redis) {
let redisOptions;
if (configuration.session.redis.host
&& configuration.session.redis.port) {
redisOptions = {
host: configuration.session.redis.host,
port: configuration.session.redis.port
};
}
if (redisOptions) {
const RedisStore = deps.ConnectRedis(deps.session);
sessionOptions.store = new RedisStore(redisOptions);
}
}
return sessionOptions;
}
}

View File

@ -1,190 +0,0 @@
import * as BluebirdPromise from "bluebird";
import * as path from "path";
import { NedbAsync } from "nedb";
import { TOTPSecret } from "../../types/TOTPSecret";
import { Nedb } from "../../types/Dependencies";
import u2f = require("u2f");
// Constants
const U2F_META_COLLECTION_NAME = "u2f_meta";
const IDENTITY_CHECK_TOKENS_COLLECTION_NAME = "identity_check_tokens";
const AUTHENTICATION_TRACES_COLLECTION_NAME = "authentication_traces";
const TOTP_SECRETS_COLLECTION_NAME = "totp_secrets";
export interface TOTPSecretDocument {
userid: string;
secret: TOTPSecret;
}
export interface U2FRegistrationDocument {
keyHandle: string;
publicKey: string;
userId: string;
appId: string;
}
export interface Options {
inMemoryOnly?: boolean;
directory?: string;
}
export interface IdentityValidationRequestContent {
userid: string;
data: string;
}
export interface IdentityValidationRequestDocument {
userid: string;
token: string;
content: IdentityValidationRequestContent;
max_date: Date;
}
interface U2FRegistrationFilter {
userId: string;
appId: string;
}
// Source
export default class UserDataStore {
private _u2f_meta_collection: NedbAsync;
private _identity_check_tokens_collection: NedbAsync;
private _authentication_traces_collection: NedbAsync;
private _totp_secret_collection: NedbAsync;
private nedb: Nedb;
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 =
this.create_collection(IDENTITY_CHECK_TOKENS_COLLECTION_NAME, options);
this._authentication_traces_collection =
this.create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options);
this._totp_secret_collection =
this.create_collection(TOTP_SECRETS_COLLECTION_NAME, options);
}
set_u2f_meta(userId: string, appId: string, keyHandle: string, publicKey: string): BluebirdPromise<any> {
const newDocument: U2FRegistrationDocument = {
userId: userId,
appId: appId,
keyHandle: keyHandle,
publicKey: publicKey
};
const filter: U2FRegistrationFilter = {
userId: userId,
appId: appId
};
return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true });
}
get_u2f_meta(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument> {
const filter: U2FRegistrationFilter = {
userId: userId,
appId: appId
};
return this._u2f_meta_collection.findOneAsync(filter);
}
save_authentication_trace(userid: string, type: string, is_success: boolean) {
const newDocument = {
userid: userid,
date: new Date(),
is_success: is_success,
type: type
};
return this._authentication_traces_collection.insertAsync(newDocument);
}
get_last_authentication_traces(userid: string, type: string, is_success: boolean, count: number): BluebirdPromise<any> {
const q = {
userid: userid,
type: type,
is_success: is_success
};
const query = this._authentication_traces_collection.find(q)
.sort({ date: -1 }).limit(count);
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): BluebirdPromise<any> {
const newDocument = {
userid: userid,
token: token,
content: {
userid: userid,
data: data
},
max_date: new Date(new Date().getTime() + max_age)
};
return this._identity_check_tokens_collection.insertAsync(newDocument);
}
consume_identity_check_token(token: string): BluebirdPromise<IdentityValidationRequestContent> {
const query = {
token: token
};
return this._identity_check_tokens_collection.findOneAsync(query)
.then(function (doc) {
if (!doc) {
return BluebirdPromise.reject(new Error("Registration token does not exist"));
}
const max_date = doc.max_date;
const current_date = new Date();
if (current_date > max_date)
return BluebirdPromise.reject(new Error("Registration token is not valid anymore"));
return BluebirdPromise.resolve(doc.content);
})
.then((content) => {
return BluebirdPromise.join(this._identity_check_tokens_collection.removeAsync(query),
BluebirdPromise.resolve(content));
})
.then((v) => {
return BluebirdPromise.resolve(v[1]);
});
}
set_totp_secret(userid: string, secret: TOTPSecret): BluebirdPromise<any> {
const doc = {
userid: userid,
secret: secret
};
const query = {
userid: userid
};
return this._totp_secret_collection.updateAsync(query, doc, { upsert: true });
}
get_totp_secret(userid: string): BluebirdPromise<TOTPSecretDocument> {
const query = {
userid: userid
};
return this._totp_secret_collection.findOneAsync(query);
}
private create_collection(name: string, options: any): NedbAsync {
const datastore_options = {
inMemoryOnly: options.inMemoryOnly || false,
autoload: true,
filename: ""
};
if (options.directory)
datastore_options.filename = path.resolve(options.directory, name);
return BluebirdPromise.promisifyAll(new this.nedb(datastore_options)) as NedbAsync;
}
}

View File

@ -1,5 +1,5 @@
import { ACLConfiguration } from "../../../types/Configuration";
import { ACLConfiguration } from "../configuration/Configuration";
import PatternBuilder from "./PatternBuilder";
import { Winston } from "../../../types/Dependencies";

View File

@ -1,6 +1,6 @@
import { Winston } from "../../../types/Dependencies";
import { ACLConfiguration, ACLGroupsRules, ACLUsersRules, ACLDefaultRules } from "../../../types/Configuration";
import { ACLConfiguration, ACLGroupsRules, ACLUsersRules, ACLDefaultRules } from "../configuration/Configuration";
import objectPath = require("object-path");
export default class AccessControlPatternBuilder {

View File

@ -50,12 +50,26 @@ export interface NotifierConfiguration {
filesystem?: FileSystemNotifierConfiguration;
}
export interface MongoStorageConfiguration {
url: string;
}
export interface LocalStorageConfiguration {
path?: string;
in_memory?: boolean;
}
export interface StorageConfiguration {
local?: LocalStorageConfiguration;
mongo?: MongoStorageConfiguration;
}
export interface UserConfiguration {
port?: number;
logs_level?: string;
ldap: LdapConfiguration;
session: SessionCookieConfiguration;
store_directory?: string;
storage: StorageConfiguration;
notifier: NotifierConfiguration;
access_control?: ACLConfiguration;
}
@ -65,8 +79,7 @@ export interface AppConfiguration {
logs_level: string;
ldap: LdapConfiguration;
session: SessionCookieConfiguration;
store_in_memory?: boolean;
store_directory?: string;
storage: StorageConfiguration;
notifier: NotifierConfiguration;
access_control?: ACLConfiguration;
}

View File

@ -1,6 +1,10 @@
import * as ObjectPath from "object-path";
import { AppConfiguration, UserConfiguration, NotifierConfiguration, ACLConfiguration, LdapConfiguration, SessionRedisOptions } from "./../../types/Configuration";
import {
AppConfiguration, UserConfiguration, NotifierConfiguration,
ACLConfiguration, LdapConfiguration, SessionRedisOptions,
MongoStorageConfiguration, LocalStorageConfiguration
} from "./Configuration";
const LDAP_URL_ENV_VARIABLE = "LDAP_URL";
@ -34,14 +38,17 @@ function adaptFromUserConfiguration(userConfiguration: UserConfiguration): AppCo
expiration: get_optional<number>(userConfiguration, "session.expiration", 3600000), // in ms
redis: ObjectPath.get<object, SessionRedisOptions>(userConfiguration, "session.redis")
},
store_directory: get_optional<string>(userConfiguration, "store_directory", undefined),
storage: {
local: get_optional<LocalStorageConfiguration>(userConfiguration, "storage.local", undefined),
mongo: get_optional<MongoStorageConfiguration>(userConfiguration, "storage.mongo", undefined)
},
logs_level: get_optional<string>(userConfiguration, "logs_level", "info"),
notifier: ObjectPath.get<object, NotifierConfiguration>(userConfiguration, "notifier"),
access_control: ObjectPath.get<object, ACLConfiguration>(userConfiguration, "access_control")
};
}
export default class ConfigurationAdapter {
export class ConfigurationAdapter {
static adapt(userConfiguration: UserConfiguration): AppConfiguration {
const appConfiguration = adaptFromUserConfiguration(userConfiguration);

View File

@ -0,0 +1,37 @@
import ExpressSession = require("express-session");
import { AppConfiguration } from "./Configuration";
import { GlobalDependencies } from "../../../types/Dependencies";
export class SessionConfigurationBuilder {
static build(configuration: AppConfiguration, deps: GlobalDependencies): ExpressSession.SessionOptions {
const sessionOptions: ExpressSession.SessionOptions = {
secret: configuration.session.secret,
resave: false,
saveUninitialized: true,
cookie: {
secure: false,
maxAge: configuration.session.expiration,
domain: configuration.session.domain
},
};
if (configuration.session.redis) {
let redisOptions;
if (configuration.session.redis.host
&& configuration.session.redis.port) {
redisOptions = {
host: configuration.session.redis.host,
port: configuration.session.redis.port
};
}
if (redisOptions) {
const RedisStore = deps.ConnectRedis(deps.session);
sessionOptions.store = new RedisStore(redisOptions);
}
}
return sessionOptions;
}
}

View File

@ -0,0 +1,5 @@
import MongoDB = require("mongodb");
export interface IMongoClient {
collection(name: string): MongoDB.Collection;
}

View File

@ -0,0 +1,6 @@
import BluebirdPromise = require("bluebird");
import { IMongoClient } from "./IMongoClient";
export interface IMongoConnector {
connect(): BluebirdPromise<IMongoClient>;
}

View File

@ -0,0 +1,5 @@
import { IMongoConnector } from "./IMongoConnector";
export interface IMongoConnectorFactory {
create(url: string): IMongoConnector;
}

View File

@ -0,0 +1,15 @@
import MongoDB = require("mongodb");
import { IMongoClient } from "./IMongoClient";
export class MongoClient implements IMongoClient {
private db: MongoDB.Db;
constructor(db: MongoDB.Db) {
this.db = db;
}
collection(name: string): MongoDB.Collection {
return this.db.collection(name);
}
}

View File

@ -0,0 +1,22 @@
import MongoDB = require("mongodb");
import BluebirdPromise = require("bluebird");
import { IMongoClient } from "./IMongoClient";
import { IMongoConnector } from "./IMongoConnector";
import { MongoClient } from "./MongoClient";
export class MongoConnector implements IMongoConnector {
private url: string;
constructor(url: string) {
this.url = url;
}
connect(): BluebirdPromise<IMongoClient> {
const connectAsync = BluebirdPromise.promisify(MongoDB.MongoClient.connect);
return connectAsync(this.url)
.then(function (db: MongoDB.Db) {
return BluebirdPromise.resolve(new MongoClient(db));
});
}
}

View File

@ -0,0 +1,12 @@
import BluebirdPromise = require("bluebird");
import { IMongoConnectorFactory } from "./IMongoConnectorFactory";
import { IMongoConnector } from "./IMongoConnector";
import { MongoConnector } from "./MongoConnector";
import MongoDB = require("mongodb");
export class MongoConnectorFactory implements IMongoConnectorFactory {
create(url: string): IMongoConnector {
return new MongoConnector(url);
}
}

View File

@ -4,7 +4,7 @@ import ldapjs = require("ldapjs");
import { Client, Attributes } from "./Client";
import { buildUserDN } from "./common";
import { LdapConfiguration } from "./../../../types/Configuration";
import { LdapConfiguration } from "../configuration/Configuration";
import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies";

View File

@ -6,7 +6,7 @@ import ldapjs = require("ldapjs");
import { buildUserDN } from "./common";
import { EventEmitter } from "events";
import { LdapConfiguration } from "./../../../types/Configuration";
import { LdapConfiguration } from "../configuration/Configuration";
import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies";
interface SearchEntry {

View File

@ -4,7 +4,7 @@ import ldapjs = require("ldapjs");
import { Client } from "./Client";
import { buildUserDN } from "./common";
import { LdapConfiguration } from "./../../../types/Configuration";
import { LdapConfiguration } from "../configuration/Configuration";
import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies";

View File

@ -4,7 +4,7 @@ import ldapjs = require("ldapjs");
import { Client } from "./Client";
import { buildUserDN } from "./common";
import { LdapConfiguration } from "./../../../types/Configuration";
import { LdapConfiguration } from "../configuration/Configuration";
import { Winston, Ldapjs, Dovehash } from "../../../types/Dependencies";

View File

@ -1,6 +1,6 @@
import util = require("util");
import { LdapConfiguration } from "./../../../types/Configuration";
import { LdapConfiguration } from "../configuration/Configuration";
export function buildUserDN(username: string, options: LdapConfiguration): string {

View File

@ -5,13 +5,12 @@ import * as fs from "fs";
import { INotifier } from "./INotifier";
import { Identity } from "../../../types/Identity";
import { FileSystemNotifierConfiguration } from "../../../types/Configuration";
import { FileSystemNotifierConfiguration } from "../configuration/Configuration";
export class FileSystemNotifier extends INotifier {
export class FileSystemNotifier implements INotifier {
private filename: string;
constructor(options: FileSystemNotifierConfiguration) {
super();
this.filename = options.filename;
}

View File

@ -7,16 +7,15 @@ import nodemailer = require("nodemailer");
import { Nodemailer } from "../../../types/Dependencies";
import { Identity } from "../../../types/Identity";
import { INotifier } from "../notifiers/INotifier";
import { GmailNotifierConfiguration } from "../../../types/Configuration";
import { GmailNotifierConfiguration } from "../configuration/Configuration";
import path = require("path");
const email_template = fs.readFileSync(path.join(__dirname, "../../resources/email-template.ejs"), "UTF-8");
export class GMailNotifier extends INotifier {
export class GMailNotifier implements INotifier {
private transporter: any;
constructor(options: GmailNotifierConfiguration, nodemailer: Nodemailer) {
super();
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {

View File

@ -0,0 +1,7 @@
import * as BluebirdPromise from "bluebird";
import { Identity } from "../../../types/Identity";
export interface INotifier {
notify(identity: Identity, subject: string, link: string): BluebirdPromise<void>;
}

View File

@ -1,7 +0,0 @@
import * as BluebirdPromise from "bluebird";
import { Identity } from "../../../types/Identity";
export abstract class INotifier {
abstract notify(identity: Identity, subject: string, link: string): BluebirdPromise<void>;
}

View File

@ -1,5 +1,5 @@
import { NotifierConfiguration } from "../../../types/Configuration";
import { NotifierConfiguration } from "../configuration/Configuration";
import { Nodemailer } from "../../../types/Dependencies";
import { INotifier } from "./INotifier";

View File

@ -5,14 +5,14 @@ import FirstFactorValidator = require("../FirstFactorValidator");
import Exceptions = require("../Exceptions");
import ErrorReplies = require("../ErrorReplies");
import objectPath = require("object-path");
import ServerVariables = require("../ServerVariables");
import { ServerVariablesHandler } from "../ServerVariablesHandler";
import AuthenticationSession = require("../AuthenticationSession");
type Handler = (req: express.Request, res: express.Response) => BluebirdPromise<void>;
export default function (callback: Handler): Handler {
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariables.getLogger(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const authSession = AuthenticationSession.get(req);
logger.debug("AuthSession is %s", JSON.stringify(authSession));

View File

@ -1,7 +1,8 @@
import BluebirdPromise = require("bluebird");
import express = require("express");
export default function (req: express.Request, res: express.Response) {
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
res.render("errors/401");
return BluebirdPromise.resolve();
}

View File

@ -1,7 +1,8 @@
import BluebirdPromise = require("bluebird");
import express = require("express");
export default function (req: express.Request, res: express.Response) {
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
res.render("errors/403");
return BluebirdPromise.resolve();
}

View File

@ -1,7 +1,8 @@
import BluebirdPromise = require("bluebird");
import express = require("express");
export default function (req: express.Request, res: express.Response) {
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
res.render("errors/404");
return BluebirdPromise.resolve();
}

View File

@ -4,10 +4,10 @@ import objectPath = require("object-path");
import winston = require("winston");
import Endpoints = require("../../../endpoints");
import AuthenticationValidator = require("../../AuthenticationValidator");
import ServerVariables = require("../../ServerVariables");
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
export default function (req: express.Request, res: express.Response) {
const logger = ServerVariables.getLogger(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
logger.debug("First factor: headers are %s", JSON.stringify(req.headers));

View File

@ -8,16 +8,16 @@ import { AuthenticationRegulator } from "../../AuthenticationRegulator";
import { Client, Attributes } from "../../ldap/Client";
import Endpoint = require("../../../endpoints");
import ErrorReplies = require("../../ErrorReplies");
import ServerVariables = require("../../ServerVariables");
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
import AuthenticationSession = require("../../AuthenticationSession");
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const username: string = req.body.username;
const password: string = req.body.password;
const logger = ServerVariables.getLogger(req.app);
const ldap = ServerVariables.getLdapAuthenticator(req.app);
const config = ServerVariables.getConfiguration(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const ldap = ServerVariablesHandler.getLdapAuthenticator(req.app);
const config = ServerVariablesHandler.getConfiguration(req.app);
if (!username || !password) {
const err = new Error("No username or password");
@ -25,8 +25,8 @@ export default function (req: express.Request, res: express.Response): BluebirdP
return BluebirdPromise.reject(err);
}
const regulator = ServerVariables.getAuthenticationRegulator(req.app);
const accessController = ServerVariables.getAccessController(req.app);
const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app);
const accessController = ServerVariablesHandler.getAccessController(req.app);
const authSession = AuthenticationSession.get(req);
logger.info("1st factor: Starting authentication of user \"%s\"", username);

View File

@ -3,15 +3,15 @@ import express = require("express");
import BluebirdPromise = require("bluebird");
import objectPath = require("object-path");
import exceptions = require("../../../Exceptions");
import ServerVariables = require("../../../ServerVariables");
import { ServerVariablesHandler } from "../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../AuthenticationSession");
import ErrorReplies = require("../../../ErrorReplies");
import Constants = require("./../constants");
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariables.getLogger(req.app);
const ldapPasswordUpdater = ServerVariables.getLdapPasswordUpdater(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const ldapPasswordUpdater = ServerVariablesHandler.getLdapPasswordUpdater(req.app);
const authSession = AuthenticationSession.get(req);
const newPassword = objectPath.get<express.Request, string>(req, "body.password");

View File

@ -8,7 +8,7 @@ import { IdentityValidable } from "../../../IdentityCheckMiddleware";
import { PRE_VALIDATION_TEMPLATE } from "../../../IdentityCheckPreValidationTemplate";
import Constants = require("../constants");
import { Winston } from "winston";
import ServerVariables = require("../../../ServerVariables");
import { ServerVariablesHandler } from "../../../ServerVariablesHandler";
export const TEMPLATE_NAME = "password-reset-form";
@ -18,14 +18,14 @@ export default class PasswordResetHandler implements IdentityValidable {
}
preValidationInit(req: express.Request): BluebirdPromise<Identity> {
const logger = ServerVariables.getLogger(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const userid: string = objectPath.get<express.Request, string>(req, "query.userid");
logger.debug("Reset Password: user '%s' requested a password reset", userid);
if (!userid)
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No user id provided"));
const emailsRetriever = ServerVariables.getLdapEmailsRetriever(req.app);
const emailsRetriever = ServerVariablesHandler.getLdapEmailsRetriever(req.app);
return emailsRetriever.retrieve(userid)
.then(function (emails: string[]) {
if (!emails && emails.length <= 0) throw new Error("No email found");

View File

@ -3,7 +3,7 @@ import express = require("express");
import objectPath = require("object-path");
import winston = require("winston");
import Endpoints = require("../../../endpoints");
import ServerVariables = require("../../ServerVariables");
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
import AuthenticationSession = require("../../AuthenticationSession");
export default function (req: express.Request, res: express.Response) {

View File

@ -9,7 +9,7 @@ import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationT
import Constants = require("../constants");
import Endpoints = require("../../../../../endpoints");
import ErrorReplies = require("../../../../ErrorReplies");
import ServerVariables = require("../../../../ServerVariables");
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession");
import FirstFactorValidator = require("../../../../FirstFactorValidator");
@ -53,7 +53,7 @@ export default class RegistrationHandler implements IdentityValidable {
}
postValidationResponse(req: express.Request, res: express.Response) {
const logger = ServerVariables.getLogger(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const authSession = AuthenticationSession.get(req);
const userid = authSession.identity_check.userid;
@ -65,12 +65,12 @@ export default class RegistrationHandler implements IdentityValidable {
return;
}
const userDataStore = ServerVariables.getUserDataStore(req.app);
const totpGenerator = ServerVariables.getTOTPGenerator(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
const totpGenerator = ServerVariablesHandler.getTOTPGenerator(req.app);
const secret = totpGenerator.generate();
logger.debug("POST new-totp-secret: save the TOTP secret in DB");
userDataStore.set_totp_secret(userid, secret)
userDataStore.saveTOTPSecret(userid, secret)
.then(function () {
objectPath.set(req, "session", undefined);

View File

@ -2,13 +2,13 @@
import exceptions = require("../../../../Exceptions");
import objectPath = require("object-path");
import express = require("express");
import { TOTPSecretDocument } from "../../../../UserDataStore";
import { TOTPSecretDocument } from "../../../../storage/TOTPSecretDocument";
import BluebirdPromise = require("bluebird");
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import Endpoints = require("../../../../../endpoints");
import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies");
import ServerVariables = require("./../../../../ServerVariables");
import { ServerVariablesHandler } from "./../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession");
const UNAUTHORIZED_MESSAGE = "Unauthorized access";
@ -16,17 +16,17 @@ const UNAUTHORIZED_MESSAGE = "Unauthorized access";
export default FirstFactorBlocker(handler);
export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariables.getLogger(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const authSession = AuthenticationSession.get(req);
const userid = authSession.userid;
logger.info("POST 2ndfactor totp: Initiate TOTP validation for user %s", userid);
const token = req.body.token;
const totpValidator = ServerVariables.getTOTPValidator(req.app);
const userDataStore = ServerVariables.getUserDataStore(req.app);
const totpValidator = ServerVariablesHandler.getTOTPValidator(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
logger.debug("POST 2ndfactor totp: Fetching secret for user %s", userid);
return userDataStore.get_totp_secret(userid)
return userDataStore.retrieveTOTPSecret(userid)
.then(function (doc: TOTPSecretDocument) {
logger.debug("POST 2ndfactor totp: TOTP secret is %s", JSON.stringify(doc));
return totpValidator.validate(token, doc.secret.base32);

View File

@ -1,15 +1,16 @@
import UserDataStore from "../../../../UserDataStore";
import { UserDataStore } from "../../../../storage/UserDataStore";
import objectPath = require("object-path");
import u2f_common = require("../U2FCommon");
import BluebirdPromise = require("bluebird");
import express = require("express");
import U2f = require("u2f");
import { U2FRegistration } from "../../../../../../types/U2FRegistration";
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies");
import ServerVariables = require("../../../../ServerVariables");
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession");
@ -34,11 +35,11 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
}
const userDataStore = ServerVariables.getUserDataStore(req.app);
const u2f = ServerVariables.getU2F(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
const u2f = ServerVariablesHandler.getU2F(req.app);
const userid = authSession.userid;
const appid = u2f_common.extract_app_id(req);
const logger = ServerVariables.getLogger(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const registrationResponse: U2f.RegistrationData = req.body;
@ -54,7 +55,11 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
const registrationResult: U2f.RegistrationResult = u2fResult as U2f.RegistrationResult;
logger.info("U2F register: Store regisutration and reply");
logger.debug("U2F register: registration = %s", JSON.stringify(registrationResult));
return userDataStore.set_u2f_meta(userid, appid, registrationResult.keyHandle, registrationResult.publicKey);
const registration: U2FRegistration = {
keyHandle: registrationResult.keyHandle,
publicKey: registrationResult.publicKey
};
return userDataStore.saveU2FRegistration(userid, appid, registration);
})
.then(function () {
authSession.identity_check = undefined;

View File

@ -1,5 +1,5 @@
import UserDataStore from "../../../../UserDataStore";
import { UserDataStore } from "../../../../storage/UserDataStore";
import objectPath = require("object-path");
import u2f_common = require("../U2FCommon");
@ -8,13 +8,13 @@ import express = require("express");
import U2f = require("u2f");
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import ErrorReplies = require("../../../../ErrorReplies");
import ServerVariables = require("../../../../ServerVariables");
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession");
export default FirstFactorBlocker(handler);
function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariables.getLogger(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const authSession = AuthenticationSession.get(req);
if (!authSession.identity_check
@ -24,7 +24,7 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
return;
}
const u2f = ServerVariables.getU2F(req.app);
const u2f = ServerVariablesHandler.getU2F(req.app);
const appid: string = u2f_common.extract_app_id(req);
logger.debug("U2F register_request: headers=%s", JSON.stringify(req.headers));

View File

@ -3,49 +3,49 @@ import objectPath = require("object-path");
import u2f_common = require("../U2FCommon");
import BluebirdPromise = require("bluebird");
import express = require("express");
import UserDataStore, { U2FRegistrationDocument } from "../../../../UserDataStore";
import { UserDataStore } from "../../../../storage/UserDataStore";
import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument";
import { Winston } from "../../../../../../types/Dependencies";
import U2f = require("u2f");
import exceptions = require("../../../../Exceptions");
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies");
import ServerVariables = require("../../../../ServerVariables");
import {  ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession");
export default FirstFactorBlocker(handler);
export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariables.getLogger(req.app);
const userDataStore = ServerVariables.getUserDataStore(req.app);
const authSession = AuthenticationSession.get(req);
const logger = ServerVariablesHandler.getLogger(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
const authSession = AuthenticationSession.get(req);
if (!authSession.sign_request) {
const err = new Error("No sign request");
ErrorReplies.replyWithError401(res, logger)(err);
return BluebirdPromise.reject(err);
}
if (!authSession.sign_request) {
const err = new Error("No sign request");
ErrorReplies.replyWithError401(res, logger)(err);
return BluebirdPromise.reject(err);
}
const userid = authSession.userid;
const appid = u2f_common.extract_app_id(req);
return userDataStore.get_u2f_meta(userid, appid)
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<U2f.SignatureResult | U2f.Error> {
const appid = u2f_common.extract_app_id(req);
const u2f = ServerVariables.getU2F(req.app);
const signRequest = authSession.sign_request;
const signData: U2f.SignatureData = req.body;
logger.info("U2F sign: Finish authentication");
return BluebirdPromise.resolve(u2f.checkSignature(signRequest, signData, doc.publicKey));
})
.then(function (result: U2f.SignatureResult | U2f.Error): BluebirdPromise<void> {
if (objectPath.has(result, "errorCode"))
return BluebirdPromise.reject(new Error("Error while signing"));
logger.info("U2F sign: Authentication successful");
authSession.second_factor = true;
redirect(req, res);
return BluebirdPromise.resolve();
})
.catch(ErrorReplies.replyWithError500(res, logger));
const userid = authSession.userid;
const appid = u2f_common.extract_app_id(req);
return userDataStore.retrieveU2FRegistration(userid, appid)
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<U2f.SignatureResult | U2f.Error> {
const appId = u2f_common.extract_app_id(req);
const u2f = ServerVariablesHandler.getU2F(req.app);
const signRequest = authSession.sign_request;
const signData: U2f.SignatureData = req.body;
logger.info("U2F sign: Finish authentication");
return BluebirdPromise.resolve(u2f.checkSignature(signRequest, signData, doc.registration.publicKey));
})
.then(function (result: U2f.SignatureResult | U2f.Error): BluebirdPromise<void> {
if (objectPath.has(result, "errorCode"))
return BluebirdPromise.reject(new Error("Error while signing"));
logger.info("U2F sign: Authentication successful");
authSession.second_factor = true;
redirect(req, res);
return BluebirdPromise.resolve();
})
.catch(ErrorReplies.replyWithError500(res, logger));
}

View File

@ -4,39 +4,39 @@ import U2f = require("u2f");
import u2f_common = require("../../../secondfactor/u2f/U2FCommon");
import BluebirdPromise = require("bluebird");
import express = require("express");
import UserDataStore, { U2FRegistrationDocument } from "../../../../UserDataStore";
import { UserDataStore } from "../../../../storage/UserDataStore";
import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument";
import { Winston } from "../../../../../../types/Dependencies";
import exceptions = require("../../../../Exceptions");
import { SignMessage } from "./SignMessage";
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import ErrorReplies = require("../../../../ErrorReplies");
import ServerVariables = require("../../../../ServerVariables");
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession");
export default FirstFactorBlocker(handler);
export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariables.getLogger(req.app);
const userDataStore = ServerVariables.getUserDataStore(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
const authSession = AuthenticationSession.get(req);
const userid = authSession.userid;
const appid = u2f_common.extract_app_id(req);
return userDataStore.get_u2f_meta(userid, appid)
const userId = authSession.userid;
const appId = u2f_common.extract_app_id(req);
return userDataStore.retrieveU2FRegistration(userId, appId)
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<SignMessage> {
if (!doc)
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No U2F registration found"));
const u2f = ServerVariables.getU2F(req.app);
const u2f = ServerVariablesHandler.getU2F(req.app);
const appId: string = u2f_common.extract_app_id(req);
logger.info("U2F sign_request: Start authentication to app %s", appId);
logger.debug("U2F sign_request: appId=%s, keyHandle=%s", appId, JSON.stringify(doc.keyHandle));
logger.debug("U2F sign_request: appId=%s, keyHandle=%s", appId, JSON.stringify(doc.registration.keyHandle));
const request = u2f.request(appId, doc.keyHandle);
const request = u2f.request(appId, doc.registration.keyHandle);
const authenticationMessage: SignMessage = {
request: request,
keyHandle: doc.keyHandle
keyHandle: doc.registration.keyHandle
};
return BluebirdPromise.resolve(authenticationMessage);
})

View File

@ -8,12 +8,12 @@ import exceptions = require("../../Exceptions");
import winston = require("winston");
import AuthenticationValidator = require("../../AuthenticationValidator");
import ErrorReplies = require("../../ErrorReplies");
import ServerVariables = require("../../ServerVariables");
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
import AuthenticationSession = require("../../AuthenticationSession");
function verify_filter(req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariables.getLogger(req.app);
const accessController = ServerVariables.getAccessController(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
const accessController = ServerVariablesHandler.getAccessController(req.app);
const authSession = AuthenticationSession.get(req);
logger.debug("Verify: headers are %s", JSON.stringify(req.headers));
@ -40,7 +40,7 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
}
export default function (req: express.Request, res: express.Response) {
const logger = ServerVariables.getLogger(req.app);
const logger = ServerVariablesHandler.getLogger(req.app);
verify_filter(req, res)
.then(function () {
res.status(204);

View File

@ -0,0 +1,6 @@
export interface AuthenticationTraceDocument {
userId: string;
date: Date;
isAuthenticationSuccessful: boolean;
}

View File

@ -0,0 +1,15 @@
import { ICollectionFactory } from "./ICollectionFactory";
import { NedbCollectionFactory, NedbOptions } from "./nedb/NedbCollectionFactory";
import { MongoCollectionFactory } from "./mongo/MongoCollectionFactory";
import { IMongoClient } from "../connectors/mongo/IMongoClient";
export class CollectionFactoryFactory {
static createNedb(options: NedbOptions): ICollectionFactory {
return new NedbCollectionFactory(options);
}
static createMongo(client: IMongoClient): ICollectionFactory {
return new MongoCollectionFactory(client);
}
}

View File

@ -0,0 +1,11 @@
/* istanbul ignore next */
import BluebirdPromise = require("bluebird");
/* istanbul ignore next */
export interface ICollection {
find(query: any, sortKeys: any, count: number): BluebirdPromise<any>;
findOne(query: any): BluebirdPromise<any>;
update(query: any, updateQuery: any, options?: any): BluebirdPromise<any>;
remove(query: any): BluebirdPromise<any>;
insert(document: any): BluebirdPromise<any>;
}

View File

@ -0,0 +1,6 @@
import { ICollection } from "./ICollection";
export interface ICollectionFactory {
build(collectionName: string): ICollection;
}

View File

@ -0,0 +1,21 @@
import BluebirdPromise = require("bluebird");
import { TOTPSecretDocument } from "./TOTPSecretDocument";
import { U2FRegistrationDocument } from "./U2FRegistrationDocument";
import { U2FRegistration } from "../../../types/U2FRegistration";
import { TOTPSecret } from "../../../types/TOTPSecret";
import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
import { IdentityValidationDocument } from "./IdentityValidationDocument";
export interface IUserDataStore {
saveU2FRegistration(userId: string, appId: string, registration: U2FRegistration): BluebirdPromise<void>;
retrieveU2FRegistration(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument>;
saveAuthenticationTrace(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void>;
retrieveLatestAuthenticationTraces(userId: string, isAuthenticationSuccessful: boolean, count: number): BluebirdPromise<AuthenticationTraceDocument[]>;
produceIdentityValidationToken(userId: string, token: string, challenge: string, maxAge: number): BluebirdPromise<any>;
consumeIdentityValidationToken(token: string, challenge: string): BluebirdPromise<IdentityValidationDocument>;
saveTOTPSecret(userId: string, secret: TOTPSecret): BluebirdPromise<void>;
retrieveTOTPSecret(userId: string): BluebirdPromise<TOTPSecretDocument>;
}

View File

@ -0,0 +1,7 @@
export interface IdentityValidationDocument {
userId: string;
token: string;
challenge: string;
maxDate: Date;
}

View File

@ -0,0 +1,6 @@
import { TOTPSecret } from "../../../types/TOTPSecret";
export interface TOTPSecretDocument {
userid: string;
secret: TOTPSecret;
}

View File

@ -0,0 +1,8 @@
import { U2FRegistration } from "../../../types/U2FRegistration";
export interface U2FRegistrationDocument {
userId: string;
appId: string;
registration: U2FRegistration;
}

View File

@ -0,0 +1,144 @@
import * as BluebirdPromise from "bluebird";
import * as path from "path";
import { IUserDataStore } from "./IUserDataStore";
import { ICollection } from "./ICollection";
import { ICollectionFactory } from "./ICollectionFactory";
import { TOTPSecretDocument } from "./TOTPSecretDocument";
import { U2FRegistrationDocument } from "./U2FRegistrationDocument";
import { U2FRegistration } from "../../../types/U2FRegistration";
import { TOTPSecret } from "../../../types/TOTPSecret";
import { AuthenticationTraceDocument } from "./AuthenticationTraceDocument";
import { IdentityValidationDocument } from "./IdentityValidationDocument";
// Constants
const IDENTITY_VALIDATION_TOKENS_COLLECTION_NAME = "identity_validation_tokens";
const AUTHENTICATION_TRACES_COLLECTION_NAME = "authentication_traces";
const U2F_REGISTRATIONS_COLLECTION_NAME = "u2f_registrations";
const TOTP_SECRETS_COLLECTION_NAME = "totp_secrets";
export interface U2FRegistrationKey {
userId: string;
appId: string;
}
// Source
export class UserDataStore implements IUserDataStore {
private u2fSecretCollection: ICollection;
private identityCheckTokensCollection: ICollection;
private authenticationTracesCollection: ICollection;
private totpSecretCollection: ICollection;
private collectionFactory: ICollectionFactory;
constructor(collectionFactory: ICollectionFactory) {
this.collectionFactory = collectionFactory;
this.u2fSecretCollection = this.collectionFactory.build(U2F_REGISTRATIONS_COLLECTION_NAME);
this.identityCheckTokensCollection = this.collectionFactory.build(IDENTITY_VALIDATION_TOKENS_COLLECTION_NAME);
this.authenticationTracesCollection = this.collectionFactory.build(AUTHENTICATION_TRACES_COLLECTION_NAME);
this.totpSecretCollection = this.collectionFactory.build(TOTP_SECRETS_COLLECTION_NAME);
}
saveU2FRegistration(userId: string, appId: string, registration: U2FRegistration): BluebirdPromise<void> {
const newDocument: U2FRegistrationDocument = {
userId: userId,
appId: appId,
registration: registration
};
const filter: U2FRegistrationKey = {
userId: userId,
appId: appId
};
return this.u2fSecretCollection.update(filter, newDocument, { upsert: true });
}
retrieveU2FRegistration(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument> {
const filter: U2FRegistrationKey = {
userId: userId,
appId: appId
};
return this.u2fSecretCollection.findOne(filter);
}
saveAuthenticationTrace(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
const newDocument: AuthenticationTraceDocument = {
userId: userId,
date: new Date(),
isAuthenticationSuccessful: isAuthenticationSuccessful,
};
return this.authenticationTracesCollection.insert(newDocument);
}
retrieveLatestAuthenticationTraces(userId: string, isAuthenticationSuccessful: boolean, count: number): BluebirdPromise<AuthenticationTraceDocument[]> {
const q = {
userId: userId,
isAuthenticationSuccessful: isAuthenticationSuccessful
};
return this.authenticationTracesCollection.find(q, { date: -1 }, count);
}
produceIdentityValidationToken(userId: string, token: string, challenge: string, maxAge: number): BluebirdPromise<any> {
const newDocument: IdentityValidationDocument = {
userId: userId,
token: token,
challenge: challenge,
maxDate: new Date(new Date().getTime() + maxAge)
};
return this.identityCheckTokensCollection.insert(newDocument);
}
consumeIdentityValidationToken(token: string, challenge: string): BluebirdPromise<IdentityValidationDocument> {
const that = this;
const filter = {
token: token,
challenge: challenge
};
let identityValidationDocument: IdentityValidationDocument;
return this.identityCheckTokensCollection.findOne(filter)
.then(function (doc: IdentityValidationDocument) {
if (!doc) {
return BluebirdPromise.reject(new Error("Registration token does not exist"));
}
identityValidationDocument = doc;
const current_date = new Date();
if (current_date > doc.maxDate)
return BluebirdPromise.reject(new Error("Registration token is not valid anymore"));
return that.identityCheckTokensCollection.remove(filter);
})
.then(() => {
return BluebirdPromise.resolve(identityValidationDocument);
});
}
saveTOTPSecret(userId: string, secret: TOTPSecret): BluebirdPromise<void> {
const doc = {
userId: userId,
secret: secret
};
const filter = {
userId: userId
};
return this.totpSecretCollection.update(filter, doc, { upsert: true });
}
retrieveTOTPSecret(userId: string): BluebirdPromise<TOTPSecretDocument> {
const filter = {
userId: userId
};
return this.totpSecretCollection.findOne(filter);
}
}

View File

@ -0,0 +1,44 @@
import BluebirdPromise = require("bluebird");
import { ICollection } from "../ICollection";
import MongoDB = require("mongodb");
export class MongoCollection implements ICollection {
private collection: MongoDB.Collection;
constructor(collection: MongoDB.Collection) {
this.collection = collection;
}
find(query: any, sortKeys?: any, count?: number): BluebirdPromise<any> {
const q = this.collection.find(query).sort(sortKeys).limit(count);
const toArrayAsync = BluebirdPromise.promisify(q.toArray, { context: q });
return toArrayAsync();
}
findOne(query: any): BluebirdPromise<any> {
const findOneAsync = BluebirdPromise.promisify<any, any>(this.collection.findOne, { context: this.collection });
return findOneAsync(query);
}
update(query: any, updateQuery: any, options?: any): BluebirdPromise<any> {
const updateAsync = BluebirdPromise.promisify<any, any, any, any>(this.collection.update, { context: this.collection });
return updateAsync(query, updateQuery, options);
}
remove(query: any): BluebirdPromise<any> {
const removeAsync = BluebirdPromise.promisify<any, any>(this.collection.remove, { context: this.collection });
return removeAsync(query);
}
insert(document: any): BluebirdPromise<any> {
const insertAsync = BluebirdPromise.promisify<any, any>(this.collection.insert, { context: this.collection });
return insertAsync(document);
}
count(query: any): BluebirdPromise<number> {
const countAsync = BluebirdPromise.promisify<any, any>(this.collection.count, { context: this.collection });
return countAsync(query);
}
}

View File

@ -0,0 +1,19 @@
import BluebirdPromise = require("bluebird");
import { ICollection } from "../ICollection";
import { ICollectionFactory } from "../ICollectionFactory";
import { MongoCollection } from "./MongoCollection";
import path = require("path");
import MongoDB = require("mongodb");
import { IMongoClient } from "../../connectors/mongo/IMongoClient";
export class MongoCollectionFactory implements ICollectionFactory {
private mongoClient: IMongoClient;
constructor(mongoClient: IMongoClient) {
this.mongoClient = mongoClient;
}
build(collectionName: string): ICollection {
return new MongoCollection(this.mongoClient.collection(collectionName));
}
}

View File

@ -0,0 +1,38 @@
import BluebirdPromise = require("bluebird");
import { ICollection } from "../ICollection";
import Nedb = require("nedb");
import { NedbAsync } from "nedb";
export class NedbCollection implements ICollection {
private collection: NedbAsync;
constructor(options: Nedb.DataStoreOptions) {
this.collection = BluebirdPromise.promisifyAll(new Nedb(options)) as NedbAsync;
}
find(query: any, sortKeys?: any, count?: number): BluebirdPromise<any> {
const q = this.collection.find(query).sort(sortKeys).limit(count);
return BluebirdPromise.promisify(q.exec, { context: q })();
}
findOne(query: any): BluebirdPromise<any> {
return this.collection.findOneAsync(query);
}
update(query: any, updateQuery: any, options?: any): BluebirdPromise<any> {
return this.collection.updateAsync(query, updateQuery, options);
}
remove(query: any): BluebirdPromise<any> {
return this.collection.removeAsync(query);
}
insert(document: any): BluebirdPromise<any> {
return this.collection.insertAsync(document);
}
count(query: any): BluebirdPromise<number> {
return this.collection.countAsync(query);
}
}

View File

@ -0,0 +1,28 @@
import { ICollection } from "../ICollection";
import { ICollectionFactory } from "../ICollectionFactory";
import { NedbCollection } from "./NedbCollection";
import path = require("path");
import Nedb = require("nedb");
export interface NedbOptions {
inMemoryOnly?: boolean;
directory?: string;
}
export class NedbCollectionFactory implements ICollectionFactory {
private options: NedbOptions;
constructor(options: NedbOptions) {
this.options = options;
}
build(collectionName: string): ICollection {
const datastoreOptions = {
inMemoryOnly: this.options.inMemoryOnly || false,
autoload: true,
filename: (this.options.directory) ? path.resolve(this.options.directory, collectionName) : undefined
};
return new NedbCollection(datastoreOptions);
}
}

View File

@ -0,0 +1,5 @@
export interface U2FRegistration {
keyHandle: string;
publicKey: string;
}

View File

@ -5,8 +5,9 @@ declare module "nedb" {
export class NedbAsync extends Nedb {
constructor(pathOrOptions?: string | Nedb.DataStoreOptions);
updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): BluebirdPromise<any>;
findOneAsync(query: any): BluebirdPromise<any>;
findOneAsync<T>(query: any): BluebirdPromise<T>;
insertAsync<T>(newDoc: T): BluebirdPromise<any>;
removeAsync(query: any): BluebirdPromise<any>;
countAsync(query: any): BluebirdPromise<number>
}
}

View File

@ -1,73 +1,120 @@
import { AuthenticationRegulator } from "../../../src/server/lib/AuthenticationRegulator";
import UserDataStore from "../../../src/server/lib/UserDataStore";
import Sinon = require("sinon");
import BluebirdPromise = require("bluebird");
import { AuthenticationRegulator } from "../../../src/server/lib/AuthenticationRegulator";
import { UserDataStore } from "../../../src/server/lib/storage/UserDataStore";
import MockDate = require("mockdate");
import exceptions = require("../../../src/server/lib/Exceptions");
import nedb = require("nedb");
import { CollectionStub } from "./mocks/storage/CollectionStub";
import { CollectionFactoryStub } from "./mocks/storage/CollectionFactoryStub";
describe("test authentication regulator", function() {
it("should mark 2 authentication and regulate (resolve)", function() {
const options = {
inMemoryOnly: true
};
const data_store = new UserDataStore(options, nedb);
const regulator = new AuthenticationRegulator(data_store, 10);
const user = "user";
describe("test authentication regulator", function () {
let collectionFactory: CollectionFactoryStub;
let collection: CollectionStub;
beforeEach(function () {
collectionFactory = new CollectionFactoryStub();
collection = new CollectionStub();
collectionFactory.buildStub.returns(collection);
});
it("should mark 2 authentication and regulate", function () {
const user = "USER";
collection.insertStub.returns(BluebirdPromise.resolve());
collection.findStub.returns(BluebirdPromise.resolve([{
userId: user,
date: new Date(),
isAuthenticationSuccessful: false
}, {
userId: user,
date: new Date(),
isAuthenticationSuccessful: true
}]));
const dataStore = new UserDataStore(collectionFactory);
const regulator = new AuthenticationRegulator(dataStore, 10);
return regulator.mark(user, false)
.then(function() {
return regulator.mark(user, true);
})
.then(function() {
return regulator.regulate(user);
});
.then(function () {
return regulator.mark(user, true);
})
.then(function () {
return regulator.regulate(user);
});
});
it("should mark 3 authentications and regulate (reject)", function(done) {
const options = {
inMemoryOnly: true
};
const data_store = new UserDataStore(options, nedb);
const regulator = new AuthenticationRegulator(data_store, 10);
const user = "user";
it("should mark 3 authentications and regulate (reject)", function (done) {
const user = "USER";
collection.insertStub.returns(BluebirdPromise.resolve());
collection.findStub.returns(BluebirdPromise.resolve([{
userId: user,
date: new Date(),
isAuthenticationSuccessful: false
}, {
userId: user,
date: new Date(),
isAuthenticationSuccessful: false
}, {
userId: user,
date: new Date(),
isAuthenticationSuccessful: false
}]));
const dataStore = new UserDataStore(collectionFactory);
const regulator = new AuthenticationRegulator(dataStore, 10);
regulator.mark(user, false)
.then(function() {
return regulator.mark(user, false);
})
.then(function() {
return regulator.mark(user, false);
})
.then(function() {
return regulator.regulate(user);
})
.catch(exceptions.AuthenticationRegulationError, function() {
done();
});
.then(function () {
return regulator.mark(user, false);
})
.then(function () {
return regulator.mark(user, false);
})
.then(function () {
return regulator.regulate(user);
})
.catch(exceptions.AuthenticationRegulationError, function () {
done();
});
});
it("should mark 3 authentications and regulate (resolve)", function(done) {
const options = {
inMemoryOnly: true
};
const data_store = new UserDataStore(options, nedb);
it("should mark 3 authentications separated by a lot of time and allow access to user", function (done) {
const user = "USER";
collection.insertStub.returns(BluebirdPromise.resolve());
collection.findStub.returns(BluebirdPromise.resolve([{
userId: user,
date: new Date("1/2/2000 06:00:15"),
isAuthenticationSuccessful: false
}, {
userId: user,
date: new Date("1/2/2000 00:00:15"),
isAuthenticationSuccessful: false
}, {
userId: user,
date: new Date("1/2/2000 00:00:00"),
isAuthenticationSuccessful: false
}]));
const data_store = new UserDataStore(collectionFactory);
const regulator = new AuthenticationRegulator(data_store, 10);
const user = "user";
MockDate.set("1/2/2000 00:00:00");
regulator.mark(user, false)
.then(function() {
MockDate.set("1/2/2000 00:00:15");
return regulator.mark(user, false);
})
.then(function() {
return regulator.mark(user, false);
})
.then(function() {
return regulator.regulate(user);
})
.then(function() {
done();
});
.then(function () {
MockDate.set("1/2/2000 00:00:15");
return regulator.mark(user, false);
})
.then(function () {
MockDate.set("1/2/2000 06:00:15");
return regulator.mark(user, false);
})
.then(function () {
return regulator.regulate(user);
})
.then(function () {
done();
});
});
});
});

View File

@ -1,6 +1,6 @@
import * as Assert from "assert";
import { UserConfiguration } from "../../../src/types/Configuration";
import ConfigurationAdapter from "../../../src/server/lib/ConfigurationAdapter";
import { UserConfiguration } from "../../../src/server/lib/configuration/Configuration";
import { ConfigurationAdapter } from "../../../src/server/lib/configuration/ConfigurationAdapter";
describe("test config adapter", function() {
function build_yaml_config(): UserConfiguration {
@ -17,7 +17,11 @@ describe("test config adapter", function() {
secret: "secret",
max_age: 40000
},
store_directory: "/mydirectory",
storage: {
local: {
path: "/mydirectory"
}
},
logs_level: "debug",
notifier: {
gmail: {

View File

@ -3,7 +3,7 @@ import * as BluebirdPromise from "bluebird";
import * as request from "request";
import Server from "../../../src/server/lib/Server";
import { UserConfiguration } from "../../../src/types/Configuration";
import { UserConfiguration } from "../../../src/server/lib/configuration/Configuration";
import { GlobalDependencies } from "../../../src/types/Dependencies";
import * as tmp from "tmp";
import U2FMock = require("./mocks/u2f");
@ -70,7 +70,11 @@ describe("test data persistence", function () {
secret: "session_secret",
expiration: 50000,
},
store_directory: tmpDir.name,
storage: {
local: {
path: tmpDir.name
}
},
notifier: {
gmail: {
username: "user@example.com",

View File

@ -2,24 +2,24 @@
import sinon = require("sinon");
import IdentityValidator = require("../../../src/server/lib/IdentityCheckMiddleware");
import AuthenticationSession = require("../../../src/server/lib/AuthenticationSession");
import { UserDataStore } from "../../../src/server/lib/storage/UserDataStore";
import exceptions = require("../../../src/server/lib/Exceptions");
import assert = require("assert");
import winston = require("winston");
import Promise = require("bluebird");
import express = require("express");
import BluebirdPromise = require("bluebird");
import ExpressMock = require("./mocks/express");
import UserDataStoreMock = require("./mocks/UserDataStore");
import NotifierMock = require("./mocks/Notifier");
import IdentityValidatorMock = require("./mocks/IdentityValidator");
import ServerVariablesMock = require("./mocks/ServerVariablesMock");
describe("test identity check process", function () {
let mocks: ServerVariablesMock.ServerVariablesMock;
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let userDataStore: UserDataStoreMock.UserDataStore;
let notifier: NotifierMock.NotifierMock;
let app: express.Application;
let app_get: sinon.SinonStub;
@ -32,12 +32,6 @@ describe("test identity check process", function () {
identityValidable = IdentityValidatorMock.IdentityValidableMock();
userDataStore = UserDataStoreMock.UserDataStore();
userDataStore.issue_identity_check_token = sinon.stub();
userDataStore.issue_identity_check_token.returns(Promise.resolve());
userDataStore.consume_identity_check_token = sinon.stub();
userDataStore.consume_identity_check_token.returns(Promise.resolve({ userid: "user" }));
notifier = NotifierMock.NotifierMock();
notifier.notify = sinon.stub().returns(Promise.resolve());
@ -47,11 +41,13 @@ describe("test identity check process", function () {
req.query = {};
req.app = {};
const mocks = ServerVariablesMock.mock(req.app);
mocks.logger = winston;
mocks.userDataStore = userDataStore as any;
mocks = ServerVariablesMock.mock(req.app);
mocks.notifier = notifier;
mocks.userDataStore.produceIdentityValidationTokenStub.returns(Promise.resolve());
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(Promise.resolve({ userId: "user" }));
app = express();
app_get = sinon.stub(app, "get");
app_post = sinon.stub(app, "post");
@ -116,9 +112,9 @@ describe("test identity check process", function () {
return callback(req as any, res as any, undefined)
.then(function () {
assert(notifier.notify.calledOnce);
assert(userDataStore.issue_identity_check_token.calledOnce);
assert.equal(userDataStore.issue_identity_check_token.getCall(0).args[0], "user");
assert.equal(userDataStore.issue_identity_check_token.getCall(0).args[3], 240000);
assert(mocks.userDataStore.produceIdentityValidationTokenStub.calledOnce);
assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[0], "user");
assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[3], 240000);
});
});
}
@ -145,8 +141,7 @@ describe("test identity check process", function () {
it("should return 500 if identity_token is provided but invalid", function () {
req.query.identity_token = "token";
userDataStore.consume_identity_check_token
.returns(BluebirdPromise.reject(new Error("Invalid token")));
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.reject(new Error("Invalid token")));
const callback = IdentityValidator.get_finish_validation(identityValidable);
return callback(req as any, res as any, undefined)

View File

@ -9,7 +9,7 @@ import u2f = require("u2f");
import nodemailer = require("nodemailer");
import session = require("express-session");
import { AppConfiguration, UserConfiguration } from "../../../src/types/Configuration";
import { AppConfiguration, UserConfiguration } from "../../../src/server/lib/configuration/Configuration";
import { GlobalDependencies, Nodemailer } from "../../../src/types/Dependencies";
import Server from "../../../src/server/lib/Server";
@ -50,7 +50,7 @@ describe("test server configuration", function () {
it("should set cookie scope to domain set in the config", function () {
const config = {
const config: UserConfiguration = {
session: {
domain: "example.com",
secret: "secret"
@ -58,15 +58,21 @@ describe("test server configuration", function () {
ldap: {
url: "http://ldap",
user: "user",
password: "password"
password: "password",
base_dn: "dc=example,dc=com"
},
notifier: {
gmail: {
username: "user@example.com",
password: "password"
}
},
storage: {
local: {
in_memory: true
}
}
} as UserConfiguration;
};
const server = new Server();
server.start(config, deps);

View File

@ -1,6 +1,7 @@
import SessionConfigurationBuilder from "../../../src/server/lib/SessionConfigurationBuilder";
import { AppConfiguration } from "../../../src/types/Configuration";
import { SessionConfigurationBuilder } from "../../../src/server/lib/configuration/SessionConfigurationBuilder";
import { AppConfiguration } from "../../../src/server/lib/configuration/Configuration";
import { GlobalDependencies } from "../../../src/types/Dependencies";
import ExpressSession = require("express-session");
import ConnectRedis = require("connect-redis");
import Sinon = require("sinon");
@ -32,7 +33,11 @@ describe("test session configuration builder", function () {
expiration: 3600,
secret: "secret"
},
store_in_memory: true
storage: {
local: {
in_memory: true
}
}
};
const deps: GlobalDependencies = {
@ -92,7 +97,11 @@ describe("test session configuration builder", function () {
port: 6379
}
},
store_in_memory: true
storage: {
local: {
in_memory: true
}
}
};
const RedisStoreMock = Sinon.spy();

View File

@ -1,205 +0,0 @@
import UserDataStore from "../../../src/server/lib/UserDataStore";
import { U2FRegistrationDocument, Options } from "../../../src/server/lib/UserDataStore";
import nedb = require("nedb");
import assert = require("assert");
import Promise = require("bluebird");
import sinon = require("sinon");
import MockDate = require("mockdate");
describe("test user data store", () => {
let options: Options;
beforeEach(function () {
options = {
inMemoryOnly: true
};
});
describe("test u2f meta", () => {
it("should save a u2f meta", function () {
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const app_id = "https://localhost";
const keyHandle = "keyhandle";
const publicKey = "publicKey";
return data_store.set_u2f_meta(userid, app_id, keyHandle, publicKey)
.then(function (numUpdated) {
assert.equal(1, numUpdated);
return Promise.resolve();
});
});
it("should retrieve no u2f meta", function () {
const options = {
inMemoryOnly: true
};
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const app_id = "https://localhost";
const meta = {
publicKey: "pbk"
};
return data_store.get_u2f_meta(userid, app_id)
.then(function (doc) {
assert.equal(undefined, doc);
return Promise.resolve();
});
});
it("should insert and retrieve a u2f meta", function () {
const options = {
inMemoryOnly: true
};
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const app_id = "https://localhost";
const keyHandle = "keyHandle";
const publicKey = "publicKey";
return data_store.set_u2f_meta(userid, app_id, keyHandle, publicKey)
.then(function (numUpdated: number) {
assert.equal(1, numUpdated);
return data_store.get_u2f_meta(userid, app_id);
})
.then(function (doc: U2FRegistrationDocument) {
assert.deepEqual(keyHandle, doc.keyHandle);
assert.deepEqual(publicKey, doc.publicKey);
assert.deepEqual(userid, doc.userId);
assert.deepEqual(app_id, doc.appId);
assert("_id" in doc);
return Promise.resolve();
});
});
});
describe("test u2f registration token", () => {
it("should save u2f registration token", function () {
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const token = "token";
const max_age = 60;
const content = "abc";
return data_store.issue_identity_check_token(userid, token, content, max_age)
.then(function (document) {
assert.equal(document.userid, userid);
assert.equal(document.token, token);
assert.deepEqual(document.content, { userid: "user", data: content });
assert("max_date" in document);
assert("_id" in document);
return Promise.resolve();
})
.catch(function (err) {
console.error(err);
return Promise.reject(err);
});
});
it("should save u2f registration token and consume it", function (done) {
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const token = "token";
const max_age = 50;
data_store.issue_identity_check_token(userid, token, {}, max_age)
.then(function (document) {
return data_store.consume_identity_check_token(token);
})
.then(function () {
done();
})
.catch(function (err) {
console.error(err);
});
});
it("should not be able to consume registration token twice", function (done) {
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const token = "token";
const max_age = 50;
data_store.issue_identity_check_token(userid, token, {}, max_age)
.then(function (document) {
return data_store.consume_identity_check_token(token);
})
.then(function (document) {
return data_store.consume_identity_check_token(token);
})
.catch(function (err) {
console.error(err);
done();
});
});
it("should fail when token does not exist", function () {
const data_store = new UserDataStore(options, nedb);
const token = "token";
return data_store.consume_identity_check_token(token)
.then(function (document) {
return Promise.reject("Error while checking token");
})
.catch(function (err) {
return Promise.resolve(err);
});
});
it("should fail when token expired", function (done) {
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const token = "token";
const max_age = 60;
MockDate.set("1/1/2000");
data_store.issue_identity_check_token(userid, token, {}, max_age)
.then(function () {
MockDate.set("1/2/2000");
return data_store.consume_identity_check_token(token);
})
.catch(function (err) {
MockDate.reset();
done();
});
});
it("should save the userid and some data with the token", function (done) {
const data_store = new UserDataStore(options, nedb);
const userid = "user";
const token = "token";
const max_age = 60;
MockDate.set("1/1/2000");
const data = "abc";
data_store.issue_identity_check_token(userid, token, data, max_age)
.then(function () {
return data_store.consume_identity_check_token(token);
})
.then(function (content) {
const expected_content = {
userid: "user",
data: "abc"
};
assert.deepEqual(content, expected_content);
done();
});
});
});
});

View File

@ -2,7 +2,7 @@
import assert = require("assert");
import winston = require("winston");
import { AccessController } from "../../../../src/server/lib/access_control/AccessController";
import { ACLConfiguration } from "../../../../src/types/Configuration";
import { ACLConfiguration } from "../../../../src/server/lib/configuration/Configuration";
describe("test access control manager", function () {
let accessController: AccessController;

View File

@ -3,7 +3,7 @@ import assert = require("assert");
import winston = require("winston");
import PatternBuilder from "../../../../src/server/lib/access_control/PatternBuilder";
import { ACLConfiguration } from "../../../../src/types/Configuration";
import { ACLConfiguration } from "../../../../src/server/lib/configuration/Configuration";
describe("test access control manager", function () {
describe("test access control pattern builder when no configuration is provided", () => {

View File

@ -0,0 +1,38 @@
import Assert = require("assert");
import Sinon = require("sinon");
import MongoDB = require("mongodb");
import { MongoClient } from "../../../../../src/server/lib/connectors/mongo/MongoClient";
describe("MongoClient", function () {
let mongoClientConnectStub: Sinon.SinonStub;
let mongoDatabase: any;
let mongoDatabaseCollectionStub: Sinon.SinonStub;
describe("collection", function () {
before(function () {
mongoDatabaseCollectionStub = Sinon.stub();
mongoDatabase = {
collection: mongoDatabaseCollectionStub
};
mongoClientConnectStub = Sinon.stub(MongoDB.MongoClient, "connect");
mongoClientConnectStub.yields(undefined, mongoDatabase);
});
after(function () {
mongoClientConnectStub.restore();
});
it("should create a collection", function () {
const COLLECTION_NAME = "mycollection";
const client = new MongoClient(mongoDatabase);
mongoDatabaseCollectionStub.returns({});
const collection = client.collection(COLLECTION_NAME);
Assert(collection);
Assert(mongoDatabaseCollectionStub.calledWith(COLLECTION_NAME ));
});
});
});

View File

@ -0,0 +1,45 @@
import Assert = require("assert");
import Sinon = require("sinon");
import MongoDB = require("mongodb");
import BluebirdPromise = require("bluebird");
import { IMongoClient } from "../../../../../src/server/lib/connectors/mongo/IMongoClient";
import { MongoConnector } from "../../../../../src/server/lib/connectors/mongo/MongoConnector";
describe("MongoConnector", function () {
let mongoClientConnectStub: Sinon.SinonStub;
describe("create", function () {
before(function () {
mongoClientConnectStub = Sinon.stub(MongoDB.MongoClient, "connect");
});
after(function() {
mongoClientConnectStub.restore();
});
it("should create a connector", function () {
mongoClientConnectStub.yields(undefined);
const url = "mongodb://test.url";
const connector = new MongoConnector(url);
return connector.connect()
.then(function (client: IMongoClient) {
Assert(client);
Assert(mongoClientConnectStub.calledWith(url));
});
});
it("should fail creating a connector", function () {
mongoClientConnectStub.yields(new Error("Error while creating mongo client"));
const url = "mongodb://test.url";
const connector = new MongoConnector(url);
return connector.connect()
.then(function () { return BluebirdPromise.reject(new Error("It should not be here")); })
.error(function (client: IMongoClient) {
Assert(client);
Assert(mongoClientConnectStub.calledWith(url));
return BluebirdPromise.resolve();
});
});
});
});

View File

@ -0,0 +1,13 @@
import Assert = require("assert");
import { MongoConnectorFactory } from "../../../../../src/server/lib/connectors/mongo/MongoConnectorFactory";
describe("MongoConnectorFactory", function () {
describe("create", function () {
it("should create a connector", function () {
const factory = new MongoConnectorFactory();
const connector = factory.create("mongodb://test.url");
Assert(connector);
});
});
});

View File

@ -1,6 +1,6 @@
import { Authenticator } from "../../../../src/server/lib/ldap/Authenticator";
import { LdapConfiguration } from "../../../../src/types/Configuration";
import { LdapConfiguration } from "../../../../src/server/lib/configuration/Configuration";
import sinon = require("sinon");
import BluebirdPromise = require("bluebird");

View File

@ -1,6 +1,6 @@
import { EmailsRetriever } from "../../../../src/server/lib/ldap/EmailsRetriever";
import { LdapConfiguration } from "../../../../src/types/Configuration";
import { LdapConfiguration } from "../../../../src/server/lib/configuration/Configuration";
import sinon = require("sinon");
import BluebirdPromise = require("bluebird");

View File

@ -1,6 +1,6 @@
import { PasswordUpdater } from "../../../../src/server/lib/ldap/PasswordUpdater";
import { LdapConfiguration } from "../../../../src/types/Configuration";
import { LdapConfiguration } from "../../../../src/server/lib/configuration/Configuration";
import sinon = require("sinon");
import BluebirdPromise = require("bluebird");

View File

@ -1,14 +1,18 @@
import sinon = require("sinon");
import express = require("express");
import {  ServerVariables, VARIABLES_KEY }  from "../../../../src/server/lib/ServerVariables";
import winston = require("winston");
import { UserDataStoreStub } from "./storage/UserDataStoreStub";
import { ServerVariables, VARIABLES_KEY }  from "../../../../src/server/lib/ServerVariablesHandler";
export interface ServerVariablesMock {
logger: any;
ldap: any;
ldapAuthenticator: any;
ldapEmailsRetriever: any;
ldapPasswordUpdater: any;
totpValidator: any;
totpGenerator: any;
u2f: any;
userDataStore: any;
userDataStore: UserDataStoreStub;
notifier: any;
regulator: any;
config: any;
@ -16,20 +20,20 @@ export interface ServerVariablesMock {
}
export function mock(app: express.Application): ServerVariables {
const mocks: ServerVariables = {
accessController: sinon.stub() as any,
config: sinon.stub() as any,
export function mock(app: express.Application): ServerVariablesMock {
const mocks: ServerVariablesMock = {
accessController: sinon.stub(),
config: sinon.stub(),
ldapAuthenticator: sinon.stub() as any,
ldapEmailsRetriever: sinon.stub() as any,
ldapPasswordUpdater: sinon.stub() as any,
logger: sinon.stub() as any,
notifier: sinon.stub() as any,
regulator: sinon.stub() as any,
totpGenerator: sinon.stub() as any,
totpValidator: sinon.stub() as any,
u2f: sinon.stub() as any,
userDataStore: sinon.stub() as any,
logger: winston,
notifier: sinon.stub(),
regulator: sinon.stub(),
totpGenerator: sinon.stub(),
totpValidator: sinon.stub(),
u2f: sinon.stub(),
userDataStore: new UserDataStoreStub()
};
app.get = sinon.stub().withArgs(VARIABLES_KEY).returns(mocks);
return mocks;

View File

@ -1,22 +0,0 @@
import sinon = require("sinon");
export interface UserDataStore {
set_u2f_meta: sinon.SinonStub;
get_u2f_meta: sinon.SinonStub;
issue_identity_check_token: sinon.SinonStub;
consume_identity_check_token: sinon.SinonStub;
get_totp_secret: sinon.SinonStub;
set_totp_secret: sinon.SinonStub;
}
export function UserDataStore(): UserDataStore {
return {
set_u2f_meta: sinon.stub(),
get_u2f_meta: sinon.stub(),
issue_identity_check_token: sinon.stub(),
consume_identity_check_token: sinon.stub(),
get_totp_secret: sinon.stub(),
set_totp_secret: sinon.stub()
};
}

View File

@ -0,0 +1,15 @@
import Sinon = require("sinon");
import MongoDB = require("mongodb");
import { IMongoClient } from "../../../../../../src/server/lib/connectors/mongo/IMongoClient";
export class MongoClientStub implements IMongoClient {
public collectionStub: Sinon.SinonStub;
constructor() {
this.collectionStub = Sinon.stub();
}
collection(name: string): MongoDB.Collection {
return this.collectionStub(name);
}
}

View File

@ -0,0 +1,16 @@
import BluebirdPromise = require("bluebird");
import Sinon = require("sinon");
import { ICollection } from "../../../../../src/server/lib/storage/ICollection";
import { ICollectionFactory } from "../../../../../src/server/lib/storage/ICollectionFactory";
export class CollectionFactoryStub implements ICollectionFactory {
buildStub: Sinon.SinonStub;
constructor() {
this.buildStub = Sinon.stub();
}
build(collectionName: string): ICollection {
return this.buildStub(collectionName);
}
}

View File

@ -0,0 +1,39 @@
import BluebirdPromise = require("bluebird");
import Sinon = require("sinon");
import { ICollection } from "../../../../../src/server/lib/storage/ICollection";
export class CollectionStub implements ICollection {
findStub: Sinon.SinonStub;
findOneStub: Sinon.SinonStub;
updateStub: Sinon.SinonStub;
removeStub: Sinon.SinonStub;
insertStub: Sinon.SinonStub;
constructor() {
this.findStub = Sinon.stub();
this.findOneStub = Sinon.stub();
this.updateStub = Sinon.stub();
this.removeStub = Sinon.stub();
this.insertStub = Sinon.stub();
}
find(filter: any, sortKeys: any, count: number): BluebirdPromise<any> {
return this.findStub(filter, sortKeys, count);
}
findOne(filter: any): BluebirdPromise<any> {
return this.findOneStub(filter);
}
update(filter: any, document: any, options: any): BluebirdPromise<any> {
return this.updateStub(filter, document, options);
}
remove(filter: any): BluebirdPromise<any> {
return this.removeStub(filter);
}
insert(document: any): BluebirdPromise<any> {
return this.insertStub(document);
}
}

View File

@ -0,0 +1,65 @@
import Sinon = require("sinon");
import BluebirdPromise = require("bluebird");
import { TOTPSecretDocument } from "../../../../../src/server/lib/storage/TOTPSecretDocument";
import { U2FRegistrationDocument } from "../../../../../src/server/lib/storage/U2FRegistrationDocument";
import { U2FRegistration } from "../../../../../src/types/U2FRegistration";
import { TOTPSecret } from "../../../../../src/types/TOTPSecret";
import { AuthenticationTraceDocument } from "../../../../../src/server/lib/storage/AuthenticationTraceDocument";
import { IdentityValidationDocument } from "../../../../../src/server/lib/storage/IdentityValidationDocument";
import { IUserDataStore } from "../../../../../src/server/lib/storage/IUserDataStore";
export class UserDataStoreStub implements IUserDataStore {
saveU2FRegistrationStub: Sinon.SinonStub;
retrieveU2FRegistrationStub: Sinon.SinonStub;
saveAuthenticationTraceStub: Sinon.SinonStub;
retrieveLatestAuthenticationTracesStub: Sinon.SinonStub;
produceIdentityValidationTokenStub: Sinon.SinonStub;
consumeIdentityValidationTokenStub: Sinon.SinonStub;
saveTOTPSecretStub: Sinon.SinonStub;
retrieveTOTPSecretStub: Sinon.SinonStub;
constructor() {
this.saveU2FRegistrationStub = Sinon.stub();
this.retrieveU2FRegistrationStub = Sinon.stub();
this.saveAuthenticationTraceStub = Sinon.stub();
this.retrieveLatestAuthenticationTracesStub = Sinon.stub();
this.produceIdentityValidationTokenStub = Sinon.stub();
this.consumeIdentityValidationTokenStub = Sinon.stub();
this.saveTOTPSecretStub = Sinon.stub();
this.retrieveTOTPSecretStub = Sinon.stub();
}
saveU2FRegistration(userId: string, appId: string, registration: U2FRegistration): BluebirdPromise<void> {
return this.saveU2FRegistrationStub(userId, appId, registration);
}
retrieveU2FRegistration(userId: string, appId: string): BluebirdPromise<U2FRegistrationDocument> {
return this.retrieveU2FRegistrationStub(userId, appId);
}
saveAuthenticationTrace(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
return this.saveAuthenticationTraceStub(userId, isAuthenticationSuccessful);
}
retrieveLatestAuthenticationTraces(userId: string, isAuthenticationSuccessful: boolean, count: number): BluebirdPromise<AuthenticationTraceDocument[]> {
return this.retrieveLatestAuthenticationTracesStub(userId, isAuthenticationSuccessful, count);
}
produceIdentityValidationToken(userId: string, token: string, challenge: string, maxAge: number): BluebirdPromise<any> {
return this.produceIdentityValidationTokenStub(userId, token, challenge, maxAge);
}
consumeIdentityValidationToken(token: string, challenge: string): BluebirdPromise<IdentityValidationDocument> {
return this.consumeIdentityValidationTokenStub(token, challenge);
}
saveTOTPSecret(userId: string, secret: TOTPSecret): BluebirdPromise<void> {
return this.saveTOTPSecretStub(userId, secret);
}
retrieveTOTPSecret(userId: string): BluebirdPromise<TOTPSecretDocument> {
return this.retrieveTOTPSecretStub(userId);
}
}

View File

@ -0,0 +1,19 @@
import Sinon = require("sinon");
import Express = require("express");
import Assert = require("assert");
import Get401 from "../../../../../../src/server/lib/routes/error/401/get";
describe("Server error 401", function () {
it("should render the page", function () {
const req = {} as Express.Request;
const res = {
render: Sinon.stub()
};
return Get401(req, res as any)
.then(function () {
Assert(res.render.calledOnce);
Assert(res.render.calledWith("errors/401"));
});
});
});

View File

@ -0,0 +1,19 @@
import Sinon = require("sinon");
import Express = require("express");
import Assert = require("assert");
import Get403 from "../../../../../../src/server/lib/routes/error/403/get";
describe("Server error 403", function () {
it("should render the page", function () {
const req = {} as Express.Request;
const res = {
render: Sinon.stub()
};
return Get403(req, res as any)
.then(function () {
Assert(res.render.calledOnce);
Assert(res.render.calledWith("errors/403"));
});
});
});

View File

@ -0,0 +1,19 @@
import Sinon = require("sinon");
import Express = require("express");
import Assert = require("assert");
import Get404 from "../../../../../../src/server/lib/routes/error/404/get";
describe("Server error 404", function () {
it("should render the page", function () {
const req = {} as Express.Request;
const res = {
render: Sinon.stub()
};
return Get404(req, res as any)
.then(function () {
Assert(res.render.calledOnce);
Assert(res.render.calledWith("errors/404"));
});
});
});

View File

@ -13,7 +13,7 @@ import AuthenticationRegulatorMock = require("../../mocks/AuthenticationRegulato
import AccessControllerMock = require("../../mocks/AccessController");
import ExpressMock = require("../../mocks/express");
import ServerVariablesMock = require("../../mocks/ServerVariablesMock");
import { ServerVariables } from "../../../../../src/server/lib/ServerVariables";
import { ServerVariables } from "../../../../../src/server/lib/ServerVariablesHandler";
describe("test the first factor validation route", function () {
let req: ExpressMock.RequestMock;

View File

@ -1,22 +1,21 @@
import PasswordResetHandler from "../../../../../../src/server/lib/routes/password-reset/identity/PasswordResetHandler";
import PasswordUpdater = require("../../../../../../src/server/lib/ldap/PasswordUpdater");
import { ServerVariables } from "../../../../../../src/server/lib/ServerVariables";
import sinon = require("sinon");
import { ServerVariablesHandler } from "../../../../../../src/server/lib/ServerVariablesHandler";
import { UserDataStore } from "../../../../../../src/server/lib/storage/UserDataStore";
import Sinon = require("sinon");
import winston = require("winston");
import assert = require("assert");
import BluebirdPromise = require("bluebird");
import ExpressMock = require("../../../mocks/express");
import { UserDataStore } from "../../../mocks/UserDataStore";
import ServerVariablesMock = require("../../../mocks/ServerVariablesMock");
describe("test reset password identity check", function () {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let userDataStore: UserDataStore;
let configuration: any;
let serverVariables: ServerVariables;
let serverVariables: ServerVariablesMock.ServerVariablesMock;
beforeEach(function () {
req = {
@ -24,7 +23,7 @@ describe("test reset password identity check", function () {
userid: "user"
},
app: {
get: sinon.stub()
get: Sinon.stub()
},
session: {
auth_session: {
@ -45,14 +44,10 @@ describe("test reset password identity check", function () {
serverVariables = ServerVariablesMock.mock(req.app);
userDataStore = UserDataStore();
userDataStore.set_u2f_meta.returns(BluebirdPromise.resolve({}));
userDataStore.get_u2f_meta.returns(BluebirdPromise.resolve({}));
userDataStore.issue_identity_check_token.returns(BluebirdPromise.resolve({}));
userDataStore.consume_identity_check_token.returns(BluebirdPromise.resolve({}));
serverVariables.userDataStore = userDataStore as any;
serverVariables.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
serverVariables.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
serverVariables.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
serverVariables.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
configuration = {
ldap: {
@ -64,9 +59,8 @@ describe("test reset password identity check", function () {
serverVariables.logger = winston;
serverVariables.config = configuration;
serverVariables.ldapEmailsRetriever = {
retrieve: sinon.stub()
retrieve: Sinon.stub()
} as any;
res = ExpressMock.ResponseMock();
});

View File

@ -2,23 +2,22 @@
import PasswordResetFormPost = require("../../../../../src/server/lib/routes/password-reset/form/post");
import { PasswordUpdater } from "../../../../../src/server/lib/ldap/PasswordUpdater";
import AuthenticationSession = require("../../../../../src/server/lib/AuthenticationSession");
import { ServerVariables } from "../../../../../src/server/lib/ServerVariables";
import sinon = require("sinon");
import { ServerVariablesHandler } from "../../../../../src/server/lib/ServerVariablesHandler";
import { UserDataStore } from "../../../../../src/server/lib/storage/UserDataStore";
import Sinon = require("sinon");
import winston = require("winston");
import assert = require("assert");
import BluebirdPromise = require("bluebird");
import ExpressMock = require("../../mocks/express");
import { UserDataStore } from "../../mocks/UserDataStore";
import ServerVariablesMock = require("../../mocks/ServerVariablesMock");
describe("test reset password route", function () {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let userDataStore: UserDataStore;
let configuration: any;
let authSession: AuthenticationSession.AuthenticationSession;
let serverVariables: ServerVariables;
let serverVariables: ServerVariablesMock.ServerVariablesMock;
beforeEach(function () {
req = {
@ -26,7 +25,7 @@ describe("test reset password route", function () {
userid: "user"
},
app: {
get: sinon.stub()
get: Sinon.stub()
},
session: {},
headers: {
@ -46,13 +45,10 @@ describe("test reset password route", function () {
};
serverVariables = ServerVariablesMock.mock(req.app);
userDataStore = UserDataStore();
userDataStore.set_u2f_meta.returns(BluebirdPromise.resolve({}));
userDataStore.get_u2f_meta.returns(BluebirdPromise.resolve({}));
userDataStore.issue_identity_check_token.returns(BluebirdPromise.resolve({}));
userDataStore.consume_identity_check_token.returns(BluebirdPromise.resolve({}));
serverVariables.userDataStore = userDataStore as any;
serverVariables.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
serverVariables.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
serverVariables.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
serverVariables.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
configuration = {
ldap: {
@ -65,7 +61,7 @@ describe("test reset password route", function () {
serverVariables.config = configuration;
serverVariables.ldapPasswordUpdater = {
updatePassword: sinon.stub()
updatePassword: Sinon.stub()
} as any;
res = ExpressMock.ResponseMock();
@ -96,7 +92,7 @@ describe("test reset password route", function () {
userid: "user",
challenge: undefined
};
res.send = sinon.spy(function () {
res.send = Sinon.spy(function () {
assert.equal(res.status.getCall(0).args[0], 403);
done();
});
@ -111,8 +107,8 @@ describe("test reset password route", function () {
req.body = {};
req.body.password = "new-password";
(serverVariables.ldapPasswordUpdater.updatePassword as sinon.SinonStub).returns(BluebirdPromise.reject("Internal error with LDAP"));
res.send = sinon.spy(function () {
(serverVariables.ldapPasswordUpdater.updatePassword as Sinon.SinonStub).returns(BluebirdPromise.reject("Internal error with LDAP"));
res.send = Sinon.spy(function () {
assert.equal(res.status.getCall(0).args[0], 500);
done();
});

View File

@ -1,19 +1,18 @@
import sinon = require("sinon");
import Sinon = require("sinon");
import winston = require("winston");
import RegistrationHandler from "../../../../../../../src/server/lib/routes/secondfactor/totp/identity/RegistrationHandler";
import { Identity } from "../../../../../../../src/types/Identity";
import AuthenticationSession = require("../../../../../../../src/server/lib/AuthenticationSession");
import { UserDataStore } from "../../../../../../../src/server/lib/storage/UserDataStore";
import assert = require("assert");
import BluebirdPromise = require("bluebird");
import ExpressMock = require("../../../../mocks/express");
import UserDataStoreMock = require("../../../../mocks/UserDataStore");
import ServerVariablesMock = require("../../../../mocks/ServerVariablesMock");
describe("test totp register", function () {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let userDataStore: UserDataStoreMock.UserDataStore;
const registrationHandler: RegistrationHandler = new RegistrationHandler();
let authSession: AuthenticationSession.AuthenticationSession;
@ -37,13 +36,11 @@ describe("test totp register", function () {
inMemoryOnly: true
};
userDataStore = UserDataStoreMock.UserDataStore();
userDataStore.set_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({}));
userDataStore.get_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({}));
userDataStore.issue_identity_check_token = sinon.stub().returns(BluebirdPromise.resolve({}));
userDataStore.consume_identity_check_token = sinon.stub().returns(BluebirdPromise.resolve({}));
userDataStore.set_totp_secret = sinon.stub().returns(BluebirdPromise.resolve({}));
mocks.userDataStore = userDataStore as any;
mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.saveTOTPSecretStub.returns(BluebirdPromise.resolve({}));
res = ExpressMock.ResponseMock();
});

View File

@ -9,15 +9,14 @@ import AuthenticationSession = require("../../../../../../../src/server/lib/Auth
import SignPost = require("../../../../../../../src/server/lib/routes/secondfactor/totp/sign/post");
import ExpressMock = require("../../../../mocks/express");
import UserDataStoreMock = require("../../../../mocks/UserDataStore");
import TOTPValidatorMock = require("../../../../mocks/TOTPValidator");
import ServerVariablesMock = require("../../../../mocks/ServerVariablesMock");
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
describe("test totp route", function () {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let totpValidator: TOTPValidatorMock.TOTPValidatorMock;
let userDataStore: UserDataStoreMock.UserDataStore;
let authSession: AuthenticationSession.AuthenticationSession;
beforeEach(function () {
@ -42,20 +41,17 @@ describe("test totp route", function () {
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));
mocks.userDataStore.retrieveTOTPSecretStub.returns(BluebirdPromise.resolve(doc));
mocks.logger = winston;
mocks.totpValidator = totpValidator as any;
mocks.config = config as any;
mocks.userDataStore = userDataStore as any;
mocks.totpValidator = totpValidator;
mocks.config = config;
});

View File

@ -8,13 +8,12 @@ import RegistrationHandler from "../../../../../../../src/server/lib/routes/seco
import AuthenticationSession = require("../../../../../../../src/server/lib/AuthenticationSession");
import ExpressMock = require("../../../../mocks/express");
import UserDataStoreMock = require("../../../../mocks/UserDataStore");
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
import ServerVariablesMock = require("../../../../mocks/ServerVariablesMock");
describe("test register handler", function () {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let userDataStore: UserDataStoreMock.UserDataStore;
let authSession: AuthenticationSession.AuthenticationSession;
beforeEach(function () {
@ -36,12 +35,10 @@ describe("test register handler", function () {
inMemoryOnly: true
};
userDataStore = UserDataStoreMock.UserDataStore();
userDataStore.set_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({}));
userDataStore.get_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({}));
userDataStore.issue_identity_check_token = sinon.stub().returns(BluebirdPromise.resolve({}));
userDataStore.consume_identity_check_token = sinon.stub().returns(BluebirdPromise.resolve({}));
mocks.userDataStore = userDataStore as any;
mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.produceIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
res = ExpressMock.ResponseMock();
res.send = sinon.spy();

View File

@ -4,11 +4,11 @@ import BluebirdPromise = require("bluebird");
import assert = require("assert");
import U2FRegisterPost = require("../../../../../../../src/server/lib/routes/secondfactor/u2f/register/post");
import AuthenticationSession = require("../../../../../../../src/server/lib/AuthenticationSession");
import { ServerVariables } from "../../../../../../../src/server/lib/ServerVariables";
import { ServerVariablesHandler } from "../../../../../../../src/server/lib/ServerVariablesHandler";
import winston = require("winston");
import ExpressMock = require("../../../../mocks/express");
import UserDataStoreMock = require("../../../../mocks/UserDataStore");
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
import U2FMock = require("../../../../mocks/u2f");
import ServerVariablesMock = require("../../../../mocks/ServerVariablesMock");
import U2f = require("u2f");
@ -16,8 +16,7 @@ import U2f = require("u2f");
describe("test u2f routes: register", function () {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let userDataStore: UserDataStoreMock.UserDataStore;
let mocks: ServerVariables;
let mocks: ServerVariablesMock.ServerVariablesMock;
let authSession: AuthenticationSession.AuthenticationSession;
beforeEach(function () {
@ -44,10 +43,8 @@ describe("test u2f routes: register", function () {
inMemoryOnly: true
};
userDataStore = UserDataStoreMock.UserDataStore();
userDataStore.set_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({}));
userDataStore.get_u2f_meta = sinon.stub().returns(BluebirdPromise.resolve({}));
mocks.userDataStore = userDataStore as any;
mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
res = ExpressMock.ResponseMock();
res.send = sinon.spy();
@ -77,7 +74,7 @@ describe("test u2f routes: register", function () {
mocks.u2f = u2f_mock;
return U2FRegisterPost.default(req as any, res as any)
.then(function () {
assert.equal("user", userDataStore.set_u2f_meta.getCall(0).args[0]);
assert.equal("user", mocks.userDataStore.saveU2FRegistrationStub.getCall(0).args[0]);
assert.equal(authSession.identity_check, undefined);
});
});

Some files were not shown because too many files have changed in this diff Show More