From 78f6028c1b8ef96db07d4580de6d4c417e384b91 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Sun, 8 Oct 2017 00:46:57 +0200 Subject: [PATCH] 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. --- config.template.yml | 4 +- package.json | 3 +- server/src/index.ts | 10 +- server/src/lib/AuthenticationSession.ts | 6 +- server/src/lib/ErrorReplies.ts | 28 +++--- server/src/lib/IdentityCheckMiddleware.ts | 33 ++++--- server/src/lib/RestApi.ts | 2 +- server/src/lib/Server.ts | 66 ++++++++++--- server/src/lib/ServerVariables.ts | 32 +++++++ server/src/lib/ServerVariablesHandler.ts | 38 ++++---- server/src/lib/logging/GlobalLogger.ts | 34 +++++++ server/src/lib/logging/IGlobalLogger.ts | 5 + server/src/lib/logging/IRequestLogger.ts | 7 ++ server/src/lib/logging/RequestLogger.ts | 45 +++++++++ server/src/lib/notifiers/GMailNotifier.ts | 18 +--- server/src/lib/notifiers/IMailSender.ts | 6 ++ .../src/lib/notifiers/IMailSenderBuilder.ts | 7 ++ server/src/lib/notifiers/MailSender.ts | 42 +++++++++ server/src/lib/notifiers/MailSenderBuilder.ts | 38 ++++++++ server/src/lib/notifiers/NotifierFactory.ts | 12 ++- server/src/lib/notifiers/SmtpNotifier.ts | 37 ++------ server/src/lib/routes/FirstFactorBlocker.ts | 2 +- server/src/lib/routes/firstfactor/get.ts | 4 - server/src/lib/routes/firstfactor/post.ts | 32 +++---- .../lib/routes/password-reset/form/post.ts | 8 +- .../identity/PasswordResetHandler.ts | 3 +- server/src/lib/routes/secondfactor/get.ts | 2 - .../src/lib/routes/secondfactor/redirect.ts | 5 +- .../totp/identity/RegistrationHandler.ts | 6 +- .../lib/routes/secondfactor/totp/sign/post.ts | 10 +- .../routes/secondfactor/u2f/register/post.ts | 12 +-- .../secondfactor/u2f/register_request/get.ts | 7 +- .../lib/routes/secondfactor/u2f/sign/post.ts | 8 +- .../secondfactor/u2f/sign_request/get.ts | 12 +-- server/src/lib/routes/verify/get.ts | 30 ++++-- server/test/ServerConfiguration.test.ts | 13 +-- .../test/SessionConfigurationBuilder.test.ts | 2 - server/test/mocks/RequestLoggerStub.ts | 26 ++++++ server/test/mocks/ServerVariablesMock.ts | 4 +- server/test/mocks/nodemailer.ts | 24 ----- .../mocks/notifiers/MailSenderBuilderStub.ts | 25 +++++ server/test/mocks/notifiers/MailSenderStub.ts | 16 ++++ server/test/notifiers/GMailNotifier.test.ts | 46 +++++++--- .../test/notifiers/MailSenderBuilder.test.ts | 46 ++++++++++ server/test/notifiers/NotifierFactory.test.ts | 23 ++--- server/test/requests.ts | 92 ------------------- server/test/routes/firstfactor/post.test.ts | 3 +- .../identity/PasswordResetHandler.test.ts | 1 - .../test/routes/password-reset/post.test.ts | 1 - .../totp/register/RegistrationHandler.test.ts | 1 - .../secondfactor/totp/sign/post.test.ts | 2 - .../u2f/identity/RegistrationHandler.test.ts | 1 - .../secondfactor/u2f/register/post.test.ts | 2 - .../u2f/register_request/get.test.ts | 1 - .../routes/secondfactor/u2f/sign/post.test.ts | 2 - .../secondfactor/u2f/sign_request/get.test.ts | 1 - server/test/routes/verify/get.test.ts | 2 +- server/test/server/PrivatePages.ts | 5 +- server/test/server/PublicPages.ts | 5 +- server/types/Dependencies.ts | 2 - 60 files changed, 582 insertions(+), 378 deletions(-) create mode 100644 server/src/lib/ServerVariables.ts create mode 100644 server/src/lib/logging/GlobalLogger.ts create mode 100644 server/src/lib/logging/IGlobalLogger.ts create mode 100644 server/src/lib/logging/IRequestLogger.ts create mode 100644 server/src/lib/logging/RequestLogger.ts create mode 100644 server/src/lib/notifiers/IMailSender.ts create mode 100644 server/src/lib/notifiers/IMailSenderBuilder.ts create mode 100644 server/src/lib/notifiers/MailSender.ts create mode 100644 server/src/lib/notifiers/MailSenderBuilder.ts create mode 100644 server/test/mocks/RequestLoggerStub.ts delete mode 100644 server/test/mocks/nodemailer.ts create mode 100644 server/test/mocks/notifiers/MailSenderBuilderStub.ts create mode 100644 server/test/mocks/notifiers/MailSenderStub.ts create mode 100644 server/test/notifiers/MailSenderBuilder.test.ts diff --git a/config.template.yml b/config.template.yml index dfc185f03..3fb5438fb 100644 --- a/config.template.yml +++ b/config.template.yml @@ -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 diff --git a/package.json b/package.json index d1c3f376e..5151a789c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/src/index.ts b/server/src/index.ts index 732a180da..b5a20ac52 100755 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -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); diff --git a/server/src/lib/AuthenticationSession.ts b/server/src/lib/AuthenticationSession.ts index 0b6214294..9414d55d7 100644 --- a/server/src/lib/AuthenticationSession.ts +++ b/server/src/lib/AuthenticationSession.ts @@ -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 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); } \ No newline at end of file diff --git a/server/src/lib/IdentityCheckMiddleware.ts b/server/src/lib/IdentityCheckMiddleware.ts index 84d8631f6..ffe9292f5 100644 --- a/server/src/lib/IdentityCheckMiddleware.ts +++ b/server/src/lib/IdentityCheckMiddleware.ts @@ -33,20 +33,20 @@ export interface IdentityValidable { mailSubject(): string; } -function createAndSaveToken(userid: string, challenge: string, userDataStore: IUserDataStore, logger: Winston): BluebirdPromise { +function createAndSaveToken(userid: string, challenge: string, userDataStore: IUserDataStore) + : BluebirdPromise { 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 { - logger.debug("identity_check: consume token %s", token); +function consumeToken(token: string, challenge: string, userDataStore: IUserDataStore) + : BluebirdPromise { 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(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)); }; } diff --git a/server/src/lib/RestApi.ts b/server/src/lib/RestApi.ts index 67b7af3c7..0006fffa7 100644 --- a/server/src/lib/RestApi.ts +++ b/server/src/lib/RestApi.ts @@ -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); }; } diff --git a/server/src/lib/Server.ts b/server/src/lib/Server.ts index cdee45775..8f9f092af 100644 --- a/server/src/lib/Server.ts +++ b/server/src/lib/Server.ts @@ -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 { 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((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 { + start(userConfiguration: UserConfiguration, deps: GlobalDependencies) + : BluebirdPromise { 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); }); } diff --git a/server/src/lib/ServerVariables.ts b/server/src/lib/ServerVariables.ts new file mode 100644 index 000000000..d11c8f38c --- /dev/null +++ b/server/src/lib/ServerVariables.ts @@ -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; +} \ No newline at end of file diff --git a/server/src/lib/ServerVariablesHandler.ts b/server/src/lib/ServerVariablesHandler.ts index e014cb2d2..f40d15331 100644 --- a/server/src/lib/ServerVariablesHandler.ts +++ b/server/src/lib/ServerVariablesHandler.ts @@ -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 { if (config.storage.local) { @@ -73,8 +65,10 @@ class UserDataStoreFactory { } export class ServerVariablesHandler { - static initialize(app: express.Application, config: Configuration.AppConfiguration, deps: GlobalDependencies): BluebirdPromise { - const notifier = NotifierFactory.build(config.notifier, deps.nodemailer); + static initialize(app: express.Application, config: Configuration.AppConfiguration, requestLogger: IRequestLogger, + deps: GlobalDependencies): BluebirdPromise { + 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; } diff --git a/server/src/lib/logging/GlobalLogger.ts b/server/src/lib/logging/GlobalLogger.ts new file mode 100644 index 000000000..4da7acf49 --- /dev/null +++ b/server/src/lib/logging/GlobalLogger.ts @@ -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)); + } +} \ No newline at end of file diff --git a/server/src/lib/logging/IGlobalLogger.ts b/server/src/lib/logging/IGlobalLogger.ts new file mode 100644 index 000000000..548515ec7 --- /dev/null +++ b/server/src/lib/logging/IGlobalLogger.ts @@ -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; +} diff --git a/server/src/lib/logging/IRequestLogger.ts b/server/src/lib/logging/IRequestLogger.ts new file mode 100644 index 000000000..126a601fe --- /dev/null +++ b/server/src/lib/logging/IRequestLogger.ts @@ -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; +} \ No newline at end of file diff --git a/server/src/lib/logging/RequestLogger.ts b/server/src/lib/logging/RequestLogger.ts new file mode 100644 index 000000000..e73ab1c7e --- /dev/null +++ b/server/src/lib/logging/RequestLogger.ts @@ -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); + } +} \ No newline at end of file diff --git a/server/src/lib/notifiers/GMailNotifier.ts b/server/src/lib/notifiers/GMailNotifier.ts index 93b015738..16aeb4171 100644 --- a/server/src/lib/notifiers/GMailNotifier.ts +++ b/server/src/lib/notifiers/GMailNotifier.ts @@ -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); } } diff --git a/server/src/lib/notifiers/IMailSender.ts b/server/src/lib/notifiers/IMailSender.ts new file mode 100644 index 000000000..34ac464a8 --- /dev/null +++ b/server/src/lib/notifiers/IMailSender.ts @@ -0,0 +1,6 @@ +import BluebirdPromise = require("bluebird"); +import Nodemailer = require("nodemailer"); + +export interface IMailSender { + send(mailOptions: Nodemailer.SendMailOptions): BluebirdPromise; +} \ No newline at end of file diff --git a/server/src/lib/notifiers/IMailSenderBuilder.ts b/server/src/lib/notifiers/IMailSenderBuilder.ts new file mode 100644 index 000000000..25aff7e7e --- /dev/null +++ b/server/src/lib/notifiers/IMailSenderBuilder.ts @@ -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; +} \ No newline at end of file diff --git a/server/src/lib/notifiers/MailSender.ts b/server/src/lib/notifiers/MailSender.ts new file mode 100644 index 000000000..536a88e63 --- /dev/null +++ b/server/src/lib/notifiers/MailSender.ts @@ -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 { + 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 { + 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(); + }); + }); + } +} \ No newline at end of file diff --git a/server/src/lib/notifiers/MailSenderBuilder.ts b/server/src/lib/notifiers/MailSenderBuilder.ts new file mode 100644 index 000000000..3b2a1ba90 --- /dev/null +++ b/server/src/lib/notifiers/MailSenderBuilder.ts @@ -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); + } +} \ No newline at end of file diff --git a/server/src/lib/notifiers/NotifierFactory.ts b/server/src/lib/notifiers/NotifierFactory.ts index 2567db6eb..8bfd0452a 100644 --- a/server/src/lib/notifiers/NotifierFactory.ts +++ b/server/src/lib/notifiers/NotifierFactory.ts @@ -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."); diff --git a/server/src/lib/notifiers/SmtpNotifier.ts b/server/src/lib/notifiers/SmtpNotifier.ts index 94b87914b..5ed86621d 100644 --- a/server/src/lib/notifiers/SmtpNotifier.ts +++ b/server/src/lib/notifiers/SmtpNotifier.ts @@ -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); } } diff --git a/server/src/lib/routes/FirstFactorBlocker.ts b/server/src/lib/routes/FirstFactorBlocker.ts index 366e0c842..0cab96e9f 100644 --- a/server/src/lib/routes/FirstFactorBlocker.ts +++ b/server/src/lib/routes/FirstFactorBlocker.ts @@ -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)); }; } \ No newline at end of file diff --git a/server/src/lib/routes/firstfactor/get.ts b/server/src/lib/routes/firstfactor/get.ts index e2ba0d087..249f9dc37 100644 --- a/server/src/lib/routes/firstfactor/get.ts +++ b/server/src/lib/routes/firstfactor/get.ts @@ -8,10 +8,6 @@ import { ServerVariablesHandler } from "../../ServerVariablesHandler"; import BluebirdPromise = require("bluebird"); export default function (req: express.Request, res: express.Response): BluebirdPromise { - 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 diff --git a/server/src/lib/routes/firstfactor/post.ts b/server/src/lib/routes/firstfactor/post.ts index 0bda38558..072a55e13 100644 --- a/server/src/lib/routes/firstfactor/post.ts +++ b/server/src/lib/routes/firstfactor/post.ts @@ -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)); } diff --git a/server/src/lib/routes/password-reset/form/post.ts b/server/src/lib/routes/password-reset/form/post.ts index e3fb36757..d84c4c863 100644 --- a/server/src/lib/routes/password-reset/form/post.ts +++ b/server/src/lib/routes/password-reset/form/post.ts @@ -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)); } diff --git a/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts b/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts index edcba028f..42647bfdb 100644 --- a/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts +++ b/server/src/lib/routes/password-reset/identity/PasswordResetHandler.ts @@ -21,7 +21,7 @@ export default class PasswordResetHandler implements IdentityValidable { const logger = ServerVariablesHandler.getLogger(req.app); const userid: string = objectPath.get(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 diff --git a/server/src/lib/routes/secondfactor/get.ts b/server/src/lib/routes/secondfactor/get.ts index ba0330362..172e1d6e6 100644 --- a/server/src/lib/routes/secondfactor/get.ts +++ b/server/src/lib/routes/secondfactor/get.ts @@ -10,8 +10,6 @@ const TEMPLATE_NAME = "secondfactor"; export default FirstFactorBlocker.default(handler); function handler(req: Express.Request, res: Express.Response): BluebirdPromise { - 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 diff --git a/server/src/lib/routes/secondfactor/redirect.ts b/server/src/lib/routes/secondfactor/redirect.ts index 75ff801df..4761f2a94 100644 --- a/server/src/lib/routes/secondfactor/redirect.ts +++ b/server/src/lib/routes/secondfactor/redirect.ts @@ -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 { + 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)); } \ No newline at end of file diff --git a/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts b/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts index 968a38587..a395bf132 100644 --- a/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts +++ b/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts @@ -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 { diff --git a/server/src/lib/routes/secondfactor/totp/sign/post.ts b/server/src/lib/routes/secondfactor/totp/sign/post.ts index 6e4275f6a..134de6533 100644 --- a/server/src/lib/routes/secondfactor/totp/sign/post.ts +++ b/server/src/lib/routes/secondfactor/totp/sign/post.ts @@ -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)); } diff --git a/server/src/lib/routes/secondfactor/u2f/register/post.ts b/server/src/lib/routes/secondfactor/u2f/register/post.ts index a0bae8b73..1a64ad64d 100644 --- a/server/src/lib/routes/secondfactor/u2f/register/post.ts +++ b/server/src/lib/routes/secondfactor/u2f/register/post.ts @@ -43,9 +43,9 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise { 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)); } diff --git a/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts b/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts index 841927d85..af0ebf295 100644 --- a/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts +++ b/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts @@ -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)); } diff --git a/server/src/lib/routes/verify/get.ts b/server/src/lib/routes/verify/get.ts index 7bd59d49a..d66cff648 100644 --- a/server/src/lib/routes/verify/get.ts +++ b/server/src/lib/routes/verify/get.ts @@ -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 { 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(req, "headers.host"); const path = objectPath.get(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)); } diff --git a/server/test/ServerConfiguration.test.ts b/server/test/ServerConfiguration.test.ts index 338d16bff..3f3002732 100644 --- a/server/test/ServerConfiguration.test.ts +++ b/server/test/ServerConfiguration.test.ts @@ -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); diff --git a/server/test/SessionConfigurationBuilder.test.ts b/server/test/SessionConfigurationBuilder.test.ts index 0884d8ee1..67fcb58e6 100644 --- a/server/test/SessionConfigurationBuilder.test.ts +++ b/server/test/SessionConfigurationBuilder.test.ts @@ -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, diff --git a/server/test/mocks/RequestLoggerStub.ts b/server/test/mocks/RequestLoggerStub.ts new file mode 100644 index 000000000..3f4a6c168 --- /dev/null +++ b/server/test/mocks/RequestLoggerStub.ts @@ -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); + } +} \ No newline at end of file diff --git a/server/test/mocks/ServerVariablesMock.ts b/server/test/mocks/ServerVariablesMock.ts index 08caf776f..a56676251 100644 --- a/server/test/mocks/ServerVariablesMock.ts +++ b/server/test/mocks/ServerVariablesMock.ts @@ -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(), diff --git a/server/test/mocks/nodemailer.ts b/server/test/mocks/nodemailer.ts deleted file mode 100644 index 2c7bc41ac..000000000 --- a/server/test/mocks/nodemailer.ts +++ /dev/null @@ -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() - }; -} diff --git a/server/test/mocks/notifiers/MailSenderBuilderStub.ts b/server/test/mocks/notifiers/MailSenderBuilderStub.ts new file mode 100644 index 000000000..ac701c7d8 --- /dev/null +++ b/server/test/mocks/notifiers/MailSenderBuilderStub.ts @@ -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); + } + +} \ No newline at end of file diff --git a/server/test/mocks/notifiers/MailSenderStub.ts b/server/test/mocks/notifiers/MailSenderStub.ts new file mode 100644 index 000000000..d57c458fc --- /dev/null +++ b/server/test/mocks/notifiers/MailSenderStub.ts @@ -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 { + return this.sendStub(mailOptions); + } +} \ No newline at end of file diff --git a/server/test/notifiers/GMailNotifier.test.ts b/server/test/notifiers/GMailNotifier.test.ts index d7c5828e2..7efba4446 100644 --- a/server/test/notifiers/GMailNotifier.test.ts +++ b/server/test/notifiers/GMailNotifier.test.ts @@ -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(); }); }); diff --git a/server/test/notifiers/MailSenderBuilder.test.ts b/server/test/notifiers/MailSenderBuilder.test.ts new file mode 100644 index 000000000..9cb088804 --- /dev/null +++ b/server/test/notifiers/MailSenderBuilder.test.ts @@ -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, + }); + }); +}); \ No newline at end of file diff --git a/server/test/notifiers/NotifierFactory.test.ts b/server/test/notifiers/NotifierFactory.test.ts index 191432777..5763f7038 100644 --- a/server/test/notifiers/NotifierFactory.test.ts +++ b/server/test/notifiers/NotifierFactory.test.ts @@ -6,26 +6,23 @@ 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; - it("should build a Gmail Notifier", function() { +describe("test notifier factory", function () { + let mailSenderBuilderStub: MailSenderBuilderStub; + it("should build a Gmail Notifier", function () { const options = { gmail: { username: "abc", 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() { + it("should build a SMTP Notifier", function () { const options = { smtp: { username: "user", @@ -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); }); }); diff --git a/server/test/requests.ts b/server/test/requests.ts index 268556f52..93fa0de47 100644 --- a/server/test/requests.ts +++ b/server/test/requests.ts @@ -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([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, }; }; diff --git a/server/test/routes/firstfactor/post.test.ts b/server/test/routes/firstfactor/post.test.ts index c7c1c866a..17b3548c9 100644 --- a/server/test/routes/firstfactor/post.test.ts +++ b/server/test/routes/firstfactor/post.test.ts @@ -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; diff --git a/server/test/routes/password-reset/identity/PasswordResetHandler.test.ts b/server/test/routes/password-reset/identity/PasswordResetHandler.test.ts index 74e761162..9f51e200f 100644 --- a/server/test/routes/password-reset/identity/PasswordResetHandler.test.ts +++ b/server/test/routes/password-reset/identity/PasswordResetHandler.test.ts @@ -56,7 +56,6 @@ describe("test reset password identity check", function () { } }; - serverVariables.logger = winston; serverVariables.config = configuration; serverVariables.ldapEmailsRetriever = { retrieve: Sinon.stub() diff --git a/server/test/routes/password-reset/post.test.ts b/server/test/routes/password-reset/post.test.ts index 920f97d27..86fda49cd 100644 --- a/server/test/routes/password-reset/post.test.ts +++ b/server/test/routes/password-reset/post.test.ts @@ -51,7 +51,6 @@ describe("test reset password route", function () { } }; - serverVariables.logger = winston; serverVariables.config = configuration; serverVariables.ldapPasswordUpdater = { diff --git a/server/test/routes/secondfactor/totp/register/RegistrationHandler.test.ts b/server/test/routes/secondfactor/totp/register/RegistrationHandler.test.ts index a913b1a4c..e1b322cd4 100644 --- a/server/test/routes/secondfactor/totp/register/RegistrationHandler.test.ts +++ b/server/test/routes/secondfactor/totp/register/RegistrationHandler.test.ts @@ -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); diff --git a/server/test/routes/secondfactor/totp/sign/post.test.ts b/server/test/routes/secondfactor/totp/sign/post.test.ts index a562d1ba2..21b9de366 100644 --- a/server/test/routes/secondfactor/totp/sign/post.test.ts +++ b/server/test/routes/secondfactor/totp/sign/post.test.ts @@ -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; diff --git a/server/test/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts b/server/test/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts index 11d28eb03..f7feaf1c5 100644 --- a/server/test/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts +++ b/server/test/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts @@ -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 = {}; diff --git a/server/test/routes/secondfactor/u2f/register/post.test.ts b/server/test/routes/secondfactor/u2f/register/post.test.ts index 9a8ced388..601e7f099 100644 --- a/server/test/routes/secondfactor/u2f/register/post.test.ts +++ b/server/test/routes/secondfactor/u2f/register/post.test.ts @@ -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"; diff --git a/server/test/routes/secondfactor/u2f/register_request/get.test.ts b/server/test/routes/secondfactor/u2f/register_request/get.test.ts index 0a6fae7ae..7465beb1e 100644 --- a/server/test/routes/secondfactor/u2f/register_request/get.test.ts +++ b/server/test/routes/secondfactor/u2f/register_request/get.test.ts @@ -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); diff --git a/server/test/routes/secondfactor/u2f/sign/post.test.ts b/server/test/routes/secondfactor/u2f/sign/post.test.ts index 54784f0c6..80d7394ea 100644 --- a/server/test/routes/secondfactor/u2f/sign/post.test.ts +++ b/server/test/routes/secondfactor/u2f/sign/post.test.ts @@ -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 = {}; diff --git a/server/test/routes/secondfactor/u2f/sign_request/get.test.ts b/server/test/routes/secondfactor/u2f/sign_request/get.test.ts index 9e3027b43..2bff38ce2 100644 --- a/server/test/routes/secondfactor/u2f/sign_request/get.test.ts +++ b/server/test/routes/secondfactor/u2f/sign_request/get.test.ts @@ -26,7 +26,6 @@ describe("test u2f routes: sign_request", function () { req.app = {}; mocks = ServerVariablesMock.mock(req.app); - mocks.logger = winston; req.session = {}; diff --git a/server/test/routes/verify/get.test.ts b/server/test/routes/verify/get.test.ts index 1f257c22e..b2083dd9c 100644 --- a/server/test/routes/verify/get.test.ts +++ b/server/test/routes/verify/get.test.ts @@ -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 () { diff --git a/server/test/server/PrivatePages.ts b/server/test/server/PrivatePages.ts index 44a8411cf..09bdb383b 100644 --- a/server/test/server/PrivatePages.ts +++ b/server/test/server/PrivatePages.ts @@ -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); }); diff --git a/server/test/server/PublicPages.ts b/server/test/server/PublicPages.ts index 180642fe9..aef0bf6b0 100644 --- a/server/test/server/PublicPages.ts +++ b/server/test/server/PublicPages.ts @@ -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); }); diff --git a/server/types/Dependencies.ts b/server/types/Dependencies.ts index ffda38148..0eda36141 100644 --- a/server/types/Dependencies.ts +++ b/server/types/Dependencies.ts @@ -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;