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. # values must be one of the two possible methods.
# #
# Note: 'per_subdomain_methods' is optional. # 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: authentication_methods:
default_method: two_factor default_method: two_factor
per_subdomain_methods: 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,17 +3,15 @@ import BluebirdPromise = require("bluebird");
import express = require("express"); import express = require("express");
import objectPath = require("object-path"); import objectPath = require("object-path");
import FirstFactorValidator = require("./FirstFactorValidator"); import FirstFactorValidator = require("./FirstFactorValidator");
import AuthenticationSessionHandler = require("./AuthenticationSession"); import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
import { IRequestLogger } from "./logging/IRequestLogger"; import { IRequestLogger } from "./logging/IRequestLogger";
export function validate(req: express.Request, logger: IRequestLogger): BluebirdPromise<void> { export function validate(req: express.Request, logger: IRequestLogger): BluebirdPromise<void> {
return FirstFactorValidator.validate(req, logger) return FirstFactorValidator.validate(req, logger)
.then(function () { .then(function () {
return AuthenticationSessionHandler.get(req, logger); const authSession = AuthenticationSessionHandler.get(req, logger);
}) if (!authSession.second_factor)
.then(function (authSession) { return BluebirdPromise.reject("No second factor variable.");
if (!authSession.second_factor) return BluebirdPromise.resolve();
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 express = require("express");
import objectPath = require("object-path"); import objectPath = require("object-path");
import Exceptions = require("./Exceptions"); import Exceptions = require("./Exceptions");
import AuthenticationSessionHandler = require("./AuthenticationSession"); import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
import { IRequestLogger } from "./logging/IRequestLogger"; import { IRequestLogger } from "./logging/IRequestLogger";
export function validate(req: express.Request, logger: IRequestLogger): BluebirdPromise<void> { export function validate(req: express.Request, logger: IRequestLogger): BluebirdPromise<void> {
return AuthenticationSessionHandler.get(req, logger) return new BluebirdPromise(function (resolve, reject) {
.then(function (authSession) { const authSession = AuthenticationSessionHandler.get(req, logger);
if (!authSession.userid || !authSession.first_factor)
return BluebirdPromise.reject(
new Exceptions.FirstFactorValidationError(
"First factor has not been validated yet."));
return BluebirdPromise.resolve(); if (!authSession.userid || !authSession.first_factor)
}); return reject(new Exceptions.FirstFactorValidationError(
"First factor has not been validated yet."));
resolve();
});
} }

View File

@ -9,7 +9,7 @@ import ejs = require("ejs");
import { IUserDataStore } from "./storage/IUserDataStore"; import { IUserDataStore } from "./storage/IUserDataStore";
import express = require("express"); import express = require("express");
import ErrorReplies = require("./ErrorReplies"); import ErrorReplies = require("./ErrorReplies");
import AuthenticationSessionHandler = require("./AuthenticationSession"); import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
import { AuthenticationSession } from "../../types/AuthenticationSession"; import { AuthenticationSession } from "../../types/AuthenticationSession";
import { ServerVariables } from "./ServerVariables"; import { ServerVariables } from "./ServerVariables";
@ -74,14 +74,9 @@ export function get_finish_validation(handler: IdentityValidable,
return checkIdentityToken(req, identityToken) return checkIdentityToken(req, identityToken)
.then(function () { .then(function () {
authSession = AuthenticationSessionHandler.get(req, vars.logger);
return handler.postValidationInit(req); return handler.postValidationInit(req);
}) })
.then(function () {
return AuthenticationSessionHandler.get(req, vars.logger);
})
.then(function (_authSession) {
authSession = _authSession;
})
.then(function () { .then(function () {
return consumeToken(identityToken, handler.challenge(), vars.userDataStore); 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, export function get_start_validation(handler: IdentityValidable,
postValidationEndpoint: string, postValidationEndpoint: string,
vars: ServerVariables) 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 { GlobalDependencies } from "../../types/Dependencies";
import { UserDataStore } from "./storage/UserDataStore"; import { UserDataStore } from "./storage/UserDataStore";
import { ConfigurationParser } from "./configuration/ConfigurationParser"; import { ConfigurationParser } from "./configuration/ConfigurationParser";
import { RestApi } from "./RestApi";
import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder"; import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder";
import { GlobalLogger } from "./logging/GlobalLogger"; import { GlobalLogger } from "./logging/GlobalLogger";
import { RequestLogger } from "./logging/RequestLogger"; import { RequestLogger } from "./logging/RequestLogger";
import { ServerVariables } from "./ServerVariables"; import { ServerVariables } from "./ServerVariables";
import { ServerVariablesInitializer } from "./ServerVariablesInitializer"; import { ServerVariablesInitializer } from "./ServerVariablesInitializer";
import { Configurator } from "./web_server/Configurator";
import * as Express from "express"; import * as Express from "express";
import * as BodyParser from "body-parser";
import * as Path from "path"; import * as Path from "path";
import * as http from "http"; import * as http from "http";
const addRequestId = require("express-request-id")();
// Constants
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) { function clone(obj: any) {
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
} }
@ -35,35 +25,12 @@ export default class Server {
private httpServer: http.Server; private httpServer: http.Server;
private globalLogger: GlobalLogger; private globalLogger: GlobalLogger;
private requestLogger: RequestLogger; private requestLogger: RequestLogger;
private serverVariables: ServerVariables;
constructor(deps: GlobalDependencies) { constructor(deps: GlobalDependencies) {
this.globalLogger = new GlobalLogger(deps.winston); this.globalLogger = new GlobalLogger(deps.winston);
this.requestLogger = new RequestLogger(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, private displayConfigurations(userConfiguration: UserConfiguration,
appConfiguration: AppConfiguration) { appConfiguration: AppConfiguration) {
const displayableUserConfiguration = clone(userConfiguration); const displayableUserConfiguration = clone(userConfiguration);
@ -94,8 +61,7 @@ export default class Server {
const that = this; const that = this;
return ServerVariablesInitializer.initialize(config, this.requestLogger, deps) return ServerVariablesInitializer.initialize(config, this.requestLogger, deps)
.then(function (vars: ServerVariables) { .then(function (vars: ServerVariables) {
that.serverVariables = vars; Configurator.configure(config, app, vars, deps);
that.setupExpressApplication(config, app, deps);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
} }

View File

@ -37,7 +37,7 @@ import { IMongoClient } from "./connectors/mongo/IMongoClient";
import { GlobalDependencies } from "../../types/Dependencies"; import { GlobalDependencies } from "../../types/Dependencies";
import { ServerVariables } from "./ServerVariables"; import { ServerVariables } from "./ServerVariables";
import { AuthenticationMethodCalculator } from "./AuthenticationMethodCalculator"; import { MethodCalculator } from "./authentication/MethodCalculator";
class UserDataStoreFactory { class UserDataStoreFactory {
static create(config: Configuration.AppConfiguration): BluebirdPromise<UserDataStore> { 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 Util = require("util");
import { import {
UserConfiguration, StorageConfiguration, UserConfiguration, StorageConfiguration,
NotifierConfiguration NotifierConfiguration, AuthenticationMethodsConfiguration
} from "./Configuration"; } from "./Configuration";
import { MethodCalculator } from "../authentication/MethodCalculator";
function validateSchema(configuration: UserConfiguration): string[] { function validateSchema(configuration: UserConfiguration): string[] {
const schema = require(Path.resolve(__dirname, "./Configuration.schema.json")); const schema = require(Path.resolve(__dirname, "./Configuration.schema.json"));
@ -34,7 +35,7 @@ function validateUnknownKeys(path: string, obj: any, knownKeys: string[]) {
return []; return [];
} }
function validateStorage(storage: any) { function validateStorage(storage: any): string[] {
const ERROR = "Storage must be either 'local' or 'mongo'"; const ERROR = "Storage must be either 'local' or 'mongo'";
if (!storage) if (!storage)
@ -53,22 +54,28 @@ function validateStorage(storage: any) {
return []; return [];
} }
function validateNotifier(notifier: NotifierConfiguration) { function validateNotifier(notifier: NotifierConfiguration,
authenticationMethods: AuthenticationMethodsConfiguration): string[] {
const ERROR = "Notifier must be either 'filesystem', 'email' or 'smtp'"; const ERROR = "Notifier must be either 'filesystem', 'email' or 'smtp'";
if (!notifier) if (!notifier)
return []; return [];
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"]); const errors = validateUnknownKeys("notifier", notifier, ["filesystem", "email", "smtp"]);
if (errors.length > 0) if (errors.length > 0)
return errors; return errors;
if (notifier && notifier.filesystem && notifier.email && notifier.smtp)
return [ERROR];
if (notifier && !notifier.filesystem && !notifier.email && !notifier.smtp)
return [ERROR];
return []; return [];
} }
@ -76,7 +83,8 @@ export class Validator {
static isValid(configuration: any): string[] { static isValid(configuration: any): string[] {
const schemaErrors = validateSchema(configuration); const schemaErrors = validateSchema(configuration);
const storageErrors = validateStorage(configuration.storage); const storageErrors = validateStorage(configuration.storage);
const notifierErrors = validateNotifier(configuration.notifier); const notifierErrors = validateNotifier(configuration.notifier,
configuration.authentication_methods);
return schemaErrors return schemaErrors
.concat(storageErrors) .concat(storageErrors)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import objectPath = require("object-path");
import winston = require("winston"); import winston = require("winston");
import Endpoints = require("../../../../../shared/api"); import Endpoints = require("../../../../../shared/api");
import { ServerVariables } from "../../ServerVariables"; import { ServerVariables } from "../../ServerVariables";
import AuthenticationSession = require("../../AuthenticationSession"); import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import ErrorReplies = require("../../ErrorReplies"); import ErrorReplies = require("../../ErrorReplies");
import UserMessages = require("../../../../../shared/UserMessages"); import UserMessages = require("../../../../../shared/UserMessages");
@ -13,18 +13,19 @@ import Constants = require("../../../../../shared/constants");
export default function (vars: ServerVariables) { export default function (vars: ServerVariables) {
return function (req: express.Request, res: express.Response): BluebirdPromise<void> { 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) {
let redirectUrl: string; const authSession = AuthenticationSessionHandler.get(req, vars.logger);
if (vars.config.default_redirection_url) { let redirectUrl: string;
redirectUrl = vars.config.default_redirection_url; if (vars.config.default_redirection_url) {
} redirectUrl = vars.config.default_redirection_url;
vars.logger.debug(req, "Request redirection to \"%s\".", redirectUrl); }
res.json({ vars.logger.debug(req, "Request redirection to \"%s\".", redirectUrl);
redirect: redirectUrl res.json({
} as RedirectionMessage); redirect: redirectUrl
return BluebirdPromise.resolve(); } as RedirectionMessage);
}) return resolve();
})
.catch(ErrorReplies.replyWithError200(req, res, vars.logger, .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
UserMessages.OPERATION_FAILED)); UserMessages.OPERATION_FAILED));
}; };

View File

@ -9,12 +9,13 @@ import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationT
import Constants = require("../constants"); import Constants = require("../constants");
import Endpoints = require("../../../../../../../shared/api"); import Endpoints = require("../../../../../../../shared/api");
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import AuthenticationSession = require("../../../../AuthenticationSession"); import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import UserMessages = require("../../../../../../../shared/UserMessages"); import UserMessages = require("../../../../../../../shared/UserMessages");
import FirstFactorValidator = require("../../../../FirstFactorValidator"); import FirstFactorValidator = require("../../../../FirstFactorValidator");
import { IRequestLogger } from "../../../../logging/IRequestLogger"; import { IRequestLogger } from "../../../../logging/IRequestLogger";
import { IUserDataStore } from "../../../../storage/IUserDataStore"; import { IUserDataStore } from "../../../../storage/IUserDataStore";
import { ITotpHandler } from "../../../../authentication/totp/ITotpHandler"; import { ITotpHandler } from "../../../../authentication/totp/ITotpHandler";
import { TOTPSecret } from "../../../../../../types/TOTPSecret";
export default class RegistrationHandler implements IdentityValidable { export default class RegistrationHandler implements IdentityValidable {
@ -35,21 +36,22 @@ export default class RegistrationHandler implements IdentityValidable {
} }
private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> { private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
return AuthenticationSession.get(req, this.logger) const that = this;
.then(function (authSession) { return new BluebirdPromise(function (resolve, reject) {
const userid = authSession.userid; const authSession = AuthenticationSessionHandler.get(req, that.logger);
const email = authSession.email; const userid = authSession.userid;
const email = authSession.email;
if (!(userid && 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 = { const identity = {
email: email, email: email,
userid: userid userid: userid
}; };
return BluebirdPromise.resolve(identity); return resolve(identity);
}); });
} }
preValidationInit(req: express.Request): BluebirdPromise<Identity> { preValidationInit(req: express.Request): BluebirdPromise<Identity> {
@ -70,26 +72,31 @@ export default class RegistrationHandler implements IdentityValidable {
postValidationResponse(req: express.Request, res: express.Response): BluebirdPromise<void> { postValidationResponse(req: express.Request, res: express.Response): BluebirdPromise<void> {
const that = this; const that = this;
return AuthenticationSession.get(req, this.logger) let secret: TOTPSecret;
.then(function (authSession) { let userId: string;
const userid = authSession.identity_check.userid; return new BluebirdPromise(function (resolve, reject) {
const challenge = authSession.identity_check.challenge; const authSession = AuthenticationSessionHandler.get(req, that.logger);
const challenge = authSession.identity_check.challenge;
userId = authSession.identity_check.userid;
if (challenge != Constants.CHALLENGE || !userid) { if (challenge != Constants.CHALLENGE || !userId) {
return BluebirdPromise.reject(new Error("Bad challenge.")); return reject(new Error("Bad challenge."));
} }
const secret = that.totp.generate(); resolve();
that.logger.debug(req, "Save the TOTP secret in DB."); })
return that.userDataStore.saveTOTPSecret(userid, secret) .then(function () {
.then(function () { secret = that.totp.generate();
AuthenticationSession.reset(req); 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, { res.render(Constants.TEMPLATE_NAME, {
base32_secret: secret.base32, base32_secret: secret.base32,
otpauth_url: secret.otpauth_url, otpauth_url: secret.otpauth_url,
login_endpoint: Endpoints.FIRST_FACTOR_GET login_endpoint: Endpoints.FIRST_FACTOR_GET
}); });
});
}) })
.catch(ErrorReplies.replyWithError200(req, res, that.logger, UserMessages.OPERATION_FAILED)); .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 express = require("express");
import { TOTPSecretDocument } from "../../../../storage/TOTPSecretDocument"; import { TOTPSecretDocument } from "../../../../storage/TOTPSecretDocument";
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import Endpoints = require("../../../../../../../shared/api"); import Endpoints = require("../../../../../../../shared/api");
import redirect from "../../redirect"; import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import AuthenticationSessionHandler = require("../../../../AuthenticationSession"); import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
import UserMessages = require("../../../../../../../shared/UserMessages"); import UserMessages = require("../../../../../../../shared/UserMessages");
import { ServerVariables } from "../../../../ServerVariables"; import { ServerVariables } from "../../../../ServerVariables";
@ -20,10 +19,12 @@ export default function (vars: ServerVariables) {
let authSession: AuthenticationSession; let authSession: AuthenticationSession;
const token = req.body.token; const token = req.body.token;
return AuthenticationSessionHandler.get(req, vars.logger) return new BluebirdPromise(function (resolve, reject) {
.then(function (_authSession) { authSession = AuthenticationSessionHandler.get(req, vars.logger);
authSession = _authSession; vars.logger.info(req, "Initiate TOTP validation for user \"%s\".", authSession.userid);
vars.logger.info(req, "Initiate TOTP validation for user \"%s\".", authSession.userid); resolve();
})
.then(function () {
return vars.userDataStore.retrieveTOTPSecret(authSession.userid); return vars.userDataStore.retrieveTOTPSecret(authSession.userid);
}) })
.then(function (doc: TOTPSecretDocument) { .then(function (doc: TOTPSecretDocument) {
@ -38,5 +39,5 @@ export default function (vars: ServerVariables) {
.catch(ErrorReplies.replyWithError200(req, res, vars.logger, .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
UserMessages.OPERATION_FAILED)); 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 { Identity } from "../../../../../../types/Identity";
import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationTemplate"; import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationTemplate";
import FirstFactorValidator = require("../../../../FirstFactorValidator"); import FirstFactorValidator = require("../../../../FirstFactorValidator");
import AuthenticationSession = require("../../../../AuthenticationSession"); import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import { IRequestLogger } from "../../../../logging/IRequestLogger"; import { IRequestLogger } from "../../../../logging/IRequestLogger";
const CHALLENGE = "u2f-register"; const CHALLENGE = "u2f-register";
@ -28,21 +28,22 @@ export default class RegistrationHandler implements IdentityValidable {
} }
private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> { private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
return AuthenticationSession.get(req, this.logger) const that = this;
.then(function (authSession) { return new BluebirdPromise(function(resolve, reject) {
const userid = authSession.userid; const authSession = AuthenticationSessionHandler.get(req, that.logger);
const email = authSession.email; const userid = authSession.userid;
const email = authSession.email;
if (!(userid && 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 = { const identity = {
email: email, email: email,
userid: userid userid: userid
}; };
return BluebirdPromise.resolve(identity); return resolve(identity);
}); });
} }
preValidationInit(req: express.Request): BluebirdPromise<Identity> { preValidationInit(req: express.Request): BluebirdPromise<Identity> {

View File

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

View File

@ -6,9 +6,8 @@ import u2f_common = require("../U2FCommon");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import express = require("express"); import express = require("express");
import U2f = require("u2f"); import U2f = require("u2f");
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import AuthenticationSessionHandler = require("../../../../AuthenticationSession"); import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
import UserMessages = require("../../../../../../../shared/UserMessages"); import UserMessages = require("../../../../../../../shared/UserMessages");
import { ServerVariables } from "../../../../ServerVariables"; import { ServerVariables } from "../../../../ServerVariables";
@ -18,21 +17,18 @@ export default function (vars: ServerVariables) {
let authSession: AuthenticationSession; let authSession: AuthenticationSession;
const appid: string = u2f_common.extract_app_id(req); const appid: string = u2f_common.extract_app_id(req);
return AuthenticationSessionHandler.get(req, vars.logger) return new BluebirdPromise(function (resolve, reject) {
.then(function (_authSession) { authSession = AuthenticationSessionHandler.get(req, vars.logger);
authSession = _authSession; if (!authSession.identity_check
|| authSession.identity_check.challenge != "u2f-register") {
res.status(403);
res.send();
return reject(new Error("Bad challenge."));
}
if (!authSession.identity_check vars.logger.info(req, "Starting registration for appId '%s'", appid);
|| authSession.identity_check.challenge != "u2f-register") { return resolve(vars.u2f.request(appid));
res.status(403); })
res.send();
return BluebirdPromise.reject(new Error("Bad challenge."));
}
vars.logger.info(req, "Starting registration for appId '%s'", appid);
return BluebirdPromise.resolve(vars.u2f.request(appid));
})
.then(function (registrationRequest: U2f.Request) { .then(function (registrationRequest: U2f.Request) {
vars.logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest)); vars.logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
authSession.register_request = registrationRequest; authSession.register_request = registrationRequest;
@ -43,5 +39,5 @@ export default function (vars: ServerVariables) {
UserMessages.OPERATION_FAILED)); 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 { Winston } from "../../../../../../types/Dependencies";
import U2f = require("u2f"); import U2f = require("u2f");
import exceptions = require("../../../../Exceptions"); import exceptions = require("../../../../Exceptions");
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import redirect from "../../redirect"; import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariables } from "../../../../ServerVariables"; import { ServerVariables } from "../../../../ServerVariables";
import AuthenticationSessionHandler = require("../../../../AuthenticationSession"); import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import UserMessages = require("../../../../../../../shared/UserMessages"); import UserMessages = require("../../../../../../../shared/UserMessages");
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
@ -21,14 +20,16 @@ export default function (vars: ServerVariables) {
let authSession: AuthenticationSession; let authSession: AuthenticationSession;
const appId = u2f_common.extract_app_id(req); const appId = u2f_common.extract_app_id(req);
return AuthenticationSessionHandler.get(req, vars.logger) return new BluebirdPromise(function (resolve, reject) {
.then(function (_authSession) { authSession = AuthenticationSessionHandler.get(req, vars.logger);
authSession = _authSession; if (!authSession.sign_request) {
if (!authSession.sign_request) { const err = new Error("No sign request");
const err = new Error("No sign request"); ErrorReplies.replyWithError401(req, res, vars.logger)(err);
ErrorReplies.replyWithError401(req, res, vars.logger)(err); return reject(err);
return BluebirdPromise.reject(err); }
} resolve();
})
.then(function () {
const userid = authSession.userid; const userid = authSession.userid;
return vars.userDataStore.retrieveU2FRegistration(userid, appId); return vars.userDataStore.retrieveU2FRegistration(userid, appId);
}) })
@ -50,6 +51,6 @@ export default function (vars: ServerVariables) {
UserMessages.OPERATION_FAILED)); 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 { Winston } from "../../../../../../types/Dependencies";
import exceptions = require("../../../../Exceptions"); import exceptions = require("../../../../Exceptions");
import { SignMessage } from "../../../../../../../shared/SignMessage"; import { SignMessage } from "../../../../../../../shared/SignMessage";
import FirstFactorBlocker from "../../../FirstFactorBlocker";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import AuthenticationSessionHandler = require("../../../../AuthenticationSession"); import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
import UserMessages = require("../../../../../../../shared/UserMessages"); import UserMessages = require("../../../../../../../shared/UserMessages");
import { ServerVariables } from "../../../../ServerVariables"; import { ServerVariables } from "../../../../ServerVariables";
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
@ -21,9 +20,11 @@ export default function (vars: ServerVariables) {
let authSession: AuthenticationSession; let authSession: AuthenticationSession;
const appId = u2f_common.extract_app_id(req); const appId = u2f_common.extract_app_id(req);
return AuthenticationSessionHandler.get(req, vars.logger) return new BluebirdPromise(function (resolve, reject) {
.then(function (_authSession) { authSession = AuthenticationSessionHandler.get(req, vars.logger);
authSession = _authSession; resolve();
})
.then(function () {
return vars.userDataStore.retrieveU2FRegistration(authSession.userid, appId); return vars.userDataStore.retrieveU2FRegistration(authSession.userid, appId);
}) })
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<SignMessage> { .then(function (doc: U2FRegistrationDocument): BluebirdPromise<SignMessage> {
@ -51,6 +52,5 @@ export default function (vars: ServerVariables) {
.catch(ErrorReplies.replyWithError200(req, res, vars.logger, .catch(ErrorReplies.replyWithError200(req, res, vars.logger,
UserMessages.OPERATION_FAILED)); UserMessages.OPERATION_FAILED));
} }
return handler;
return FirstFactorBlocker(handler, vars.logger);
} }

View File

@ -7,13 +7,13 @@ import winston = require("winston");
import AuthenticationValidator = require("../../AuthenticationValidator"); import AuthenticationValidator = require("../../AuthenticationValidator");
import ErrorReplies = require("../../ErrorReplies"); import ErrorReplies = require("../../ErrorReplies");
import { AppConfiguration } from "../../configuration/Configuration"; import { AppConfiguration } from "../../configuration/Configuration";
import AuthenticationSessionHandler = require("../../AuthenticationSession"); import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../../types/AuthenticationSession";
import Constants = require("../../../../../shared/constants"); import Constants = require("../../../../../shared/constants");
import Util = require("util"); import Util = require("util");
import { DomainExtractor } from "../../utils/DomainExtractor"; import { DomainExtractor } from "../../utils/DomainExtractor";
import { ServerVariables } from "../../ServerVariables"; import { ServerVariables } from "../../ServerVariables";
import { AuthenticationMethodCalculator } from "../../AuthenticationMethodCalculator"; import { MethodCalculator } from "../../authentication/MethodCalculator";
import { IRequestLogger } from "../../logging/IRequestLogger"; import { IRequestLogger } from "../../logging/IRequestLogger";
const FIRST_FACTOR_NOT_VALIDATED_MESSAGE = "First factor not yet validated"; 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, function verify_filter(req: express.Request, res: express.Response,
vars: ServerVariables): BluebirdPromise<void> { vars: ServerVariables): BluebirdPromise<void> {
let _authSession: AuthenticationSession; let authSession: AuthenticationSession;
let username: string; let username: string;
let groups: string[]; let groups: string[];
return AuthenticationSessionHandler.get(req, vars.logger) return new BluebirdPromise(function (resolve, reject) {
.then(function (authSession) { authSession = AuthenticationSessionHandler.get(req, vars.logger);
_authSession = authSession; username = authSession.userid;
username = _authSession.userid; groups = authSession.groups;
groups = _authSession.groups;
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] + res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] +
req.headers["x-original-uri"])); req.headers["x-original-uri"]));
if (!_authSession.userid) if (!authSession.userid) {
return BluebirdPromise.reject( reject(new exceptions.AccessDeniedError(
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE)); Util.format("%s: %s.", FIRST_FACTOR_NOT_VALIDATED_MESSAGE, "userid is missing")));
return;
}
const host = objectPath.get<express.Request, string>(req, "headers.host"); const host = objectPath.get<express.Request, string>(req, "headers.host");
const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri"); const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri");
const domain = DomainExtractor.fromHostHeader(host); const domain = DomainExtractor.fromHostHeader(host);
const authenticationMethod = const authenticationMethod =
new AuthenticationMethodCalculator(vars.config.authentication_methods) MethodCalculator.compute(vars.config.authentication_methods, domain);
.compute(domain); vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, path,
vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, path, username, groups.join(","));
username, groups.join(","));
if (!_authSession.first_factor) if (!authSession.first_factor)
return BluebirdPromise.reject( return reject(new exceptions.AccessDeniedError(
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE)); Util.format("%s: %s.", FIRST_FACTOR_NOT_VALIDATED_MESSAGE, "first factor is false")));
if (authenticationMethod == "two_factor" && !_authSession.second_factor) if (authenticationMethod == "two_factor" && !authSession.second_factor)
return BluebirdPromise.reject( return reject(new exceptions.AccessDeniedError(
new exceptions.AccessDeniedError(SECOND_FACTOR_NOT_VALIDATED_MESSAGE)); Util.format("%s: %s.", SECOND_FACTOR_NOT_VALIDATED_MESSAGE, "second factor is false")));
const isAllowed = vars.accessController.isAccessAllowed(domain, path, username, groups); 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'", new exceptions.DomainAccessDenied(Util.format("User '%s' does not have access to '%s'",
username, domain))); username, domain)));
return BluebirdPromise.resolve();
}) resolve();
})
.then(function () { .then(function () {
return verify_inactivity(req, _authSession, return verify_inactivity(req, authSession,
vars.config, vars.logger); vars.config, vars.logger);
}) })
.then(function () { .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 sinon = require("sinon");
import IdentityValidator = require("../src/lib/IdentityCheckMiddleware"); import IdentityValidator = require("../src/lib/IdentityCheckMiddleware");
import AuthenticationSessionHandler = require("../src/lib/AuthenticationSession"); import { AuthenticationSessionHandler } from "../src/lib/AuthenticationSessionHandler";
import { AuthenticationSession } from "../types/AuthenticationSession"; import { AuthenticationSession } from "../types/AuthenticationSession";
import { UserDataStore } from "../src/lib/storage/UserDataStore"; import { UserDataStore } from "../src/lib/storage/UserDataStore";
import exceptions = require("../src/lib/Exceptions"); import exceptions = require("../src/lib/Exceptions");
@ -155,14 +155,10 @@ describe("test identity check process", function () {
req.query.identity_token = "token"; req.query.identity_token = "token";
req.session = {}; req.session = {};
let authSession: AuthenticationSession; const authSession: AuthenticationSession = AuthenticationSessionHandler.get(req as any, vars.logger);
const callback = IdentityValidator.get_finish_validation(identityValidable, vars); const callback = IdentityValidator.get_finish_validation(identityValidable, vars);
return AuthenticationSessionHandler.get(req as any, vars.logger) return callback(req as any, res as any, undefined)
.then(function (_authSession) {
authSession = _authSession;
return callback(req as any, res as any, undefined);
})
.then(function () { return BluebirdPromise.reject("Should fail"); }) .then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () { .catch(function () {
Assert.equal(authSession.identity_check.userid, "user"); 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.regulation should have required property 'max_retries'",
"data.session should have required property 'secret'", "data.session should have required property 'secret'",
"Storage must be either 'local' or 'mongo'", "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({ Assert.deepStrictEqual(Validator.isValid({
@ -54,7 +54,7 @@ describe("test validator", function () {
} }
}), [ }), [
"data.storage has unknown key 'abc'", "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 { IRequestLogger } from "../../src/lib/logging/IRequestLogger";
import Sinon = require("sinon"); import Sinon = require("sinon");
import { RequestLogger } from "../../src/lib/logging/RequestLogger";
import Winston = require("winston");
import Express = require("express");
export class RequestLoggerStub implements IRequestLogger { export class RequestLoggerStub implements IRequestLogger {
infoStub: Sinon.SinonStub; infoStub: Sinon.SinonStub;
debugStub: Sinon.SinonStub; debugStub: Sinon.SinonStub;
errorStub: Sinon.SinonStub; errorStub: Sinon.SinonStub;
private requestLogger: RequestLogger;
constructor() { constructor(enableLogging?: boolean) {
this.infoStub = Sinon.stub(); this.infoStub = Sinon.stub();
this.debugStub = Sinon.stub(); this.debugStub = Sinon.stub();
this.errorStub = Sinon.stub(); this.errorStub = Sinon.stub();
if (enableLogging)
this.requestLogger = new RequestLogger(Winston);
} }
info(req: Express.Request, message: string, ...args: any[]): void { 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 { 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 { 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 { export class ServerVariablesMockBuilder {
static build(): { variables: ServerVariables, mocks: ServerVariablesMock} { static build(enableLogging?: boolean): { variables: ServerVariables, mocks: ServerVariablesMock} {
const mocks: ServerVariablesMock = { const mocks: ServerVariablesMock = {
accessController: new AccessControllerStub(), accessController: new AccessControllerStub(),
config: { config: {
@ -62,7 +62,7 @@ export class ServerVariablesMockBuilder {
ldapAuthenticator: new AuthenticatorStub(), ldapAuthenticator: new AuthenticatorStub(),
ldapEmailsRetriever: new EmailsRetrieverStub(), ldapEmailsRetriever: new EmailsRetrieverStub(),
ldapPasswordUpdater: new PasswordUpdaterStub(), ldapPasswordUpdater: new PasswordUpdaterStub(),
logger: new RequestLoggerStub(), logger: new RequestLoggerStub(enableLogging),
notifier: new NotifierStub(), notifier: new NotifierStub(),
regulator: new RegulatorStub(), regulator: new RegulatorStub(),
totpHandler: new TotpHandlerStub(), totpHandler: new TotpHandlerStub(),

View File

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

View File

@ -4,7 +4,7 @@ import BluebirdPromise = require("bluebird");
import Assert = require("assert"); import Assert = require("assert");
import FirstFactorPost = require("../../../src/lib/routes/firstfactor/post"); import FirstFactorPost = require("../../../src/lib/routes/firstfactor/post");
import exceptions = require("../../../src/lib/Exceptions"); import exceptions = require("../../../src/lib/Exceptions");
import AuthenticationSessionHandler = require("../../../src/lib/AuthenticationSession"); import { AuthenticationSessionHandler } from "../../../src/lib/AuthenticationSessionHandler";
import { AuthenticationSession } from "../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../types/AuthenticationSession";
import Endpoints = require("../../../../shared/api"); import Endpoints = require("../../../../shared/api");
import AuthenticationRegulatorMock = require("../../mocks/AuthenticationRegulator"); import AuthenticationRegulatorMock = require("../../mocks/AuthenticationRegulator");
@ -20,6 +20,7 @@ describe("test the first factor validation route", function () {
let groups: string[]; let groups: string[];
let vars: ServerVariables; let vars: ServerVariables;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let authSession: AuthenticationSession;
beforeEach(function () { beforeEach(function () {
emails = ["test_ok@example.com"]; emails = ["test_ok@example.com"];
@ -48,6 +49,7 @@ describe("test the first factor validation route", function () {
}; };
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
}); });
it("should reply with 204 if success", function () { it("should reply with 204 if success", function () {
@ -56,12 +58,7 @@ describe("test the first factor validation route", function () {
emails: emails, emails: emails,
groups: groups groups: groups
})); }));
let authSession: AuthenticationSession; return FirstFactorPost.default(vars)(req as any, res as any)
return AuthenticationSessionHandler.get(req as any, vars.logger)
.then(function (_authSession) {
authSession = _authSession;
return FirstFactorPost.default(vars)(req as any, res as any);
})
.then(function () { .then(function () {
Assert.equal("username", authSession.userid); Assert.equal("username", authSession.userid);
Assert(res.send.calledOnce); 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 () { it("should set first email address as user session variable", function () {
const emails = ["test_ok@example.com"]; const emails = ["test_ok@example.com"];
let authSession: AuthenticationSession;
mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password") mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password")
.returns(BluebirdPromise.resolve({ .returns(BluebirdPromise.resolve({
emails: emails, emails: emails,
groups: groups groups: groups
})); }));
return AuthenticationSessionHandler.get(req as any, vars.logger) return FirstFactorPost.default(vars)(req as any, res as any)
.then(function (_authSession) {
authSession = _authSession;
return FirstFactorPost.default(vars)(req as any, res as any);
})
.then(function () { .then(function () {
Assert.equal("test_ok@example.com", authSession.email); 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 PasswordResetFormPost = require("../../../src/lib/routes/password-reset/form/post");
import { PasswordUpdater } from "../../../src/lib/ldap/PasswordUpdater"; 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 { UserDataStore } from "../../../src/lib/storage/UserDataStore";
import Sinon = require("sinon"); import Sinon = require("sinon");
import Assert = require("assert"); import Assert = require("assert");
@ -15,6 +16,7 @@ describe("test reset password route", function () {
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let vars: ServerVariables; let vars: ServerVariables;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let authSession: AuthenticationSession;
beforeEach(function () { beforeEach(function () {
req = { req = {
@ -53,13 +55,11 @@ describe("test reset password route", function () {
}; };
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
AuthenticationSessionHandler.get(req as any, vars.logger) authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
.then(function (authSession) { authSession.userid = "user";
authSession.userid = "user"; authSession.email = "user@example.com";
authSession.email = "user@example.com"; authSession.first_factor = true;
authSession.first_factor = true; authSession.second_factor = false;
authSession.second_factor = false;
});
}); });
describe("test reset password post", () => { describe("test reset password post", () => {
@ -69,14 +69,11 @@ describe("test reset password route", function () {
mocks.ldapPasswordUpdater.updatePasswordStub.returns(BluebirdPromise.resolve()); mocks.ldapPasswordUpdater.updatePasswordStub.returns(BluebirdPromise.resolve());
return AuthenticationSessionHandler.get(req as any, vars.logger) authSession.identity_check = {
.then(function (authSession) { userid: "user",
authSession.identity_check = { challenge: "reset-password"
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 () { .then(function () {
return AuthenticationSessionHandler.get(req as any, vars.logger); return AuthenticationSessionHandler.get(req as any, vars.logger);
}).then(function (_authSession) { }).then(function (_authSession) {
@ -88,14 +85,11 @@ describe("test reset password route", function () {
}); });
it("should fail if identity_challenge does not exist", function () { it("should fail if identity_challenge does not exist", function () {
return AuthenticationSessionHandler.get(req as any, vars.logger) authSession.identity_check = {
.then(function (authSession) { userid: "user",
authSession.identity_check = { challenge: undefined
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 () { .then(function () {
Assert.equal(res.status.getCall(0).args[0], 200); Assert.equal(res.status.getCall(0).args[0], 200);
Assert.deepEqual(res.send.getCall(0).args[0], { Assert.deepEqual(res.send.getCall(0).args[0], {
@ -111,14 +105,12 @@ describe("test reset password route", function () {
mocks.ldapPasswordUpdater.updatePasswordStub mocks.ldapPasswordUpdater.updatePasswordStub
.returns(BluebirdPromise.reject("Internal error with LDAP")); .returns(BluebirdPromise.reject("Internal error with LDAP"));
return AuthenticationSessionHandler.get(req as any, vars.logger) authSession.identity_check = {
.then(function (authSession) { challenge: "reset-password",
authSession.identity_check = { userid: "user"
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.equal(res.status.getCall(0).args[0], 200);
Assert.deepEqual(res.send.getCall(0).args[0], { Assert.deepEqual(res.send.getCall(0).args[0], {
error: "An error occurred during password reset. Your password has not been changed." 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 winston = require("winston");
import RegistrationHandler from "../../../../../src/lib/routes/secondfactor/totp/identity/RegistrationHandler"; import RegistrationHandler from "../../../../../src/lib/routes/secondfactor/totp/identity/RegistrationHandler";
import { Identity } from "../../../../../types/Identity"; import { Identity } from "../../../../../types/Identity";
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
import { UserDataStore } from "../../../../../src/lib/storage/UserDataStore"; import { UserDataStore } from "../../../../../src/lib/storage/UserDataStore";
import assert = require("assert"); import assert = require("assert");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");

View File

@ -1,11 +1,9 @@
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import Sinon = require("sinon"); import Sinon = require("sinon");
import assert = require("assert"); import Assert = require("assert");
import winston = require("winston"); import Exceptions = require("../../../../../src/lib/Exceptions");
import { AuthenticationSessionHandler } from "../../../../../src/lib/AuthenticationSessionHandler";
import exceptions = require("../../../../../src/lib/Exceptions");
import AuthenticationSessionHandler = require("../../../../../src/lib/AuthenticationSession");
import { AuthenticationSession } from "../../../../../types/AuthenticationSession"; import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
import SignPost = require("../../../../../src/lib/routes/secondfactor/totp/sign/post"); import SignPost = require("../../../../../src/lib/routes/secondfactor/totp/sign/post");
import { ServerVariables } from "../../../../../src/lib/ServerVariables"; import { ServerVariables } from "../../../../../src/lib/ServerVariables";
@ -27,9 +25,7 @@ describe("test totp route", function () {
mocks = s.mocks; mocks = s.mocks;
const app_get = Sinon.stub(); const app_get = Sinon.stub();
req = { req = {
app: { app: {},
get: Sinon.stub().returns({ logger: winston })
},
body: { body: {
token: "abc" token: "abc"
}, },
@ -47,13 +43,10 @@ describe("test totp route", function () {
} }
}; };
mocks.userDataStore.retrieveTOTPSecretStub.returns(BluebirdPromise.resolve(doc)); mocks.userDataStore.retrieveTOTPSecretStub.returns(BluebirdPromise.resolve(doc));
return AuthenticationSessionHandler.get(req as any, vars.logger) authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
.then(function (_authSession) { authSession.userid = "user";
authSession = _authSession; authSession.first_factor = true;
authSession.userid = "user"; authSession.second_factor = false;
authSession.first_factor = true;
authSession.second_factor = false;
});
}); });
@ -61,7 +54,7 @@ describe("test totp route", function () {
mocks.totpHandler.validateStub.returns(true); mocks.totpHandler.validateStub.returns(true);
return SignPost.default(vars)(req as any, res as any) return SignPost.default(vars)(req as any, res as any)
.then(function () { .then(function () {
assert.equal(true, authSession.second_factor); Assert.equal(true, authSession.second_factor);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
}); });
@ -70,24 +63,13 @@ describe("test totp route", function () {
mocks.totpHandler.validateStub.returns(false); mocks.totpHandler.validateStub.returns(false);
return SignPost.default(vars)(req as any, res as any) return SignPost.default(vars)(req as any, res as any)
.then(function () { .then(function () {
assert.equal(false, authSession.second_factor); Assert.equal(false, authSession.second_factor);
assert.equal(res.status.getCall(0).args[0], 200); Assert.equal(res.status.getCall(0).args[0], 200);
assert.deepEqual(res.send.getCall(0).args[0], { Assert.deepEqual(res.send.getCall(0).args[0], {
error: "Operation failed." error: "Operation failed."
}); });
return BluebirdPromise.resolve(); 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 Sinon = require("sinon");
import winston = require("winston"); import Assert = require("assert");
import assert = require("assert");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import { Identity } from "../../../../../types/Identity"; import { Identity } from "../../../../../types/Identity";
import RegistrationHandler from "../../../../../src/lib/routes/secondfactor/u2f/identity/RegistrationHandler"; import RegistrationHandler from "../../../../../src/lib/routes/secondfactor/u2f/identity/RegistrationHandler";
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
import ExpressMock = require("../../../../mocks/express"); import ExpressMock = require("../../../../mocks/express");
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub"; import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../mocks/ServerVariablesMockBuilder"; import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../mocks/ServerVariablesMockBuilder";
import { ServerVariables } from "../../../../../src/lib/ServerVariables"; import { ServerVariables } from "../../../../../src/lib/ServerVariables";
describe("test register handler", function () { describe("test U2F register handler", function () {
let req: ExpressMock.RequestMock; let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
@ -46,9 +43,9 @@ describe("test register handler", function () {
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({})); mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
res.send = sinon.spy(); res.send = Sinon.spy();
res.json = sinon.spy(); res.json = Sinon.spy();
res.status = sinon.spy(); res.status = Sinon.spy();
}); });
describe("test u2f registration check", test_registration_check); 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.first_factor = false;
req.session.auth.userid = undefined; req.session.auth.userid = undefined;
new RegistrationHandler(vars.logger).preValidationInit(req as any) return new RegistrationHandler(vars.logger).preValidationInit(req as any)
.catch(function (err: Error) { .then(function () {
done(); 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.first_factor = false;
req.session.auth.email = undefined; req.session.auth.email = undefined;
new RegistrationHandler(vars.logger).preValidationInit(req as any) return new RegistrationHandler(vars.logger).preValidationInit(req as any)
.catch(function (err: Error) { .then(function () {
done(); 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) { it("should succeed if first factor passed, userid and email are provided", function () {
new RegistrationHandler(vars.logger).preValidationInit(req as any) req.session.auth.first_factor = true;
.then(function (identity: Identity) { req.session.auth.email = "admin@example.com";
done(); 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 BluebirdPromise = require("bluebird");
import assert = require("assert"); import assert = require("assert");
import U2FRegisterPost = require("../../../../../src/lib/routes/secondfactor/u2f/register/post"); 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 ExpressMock = require("../../../../mocks/express");
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub"; import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../mocks/ServerVariablesMockBuilder"; import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../mocks/ServerVariablesMockBuilder";
@ -15,6 +16,7 @@ describe("test u2f routes: register", function () {
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock; let mocks: ServerVariablesMock;
let vars: ServerVariables; let vars: ServerVariables;
let authSession: AuthenticationSession;
beforeEach(function () { beforeEach(function () {
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
@ -48,6 +50,8 @@ describe("test u2f routes: register", function () {
res.send = sinon.spy(); res.send = sinon.spy();
res.json = sinon.spy(); res.json = sinon.spy();
res.status = sinon.spy(); res.status = sinon.spy();
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
}); });
describe("test registration", test_registration); describe("test registration", test_registration);
@ -62,20 +66,14 @@ describe("test u2f routes: register", function () {
}; };
mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve(expectedStatus)); mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve(expectedStatus));
return AuthenticationSession.get(req as any, vars.logger) authSession.register_request = {
.then(function (authSession) { appId: "app",
authSession.register_request = { challenge: "challenge",
appId: "app", keyHandle: "key",
challenge: "challenge", version: "U2F_V2"
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 () { .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("user", mocks.userDataStore.saveU2FRegistrationStub.getCall(0).args[0]);
assert.equal(authSession.identity_check, undefined); assert.equal(authSession.identity_check, undefined);
}); });
@ -84,17 +82,14 @@ describe("test u2f routes: register", function () {
it("should return error message on finishRegistration error", function () { it("should return error message on finishRegistration error", function () {
mocks.u2f.checkRegistrationStub.returns({ errorCode: 500 }); mocks.u2f.checkRegistrationStub.returns({ errorCode: 500 });
return AuthenticationSession.get(req as any, vars.logger) authSession.register_request = {
.then(function (authSession) { appId: "app",
authSession.register_request = { challenge: "challenge",
appId: "app", keyHandle: "key",
challenge: "challenge", version: "U2F_V2"
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 BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(200, res.status.getCall(0).args[0]); 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 () { it("should return error message when register_request is not provided", function () {
mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve()); mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve());
return AuthenticationSession.get(req as any, vars.logger) authSession.register_request = undefined;
.then(function (authSession) { return U2FRegisterPost.default(vars)(req as any, res as any)
authSession.register_request = undefined;
return U2FRegisterPost.default(vars)(req as any, res as any);
})
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(200, res.status.getCall(0).args[0]); 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 () { it("should return error message when no auth request has been initiated", function () {
mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve()); mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve());
return AuthenticationSession.get(req as any, vars.logger) authSession.register_request = undefined;
.then(function (authSession) { return U2FRegisterPost.default(vars)(req as any, res as any)
authSession.register_request = undefined;
return U2FRegisterPost.default(vars)(req as any, res as any);
})
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(200, res.status.getCall(0).args[0]); 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 () { it("should return error message when identity has not been verified", function () {
return AuthenticationSession.get(req as any, vars.logger) authSession.identity_check = undefined;
.then(function (authSession) { return U2FRegisterPost.default(vars)(req as any, res as any)
authSession.identity_check = undefined;
return U2FRegisterPost.default(vars)(req as any, res as any);
})
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(200, res.status.getCall(0).args[0]); assert.equal(200, res.status.getCall(0).args[0]);

View File

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

View File

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

View File

@ -3,8 +3,6 @@ import sinon = require("sinon");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import assert = require("assert"); import assert = require("assert");
import U2FSignRequestGet = require("../../../../../src/lib/routes/secondfactor/u2f/sign_request/get"); 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 ExpressMock = require("../../../../mocks/express");
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub"; import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
import U2FMock = require("../../../../mocks/u2f"); import U2FMock = require("../../../../mocks/u2f");

View File

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

@ -17,7 +17,7 @@ Feature: User has access restricted access to domains
| https://dev.test.local:8080/users/bob/secret.html | | https://dev.test.local:8080/users/bob/secret.html |
| https://admin.test.local:8080/secret.html | | https://admin.test.local:8080/secret.html |
| https://mx1.mail.test.local:8080/secret.html | | https://mx1.mail.test.local:8080/secret.html |
| https://single_factor.test.local:8080/secret.html | | https://single_factor.test.local:8080/secret.html |
And I have no access to: And I have no access to:
| url | | url |
| https://mx2.mail.test.local:8080/secret.html | | https://mx2.mail.test.local:8080/secret.html |
@ -42,7 +42,7 @@ Feature: User has access restricted access to domains
| https://admin.test.local:8080/secret.html | | https://admin.test.local:8080/secret.html |
| https://dev.test.local:8080/users/john/secret.html | | https://dev.test.local:8080/users/john/secret.html |
| https://dev.test.local:8080/users/harry/secret.html | | https://dev.test.local:8080/users/harry/secret.html |
| https://single_factor.test.local:8080/secret.html | | https://single_factor.test.local:8080/secret.html |
@need-registered-user-harry @need-registered-user-harry
Scenario: User harry has restricted access Scenario: User harry has restricted access
@ -64,4 +64,4 @@ Feature: User has access restricted access to domains
| https://dev.test.local:8080/users/john/secret.html | | https://dev.test.local:8080/users/john/secret.html |
| https://mx1.mail.test.local:8080/secret.html | | https://mx1.mail.test.local:8080/secret.html |
| https://mx2.mail.test.local:8080/secret.html | | https://mx2.mail.test.local:8080/secret.html |
| https://single_factor.test.local:8080/secret.html | | https://single_factor.test.local:8080/secret.html |

View File

@ -1,16 +1,36 @@
Feature: Non authenticated users have no access to certain pages Feature: Non authenticated users have no access to certain pages
Scenario Outline: Anonymous user has no access to protected pages Scenario: Anonymous user has no access to protected pages
When I visit "<url>" Then I get the following status code when requesting:
Then I get an error <error code> | 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 | @needs-single_factor-config
| https://auth.test.local:8080/secondfactor | 401 | @need-registered-user-john
| https://auth.test.local:8080/verify | 401 | Scenario: User does not have acces to second factor related endpoints when in single factor mode
| https://auth.test.local:8080/secondfactor/u2f/identity/start | 401 | Given I post "https://auth.test.local:8080/api/firstfactor" with body:
| https://auth.test.local:8080/secondfactor/u2f/identity/finish | 401 | | key | value |
| https://auth.test.local:8080/secondfactor/totp/identity/start | 401 | | username | john |
| https://auth.test.local:8080/secondfactor/totp/identity/finish | 401 | | password | password |
| https://auth.test.local:8080/password-reset/identity/start | 401 | Then I get the following status code when requesting:
| https://auth.test.local:8080/password-reset/identity/finish | 401 | | 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,19 +5,20 @@ import Fs = require("fs");
import Speakeasy = require("speakeasy"); import Speakeasy = require("speakeasy");
import CustomWorld = require("../support/world"); import CustomWorld = require("../support/world");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import Request = require("request-promise");
Cucumber.defineSupportCode(function ({ Given, When, Then }) { Cucumber.defineSupportCode(function ({ Given, When, Then }) {
When(/^I visit "(https:\/\/[a-zA-Z0-9:%&._\/=?-]+)"$/, function (link: string) { When(/^I visit "(https:\/\/[a-zA-Z0-9:%&._\/=?-]+)"$/, function (link: string) {
return this.visit(link); return this.visit(link);
}); });
When("I wait for notification to disappear", function() { When("I wait for notification to disappear", function () {
const that = this; const that = this;
const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification")); const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification"));
return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 15000) return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 15000)
.then(function() { .then(function () {
return that.driver.wait(seleniumWebdriver.until.elementIsNotVisible(notificationEl), 15000); return that.driver.wait(seleniumWebdriver.until.elementIsNotVisible(notificationEl), 15000);
}) })
}) })
When("I set field {stringInDoubleQuotes} to {stringInDoubleQuotes}", function (fieldName: string, content: string) { When("I set field {stringInDoubleQuotes} to {stringInDoubleQuotes}", function (fieldName: string, content: string) {
@ -104,4 +105,29 @@ and I use TOTP token handle {stringInDoubleQuotes}",
} }
return BluebirdPromise.all(promises); 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>) { function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise<void>) {
Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () { Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
return cb() return cb()
@ -54,6 +61,7 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
declareNeedsConfiguration("regulation", createRegulationConfiguration); declareNeedsConfiguration("regulation", createRegulationConfiguration);
declareNeedsConfiguration("inactivity", createInactivityConfiguration); declareNeedsConfiguration("inactivity", createInactivityConfiguration);
declareNeedsConfiguration("single_factor", createSingleFactorConfiguration);
function registerUser(context: any, username: string) { function registerUser(context: any, username: string) {
let secret: Speakeasy.Key; let secret: Speakeasy.Key;

View File

@ -1,9 +1,72 @@
import Cucumber = require("cucumber"); import Cucumber = require("cucumber");
import seleniumWebdriver = require("selenium-webdriver"); import seleniumWebdriver = require("selenium-webdriver");
import Assert = require("assert"); 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) { Then("I get an error {number}", function (code: number) {
return this.getErrorPage(code); 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 Request = require("request-promise");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
function CustomWorld() { function CustomWorld() {
const that = this; const that = this;
this.driver = new seleniumWebdriver.Builder() this.driver = new seleniumWebdriver.Builder()
@ -38,8 +40,13 @@ function CustomWorld() {
.findElement(seleniumWebdriver.By.tagName("h1")).getText(); .findElement(seleniumWebdriver.By.tagName("h1")).getText();
}) })
.then(function (txt: string) { .then(function (txt: string) {
Assert.equal(txt, "Error " + code); try {
}); Assert.equal(txt, "Error " + code);
} catch (e) {
console.log(txt);
throw e;
}
})
}; };
this.clickOnButton = function (buttonText: string) { this.clickOnButton = function (buttonText: string) {