Improve logging format for clarity
Previously, logs were not very friendly and it was hard to track a request because of the lack of request ID. Now every log message comes with a header containing: method, path request ID, session ID, IP of the user, date. Moreover, the configurations displayed in the logs have their secrets hidden from this commit.pull/125/head
parent
26418278bc
commit
78f6028c1b
|
@ -134,7 +134,7 @@ access_control:
|
|||
# The session cookies identify the user once logged in.
|
||||
session:
|
||||
# The secret to encrypt the session cookie.
|
||||
secret: unsecure_secret
|
||||
secret: unsecure_session_secret
|
||||
|
||||
# The time before the cookie expires.
|
||||
expiration: 3600000
|
||||
|
@ -190,7 +190,7 @@ notifier:
|
|||
# Use a SMTP server for sending notifications
|
||||
smtp:
|
||||
username: test
|
||||
password: test
|
||||
password: password
|
||||
secure: false
|
||||
host: 'smtp'
|
||||
port: 1025
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"dovehash": "0.0.5",
|
||||
"ejs": "^2.5.5",
|
||||
"express": "^4.14.0",
|
||||
"express-request-id": "^1.4.0",
|
||||
"express-session": "^1.14.2",
|
||||
"ldapjs": "^1.0.1",
|
||||
"mongodb": "^2.2.30",
|
||||
|
@ -59,7 +60,7 @@
|
|||
"@types/mockdate": "^2.0.0",
|
||||
"@types/mongodb": "^2.2.7",
|
||||
"@types/nedb": "^1.8.3",
|
||||
"@types/nodemailer": "^1.3.32",
|
||||
"@types/nodemailer": "^3.1.3",
|
||||
"@types/object-path": "^0.9.28",
|
||||
"@types/proxyquire": "^1.3.27",
|
||||
"@types/query-string": "^4.3.1",
|
||||
|
|
|
@ -13,14 +13,11 @@ if (!configurationFilepath) {
|
|||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log("Parse configuration file: %s", configurationFilepath);
|
||||
|
||||
const yamlContent = YAML.load(configurationFilepath);
|
||||
|
||||
const deps: GlobalDependencies = {
|
||||
u2f: require("u2f"),
|
||||
dovehash: require("dovehash"),
|
||||
nodemailer: require("nodemailer"),
|
||||
ldapjs: require("ldapjs"),
|
||||
session: require("express-session"),
|
||||
winston: require("winston"),
|
||||
|
@ -29,8 +26,5 @@ const deps: GlobalDependencies = {
|
|||
ConnectRedis: require("connect-redis")
|
||||
};
|
||||
|
||||
const server = new Server();
|
||||
server.start(yamlContent, deps)
|
||||
.then(() => {
|
||||
console.log("The server is started!");
|
||||
});
|
||||
const server = new Server(deps);
|
||||
server.start(yamlContent, deps);
|
||||
|
|
|
@ -34,7 +34,7 @@ const INITIAL_AUTHENTICATION_SESSION: AuthenticationSession = {
|
|||
|
||||
export function reset(req: express.Request): void {
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
logger.debug("Authentication session %s is being reset.", req.sessionID);
|
||||
logger.debug(req, "Authentication session %s is being reset.", req.sessionID);
|
||||
req.session.auth = Object.assign({}, INITIAL_AUTHENTICATION_SESSION, {});
|
||||
}
|
||||
|
||||
|
@ -42,12 +42,12 @@ export function get(req: express.Request): BluebirdPromise<AuthenticationSession
|
|||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
if (!req.session) {
|
||||
const errorMsg = "Something is wrong with session cookies. Please check Redis is running and Authelia can contact it.";
|
||||
logger.error(errorMsg);
|
||||
logger.error(req, errorMsg);
|
||||
return BluebirdPromise.reject(new Error(errorMsg));
|
||||
}
|
||||
|
||||
if (!req.session.auth) {
|
||||
logger.debug("Authentication session %s was undefined. Resetting.", req.sessionID);
|
||||
logger.debug(req, "Authentication session %s was undefined. Resetting.", req.sessionID);
|
||||
reset(req);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,33 @@
|
|||
import express = require("express");
|
||||
import { Winston } from "winston";
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { IRequestLogger } from "./logging/IRequestLogger";
|
||||
|
||||
function replyWithError(res: express.Response, code: number, logger: Winston): (err: Error) => void {
|
||||
function replyWithError(req: express.Request, res: express.Response,
|
||||
code: number, logger: IRequestLogger): (err: Error) => void {
|
||||
return function (err: Error): void {
|
||||
logger.error("Reply with error %d: %s", code, err.stack);
|
||||
logger.error(req, "Reply with error %d: %s", code, err.message);
|
||||
logger.debug(req, "%s", err.stack);
|
||||
res.status(code);
|
||||
res.send();
|
||||
};
|
||||
}
|
||||
|
||||
export function replyWithError400(res: express.Response, logger: Winston) {
|
||||
return replyWithError(res, 400, logger);
|
||||
export function replyWithError400(req: express.Request,
|
||||
res: express.Response, logger: IRequestLogger) {
|
||||
return replyWithError(req, res, 400, logger);
|
||||
}
|
||||
|
||||
export function replyWithError401(res: express.Response, logger: Winston) {
|
||||
return replyWithError(res, 401, logger);
|
||||
export function replyWithError401(req: express.Request,
|
||||
res: express.Response, logger: IRequestLogger) {
|
||||
return replyWithError(req, res, 401, logger);
|
||||
}
|
||||
|
||||
export function replyWithError403(res: express.Response, logger: Winston) {
|
||||
return replyWithError(res, 403, logger);
|
||||
export function replyWithError403(req: express.Request,
|
||||
res: express.Response, logger: IRequestLogger) {
|
||||
return replyWithError(req, res, 403, logger);
|
||||
}
|
||||
|
||||
export function replyWithError500(res: express.Response, logger: Winston) {
|
||||
return replyWithError(res, 500, logger);
|
||||
export function replyWithError500(req: express.Request,
|
||||
res: express.Response, logger: IRequestLogger) {
|
||||
return replyWithError(req, res, 500, logger);
|
||||
}
|
|
@ -33,20 +33,20 @@ export interface IdentityValidable {
|
|||
mailSubject(): string;
|
||||
}
|
||||
|
||||
function createAndSaveToken(userid: string, challenge: string, userDataStore: IUserDataStore, logger: Winston): BluebirdPromise<string> {
|
||||
function createAndSaveToken(userid: string, challenge: string, userDataStore: IUserDataStore)
|
||||
: 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.produceIdentityValidationToken(userid, token, challenge, five_minutes)
|
||||
.then(function () {
|
||||
return BluebirdPromise.resolve(token);
|
||||
});
|
||||
}
|
||||
|
||||
function consumeToken(token: string, challenge: string, userDataStore: IUserDataStore, logger: Winston): BluebirdPromise<IdentityValidationDocument> {
|
||||
logger.debug("identity_check: consume token %s", token);
|
||||
function consumeToken(token: string, challenge: string, userDataStore: IUserDataStore)
|
||||
: BluebirdPromise<IdentityValidationDocument> {
|
||||
return userDataStore.consumeIdentityValidationToken(token, challenge);
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
|
|||
|
||||
let authSession: AuthenticationSession.AuthenticationSession;
|
||||
const identityToken = objectPath.get<express.Request, string>(req, "query.identity_token");
|
||||
logger.info("GET identity_check: identity token provided is %s", identityToken);
|
||||
logger.debug(req, "Identity token provided is %s", identityToken);
|
||||
|
||||
return checkIdentityToken(req, identityToken)
|
||||
.then(function () {
|
||||
|
@ -81,7 +81,7 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
|
|||
authSession = _authSession;
|
||||
})
|
||||
.then(function () {
|
||||
return consumeToken(identityToken, handler.challenge(), userDataStore, logger);
|
||||
return consumeToken(identityToken, handler.challenge(), userDataStore);
|
||||
})
|
||||
.then(function (doc: IdentityValidationDocument) {
|
||||
authSession.identity_check = {
|
||||
|
@ -91,9 +91,9 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
|
|||
handler.postValidationResponse(req, res);
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(res, logger))
|
||||
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(res, logger))
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger))
|
||||
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(req, res, logger))
|
||||
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -104,33 +104,32 @@ export function get_start_validation(handler: IdentityValidable, postValidationE
|
|||
const notifier = ServerVariablesHandler.getNotifier(req.app);
|
||||
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
||||
let identity: Identity.Identity;
|
||||
logger.info("Identity Validation: Start identity validation");
|
||||
|
||||
return handler.preValidationInit(req)
|
||||
.then(function (id: Identity.Identity) {
|
||||
logger.debug("Identity Validation: retrieved identity is %s", JSON.stringify(id));
|
||||
identity = id;
|
||||
const email = identity.email;
|
||||
const userid = identity.userid;
|
||||
logger.info(req, "Start identity validation of user \"%s\"", userid);
|
||||
|
||||
if (!(email && userid))
|
||||
return BluebirdPromise.reject(new Exceptions.IdentityError("Missing user id or email address"));
|
||||
|
||||
return createAndSaveToken(userid, handler.challenge(), userDataStore, logger);
|
||||
return createAndSaveToken(userid, handler.challenge(), userDataStore);
|
||||
})
|
||||
.then(function (token: string) {
|
||||
const host = req.get("Host");
|
||||
const link_url = util.format("https://%s%s?identity_token=%s", host, postValidationEndpoint, token);
|
||||
logger.info("POST identity_check: notification sent to user %s", identity.userid);
|
||||
logger.info(req, "Notification sent to user \"%s\"", identity.userid);
|
||||
return notifier.notify(identity, handler.mailSubject(), link_url);
|
||||
})
|
||||
.then(function () {
|
||||
handler.preValidationResponse(req, res);
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(res, logger))
|
||||
.catch(Exceptions.IdentityError, ErrorReplies.replyWithError400(res, logger))
|
||||
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(res, logger))
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger))
|
||||
.catch(Exceptions.IdentityError, ErrorReplies.replyWithError400(req, res, logger))
|
||||
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(req, res, logger))
|
||||
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||
};
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ import Endpoints = require("../../../shared/api");
|
|||
function withLog(fn: (req: Express.Request, res: Express.Response) => void) {
|
||||
return function(req: Express.Request, res: Express.Response) {
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
logger.info("Request %s handled on %s", req.method, req.originalUrl);
|
||||
logger.debug(req, "Headers = %s", JSON.stringify(req.headers));
|
||||
fn(req, res);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import ObjectPath = require("object-path");
|
||||
|
||||
import { AccessController } from "./access_control/AccessController";
|
||||
import { AppConfiguration, UserConfiguration } from "./configuration/Configuration";
|
||||
|
@ -12,12 +13,16 @@ import { RestApi } from "./RestApi";
|
|||
import { Client } from "./ldap/Client";
|
||||
import { ServerVariablesHandler } from "./ServerVariablesHandler";
|
||||
import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder";
|
||||
import { GlobalLogger } from "./logging/GlobalLogger";
|
||||
import { RequestLogger } from "./logging/RequestLogger";
|
||||
|
||||
import * as Express from "express";
|
||||
import * as BodyParser from "body-parser";
|
||||
import * as Path from "path";
|
||||
import * as http from "http";
|
||||
|
||||
const addRequestId = require("express-request-id")();
|
||||
|
||||
// Constants
|
||||
|
||||
const TRUST_PROXY = "trust proxy";
|
||||
|
@ -25,9 +30,19 @@ const VIEWS = "views";
|
|||
const VIEW_ENGINE = "view engine";
|
||||
const PUG = "pug";
|
||||
|
||||
function clone(obj: any) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
export default class Server {
|
||||
private httpServer: http.Server;
|
||||
private globalLogger: GlobalLogger;
|
||||
private requestLogger: RequestLogger;
|
||||
|
||||
constructor(deps: GlobalDependencies) {
|
||||
this.globalLogger = new GlobalLogger(deps.winston);
|
||||
this.requestLogger = new RequestLogger(deps.winston);
|
||||
}
|
||||
|
||||
private setupExpressApplication(config: AppConfiguration, app: Express.Application, deps: GlobalDependencies): void {
|
||||
const viewsDirectory = Path.resolve(__dirname, "../views");
|
||||
|
@ -39,6 +54,7 @@ export default class Server {
|
|||
app.use(BodyParser.urlencoded({ extended: false }));
|
||||
app.use(BodyParser.json());
|
||||
app.use(deps.session(expressSessionOptions));
|
||||
app.use(addRequestId);
|
||||
app.disable("x-powered-by");
|
||||
|
||||
app.set(TRUST_PROXY, 1);
|
||||
|
@ -48,39 +64,61 @@ export default class Server {
|
|||
RestApi.setup(app);
|
||||
}
|
||||
|
||||
private adaptConfiguration(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): AppConfiguration {
|
||||
const config = ConfigurationAdapter.adapt(yamlConfiguration);
|
||||
private displayConfigurations(userConfiguration: UserConfiguration,
|
||||
appConfiguration: AppConfiguration) {
|
||||
const displayableUserConfiguration = clone(userConfiguration);
|
||||
const displayableAppConfiguration = clone(appConfiguration);
|
||||
const STARS = "*****";
|
||||
|
||||
// by default the level of logs is info
|
||||
deps.winston.level = config.logs_level;
|
||||
console.log("Log level = ", deps.winston.level);
|
||||
displayableUserConfiguration.ldap.password = STARS;
|
||||
displayableUserConfiguration.session.secret = STARS;
|
||||
if (displayableUserConfiguration.notifier && displayableUserConfiguration.notifier.gmail)
|
||||
displayableUserConfiguration.notifier.gmail.password = STARS;
|
||||
if (displayableUserConfiguration.notifier && displayableUserConfiguration.notifier.smtp)
|
||||
displayableUserConfiguration.notifier.smtp.password = STARS;
|
||||
|
||||
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;
|
||||
displayableAppConfiguration.ldap.password = STARS;
|
||||
displayableAppConfiguration.session.secret = STARS;
|
||||
if (displayableAppConfiguration.notifier && displayableAppConfiguration.notifier.gmail)
|
||||
displayableAppConfiguration.notifier.gmail.password = STARS;
|
||||
if (displayableAppConfiguration.notifier && displayableAppConfiguration.notifier.smtp)
|
||||
displayableAppConfiguration.notifier.smtp.password = STARS;
|
||||
|
||||
this.globalLogger.debug("User configuration is %s",
|
||||
JSON.stringify(displayableUserConfiguration, undefined, 2));
|
||||
this.globalLogger.debug("Adapted configuration is %s",
|
||||
JSON.stringify(displayableAppConfiguration, undefined, 2));
|
||||
}
|
||||
|
||||
private setup(config: AppConfiguration, app: Express.Application, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
this.setupExpressApplication(config, app, deps);
|
||||
return ServerVariablesHandler.initialize(app, config, deps);
|
||||
return ServerVariablesHandler.initialize(app, config, this.requestLogger, deps);
|
||||
}
|
||||
|
||||
private startServer(app: Express.Application, port: number) {
|
||||
const that = this;
|
||||
return new BluebirdPromise<void>((resolve, reject) => {
|
||||
this.httpServer = app.listen(port, function (err: string) {
|
||||
console.log("Listening on %d...", port);
|
||||
that.globalLogger.info("Listening on port %d...", port);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
start(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
start(userConfiguration: UserConfiguration, deps: GlobalDependencies)
|
||||
: BluebirdPromise<void> {
|
||||
const that = this;
|
||||
const app = Express();
|
||||
const config = this.adaptConfiguration(yamlConfiguration, deps);
|
||||
return this.setup(config, app, deps)
|
||||
|
||||
const appConfiguration = ConfigurationAdapter.adapt(userConfiguration);
|
||||
|
||||
// by default the level of logs is info
|
||||
deps.winston.level = userConfiguration.logs_level;
|
||||
this.displayConfigurations(userConfiguration, appConfiguration);
|
||||
|
||||
return this.setup(appConfiguration, app, deps)
|
||||
.then(function () {
|
||||
return that.startServer(app, config.port);
|
||||
return that.startServer(app, appConfiguration.port);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
|
||||
import U2F = require("u2f");
|
||||
|
||||
import { IRequestLogger } from "./logging/IRequestLogger";
|
||||
import { IAuthenticator } from "./ldap/IAuthenticator";
|
||||
import { IPasswordUpdater } from "./ldap/IPasswordUpdater";
|
||||
import { IEmailsRetriever } from "./ldap/IEmailsRetriever";
|
||||
|
||||
import { TOTPValidator } from "./TOTPValidator";
|
||||
import { TOTPGenerator } from "./TOTPGenerator";
|
||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||
import { INotifier } from "./notifiers/INotifier";
|
||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||
import Configuration = require("./configuration/Configuration");
|
||||
import { AccessController } from "./access_control/AccessController";
|
||||
|
||||
|
||||
export interface ServerVariables {
|
||||
logger: IRequestLogger;
|
||||
ldapAuthenticator: IAuthenticator;
|
||||
ldapPasswordUpdater: IPasswordUpdater;
|
||||
ldapEmailsRetriever: IEmailsRetriever;
|
||||
totpValidator: TOTPValidator;
|
||||
totpGenerator: TOTPGenerator;
|
||||
u2f: typeof U2F;
|
||||
userDataStore: IUserDataStore;
|
||||
notifier: INotifier;
|
||||
regulator: AuthenticationRegulator;
|
||||
config: Configuration.AppConfiguration;
|
||||
accessController: AccessController;
|
||||
}
|
|
@ -2,6 +2,10 @@
|
|||
import winston = require("winston");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import U2F = require("u2f");
|
||||
import Nodemailer = require("nodemailer");
|
||||
|
||||
import { IRequestLogger } from "./logging/IRequestLogger";
|
||||
import { RequestLogger } from "./logging/RequestLogger";
|
||||
|
||||
import { IAuthenticator } from "./ldap/IAuthenticator";
|
||||
import { IPasswordUpdater } from "./ldap/IPasswordUpdater";
|
||||
|
@ -14,40 +18,28 @@ import { LdapClientFactory } from "./ldap/LdapClientFactory";
|
|||
|
||||
import { TOTPValidator } from "./TOTPValidator";
|
||||
import { TOTPGenerator } from "./TOTPGenerator";
|
||||
|
||||
import { NotifierFactory } from "./notifiers/NotifierFactory";
|
||||
import { MailSenderBuilder } from "./notifiers/MailSenderBuilder";
|
||||
|
||||
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 { ServerVariables } from "./ServerVariables";
|
||||
|
||||
import express = require("express");
|
||||
|
||||
export const VARIABLES_KEY = "authelia-variables";
|
||||
|
||||
export interface ServerVariables {
|
||||
logger: typeof winston;
|
||||
ldapAuthenticator: IAuthenticator;
|
||||
ldapPasswordUpdater: IPasswordUpdater;
|
||||
ldapEmailsRetriever: IEmailsRetriever;
|
||||
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) {
|
||||
|
@ -73,8 +65,10 @@ class UserDataStoreFactory {
|
|||
}
|
||||
|
||||
export class ServerVariablesHandler {
|
||||
static initialize(app: express.Application, config: Configuration.AppConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
|
||||
static initialize(app: express.Application, config: Configuration.AppConfiguration, requestLogger: IRequestLogger,
|
||||
deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
|
||||
const notifier = NotifierFactory.build(config.notifier, mailSenderBuilder);
|
||||
const ldapClientFactory = new LdapClientFactory(config.ldap, deps.ldapjs);
|
||||
const clientFactory = new ClientFactory(config.ldap, ldapClientFactory, deps.dovehash, deps.winston);
|
||||
|
||||
|
@ -96,20 +90,20 @@ export class ServerVariablesHandler {
|
|||
ldapAuthenticator: ldapAuthenticator,
|
||||
ldapPasswordUpdater: ldapPasswordUpdater,
|
||||
ldapEmailsRetriever: ldapEmailsRetriever,
|
||||
logger: deps.winston,
|
||||
logger: requestLogger,
|
||||
notifier: notifier,
|
||||
regulator: regulator,
|
||||
totpGenerator: totpGenerator,
|
||||
totpValidator: totpValidator,
|
||||
u2f: deps.u2f,
|
||||
userDataStore: userDataStore
|
||||
userDataStore: userDataStore,
|
||||
};
|
||||
|
||||
app.set(VARIABLES_KEY, variables);
|
||||
});
|
||||
}
|
||||
|
||||
static getLogger(app: express.Application): typeof winston {
|
||||
static getLogger(app: express.Application): IRequestLogger {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).logger;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import { IGlobalLogger } from "./IGlobalLogger";
|
||||
import Util = require("util");
|
||||
import Express = require("express");
|
||||
import Winston = require("winston");
|
||||
|
||||
declare module "express" {
|
||||
interface Request {
|
||||
id: string;
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalLogger implements IGlobalLogger {
|
||||
private winston: typeof Winston;
|
||||
constructor(winston: typeof Winston) {
|
||||
this.winston = winston;
|
||||
}
|
||||
|
||||
private buildMessage(message: string, ...args: any[]): string {
|
||||
return Util.format("date='%s' message='%s'", new Date(),
|
||||
Util.format(message, ...args));
|
||||
}
|
||||
|
||||
info(message: string, ...args: any[]): void {
|
||||
this.winston.info(this.buildMessage(message, ...args));
|
||||
}
|
||||
|
||||
debug(message: string, ...args: any[]): void {
|
||||
this.winston.debug(this.buildMessage(message, ...args));
|
||||
}
|
||||
|
||||
error(message: string, ...args: any[]): void {
|
||||
this.winston.debug(this.buildMessage(message, ...args));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export interface IGlobalLogger {
|
||||
info(message: string, ...args: any[]): void;
|
||||
debug(message: string, ...args: any[]): void;
|
||||
error(message: string, ...args: any[]): void;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import Express = require("express");
|
||||
|
||||
export interface IRequestLogger {
|
||||
info(req: Express.Request, message: string, ...args: any[]): void;
|
||||
debug(req: Express.Request, message: string, ...args: any[]): void;
|
||||
error(req: Express.Request, message: string, ...args: any[]): void;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { IRequestLogger } from "./IRequestLogger";
|
||||
import Util = require("util");
|
||||
import Express = require("express");
|
||||
import Winston = require("winston");
|
||||
|
||||
declare module "express" {
|
||||
interface Request {
|
||||
id: string;
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestLogger implements IRequestLogger {
|
||||
private winston: typeof Winston;
|
||||
|
||||
constructor(winston: typeof Winston) {
|
||||
this.winston = winston;
|
||||
}
|
||||
|
||||
private formatHeader(req: Express.Request) {
|
||||
const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
|
||||
return Util.format("date='%s' method='%s', path='%s' requestId='%s' sessionId='%s' ip='%s'",
|
||||
new Date(), req.method, req.path, req.id, req.sessionID, ip);
|
||||
}
|
||||
|
||||
private formatBody(message: string) {
|
||||
return Util.format("message='%s'", message);
|
||||
}
|
||||
|
||||
private formatMessage(req: Express.Request, message: string) {
|
||||
return Util.format("%s %s", this.formatHeader(req),
|
||||
this.formatBody(message));
|
||||
}
|
||||
|
||||
info(req: Express.Request, message: string, ...args: any[]): void {
|
||||
this.winston.info(this.formatMessage(req, message), ...args);
|
||||
}
|
||||
|
||||
debug(req: Express.Request, message: string, ...args: any[]): void {
|
||||
this.winston.debug(this.formatMessage(req, message), ...args);
|
||||
}
|
||||
|
||||
error(req: Express.Request, message: string, ...args: any[]): void {
|
||||
this.winston.error(this.formatMessage(req, message), ...args);
|
||||
}
|
||||
}
|
|
@ -1,24 +1,16 @@
|
|||
|
||||
import * as BluebirdPromise from "bluebird";
|
||||
import nodemailer = require("nodemailer");
|
||||
|
||||
import { Nodemailer } from "../../../types/Dependencies";
|
||||
import { AbstractEmailNotifier } from "../notifiers/AbstractEmailNotifier";
|
||||
import { GmailNotifierConfiguration } from "../configuration/Configuration";
|
||||
import { IMailSender } from "./IMailSender";
|
||||
|
||||
export class GMailNotifier extends AbstractEmailNotifier {
|
||||
private transporter: any;
|
||||
private mailSender: IMailSender;
|
||||
|
||||
constructor(options: GmailNotifierConfiguration, nodemailer: Nodemailer) {
|
||||
constructor(options: GmailNotifierConfiguration, mailSender: IMailSender) {
|
||||
super();
|
||||
const transporter = nodemailer.createTransport({
|
||||
service: "gmail",
|
||||
auth: {
|
||||
user: options.username,
|
||||
pass: options.password
|
||||
}
|
||||
});
|
||||
this.transporter = BluebirdPromise.promisifyAll(transporter);
|
||||
this.mailSender = mailSender;
|
||||
}
|
||||
|
||||
sendEmail(email: string, subject: string, content: string) {
|
||||
|
@ -28,6 +20,6 @@ export class GMailNotifier extends AbstractEmailNotifier {
|
|||
subject: subject,
|
||||
html: content
|
||||
};
|
||||
return this.transporter.sendMailAsync(mailOptions);
|
||||
return this.mailSender.send(mailOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import Nodemailer = require("nodemailer");
|
||||
|
||||
export interface IMailSender {
|
||||
send(mailOptions: Nodemailer.SendMailOptions): BluebirdPromise<void>;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { IMailSender } from "./IMailSender";
|
||||
import { SmtpNotifierConfiguration, GmailNotifierConfiguration } from "../configuration/Configuration";
|
||||
|
||||
export interface IMailSenderBuilder {
|
||||
buildGmail(options: GmailNotifierConfiguration): IMailSender;
|
||||
buildSmtp(options: SmtpNotifierConfiguration): IMailSender;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import { IMailSender } from "./IMailSender";
|
||||
import Nodemailer = require("nodemailer");
|
||||
import NodemailerDirectTransport = require("nodemailer-direct-transport");
|
||||
import NodemailerSmtpTransport = require("nodemailer-smtp-transport");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
export class MailSender implements IMailSender {
|
||||
private transporter: Nodemailer.Transporter;
|
||||
|
||||
constructor(options: NodemailerDirectTransport.DirectOptions |
|
||||
NodemailerSmtpTransport.SmtpOptions, nodemailer: typeof Nodemailer) {
|
||||
this.transporter = nodemailer.createTransport(options);
|
||||
}
|
||||
|
||||
verify(): BluebirdPromise<void> {
|
||||
const that = this;
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
that.transporter.verify(function (error: Error, success: any) {
|
||||
if (error) {
|
||||
reject(new Error("Unable to connect to SMTP server. \
|
||||
Please check the service is running and your credentials are correct."));
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
send(mailOptions: Nodemailer.SendMailOptions): BluebirdPromise<void> {
|
||||
const that = this;
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
that.transporter.sendMail(mailOptions, (error: Error,
|
||||
data: Nodemailer.SentMessageInfo) => {
|
||||
if (error) {
|
||||
reject(new Error("Error while sending email: " + error.message));
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { IMailSender } from "./IMailSender";
|
||||
import { IMailSenderBuilder } from "./IMailSenderBuilder";
|
||||
import { MailSender } from "./MailSender";
|
||||
import Nodemailer = require("nodemailer");
|
||||
import NodemailerSmtpTransport = require("nodemailer-smtp-transport");
|
||||
import { SmtpNotifierConfiguration, GmailNotifierConfiguration } from "../configuration/Configuration";
|
||||
|
||||
export class MailSenderBuilder implements IMailSenderBuilder {
|
||||
private nodemailer: typeof Nodemailer;
|
||||
|
||||
constructor(nodemailer: typeof Nodemailer) {
|
||||
this.nodemailer = nodemailer;
|
||||
}
|
||||
|
||||
buildGmail(options: GmailNotifierConfiguration): IMailSender {
|
||||
const gmailOptions = {
|
||||
service: "gmail",
|
||||
auth: {
|
||||
user: options.username,
|
||||
pass: options.password
|
||||
}
|
||||
};
|
||||
return new MailSender(gmailOptions, this.nodemailer);
|
||||
}
|
||||
|
||||
buildSmtp(options: SmtpNotifierConfiguration): IMailSender {
|
||||
const smtpOptions: NodemailerSmtpTransport.SmtpOptions = {
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
secure: options.secure, // upgrade later with STARTTLS
|
||||
auth: {
|
||||
user: options.username,
|
||||
pass: options.password
|
||||
}
|
||||
};
|
||||
return new MailSender(smtpOptions, this.nodemailer);
|
||||
}
|
||||
}
|
|
@ -1,18 +1,22 @@
|
|||
|
||||
import { NotifierConfiguration } from "../configuration/Configuration";
|
||||
import { Nodemailer } from "../../../types/Dependencies";
|
||||
import Nodemailer = require("nodemailer");
|
||||
import { INotifier } from "./INotifier";
|
||||
|
||||
import { GMailNotifier } from "./GMailNotifier";
|
||||
import { SmtpNotifier } from "./SmtpNotifier";
|
||||
import { IMailSender } from "./IMailSender";
|
||||
import { IMailSenderBuilder } from "./IMailSenderBuilder";
|
||||
|
||||
export class NotifierFactory {
|
||||
static build(options: NotifierConfiguration, nodemailer: Nodemailer): INotifier {
|
||||
static build(options: NotifierConfiguration, mailSenderBuilder: IMailSenderBuilder): INotifier {
|
||||
if ("gmail" in options) {
|
||||
return new GMailNotifier(options.gmail, nodemailer);
|
||||
const mailSender = mailSenderBuilder.buildGmail(options.gmail);
|
||||
return new GMailNotifier(options.gmail, mailSender);
|
||||
}
|
||||
else if ("smtp" in options) {
|
||||
return new SmtpNotifier(options.smtp, nodemailer);
|
||||
const mailSender = mailSenderBuilder.buildSmtp(options.smtp);
|
||||
return new SmtpNotifier(options.smtp, mailSender);
|
||||
}
|
||||
else {
|
||||
throw new Error("No available notifier option detected.");
|
||||
|
|
|
@ -1,37 +1,18 @@
|
|||
|
||||
|
||||
import * as BluebirdPromise from "bluebird";
|
||||
import Nodemailer = require("nodemailer");
|
||||
|
||||
import { IMailSender } from "./IMailSender";
|
||||
import { AbstractEmailNotifier } from "../notifiers/AbstractEmailNotifier";
|
||||
import { SmtpNotifierConfiguration } from "../configuration/Configuration";
|
||||
|
||||
export class SmtpNotifier extends AbstractEmailNotifier {
|
||||
private transporter: any;
|
||||
private mailSender: IMailSender;
|
||||
|
||||
constructor(options: SmtpNotifierConfiguration, nodemailer: typeof Nodemailer) {
|
||||
constructor(options: SmtpNotifierConfiguration,
|
||||
mailSender: IMailSender) {
|
||||
super();
|
||||
const smtpOptions = {
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
secure: options.secure, // upgrade later with STARTTLS
|
||||
auth: {
|
||||
user: options.username,
|
||||
pass: options.password
|
||||
}
|
||||
};
|
||||
const transporter = nodemailer.createTransport(smtpOptions);
|
||||
this.transporter = BluebirdPromise.promisifyAll(transporter);
|
||||
|
||||
// verify connection configuration
|
||||
transporter.verify(function (error, success) {
|
||||
if (error) {
|
||||
throw new Error("Unable to connect to SMTP server. \
|
||||
Please check the service is running and your credentials are correct.");
|
||||
} else {
|
||||
console.log("SMTP Server is ready to take our messages");
|
||||
}
|
||||
});
|
||||
this.mailSender = mailSender;
|
||||
}
|
||||
|
||||
sendEmail(email: string, subject: string, content: string) {
|
||||
|
@ -41,11 +22,7 @@ Please check the service is running and your credentials are correct.");
|
|||
subject: subject,
|
||||
html: content
|
||||
};
|
||||
return this.transporter.sendMail(mailOptions, (error: Error, data: string) => {
|
||||
if (error) {
|
||||
return console.log(error);
|
||||
}
|
||||
console.log("Message sent: %s", JSON.stringify(data));
|
||||
});
|
||||
const that = this;
|
||||
return this.mailSender.send(mailOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,6 @@ export default function (callback: Handler): Handler {
|
|||
.then(function () {
|
||||
return callback(req, res);
|
||||
})
|
||||
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(res, logger));
|
||||
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger));
|
||||
};
|
||||
}
|
|
@ -8,10 +8,6 @@ import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
|||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
|
||||
logger.debug("First factor: headers are %s", JSON.stringify(req.headers));
|
||||
|
||||
res.render("firstfactor", {
|
||||
first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
|
||||
reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET
|
||||
|
|
|
@ -22,7 +22,7 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
|
||||
if (!username || !password) {
|
||||
const err = new Error("No username or password");
|
||||
ErrorReplies.replyWithError401(res, logger)(err);
|
||||
ErrorReplies.replyWithError401(req, res, logger)(err);
|
||||
return BluebirdPromise.reject(err);
|
||||
}
|
||||
|
||||
|
@ -30,21 +30,18 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
const accessController = ServerVariablesHandler.getAccessController(req.app);
|
||||
let authSession: AuthenticationSession.AuthenticationSession;
|
||||
|
||||
logger.info("1st factor: Starting authentication of user \"%s\"", username);
|
||||
logger.debug("1st factor: Start bind operation against LDAP");
|
||||
logger.debug("1st factor: username=%s", username);
|
||||
|
||||
logger.info(req, "Starting authentication of user \"%s\"", username);
|
||||
return AuthenticationSession.get(req)
|
||||
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
|
||||
authSession = _authSession;
|
||||
return regulator.regulate(username);
|
||||
})
|
||||
.then(function () {
|
||||
logger.info("1st factor: No regulation applied.");
|
||||
logger.info(req, "No regulation applied.");
|
||||
return ldap.authenticate(username, password);
|
||||
})
|
||||
.then(function (groupsAndEmails: GroupsAndEmails) {
|
||||
logger.info("1st factor: LDAP binding successful. Retrieved information about user are %s",
|
||||
logger.info(req, "LDAP binding successful. Retrieved information about user are %s",
|
||||
JSON.stringify(groupsAndEmails));
|
||||
authSession.userid = username;
|
||||
authSession.first_factor = true;
|
||||
|
@ -56,43 +53,40 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
|
||||
if (!emails || emails.length <= 0) {
|
||||
const errMessage = "No emails found. The user should have at least one email address to reset password.";
|
||||
logger.error("1s factor: %s", errMessage);
|
||||
logger.error(req, "%s", errMessage);
|
||||
return BluebirdPromise.reject(new Error(errMessage));
|
||||
}
|
||||
|
||||
authSession.email = emails[0];
|
||||
authSession.groups = groups;
|
||||
|
||||
logger.debug("1st factor: Mark successful authentication to regulator.");
|
||||
logger.debug(req, "Mark successful authentication to regulator.");
|
||||
regulator.mark(username, true);
|
||||
|
||||
logger.debug("1st factor: Redirect URL is %s", redirectUrl);
|
||||
logger.debug("1st factor: %s? %s", Constants.ONLY_BASIC_AUTH_QUERY_PARAM, onlyBasicAuth);
|
||||
|
||||
if (onlyBasicAuth) {
|
||||
res.send({
|
||||
redirect: redirectUrl
|
||||
});
|
||||
logger.debug("1st factor: redirect to '%s'", redirectUrl);
|
||||
logger.debug(req, "Redirect to '%s'", redirectUrl);
|
||||
}
|
||||
else {
|
||||
let newRedirectUrl = Endpoint.SECOND_FACTOR_GET;
|
||||
if (redirectUrl !== "undefined") {
|
||||
newRedirectUrl += "?redirect=" + encodeURIComponent(redirectUrl);
|
||||
}
|
||||
logger.debug("1st factor: redirect to '%s'", newRedirectUrl, typeof redirectUrl);
|
||||
logger.debug(req, "Redirect to '%s'", newRedirectUrl, typeof redirectUrl);
|
||||
res.send({
|
||||
redirect: newRedirectUrl
|
||||
});
|
||||
}
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(res, logger))
|
||||
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(req, res, logger))
|
||||
.catch(exceptions.LdapBindError, function (err: Error) {
|
||||
regulator.mark(username, false);
|
||||
return ErrorReplies.replyWithError401(res, logger)(err);
|
||||
return ErrorReplies.replyWithError401(req, res, logger)(err);
|
||||
})
|
||||
.catch(exceptions.AuthenticationRegulationError, ErrorReplies.replyWithError403(res, logger))
|
||||
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError401(res, logger))
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
.catch(exceptions.AuthenticationRegulationError, ErrorReplies.replyWithError403(req, res, logger))
|
||||
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError401(req, res, logger))
|
||||
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
return AuthenticationSession.get(req)
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
logger.info("POST reset-password: User %s wants to reset his/her password.",
|
||||
logger.info(req, "User %s wants to reset his/her password.",
|
||||
authSession.identity_check.userid);
|
||||
logger.info("POST reset-password: Challenge %s", authSession.identity_check.challenge);
|
||||
logger.debug(req, "Challenge %s", authSession.identity_check.challenge);
|
||||
|
||||
if (authSession.identity_check.challenge != Constants.CHALLENGE) {
|
||||
res.status(403);
|
||||
|
@ -30,12 +30,12 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
return ldapPasswordUpdater.updatePassword(authSession.identity_check.userid, newPassword);
|
||||
})
|
||||
.then(function () {
|
||||
logger.info("POST reset-password: Password reset for user '%s'",
|
||||
logger.info(req, "Password reset for user '%s'",
|
||||
authSession.identity_check.userid);
|
||||
AuthenticationSession.reset(req);
|
||||
res.status(204);
|
||||
res.send();
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export default class PasswordResetHandler implements IdentityValidable {
|
|||
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);
|
||||
logger.debug(req, "User '%s' requested a password reset", userid);
|
||||
if (!userid)
|
||||
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No user id provided"));
|
||||
|
||||
|
@ -29,7 +29,6 @@ export default class PasswordResetHandler implements IdentityValidable {
|
|||
return emailsRetriever.retrieve(userid)
|
||||
.then(function (emails: string[]) {
|
||||
if (!emails && emails.length <= 0) throw new Error("No email found");
|
||||
|
||||
const identity = {
|
||||
email: emails[0],
|
||||
userid: userid
|
||||
|
|
|
@ -10,8 +10,6 @@ const TEMPLATE_NAME = "secondfactor";
|
|||
export default FirstFactorBlocker.default(handler);
|
||||
|
||||
function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
logger.debug("secondfactor request is coming from %s", req.originalUrl);
|
||||
res.render(TEMPLATE_NAME, {
|
||||
totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
|
||||
u2f_identity_start_endpoint: Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET
|
||||
|
|
|
@ -6,8 +6,10 @@ import Endpoints = require("../../../../../shared/api");
|
|||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../AuthenticationSession");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
return AuthenticationSession.get(req)
|
||||
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
||||
const redirectUrl = req.query.redirect || Endpoints.FIRST_FACTOR_GET;
|
||||
|
@ -15,5 +17,6 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
redirection_url: redirectUrl
|
||||
});
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||
}
|
|
@ -71,10 +71,10 @@ export default class RegistrationHandler implements IdentityValidable {
|
|||
const totpGenerator = ServerVariablesHandler.getTOTPGenerator(req.app);
|
||||
const secret = totpGenerator.generate();
|
||||
|
||||
logger.debug("POST new-totp-secret: save the TOTP secret in DB");
|
||||
logger.debug(req, "Save the TOTP secret in DB");
|
||||
return userDataStore.saveTOTPSecret(userid, secret)
|
||||
.then(function () {
|
||||
objectPath.set(req, "session", undefined);
|
||||
AuthenticationSession.reset(req);
|
||||
|
||||
res.render(Constants.TEMPLATE_NAME, {
|
||||
base32_secret: secret.base32,
|
||||
|
@ -83,7 +83,7 @@ export default class RegistrationHandler implements IdentityValidable {
|
|||
});
|
||||
});
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||
}
|
||||
|
||||
mailSubject(): string {
|
||||
|
|
|
@ -25,19 +25,19 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr
|
|||
return AuthenticationSession.get(req)
|
||||
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
|
||||
authSession = _authSession;
|
||||
logger.info("POST 2ndfactor totp: Initiate TOTP validation for user %s", authSession.userid);
|
||||
logger.info(req, "Initiate TOTP validation for user '%s'", authSession.userid);
|
||||
return userDataStore.retrieveTOTPSecret(authSession.userid);
|
||||
})
|
||||
.then(function (doc: TOTPSecretDocument) {
|
||||
logger.debug("POST 2ndfactor totp: TOTP secret is %s", JSON.stringify(doc));
|
||||
logger.debug(req, "TOTP secret is %s", JSON.stringify(doc));
|
||||
return totpValidator.validate(token, doc.secret.base32);
|
||||
})
|
||||
.then(function () {
|
||||
logger.debug("POST 2ndfactor totp: TOTP validation succeeded");
|
||||
logger.debug(req, "TOTP validation succeeded");
|
||||
authSession.second_factor = true;
|
||||
redirect(req, res);
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(exceptions.InvalidTOTPError, ErrorReplies.replyWithError401(res, logger))
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
.catch(exceptions.InvalidTOTPError, ErrorReplies.replyWithError401(req, res, logger))
|
||||
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||
}
|
||||
|
|
|
@ -43,9 +43,9 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
|
|||
return BluebirdPromise.reject(new Error("Bad challenge for registration request"));
|
||||
}
|
||||
|
||||
logger.info("U2F register: Finishing registration");
|
||||
logger.debug("U2F register: registrationRequest = %s", JSON.stringify(registrationRequest));
|
||||
logger.debug("U2F register: registrationResponse = %s", JSON.stringify(registrationResponse));
|
||||
logger.info(req, "Finishing registration");
|
||||
logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
|
||||
logger.debug(req, "RegistrationResponse = %s", JSON.stringify(registrationResponse));
|
||||
|
||||
return BluebirdPromise.resolve(u2f.checkRegistration(registrationRequest, registrationResponse));
|
||||
})
|
||||
|
@ -54,8 +54,8 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
|
|||
return BluebirdPromise.reject(new Error("Error while registering."));
|
||||
|
||||
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));
|
||||
logger.info(req, "Store registration and reply");
|
||||
logger.debug(req, "RegistrationResult = %s", JSON.stringify(registrationResult));
|
||||
const registration: U2FRegistration = {
|
||||
keyHandle: registrationResult.keyHandle,
|
||||
publicKey: registrationResult.publicKey
|
||||
|
@ -67,5 +67,5 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
|
|||
redirect(req, res);
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||
}
|
||||
|
|
|
@ -31,16 +31,15 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
|
|||
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));
|
||||
logger.info("U2F register_request: Starting registration for appId %s", appid);
|
||||
logger.info(req, "Starting registration for appId '%s'", appid);
|
||||
|
||||
return BluebirdPromise.resolve(u2f.request(appid));
|
||||
})
|
||||
.then(function (registrationRequest: U2f.Request) {
|
||||
logger.debug("U2F register_request: registrationRequest = %s", JSON.stringify(registrationRequest));
|
||||
logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
|
||||
authSession.register_request = registrationRequest;
|
||||
res.json(registrationRequest);
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||
}
|
|
@ -26,7 +26,7 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr
|
|||
authSession = _authSession;
|
||||
if (!authSession.sign_request) {
|
||||
const err = new Error("No sign request");
|
||||
ErrorReplies.replyWithError401(res, logger)(err);
|
||||
ErrorReplies.replyWithError401(req, res, logger)(err);
|
||||
return BluebirdPromise.reject(err);
|
||||
}
|
||||
|
||||
|
@ -39,17 +39,17 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr
|
|||
const u2f = ServerVariablesHandler.getU2F(req.app);
|
||||
const signRequest = authSession.sign_request;
|
||||
const signData: U2f.SignatureData = req.body;
|
||||
logger.info("U2F sign: Finish authentication");
|
||||
logger.info(req, "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");
|
||||
logger.info(req, "Successful authentication");
|
||||
authSession.second_factor = true;
|
||||
redirect(req, res);
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||
}
|
||||
|
||||
|
|
|
@ -33,8 +33,8 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr
|
|||
|
||||
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.registration.keyHandle));
|
||||
logger.info(req, "Start authentication of app '%s'", appId);
|
||||
logger.debug(req, "AppId = %s, keyHandle = %s", appId, JSON.stringify(doc.registration.keyHandle));
|
||||
|
||||
const request = u2f.request(appId, doc.registration.keyHandle);
|
||||
const authenticationMessage: SignMessage = {
|
||||
|
@ -44,13 +44,13 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr
|
|||
return BluebirdPromise.resolve(authenticationMessage);
|
||||
})
|
||||
.then(function (authenticationMessage: SignMessage) {
|
||||
logger.info("U2F sign_request: Store authentication request and reply");
|
||||
logger.debug("U2F sign_request: authenticationRequest=%s", authenticationMessage);
|
||||
logger.info(req, "Store authentication request and reply");
|
||||
logger.debug(req, "AuthenticationRequest = %s", authenticationMessage);
|
||||
authSession.sign_request = authenticationMessage.request;
|
||||
res.json(authenticationMessage);
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(exceptions.AccessDeniedError, ErrorReplies.replyWithError401(res, logger))
|
||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
||||
.catch(exceptions.AccessDeniedError, ErrorReplies.replyWithError401(req, res, logger))
|
||||
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,10 @@ import ErrorReplies = require("../../ErrorReplies");
|
|||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../AuthenticationSession");
|
||||
import Constants = require("../../../../../shared/constants");
|
||||
import Util = require("util");
|
||||
|
||||
const FIRST_FACTOR_NOT_VALIDATED_MESSAGE = "First factor not yet validated";
|
||||
const SECOND_FACTOR_NOT_VALIDATED_MESSAGE = "Second factor not yet validated";
|
||||
|
||||
function verify_filter(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
|
@ -16,30 +20,36 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
|
|||
|
||||
return AuthenticationSession.get(req)
|
||||
.then(function (authSession) {
|
||||
logger.debug("Verify: headers are %s", JSON.stringify(req.headers));
|
||||
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] + req.headers["x-original-uri"]));
|
||||
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] +
|
||||
req.headers["x-original-uri"]));
|
||||
|
||||
const username = authSession.userid;
|
||||
const groups = authSession.groups;
|
||||
if (!authSession.userid)
|
||||
return BluebirdPromise.reject(
|
||||
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE));
|
||||
|
||||
const onlyBasicAuth = req.query[Constants.ONLY_BASIC_AUTH_QUERY_PARAM] === "true";
|
||||
logger.debug("Verify: %s=%s", Constants.ONLY_BASIC_AUTH_QUERY_PARAM, onlyBasicAuth);
|
||||
|
||||
const host = objectPath.get<express.Request, string>(req, "headers.host");
|
||||
const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri");
|
||||
|
||||
const domain = host.split(":")[0];
|
||||
logger.debug("Verify: domain=%s, path=%s", domain, path);
|
||||
logger.debug("Verify: user=%s, groups=%s", username, groups.join(","));
|
||||
logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, path,
|
||||
username, groups.join(","));
|
||||
|
||||
if (!authSession.first_factor)
|
||||
return BluebirdPromise.reject(new exceptions.AccessDeniedError("First factor not validated."));
|
||||
return BluebirdPromise.reject(
|
||||
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE));
|
||||
|
||||
const isAllowed = accessController.isAccessAllowed(domain, path, username, groups);
|
||||
if (!isAllowed) return BluebirdPromise.reject(
|
||||
new exceptions.DomainAccessDenied("User '" + username + "' does not have access to " + domain));
|
||||
new exceptions.DomainAccessDenied(Util.format("User '%s' does not have access to '%'",
|
||||
username, domain)));
|
||||
|
||||
if (!onlyBasicAuth && !authSession.second_factor)
|
||||
return BluebirdPromise.reject(new exceptions.AccessDeniedError("Second factor not validated."));
|
||||
return BluebirdPromise.reject(
|
||||
new exceptions.AccessDeniedError(SECOND_FACTOR_NOT_VALIDATED_MESSAGE));
|
||||
|
||||
res.setHeader("Remote-User", username);
|
||||
res.setHeader("Remote-Groups", groups.join(","));
|
||||
|
@ -56,7 +66,7 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
res.send();
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError403(res, logger))
|
||||
.catch(ErrorReplies.replyWithError401(res, logger));
|
||||
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError403(req, res, logger))
|
||||
.catch(ErrorReplies.replyWithError401(req, res, logger));
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,10 @@ import express = require("express");
|
|||
import winston = require("winston");
|
||||
import speakeasy = require("speakeasy");
|
||||
import u2f = require("u2f");
|
||||
import nodemailer = require("nodemailer");
|
||||
import session = require("express-session");
|
||||
|
||||
import { AppConfiguration, UserConfiguration } from "../src/lib/configuration/Configuration";
|
||||
import { GlobalDependencies, Nodemailer } from "../types/Dependencies";
|
||||
import { GlobalDependencies } from "../types/Dependencies";
|
||||
import Server from "../src/lib/Server";
|
||||
|
||||
|
||||
|
@ -19,17 +18,9 @@ describe("test server configuration", function () {
|
|||
let sessionMock: Sinon.SinonSpy;
|
||||
|
||||
before(function () {
|
||||
const transporter = {
|
||||
sendMail: Sinon.stub().yields()
|
||||
};
|
||||
|
||||
const createTransport = Sinon.stub(nodemailer, "createTransport");
|
||||
createTransport.returns(transporter);
|
||||
|
||||
sessionMock = Sinon.spy(session);
|
||||
|
||||
deps = {
|
||||
nodemailer: nodemailer,
|
||||
speakeasy: speakeasy,
|
||||
u2f: u2f,
|
||||
nedb: nedb,
|
||||
|
@ -79,7 +70,7 @@ describe("test server configuration", function () {
|
|||
}
|
||||
};
|
||||
|
||||
const server = new Server();
|
||||
const server = new Server(deps);
|
||||
server.start(config, deps);
|
||||
|
||||
assert(sessionMock.calledOnce);
|
||||
|
|
|
@ -55,7 +55,6 @@ describe("test session configuration builder", function () {
|
|||
ConnectRedis: Sinon.spy() as any,
|
||||
ldapjs: Sinon.spy() as any,
|
||||
nedb: Sinon.spy() as any,
|
||||
nodemailer: Sinon.spy() as any,
|
||||
session: Sinon.spy() as any,
|
||||
speakeasy: Sinon.spy() as any,
|
||||
u2f: Sinon.spy() as any,
|
||||
|
@ -132,7 +131,6 @@ describe("test session configuration builder", function () {
|
|||
ConnectRedis: Sinon.stub().returns(RedisStoreMock) as any,
|
||||
ldapjs: Sinon.spy() as any,
|
||||
nedb: Sinon.spy() as any,
|
||||
nodemailer: Sinon.spy() as any,
|
||||
session: Sinon.spy() as any,
|
||||
speakeasy: Sinon.spy() as any,
|
||||
u2f: Sinon.spy() as any,
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { IRequestLogger } from "../../src/lib/logging/IRequestLogger";
|
||||
import Sinon = require("sinon");
|
||||
|
||||
export class RequestLoggerStub implements IRequestLogger {
|
||||
infoStub: Sinon.SinonStub;
|
||||
debugStub: Sinon.SinonStub;
|
||||
errorStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.infoStub = Sinon.stub();
|
||||
this.debugStub = Sinon.stub();
|
||||
this.errorStub = Sinon.stub();
|
||||
}
|
||||
|
||||
info(req: Express.Request, message: string, ...args: any[]): void {
|
||||
return this.infoStub(req, message, ...args);
|
||||
}
|
||||
|
||||
debug(req: Express.Request, message: string, ...args: any[]): void {
|
||||
return this.debugStub(req, message, ...args);
|
||||
}
|
||||
|
||||
error(req: Express.Request, message: string, ...args: any[]): void {
|
||||
return this.errorStub(req, message, ...args);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import Sinon = require("sinon");
|
||||
import express = require("express");
|
||||
import winston = require("winston");
|
||||
import { RequestLoggerStub } from "./RequestLoggerStub";
|
||||
import { UserDataStoreStub } from "./storage/UserDataStoreStub";
|
||||
import { VARIABLES_KEY } from "../../src/lib/ServerVariablesHandler";
|
||||
|
||||
|
@ -27,7 +27,7 @@ export function mock(app: express.Application): ServerVariablesMock {
|
|||
ldapAuthenticator: Sinon.stub() as any,
|
||||
ldapEmailsRetriever: Sinon.stub() as any,
|
||||
ldapPasswordUpdater: Sinon.stub() as any,
|
||||
logger: winston,
|
||||
logger: new RequestLoggerStub(),
|
||||
notifier: Sinon.stub(),
|
||||
regulator: Sinon.stub(),
|
||||
totpGenerator: Sinon.stub(),
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
|
||||
import sinon = require("sinon");
|
||||
|
||||
export interface NodemailerMock {
|
||||
createTransport: sinon.SinonStub;
|
||||
}
|
||||
|
||||
export function NodemailerMock(): NodemailerMock {
|
||||
return {
|
||||
createTransport: sinon.stub()
|
||||
};
|
||||
}
|
||||
|
||||
export interface NodemailerTransporterMock {
|
||||
sendMail: sinon.SinonStub;
|
||||
verify: sinon.SinonStub;
|
||||
}
|
||||
|
||||
export function NodemailerTransporterMock() {
|
||||
return {
|
||||
sendMail: sinon.stub(),
|
||||
verify: sinon.stub()
|
||||
};
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { IMailSenderBuilder } from "../../../src/lib/notifiers/IMailSenderBuilder";
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Nodemailer = require("nodemailer");
|
||||
import Sinon = require("sinon");
|
||||
import { IMailSender } from "../../../src/lib/notifiers/IMailSender";
|
||||
import { SmtpNotifierConfiguration, GmailNotifierConfiguration } from "../../../src/lib/configuration/Configuration";
|
||||
|
||||
export class MailSenderBuilderStub implements IMailSenderBuilder {
|
||||
buildGmailStub: Sinon.SinonStub;
|
||||
buildSmtpStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.buildGmailStub = Sinon.stub();
|
||||
this.buildSmtpStub = Sinon.stub();
|
||||
}
|
||||
|
||||
buildGmail(options: GmailNotifierConfiguration): IMailSender {
|
||||
return this.buildGmailStub(options);
|
||||
}
|
||||
|
||||
buildSmtp(options: SmtpNotifierConfiguration): IMailSender {
|
||||
return this.buildSmtpStub(options);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { IMailSender } from "../../../src/lib/notifiers/IMailSender";
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Nodemailer = require("nodemailer");
|
||||
import Sinon = require("sinon");
|
||||
|
||||
export class MailSenderStub implements IMailSender {
|
||||
sendStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.sendStub = Sinon.stub();
|
||||
}
|
||||
|
||||
send(mailOptions: Nodemailer.SendMailOptions): BluebirdPromise<void> {
|
||||
return this.sendStub(mailOptions);
|
||||
}
|
||||
}
|
|
@ -2,24 +2,20 @@ import * as sinon from "sinon";
|
|||
import * as assert from "assert";
|
||||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
import NodemailerMock = require("../mocks/nodemailer");
|
||||
import { MailSenderStub } from "../mocks/notifiers/MailSenderStub";
|
||||
import GMailNotifier = require("../../src/lib/notifiers/GMailNotifier");
|
||||
|
||||
|
||||
describe("test gmail notifier", function () {
|
||||
it("should send an email", function () {
|
||||
const transporter = {
|
||||
sendMail: sinon.stub().yields()
|
||||
};
|
||||
const nodemailerMock = NodemailerMock.NodemailerMock();
|
||||
nodemailerMock.createTransport.returns(transporter);
|
||||
|
||||
it("should send an email to given user", function () {
|
||||
const mailSender = new MailSenderStub();
|
||||
const options = {
|
||||
username: "user_gmail",
|
||||
password: "pass_gmail"
|
||||
};
|
||||
|
||||
const sender = new GMailNotifier.GMailNotifier(options, nodemailerMock);
|
||||
mailSender.sendStub.returns(BluebirdPromise.resolve());
|
||||
const sender = new GMailNotifier.GMailNotifier(options, mailSender);
|
||||
const subject = "subject";
|
||||
|
||||
const identity = {
|
||||
|
@ -31,10 +27,34 @@ describe("test gmail notifier", function () {
|
|||
|
||||
return sender.notify(identity, subject, url)
|
||||
.then(function () {
|
||||
assert.equal(nodemailerMock.createTransport.getCall(0).args[0].auth.user, "user_gmail");
|
||||
assert.equal(nodemailerMock.createTransport.getCall(0).args[0].auth.pass, "pass_gmail");
|
||||
assert.equal(transporter.sendMail.getCall(0).args[0].to, "user@example.com");
|
||||
assert.equal(transporter.sendMail.getCall(0).args[0].subject, "subject");
|
||||
assert.equal(mailSender.sendStub.getCall(0).args[0].to, "user@example.com");
|
||||
assert.equal(mailSender.sendStub.getCall(0).args[0].subject, "subject");
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail while sending an email", function () {
|
||||
const mailSender = new MailSenderStub();
|
||||
const options = {
|
||||
username: "user_gmail",
|
||||
password: "pass_gmail"
|
||||
};
|
||||
|
||||
mailSender.sendStub.returns(BluebirdPromise.reject(new Error("Failed to send mail")));
|
||||
const sender = new GMailNotifier.GMailNotifier(options, mailSender);
|
||||
const subject = "subject";
|
||||
|
||||
const identity = {
|
||||
userid: "user",
|
||||
email: "user@example.com"
|
||||
};
|
||||
|
||||
const url = "http://test.com";
|
||||
|
||||
return sender.notify(identity, subject, url)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject(new Error());
|
||||
}, function() {
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
import { MailSenderBuilder } from "../../src/lib/notifiers/MailSenderBuilder";
|
||||
import Nodemailer = require("nodemailer");
|
||||
import Sinon = require("sinon");
|
||||
import Assert = require("assert");
|
||||
|
||||
describe("test MailSenderBuilder", function() {
|
||||
let createTransportStub: Sinon.SinonStub;
|
||||
beforeEach(function() {
|
||||
createTransportStub = Sinon.stub(Nodemailer, "createTransport");
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
createTransportStub.restore();
|
||||
});
|
||||
|
||||
it("should create a gmail mail sender", function() {
|
||||
const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
|
||||
mailSenderBuilder.buildGmail({
|
||||
username: "user_gmail",
|
||||
password: "pass_gmail"
|
||||
});
|
||||
Assert.equal(createTransportStub.getCall(0).args[0].auth.user, "user_gmail");
|
||||
Assert.equal(createTransportStub.getCall(0).args[0].auth.pass, "pass_gmail");
|
||||
});
|
||||
|
||||
it("should create a smtp mail sender", function() {
|
||||
const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
|
||||
mailSenderBuilder.buildSmtp({
|
||||
host: "mail.example.com",
|
||||
password: "password",
|
||||
port: 25,
|
||||
secure: true,
|
||||
username: "user"
|
||||
});
|
||||
Assert.deepStrictEqual(createTransportStub.getCall(0).args[0], {
|
||||
host: "mail.example.com",
|
||||
auth: {
|
||||
pass: "password",
|
||||
user: "user"
|
||||
},
|
||||
port: 25,
|
||||
secure: true,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,12 +6,11 @@ import * as assert from "assert";
|
|||
import { NotifierFactory } from "../../src/lib/notifiers/NotifierFactory";
|
||||
import { GMailNotifier } from "../../src/lib/notifiers/GMailNotifier";
|
||||
import { SmtpNotifier } from "../../src/lib/notifiers/SmtpNotifier";
|
||||
|
||||
import NodemailerMock = require("../mocks/nodemailer");
|
||||
import { MailSenderBuilderStub } from "../mocks/notifiers/MailSenderBuilderStub";
|
||||
|
||||
|
||||
describe("test notifier factory", function () {
|
||||
let nodemailerMock: NodemailerMock.NodemailerMock;
|
||||
let mailSenderBuilderStub: MailSenderBuilderStub;
|
||||
it("should build a Gmail Notifier", function () {
|
||||
const options = {
|
||||
gmail: {
|
||||
|
@ -19,10 +18,8 @@ describe("test notifier factory", function() {
|
|||
password: "password"
|
||||
}
|
||||
};
|
||||
nodemailerMock = NodemailerMock.NodemailerMock();
|
||||
const transporterMock = NodemailerMock.NodemailerTransporterMock();
|
||||
nodemailerMock.createTransport.returns(transporterMock);
|
||||
assert(NotifierFactory.build(options, nodemailerMock) instanceof GMailNotifier);
|
||||
mailSenderBuilderStub = new MailSenderBuilderStub();
|
||||
assert(NotifierFactory.build(options, mailSenderBuilderStub) instanceof GMailNotifier);
|
||||
});
|
||||
|
||||
it("should build a SMTP Notifier", function () {
|
||||
|
@ -36,9 +33,7 @@ describe("test notifier factory", function() {
|
|||
}
|
||||
};
|
||||
|
||||
nodemailerMock = NodemailerMock.NodemailerMock();
|
||||
const transporterMock = NodemailerMock.NodemailerTransporterMock();
|
||||
nodemailerMock.createTransport.returns(transporterMock);
|
||||
assert(NotifierFactory.build(options, nodemailerMock) instanceof SmtpNotifier);
|
||||
mailSenderBuilderStub = new MailSenderBuilderStub();
|
||||
assert(NotifierFactory.build(options, mailSenderBuilderStub) instanceof SmtpNotifier);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,8 +6,6 @@ import express = require("express");
|
|||
import nodemailer = require("nodemailer");
|
||||
import Endpoints = require("../../shared/api");
|
||||
|
||||
import NodemailerMock = require("./mocks/nodemailer");
|
||||
|
||||
declare module "request" {
|
||||
export interface RequestAPI<TRequest extends Request,
|
||||
TOptions extends CoreOptions,
|
||||
|
@ -28,58 +26,6 @@ export = function (port: number) {
|
|||
const PORT = port;
|
||||
const BASE_URL = "http://localhost:" + PORT;
|
||||
|
||||
function execute_reset_password(jar: request.CookieJar, transporter: NodemailerMock.NodemailerTransporterMock, user: string, new_password: string) {
|
||||
return requestAsync.getAsync({
|
||||
url: BASE_URL + Endpoints.RESET_PASSWORD_IDENTITY_START_GET,
|
||||
jar: jar,
|
||||
qs: { userid: user }
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
const html_content = transporter.sendMail.getCall(0).args[0].html;
|
||||
const regexp = /identity_token=([a-zA-Z0-9]+)/;
|
||||
const token = regexp.exec(html_content)[1];
|
||||
// console.log(html_content, token);
|
||||
return requestAsync.getAsync({
|
||||
url: BASE_URL + Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET + "?identity_token=" + token,
|
||||
jar: jar
|
||||
});
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
return requestAsync.postAsync({
|
||||
url: BASE_URL + Endpoints.RESET_PASSWORD_FORM_POST,
|
||||
jar: jar,
|
||||
form: {
|
||||
password: new_password
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function execute_register_totp(jar: request.CookieJar, transporter: NodemailerMock.NodemailerTransporterMock) {
|
||||
return requestAsync.getAsync({
|
||||
url: BASE_URL + Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
|
||||
jar: jar
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
const html_content = transporter.sendMail.getCall(0).args[0].html;
|
||||
const regexp = /identity_token=([a-zA-Z0-9]+)/;
|
||||
const token = regexp.exec(html_content)[1];
|
||||
return requestAsync.getAsync({
|
||||
url: BASE_URL + Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET + "?identity_token=" + token,
|
||||
jar: jar
|
||||
});
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
const regex = /<p id="secret">([A-Z0-9]+)<\/p>/g;
|
||||
const secret = regex.exec(res.body);
|
||||
return BluebirdPromise.resolve(secret[1]);
|
||||
});
|
||||
}
|
||||
|
||||
function execute_totp(jar: request.CookieJar, token: string) {
|
||||
return requestAsync.postAsync({
|
||||
url: BASE_URL + Endpoints.SECOND_FACTOR_TOTP_POST,
|
||||
|
@ -114,41 +60,6 @@ export = function (port: number) {
|
|||
return requestAsync.getAsync({ url: BASE_URL + Endpoints.FIRST_FACTOR_GET, jar: jar });
|
||||
}
|
||||
|
||||
function execute_u2f_registration(jar: request.CookieJar, transporter: NodemailerMock.NodemailerTransporterMock) {
|
||||
return requestAsync.getAsync({
|
||||
url: BASE_URL + Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET,
|
||||
jar: jar
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
const html_content = transporter.sendMail.getCall(0).args[0].html;
|
||||
const regexp = /identity_token=([a-zA-Z0-9]+)/;
|
||||
const token = regexp.exec(html_content)[1];
|
||||
// console.log(html_content, token);
|
||||
return requestAsync.getAsync({
|
||||
url: BASE_URL + Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET + "?identity_token=" + token,
|
||||
jar: jar
|
||||
});
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
return requestAsync.getAsync({
|
||||
url: BASE_URL + Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET,
|
||||
jar: jar,
|
||||
});
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
return requestAsync.postAsync({
|
||||
url: BASE_URL + Endpoints.SECOND_FACTOR_U2F_REGISTER_POST,
|
||||
jar: jar,
|
||||
form: {
|
||||
s: "test"
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function execute_first_factor(jar: request.CookieJar) {
|
||||
return requestAsync.postAsync({
|
||||
url: BASE_URL + Endpoints.FIRST_FACTOR_POST,
|
||||
|
@ -174,13 +85,10 @@ export = function (port: number) {
|
|||
return {
|
||||
login: execute_login,
|
||||
verify: execute_verification,
|
||||
reset_password: execute_reset_password,
|
||||
u2f_authentication: execute_u2f_authentication,
|
||||
u2f_registration: execute_u2f_registration,
|
||||
first_factor: execute_first_factor,
|
||||
failing_first_factor: execute_failing_first_factor,
|
||||
totp: execute_totp,
|
||||
register_totp: execute_register_totp,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import AuthenticationRegulatorMock = require("../../mocks/AuthenticationRegulato
|
|||
import { AccessControllerStub } from "../../mocks/AccessControllerStub";
|
||||
import ExpressMock = require("../../mocks/express");
|
||||
import ServerVariablesMock = require("../../mocks/ServerVariablesMock");
|
||||
import { ServerVariables } from "../../../src/lib/ServerVariablesHandler";
|
||||
import { ServerVariables } from "../../../src/lib/ServerVariables";
|
||||
|
||||
describe("test the first factor validation route", function () {
|
||||
let req: ExpressMock.RequestMock;
|
||||
|
@ -68,7 +68,6 @@ describe("test the first factor validation route", function () {
|
|||
authenticate: sinon.stub()
|
||||
} as any;
|
||||
serverVariables.config = configuration as any;
|
||||
serverVariables.logger = winston as any;
|
||||
serverVariables.regulator = regulator as any;
|
||||
serverVariables.accessController = accessController as any;
|
||||
|
||||
|
|
|
@ -56,7 +56,6 @@ describe("test reset password identity check", function () {
|
|||
}
|
||||
};
|
||||
|
||||
serverVariables.logger = winston;
|
||||
serverVariables.config = configuration;
|
||||
serverVariables.ldapEmailsRetriever = {
|
||||
retrieve: Sinon.stub()
|
||||
|
|
|
@ -51,7 +51,6 @@ describe("test reset password route", function () {
|
|||
}
|
||||
};
|
||||
|
||||
serverVariables.logger = winston;
|
||||
serverVariables.config = configuration;
|
||||
|
||||
serverVariables.ldapPasswordUpdater = {
|
||||
|
|
|
@ -19,7 +19,6 @@ describe("test totp register", function () {
|
|||
beforeEach(function () {
|
||||
req = ExpressMock.RequestMock();
|
||||
const mocks = ServerVariablesMock.mock(req.app);
|
||||
mocks.logger = winston;
|
||||
req.session = {};
|
||||
|
||||
AuthenticationSession.reset(req as any);
|
||||
|
|
|
@ -44,8 +44,6 @@ describe("test totp route", function () {
|
|||
}
|
||||
};
|
||||
mocks.userDataStore.retrieveTOTPSecretStub.returns(BluebirdPromise.resolve(doc));
|
||||
|
||||
mocks.logger = winston;
|
||||
mocks.totpValidator = totpValidator;
|
||||
mocks.config = config;
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ describe("test register handler", function () {
|
|||
req = ExpressMock.RequestMock();
|
||||
req.app = {};
|
||||
const mocks = ServerVariablesMock.mock(req.app);
|
||||
mocks.logger = winston;
|
||||
req.session = {};
|
||||
AuthenticationSession.reset(req as any);
|
||||
req.headers = {};
|
||||
|
|
|
@ -22,8 +22,6 @@ describe("test u2f routes: register", function () {
|
|||
req = ExpressMock.RequestMock();
|
||||
req.app = {};
|
||||
mocks = ServerVariablesMock.mock(req.app);
|
||||
mocks.logger = winston;
|
||||
|
||||
req.session = {};
|
||||
req.headers = {};
|
||||
req.headers.host = "localhost";
|
||||
|
|
|
@ -23,7 +23,6 @@ describe("test u2f routes: register_request", function () {
|
|||
req = ExpressMock.RequestMock();
|
||||
req.app = {};
|
||||
mocks = ServerVariablesMock.mock(req.app);
|
||||
mocks.logger = winston;
|
||||
req.session = {};
|
||||
AuthenticationSession.reset(req as any);
|
||||
|
||||
|
|
|
@ -23,8 +23,6 @@ describe("test u2f routes: sign", function () {
|
|||
req.app = {};
|
||||
|
||||
mocks = ServerVariablesMock.mock(req.app);
|
||||
mocks.logger = winston;
|
||||
|
||||
req.session = {};
|
||||
AuthenticationSession.reset(req as any);
|
||||
req.headers = {};
|
||||
|
|
|
@ -26,7 +26,6 @@ describe("test u2f routes: sign_request", function () {
|
|||
req.app = {};
|
||||
|
||||
mocks = ServerVariablesMock.mock(req.app);
|
||||
mocks.logger = winston;
|
||||
|
||||
req.session = {};
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ describe("test authentication token verification", function () {
|
|||
req.headers.host = "secret.example.com";
|
||||
const mocks = ServerVariablesMock.mock(req.app);
|
||||
mocks.config = {} as any;
|
||||
mocks.logger = winston;
|
||||
mocks.accessController = accessController as any;
|
||||
});
|
||||
|
||||
|
@ -163,6 +162,7 @@ describe("test authentication token verification", function () {
|
|||
return AuthenticationSession.get(req as any)
|
||||
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
||||
authSession.first_factor = true;
|
||||
authSession.userid = "user1";
|
||||
return VerifyGet.default(req as express.Request, res as any);
|
||||
})
|
||||
.then(function () {
|
||||
|
|
|
@ -108,9 +108,8 @@ describe("Private pages of the server must not be accessible without session", f
|
|||
ldap_client.search.yields(undefined, search_res);
|
||||
|
||||
const deps: GlobalDependencies = {
|
||||
u2f: u2f,
|
||||
u2f: u2f as any,
|
||||
nedb: nedb,
|
||||
nodemailer: nodemailer,
|
||||
ldapjs: ldap,
|
||||
session: ExpressSession,
|
||||
winston: Winston,
|
||||
|
@ -119,7 +118,7 @@ describe("Private pages of the server must not be accessible without session", f
|
|||
dovehash: Sinon.spy() as any
|
||||
};
|
||||
|
||||
server = new Server();
|
||||
server = new Server(deps);
|
||||
return server.start(config, deps);
|
||||
});
|
||||
|
||||
|
|
|
@ -108,9 +108,8 @@ describe("Public pages of the server must be accessible without session", functi
|
|||
ldap_client.search.yields(undefined, search_res);
|
||||
|
||||
const deps: GlobalDependencies = {
|
||||
u2f: u2f,
|
||||
u2f: u2f as any,
|
||||
nedb: nedb,
|
||||
nodemailer: nodemailer,
|
||||
ldapjs: ldap,
|
||||
session: ExpressSession,
|
||||
winston: Winston,
|
||||
|
@ -119,7 +118,7 @@ describe("Public pages of the server must be accessible without session", functi
|
|||
dovehash: Sinon.spy() as any
|
||||
};
|
||||
|
||||
server = new Server();
|
||||
server = new Server(deps);
|
||||
return server.start(config, deps);
|
||||
});
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import RedisSession = require("connect-redis");
|
|||
import dovehash = require("dovehash");
|
||||
|
||||
export type Dovehash = typeof dovehash;
|
||||
export type Nodemailer = typeof nodemailer;
|
||||
export type Speakeasy = typeof speakeasy;
|
||||
export type Winston = typeof winston;
|
||||
export type Session = typeof session;
|
||||
|
@ -21,7 +20,6 @@ export type ConnectRedis = typeof RedisSession;
|
|||
export interface GlobalDependencies {
|
||||
u2f: U2f;
|
||||
dovehash: Dovehash;
|
||||
nodemailer: Nodemailer;
|
||||
ldapjs: Ldapjs;
|
||||
session: Session;
|
||||
ConnectRedis: ConnectRedis;
|
||||
|
|
Loading…
Reference in New Issue