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.
|
# The session cookies identify the user once logged in.
|
||||||
session:
|
session:
|
||||||
# The secret to encrypt the session cookie.
|
# The secret to encrypt the session cookie.
|
||||||
secret: unsecure_secret
|
secret: unsecure_session_secret
|
||||||
|
|
||||||
# The time before the cookie expires.
|
# The time before the cookie expires.
|
||||||
expiration: 3600000
|
expiration: 3600000
|
||||||
|
@ -190,7 +190,7 @@ notifier:
|
||||||
# Use a SMTP server for sending notifications
|
# Use a SMTP server for sending notifications
|
||||||
smtp:
|
smtp:
|
||||||
username: test
|
username: test
|
||||||
password: test
|
password: password
|
||||||
secure: false
|
secure: false
|
||||||
host: 'smtp'
|
host: 'smtp'
|
||||||
port: 1025
|
port: 1025
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
"dovehash": "0.0.5",
|
"dovehash": "0.0.5",
|
||||||
"ejs": "^2.5.5",
|
"ejs": "^2.5.5",
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
|
"express-request-id": "^1.4.0",
|
||||||
"express-session": "^1.14.2",
|
"express-session": "^1.14.2",
|
||||||
"ldapjs": "^1.0.1",
|
"ldapjs": "^1.0.1",
|
||||||
"mongodb": "^2.2.30",
|
"mongodb": "^2.2.30",
|
||||||
|
@ -59,7 +60,7 @@
|
||||||
"@types/mockdate": "^2.0.0",
|
"@types/mockdate": "^2.0.0",
|
||||||
"@types/mongodb": "^2.2.7",
|
"@types/mongodb": "^2.2.7",
|
||||||
"@types/nedb": "^1.8.3",
|
"@types/nedb": "^1.8.3",
|
||||||
"@types/nodemailer": "^1.3.32",
|
"@types/nodemailer": "^3.1.3",
|
||||||
"@types/object-path": "^0.9.28",
|
"@types/object-path": "^0.9.28",
|
||||||
"@types/proxyquire": "^1.3.27",
|
"@types/proxyquire": "^1.3.27",
|
||||||
"@types/query-string": "^4.3.1",
|
"@types/query-string": "^4.3.1",
|
||||||
|
|
|
@ -13,14 +13,11 @@ if (!configurationFilepath) {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Parse configuration file: %s", configurationFilepath);
|
|
||||||
|
|
||||||
const yamlContent = YAML.load(configurationFilepath);
|
const yamlContent = YAML.load(configurationFilepath);
|
||||||
|
|
||||||
const deps: GlobalDependencies = {
|
const deps: GlobalDependencies = {
|
||||||
u2f: require("u2f"),
|
u2f: require("u2f"),
|
||||||
dovehash: require("dovehash"),
|
dovehash: require("dovehash"),
|
||||||
nodemailer: require("nodemailer"),
|
|
||||||
ldapjs: require("ldapjs"),
|
ldapjs: require("ldapjs"),
|
||||||
session: require("express-session"),
|
session: require("express-session"),
|
||||||
winston: require("winston"),
|
winston: require("winston"),
|
||||||
|
@ -29,8 +26,5 @@ const deps: GlobalDependencies = {
|
||||||
ConnectRedis: require("connect-redis")
|
ConnectRedis: require("connect-redis")
|
||||||
};
|
};
|
||||||
|
|
||||||
const server = new Server();
|
const server = new Server(deps);
|
||||||
server.start(yamlContent, deps)
|
server.start(yamlContent, deps);
|
||||||
.then(() => {
|
|
||||||
console.log("The server is started!");
|
|
||||||
});
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ const INITIAL_AUTHENTICATION_SESSION: AuthenticationSession = {
|
||||||
|
|
||||||
export function reset(req: express.Request): void {
|
export function reset(req: express.Request): void {
|
||||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
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, {});
|
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);
|
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||||
if (!req.session) {
|
if (!req.session) {
|
||||||
const errorMsg = "Something is wrong with session cookies. Please check Redis is running and Authelia can contact it.";
|
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));
|
return BluebirdPromise.reject(new Error(errorMsg));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.session.auth) {
|
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);
|
reset(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
import express = require("express");
|
import express = require("express");
|
||||||
import { Winston } from "winston";
|
|
||||||
import BluebirdPromise = require("bluebird");
|
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 {
|
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.status(code);
|
||||||
res.send();
|
res.send();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replyWithError400(res: express.Response, logger: Winston) {
|
export function replyWithError400(req: express.Request,
|
||||||
return replyWithError(res, 400, logger);
|
res: express.Response, logger: IRequestLogger) {
|
||||||
|
return replyWithError(req, res, 400, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replyWithError401(res: express.Response, logger: Winston) {
|
export function replyWithError401(req: express.Request,
|
||||||
return replyWithError(res, 401, logger);
|
res: express.Response, logger: IRequestLogger) {
|
||||||
|
return replyWithError(req, res, 401, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replyWithError403(res: express.Response, logger: Winston) {
|
export function replyWithError403(req: express.Request,
|
||||||
return replyWithError(res, 403, logger);
|
res: express.Response, logger: IRequestLogger) {
|
||||||
|
return replyWithError(req, res, 403, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replyWithError500(res: express.Response, logger: Winston) {
|
export function replyWithError500(req: express.Request,
|
||||||
return replyWithError(res, 500, logger);
|
res: express.Response, logger: IRequestLogger) {
|
||||||
|
return replyWithError(req, res, 500, logger);
|
||||||
}
|
}
|
|
@ -33,20 +33,20 @@ export interface IdentityValidable {
|
||||||
mailSubject(): string;
|
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 five_minutes = 4 * 60 * 1000;
|
||||||
const token = randomstring.generate({ length: 64 });
|
const token = randomstring.generate({ length: 64 });
|
||||||
const that = this;
|
const that = this;
|
||||||
|
|
||||||
logger.debug("identity_check: issue identity token %s for 5 minutes", token);
|
|
||||||
return userDataStore.produceIdentityValidationToken(userid, token, challenge, five_minutes)
|
return userDataStore.produceIdentityValidationToken(userid, token, challenge, five_minutes)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return BluebirdPromise.resolve(token);
|
return BluebirdPromise.resolve(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function consumeToken(token: string, challenge: string, userDataStore: IUserDataStore, logger: Winston): BluebirdPromise<IdentityValidationDocument> {
|
function consumeToken(token: string, challenge: string, userDataStore: IUserDataStore)
|
||||||
logger.debug("identity_check: consume token %s", token);
|
: BluebirdPromise<IdentityValidationDocument> {
|
||||||
return userDataStore.consumeIdentityValidationToken(token, challenge);
|
return userDataStore.consumeIdentityValidationToken(token, challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
|
||||||
|
|
||||||
let authSession: AuthenticationSession.AuthenticationSession;
|
let authSession: AuthenticationSession.AuthenticationSession;
|
||||||
const identityToken = objectPath.get<express.Request, string>(req, "query.identity_token");
|
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)
|
return checkIdentityToken(req, identityToken)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
|
@ -81,7 +81,7 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
|
||||||
authSession = _authSession;
|
authSession = _authSession;
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return consumeToken(identityToken, handler.challenge(), userDataStore, logger);
|
return consumeToken(identityToken, handler.challenge(), userDataStore);
|
||||||
})
|
})
|
||||||
.then(function (doc: IdentityValidationDocument) {
|
.then(function (doc: IdentityValidationDocument) {
|
||||||
authSession.identity_check = {
|
authSession.identity_check = {
|
||||||
|
@ -91,9 +91,9 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
|
||||||
handler.postValidationResponse(req, res);
|
handler.postValidationResponse(req, res);
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(res, logger))
|
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger))
|
||||||
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(res, logger))
|
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(req, res, logger))
|
||||||
.catch(ErrorReplies.replyWithError500(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 notifier = ServerVariablesHandler.getNotifier(req.app);
|
||||||
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
||||||
let identity: Identity.Identity;
|
let identity: Identity.Identity;
|
||||||
logger.info("Identity Validation: Start identity validation");
|
|
||||||
|
|
||||||
return handler.preValidationInit(req)
|
return handler.preValidationInit(req)
|
||||||
.then(function (id: Identity.Identity) {
|
.then(function (id: Identity.Identity) {
|
||||||
logger.debug("Identity Validation: retrieved identity is %s", JSON.stringify(id));
|
|
||||||
identity = id;
|
identity = id;
|
||||||
const email = identity.email;
|
const email = identity.email;
|
||||||
const userid = identity.userid;
|
const userid = identity.userid;
|
||||||
|
logger.info(req, "Start identity validation of user \"%s\"", userid);
|
||||||
|
|
||||||
if (!(email && userid))
|
if (!(email && userid))
|
||||||
return BluebirdPromise.reject(new Exceptions.IdentityError("Missing user id or email address"));
|
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) {
|
.then(function (token: string) {
|
||||||
const host = req.get("Host");
|
const host = req.get("Host");
|
||||||
const link_url = util.format("https://%s%s?identity_token=%s", host, postValidationEndpoint, token);
|
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);
|
return notifier.notify(identity, handler.mailSubject(), link_url);
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
handler.preValidationResponse(req, res);
|
handler.preValidationResponse(req, res);
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(res, logger))
|
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger))
|
||||||
.catch(Exceptions.IdentityError, ErrorReplies.replyWithError400(res, logger))
|
.catch(Exceptions.IdentityError, ErrorReplies.replyWithError400(req, res, logger))
|
||||||
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(res, logger))
|
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(req, res, logger))
|
||||||
.catch(ErrorReplies.replyWithError500(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) {
|
function withLog(fn: (req: Express.Request, res: Express.Response) => void) {
|
||||||
return function(req: Express.Request, res: Express.Response) {
|
return function(req: Express.Request, res: Express.Response) {
|
||||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
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);
|
fn(req, res);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import ObjectPath = require("object-path");
|
||||||
|
|
||||||
import { AccessController } from "./access_control/AccessController";
|
import { AccessController } from "./access_control/AccessController";
|
||||||
import { AppConfiguration, UserConfiguration } from "./configuration/Configuration";
|
import { AppConfiguration, UserConfiguration } from "./configuration/Configuration";
|
||||||
|
@ -12,12 +13,16 @@ import { RestApi } from "./RestApi";
|
||||||
import { Client } from "./ldap/Client";
|
import { Client } from "./ldap/Client";
|
||||||
import { ServerVariablesHandler } from "./ServerVariablesHandler";
|
import { ServerVariablesHandler } from "./ServerVariablesHandler";
|
||||||
import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder";
|
import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder";
|
||||||
|
import { GlobalLogger } from "./logging/GlobalLogger";
|
||||||
|
import { RequestLogger } from "./logging/RequestLogger";
|
||||||
|
|
||||||
import * as Express from "express";
|
import * as Express from "express";
|
||||||
import * as BodyParser from "body-parser";
|
import * as BodyParser from "body-parser";
|
||||||
import * as Path from "path";
|
import * as Path from "path";
|
||||||
import * as http from "http";
|
import * as http from "http";
|
||||||
|
|
||||||
|
const addRequestId = require("express-request-id")();
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
|
|
||||||
const TRUST_PROXY = "trust proxy";
|
const TRUST_PROXY = "trust proxy";
|
||||||
|
@ -25,9 +30,19 @@ const VIEWS = "views";
|
||||||
const VIEW_ENGINE = "view engine";
|
const VIEW_ENGINE = "view engine";
|
||||||
const PUG = "pug";
|
const PUG = "pug";
|
||||||
|
|
||||||
|
function clone(obj: any) {
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
|
||||||
export default class Server {
|
export default class Server {
|
||||||
private httpServer: http.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 {
|
private setupExpressApplication(config: AppConfiguration, app: Express.Application, deps: GlobalDependencies): void {
|
||||||
const viewsDirectory = Path.resolve(__dirname, "../views");
|
const viewsDirectory = Path.resolve(__dirname, "../views");
|
||||||
|
@ -39,6 +54,7 @@ export default class Server {
|
||||||
app.use(BodyParser.urlencoded({ extended: false }));
|
app.use(BodyParser.urlencoded({ extended: false }));
|
||||||
app.use(BodyParser.json());
|
app.use(BodyParser.json());
|
||||||
app.use(deps.session(expressSessionOptions));
|
app.use(deps.session(expressSessionOptions));
|
||||||
|
app.use(addRequestId);
|
||||||
app.disable("x-powered-by");
|
app.disable("x-powered-by");
|
||||||
|
|
||||||
app.set(TRUST_PROXY, 1);
|
app.set(TRUST_PROXY, 1);
|
||||||
|
@ -48,39 +64,61 @@ export default class Server {
|
||||||
RestApi.setup(app);
|
RestApi.setup(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
private adaptConfiguration(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): AppConfiguration {
|
private displayConfigurations(userConfiguration: UserConfiguration,
|
||||||
const config = ConfigurationAdapter.adapt(yamlConfiguration);
|
appConfiguration: AppConfiguration) {
|
||||||
|
const displayableUserConfiguration = clone(userConfiguration);
|
||||||
|
const displayableAppConfiguration = clone(appConfiguration);
|
||||||
|
const STARS = "*****";
|
||||||
|
|
||||||
// by default the level of logs is info
|
displayableUserConfiguration.ldap.password = STARS;
|
||||||
deps.winston.level = config.logs_level;
|
displayableUserConfiguration.session.secret = STARS;
|
||||||
console.log("Log level = ", deps.winston.level);
|
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));
|
displayableAppConfiguration.ldap.password = STARS;
|
||||||
deps.winston.debug("Authelia configuration is %s", JSON.stringify(config, undefined, 2));
|
displayableAppConfiguration.session.secret = STARS;
|
||||||
return config;
|
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> {
|
private setup(config: AppConfiguration, app: Express.Application, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||||
this.setupExpressApplication(config, app, deps);
|
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) {
|
private startServer(app: Express.Application, port: number) {
|
||||||
|
const that = this;
|
||||||
return new BluebirdPromise<void>((resolve, reject) => {
|
return new BluebirdPromise<void>((resolve, reject) => {
|
||||||
this.httpServer = app.listen(port, function (err: string) {
|
this.httpServer = app.listen(port, function (err: string) {
|
||||||
console.log("Listening on %d...", port);
|
that.globalLogger.info("Listening on port %d...", port);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
start(yamlConfiguration: UserConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
start(userConfiguration: UserConfiguration, deps: GlobalDependencies)
|
||||||
|
: BluebirdPromise<void> {
|
||||||
const that = this;
|
const that = this;
|
||||||
const app = Express();
|
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 () {
|
.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 winston = require("winston");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import U2F = require("u2f");
|
import U2F = require("u2f");
|
||||||
|
import Nodemailer = require("nodemailer");
|
||||||
|
|
||||||
|
import { IRequestLogger } from "./logging/IRequestLogger";
|
||||||
|
import { RequestLogger } from "./logging/RequestLogger";
|
||||||
|
|
||||||
import { IAuthenticator } from "./ldap/IAuthenticator";
|
import { IAuthenticator } from "./ldap/IAuthenticator";
|
||||||
import { IPasswordUpdater } from "./ldap/IPasswordUpdater";
|
import { IPasswordUpdater } from "./ldap/IPasswordUpdater";
|
||||||
|
@ -14,40 +18,28 @@ import { LdapClientFactory } from "./ldap/LdapClientFactory";
|
||||||
|
|
||||||
import { TOTPValidator } from "./TOTPValidator";
|
import { TOTPValidator } from "./TOTPValidator";
|
||||||
import { TOTPGenerator } from "./TOTPGenerator";
|
import { TOTPGenerator } from "./TOTPGenerator";
|
||||||
|
|
||||||
|
import { NotifierFactory } from "./notifiers/NotifierFactory";
|
||||||
|
import { MailSenderBuilder } from "./notifiers/MailSenderBuilder";
|
||||||
|
|
||||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||||
import { UserDataStore } from "./storage/UserDataStore";
|
import { UserDataStore } from "./storage/UserDataStore";
|
||||||
import { INotifier } from "./notifiers/INotifier";
|
import { INotifier } from "./notifiers/INotifier";
|
||||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||||
import Configuration = require("./configuration/Configuration");
|
import Configuration = require("./configuration/Configuration");
|
||||||
import { AccessController } from "./access_control/AccessController";
|
import { AccessController } from "./access_control/AccessController";
|
||||||
import { NotifierFactory } from "./notifiers/NotifierFactory";
|
|
||||||
import { CollectionFactoryFactory } from "./storage/CollectionFactoryFactory";
|
import { CollectionFactoryFactory } from "./storage/CollectionFactoryFactory";
|
||||||
import { ICollectionFactory } from "./storage/ICollectionFactory";
|
import { ICollectionFactory } from "./storage/ICollectionFactory";
|
||||||
import { MongoCollectionFactory } from "./storage/mongo/MongoCollectionFactory";
|
import { MongoCollectionFactory } from "./storage/mongo/MongoCollectionFactory";
|
||||||
import { MongoConnectorFactory } from "./connectors/mongo/MongoConnectorFactory";
|
import { MongoConnectorFactory } from "./connectors/mongo/MongoConnectorFactory";
|
||||||
import { IMongoClient } from "./connectors/mongo/IMongoClient";
|
import { IMongoClient } from "./connectors/mongo/IMongoClient";
|
||||||
|
|
||||||
import { GlobalDependencies } from "../../types/Dependencies";
|
import { GlobalDependencies } from "../../types/Dependencies";
|
||||||
|
import { ServerVariables } from "./ServerVariables";
|
||||||
|
|
||||||
import express = require("express");
|
import express = require("express");
|
||||||
|
|
||||||
export const VARIABLES_KEY = "authelia-variables";
|
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 {
|
class UserDataStoreFactory {
|
||||||
static create(config: Configuration.AppConfiguration): BluebirdPromise<UserDataStore> {
|
static create(config: Configuration.AppConfiguration): BluebirdPromise<UserDataStore> {
|
||||||
if (config.storage.local) {
|
if (config.storage.local) {
|
||||||
|
@ -73,8 +65,10 @@ class UserDataStoreFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ServerVariablesHandler {
|
export class ServerVariablesHandler {
|
||||||
static initialize(app: express.Application, config: Configuration.AppConfiguration, deps: GlobalDependencies): BluebirdPromise<void> {
|
static initialize(app: express.Application, config: Configuration.AppConfiguration, requestLogger: IRequestLogger,
|
||||||
const notifier = NotifierFactory.build(config.notifier, deps.nodemailer);
|
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 ldapClientFactory = new LdapClientFactory(config.ldap, deps.ldapjs);
|
||||||
const clientFactory = new ClientFactory(config.ldap, ldapClientFactory, deps.dovehash, deps.winston);
|
const clientFactory = new ClientFactory(config.ldap, ldapClientFactory, deps.dovehash, deps.winston);
|
||||||
|
|
||||||
|
@ -96,20 +90,20 @@ export class ServerVariablesHandler {
|
||||||
ldapAuthenticator: ldapAuthenticator,
|
ldapAuthenticator: ldapAuthenticator,
|
||||||
ldapPasswordUpdater: ldapPasswordUpdater,
|
ldapPasswordUpdater: ldapPasswordUpdater,
|
||||||
ldapEmailsRetriever: ldapEmailsRetriever,
|
ldapEmailsRetriever: ldapEmailsRetriever,
|
||||||
logger: deps.winston,
|
logger: requestLogger,
|
||||||
notifier: notifier,
|
notifier: notifier,
|
||||||
regulator: regulator,
|
regulator: regulator,
|
||||||
totpGenerator: totpGenerator,
|
totpGenerator: totpGenerator,
|
||||||
totpValidator: totpValidator,
|
totpValidator: totpValidator,
|
||||||
u2f: deps.u2f,
|
u2f: deps.u2f,
|
||||||
userDataStore: userDataStore
|
userDataStore: userDataStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
app.set(VARIABLES_KEY, variables);
|
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;
|
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 * as BluebirdPromise from "bluebird";
|
||||||
import nodemailer = require("nodemailer");
|
|
||||||
|
|
||||||
import { Nodemailer } from "../../../types/Dependencies";
|
|
||||||
import { AbstractEmailNotifier } from "../notifiers/AbstractEmailNotifier";
|
import { AbstractEmailNotifier } from "../notifiers/AbstractEmailNotifier";
|
||||||
import { GmailNotifierConfiguration } from "../configuration/Configuration";
|
import { GmailNotifierConfiguration } from "../configuration/Configuration";
|
||||||
|
import { IMailSender } from "./IMailSender";
|
||||||
|
|
||||||
export class GMailNotifier extends AbstractEmailNotifier {
|
export class GMailNotifier extends AbstractEmailNotifier {
|
||||||
private transporter: any;
|
private mailSender: IMailSender;
|
||||||
|
|
||||||
constructor(options: GmailNotifierConfiguration, nodemailer: Nodemailer) {
|
constructor(options: GmailNotifierConfiguration, mailSender: IMailSender) {
|
||||||
super();
|
super();
|
||||||
const transporter = nodemailer.createTransport({
|
this.mailSender = mailSender;
|
||||||
service: "gmail",
|
|
||||||
auth: {
|
|
||||||
user: options.username,
|
|
||||||
pass: options.password
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.transporter = BluebirdPromise.promisifyAll(transporter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEmail(email: string, subject: string, content: string) {
|
sendEmail(email: string, subject: string, content: string) {
|
||||||
|
@ -28,6 +20,6 @@ export class GMailNotifier extends AbstractEmailNotifier {
|
||||||
subject: subject,
|
subject: subject,
|
||||||
html: content
|
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 { NotifierConfiguration } from "../configuration/Configuration";
|
||||||
import { Nodemailer } from "../../../types/Dependencies";
|
import Nodemailer = require("nodemailer");
|
||||||
import { INotifier } from "./INotifier";
|
import { INotifier } from "./INotifier";
|
||||||
|
|
||||||
import { GMailNotifier } from "./GMailNotifier";
|
import { GMailNotifier } from "./GMailNotifier";
|
||||||
import { SmtpNotifier } from "./SmtpNotifier";
|
import { SmtpNotifier } from "./SmtpNotifier";
|
||||||
|
import { IMailSender } from "./IMailSender";
|
||||||
|
import { IMailSenderBuilder } from "./IMailSenderBuilder";
|
||||||
|
|
||||||
export class NotifierFactory {
|
export class NotifierFactory {
|
||||||
static build(options: NotifierConfiguration, nodemailer: Nodemailer): INotifier {
|
static build(options: NotifierConfiguration, mailSenderBuilder: IMailSenderBuilder): INotifier {
|
||||||
if ("gmail" in options) {
|
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) {
|
else if ("smtp" in options) {
|
||||||
return new SmtpNotifier(options.smtp, nodemailer);
|
const mailSender = mailSenderBuilder.buildSmtp(options.smtp);
|
||||||
|
return new SmtpNotifier(options.smtp, mailSender);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new Error("No available notifier option detected.");
|
throw new Error("No available notifier option detected.");
|
||||||
|
|
|
@ -1,37 +1,18 @@
|
||||||
|
|
||||||
|
|
||||||
import * as BluebirdPromise from "bluebird";
|
import * as BluebirdPromise from "bluebird";
|
||||||
import Nodemailer = require("nodemailer");
|
|
||||||
|
|
||||||
|
import { IMailSender } from "./IMailSender";
|
||||||
import { AbstractEmailNotifier } from "../notifiers/AbstractEmailNotifier";
|
import { AbstractEmailNotifier } from "../notifiers/AbstractEmailNotifier";
|
||||||
import { SmtpNotifierConfiguration } from "../configuration/Configuration";
|
import { SmtpNotifierConfiguration } from "../configuration/Configuration";
|
||||||
|
|
||||||
export class SmtpNotifier extends AbstractEmailNotifier {
|
export class SmtpNotifier extends AbstractEmailNotifier {
|
||||||
private transporter: any;
|
private mailSender: IMailSender;
|
||||||
|
|
||||||
constructor(options: SmtpNotifierConfiguration, nodemailer: typeof Nodemailer) {
|
constructor(options: SmtpNotifierConfiguration,
|
||||||
|
mailSender: IMailSender) {
|
||||||
super();
|
super();
|
||||||
const smtpOptions = {
|
this.mailSender = mailSender;
|
||||||
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");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEmail(email: string, subject: string, content: string) {
|
sendEmail(email: string, subject: string, content: string) {
|
||||||
|
@ -41,11 +22,7 @@ Please check the service is running and your credentials are correct.");
|
||||||
subject: subject,
|
subject: subject,
|
||||||
html: content
|
html: content
|
||||||
};
|
};
|
||||||
return this.transporter.sendMail(mailOptions, (error: Error, data: string) => {
|
const that = this;
|
||||||
if (error) {
|
return this.mailSender.send(mailOptions);
|
||||||
return console.log(error);
|
|
||||||
}
|
|
||||||
console.log("Message sent: %s", JSON.stringify(data));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,6 @@ export default function (callback: Handler): Handler {
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return callback(req, res);
|
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");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
|
||||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
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", {
|
res.render("firstfactor", {
|
||||||
first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
|
first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
|
||||||
reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET
|
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) {
|
if (!username || !password) {
|
||||||
const err = new Error("No username or password");
|
const err = new Error("No username or password");
|
||||||
ErrorReplies.replyWithError401(res, logger)(err);
|
ErrorReplies.replyWithError401(req, res, logger)(err);
|
||||||
return BluebirdPromise.reject(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);
|
const accessController = ServerVariablesHandler.getAccessController(req.app);
|
||||||
let authSession: AuthenticationSession.AuthenticationSession;
|
let authSession: AuthenticationSession.AuthenticationSession;
|
||||||
|
|
||||||
logger.info("1st factor: Starting authentication of user \"%s\"", username);
|
logger.info(req, "Starting authentication of user \"%s\"", username);
|
||||||
logger.debug("1st factor: Start bind operation against LDAP");
|
|
||||||
logger.debug("1st factor: username=%s", username);
|
|
||||||
|
|
||||||
return AuthenticationSession.get(req)
|
return AuthenticationSession.get(req)
|
||||||
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
|
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
|
||||||
authSession = _authSession;
|
authSession = _authSession;
|
||||||
return regulator.regulate(username);
|
return regulator.regulate(username);
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
logger.info("1st factor: No regulation applied.");
|
logger.info(req, "No regulation applied.");
|
||||||
return ldap.authenticate(username, password);
|
return ldap.authenticate(username, password);
|
||||||
})
|
})
|
||||||
.then(function (groupsAndEmails: GroupsAndEmails) {
|
.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));
|
JSON.stringify(groupsAndEmails));
|
||||||
authSession.userid = username;
|
authSession.userid = username;
|
||||||
authSession.first_factor = true;
|
authSession.first_factor = true;
|
||||||
|
@ -56,43 +53,40 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
||||||
|
|
||||||
if (!emails || emails.length <= 0) {
|
if (!emails || emails.length <= 0) {
|
||||||
const errMessage = "No emails found. The user should have at least one email address to reset password.";
|
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));
|
return BluebirdPromise.reject(new Error(errMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
authSession.email = emails[0];
|
authSession.email = emails[0];
|
||||||
authSession.groups = groups;
|
authSession.groups = groups;
|
||||||
|
|
||||||
logger.debug("1st factor: Mark successful authentication to regulator.");
|
logger.debug(req, "Mark successful authentication to regulator.");
|
||||||
regulator.mark(username, true);
|
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) {
|
if (onlyBasicAuth) {
|
||||||
res.send({
|
res.send({
|
||||||
redirect: redirectUrl
|
redirect: redirectUrl
|
||||||
});
|
});
|
||||||
logger.debug("1st factor: redirect to '%s'", redirectUrl);
|
logger.debug(req, "Redirect to '%s'", redirectUrl);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let newRedirectUrl = Endpoint.SECOND_FACTOR_GET;
|
let newRedirectUrl = Endpoint.SECOND_FACTOR_GET;
|
||||||
if (redirectUrl !== "undefined") {
|
if (redirectUrl !== "undefined") {
|
||||||
newRedirectUrl += "?redirect=" + encodeURIComponent(redirectUrl);
|
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({
|
res.send({
|
||||||
redirect: newRedirectUrl
|
redirect: newRedirectUrl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(res, logger))
|
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(req, res, logger))
|
||||||
.catch(exceptions.LdapBindError, function (err: Error) {
|
.catch(exceptions.LdapBindError, function (err: Error) {
|
||||||
regulator.mark(username, false);
|
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.AuthenticationRegulationError, ErrorReplies.replyWithError403(req, res, logger))
|
||||||
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError401(res, logger))
|
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError401(req, res, logger))
|
||||||
.catch(ErrorReplies.replyWithError500(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)
|
return AuthenticationSession.get(req)
|
||||||
.then(function (_authSession) {
|
.then(function (_authSession) {
|
||||||
authSession = _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);
|
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) {
|
if (authSession.identity_check.challenge != Constants.CHALLENGE) {
|
||||||
res.status(403);
|
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);
|
return ldapPasswordUpdater.updatePassword(authSession.identity_check.userid, newPassword);
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
logger.info("POST reset-password: Password reset for user '%s'",
|
logger.info(req, "Password reset for user '%s'",
|
||||||
authSession.identity_check.userid);
|
authSession.identity_check.userid);
|
||||||
AuthenticationSession.reset(req);
|
AuthenticationSession.reset(req);
|
||||||
res.status(204);
|
res.status(204);
|
||||||
res.send();
|
res.send();
|
||||||
return BluebirdPromise.resolve();
|
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 logger = ServerVariablesHandler.getLogger(req.app);
|
||||||
const userid: string = objectPath.get<express.Request, string>(req, "query.userid");
|
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)
|
if (!userid)
|
||||||
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No user id provided"));
|
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No user id provided"));
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@ export default class PasswordResetHandler implements IdentityValidable {
|
||||||
return emailsRetriever.retrieve(userid)
|
return emailsRetriever.retrieve(userid)
|
||||||
.then(function (emails: string[]) {
|
.then(function (emails: string[]) {
|
||||||
if (!emails && emails.length <= 0) throw new Error("No email found");
|
if (!emails && emails.length <= 0) throw new Error("No email found");
|
||||||
|
|
||||||
const identity = {
|
const identity = {
|
||||||
email: emails[0],
|
email: emails[0],
|
||||||
userid: userid
|
userid: userid
|
||||||
|
|
|
@ -10,8 +10,6 @@ const TEMPLATE_NAME = "secondfactor";
|
||||||
export default FirstFactorBlocker.default(handler);
|
export default FirstFactorBlocker.default(handler);
|
||||||
|
|
||||||
function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
|
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, {
|
res.render(TEMPLATE_NAME, {
|
||||||
totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
|
totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
|
||||||
u2f_identity_start_endpoint: Endpoints.SECOND_FACTOR_U2F_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 { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||||
import AuthenticationSession = require("../../AuthenticationSession");
|
import AuthenticationSession = require("../../AuthenticationSession");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import ErrorReplies = require("../../ErrorReplies");
|
||||||
|
|
||||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||||
|
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||||
return AuthenticationSession.get(req)
|
return AuthenticationSession.get(req)
|
||||||
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
||||||
const redirectUrl = req.query.redirect || Endpoints.FIRST_FACTOR_GET;
|
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
|
redirection_url: redirectUrl
|
||||||
});
|
});
|
||||||
return BluebirdPromise.resolve();
|
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 totpGenerator = ServerVariablesHandler.getTOTPGenerator(req.app);
|
||||||
const secret = totpGenerator.generate();
|
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)
|
return userDataStore.saveTOTPSecret(userid, secret)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
objectPath.set(req, "session", undefined);
|
AuthenticationSession.reset(req);
|
||||||
|
|
||||||
res.render(Constants.TEMPLATE_NAME, {
|
res.render(Constants.TEMPLATE_NAME, {
|
||||||
base32_secret: secret.base32,
|
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 {
|
mailSubject(): string {
|
||||||
|
|
|
@ -25,19 +25,19 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr
|
||||||
return AuthenticationSession.get(req)
|
return AuthenticationSession.get(req)
|
||||||
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
|
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
|
||||||
authSession = _authSession;
|
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);
|
return userDataStore.retrieveTOTPSecret(authSession.userid);
|
||||||
})
|
})
|
||||||
.then(function (doc: TOTPSecretDocument) {
|
.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);
|
return totpValidator.validate(token, doc.secret.base32);
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
logger.debug("POST 2ndfactor totp: TOTP validation succeeded");
|
logger.debug(req, "TOTP validation succeeded");
|
||||||
authSession.second_factor = true;
|
authSession.second_factor = true;
|
||||||
redirect(req, res);
|
redirect(req, res);
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(exceptions.InvalidTOTPError, ErrorReplies.replyWithError401(res, logger))
|
.catch(exceptions.InvalidTOTPError, ErrorReplies.replyWithError401(req, res, logger))
|
||||||
.catch(ErrorReplies.replyWithError500(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"));
|
return BluebirdPromise.reject(new Error("Bad challenge for registration request"));
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("U2F register: Finishing registration");
|
logger.info(req, "Finishing registration");
|
||||||
logger.debug("U2F register: registrationRequest = %s", JSON.stringify(registrationRequest));
|
logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
|
||||||
logger.debug("U2F register: registrationResponse = %s", JSON.stringify(registrationResponse));
|
logger.debug(req, "RegistrationResponse = %s", JSON.stringify(registrationResponse));
|
||||||
|
|
||||||
return BluebirdPromise.resolve(u2f.checkRegistration(registrationRequest, 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."));
|
return BluebirdPromise.reject(new Error("Error while registering."));
|
||||||
|
|
||||||
const registrationResult: U2f.RegistrationResult = u2fResult as U2f.RegistrationResult;
|
const registrationResult: U2f.RegistrationResult = u2fResult as U2f.RegistrationResult;
|
||||||
logger.info("U2F register: Store regisutration and reply");
|
logger.info(req, "Store registration and reply");
|
||||||
logger.debug("U2F register: registration = %s", JSON.stringify(registrationResult));
|
logger.debug(req, "RegistrationResult = %s", JSON.stringify(registrationResult));
|
||||||
const registration: U2FRegistration = {
|
const registration: U2FRegistration = {
|
||||||
keyHandle: registrationResult.keyHandle,
|
keyHandle: registrationResult.keyHandle,
|
||||||
publicKey: registrationResult.publicKey
|
publicKey: registrationResult.publicKey
|
||||||
|
@ -67,5 +67,5 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
|
||||||
redirect(req, res);
|
redirect(req, res);
|
||||||
return BluebirdPromise.resolve();
|
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 u2f = ServerVariablesHandler.getU2F(req.app);
|
||||||
const appid: string = u2f_common.extract_app_id(req);
|
const appid: string = u2f_common.extract_app_id(req);
|
||||||
|
|
||||||
logger.debug("U2F register_request: headers=%s", JSON.stringify(req.headers));
|
logger.info(req, "Starting registration for appId '%s'", appid);
|
||||||
logger.info("U2F register_request: Starting registration for appId %s", appid);
|
|
||||||
|
|
||||||
return BluebirdPromise.resolve(u2f.request(appid));
|
return BluebirdPromise.resolve(u2f.request(appid));
|
||||||
})
|
})
|
||||||
.then(function (registrationRequest: U2f.Request) {
|
.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;
|
authSession.register_request = registrationRequest;
|
||||||
res.json(registrationRequest);
|
res.json(registrationRequest);
|
||||||
return BluebirdPromise.resolve();
|
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;
|
authSession = _authSession;
|
||||||
if (!authSession.sign_request) {
|
if (!authSession.sign_request) {
|
||||||
const err = new Error("No sign request");
|
const err = new Error("No sign request");
|
||||||
ErrorReplies.replyWithError401(res, logger)(err);
|
ErrorReplies.replyWithError401(req, res, logger)(err);
|
||||||
return BluebirdPromise.reject(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 u2f = ServerVariablesHandler.getU2F(req.app);
|
||||||
const signRequest = authSession.sign_request;
|
const signRequest = authSession.sign_request;
|
||||||
const signData: U2f.SignatureData = req.body;
|
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));
|
return BluebirdPromise.resolve(u2f.checkSignature(signRequest, signData, doc.registration.publicKey));
|
||||||
})
|
})
|
||||||
.then(function (result: U2f.SignatureResult | U2f.Error): BluebirdPromise<void> {
|
.then(function (result: U2f.SignatureResult | U2f.Error): BluebirdPromise<void> {
|
||||||
if (objectPath.has(result, "errorCode"))
|
if (objectPath.has(result, "errorCode"))
|
||||||
return BluebirdPromise.reject(new Error("Error while signing"));
|
return BluebirdPromise.reject(new Error("Error while signing"));
|
||||||
logger.info("U2F sign: Authentication successful");
|
logger.info(req, "Successful authentication");
|
||||||
authSession.second_factor = true;
|
authSession.second_factor = true;
|
||||||
redirect(req, res);
|
redirect(req, res);
|
||||||
return BluebirdPromise.resolve();
|
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 u2f = ServerVariablesHandler.getU2F(req.app);
|
||||||
const appId: string = u2f_common.extract_app_id(req);
|
const appId: string = u2f_common.extract_app_id(req);
|
||||||
logger.info("U2F sign_request: Start authentication to app %s", appId);
|
logger.info(req, "Start authentication of app '%s'", appId);
|
||||||
logger.debug("U2F sign_request: appId=%s, keyHandle=%s", appId, JSON.stringify(doc.registration.keyHandle));
|
logger.debug(req, "AppId = %s, keyHandle = %s", appId, JSON.stringify(doc.registration.keyHandle));
|
||||||
|
|
||||||
const request = u2f.request(appId, doc.registration.keyHandle);
|
const request = u2f.request(appId, doc.registration.keyHandle);
|
||||||
const authenticationMessage: SignMessage = {
|
const authenticationMessage: SignMessage = {
|
||||||
|
@ -44,13 +44,13 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr
|
||||||
return BluebirdPromise.resolve(authenticationMessage);
|
return BluebirdPromise.resolve(authenticationMessage);
|
||||||
})
|
})
|
||||||
.then(function (authenticationMessage: SignMessage) {
|
.then(function (authenticationMessage: SignMessage) {
|
||||||
logger.info("U2F sign_request: Store authentication request and reply");
|
logger.info(req, "Store authentication request and reply");
|
||||||
logger.debug("U2F sign_request: authenticationRequest=%s", authenticationMessage);
|
logger.debug(req, "AuthenticationRequest = %s", authenticationMessage);
|
||||||
authSession.sign_request = authenticationMessage.request;
|
authSession.sign_request = authenticationMessage.request;
|
||||||
res.json(authenticationMessage);
|
res.json(authenticationMessage);
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(exceptions.AccessDeniedError, ErrorReplies.replyWithError401(res, logger))
|
.catch(exceptions.AccessDeniedError, ErrorReplies.replyWithError401(req, res, logger))
|
||||||
.catch(ErrorReplies.replyWithError500(res, logger));
|
.catch(ErrorReplies.replyWithError500(req, res, logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,10 @@ import ErrorReplies = require("../../ErrorReplies");
|
||||||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||||
import AuthenticationSession = require("../../AuthenticationSession");
|
import AuthenticationSession = require("../../AuthenticationSession");
|
||||||
import Constants = require("../../../../../shared/constants");
|
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> {
|
function verify_filter(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||||
|
@ -16,30 +20,36 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
|
||||||
|
|
||||||
return AuthenticationSession.get(req)
|
return AuthenticationSession.get(req)
|
||||||
.then(function (authSession) {
|
.then(function (authSession) {
|
||||||
logger.debug("Verify: headers are %s", JSON.stringify(req.headers));
|
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] +
|
||||||
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] + req.headers["x-original-uri"]));
|
req.headers["x-original-uri"]));
|
||||||
|
|
||||||
const username = authSession.userid;
|
const username = authSession.userid;
|
||||||
const groups = authSession.groups;
|
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";
|
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 host = objectPath.get<express.Request, string>(req, "headers.host");
|
||||||
const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri");
|
const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri");
|
||||||
|
|
||||||
const domain = host.split(":")[0];
|
const domain = host.split(":")[0];
|
||||||
logger.debug("Verify: domain=%s, path=%s", domain, path);
|
logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, path,
|
||||||
logger.debug("Verify: user=%s, groups=%s", username, groups.join(","));
|
username, groups.join(","));
|
||||||
|
|
||||||
if (!authSession.first_factor)
|
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);
|
const isAllowed = accessController.isAccessAllowed(domain, path, username, groups);
|
||||||
if (!isAllowed) return BluebirdPromise.reject(
|
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)
|
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-User", username);
|
||||||
res.setHeader("Remote-Groups", groups.join(","));
|
res.setHeader("Remote-Groups", groups.join(","));
|
||||||
|
@ -56,7 +66,7 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
||||||
res.send();
|
res.send();
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError403(res, logger))
|
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError403(req, res, logger))
|
||||||
.catch(ErrorReplies.replyWithError401(res, logger));
|
.catch(ErrorReplies.replyWithError401(req, res, logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,10 @@ import express = require("express");
|
||||||
import winston = require("winston");
|
import winston = require("winston");
|
||||||
import speakeasy = require("speakeasy");
|
import speakeasy = require("speakeasy");
|
||||||
import u2f = require("u2f");
|
import u2f = require("u2f");
|
||||||
import nodemailer = require("nodemailer");
|
|
||||||
import session = require("express-session");
|
import session = require("express-session");
|
||||||
|
|
||||||
import { AppConfiguration, UserConfiguration } from "../src/lib/configuration/Configuration";
|
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";
|
import Server from "../src/lib/Server";
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,17 +18,9 @@ describe("test server configuration", function () {
|
||||||
let sessionMock: Sinon.SinonSpy;
|
let sessionMock: Sinon.SinonSpy;
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
const transporter = {
|
|
||||||
sendMail: Sinon.stub().yields()
|
|
||||||
};
|
|
||||||
|
|
||||||
const createTransport = Sinon.stub(nodemailer, "createTransport");
|
|
||||||
createTransport.returns(transporter);
|
|
||||||
|
|
||||||
sessionMock = Sinon.spy(session);
|
sessionMock = Sinon.spy(session);
|
||||||
|
|
||||||
deps = {
|
deps = {
|
||||||
nodemailer: nodemailer,
|
|
||||||
speakeasy: speakeasy,
|
speakeasy: speakeasy,
|
||||||
u2f: u2f,
|
u2f: u2f,
|
||||||
nedb: nedb,
|
nedb: nedb,
|
||||||
|
@ -79,7 +70,7 @@ describe("test server configuration", function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const server = new Server();
|
const server = new Server(deps);
|
||||||
server.start(config, deps);
|
server.start(config, deps);
|
||||||
|
|
||||||
assert(sessionMock.calledOnce);
|
assert(sessionMock.calledOnce);
|
||||||
|
|
|
@ -55,7 +55,6 @@ describe("test session configuration builder", function () {
|
||||||
ConnectRedis: Sinon.spy() as any,
|
ConnectRedis: Sinon.spy() as any,
|
||||||
ldapjs: Sinon.spy() as any,
|
ldapjs: Sinon.spy() as any,
|
||||||
nedb: Sinon.spy() as any,
|
nedb: Sinon.spy() as any,
|
||||||
nodemailer: Sinon.spy() as any,
|
|
||||||
session: Sinon.spy() as any,
|
session: Sinon.spy() as any,
|
||||||
speakeasy: Sinon.spy() as any,
|
speakeasy: Sinon.spy() as any,
|
||||||
u2f: 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,
|
ConnectRedis: Sinon.stub().returns(RedisStoreMock) as any,
|
||||||
ldapjs: Sinon.spy() as any,
|
ldapjs: Sinon.spy() as any,
|
||||||
nedb: Sinon.spy() as any,
|
nedb: Sinon.spy() as any,
|
||||||
nodemailer: Sinon.spy() as any,
|
|
||||||
session: Sinon.spy() as any,
|
session: Sinon.spy() as any,
|
||||||
speakeasy: Sinon.spy() as any,
|
speakeasy: Sinon.spy() as any,
|
||||||
u2f: 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 Sinon = require("sinon");
|
||||||
import express = require("express");
|
import express = require("express");
|
||||||
import winston = require("winston");
|
import { RequestLoggerStub } from "./RequestLoggerStub";
|
||||||
import { UserDataStoreStub } from "./storage/UserDataStoreStub";
|
import { UserDataStoreStub } from "./storage/UserDataStoreStub";
|
||||||
import { VARIABLES_KEY } from "../../src/lib/ServerVariablesHandler";
|
import { VARIABLES_KEY } from "../../src/lib/ServerVariablesHandler";
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export function mock(app: express.Application): ServerVariablesMock {
|
||||||
ldapAuthenticator: Sinon.stub() as any,
|
ldapAuthenticator: Sinon.stub() as any,
|
||||||
ldapEmailsRetriever: Sinon.stub() as any,
|
ldapEmailsRetriever: Sinon.stub() as any,
|
||||||
ldapPasswordUpdater: Sinon.stub() as any,
|
ldapPasswordUpdater: Sinon.stub() as any,
|
||||||
logger: winston,
|
logger: new RequestLoggerStub(),
|
||||||
notifier: Sinon.stub(),
|
notifier: Sinon.stub(),
|
||||||
regulator: Sinon.stub(),
|
regulator: Sinon.stub(),
|
||||||
totpGenerator: 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 * as assert from "assert";
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
|
||||||
import NodemailerMock = require("../mocks/nodemailer");
|
import { MailSenderStub } from "../mocks/notifiers/MailSenderStub";
|
||||||
import GMailNotifier = require("../../src/lib/notifiers/GMailNotifier");
|
import GMailNotifier = require("../../src/lib/notifiers/GMailNotifier");
|
||||||
|
|
||||||
|
|
||||||
describe("test gmail notifier", function () {
|
describe("test gmail notifier", function () {
|
||||||
it("should send an email", function () {
|
it("should send an email to given user", function () {
|
||||||
const transporter = {
|
const mailSender = new MailSenderStub();
|
||||||
sendMail: sinon.stub().yields()
|
|
||||||
};
|
|
||||||
const nodemailerMock = NodemailerMock.NodemailerMock();
|
|
||||||
nodemailerMock.createTransport.returns(transporter);
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
username: "user_gmail",
|
username: "user_gmail",
|
||||||
password: "pass_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 subject = "subject";
|
||||||
|
|
||||||
const identity = {
|
const identity = {
|
||||||
|
@ -31,10 +27,34 @@ describe("test gmail notifier", function () {
|
||||||
|
|
||||||
return sender.notify(identity, subject, url)
|
return sender.notify(identity, subject, url)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
assert.equal(nodemailerMock.createTransport.getCall(0).args[0].auth.user, "user_gmail");
|
assert.equal(mailSender.sendStub.getCall(0).args[0].to, "user@example.com");
|
||||||
assert.equal(nodemailerMock.createTransport.getCall(0).args[0].auth.pass, "pass_gmail");
|
assert.equal(mailSender.sendStub.getCall(0).args[0].subject, "subject");
|
||||||
assert.equal(transporter.sendMail.getCall(0).args[0].to, "user@example.com");
|
return BluebirdPromise.resolve();
|
||||||
assert.equal(transporter.sendMail.getCall(0).args[0].subject, "subject");
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
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,26 +6,23 @@ import * as assert from "assert";
|
||||||
import { NotifierFactory } from "../../src/lib/notifiers/NotifierFactory";
|
import { NotifierFactory } from "../../src/lib/notifiers/NotifierFactory";
|
||||||
import { GMailNotifier } from "../../src/lib/notifiers/GMailNotifier";
|
import { GMailNotifier } from "../../src/lib/notifiers/GMailNotifier";
|
||||||
import { SmtpNotifier } from "../../src/lib/notifiers/SmtpNotifier";
|
import { SmtpNotifier } from "../../src/lib/notifiers/SmtpNotifier";
|
||||||
|
import { MailSenderBuilderStub } from "../mocks/notifiers/MailSenderBuilderStub";
|
||||||
import NodemailerMock = require("../mocks/nodemailer");
|
|
||||||
|
|
||||||
|
|
||||||
describe("test notifier factory", function() {
|
describe("test notifier factory", function () {
|
||||||
let nodemailerMock: NodemailerMock.NodemailerMock;
|
let mailSenderBuilderStub: MailSenderBuilderStub;
|
||||||
it("should build a Gmail Notifier", function() {
|
it("should build a Gmail Notifier", function () {
|
||||||
const options = {
|
const options = {
|
||||||
gmail: {
|
gmail: {
|
||||||
username: "abc",
|
username: "abc",
|
||||||
password: "password"
|
password: "password"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
nodemailerMock = NodemailerMock.NodemailerMock();
|
mailSenderBuilderStub = new MailSenderBuilderStub();
|
||||||
const transporterMock = NodemailerMock.NodemailerTransporterMock();
|
assert(NotifierFactory.build(options, mailSenderBuilderStub) instanceof GMailNotifier);
|
||||||
nodemailerMock.createTransport.returns(transporterMock);
|
|
||||||
assert(NotifierFactory.build(options, nodemailerMock) instanceof GMailNotifier);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should build a SMTP Notifier", function() {
|
it("should build a SMTP Notifier", function () {
|
||||||
const options = {
|
const options = {
|
||||||
smtp: {
|
smtp: {
|
||||||
username: "user",
|
username: "user",
|
||||||
|
@ -36,9 +33,7 @@ describe("test notifier factory", function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
nodemailerMock = NodemailerMock.NodemailerMock();
|
mailSenderBuilderStub = new MailSenderBuilderStub();
|
||||||
const transporterMock = NodemailerMock.NodemailerTransporterMock();
|
assert(NotifierFactory.build(options, mailSenderBuilderStub) instanceof SmtpNotifier);
|
||||||
nodemailerMock.createTransport.returns(transporterMock);
|
|
||||||
assert(NotifierFactory.build(options, nodemailerMock) instanceof SmtpNotifier);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,8 +6,6 @@ import express = require("express");
|
||||||
import nodemailer = require("nodemailer");
|
import nodemailer = require("nodemailer");
|
||||||
import Endpoints = require("../../shared/api");
|
import Endpoints = require("../../shared/api");
|
||||||
|
|
||||||
import NodemailerMock = require("./mocks/nodemailer");
|
|
||||||
|
|
||||||
declare module "request" {
|
declare module "request" {
|
||||||
export interface RequestAPI<TRequest extends Request,
|
export interface RequestAPI<TRequest extends Request,
|
||||||
TOptions extends CoreOptions,
|
TOptions extends CoreOptions,
|
||||||
|
@ -28,58 +26,6 @@ export = function (port: number) {
|
||||||
const PORT = port;
|
const PORT = port;
|
||||||
const BASE_URL = "http://localhost:" + 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) {
|
function execute_totp(jar: request.CookieJar, token: string) {
|
||||||
return requestAsync.postAsync({
|
return requestAsync.postAsync({
|
||||||
url: BASE_URL + Endpoints.SECOND_FACTOR_TOTP_POST,
|
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 });
|
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) {
|
function execute_first_factor(jar: request.CookieJar) {
|
||||||
return requestAsync.postAsync({
|
return requestAsync.postAsync({
|
||||||
url: BASE_URL + Endpoints.FIRST_FACTOR_POST,
|
url: BASE_URL + Endpoints.FIRST_FACTOR_POST,
|
||||||
|
@ -174,13 +85,10 @@ export = function (port: number) {
|
||||||
return {
|
return {
|
||||||
login: execute_login,
|
login: execute_login,
|
||||||
verify: execute_verification,
|
verify: execute_verification,
|
||||||
reset_password: execute_reset_password,
|
|
||||||
u2f_authentication: execute_u2f_authentication,
|
u2f_authentication: execute_u2f_authentication,
|
||||||
u2f_registration: execute_u2f_registration,
|
|
||||||
first_factor: execute_first_factor,
|
first_factor: execute_first_factor,
|
||||||
failing_first_factor: execute_failing_first_factor,
|
failing_first_factor: execute_failing_first_factor,
|
||||||
totp: execute_totp,
|
totp: execute_totp,
|
||||||
register_totp: execute_register_totp,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import AuthenticationRegulatorMock = require("../../mocks/AuthenticationRegulato
|
||||||
import { AccessControllerStub } from "../../mocks/AccessControllerStub";
|
import { AccessControllerStub } from "../../mocks/AccessControllerStub";
|
||||||
import ExpressMock = require("../../mocks/express");
|
import ExpressMock = require("../../mocks/express");
|
||||||
import ServerVariablesMock = require("../../mocks/ServerVariablesMock");
|
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 () {
|
describe("test the first factor validation route", function () {
|
||||||
let req: ExpressMock.RequestMock;
|
let req: ExpressMock.RequestMock;
|
||||||
|
@ -68,7 +68,6 @@ describe("test the first factor validation route", function () {
|
||||||
authenticate: sinon.stub()
|
authenticate: sinon.stub()
|
||||||
} as any;
|
} as any;
|
||||||
serverVariables.config = configuration as any;
|
serverVariables.config = configuration as any;
|
||||||
serverVariables.logger = winston as any;
|
|
||||||
serverVariables.regulator = regulator as any;
|
serverVariables.regulator = regulator as any;
|
||||||
serverVariables.accessController = accessController 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.config = configuration;
|
||||||
serverVariables.ldapEmailsRetriever = {
|
serverVariables.ldapEmailsRetriever = {
|
||||||
retrieve: Sinon.stub()
|
retrieve: Sinon.stub()
|
||||||
|
|
|
@ -51,7 +51,6 @@ describe("test reset password route", function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
serverVariables.logger = winston;
|
|
||||||
serverVariables.config = configuration;
|
serverVariables.config = configuration;
|
||||||
|
|
||||||
serverVariables.ldapPasswordUpdater = {
|
serverVariables.ldapPasswordUpdater = {
|
||||||
|
|
|
@ -19,7 +19,6 @@ describe("test totp register", function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
req = ExpressMock.RequestMock();
|
req = ExpressMock.RequestMock();
|
||||||
const mocks = ServerVariablesMock.mock(req.app);
|
const mocks = ServerVariablesMock.mock(req.app);
|
||||||
mocks.logger = winston;
|
|
||||||
req.session = {};
|
req.session = {};
|
||||||
|
|
||||||
AuthenticationSession.reset(req as any);
|
AuthenticationSession.reset(req as any);
|
||||||
|
|
|
@ -44,8 +44,6 @@ describe("test totp route", function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
mocks.userDataStore.retrieveTOTPSecretStub.returns(BluebirdPromise.resolve(doc));
|
mocks.userDataStore.retrieveTOTPSecretStub.returns(BluebirdPromise.resolve(doc));
|
||||||
|
|
||||||
mocks.logger = winston;
|
|
||||||
mocks.totpValidator = totpValidator;
|
mocks.totpValidator = totpValidator;
|
||||||
mocks.config = config;
|
mocks.config = config;
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ describe("test register handler", function () {
|
||||||
req = ExpressMock.RequestMock();
|
req = ExpressMock.RequestMock();
|
||||||
req.app = {};
|
req.app = {};
|
||||||
const mocks = ServerVariablesMock.mock(req.app);
|
const mocks = ServerVariablesMock.mock(req.app);
|
||||||
mocks.logger = winston;
|
|
||||||
req.session = {};
|
req.session = {};
|
||||||
AuthenticationSession.reset(req as any);
|
AuthenticationSession.reset(req as any);
|
||||||
req.headers = {};
|
req.headers = {};
|
||||||
|
|
|
@ -22,8 +22,6 @@ describe("test u2f routes: register", function () {
|
||||||
req = ExpressMock.RequestMock();
|
req = ExpressMock.RequestMock();
|
||||||
req.app = {};
|
req.app = {};
|
||||||
mocks = ServerVariablesMock.mock(req.app);
|
mocks = ServerVariablesMock.mock(req.app);
|
||||||
mocks.logger = winston;
|
|
||||||
|
|
||||||
req.session = {};
|
req.session = {};
|
||||||
req.headers = {};
|
req.headers = {};
|
||||||
req.headers.host = "localhost";
|
req.headers.host = "localhost";
|
||||||
|
|
|
@ -23,7 +23,6 @@ describe("test u2f routes: register_request", function () {
|
||||||
req = ExpressMock.RequestMock();
|
req = ExpressMock.RequestMock();
|
||||||
req.app = {};
|
req.app = {};
|
||||||
mocks = ServerVariablesMock.mock(req.app);
|
mocks = ServerVariablesMock.mock(req.app);
|
||||||
mocks.logger = winston;
|
|
||||||
req.session = {};
|
req.session = {};
|
||||||
AuthenticationSession.reset(req as any);
|
AuthenticationSession.reset(req as any);
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,6 @@ describe("test u2f routes: sign", function () {
|
||||||
req.app = {};
|
req.app = {};
|
||||||
|
|
||||||
mocks = ServerVariablesMock.mock(req.app);
|
mocks = ServerVariablesMock.mock(req.app);
|
||||||
mocks.logger = winston;
|
|
||||||
|
|
||||||
req.session = {};
|
req.session = {};
|
||||||
AuthenticationSession.reset(req as any);
|
AuthenticationSession.reset(req as any);
|
||||||
req.headers = {};
|
req.headers = {};
|
||||||
|
|
|
@ -26,7 +26,6 @@ describe("test u2f routes: sign_request", function () {
|
||||||
req.app = {};
|
req.app = {};
|
||||||
|
|
||||||
mocks = ServerVariablesMock.mock(req.app);
|
mocks = ServerVariablesMock.mock(req.app);
|
||||||
mocks.logger = winston;
|
|
||||||
|
|
||||||
req.session = {};
|
req.session = {};
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,6 @@ describe("test authentication token verification", function () {
|
||||||
req.headers.host = "secret.example.com";
|
req.headers.host = "secret.example.com";
|
||||||
const mocks = ServerVariablesMock.mock(req.app);
|
const mocks = ServerVariablesMock.mock(req.app);
|
||||||
mocks.config = {} as any;
|
mocks.config = {} as any;
|
||||||
mocks.logger = winston;
|
|
||||||
mocks.accessController = accessController as any;
|
mocks.accessController = accessController as any;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -163,6 +162,7 @@ describe("test authentication token verification", function () {
|
||||||
return AuthenticationSession.get(req as any)
|
return AuthenticationSession.get(req as any)
|
||||||
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
||||||
authSession.first_factor = true;
|
authSession.first_factor = true;
|
||||||
|
authSession.userid = "user1";
|
||||||
return VerifyGet.default(req as express.Request, res as any);
|
return VerifyGet.default(req as express.Request, res as any);
|
||||||
})
|
})
|
||||||
.then(function () {
|
.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);
|
ldap_client.search.yields(undefined, search_res);
|
||||||
|
|
||||||
const deps: GlobalDependencies = {
|
const deps: GlobalDependencies = {
|
||||||
u2f: u2f,
|
u2f: u2f as any,
|
||||||
nedb: nedb,
|
nedb: nedb,
|
||||||
nodemailer: nodemailer,
|
|
||||||
ldapjs: ldap,
|
ldapjs: ldap,
|
||||||
session: ExpressSession,
|
session: ExpressSession,
|
||||||
winston: Winston,
|
winston: Winston,
|
||||||
|
@ -119,7 +118,7 @@ describe("Private pages of the server must not be accessible without session", f
|
||||||
dovehash: Sinon.spy() as any
|
dovehash: Sinon.spy() as any
|
||||||
};
|
};
|
||||||
|
|
||||||
server = new Server();
|
server = new Server(deps);
|
||||||
return server.start(config, 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);
|
ldap_client.search.yields(undefined, search_res);
|
||||||
|
|
||||||
const deps: GlobalDependencies = {
|
const deps: GlobalDependencies = {
|
||||||
u2f: u2f,
|
u2f: u2f as any,
|
||||||
nedb: nedb,
|
nedb: nedb,
|
||||||
nodemailer: nodemailer,
|
|
||||||
ldapjs: ldap,
|
ldapjs: ldap,
|
||||||
session: ExpressSession,
|
session: ExpressSession,
|
||||||
winston: Winston,
|
winston: Winston,
|
||||||
|
@ -119,7 +118,7 @@ describe("Public pages of the server must be accessible without session", functi
|
||||||
dovehash: Sinon.spy() as any
|
dovehash: Sinon.spy() as any
|
||||||
};
|
};
|
||||||
|
|
||||||
server = new Server();
|
server = new Server(deps);
|
||||||
return server.start(config, deps);
|
return server.start(config, deps);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import RedisSession = require("connect-redis");
|
||||||
import dovehash = require("dovehash");
|
import dovehash = require("dovehash");
|
||||||
|
|
||||||
export type Dovehash = typeof dovehash;
|
export type Dovehash = typeof dovehash;
|
||||||
export type Nodemailer = typeof nodemailer;
|
|
||||||
export type Speakeasy = typeof speakeasy;
|
export type Speakeasy = typeof speakeasy;
|
||||||
export type Winston = typeof winston;
|
export type Winston = typeof winston;
|
||||||
export type Session = typeof session;
|
export type Session = typeof session;
|
||||||
|
@ -21,7 +20,6 @@ export type ConnectRedis = typeof RedisSession;
|
||||||
export interface GlobalDependencies {
|
export interface GlobalDependencies {
|
||||||
u2f: U2f;
|
u2f: U2f;
|
||||||
dovehash: Dovehash;
|
dovehash: Dovehash;
|
||||||
nodemailer: Nodemailer;
|
|
||||||
ldapjs: Ldapjs;
|
ldapjs: Ldapjs;
|
||||||
session: Session;
|
session: Session;
|
||||||
ConnectRedis: ConnectRedis;
|
ConnectRedis: ConnectRedis;
|
||||||
|
|
Loading…
Reference in New Issue