Disable notifiers when server uses single factor method only

Notifier is not mandatory when authentication method is single_factor for
all sub-domains since there is no registration required.
pull/175/head
Clement Michaud 2017-10-22 17:42:05 +02:00
parent 3052c883a0
commit 73d5253297
62 changed files with 1174 additions and 1108 deletions

View File

@ -71,6 +71,9 @@ ldap:
# values must be one of the two possible methods.
#
# Note: 'per_subdomain_methods' is optional.
#
# Note: authentication_methods is optional. If it is not set all sub-domains
# are protected by two factors.
authentication_methods:
default_method: two_factor
per_subdomain_methods:

View File

@ -1,18 +0,0 @@
import { AuthenticationMethod, AuthenticationMethodsConfiguration } from "./configuration/Configuration";
export class AuthenticationMethodCalculator {
private configuration: AuthenticationMethodsConfiguration;
constructor(config: AuthenticationMethodsConfiguration) {
this.configuration = config;
}
compute(subDomain: string): AuthenticationMethod {
if (this.configuration
&& this.configuration.per_subdomain_methods
&& subDomain in this.configuration.per_subdomain_methods) {
return this.configuration.per_subdomain_methods[subDomain];
}
return this.configuration.default_method;
}
}

View File

@ -1,42 +0,0 @@
import express = require("express");
import U2f = require("u2f");
import BluebirdPromise = require("bluebird");
import { AuthenticationSession } from "../../types/AuthenticationSession";
import { IRequestLogger } from "./logging/IRequestLogger";
const INITIAL_AUTHENTICATION_SESSION: AuthenticationSession = {
first_factor: false,
second_factor: false,
last_activity_datetime: undefined,
userid: undefined,
email: undefined,
groups: [],
register_request: undefined,
sign_request: undefined,
identity_check: undefined,
redirect: undefined
};
export function reset(req: express.Request): void {
req.session.auth = Object.assign({}, INITIAL_AUTHENTICATION_SESSION, {});
// Initialize last activity with current time
req.session.auth.last_activity_datetime = new Date().getTime();
}
export function get(req: express.Request, logger: IRequestLogger): BluebirdPromise<AuthenticationSession> {
if (!req.session) {
const errorMsg = "Something is wrong with session cookies. Please check Redis is running and Authelia can contact it.";
logger.error(req, errorMsg);
return BluebirdPromise.reject(new Error(errorMsg));
}
if (!req.session.auth) {
logger.debug(req, "Authentication session %s was undefined. Resetting.", req.sessionID);
reset(req);
}
return BluebirdPromise.resolve(req.session.auth);
}

View File

@ -0,0 +1,44 @@
import express = require("express");
import U2f = require("u2f");
import BluebirdPromise = require("bluebird");
import { AuthenticationSession } from "../../types/AuthenticationSession";
import { IRequestLogger } from "./logging/IRequestLogger";
const INITIAL_AUTHENTICATION_SESSION: AuthenticationSession = {
first_factor: false,
second_factor: false,
last_activity_datetime: undefined,
userid: undefined,
email: undefined,
groups: [],
register_request: undefined,
sign_request: undefined,
identity_check: undefined,
redirect: undefined
};
export class AuthenticationSessionHandler {
static reset(req: express.Request): void {
req.session.auth = Object.assign({}, INITIAL_AUTHENTICATION_SESSION, {});
// Initialize last activity with current time
req.session.auth.last_activity_datetime = new Date().getTime();
}
static get(req: express.Request, logger: IRequestLogger): AuthenticationSession {
if (!req.session) {
const errorMsg = "Something is wrong with session cookies. Please check Redis is running and Authelia can contact it.";
logger.error(req, errorMsg);
throw new Error(errorMsg);
}
if (!req.session.auth) {
logger.debug(req, "Authentication session %s was undefined. Resetting.", req.sessionID);
AuthenticationSessionHandler.reset(req);
}
return req.session.auth;
}
}

View File

@ -3,15 +3,13 @@ import BluebirdPromise = require("bluebird");
import express = require("express");
import objectPath = require("object-path");
import FirstFactorValidator = require("./FirstFactorValidator");
import AuthenticationSessionHandler = require("./AuthenticationSession");
import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
import { IRequestLogger } from "./logging/IRequestLogger";
export function validate(req: express.Request, logger: IRequestLogger): BluebirdPromise<void> {
return FirstFactorValidator.validate(req, logger)
.then(function () {
return AuthenticationSessionHandler.get(req, logger);
})
.then(function (authSession) {
const authSession = AuthenticationSessionHandler.get(req, logger);
if (!authSession.second_factor)
return BluebirdPromise.reject("No second factor variable.");
return BluebirdPromise.resolve();

View File

@ -3,17 +3,17 @@ import BluebirdPromise = require("bluebird");
import express = require("express");
import objectPath = require("object-path");
import Exceptions = require("./Exceptions");
import AuthenticationSessionHandler = require("./AuthenticationSession");
import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
import { IRequestLogger } from "./logging/IRequestLogger";
export function validate(req: express.Request, logger: IRequestLogger): BluebirdPromise<void> {
return AuthenticationSessionHandler.get(req, logger)
.then(function (authSession) {
return new BluebirdPromise(function (resolve, reject) {
const authSession = AuthenticationSessionHandler.get(req, logger);
if (!authSession.userid || !authSession.first_factor)
return BluebirdPromise.reject(
new Exceptions.FirstFactorValidationError(
return reject(new Exceptions.FirstFactorValidationError(
"First factor has not been validated yet."));
return BluebirdPromise.resolve();
resolve();
});
}

View File

@ -9,7 +9,7 @@ import ejs = require("ejs");
import { IUserDataStore } from "./storage/IUserDataStore";
import express = require("express");
import ErrorReplies = require("./ErrorReplies");
import AuthenticationSessionHandler = require("./AuthenticationSession");
import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
import { AuthenticationSession } from "../../types/AuthenticationSession";
import { ServerVariables } from "./ServerVariables";
@ -74,14 +74,9 @@ export function get_finish_validation(handler: IdentityValidable,
return checkIdentityToken(req, identityToken)
.then(function () {
authSession = AuthenticationSessionHandler.get(req, vars.logger);
return handler.postValidationInit(req);
})
.then(function () {
return AuthenticationSessionHandler.get(req, vars.logger);
})
.then(function (_authSession) {
authSession = _authSession;
})
.then(function () {
return consumeToken(identityToken, handler.challenge(), vars.userDataStore);
})
@ -97,7 +92,6 @@ export function get_finish_validation(handler: IdentityValidable,
};
}
export function get_start_validation(handler: IdentityValidable,
postValidationEndpoint: string,
vars: ServerVariables)

View File

@ -1,84 +0,0 @@
import Express = require("express");
import { UserDataStore } from "./storage/UserDataStore";
import { Winston } from "../../types/Dependencies";
import FirstFactorGet = require("./routes/firstfactor/get");
import SecondFactorGet = require("./routes/secondfactor/get");
import FirstFactorPost = require("./routes/firstfactor/post");
import LogoutGet = require("./routes/logout/get");
import VerifyGet = require("./routes/verify/get");
import TOTPSignGet = require("./routes/secondfactor/totp/sign/post");
import IdentityCheckMiddleware = require("./IdentityCheckMiddleware");
import TOTPRegistrationIdentityHandler from "./routes/secondfactor/totp/identity/RegistrationHandler";
import U2FRegistrationIdentityHandler from "./routes/secondfactor/u2f/identity/RegistrationHandler";
import ResetPasswordIdentityHandler from "./routes/password-reset/identity/PasswordResetHandler";
import U2FSignPost = require("./routes/secondfactor/u2f/sign/post");
import U2FSignRequestGet = require("./routes/secondfactor/u2f/sign_request/get");
import U2FRegisterPost = require("./routes/secondfactor/u2f/register/post");
import U2FRegisterRequestGet = require("./routes/secondfactor/u2f/register_request/get");
import ResetPasswordFormPost = require("./routes/password-reset/form/post");
import ResetPasswordRequestPost = require("./routes/password-reset/request/get");
import Error401Get = require("./routes/error/401/get");
import Error403Get = require("./routes/error/403/get");
import Error404Get = require("./routes/error/404/get");
import LoggedIn = require("./routes/loggedin/get");
import { ServerVariables } from "./ServerVariables";
import { IRequestLogger } from "./logging/IRequestLogger";
import Endpoints = require("../../../shared/api");
function withHeadersLogged(fn: (req: Express.Request, res: Express.Response) => void,
logger: IRequestLogger) {
return function (req: Express.Request, res: Express.Response) {
logger.debug(req, "Headers = %s", JSON.stringify(req.headers));
fn(req, res);
};
}
export class RestApi {
static setup(app: Express.Application, vars: ServerVariables): void {
app.get(Endpoints.FIRST_FACTOR_GET, withHeadersLogged(FirstFactorGet.default(vars), vars.logger));
app.get(Endpoints.SECOND_FACTOR_GET, withHeadersLogged(SecondFactorGet.default(vars), vars.logger));
app.get(Endpoints.LOGOUT_GET, withHeadersLogged(LogoutGet.default, vars.logger));
IdentityCheckMiddleware.register(app, Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET,
new TOTPRegistrationIdentityHandler(vars.logger, vars.userDataStore, vars.totpHandler), vars);
IdentityCheckMiddleware.register(app, Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET,
Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET,
new U2FRegistrationIdentityHandler(vars.logger), vars);
IdentityCheckMiddleware.register(app, Endpoints.RESET_PASSWORD_IDENTITY_START_GET,
Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET,
new ResetPasswordIdentityHandler(vars.logger, vars.ldapEmailsRetriever), vars);
app.get(Endpoints.RESET_PASSWORD_REQUEST_GET, withHeadersLogged(ResetPasswordRequestPost.default, vars.logger));
app.post(Endpoints.RESET_PASSWORD_FORM_POST, withHeadersLogged(ResetPasswordFormPost.default(vars), vars.logger));
app.get(Endpoints.VERIFY_GET, withHeadersLogged(VerifyGet.default(vars), vars.logger));
app.post(Endpoints.FIRST_FACTOR_POST, withHeadersLogged(FirstFactorPost.default(vars), vars.logger));
app.post(Endpoints.SECOND_FACTOR_TOTP_POST, withHeadersLogged(TOTPSignGet.default(vars), vars.logger));
app.get(Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, withHeadersLogged(U2FSignRequestGet.default(vars), vars.logger));
app.post(Endpoints.SECOND_FACTOR_U2F_SIGN_POST, withHeadersLogged(U2FSignPost.default(vars), vars.logger));
app.get(Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, withHeadersLogged(U2FRegisterRequestGet.default(vars), vars.logger));
app.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, withHeadersLogged(U2FRegisterPost.default(vars), vars.logger));
app.get(Endpoints.ERROR_401_GET, withHeadersLogged(Error401Get.default, vars.logger));
app.get(Endpoints.ERROR_403_GET, withHeadersLogged(Error403Get.default, vars.logger));
app.get(Endpoints.ERROR_404_GET, withHeadersLogged(Error404Get.default, vars.logger));
app.get(Endpoints.LOGGED_IN, withHeadersLogged(LoggedIn.default(vars), vars.logger));
}
}

View File

@ -6,27 +6,17 @@ import { AppConfiguration, UserConfiguration } from "./configuration/Configurati
import { GlobalDependencies } from "../../types/Dependencies";
import { UserDataStore } from "./storage/UserDataStore";
import { ConfigurationParser } from "./configuration/ConfigurationParser";
import { RestApi } from "./RestApi";
import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder";
import { GlobalLogger } from "./logging/GlobalLogger";
import { RequestLogger } from "./logging/RequestLogger";
import { ServerVariables } from "./ServerVariables";
import { ServerVariablesInitializer } from "./ServerVariablesInitializer";
import { Configurator } from "./web_server/Configurator";
import * as Express from "express";
import * as BodyParser from "body-parser";
import * as Path from "path";
import * as http from "http";
const addRequestId = require("express-request-id")();
// Constants
const TRUST_PROXY = "trust proxy";
const X_POWERED_BY = "x-powered-by";
const VIEWS = "views";
const VIEW_ENGINE = "view engine";
const PUG = "pug";
function clone(obj: any) {
return JSON.parse(JSON.stringify(obj));
}
@ -35,35 +25,12 @@ export default class Server {
private httpServer: http.Server;
private globalLogger: GlobalLogger;
private requestLogger: RequestLogger;
private serverVariables: ServerVariables;
constructor(deps: GlobalDependencies) {
this.globalLogger = new GlobalLogger(deps.winston);
this.requestLogger = new RequestLogger(deps.winston);
}
private setupExpressApplication(config: AppConfiguration,
app: Express.Application,
deps: GlobalDependencies): void {
const viewsDirectory = Path.resolve(__dirname, "../views");
const publicHtmlDirectory = Path.resolve(__dirname, "../public_html");
const expressSessionOptions = SessionConfigurationBuilder.build(config, deps);
app.use(Express.static(publicHtmlDirectory));
app.use(BodyParser.urlencoded({ extended: false }));
app.use(BodyParser.json());
app.use(deps.session(expressSessionOptions));
app.use(addRequestId);
app.disable(X_POWERED_BY);
app.enable(TRUST_PROXY);
app.set(VIEWS, viewsDirectory);
app.set(VIEW_ENGINE, PUG);
RestApi.setup(app, this.serverVariables);
}
private displayConfigurations(userConfiguration: UserConfiguration,
appConfiguration: AppConfiguration) {
const displayableUserConfiguration = clone(userConfiguration);
@ -94,8 +61,7 @@ export default class Server {
const that = this;
return ServerVariablesInitializer.initialize(config, this.requestLogger, deps)
.then(function (vars: ServerVariables) {
that.serverVariables = vars;
that.setupExpressApplication(config, app, deps);
Configurator.configure(config, app, vars, deps);
return BluebirdPromise.resolve();
});
}

View File

@ -37,7 +37,7 @@ import { IMongoClient } from "./connectors/mongo/IMongoClient";
import { GlobalDependencies } from "../../types/Dependencies";
import { ServerVariables } from "./ServerVariables";
import { AuthenticationMethodCalculator } from "./AuthenticationMethodCalculator";
import { MethodCalculator } from "./authentication/MethodCalculator";
class UserDataStoreFactory {
static create(config: Configuration.AppConfiguration): BluebirdPromise<UserDataStore> {

View File

@ -0,0 +1,40 @@
import {
AuthenticationMethod,
AuthenticationMethodsConfiguration
} from "../configuration/Configuration";
function computeIsSingleFactorOnlyMode(
configuration: AuthenticationMethodsConfiguration): boolean {
if (!configuration)
return false;
const method: AuthenticationMethod = configuration.default_method;
if (configuration.default_method == "two_factor")
return false;
if (configuration.per_subdomain_methods) {
for (const key in configuration.per_subdomain_methods) {
const method = configuration.per_subdomain_methods[key];
if (method == "two_factor")
return false;
}
}
return true;
}
export class MethodCalculator {
static compute(config: AuthenticationMethodsConfiguration, subDomain: string)
: AuthenticationMethod {
if (config
&& config.per_subdomain_methods
&& subDomain in config.per_subdomain_methods) {
return config.per_subdomain_methods[subDomain];
}
return config.default_method;
}
static isSingleFactorOnlyMode(config: AuthenticationMethodsConfiguration)
: boolean {
return computeIsSingleFactorOnlyMode(config);
}
}

View File

@ -3,8 +3,9 @@ import Path = require("path");
import Util = require("util");
import {
UserConfiguration, StorageConfiguration,
NotifierConfiguration
NotifierConfiguration, AuthenticationMethodsConfiguration
} from "./Configuration";
import { MethodCalculator } from "../authentication/MethodCalculator";
function validateSchema(configuration: UserConfiguration): string[] {
const schema = require(Path.resolve(__dirname, "./Configuration.schema.json"));
@ -34,7 +35,7 @@ function validateUnknownKeys(path: string, obj: any, knownKeys: string[]) {
return [];
}
function validateStorage(storage: any) {
function validateStorage(storage: any): string[] {
const ERROR = "Storage must be either 'local' or 'mongo'";
if (!storage)
@ -53,21 +54,27 @@ function validateStorage(storage: any) {
return [];
}
function validateNotifier(notifier: NotifierConfiguration) {
function validateNotifier(notifier: NotifierConfiguration,
authenticationMethods: AuthenticationMethodsConfiguration): string[] {
const ERROR = "Notifier must be either 'filesystem', 'email' or 'smtp'";
if (!notifier)
return [];
const errors = validateUnknownKeys("notifier", notifier, ["filesystem", "email", "smtp"]);
if (errors.length > 0)
return errors;
if (!MethodCalculator.isSingleFactorOnlyMode(authenticationMethods)) {
if (Object.keys(notifier).length != 1)
return ["A notifier needs to be declared when server is used with two-factor"];
if (notifier && notifier.filesystem && notifier.email && notifier.smtp)
return [ERROR];
if (notifier && !notifier.filesystem && !notifier.email && !notifier.smtp)
return [ERROR];
}
const errors = validateUnknownKeys("notifier", notifier, ["filesystem", "email", "smtp"]);
if (errors.length > 0)
return errors;
return [];
}
@ -76,7 +83,8 @@ export class Validator {
static isValid(configuration: any): string[] {
const schemaErrors = validateSchema(configuration);
const storageErrors = validateStorage(configuration.storage);
const notifierErrors = validateNotifier(configuration.notifier);
const notifierErrors = validateNotifier(configuration.notifier,
configuration.authentication_methods);
return schemaErrors
.concat(storageErrors)

View File

@ -32,7 +32,7 @@ export class LdapClient implements ILdapClient {
clientLogger.level("trace");
}*/
this.client = BluebirdPromise.promisifyAll(ldapClient) as LdapJs.ClientAsync;
this.client = BluebirdPromise.promisifyAll(ldapClient) as any;
}
bindAsync(username: string, password: string): BluebirdPromise<void> {

View File

@ -1,25 +0,0 @@
import express = require("express");
import BluebirdPromise = require("bluebird");
import FirstFactorValidator = require("../FirstFactorValidator");
import Exceptions = require("../Exceptions");
import ErrorReplies = require("../ErrorReplies");
import objectPath = require("object-path");
import AuthenticationSession = require("../AuthenticationSession");
import UserMessages = require("../../../../shared/UserMessages");
import { IRequestLogger } from "../logging/IRequestLogger";
type Handler = (req: express.Request, res: express.Response) => BluebirdPromise<void>;
export default function (callback: Handler, logger: IRequestLogger): Handler {
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
return AuthenticationSession.get(req, logger)
.then(function (authSession) {
return FirstFactorValidator.validate(req, logger);
})
.then(function () {
return callback(req, res);
})
.catch(ErrorReplies.replyWithError401(req, res, logger));
};
}

View File

@ -5,7 +5,7 @@ import winston = require("winston");
import Endpoints = require("../../../../../shared/api");
import AuthenticationValidator = require("../../AuthenticationValidator");
import BluebirdPromise = require("bluebird");
import AuthenticationSession = require("../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import Constants = require("../../../../../shared/constants");
import Util = require("util");
import { ServerVariables } from "../../ServerVariables";
@ -42,18 +42,18 @@ function renderFirstFactor(res: express.Response) {
export default function (vars: ServerVariables) {
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
return AuthenticationSession.get(req, vars.logger)
.then(function (authSession) {
return new BluebirdPromise(function (resolve, reject) {
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
if (authSession.first_factor) {
if (authSession.second_factor)
redirectToService(req, res);
else
redirectToSecondFactorPage(req, res);
return BluebirdPromise.resolve();
resolve();
return;
}
renderFirstFactor(res);
return BluebirdPromise.resolve();
resolve();
});
};
}

View File

@ -8,11 +8,11 @@ import { Regulator } from "../../regulation/Regulator";
import { GroupsAndEmails } from "../../ldap/IClient";
import Endpoint = require("../../../../../shared/api");
import ErrorReplies = require("../../ErrorReplies");
import AuthenticationSessionHandler = require("../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import Constants = require("../../../../../shared/constants");
import { DomainExtractor } from "../../utils/DomainExtractor";
import UserMessages = require("../../../../../shared/UserMessages");
import { AuthenticationMethodCalculator } from "../../AuthenticationMethodCalculator";
import { MethodCalculator } from "../../authentication/MethodCalculator";
import { ServerVariables } from "../../ServerVariables";
import { AuthenticationSession } from "../../../../types/AuthenticationSession";
@ -29,10 +29,7 @@ export default function (vars: ServerVariables) {
return BluebirdPromise.reject(new Error("No username or password."));
}
vars.logger.info(req, "Starting authentication of user \"%s\"", username);
return AuthenticationSessionHandler.get(req, vars.logger);
})
.then(function (_authSession) {
authSession = _authSession;
authSession = AuthenticationSessionHandler.get(req, vars.logger);
return vars.regulator.regulate(username);
})
.then(function () {
@ -40,7 +37,8 @@ export default function (vars: ServerVariables) {
return vars.ldapAuthenticator.authenticate(username, password);
})
.then(function (groupsAndEmails: GroupsAndEmails) {
vars.logger.info(req, "LDAP binding successful. Retrieved information about user are %s",
vars.logger.info(req,
"LDAP binding successful. Retrieved information about user are %s",
JSON.stringify(groupsAndEmails));
authSession.userid = username;
authSession.first_factor = true;
@ -52,18 +50,12 @@ export default function (vars: ServerVariables) {
const emails: string[] = groupsAndEmails.emails;
const groups: string[] = groupsAndEmails.groups;
const redirectHost: string = DomainExtractor.fromUrl(redirectUrl);
const authMethod =
new AuthenticationMethodCalculator(vars.config.authentication_methods)
.compute(redirectHost);
vars.logger.debug(req, "Authentication method for \"%s\" is \"%s\"", redirectHost, authMethod);
if (!emails || emails.length <= 0) {
const errMessage =
"No emails found. The user should have at least one email address to reset password.";
vars.logger.error(req, "%s", errMessage);
return BluebirdPromise.reject(new Error(errMessage));
}
const authMethod = MethodCalculator.compute(
vars.config.authentication_methods, redirectHost);
vars.logger.debug(req, "Authentication method for \"%s\" is \"%s\"",
redirectHost, authMethod);
if (emails.length > 0)
authSession.email = emails[0];
authSession.groups = groups;
@ -71,8 +63,11 @@ export default function (vars: ServerVariables) {
vars.regulator.mark(username, true);
if (authMethod == "single_factor") {
let newRedirectionUrl: string = redirectUrl;
if (!newRedirectionUrl)
newRedirectionUrl = Endpoint.LOGGED_IN;
res.send({
redirect: redirectUrl
redirect: newRedirectionUrl
});
vars.logger.debug(req, "Redirect to '%s'", redirectUrl);
}
@ -82,7 +77,7 @@ export default function (vars: ServerVariables) {
newRedirectUrl += "?" + Constants.REDIRECT_QUERY_PARAM + "="
+ encodeURIComponent(redirectUrl);
}
vars.logger.debug(req, "Redirect to '%s'", newRedirectUrl, typeof redirectUrl);
vars.logger.debug(req, "Redirect to '%s'", newRedirectUrl);
res.send({
redirect: newRedirectUrl
});

View File

@ -1,21 +1,23 @@
import Express = require("express");
import Endpoints = require("../../../../../shared/api");
import FirstFactorBlocker from "../FirstFactorBlocker";
import BluebirdPromise = require("bluebird");
import AuthenticationSessionHandler = require("../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import { ServerVariables } from "../../ServerVariables";
import ErrorReplies = require("../../ErrorReplies");
export default function (vars: ServerVariables) {
function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
return AuthenticationSessionHandler.get(req, vars.logger)
.then(function (authSession) {
return new BluebirdPromise<void>(function (resolve, reject) {
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
res.render("already-logged-in", {
logout_endpoint: Endpoints.LOGOUT_GET,
username: authSession.userid,
redirection_url: vars.config.default_redirection_url
});
});
resolve();
})
.catch(ErrorReplies.replyWithError401(req, res, vars.logger));
}
return FirstFactorBlocker(handler, vars.logger);
return handler;
}

View File

@ -1,10 +1,10 @@
import express = require("express");
import AuthenticationSession = require("../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
export default function(req: express.Request, res: express.Response) {
const redirect_param = req.query.redirect;
const redirect_url = redirect_param || "/";
AuthenticationSession.reset(req);
AuthenticationSessionHandler.reset(req);
res.redirect(redirect_url);
}

View File

@ -3,7 +3,7 @@ import express = require("express");
import BluebirdPromise = require("bluebird");
import objectPath = require("object-path");
import exceptions = require("../../../Exceptions");
import AuthenticationSessionHandler = require("../../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
import ErrorReplies = require("../../../ErrorReplies");
import UserMessages = require("../../../../../../shared/UserMessages");
@ -16,16 +16,24 @@ export default function (vars: ServerVariables) {
let authSession: AuthenticationSession;
const newPassword = objectPath.get<express.Request, string>(req, "body.password");
return AuthenticationSessionHandler.get(req, vars.logger)
.then(function (_authSession) {
authSession = _authSession;
return new BluebirdPromise(function (resolve, reject) {
authSession = AuthenticationSessionHandler.get(req, vars.logger);
if (!authSession.identity_check) {
reject(new Error("No identity check initiated"));
return;
}
vars.logger.info(req, "User %s wants to reset his/her password.",
authSession.identity_check.userid);
vars.logger.debug(req, "Challenge %s", authSession.identity_check.challenge);
if (authSession.identity_check.challenge != Constants.CHALLENGE) {
return BluebirdPromise.reject(new Error("Bad challenge."));
reject(new Error("Bad challenge."));
return;
}
resolve();
})
.then(function () {
return vars.ldapPasswordUpdater.updatePassword(authSession.identity_check.userid, newPassword);
})
.then(function () {

View File

@ -1,30 +1,37 @@
import Express = require("express");
import Endpoints = require("../../../../../shared/api");
import FirstFactorBlocker = require("../FirstFactorBlocker");
import BluebirdPromise = require("bluebird");
import AuthenticationSessionHandler = require("../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import { ServerVariables } from "../../ServerVariables";
import { MethodCalculator } from "../../authentication/MethodCalculator";
const TEMPLATE_NAME = "secondfactor";
export default function (vars: ServerVariables) {
function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
return AuthenticationSessionHandler.get(req, vars.logger)
.then(function (authSession) {
if (authSession.first_factor && authSession.second_factor) {
function handler(req: Express.Request, res: Express.Response)
: BluebirdPromise<void> {
return new BluebirdPromise(function (resolve, reject) {
const isSingleFactorMode: boolean = MethodCalculator.isSingleFactorOnlyMode(
vars.config.authentication_methods);
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
if (isSingleFactorMode
|| (authSession.first_factor && authSession.second_factor)) {
res.redirect(Endpoints.LOGGED_IN);
return BluebirdPromise.resolve();
resolve();
return;
}
res.render(TEMPLATE_NAME, {
username: authSession.userid,
totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
u2f_identity_start_endpoint: Endpoints.SECOND_FACTOR_U2F_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
});
return BluebirdPromise.resolve();
resolve();
});
}
return FirstFactorBlocker.default(handler, vars.logger);
return handler;
}

View File

@ -4,7 +4,7 @@ import objectPath = require("object-path");
import winston = require("winston");
import Endpoints = require("../../../../../shared/api");
import { ServerVariables } from "../../ServerVariables";
import AuthenticationSession = require("../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import BluebirdPromise = require("bluebird");
import ErrorReplies = require("../../ErrorReplies");
import UserMessages = require("../../../../../shared/UserMessages");
@ -13,8 +13,9 @@ import Constants = require("../../../../../shared/constants");
export default function (vars: ServerVariables) {
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
return AuthenticationSession.get(req, vars.logger)
.then(function (authSession) {
return new BluebirdPromise<void>(function (resolve, reject) {
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
let redirectUrl: string;
if (vars.config.default_redirection_url) {
redirectUrl = vars.config.default_redirection_url;
@ -23,7 +24,7 @@ export default function (vars: ServerVariables) {
res.json({
redirect: redirectUrl
} as RedirectionMessage);
return BluebirdPromise.resolve();
return resolve();
})
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
UserMessages.OPERATION_FAILED));

View File

@ -9,12 +9,13 @@ import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationT
import Constants = require("../constants");
import Endpoints = require("../../../../../../../shared/api");
import ErrorReplies = require("../../../../ErrorReplies");
import AuthenticationSession = require("../../../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import UserMessages = require("../../../../../../../shared/UserMessages");
import FirstFactorValidator = require("../../../../FirstFactorValidator");
import { IRequestLogger } from "../../../../logging/IRequestLogger";
import { IUserDataStore } from "../../../../storage/IUserDataStore";
import { ITotpHandler } from "../../../../authentication/totp/ITotpHandler";
import { TOTPSecret } from "../../../../../../types/TOTPSecret";
export default class RegistrationHandler implements IdentityValidable {
@ -35,20 +36,21 @@ export default class RegistrationHandler implements IdentityValidable {
}
private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
return AuthenticationSession.get(req, this.logger)
.then(function (authSession) {
const that = this;
return new BluebirdPromise(function (resolve, reject) {
const authSession = AuthenticationSessionHandler.get(req, that.logger);
const userid = authSession.userid;
const email = authSession.email;
if (!(userid && email)) {
return BluebirdPromise.reject(new Error("User ID or email is missing."));
return reject(new Error("User ID or email is missing"));
}
const identity = {
email: email,
userid: userid
};
return BluebirdPromise.resolve(identity);
return resolve(identity);
});
}
@ -70,26 +72,31 @@ export default class RegistrationHandler implements IdentityValidable {
postValidationResponse(req: express.Request, res: express.Response): BluebirdPromise<void> {
const that = this;
return AuthenticationSession.get(req, this.logger)
.then(function (authSession) {
const userid = authSession.identity_check.userid;
let secret: TOTPSecret;
let userId: string;
return new BluebirdPromise(function (resolve, reject) {
const authSession = AuthenticationSessionHandler.get(req, that.logger);
const challenge = authSession.identity_check.challenge;
userId = authSession.identity_check.userid;
if (challenge != Constants.CHALLENGE || !userid) {
return BluebirdPromise.reject(new Error("Bad challenge."));
if (challenge != Constants.CHALLENGE || !userId) {
return reject(new Error("Bad challenge."));
}
const secret = that.totp.generate();
that.logger.debug(req, "Save the TOTP secret in DB.");
return that.userDataStore.saveTOTPSecret(userid, secret)
resolve();
})
.then(function () {
AuthenticationSession.reset(req);
secret = that.totp.generate();
that.logger.debug(req, "Save the TOTP secret in DB");
return that.userDataStore.saveTOTPSecret(userId, secret);
})
.then(function () {
AuthenticationSessionHandler.reset(req);
res.render(Constants.TEMPLATE_NAME, {
base32_secret: secret.base32,
otpauth_url: secret.otpauth_url,
login_endpoint: Endpoints.FIRST_FACTOR_GET
});
});
})
.catch(ErrorReplies.replyWithError200(req, res, that.logger, UserMessages.OPERATION_FAILED));
}

View File

@ -4,11 +4,10 @@ import objectPath = require("object-path");
import express = require("express");
import { TOTPSecretDocument } from "../../../../storage/TOTPSecretDocument";
import BluebirdPromise = require("bluebird");
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import Endpoints = require("../../../../../../../shared/api");
import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies");
import AuthenticationSessionHandler = require("../../../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
import UserMessages = require("../../../../../../../shared/UserMessages");
import { ServerVariables } from "../../../../ServerVariables";
@ -20,10 +19,12 @@ export default function (vars: ServerVariables) {
let authSession: AuthenticationSession;
const token = req.body.token;
return AuthenticationSessionHandler.get(req, vars.logger)
.then(function (_authSession) {
authSession = _authSession;
return new BluebirdPromise(function (resolve, reject) {
authSession = AuthenticationSessionHandler.get(req, vars.logger);
vars.logger.info(req, "Initiate TOTP validation for user \"%s\".", authSession.userid);
resolve();
})
.then(function () {
return vars.userDataStore.retrieveTOTPSecret(authSession.userid);
})
.then(function (doc: TOTPSecretDocument) {
@ -38,5 +39,5 @@ export default function (vars: ServerVariables) {
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
UserMessages.OPERATION_FAILED));
}
return FirstFactorBlocker(handler, vars.logger);
return handler;
}

View File

@ -7,7 +7,7 @@ import { IdentityValidable } from "../../../../IdentityCheckMiddleware";
import { Identity } from "../../../../../../types/Identity";
import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationTemplate";
import FirstFactorValidator = require("../../../../FirstFactorValidator");
import AuthenticationSession = require("../../../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import { IRequestLogger } from "../../../../logging/IRequestLogger";
const CHALLENGE = "u2f-register";
@ -28,20 +28,21 @@ export default class RegistrationHandler implements IdentityValidable {
}
private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
return AuthenticationSession.get(req, this.logger)
.then(function (authSession) {
const that = this;
return new BluebirdPromise(function(resolve, reject) {
const authSession = AuthenticationSessionHandler.get(req, that.logger);
const userid = authSession.userid;
const email = authSession.email;
if (!(userid && email)) {
return BluebirdPromise.reject(new Error("User ID or email is missing"));
return reject(new Error("User ID or email is missing"));
}
const identity = {
email: email,
userid: userid
};
return BluebirdPromise.resolve(identity);
return resolve(identity);
});
}

View File

@ -1,17 +1,15 @@
import { UserDataStore } from "../../../../storage/UserDataStore";
import objectPath = require("object-path");
import u2f_common = require("../U2FCommon");
import BluebirdPromise = require("bluebird");
import express = require("express");
import U2f = require("u2f");
import { U2FRegistration } from "../../../../../../types/U2FRegistration";
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariables } from "../../../../ServerVariables";
import AuthenticationSessionHandler = require("../../../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import UserMessages = require("../../../../../../../shared/UserMessages");
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
@ -22,25 +20,24 @@ export default function (vars: ServerVariables) {
const appid = u2f_common.extract_app_id(req);
const registrationResponse: U2f.RegistrationData = req.body;
return AuthenticationSessionHandler.get(req, vars.logger)
.then(function (_authSession) {
authSession = _authSession;
return new BluebirdPromise(function (resolve, reject) {
authSession = AuthenticationSessionHandler.get(req, vars.logger);
const registrationRequest = authSession.register_request;
if (!registrationRequest) {
return BluebirdPromise.reject(new Error("No registration request"));
return reject(new Error("No registration request"));
}
if (!authSession.identity_check
|| authSession.identity_check.challenge != "u2f-register") {
return BluebirdPromise.reject(new Error("Bad challenge for registration request"));
return reject(new Error("Bad challenge for registration request"));
}
vars.logger.info(req, "Finishing registration");
vars.logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
vars.logger.debug(req, "RegistrationResponse = %s", JSON.stringify(registrationResponse));
return BluebirdPromise.resolve(vars.u2f.checkRegistration(registrationRequest, registrationResponse));
return resolve(vars.u2f.checkRegistration(registrationRequest, registrationResponse));
})
.then(function (u2fResult: U2f.RegistrationResult | U2f.Error): BluebirdPromise<void> {
if (objectPath.has(u2fResult, "errorCode"))
@ -63,6 +60,5 @@ export default function (vars: ServerVariables) {
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
UserMessages.OPERATION_FAILED));
}
return FirstFactorBlocker(handler, vars.logger);
return handler;
}

View File

@ -6,9 +6,8 @@ import u2f_common = require("../U2FCommon");
import BluebirdPromise = require("bluebird");
import express = require("express");
import U2f = require("u2f");
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import ErrorReplies = require("../../../../ErrorReplies");
import AuthenticationSessionHandler = require("../../../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
import UserMessages = require("../../../../../../../shared/UserMessages");
import { ServerVariables } from "../../../../ServerVariables";
@ -18,20 +17,17 @@ export default function (vars: ServerVariables) {
let authSession: AuthenticationSession;
const appid: string = u2f_common.extract_app_id(req);
return AuthenticationSessionHandler.get(req, vars.logger)
.then(function (_authSession) {
authSession = _authSession;
return new BluebirdPromise(function (resolve, reject) {
authSession = AuthenticationSessionHandler.get(req, vars.logger);
if (!authSession.identity_check
|| authSession.identity_check.challenge != "u2f-register") {
res.status(403);
res.send();
return BluebirdPromise.reject(new Error("Bad challenge."));
return reject(new Error("Bad challenge."));
}
vars.logger.info(req, "Starting registration for appId '%s'", appid);
return BluebirdPromise.resolve(vars.u2f.request(appid));
return resolve(vars.u2f.request(appid));
})
.then(function (registrationRequest: U2f.Request) {
vars.logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
@ -43,5 +39,5 @@ export default function (vars: ServerVariables) {
UserMessages.OPERATION_FAILED));
}
return FirstFactorBlocker(handler, vars.logger);
return handler;
}

View File

@ -8,11 +8,10 @@ import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocu
import { Winston } from "../../../../../../types/Dependencies";
import U2f = require("u2f");
import exceptions = require("../../../../Exceptions");
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariables } from "../../../../ServerVariables";
import AuthenticationSessionHandler = require("../../../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import UserMessages = require("../../../../../../../shared/UserMessages");
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
@ -21,14 +20,16 @@ export default function (vars: ServerVariables) {
let authSession: AuthenticationSession;
const appId = u2f_common.extract_app_id(req);
return AuthenticationSessionHandler.get(req, vars.logger)
.then(function (_authSession) {
authSession = _authSession;
return new BluebirdPromise(function (resolve, reject) {
authSession = AuthenticationSessionHandler.get(req, vars.logger);
if (!authSession.sign_request) {
const err = new Error("No sign request");
ErrorReplies.replyWithError401(req, res, vars.logger)(err);
return BluebirdPromise.reject(err);
return reject(err);
}
resolve();
})
.then(function () {
const userid = authSession.userid;
return vars.userDataStore.retrieveU2FRegistration(userid, appId);
})
@ -50,6 +51,6 @@ export default function (vars: ServerVariables) {
UserMessages.OPERATION_FAILED));
}
return FirstFactorBlocker(handler, vars.logger);
return handler;
}

View File

@ -9,9 +9,8 @@ import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocu
import { Winston } from "../../../../../../types/Dependencies";
import exceptions = require("../../../../Exceptions");
import { SignMessage } from "../../../../../../../shared/SignMessage";
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import ErrorReplies = require("../../../../ErrorReplies");
import AuthenticationSessionHandler = require("../../../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import UserMessages = require("../../../../../../../shared/UserMessages");
import { ServerVariables } from "../../../../ServerVariables";
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
@ -21,9 +20,11 @@ export default function (vars: ServerVariables) {
let authSession: AuthenticationSession;
const appId = u2f_common.extract_app_id(req);
return AuthenticationSessionHandler.get(req, vars.logger)
.then(function (_authSession) {
authSession = _authSession;
return new BluebirdPromise(function (resolve, reject) {
authSession = AuthenticationSessionHandler.get(req, vars.logger);
resolve();
})
.then(function () {
return vars.userDataStore.retrieveU2FRegistration(authSession.userid, appId);
})
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<SignMessage> {
@ -51,6 +52,5 @@ export default function (vars: ServerVariables) {
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
UserMessages.OPERATION_FAILED));
}
return FirstFactorBlocker(handler, vars.logger);
return handler;
}

View File

@ -7,13 +7,13 @@ import winston = require("winston");
import AuthenticationValidator = require("../../AuthenticationValidator");
import ErrorReplies = require("../../ErrorReplies");
import { AppConfiguration } from "../../configuration/Configuration";
import AuthenticationSessionHandler = require("../../AuthenticationSession");
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../types/AuthenticationSession";
import Constants = require("../../../../../shared/constants");
import Util = require("util");
import { DomainExtractor } from "../../utils/DomainExtractor";
import { ServerVariables } from "../../ServerVariables";
import { AuthenticationMethodCalculator } from "../../AuthenticationMethodCalculator";
import { MethodCalculator } from "../../authentication/MethodCalculator";
import { IRequestLogger } from "../../logging/IRequestLogger";
const FIRST_FACTOR_NOT_VALIDATED_MESSAGE = "First factor not yet validated";
@ -50,49 +50,50 @@ function verify_inactivity(req: express.Request,
function verify_filter(req: express.Request, res: express.Response,
vars: ServerVariables): BluebirdPromise<void> {
let _authSession: AuthenticationSession;
let authSession: AuthenticationSession;
let username: string;
let groups: string[];
return AuthenticationSessionHandler.get(req, vars.logger)
.then(function (authSession) {
_authSession = authSession;
username = _authSession.userid;
groups = _authSession.groups;
return new BluebirdPromise(function (resolve, reject) {
authSession = AuthenticationSessionHandler.get(req, vars.logger);
username = authSession.userid;
groups = authSession.groups;
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] +
req.headers["x-original-uri"]));
if (!_authSession.userid)
return BluebirdPromise.reject(
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE));
if (!authSession.userid) {
reject(new exceptions.AccessDeniedError(
Util.format("%s: %s.", FIRST_FACTOR_NOT_VALIDATED_MESSAGE, "userid is missing")));
return;
}
const host = objectPath.get<express.Request, string>(req, "headers.host");
const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri");
const domain = DomainExtractor.fromHostHeader(host);
const authenticationMethod =
new AuthenticationMethodCalculator(vars.config.authentication_methods)
.compute(domain);
MethodCalculator.compute(vars.config.authentication_methods, domain);
vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, path,
username, groups.join(","));
if (!_authSession.first_factor)
return BluebirdPromise.reject(
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE));
if (!authSession.first_factor)
return reject(new exceptions.AccessDeniedError(
Util.format("%s: %s.", FIRST_FACTOR_NOT_VALIDATED_MESSAGE, "first factor is false")));
if (authenticationMethod == "two_factor" && !_authSession.second_factor)
return BluebirdPromise.reject(
new exceptions.AccessDeniedError(SECOND_FACTOR_NOT_VALIDATED_MESSAGE));
if (authenticationMethod == "two_factor" && !authSession.second_factor)
return reject(new exceptions.AccessDeniedError(
Util.format("%s: %s.", SECOND_FACTOR_NOT_VALIDATED_MESSAGE, "second factor is false")));
const isAllowed = vars.accessController.isAccessAllowed(domain, path, username, groups);
if (!isAllowed) return BluebirdPromise.reject(
if (!isAllowed) return reject(
new exceptions.DomainAccessDenied(Util.format("User '%s' does not have access to '%s'",
username, domain)));
return BluebirdPromise.resolve();
resolve();
})
.then(function () {
return verify_inactivity(req, _authSession,
return verify_inactivity(req, authSession,
vars.config, vars.logger);
})
.then(function () {

View File

@ -0,0 +1,45 @@
import { AppConfiguration } from "../configuration/Configuration";
import { GlobalDependencies } from "../../../types/Dependencies";
import { SessionConfigurationBuilder } from
"../configuration/SessionConfigurationBuilder";
import Path = require("path");
import Express = require("express");
import * as BodyParser from "body-parser";
import { RestApi } from "./RestApi";
import { WithHeadersLogged } from "./middlewares/WithHeadersLogged";
import { ServerVariables } from "../ServerVariables";
const addRequestId = require("express-request-id")();
// Constants
const TRUST_PROXY = "trust proxy";
const X_POWERED_BY = "x-powered-by";
const VIEWS = "views";
const VIEW_ENGINE = "view engine";
const PUG = "pug";
export class Configurator {
static configure(config: AppConfiguration,
app: Express.Application,
vars: ServerVariables,
deps: GlobalDependencies): void {
const viewsDirectory = Path.resolve(__dirname, "../../views");
const publicHtmlDirectory = Path.resolve(__dirname, "../../public_html");
const expressSessionOptions = SessionConfigurationBuilder.build(config, deps);
app.use(Express.static(publicHtmlDirectory));
app.use(BodyParser.urlencoded({ extended: false }));
app.use(BodyParser.json());
app.use(deps.session(expressSessionOptions));
app.use(addRequestId);
app.use(WithHeadersLogged.middleware(vars.logger));
app.disable(X_POWERED_BY);
app.enable(TRUST_PROXY);
app.set(VIEWS, viewsDirectory);
app.set(VIEW_ENGINE, PUG);
RestApi.setup(app, vars);
}
}

View File

@ -0,0 +1,139 @@
import Express = require("express");
import FirstFactorGet = require("../routes/firstfactor/get");
import SecondFactorGet = require("../routes/secondfactor/get");
import FirstFactorPost = require("../routes/firstfactor/post");
import LogoutGet = require("../routes/logout/get");
import VerifyGet = require("../routes/verify/get");
import TOTPSignGet = require("../routes/secondfactor/totp/sign/post");
import IdentityCheckMiddleware = require("../IdentityCheckMiddleware");
import TOTPRegistrationIdentityHandler from "../routes/secondfactor/totp/identity/RegistrationHandler";
import U2FRegistrationIdentityHandler from "../routes/secondfactor/u2f/identity/RegistrationHandler";
import ResetPasswordIdentityHandler from "../routes/password-reset/identity/PasswordResetHandler";
import U2FSignPost = require("../routes/secondfactor/u2f/sign/post");
import U2FSignRequestGet = require("../routes/secondfactor/u2f/sign_request/get");
import U2FRegisterPost = require("../routes/secondfactor/u2f/register/post");
import U2FRegisterRequestGet = require("../routes/secondfactor/u2f/register_request/get");
import ResetPasswordFormPost = require("../routes/password-reset/form/post");
import ResetPasswordRequestPost = require("../routes/password-reset/request/get");
import Error401Get = require("../routes/error/401/get");
import Error403Get = require("../routes/error/403/get");
import Error404Get = require("../routes/error/404/get");
import LoggedIn = require("../routes/loggedin/get");
import { ServerVariables } from "../ServerVariables";
import Endpoints = require("../../../../shared/api");
import { RequireValidatedFirstFactor } from "./middlewares/RequireValidatedFirstFactor";
import { RequireTwoFactorEnabled } from "./middlewares/RequireTwoFactorEnabled";
function setupTotp(app: Express.Application, vars: ServerVariables) {
app.post(Endpoints.SECOND_FACTOR_TOTP_POST,
RequireTwoFactorEnabled.middleware(vars.logger,
vars.config.authentication_methods),
RequireValidatedFirstFactor.middleware(vars.logger),
TOTPSignGet.default(vars));
app.get(Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
RequireTwoFactorEnabled.middleware(vars.logger,
vars.config.authentication_methods),
RequireValidatedFirstFactor.middleware(vars.logger));
app.get(Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET,
RequireTwoFactorEnabled.middleware(vars.logger,
vars.config.authentication_methods),
RequireValidatedFirstFactor.middleware(vars.logger));
IdentityCheckMiddleware.register(app,
Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET,
new TOTPRegistrationIdentityHandler(vars.logger,
vars.userDataStore, vars.totpHandler),
vars);
}
function setupU2f(app: Express.Application, vars: ServerVariables) {
app.get(Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET,
RequireTwoFactorEnabled.middleware(vars.logger,
vars.config.authentication_methods),
RequireValidatedFirstFactor.middleware(vars.logger),
U2FSignRequestGet.default(vars));
app.post(Endpoints.SECOND_FACTOR_U2F_SIGN_POST,
RequireTwoFactorEnabled.middleware(vars.logger,
vars.config.authentication_methods),
RequireValidatedFirstFactor.middleware(vars.logger),
U2FSignPost.default(vars));
app.get(Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET,
RequireTwoFactorEnabled.middleware(vars.logger,
vars.config.authentication_methods),
RequireValidatedFirstFactor.middleware(vars.logger),
U2FRegisterRequestGet.default(vars));
app.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST,
RequireTwoFactorEnabled.middleware(vars.logger,
vars.config.authentication_methods),
RequireValidatedFirstFactor.middleware(vars.logger),
U2FRegisterPost.default(vars));
app.get(Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET,
RequireTwoFactorEnabled.middleware(vars.logger,
vars.config.authentication_methods),
RequireValidatedFirstFactor.middleware(vars.logger));
app.get(Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET,
RequireTwoFactorEnabled.middleware(vars.logger,
vars.config.authentication_methods),
RequireValidatedFirstFactor.middleware(vars.logger));
IdentityCheckMiddleware.register(app,
Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET,
Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET,
new U2FRegistrationIdentityHandler(vars.logger), vars);
}
function setupResetPassword(app: Express.Application, vars: ServerVariables) {
IdentityCheckMiddleware.register(app, Endpoints.RESET_PASSWORD_IDENTITY_START_GET,
Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET,
new ResetPasswordIdentityHandler(vars.logger, vars.ldapEmailsRetriever), vars);
app.get(Endpoints.RESET_PASSWORD_REQUEST_GET, ResetPasswordRequestPost.default);
app.post(Endpoints.RESET_PASSWORD_FORM_POST, ResetPasswordFormPost.default(vars));
}
function setupErrors(app: Express.Application) {
app.get(Endpoints.ERROR_401_GET, Error401Get.default);
app.get(Endpoints.ERROR_403_GET, Error403Get.default);
app.get(Endpoints.ERROR_404_GET, Error404Get.default);
}
export class RestApi {
static setup(app: Express.Application, vars: ServerVariables): void {
app.get(Endpoints.FIRST_FACTOR_GET, FirstFactorGet.default(vars));
app.get(Endpoints.SECOND_FACTOR_GET,
RequireTwoFactorEnabled.middleware(vars.logger,
vars.config.authentication_methods),
RequireValidatedFirstFactor.middleware(vars.logger),
SecondFactorGet.default(vars));
app.get(Endpoints.LOGOUT_GET, LogoutGet.default);
app.get(Endpoints.VERIFY_GET, VerifyGet.default(vars));
app.post(Endpoints.FIRST_FACTOR_POST, FirstFactorPost.default(vars));
setupTotp(app, vars);
setupU2f(app, vars);
setupResetPassword(app, vars);
setupErrors(app);
app.get(Endpoints.LOGGED_IN, LoggedIn.default(vars));
}
}

View File

@ -0,0 +1,27 @@
import Express = require("express");
import BluebirdPromise = require("bluebird");
import ErrorReplies = require("../../ErrorReplies");
import { IRequestLogger } from "../../logging/IRequestLogger";
import { MethodCalculator } from "../../authentication/MethodCalculator";
import { AuthenticationMethodsConfiguration } from
"../../configuration/Configuration";
export class RequireTwoFactorEnabled {
static middleware(logger: IRequestLogger,
configuration: AuthenticationMethodsConfiguration) {
return function (req: Express.Request, res: Express.Response,
next: Express.NextFunction): void {
const isSingleFactorMode = MethodCalculator.isSingleFactorOnlyMode(
configuration);
if (isSingleFactorMode) {
ErrorReplies.replyWithError401(req, res, logger)(new Error(
"Restricted access because server is in single factor mode."));
return;
}
next();
};
}
}

View File

@ -0,0 +1,26 @@
import Express = require("express");
import BluebirdPromise = require("bluebird");
import ErrorReplies = require("../../ErrorReplies");
import { IRequestLogger } from "../../logging/IRequestLogger";
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import Exceptions = require("../../Exceptions");
export class RequireValidatedFirstFactor {
static middleware(logger: IRequestLogger) {
return function (req: Express.Request, res: Express.Response,
next: Express.NextFunction): BluebirdPromise<void> {
return new BluebirdPromise<void>(function (resolve, reject) {
const authSession = AuthenticationSessionHandler.get(req, logger);
if (!authSession.userid || !authSession.first_factor)
return reject(
new Exceptions.FirstFactorValidationError(
"First factor has not been validated yet."));
next();
resolve();
})
.catch(ErrorReplies.replyWithError401(req, res, logger));
};
}
}

View File

@ -0,0 +1,12 @@
import Express = require("express");
import { IRequestLogger } from "../../logging/IRequestLogger";
export class WithHeadersLogged {
static middleware(logger: IRequestLogger) {
return function (req: Express.Request, res: Express.Response,
next: Express.NextFunction): void {
logger.debug(req, "Headers = %s", JSON.stringify(req.headers));
next();
};
}
}

View File

@ -1,31 +0,0 @@
import { AuthenticationMethodCalculator } from "../src/lib/AuthenticationMethodCalculator";
import { AuthenticationMethodsConfiguration } from "../src/lib/configuration/Configuration";
import Assert = require("assert");
describe("test authentication method calculator", function() {
it("should return default method when sub domain not overriden", function() {
const options1: AuthenticationMethodsConfiguration = {
default_method: "two_factor",
per_subdomain_methods: {}
};
const options2: AuthenticationMethodsConfiguration = {
default_method: "single_factor",
per_subdomain_methods: {}
};
const calculator1 = new AuthenticationMethodCalculator(options1);
const calculator2 = new AuthenticationMethodCalculator(options2);
Assert.equal(calculator1.compute("www.example.com"), "two_factor");
Assert.equal(calculator2.compute("www.example.com"), "single_factor");
});
it("should return overridden method when sub domain method is defined", function() {
const options1: AuthenticationMethodsConfiguration = {
default_method: "two_factor",
per_subdomain_methods: {
"www.example.com": "single_factor"
}
};
const calculator1 = new AuthenticationMethodCalculator(options1);
Assert.equal(calculator1.compute("www.example.com"), "single_factor");
});
});

View File

@ -1,7 +1,7 @@
import sinon = require("sinon");
import IdentityValidator = require("../src/lib/IdentityCheckMiddleware");
import AuthenticationSessionHandler = require("../src/lib/AuthenticationSession");
import { AuthenticationSessionHandler } from "../src/lib/AuthenticationSessionHandler";
import { AuthenticationSession } from "../types/AuthenticationSession";
import { UserDataStore } from "../src/lib/storage/UserDataStore";
import exceptions = require("../src/lib/Exceptions");
@ -155,14 +155,10 @@ describe("test identity check process", function () {
req.query.identity_token = "token";
req.session = {};
let authSession: AuthenticationSession;
const authSession: AuthenticationSession = AuthenticationSessionHandler.get(req as any, vars.logger);
const callback = IdentityValidator.get_finish_validation(identityValidable, vars);
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (_authSession) {
authSession = _authSession;
return callback(req as any, res as any, undefined);
})
return callback(req as any, res as any, undefined)
.then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () {
Assert.equal(authSession.identity_check.userid, "user");

View File

@ -0,0 +1,74 @@
import { MethodCalculator }
from "../../src/lib/authentication/MethodCalculator";
import { AuthenticationMethodsConfiguration }
from "../../src/lib/configuration/Configuration";
import Assert = require("assert");
describe("test MethodCalculator", function () {
describe("test compute method", function () {
it("should return default method when sub domain not overriden",
function () {
const options1: AuthenticationMethodsConfiguration = {
default_method: "two_factor",
per_subdomain_methods: {}
};
const options2: AuthenticationMethodsConfiguration = {
default_method: "single_factor",
per_subdomain_methods: {}
};
Assert.equal(MethodCalculator.compute(options1, "www.example.com"),
"two_factor");
Assert.equal(MethodCalculator.compute(options2, "www.example.com"),
"single_factor");
});
it("should return overridden method when sub domain method is defined",
function () {
const options1: AuthenticationMethodsConfiguration = {
default_method: "two_factor",
per_subdomain_methods: {
"www.example.com": "single_factor"
}
};
Assert.equal(MethodCalculator.compute(options1, "www.example.com"),
"single_factor");
Assert.equal(MethodCalculator.compute(options1, "home.example.com"),
"two_factor");
});
});
describe("test isSingleFactorOnlyMode method", function () {
it("should return true when default domains and all domains are single_factor",
function () {
const options: AuthenticationMethodsConfiguration = {
default_method: "single_factor",
per_subdomain_methods: {
"www.example.com": "single_factor"
}
};
Assert(MethodCalculator.isSingleFactorOnlyMode(options));
});
it("should return false when default domains is single_factor and at least one sub-domain is two_factor", function () {
const options: AuthenticationMethodsConfiguration = {
default_method: "single_factor",
per_subdomain_methods: {
"www.example.com": "two_factor",
"home.example.com": "single_factor"
}
};
Assert(!MethodCalculator.isSingleFactorOnlyMode(options));
});
it("should return false when default domains is two_factor", function () {
const options: AuthenticationMethodsConfiguration = {
default_method: "two_factor",
per_subdomain_methods: {
"www.example.com": "single_factor",
"home.example.com": "single_factor"
}
};
Assert(!MethodCalculator.isSingleFactorOnlyMode(options));
});
});
});

View File

@ -28,7 +28,7 @@ describe("test validator", function () {
"data.regulation should have required property 'max_retries'",
"data.session should have required property 'secret'",
"Storage must be either 'local' or 'mongo'",
"Notifier must be either 'filesystem', 'email' or 'smtp'"
"A notifier needs to be declared when server is used with two-factor"
]);
Assert.deepStrictEqual(Validator.isValid({
@ -54,7 +54,7 @@ describe("test validator", function () {
}
}), [
"data.storage has unknown key 'abc'",
"data.notifier has unknown key 'abcd'"
"Notifier must be either 'filesystem', 'email' or 'smtp'"
]);
});
@ -89,4 +89,93 @@ describe("test validator", function () {
}
}), []);
});
it("should return false when notifier is not defined while there is at least \
one second factor enabled sub-domain", function () {
const options1 = {
ldap: {
base_dn: "dc=example,dc=com",
password: "password",
url: "ldap://ldap",
user: "user"
},
authentication_methods: {
default_method: "two_factor"
},
notifier: {},
regulation: {
ban_time: 120,
find_time: 30,
max_retries: 3
},
session: {
secret: "unsecure_secret"
},
storage: {
local: {
path: "/var/lib/authelia"
}
}
};
const options2 = {
ldap: {
base_dn: "dc=example,dc=com",
password: "password",
url: "ldap://ldap",
user: "user"
},
authentication_methods: {
default_method: "two_factor"
},
notifier: {
email: {
username: "user@gmail.com",
password: "pass",
sender: "admin@example.com",
service: "gmail"
}
},
regulation: {
ban_time: 120,
find_time: 30,
max_retries: 3
},
session: {
secret: "unsecure_secret"
},
storage: {
local: {
path: "/var/lib/authelia"
}
}
};
const options3 = {
ldap: {
base_dn: "dc=example,dc=com",
password: "password",
url: "ldap://ldap",
user: "user"
},
authentication_methods: {
default_method: "single_factor"
},
notifier: {},
regulation: {
ban_time: 120,
find_time: 30,
max_retries: 3
},
session: {
secret: "unsecure_secret"
},
storage: {
local: {
path: "/var/lib/authelia"
}
}
};
Assert.deepStrictEqual(Validator.isValid(options1), ["A notifier needs to be declared when server is used with two-factor"]);
Assert.deepStrictEqual(Validator.isValid(options2), []);
Assert.deepStrictEqual(Validator.isValid(options3), []);
});
});

View File

@ -1,26 +1,38 @@
import { IRequestLogger } from "../../src/lib/logging/IRequestLogger";
import Sinon = require("sinon");
import { RequestLogger } from "../../src/lib/logging/RequestLogger";
import Winston = require("winston");
import Express = require("express");
export class RequestLoggerStub implements IRequestLogger {
infoStub: Sinon.SinonStub;
debugStub: Sinon.SinonStub;
errorStub: Sinon.SinonStub;
private requestLogger: RequestLogger;
constructor() {
constructor(enableLogging?: boolean) {
this.infoStub = Sinon.stub();
this.debugStub = Sinon.stub();
this.errorStub = Sinon.stub();
if (enableLogging)
this.requestLogger = new RequestLogger(Winston);
}
info(req: Express.Request, message: string, ...args: any[]): void {
return this.infoStub(req, message, ...args);
if (this.requestLogger)
this.requestLogger.info(req, message, ...args);
this.infoStub(req, message, ...args);
}
debug(req: Express.Request, message: string, ...args: any[]): void {
return this.debugStub(req, message, ...args);
if (this.requestLogger)
this.requestLogger.info(req, message, ...args);
this.debugStub(req, message, ...args);
}
error(req: Express.Request, message: string, ...args: any[]): void {
return this.errorStub(req, message, ...args);
if (this.requestLogger)
this.requestLogger.info(req, message, ...args);
this.errorStub(req, message, ...args);
}
}

View File

@ -27,7 +27,7 @@ export interface ServerVariablesMock {
}
export class ServerVariablesMockBuilder {
static build(): { variables: ServerVariables, mocks: ServerVariablesMock} {
static build(enableLogging?: boolean): { variables: ServerVariables, mocks: ServerVariablesMock} {
const mocks: ServerVariablesMock = {
accessController: new AccessControllerStub(),
config: {
@ -62,7 +62,7 @@ export class ServerVariablesMockBuilder {
ldapAuthenticator: new AuthenticatorStub(),
ldapEmailsRetriever: new EmailsRetrieverStub(),
ldapPasswordUpdater: new PasswordUpdaterStub(),
logger: new RequestLoggerStub(),
logger: new RequestLoggerStub(enableLogging),
notifier: new NotifierStub(),
regulator: new RegulatorStub(),
totpHandler: new TotpHandlerStub(),

View File

@ -53,7 +53,11 @@ export function RequestMock(): RequestMock {
return {
app: {
get: sinon.stub()
}
},
headers: {
"x-forwarded-for": "127.0.0.1"
},
session: {}
};
}
export function ResponseMock(): ResponseMock {

View File

@ -4,7 +4,7 @@ import BluebirdPromise = require("bluebird");
import Assert = require("assert");
import FirstFactorPost = require("../../../src/lib/routes/firstfactor/post");
import exceptions = require("../../../src/lib/Exceptions");
import AuthenticationSessionHandler = require("../../../src/lib/AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../src/lib/AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../types/AuthenticationSession";
import Endpoints = require("../../../../shared/api");
import AuthenticationRegulatorMock = require("../../mocks/AuthenticationRegulator");
@ -20,6 +20,7 @@ describe("test the first factor validation route", function () {
let groups: string[];
let vars: ServerVariables;
let mocks: ServerVariablesMock;
let authSession: AuthenticationSession;
beforeEach(function () {
emails = ["test_ok@example.com"];
@ -48,6 +49,7 @@ describe("test the first factor validation route", function () {
};
res = ExpressMock.ResponseMock();
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
});
it("should reply with 204 if success", function () {
@ -56,12 +58,7 @@ describe("test the first factor validation route", function () {
emails: emails,
groups: groups
}));
let authSession: AuthenticationSession;
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (_authSession) {
authSession = _authSession;
return FirstFactorPost.default(vars)(req as any, res as any);
})
return FirstFactorPost.default(vars)(req as any, res as any)
.then(function () {
Assert.equal("username", authSession.userid);
Assert(res.send.calledOnce);
@ -76,18 +73,13 @@ describe("test the first factor validation route", function () {
it("should set first email address as user session variable", function () {
const emails = ["test_ok@example.com"];
let authSession: AuthenticationSession;
mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password")
.returns(BluebirdPromise.resolve({
emails: emails,
groups: groups
}));
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (_authSession) {
authSession = _authSession;
return FirstFactorPost.default(vars)(req as any, res as any);
})
return FirstFactorPost.default(vars)(req as any, res as any)
.then(function () {
Assert.equal("test_ok@example.com", authSession.email);
});

View File

@ -1,7 +1,8 @@
import PasswordResetFormPost = require("../../../src/lib/routes/password-reset/form/post");
import { PasswordUpdater } from "../../../src/lib/ldap/PasswordUpdater";
import AuthenticationSessionHandler = require("../../../src/lib/AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../src/lib/AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../types/AuthenticationSession";
import { UserDataStore } from "../../../src/lib/storage/UserDataStore";
import Sinon = require("sinon");
import Assert = require("assert");
@ -15,6 +16,7 @@ describe("test reset password route", function () {
let res: ExpressMock.ResponseMock;
let vars: ServerVariables;
let mocks: ServerVariablesMock;
let authSession: AuthenticationSession;
beforeEach(function () {
req = {
@ -53,14 +55,12 @@ describe("test reset password route", function () {
};
res = ExpressMock.ResponseMock();
AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (authSession) {
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
authSession.userid = "user";
authSession.email = "user@example.com";
authSession.first_factor = true;
authSession.second_factor = false;
});
});
describe("test reset password post", () => {
it("should update the password and reset auth_session for reauthentication", function () {
@ -69,14 +69,11 @@ describe("test reset password route", function () {
mocks.ldapPasswordUpdater.updatePasswordStub.returns(BluebirdPromise.resolve());
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (authSession) {
authSession.identity_check = {
userid: "user",
challenge: "reset-password"
};
return PasswordResetFormPost.default(vars)(req as any, res as any);
})
return PasswordResetFormPost.default(vars)(req as any, res as any)
.then(function () {
return AuthenticationSessionHandler.get(req as any, vars.logger);
}).then(function (_authSession) {
@ -88,14 +85,11 @@ describe("test reset password route", function () {
});
it("should fail if identity_challenge does not exist", function () {
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (authSession) {
authSession.identity_check = {
userid: "user",
challenge: undefined
};
return PasswordResetFormPost.default(vars)(req as any, res as any);
})
return PasswordResetFormPost.default(vars)(req as any, res as any)
.then(function () {
Assert.equal(res.status.getCall(0).args[0], 200);
Assert.deepEqual(res.send.getCall(0).args[0], {
@ -111,14 +105,12 @@ describe("test reset password route", function () {
mocks.ldapPasswordUpdater.updatePasswordStub
.returns(BluebirdPromise.reject("Internal error with LDAP"));
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (authSession) {
authSession.identity_check = {
challenge: "reset-password",
userid: "user"
};
return PasswordResetFormPost.default(vars)(req as any, res as any);
}).then(function () {
return PasswordResetFormPost.default(vars)(req as any, res as any)
.then(function () {
Assert.equal(res.status.getCall(0).args[0], 200);
Assert.deepEqual(res.send.getCall(0).args[0], {
error: "An error occurred during password reset. Your password has not been changed."

View File

@ -0,0 +1,64 @@
import SecondFactorGet from "../../../src/lib/routes/secondfactor/get";
import { ServerVariablesMockBuilder, ServerVariablesMock }
from "../../mocks/ServerVariablesMockBuilder";
import { ServerVariables } from "../../../src/lib/ServerVariables";
import Sinon = require("sinon");
import ExpressMock = require("../../mocks/express");
import Assert = require("assert");
import Endpoints = require("../../../../shared/api");
import BluebirdPromise = require("bluebird");
describe("test second factor GET endpoint handler", function () {
let mocks: ServerVariablesMock;
let vars: ServerVariables;
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
beforeEach(function () {
const s = ServerVariablesMockBuilder.build(true);
mocks = s.mocks;
vars = s.variables;
req = ExpressMock.RequestMock();
res = ExpressMock.ResponseMock();
req.session = {
auth: {
userid: "user",
first_factor: true,
second_factor: false
}
};
});
describe("test redirection", function () {
it("should redirect to already logged in page if server is in single_factor mode", function () {
vars.config.authentication_methods.default_method = "single_factor";
return SecondFactorGet(vars)(req as any, res as any)
.then(function () {
Assert(res.redirect.calledWith(Endpoints.LOGGED_IN));
return BluebirdPromise.resolve();
});
});
it("should redirect to already logged in page if user already authenticated", function () {
vars.config.authentication_methods.default_method = "two_factor";
req.session.auth.second_factor = true;
return SecondFactorGet(vars)(req as any, res as any)
.then(function () {
Assert(res.redirect.calledWith(Endpoints.LOGGED_IN));
return BluebirdPromise.resolve();
});
});
it("should render second factor page", function () {
vars.config.authentication_methods.default_method = "two_factor";
req.session.auth.second_factor = false;
return SecondFactorGet(vars)(req as any, res as any)
.then(function () {
Assert(res.render.calledWith("secondfactor"));
return BluebirdPromise.resolve();
});
});
});
});

View File

@ -2,7 +2,6 @@ import Sinon = require("sinon");
import winston = require("winston");
import RegistrationHandler from "../../../../../src/lib/routes/secondfactor/totp/identity/RegistrationHandler";
import { Identity } from "../../../../../types/Identity";
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
import { UserDataStore } from "../../../../../src/lib/storage/UserDataStore";
import assert = require("assert");
import BluebirdPromise = require("bluebird");

View File

@ -1,11 +1,9 @@
import BluebirdPromise = require("bluebird");
import Sinon = require("sinon");
import assert = require("assert");
import winston = require("winston");
import exceptions = require("../../../../../src/lib/Exceptions");
import AuthenticationSessionHandler = require("../../../../../src/lib/AuthenticationSession");
import Assert = require("assert");
import Exceptions = require("../../../../../src/lib/Exceptions");
import { AuthenticationSessionHandler } from "../../../../../src/lib/AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
import SignPost = require("../../../../../src/lib/routes/secondfactor/totp/sign/post");
import { ServerVariables } from "../../../../../src/lib/ServerVariables";
@ -27,9 +25,7 @@ describe("test totp route", function () {
mocks = s.mocks;
const app_get = Sinon.stub();
req = {
app: {
get: Sinon.stub().returns({ logger: winston })
},
app: {},
body: {
token: "abc"
},
@ -47,21 +43,18 @@ describe("test totp route", function () {
}
};
mocks.userDataStore.retrieveTOTPSecretStub.returns(BluebirdPromise.resolve(doc));
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (_authSession) {
authSession = _authSession;
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
authSession.userid = "user";
authSession.first_factor = true;
authSession.second_factor = false;
});
});
it("should send status code 200 when totp is valid", function () {
mocks.totpHandler.validateStub.returns(true);
return SignPost.default(vars)(req as any, res as any)
.then(function () {
assert.equal(true, authSession.second_factor);
Assert.equal(true, authSession.second_factor);
return BluebirdPromise.resolve();
});
});
@ -70,24 +63,13 @@ describe("test totp route", function () {
mocks.totpHandler.validateStub.returns(false);
return SignPost.default(vars)(req as any, res as any)
.then(function () {
assert.equal(false, authSession.second_factor);
assert.equal(res.status.getCall(0).args[0], 200);
assert.deepEqual(res.send.getCall(0).args[0], {
Assert.equal(false, authSession.second_factor);
Assert.equal(res.status.getCall(0).args[0], 200);
Assert.deepEqual(res.send.getCall(0).args[0], {
error: "Operation failed."
});
return BluebirdPromise.resolve();
});
});
it("should send status code 401 when session has not been initiated", function () {
mocks.totpHandler.validateStub.returns(true);
req.session = {};
return SignPost.default(vars)(req as any, res as any)
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () {
assert.equal(401, res.status.getCall(0).args[0]);
return BluebirdPromise.resolve();
});
});
});

View File

@ -1,18 +1,15 @@
import sinon = require("sinon");
import winston = require("winston");
import assert = require("assert");
import Sinon = require("sinon");
import Assert = require("assert");
import BluebirdPromise = require("bluebird");
import { Identity } from "../../../../../types/Identity";
import RegistrationHandler from "../../../../../src/lib/routes/secondfactor/u2f/identity/RegistrationHandler";
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
import ExpressMock = require("../../../../mocks/express");
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../mocks/ServerVariablesMockBuilder";
import { ServerVariables } from "../../../../../src/lib/ServerVariables";
describe("test register handler", function () {
describe("test U2F register handler", function () {
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock;
@ -46,9 +43,9 @@ describe("test register handler", function () {
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
res = ExpressMock.ResponseMock();
res.send = sinon.spy();
res.json = sinon.spy();
res.status = sinon.spy();
res.send = Sinon.spy();
res.json = Sinon.spy();
res.status = Sinon.spy();
});
describe("test u2f registration check", test_registration_check);
@ -63,31 +60,37 @@ describe("test register handler", function () {
});
});
it("should fail if userid is missing", function (done) {
it("should fail if userid is missing", function () {
req.session.auth.first_factor = false;
req.session.auth.userid = undefined;
new RegistrationHandler(vars.logger).preValidationInit(req as any)
.catch(function (err: Error) {
done();
return new RegistrationHandler(vars.logger).preValidationInit(req as any)
.then(function () {
return BluebirdPromise.reject(new Error("should not be here"));
},
function (err: Error) {
return BluebirdPromise.resolve();
});
});
it("should fail if email is missing", function (done) {
it("should fail if email is missing", function () {
req.session.auth.first_factor = false;
req.session.auth.email = undefined;
new RegistrationHandler(vars.logger).preValidationInit(req as any)
.catch(function (err: Error) {
done();
return new RegistrationHandler(vars.logger).preValidationInit(req as any)
.then(function () {
return BluebirdPromise.reject(new Error("should not be here"));
},
function (err: Error) {
return BluebirdPromise.resolve();
});
});
it("should succeed if first factor passed, userid and email are provided", function (done) {
new RegistrationHandler(vars.logger).preValidationInit(req as any)
.then(function (identity: Identity) {
done();
});
it("should succeed if first factor passed, userid and email are provided", function () {
req.session.auth.first_factor = true;
req.session.auth.email = "admin@example.com";
req.session.auth.userid = "user";
return new RegistrationHandler(vars.logger).preValidationInit(req as any);
});
}
});

View File

@ -3,7 +3,8 @@ import sinon = require("sinon");
import BluebirdPromise = require("bluebird");
import assert = require("assert");
import U2FRegisterPost = require("../../../../../src/lib/routes/secondfactor/u2f/register/post");
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../../../src/lib/AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
import ExpressMock = require("../../../../mocks/express");
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../mocks/ServerVariablesMockBuilder";
@ -15,6 +16,7 @@ describe("test u2f routes: register", function () {
let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock;
let vars: ServerVariables;
let authSession: AuthenticationSession;
beforeEach(function () {
req = ExpressMock.RequestMock();
@ -48,6 +50,8 @@ describe("test u2f routes: register", function () {
res.send = sinon.spy();
res.json = sinon.spy();
res.status = sinon.spy();
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
});
describe("test registration", test_registration);
@ -62,20 +66,14 @@ describe("test u2f routes: register", function () {
};
mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve(expectedStatus));
return AuthenticationSession.get(req as any, vars.logger)
.then(function (authSession) {
authSession.register_request = {
appId: "app",
challenge: "challenge",
keyHandle: "key",
version: "U2F_V2"
};
return U2FRegisterPost.default(vars)(req as any, res as any);
})
return U2FRegisterPost.default(vars)(req as any, res as any)
.then(function () {
return AuthenticationSession.get(req as any, vars.logger);
})
.then(function (authSession) {
assert.equal("user", mocks.userDataStore.saveU2FRegistrationStub.getCall(0).args[0]);
assert.equal(authSession.identity_check, undefined);
});
@ -84,8 +82,6 @@ describe("test u2f routes: register", function () {
it("should return error message on finishRegistration error", function () {
mocks.u2f.checkRegistrationStub.returns({ errorCode: 500 });
return AuthenticationSession.get(req as any, vars.logger)
.then(function (authSession) {
authSession.register_request = {
appId: "app",
challenge: "challenge",
@ -93,8 +89,7 @@ describe("test u2f routes: register", function () {
version: "U2F_V2"
};
return U2FRegisterPost.default(vars)(req as any, res as any);
})
return U2FRegisterPost.default(vars)(req as any, res as any)
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () {
assert.equal(200, res.status.getCall(0).args[0]);
@ -107,11 +102,8 @@ describe("test u2f routes: register", function () {
it("should return error message when register_request is not provided", function () {
mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve());
return AuthenticationSession.get(req as any, vars.logger)
.then(function (authSession) {
authSession.register_request = undefined;
return U2FRegisterPost.default(vars)(req as any, res as any);
})
return U2FRegisterPost.default(vars)(req as any, res as any)
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () {
assert.equal(200, res.status.getCall(0).args[0]);
@ -124,11 +116,8 @@ describe("test u2f routes: register", function () {
it("should return error message when no auth request has been initiated", function () {
mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve());
return AuthenticationSession.get(req as any, vars.logger)
.then(function (authSession) {
authSession.register_request = undefined;
return U2FRegisterPost.default(vars)(req as any, res as any);
})
return U2FRegisterPost.default(vars)(req as any, res as any)
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () {
assert.equal(200, res.status.getCall(0).args[0]);
@ -140,11 +129,8 @@ describe("test u2f routes: register", function () {
});
it("should return error message when identity has not been verified", function () {
return AuthenticationSession.get(req as any, vars.logger)
.then(function (authSession) {
authSession.identity_check = undefined;
return U2FRegisterPost.default(vars)(req as any, res as any);
})
return U2FRegisterPost.default(vars)(req as any, res as any)
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () {
assert.equal(200, res.status.getCall(0).args[0]);

View File

@ -3,7 +3,6 @@ import sinon = require("sinon");
import BluebirdPromise = require("bluebird");
import Assert = require("assert");
import U2FRegisterRequestGet = require("../../../../../src/lib/routes/secondfactor/u2f/register_request/get");
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
import ExpressMock = require("../../../../mocks/express");
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../mocks/ServerVariablesMockBuilder";

View File

@ -3,7 +3,6 @@ import sinon = require("sinon");
import BluebirdPromise = require("bluebird");
import Assert = require("assert");
import U2FSignPost = require("../../../../../src/lib/routes/secondfactor/u2f/sign/post");
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
import { ServerVariables } from "../../../../../src/lib/ServerVariables";
import winston = require("winston");

View File

@ -3,8 +3,6 @@ import sinon = require("sinon");
import BluebirdPromise = require("bluebird");
import assert = require("assert");
import U2FSignRequestGet = require("../../../../../src/lib/routes/secondfactor/u2f/sign_request/get");
import AuthenticationSessionHandler = require("../../../../../src/lib/AuthenticationSession");
import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
import ExpressMock = require("../../../../mocks/express");
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
import U2FMock = require("../../../../mocks/u2f");

View File

@ -1,9 +1,8 @@
import Assert = require("assert");
import VerifyGet = require("../../../src/lib/routes/verify/get");
import AuthenticationSessionHandler = require("../../../src/lib/AuthenticationSession");
import { AuthenticationSessionHandler } from "../../../src/lib/AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../types/AuthenticationSession";
import { AuthenticationMethodCalculator } from "../../../src/lib/AuthenticationMethodCalculator";
import { AuthenticationMethodsConfiguration } from "../../../src/lib/configuration/Configuration";
import Sinon = require("sinon");
import winston = require("winston");
@ -18,37 +17,31 @@ describe("test /verify endpoint", function () {
let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock;
let vars: ServerVariables;
let authSession: AuthenticationSession;
beforeEach(function () {
req = ExpressMock.RequestMock();
res = ExpressMock.ResponseMock();
req.session = {};
req.query = {
redirect: "http://redirect.url"
};
req.app = {
get: Sinon.stub().returns({ logger: winston })
};
AuthenticationSessionHandler.reset(req as any);
req.headers = {};
req.headers.host = "secret.example.com";
const s = ServerVariablesMockBuilder.build();
mocks = s.mocks;
vars = s.variables;
vars.config.authentication_methods.default_method = "two_factor";
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
});
it("should be already authenticated", function () {
req.session = {};
mocks.accessController.isAccessAllowedMock.returns(true);
AuthenticationSessionHandler.reset(req as any);
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (authSession) {
authSession.first_factor = true;
authSession.second_factor = true;
authSession.userid = "myuser";
authSession.groups = ["mygroup", "othergroup"];
return VerifyGet.default(vars)(req as express.Request, res as any);
})
return VerifyGet.default(vars)(req as express.Request, res as any)
.then(function () {
Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "myuser");
Sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup");
@ -57,11 +50,7 @@ describe("test /verify endpoint", function () {
});
function test_session(_authSession: AuthenticationSession, status_code: number) {
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (authSession) {
authSession = _authSession;
return VerifyGet.default(vars)(req as express.Request, res as any);
})
return VerifyGet.default(vars)(req as express.Request, res as any)
.then(function () {
Assert.equal(status_code, res.status.getCall(0).args[0]);
});
@ -134,8 +123,6 @@ describe("test /verify endpoint", function () {
});
it("should not be authenticated when domain is not allowed for user", function () {
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (authSession) {
authSession.first_factor = true;
authSession.second_factor = true;
authSession.userid = "myuser";
@ -153,7 +140,6 @@ describe("test /verify endpoint", function () {
});
});
});
});
describe("given user tries to access a basic auth endpoint", function () {
beforeEach(function () {
@ -168,12 +154,9 @@ describe("test /verify endpoint", function () {
it("should be authenticated when first factor is validated and second factor is not", function () {
mocks.accessController.isAccessAllowedMock.returns(true);
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (authSession) {
authSession.first_factor = true;
authSession.userid = "user1";
return VerifyGet.default(vars)(req as express.Request, res as any);
})
return VerifyGet.default(vars)(req as express.Request, res as any)
.then(function () {
Assert(res.status.calledWith(204));
Assert(res.send.calledOnce);
@ -182,11 +165,8 @@ describe("test /verify endpoint", function () {
it("should be rejected with 401 when first factor is not validated", function () {
mocks.accessController.isAccessAllowedMock.returns(true);
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (authSession) {
authSession.first_factor = false;
return VerifyGet.default(vars)(req as express.Request, res as any);
})
return VerifyGet.default(vars)(req as express.Request, res as any)
.then(function () {
Assert(res.status.calledWith(401));
});
@ -199,15 +179,12 @@ describe("test /verify endpoint", function () {
mocks.accessController.isAccessAllowedMock.returns(true);
const currentTime = new Date().getTime() - 1000;
AuthenticationSessionHandler.reset(req as any);
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (authSession) {
authSession.first_factor = true;
authSession.second_factor = true;
authSession.userid = "myuser";
authSession.groups = ["mygroup", "othergroup"];
authSession.last_activity_datetime = currentTime;
return VerifyGet.default(vars)(req as express.Request, res as any);
})
return VerifyGet.default(vars)(req as express.Request, res as any)
.then(function () {
return AuthenticationSessionHandler.get(req as any, vars.logger);
})
@ -221,15 +198,12 @@ describe("test /verify endpoint", function () {
mocks.accessController.isAccessAllowedMock.returns(true);
const currentTime = new Date().getTime() - 1000;
AuthenticationSessionHandler.reset(req as any);
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (authSession) {
authSession.first_factor = true;
authSession.second_factor = true;
authSession.userid = "myuser";
authSession.groups = ["mygroup", "othergroup"];
authSession.last_activity_datetime = currentTime;
return VerifyGet.default(vars)(req as express.Request, res as any);
})
return VerifyGet.default(vars)(req as express.Request, res as any)
.then(function () {
return AuthenticationSessionHandler.get(req as any, vars.logger);
})

View File

@ -1,182 +0,0 @@
import Server from "../../src/lib/Server";
import BluebirdPromise = require("bluebird");
import speakeasy = require("speakeasy");
import request = require("request");
import nedb = require("nedb");
import { GlobalDependencies } from "../../types/Dependencies";
import { UserConfiguration } from "../../src/lib/configuration/Configuration";
import { TOTPSecret } from "../../types/TOTPSecret";
import U2FMock = require("./../mocks/u2f");
import Endpoints = require("../../../shared/api");
import Requests = require("../requests");
import Assert = require("assert");
import Sinon = require("sinon");
import Winston = require("winston");
import MockDate = require("mockdate");
import ExpressSession = require("express-session");
import ldapjs = require("ldapjs");
const requestp = BluebirdPromise.promisifyAll(request) as typeof request;
const PORT = 8090;
const BASE_URL = "http://localhost:" + PORT;
const requests = Requests(PORT);
describe("Private pages of the server must not be accessible without session", function () {
let server: Server;
let transporter: any;
let u2f: U2FMock.U2FMock;
beforeEach(function () {
const config: UserConfiguration = {
port: PORT,
ldap: {
url: "ldap://127.0.0.1:389",
base_dn: "ou=users,dc=example,dc=com",
user: "cn=admin,dc=example,dc=com",
password: "password",
},
session: {
secret: "session_secret",
expiration: 50000,
},
regulation: {
max_retries: 3,
ban_time: 5 * 60,
find_time: 5 * 60
},
storage: {
local: {
in_memory: true
}
},
notifier: {
email: {
username: "user@example.com",
password: "password",
sender: "admin@example.com",
service: "gmail"
}
}
};
const ldap_client = {
bind: Sinon.stub(),
search: Sinon.stub(),
modify: Sinon.stub(),
on: Sinon.spy()
};
const ldap = {
Change: Sinon.spy(),
createClient: Sinon.spy(function () {
return ldap_client;
})
};
u2f = U2FMock.U2FMock();
transporter = {
sendMail: Sinon.stub().yields()
};
const nodemailer = {
createTransport: Sinon.spy(function () {
return transporter;
})
};
const ldap_document = {
object: {
mail: "test_ok@example.com",
}
};
const search_res = {
on: Sinon.spy(function (event: string, fn: (s: any) => void) {
if (event != "error") fn(ldap_document);
})
};
ldap_client.bind.withArgs("cn=test_ok,ou=users,dc=example,dc=com",
"password").yields(undefined);
ldap_client.bind.withArgs("cn=admin,dc=example,dc=com",
"password").yields(undefined);
ldap_client.bind.withArgs("cn=test_nok,ou=users,dc=example,dc=com",
"password").yields("error");
ldap_client.modify.yields(undefined);
ldap_client.search.yields(undefined, search_res);
const deps: GlobalDependencies = {
u2f: u2f as any,
nedb: nedb,
ldapjs: ldap,
session: ExpressSession,
winston: Winston,
speakeasy: speakeasy,
ConnectRedis: Sinon.spy()
};
server = new Server(deps);
return server.start(config, deps);
});
afterEach(function () {
server.stop();
});
describe("Second factor endpoints must be protected if first factor is not validated", function () {
function should_post_and_reply_with_401(url: string): BluebirdPromise<void> {
return requestp.postAsync(url).then(function (response: request.RequestResponse) {
Assert.equal(response.statusCode, 401);
return BluebirdPromise.resolve();
});
}
function should_get_and_reply_with_401(url: string): BluebirdPromise<void> {
return requestp.getAsync(url).then(function (response: request.RequestResponse) {
Assert.equal(response.statusCode, 401);
return BluebirdPromise.resolve();
});
}
it("should block " + Endpoints.SECOND_FACTOR_GET, function () {
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_GET);
});
it("should block " + Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET, function () {
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET);
});
it("should block " + Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET, function () {
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_IDENTITY_FINISH_GET + "?identity_token=dummy");
});
it("should block " + Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, function () {
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET);
});
it("should block " + Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, function () {
return should_post_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_REGISTER_POST);
});
it("should block " + Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, function () {
return should_get_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET);
});
it("should block " + Endpoints.SECOND_FACTOR_U2F_SIGN_POST, function () {
return should_post_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_U2F_SIGN_POST);
});
it("should block " + Endpoints.SECOND_FACTOR_TOTP_POST, function () {
return should_post_and_reply_with_401(BASE_URL + Endpoints.SECOND_FACTOR_TOTP_POST);
});
it("should block " + Endpoints.LOGGED_IN, function () {
return should_get_and_reply_with_401(BASE_URL + Endpoints.LOGGED_IN);
});
});
});

View File

@ -1,173 +0,0 @@
import Server from "../../src/lib/Server";
import BluebirdPromise = require("bluebird");
import speakeasy = require("speakeasy");
import Request = require("request");
import nedb = require("nedb");
import { GlobalDependencies } from "../../types/Dependencies";
import { UserConfiguration } from "../../src/lib/configuration/Configuration";
import { TOTPSecret } from "../../types/TOTPSecret";
import U2FMock = require("./../mocks/u2f");
import Endpoints = require("../../../shared/api");
import Requests = require("../requests");
import Assert = require("assert");
import Sinon = require("sinon");
import Winston = require("winston");
import MockDate = require("mockdate");
import ExpressSession = require("express-session");
import ldapjs = require("ldapjs");
const requestp = BluebirdPromise.promisifyAll(Request) as typeof Request;
const PORT = 8090;
const BASE_URL = "http://localhost:" + PORT;
const requests = Requests(PORT);
describe("Public pages of the server must be accessible without session", function () {
let server: Server;
let transporter: object;
let u2f: U2FMock.U2FMock;
beforeEach(function () {
const config: UserConfiguration = {
port: PORT,
ldap: {
url: "ldap://127.0.0.1:389",
base_dn: "ou=users,dc=example,dc=com",
user: "cn=admin,dc=example,dc=com",
password: "password",
},
session: {
secret: "session_secret",
expiration: 50000,
},
storage: {
local: {
in_memory: true
}
},
regulation: {
max_retries: 3,
ban_time: 5 * 60,
find_time: 5 * 60
},
notifier: {
email: {
username: "user@example.com",
password: "password",
sender: "admin@example.com",
service: "gmail"
}
}
};
const ldap_client = {
bind: Sinon.stub(),
search: Sinon.stub(),
modify: Sinon.stub(),
on: Sinon.spy()
};
const ldap = {
Change: Sinon.spy(),
createClient: Sinon.spy(function () {
return ldap_client;
})
};
u2f = U2FMock.U2FMock();
transporter = {
sendMail: Sinon.stub().yields()
};
const nodemailer = {
createTransport: Sinon.spy(function () {
return transporter;
})
};
const ldap_document = {
object: {
mail: "test_ok@example.com",
}
};
const search_res = {
on: Sinon.spy(function (event: string, fn: (s: any) => void) {
if (event != "error") fn(ldap_document);
})
};
ldap_client.bind.withArgs("cn=test_ok,ou=users,dc=example,dc=com",
"password").yields(undefined);
ldap_client.bind.withArgs("cn=admin,dc=example,dc=com",
"password").yields(undefined);
ldap_client.bind.withArgs("cn=test_nok,ou=users,dc=example,dc=com",
"password").yields("error");
ldap_client.modify.yields(undefined);
ldap_client.search.yields(undefined, search_res);
const deps: GlobalDependencies = {
u2f: u2f as any,
nedb: nedb,
ldapjs: ldap,
session: ExpressSession,
winston: Winston,
speakeasy: speakeasy,
ConnectRedis: Sinon.spy()
};
server = new Server(deps);
return server.start(config, deps);
});
afterEach(function () {
server.stop();
});
describe("test GET " + Endpoints.FIRST_FACTOR_GET, function () {
test_login();
});
describe("test GET " + Endpoints.LOGOUT_GET, function () {
test_logout();
});
describe("test GET" + Endpoints.RESET_PASSWORD_REQUEST_GET, function () {
test_reset_password_form();
});
function test_reset_password_form() {
it("should serve the reset password form page", function (done) {
requestp.getAsync(BASE_URL + Endpoints.RESET_PASSWORD_REQUEST_GET)
.then(function (response: Request.RequestResponse) {
Assert.equal(response.statusCode, 200);
done();
});
});
}
function test_login() {
it("should serve the login page", function (done) {
requestp.getAsync(BASE_URL + Endpoints.FIRST_FACTOR_GET)
.then(function (response: Request.RequestResponse) {
Assert.equal(response.statusCode, 200);
done();
});
});
}
function test_logout() {
it("should logout and redirect to /", function (done) {
requestp.getAsync(BASE_URL + Endpoints.LOGOUT_GET)
.then(function (response: any) {
Assert.equal(response.req.path, "/");
done();
});
});
}
});

View File

@ -1,16 +1,36 @@
Feature: Non authenticated users have no access to certain pages
Scenario Outline: Anonymous user has no access to protected pages
When I visit "<url>"
Then I get an error <error code>
Scenario: Anonymous user has no access to protected pages
Then I get the following status code when requesting:
| url | code | method |
| https://auth.test.local:8080/secondfactor | 401 | GET |
| https://auth.test.local:8080/secondfactor/u2f/identity/start | 401 | GET |
| https://auth.test.local:8080/secondfactor/u2f/identity/finish | 401 | GET |
| https://auth.test.local:8080/secondfactor/totp/identity/start | 401 | GET |
| https://auth.test.local:8080/secondfactor/totp/identity/finish | 401 | GET |
| https://auth.test.local:8080/api/totp | 401 | POST |
| https://auth.test.local:8080/api/u2f/sign_request | 401 | GET |
| https://auth.test.local:8080/api/u2f/sign | 401 | POST |
| https://auth.test.local:8080/api/u2f/register_request | 401 | GET |
| https://auth.test.local:8080/api/u2f/register | 401 | POST |
Examples:
| url | error code |
| https://auth.test.local:8080/secondfactor | 401 |
| https://auth.test.local:8080/verify | 401 |
| https://auth.test.local:8080/secondfactor/u2f/identity/start | 401 |
| https://auth.test.local:8080/secondfactor/u2f/identity/finish | 401 |
| https://auth.test.local:8080/secondfactor/totp/identity/start | 401 |
| https://auth.test.local:8080/secondfactor/totp/identity/finish | 401 |
| https://auth.test.local:8080/password-reset/identity/start | 401 |
| https://auth.test.local:8080/password-reset/identity/finish | 401 |
@needs-single_factor-config
@need-registered-user-john
Scenario: User does not have acces to second factor related endpoints when in single factor mode
Given I post "https://auth.test.local:8080/api/firstfactor" with body:
| key | value |
| username | john |
| password | password |
Then I get the following status code when requesting:
| url | code | method |
| https://auth.test.local:8080/secondfactor | 401 | GET |
| https://auth.test.local:8080/secondfactor/u2f/identity/start | 401 | GET |
| https://auth.test.local:8080/secondfactor/u2f/identity/finish | 401 | GET |
| https://auth.test.local:8080/secondfactor/totp/identity/start | 401 | GET |
| https://auth.test.local:8080/secondfactor/totp/identity/finish | 401 | GET |
| https://auth.test.local:8080/api/totp | 401 | POST |
| https://auth.test.local:8080/api/u2f/sign_request | 401 | GET |
| https://auth.test.local:8080/api/u2f/sign | 401 | POST |
| https://auth.test.local:8080/api/u2f/register_request | 401 | GET |
| https://auth.test.local:8080/api/u2f/register | 401 | POST |

View File

@ -0,0 +1,16 @@
@needs-single_factor-config
Feature: Server is configured as a single factor only server
@need-registered-user-john
Scenario: User is redirected to service after first factor if allowed
When I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password"
Then I'm redirected to "https://public.test.local:8080/secret.html"
@need-registered-user-john
Scenario: User is correctly redirected according to default redirection URL
When I visit "https://auth.test.local:8080"
And I login with user "john" and password "password"
Then I'm redirected to "https://auth.test.local:8080/loggedin"
And I sleep for 5 seconds
Then I'm redirected to "https://home.test.local:8080/"

View File

@ -5,6 +5,7 @@ import Fs = require("fs");
import Speakeasy = require("speakeasy");
import CustomWorld = require("../support/world");
import BluebirdPromise = require("bluebird");
import Request = require("request-promise");
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
When(/^I visit "(https:\/\/[a-zA-Z0-9:%&._\/=?-]+)"$/, function (link: string) {
@ -104,4 +105,29 @@ and I use TOTP token handle {stringInDoubleQuotes}",
}
return BluebirdPromise.all(promises);
});
function endpointReplyWith(context: any, link: string, method: string,
returnCode: number) {
return Request(link, {
method: method
})
.then(function (response: string) {
Assert(response.indexOf("Error " + returnCode) >= 0);
return BluebirdPromise.resolve();
}, function (response: any) {
Assert.equal(response.statusCode, returnCode);
return BluebirdPromise.resolve();
});
}
Then("the following endpoints reply with:", function (dataTable: Cucumber.TableDefinition) {
const promises = [];
for (let i = 0; i < dataTable.rows().length; i++) {
const url: string = (dataTable.hashes() as any)[i].url;
const method: string = (dataTable.hashes() as any)[i].method;
const code: number = (dataTable.hashes() as any)[i].code;
promises.push(endpointReplyWith(this, url, method, code));
}
return BluebirdPromise.all(promises);
});
});

View File

@ -36,6 +36,13 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
");
}
function createSingleFactorConfiguration(): BluebirdPromise<void> {
return exec("\
cat config.template.yml | \
sed 's/default_method: two_factor/default_method: single_factor/' > config.test.yml \
");
}
function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise<void>) {
Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
return cb()
@ -54,6 +61,7 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
declareNeedsConfiguration("regulation", createRegulationConfiguration);
declareNeedsConfiguration("inactivity", createInactivityConfiguration);
declareNeedsConfiguration("single_factor", createSingleFactorConfiguration);
function registerUser(context: any, username: string) {
let secret: Speakeasy.Key;

View File

@ -1,9 +1,72 @@
import Cucumber = require("cucumber");
import seleniumWebdriver = require("selenium-webdriver");
import Assert = require("assert");
import Request = require("request-promise");
import Bluebird = require("bluebird");
Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) {
Before(function () {
this.jar = Request.jar();
})
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
Then("I get an error {number}", function (code: number) {
return this.getErrorPage(code);
});
When("I request {stringInDoubleQuotes} with method {stringInDoubleQuotes}",
function (url: string, method: string) {
const that = this;
})
function requestAndExpectStatusCode(ctx: any, url: string, method: string,
expectedStatusCode: number) {
return Request(url, {
method: method,
jar: ctx.jar
})
.then(function (body: string) {
return Bluebird.resolve(parseInt(body.match(/Error ([0-9]{3})/)[1]));
}, function (response: any) {
return Bluebird.resolve(response.statusCode)
})
.then(function (statusCode: number) {
try {
Assert.equal(statusCode, expectedStatusCode);
}
catch (e) {
console.log(url);
console.log("%s (actual) != %s (expected)", statusCode,
expectedStatusCode);
throw e;
}
})
}
Then("I get the following status code when requesting:",
function (dataTable: Cucumber.TableDefinition) {
const promises: Bluebird<void>[] = [];
for (let i = 0; i < dataTable.rows().length; i++) {
const url: string = (dataTable.hashes() as any)[i].url;
const method: string = (dataTable.hashes() as any)[i].method;
const code: number = (dataTable.hashes() as any)[i].code;
promises.push(requestAndExpectStatusCode(this, url, method, code));
}
return Bluebird.all(promises);
})
When("I post {stringInDoubleQuotes} with body:", function (url: string,
dataTable: Cucumber.TableDefinition) {
const body = {};
for (let i = 0; i < dataTable.rows().length; i++) {
const key = (dataTable.hashes() as any)[i].key;
const value = (dataTable.hashes() as any)[i].value;
body[key] = value;
}
return Request.post(url, {
body: body,
jar: this.jar,
json: true
});
})
});

View File

@ -7,6 +7,8 @@ import Assert = require("assert");
import Request = require("request-promise");
import BluebirdPromise = require("bluebird");
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
function CustomWorld() {
const that = this;
this.driver = new seleniumWebdriver.Builder()
@ -38,8 +40,13 @@ function CustomWorld() {
.findElement(seleniumWebdriver.By.tagName("h1")).getText();
})
.then(function (txt: string) {
try {
Assert.equal(txt, "Error " + code);
});
} catch (e) {
console.log(txt);
throw e;
}
})
};
this.clickOnButton = function (buttonText: string) {