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
parent
3052c883a0
commit
73d5253297
|
@ -71,6 +71,9 @@ ldap:
|
|||
# values must be one of the two possible methods.
|
||||
#
|
||||
# Note: 'per_subdomain_methods' is optional.
|
||||
#
|
||||
# Note: authentication_methods is optional. If it is not set all sub-domains
|
||||
# are protected by two factors.
|
||||
authentication_methods:
|
||||
default_method: two_factor
|
||||
per_subdomain_methods:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -3,15 +3,13 @@ import BluebirdPromise = require("bluebird");
|
|||
import express = require("express");
|
||||
import objectPath = require("object-path");
|
||||
import FirstFactorValidator = require("./FirstFactorValidator");
|
||||
import AuthenticationSessionHandler = require("./AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
|
||||
import { IRequestLogger } from "./logging/IRequestLogger";
|
||||
|
||||
export function validate(req: express.Request, logger: IRequestLogger): BluebirdPromise<void> {
|
||||
return FirstFactorValidator.validate(req, logger)
|
||||
.then(function () {
|
||||
return AuthenticationSessionHandler.get(req, logger);
|
||||
})
|
||||
.then(function (authSession) {
|
||||
const authSession = AuthenticationSessionHandler.get(req, logger);
|
||||
if (!authSession.second_factor)
|
||||
return BluebirdPromise.reject("No second factor variable.");
|
||||
return BluebirdPromise.resolve();
|
||||
|
|
|
@ -3,17 +3,17 @@ import BluebirdPromise = require("bluebird");
|
|||
import express = require("express");
|
||||
import objectPath = require("object-path");
|
||||
import Exceptions = require("./Exceptions");
|
||||
import AuthenticationSessionHandler = require("./AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
|
||||
import { IRequestLogger } from "./logging/IRequestLogger";
|
||||
|
||||
export function validate(req: express.Request, logger: IRequestLogger): BluebirdPromise<void> {
|
||||
return AuthenticationSessionHandler.get(req, logger)
|
||||
.then(function (authSession) {
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
const authSession = AuthenticationSessionHandler.get(req, logger);
|
||||
|
||||
if (!authSession.userid || !authSession.first_factor)
|
||||
return BluebirdPromise.reject(
|
||||
new Exceptions.FirstFactorValidationError(
|
||||
return reject(new Exceptions.FirstFactorValidationError(
|
||||
"First factor has not been validated yet."));
|
||||
|
||||
return BluebirdPromise.resolve();
|
||||
resolve();
|
||||
});
|
||||
}
|
|
@ -9,7 +9,7 @@ import ejs = require("ejs");
|
|||
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||
import express = require("express");
|
||||
import ErrorReplies = require("./ErrorReplies");
|
||||
import AuthenticationSessionHandler = require("./AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../types/AuthenticationSession";
|
||||
import { ServerVariables } from "./ServerVariables";
|
||||
|
||||
|
@ -74,14 +74,9 @@ export function get_finish_validation(handler: IdentityValidable,
|
|||
|
||||
return checkIdentityToken(req, identityToken)
|
||||
.then(function () {
|
||||
authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
return handler.postValidationInit(req);
|
||||
})
|
||||
.then(function () {
|
||||
return AuthenticationSessionHandler.get(req, vars.logger);
|
||||
})
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
})
|
||||
.then(function () {
|
||||
return consumeToken(identityToken, handler.challenge(), vars.userDataStore);
|
||||
})
|
||||
|
@ -97,7 +92,6 @@ export function get_finish_validation(handler: IdentityValidable,
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
export function get_start_validation(handler: IdentityValidable,
|
||||
postValidationEndpoint: string,
|
||||
vars: ServerVariables)
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -6,27 +6,17 @@ import { AppConfiguration, UserConfiguration } from "./configuration/Configurati
|
|||
import { GlobalDependencies } from "../../types/Dependencies";
|
||||
import { UserDataStore } from "./storage/UserDataStore";
|
||||
import { ConfigurationParser } from "./configuration/ConfigurationParser";
|
||||
import { RestApi } from "./RestApi";
|
||||
import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder";
|
||||
import { GlobalLogger } from "./logging/GlobalLogger";
|
||||
import { RequestLogger } from "./logging/RequestLogger";
|
||||
import { ServerVariables } from "./ServerVariables";
|
||||
import { ServerVariablesInitializer } from "./ServerVariablesInitializer";
|
||||
import { Configurator } from "./web_server/Configurator";
|
||||
|
||||
import * as Express from "express";
|
||||
import * as BodyParser from "body-parser";
|
||||
import * as Path from "path";
|
||||
import * as http from "http";
|
||||
|
||||
const addRequestId = require("express-request-id")();
|
||||
|
||||
// Constants
|
||||
const TRUST_PROXY = "trust proxy";
|
||||
const X_POWERED_BY = "x-powered-by";
|
||||
const VIEWS = "views";
|
||||
const VIEW_ENGINE = "view engine";
|
||||
const PUG = "pug";
|
||||
|
||||
function clone(obj: any) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
@ -35,35 +25,12 @@ export default class Server {
|
|||
private httpServer: http.Server;
|
||||
private globalLogger: GlobalLogger;
|
||||
private requestLogger: RequestLogger;
|
||||
private serverVariables: ServerVariables;
|
||||
|
||||
constructor(deps: GlobalDependencies) {
|
||||
this.globalLogger = new GlobalLogger(deps.winston);
|
||||
this.requestLogger = new RequestLogger(deps.winston);
|
||||
}
|
||||
|
||||
private setupExpressApplication(config: AppConfiguration,
|
||||
app: Express.Application,
|
||||
deps: GlobalDependencies): void {
|
||||
const viewsDirectory = Path.resolve(__dirname, "../views");
|
||||
const publicHtmlDirectory = Path.resolve(__dirname, "../public_html");
|
||||
|
||||
const expressSessionOptions = SessionConfigurationBuilder.build(config, deps);
|
||||
|
||||
app.use(Express.static(publicHtmlDirectory));
|
||||
app.use(BodyParser.urlencoded({ extended: false }));
|
||||
app.use(BodyParser.json());
|
||||
app.use(deps.session(expressSessionOptions));
|
||||
app.use(addRequestId);
|
||||
app.disable(X_POWERED_BY);
|
||||
app.enable(TRUST_PROXY);
|
||||
|
||||
app.set(VIEWS, viewsDirectory);
|
||||
app.set(VIEW_ENGINE, PUG);
|
||||
|
||||
RestApi.setup(app, this.serverVariables);
|
||||
}
|
||||
|
||||
private displayConfigurations(userConfiguration: UserConfiguration,
|
||||
appConfiguration: AppConfiguration) {
|
||||
const displayableUserConfiguration = clone(userConfiguration);
|
||||
|
@ -94,8 +61,7 @@ export default class Server {
|
|||
const that = this;
|
||||
return ServerVariablesInitializer.initialize(config, this.requestLogger, deps)
|
||||
.then(function (vars: ServerVariables) {
|
||||
that.serverVariables = vars;
|
||||
that.setupExpressApplication(config, app, deps);
|
||||
Configurator.configure(config, app, vars, deps);
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ import { IMongoClient } from "./connectors/mongo/IMongoClient";
|
|||
|
||||
import { GlobalDependencies } from "../../types/Dependencies";
|
||||
import { ServerVariables } from "./ServerVariables";
|
||||
import { AuthenticationMethodCalculator } from "./AuthenticationMethodCalculator";
|
||||
import { MethodCalculator } from "./authentication/MethodCalculator";
|
||||
|
||||
class UserDataStoreFactory {
|
||||
static create(config: Configuration.AppConfiguration): BluebirdPromise<UserDataStore> {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -3,8 +3,9 @@ import Path = require("path");
|
|||
import Util = require("util");
|
||||
import {
|
||||
UserConfiguration, StorageConfiguration,
|
||||
NotifierConfiguration
|
||||
NotifierConfiguration, AuthenticationMethodsConfiguration
|
||||
} from "./Configuration";
|
||||
import { MethodCalculator } from "../authentication/MethodCalculator";
|
||||
|
||||
function validateSchema(configuration: UserConfiguration): string[] {
|
||||
const schema = require(Path.resolve(__dirname, "./Configuration.schema.json"));
|
||||
|
@ -34,7 +35,7 @@ function validateUnknownKeys(path: string, obj: any, knownKeys: string[]) {
|
|||
return [];
|
||||
}
|
||||
|
||||
function validateStorage(storage: any) {
|
||||
function validateStorage(storage: any): string[] {
|
||||
const ERROR = "Storage must be either 'local' or 'mongo'";
|
||||
|
||||
if (!storage)
|
||||
|
@ -53,21 +54,27 @@ function validateStorage(storage: any) {
|
|||
return [];
|
||||
}
|
||||
|
||||
function validateNotifier(notifier: NotifierConfiguration) {
|
||||
function validateNotifier(notifier: NotifierConfiguration,
|
||||
authenticationMethods: AuthenticationMethodsConfiguration): string[] {
|
||||
const ERROR = "Notifier must be either 'filesystem', 'email' or 'smtp'";
|
||||
|
||||
if (!notifier)
|
||||
return [];
|
||||
|
||||
const errors = validateUnknownKeys("notifier", notifier, ["filesystem", "email", "smtp"]);
|
||||
if (errors.length > 0)
|
||||
return errors;
|
||||
if (!MethodCalculator.isSingleFactorOnlyMode(authenticationMethods)) {
|
||||
if (Object.keys(notifier).length != 1)
|
||||
return ["A notifier needs to be declared when server is used with two-factor"];
|
||||
|
||||
if (notifier && notifier.filesystem && notifier.email && notifier.smtp)
|
||||
return [ERROR];
|
||||
|
||||
if (notifier && !notifier.filesystem && !notifier.email && !notifier.smtp)
|
||||
return [ERROR];
|
||||
}
|
||||
|
||||
const errors = validateUnknownKeys("notifier", notifier, ["filesystem", "email", "smtp"]);
|
||||
if (errors.length > 0)
|
||||
return errors;
|
||||
|
||||
return [];
|
||||
}
|
||||
|
@ -76,7 +83,8 @@ export class Validator {
|
|||
static isValid(configuration: any): string[] {
|
||||
const schemaErrors = validateSchema(configuration);
|
||||
const storageErrors = validateStorage(configuration.storage);
|
||||
const notifierErrors = validateNotifier(configuration.notifier);
|
||||
const notifierErrors = validateNotifier(configuration.notifier,
|
||||
configuration.authentication_methods);
|
||||
|
||||
return schemaErrors
|
||||
.concat(storageErrors)
|
||||
|
|
|
@ -32,7 +32,7 @@ export class LdapClient implements ILdapClient {
|
|||
clientLogger.level("trace");
|
||||
}*/
|
||||
|
||||
this.client = BluebirdPromise.promisifyAll(ldapClient) as LdapJs.ClientAsync;
|
||||
this.client = BluebirdPromise.promisifyAll(ldapClient) as any;
|
||||
}
|
||||
|
||||
bindAsync(username: string, password: string): BluebirdPromise<void> {
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
}
|
|
@ -5,7 +5,7 @@ import winston = require("winston");
|
|||
import Endpoints = require("../../../../../shared/api");
|
||||
import AuthenticationValidator = require("../../AuthenticationValidator");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import AuthenticationSession = require("../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
|
||||
import Constants = require("../../../../../shared/constants");
|
||||
import Util = require("util");
|
||||
import { ServerVariables } from "../../ServerVariables";
|
||||
|
@ -42,18 +42,18 @@ function renderFirstFactor(res: express.Response) {
|
|||
|
||||
export default function (vars: ServerVariables) {
|
||||
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
return AuthenticationSession.get(req, vars.logger)
|
||||
.then(function (authSession) {
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
if (authSession.first_factor) {
|
||||
if (authSession.second_factor)
|
||||
redirectToService(req, res);
|
||||
else
|
||||
redirectToSecondFactorPage(req, res);
|
||||
return BluebirdPromise.resolve();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
renderFirstFactor(res);
|
||||
return BluebirdPromise.resolve();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
}
|
|
@ -8,11 +8,11 @@ import { Regulator } from "../../regulation/Regulator";
|
|||
import { GroupsAndEmails } from "../../ldap/IClient";
|
||||
import Endpoint = require("../../../../../shared/api");
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
import AuthenticationSessionHandler = require("../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
|
||||
import Constants = require("../../../../../shared/constants");
|
||||
import { DomainExtractor } from "../../utils/DomainExtractor";
|
||||
import UserMessages = require("../../../../../shared/UserMessages");
|
||||
import { AuthenticationMethodCalculator } from "../../AuthenticationMethodCalculator";
|
||||
import { MethodCalculator } from "../../authentication/MethodCalculator";
|
||||
import { ServerVariables } from "../../ServerVariables";
|
||||
import { AuthenticationSession } from "../../../../types/AuthenticationSession";
|
||||
|
||||
|
@ -29,10 +29,7 @@ export default function (vars: ServerVariables) {
|
|||
return BluebirdPromise.reject(new Error("No username or password."));
|
||||
}
|
||||
vars.logger.info(req, "Starting authentication of user \"%s\"", username);
|
||||
return AuthenticationSessionHandler.get(req, vars.logger);
|
||||
})
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
return vars.regulator.regulate(username);
|
||||
})
|
||||
.then(function () {
|
||||
|
@ -40,7 +37,8 @@ export default function (vars: ServerVariables) {
|
|||
return vars.ldapAuthenticator.authenticate(username, password);
|
||||
})
|
||||
.then(function (groupsAndEmails: GroupsAndEmails) {
|
||||
vars.logger.info(req, "LDAP binding successful. Retrieved information about user are %s",
|
||||
vars.logger.info(req,
|
||||
"LDAP binding successful. Retrieved information about user are %s",
|
||||
JSON.stringify(groupsAndEmails));
|
||||
authSession.userid = username;
|
||||
authSession.first_factor = true;
|
||||
|
@ -52,18 +50,12 @@ export default function (vars: ServerVariables) {
|
|||
const emails: string[] = groupsAndEmails.emails;
|
||||
const groups: string[] = groupsAndEmails.groups;
|
||||
const redirectHost: string = DomainExtractor.fromUrl(redirectUrl);
|
||||
const authMethod =
|
||||
new AuthenticationMethodCalculator(vars.config.authentication_methods)
|
||||
.compute(redirectHost);
|
||||
vars.logger.debug(req, "Authentication method for \"%s\" is \"%s\"", redirectHost, authMethod);
|
||||
|
||||
if (!emails || emails.length <= 0) {
|
||||
const errMessage =
|
||||
"No emails found. The user should have at least one email address to reset password.";
|
||||
vars.logger.error(req, "%s", errMessage);
|
||||
return BluebirdPromise.reject(new Error(errMessage));
|
||||
}
|
||||
const authMethod = MethodCalculator.compute(
|
||||
vars.config.authentication_methods, redirectHost);
|
||||
vars.logger.debug(req, "Authentication method for \"%s\" is \"%s\"",
|
||||
redirectHost, authMethod);
|
||||
|
||||
if (emails.length > 0)
|
||||
authSession.email = emails[0];
|
||||
authSession.groups = groups;
|
||||
|
||||
|
@ -71,8 +63,11 @@ export default function (vars: ServerVariables) {
|
|||
vars.regulator.mark(username, true);
|
||||
|
||||
if (authMethod == "single_factor") {
|
||||
let newRedirectionUrl: string = redirectUrl;
|
||||
if (!newRedirectionUrl)
|
||||
newRedirectionUrl = Endpoint.LOGGED_IN;
|
||||
res.send({
|
||||
redirect: redirectUrl
|
||||
redirect: newRedirectionUrl
|
||||
});
|
||||
vars.logger.debug(req, "Redirect to '%s'", redirectUrl);
|
||||
}
|
||||
|
@ -82,7 +77,7 @@ export default function (vars: ServerVariables) {
|
|||
newRedirectUrl += "?" + Constants.REDIRECT_QUERY_PARAM + "="
|
||||
+ encodeURIComponent(redirectUrl);
|
||||
}
|
||||
vars.logger.debug(req, "Redirect to '%s'", newRedirectUrl, typeof redirectUrl);
|
||||
vars.logger.debug(req, "Redirect to '%s'", newRedirectUrl);
|
||||
res.send({
|
||||
redirect: newRedirectUrl
|
||||
});
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
import Express = require("express");
|
||||
import Endpoints = require("../../../../../shared/api");
|
||||
import FirstFactorBlocker from "../FirstFactorBlocker";
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import AuthenticationSessionHandler = require("../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
|
||||
import { ServerVariables } from "../../ServerVariables";
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
|
||||
export default function (vars: ServerVariables) {
|
||||
function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
|
||||
return AuthenticationSessionHandler.get(req, vars.logger)
|
||||
.then(function (authSession) {
|
||||
return new BluebirdPromise<void>(function (resolve, reject) {
|
||||
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
res.render("already-logged-in", {
|
||||
logout_endpoint: Endpoints.LOGOUT_GET,
|
||||
username: authSession.userid,
|
||||
redirection_url: vars.config.default_redirection_url
|
||||
});
|
||||
});
|
||||
resolve();
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError401(req, res, vars.logger));
|
||||
}
|
||||
|
||||
return FirstFactorBlocker(handler, vars.logger);
|
||||
return handler;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
|
||||
import express = require("express");
|
||||
import AuthenticationSession = require("../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
|
||||
|
||||
export default function(req: express.Request, res: express.Response) {
|
||||
const redirect_param = req.query.redirect;
|
||||
const redirect_url = redirect_param || "/";
|
||||
AuthenticationSession.reset(req);
|
||||
AuthenticationSessionHandler.reset(req);
|
||||
res.redirect(redirect_url);
|
||||
}
|
|
@ -3,7 +3,7 @@ import express = require("express");
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import objectPath = require("object-path");
|
||||
import exceptions = require("../../../Exceptions");
|
||||
import AuthenticationSessionHandler = require("../../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
|
||||
import ErrorReplies = require("../../../ErrorReplies");
|
||||
import UserMessages = require("../../../../../../shared/UserMessages");
|
||||
|
@ -16,16 +16,24 @@ export default function (vars: ServerVariables) {
|
|||
let authSession: AuthenticationSession;
|
||||
const newPassword = objectPath.get<express.Request, string>(req, "body.password");
|
||||
|
||||
return AuthenticationSessionHandler.get(req, vars.logger)
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
if (!authSession.identity_check) {
|
||||
reject(new Error("No identity check initiated"));
|
||||
return;
|
||||
}
|
||||
|
||||
vars.logger.info(req, "User %s wants to reset his/her password.",
|
||||
authSession.identity_check.userid);
|
||||
vars.logger.debug(req, "Challenge %s", authSession.identity_check.challenge);
|
||||
|
||||
if (authSession.identity_check.challenge != Constants.CHALLENGE) {
|
||||
return BluebirdPromise.reject(new Error("Bad challenge."));
|
||||
reject(new Error("Bad challenge."));
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
.then(function () {
|
||||
return vars.ldapPasswordUpdater.updatePassword(authSession.identity_check.userid, newPassword);
|
||||
})
|
||||
.then(function () {
|
||||
|
|
|
@ -1,30 +1,37 @@
|
|||
|
||||
import Express = require("express");
|
||||
import Endpoints = require("../../../../../shared/api");
|
||||
import FirstFactorBlocker = require("../FirstFactorBlocker");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import AuthenticationSessionHandler = require("../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
|
||||
import { ServerVariables } from "../../ServerVariables";
|
||||
import { MethodCalculator } from "../../authentication/MethodCalculator";
|
||||
|
||||
const TEMPLATE_NAME = "secondfactor";
|
||||
|
||||
export default function (vars: ServerVariables) {
|
||||
function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
|
||||
return AuthenticationSessionHandler.get(req, vars.logger)
|
||||
.then(function (authSession) {
|
||||
if (authSession.first_factor && authSession.second_factor) {
|
||||
function handler(req: Express.Request, res: Express.Response)
|
||||
: BluebirdPromise<void> {
|
||||
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
const isSingleFactorMode: boolean = MethodCalculator.isSingleFactorOnlyMode(
|
||||
vars.config.authentication_methods);
|
||||
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
if (isSingleFactorMode
|
||||
|| (authSession.first_factor && authSession.second_factor)) {
|
||||
res.redirect(Endpoints.LOGGED_IN);
|
||||
return BluebirdPromise.resolve();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
res.render(TEMPLATE_NAME, {
|
||||
username: authSession.userid,
|
||||
totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
|
||||
u2f_identity_start_endpoint: Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET
|
||||
totp_identity_start_endpoint:
|
||||
Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
|
||||
u2f_identity_start_endpoint:
|
||||
Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET
|
||||
});
|
||||
return BluebirdPromise.resolve();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
return FirstFactorBlocker.default(handler, vars.logger);
|
||||
return handler;
|
||||
}
|
|
@ -4,7 +4,7 @@ import objectPath = require("object-path");
|
|||
import winston = require("winston");
|
||||
import Endpoints = require("../../../../../shared/api");
|
||||
import { ServerVariables } from "../../ServerVariables";
|
||||
import AuthenticationSession = require("../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
import UserMessages = require("../../../../../shared/UserMessages");
|
||||
|
@ -13,8 +13,9 @@ import Constants = require("../../../../../shared/constants");
|
|||
|
||||
export default function (vars: ServerVariables) {
|
||||
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
return AuthenticationSession.get(req, vars.logger)
|
||||
.then(function (authSession) {
|
||||
|
||||
return new BluebirdPromise<void>(function (resolve, reject) {
|
||||
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
let redirectUrl: string;
|
||||
if (vars.config.default_redirection_url) {
|
||||
redirectUrl = vars.config.default_redirection_url;
|
||||
|
@ -23,7 +24,7 @@ export default function (vars: ServerVariables) {
|
|||
res.json({
|
||||
redirect: redirectUrl
|
||||
} as RedirectionMessage);
|
||||
return BluebirdPromise.resolve();
|
||||
return resolve();
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
||||
UserMessages.OPERATION_FAILED));
|
||||
|
|
|
@ -9,12 +9,13 @@ import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationT
|
|||
import Constants = require("../constants");
|
||||
import Endpoints = require("../../../../../../../shared/api");
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import AuthenticationSession = require("../../../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
|
||||
import UserMessages = require("../../../../../../../shared/UserMessages");
|
||||
import FirstFactorValidator = require("../../../../FirstFactorValidator");
|
||||
import { IRequestLogger } from "../../../../logging/IRequestLogger";
|
||||
import { IUserDataStore } from "../../../../storage/IUserDataStore";
|
||||
import { ITotpHandler } from "../../../../authentication/totp/ITotpHandler";
|
||||
import { TOTPSecret } from "../../../../../../types/TOTPSecret";
|
||||
|
||||
|
||||
export default class RegistrationHandler implements IdentityValidable {
|
||||
|
@ -35,20 +36,21 @@ export default class RegistrationHandler implements IdentityValidable {
|
|||
}
|
||||
|
||||
private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
|
||||
return AuthenticationSession.get(req, this.logger)
|
||||
.then(function (authSession) {
|
||||
const that = this;
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
const authSession = AuthenticationSessionHandler.get(req, that.logger);
|
||||
const userid = authSession.userid;
|
||||
const email = authSession.email;
|
||||
|
||||
if (!(userid && email)) {
|
||||
return BluebirdPromise.reject(new Error("User ID or email is missing."));
|
||||
return reject(new Error("User ID or email is missing"));
|
||||
}
|
||||
|
||||
const identity = {
|
||||
email: email,
|
||||
userid: userid
|
||||
};
|
||||
return BluebirdPromise.resolve(identity);
|
||||
return resolve(identity);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -70,26 +72,31 @@ export default class RegistrationHandler implements IdentityValidable {
|
|||
|
||||
postValidationResponse(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const that = this;
|
||||
return AuthenticationSession.get(req, this.logger)
|
||||
.then(function (authSession) {
|
||||
const userid = authSession.identity_check.userid;
|
||||
let secret: TOTPSecret;
|
||||
let userId: string;
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
const authSession = AuthenticationSessionHandler.get(req, that.logger);
|
||||
const challenge = authSession.identity_check.challenge;
|
||||
userId = authSession.identity_check.userid;
|
||||
|
||||
if (challenge != Constants.CHALLENGE || !userid) {
|
||||
return BluebirdPromise.reject(new Error("Bad challenge."));
|
||||
if (challenge != Constants.CHALLENGE || !userId) {
|
||||
return reject(new Error("Bad challenge."));
|
||||
}
|
||||
const secret = that.totp.generate();
|
||||
that.logger.debug(req, "Save the TOTP secret in DB.");
|
||||
return that.userDataStore.saveTOTPSecret(userid, secret)
|
||||
resolve();
|
||||
})
|
||||
.then(function () {
|
||||
AuthenticationSession.reset(req);
|
||||
secret = that.totp.generate();
|
||||
that.logger.debug(req, "Save the TOTP secret in DB");
|
||||
return that.userDataStore.saveTOTPSecret(userId, secret);
|
||||
})
|
||||
.then(function () {
|
||||
AuthenticationSessionHandler.reset(req);
|
||||
|
||||
res.render(Constants.TEMPLATE_NAME, {
|
||||
base32_secret: secret.base32,
|
||||
otpauth_url: secret.otpauth_url,
|
||||
login_endpoint: Endpoints.FIRST_FACTOR_GET
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError200(req, res, that.logger, UserMessages.OPERATION_FAILED));
|
||||
}
|
||||
|
|
|
@ -4,11 +4,10 @@ import objectPath = require("object-path");
|
|||
import express = require("express");
|
||||
import { TOTPSecretDocument } from "../../../../storage/TOTPSecretDocument";
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import FirstFactorBlocker from "../../../FirstFactorBlocker";
|
||||
import Endpoints = require("../../../../../../../shared/api");
|
||||
import redirect from "../../redirect";
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import AuthenticationSessionHandler = require("../../../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
|
||||
import UserMessages = require("../../../../../../../shared/UserMessages");
|
||||
import { ServerVariables } from "../../../../ServerVariables";
|
||||
|
@ -20,10 +19,12 @@ export default function (vars: ServerVariables) {
|
|||
let authSession: AuthenticationSession;
|
||||
const token = req.body.token;
|
||||
|
||||
return AuthenticationSessionHandler.get(req, vars.logger)
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
vars.logger.info(req, "Initiate TOTP validation for user \"%s\".", authSession.userid);
|
||||
resolve();
|
||||
})
|
||||
.then(function () {
|
||||
return vars.userDataStore.retrieveTOTPSecret(authSession.userid);
|
||||
})
|
||||
.then(function (doc: TOTPSecretDocument) {
|
||||
|
@ -38,5 +39,5 @@ export default function (vars: ServerVariables) {
|
|||
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
||||
UserMessages.OPERATION_FAILED));
|
||||
}
|
||||
return FirstFactorBlocker(handler, vars.logger);
|
||||
return handler;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { IdentityValidable } from "../../../../IdentityCheckMiddleware";
|
|||
import { Identity } from "../../../../../../types/Identity";
|
||||
import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationTemplate";
|
||||
import FirstFactorValidator = require("../../../../FirstFactorValidator");
|
||||
import AuthenticationSession = require("../../../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
|
||||
import { IRequestLogger } from "../../../../logging/IRequestLogger";
|
||||
|
||||
const CHALLENGE = "u2f-register";
|
||||
|
@ -28,20 +28,21 @@ export default class RegistrationHandler implements IdentityValidable {
|
|||
}
|
||||
|
||||
private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
|
||||
return AuthenticationSession.get(req, this.logger)
|
||||
.then(function (authSession) {
|
||||
const that = this;
|
||||
return new BluebirdPromise(function(resolve, reject) {
|
||||
const authSession = AuthenticationSessionHandler.get(req, that.logger);
|
||||
const userid = authSession.userid;
|
||||
const email = authSession.email;
|
||||
|
||||
if (!(userid && email)) {
|
||||
return BluebirdPromise.reject(new Error("User ID or email is missing"));
|
||||
return reject(new Error("User ID or email is missing"));
|
||||
}
|
||||
|
||||
const identity = {
|
||||
email: email,
|
||||
userid: userid
|
||||
};
|
||||
return BluebirdPromise.resolve(identity);
|
||||
return resolve(identity);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
|
||||
import { UserDataStore } from "../../../../storage/UserDataStore";
|
||||
|
||||
import objectPath = require("object-path");
|
||||
import u2f_common = require("../U2FCommon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
import U2f = require("u2f");
|
||||
import { U2FRegistration } from "../../../../../../types/U2FRegistration";
|
||||
import FirstFactorBlocker from "../../../FirstFactorBlocker";
|
||||
import redirect from "../../redirect";
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import { ServerVariables } from "../../../../ServerVariables";
|
||||
import AuthenticationSessionHandler = require("../../../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
|
||||
import UserMessages = require("../../../../../../../shared/UserMessages");
|
||||
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
|
||||
|
||||
|
@ -22,25 +20,24 @@ export default function (vars: ServerVariables) {
|
|||
const appid = u2f_common.extract_app_id(req);
|
||||
const registrationResponse: U2f.RegistrationData = req.body;
|
||||
|
||||
return AuthenticationSessionHandler.get(req, vars.logger)
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
const registrationRequest = authSession.register_request;
|
||||
|
||||
if (!registrationRequest) {
|
||||
return BluebirdPromise.reject(new Error("No registration request"));
|
||||
return reject(new Error("No registration request"));
|
||||
}
|
||||
|
||||
if (!authSession.identity_check
|
||||
|| authSession.identity_check.challenge != "u2f-register") {
|
||||
return BluebirdPromise.reject(new Error("Bad challenge for registration request"));
|
||||
return reject(new Error("Bad challenge for registration request"));
|
||||
}
|
||||
|
||||
vars.logger.info(req, "Finishing registration");
|
||||
vars.logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
|
||||
vars.logger.debug(req, "RegistrationResponse = %s", JSON.stringify(registrationResponse));
|
||||
|
||||
return BluebirdPromise.resolve(vars.u2f.checkRegistration(registrationRequest, registrationResponse));
|
||||
return resolve(vars.u2f.checkRegistration(registrationRequest, registrationResponse));
|
||||
})
|
||||
.then(function (u2fResult: U2f.RegistrationResult | U2f.Error): BluebirdPromise<void> {
|
||||
if (objectPath.has(u2fResult, "errorCode"))
|
||||
|
@ -63,6 +60,5 @@ export default function (vars: ServerVariables) {
|
|||
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
||||
UserMessages.OPERATION_FAILED));
|
||||
}
|
||||
|
||||
return FirstFactorBlocker(handler, vars.logger);
|
||||
return handler;
|
||||
}
|
||||
|
|
|
@ -6,9 +6,8 @@ import u2f_common = require("../U2FCommon");
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
import U2f = require("u2f");
|
||||
import FirstFactorBlocker from "../../../FirstFactorBlocker";
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import AuthenticationSessionHandler = require("../../../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
|
||||
import UserMessages = require("../../../../../../../shared/UserMessages");
|
||||
import { ServerVariables } from "../../../../ServerVariables";
|
||||
|
@ -18,20 +17,17 @@ export default function (vars: ServerVariables) {
|
|||
let authSession: AuthenticationSession;
|
||||
const appid: string = u2f_common.extract_app_id(req);
|
||||
|
||||
return AuthenticationSessionHandler.get(req, vars.logger)
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
if (!authSession.identity_check
|
||||
|| authSession.identity_check.challenge != "u2f-register") {
|
||||
res.status(403);
|
||||
res.send();
|
||||
return BluebirdPromise.reject(new Error("Bad challenge."));
|
||||
return reject(new Error("Bad challenge."));
|
||||
}
|
||||
|
||||
vars.logger.info(req, "Starting registration for appId '%s'", appid);
|
||||
|
||||
return BluebirdPromise.resolve(vars.u2f.request(appid));
|
||||
return resolve(vars.u2f.request(appid));
|
||||
})
|
||||
.then(function (registrationRequest: U2f.Request) {
|
||||
vars.logger.debug(req, "RegistrationRequest = %s", JSON.stringify(registrationRequest));
|
||||
|
@ -43,5 +39,5 @@ export default function (vars: ServerVariables) {
|
|||
UserMessages.OPERATION_FAILED));
|
||||
}
|
||||
|
||||
return FirstFactorBlocker(handler, vars.logger);
|
||||
return handler;
|
||||
}
|
|
@ -8,11 +8,10 @@ import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocu
|
|||
import { Winston } from "../../../../../../types/Dependencies";
|
||||
import U2f = require("u2f");
|
||||
import exceptions = require("../../../../Exceptions");
|
||||
import FirstFactorBlocker from "../../../FirstFactorBlocker";
|
||||
import redirect from "../../redirect";
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import { ServerVariables } from "../../../../ServerVariables";
|
||||
import AuthenticationSessionHandler = require("../../../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
|
||||
import UserMessages = require("../../../../../../../shared/UserMessages");
|
||||
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
|
||||
|
||||
|
@ -21,14 +20,16 @@ export default function (vars: ServerVariables) {
|
|||
let authSession: AuthenticationSession;
|
||||
const appId = u2f_common.extract_app_id(req);
|
||||
|
||||
return AuthenticationSessionHandler.get(req, vars.logger)
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
if (!authSession.sign_request) {
|
||||
const err = new Error("No sign request");
|
||||
ErrorReplies.replyWithError401(req, res, vars.logger)(err);
|
||||
return BluebirdPromise.reject(err);
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
.then(function () {
|
||||
const userid = authSession.userid;
|
||||
return vars.userDataStore.retrieveU2FRegistration(userid, appId);
|
||||
})
|
||||
|
@ -50,6 +51,6 @@ export default function (vars: ServerVariables) {
|
|||
UserMessages.OPERATION_FAILED));
|
||||
}
|
||||
|
||||
return FirstFactorBlocker(handler, vars.logger);
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,8 @@ import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocu
|
|||
import { Winston } from "../../../../../../types/Dependencies";
|
||||
import exceptions = require("../../../../Exceptions");
|
||||
import { SignMessage } from "../../../../../../../shared/SignMessage";
|
||||
import FirstFactorBlocker from "../../../FirstFactorBlocker";
|
||||
import ErrorReplies = require("../../../../ErrorReplies");
|
||||
import AuthenticationSessionHandler = require("../../../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler";
|
||||
import UserMessages = require("../../../../../../../shared/UserMessages");
|
||||
import { ServerVariables } from "../../../../ServerVariables";
|
||||
import { AuthenticationSession } from "../../../../../../types/AuthenticationSession";
|
||||
|
@ -21,9 +20,11 @@ export default function (vars: ServerVariables) {
|
|||
let authSession: AuthenticationSession;
|
||||
const appId = u2f_common.extract_app_id(req);
|
||||
|
||||
return AuthenticationSessionHandler.get(req, vars.logger)
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
resolve();
|
||||
})
|
||||
.then(function () {
|
||||
return vars.userDataStore.retrieveU2FRegistration(authSession.userid, appId);
|
||||
})
|
||||
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<SignMessage> {
|
||||
|
@ -51,6 +52,5 @@ export default function (vars: ServerVariables) {
|
|||
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
||||
UserMessages.OPERATION_FAILED));
|
||||
}
|
||||
|
||||
return FirstFactorBlocker(handler, vars.logger);
|
||||
return handler;
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ import winston = require("winston");
|
|||
import AuthenticationValidator = require("../../AuthenticationValidator");
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
import { AppConfiguration } from "../../configuration/Configuration";
|
||||
import AuthenticationSessionHandler = require("../../AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../../../types/AuthenticationSession";
|
||||
import Constants = require("../../../../../shared/constants");
|
||||
import Util = require("util");
|
||||
import { DomainExtractor } from "../../utils/DomainExtractor";
|
||||
import { ServerVariables } from "../../ServerVariables";
|
||||
import { AuthenticationMethodCalculator } from "../../AuthenticationMethodCalculator";
|
||||
import { MethodCalculator } from "../../authentication/MethodCalculator";
|
||||
import { IRequestLogger } from "../../logging/IRequestLogger";
|
||||
|
||||
const FIRST_FACTOR_NOT_VALIDATED_MESSAGE = "First factor not yet validated";
|
||||
|
@ -50,49 +50,50 @@ function verify_inactivity(req: express.Request,
|
|||
|
||||
function verify_filter(req: express.Request, res: express.Response,
|
||||
vars: ServerVariables): BluebirdPromise<void> {
|
||||
let _authSession: AuthenticationSession;
|
||||
let authSession: AuthenticationSession;
|
||||
let username: string;
|
||||
let groups: string[];
|
||||
|
||||
return AuthenticationSessionHandler.get(req, vars.logger)
|
||||
.then(function (authSession) {
|
||||
_authSession = authSession;
|
||||
username = _authSession.userid;
|
||||
groups = _authSession.groups;
|
||||
return new BluebirdPromise(function (resolve, reject) {
|
||||
authSession = AuthenticationSessionHandler.get(req, vars.logger);
|
||||
username = authSession.userid;
|
||||
groups = authSession.groups;
|
||||
|
||||
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] +
|
||||
req.headers["x-original-uri"]));
|
||||
|
||||
if (!_authSession.userid)
|
||||
return BluebirdPromise.reject(
|
||||
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE));
|
||||
if (!authSession.userid) {
|
||||
reject(new exceptions.AccessDeniedError(
|
||||
Util.format("%s: %s.", FIRST_FACTOR_NOT_VALIDATED_MESSAGE, "userid is missing")));
|
||||
return;
|
||||
}
|
||||
|
||||
const host = objectPath.get<express.Request, string>(req, "headers.host");
|
||||
const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri");
|
||||
|
||||
const domain = DomainExtractor.fromHostHeader(host);
|
||||
const authenticationMethod =
|
||||
new AuthenticationMethodCalculator(vars.config.authentication_methods)
|
||||
.compute(domain);
|
||||
MethodCalculator.compute(vars.config.authentication_methods, domain);
|
||||
vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, path,
|
||||
username, groups.join(","));
|
||||
|
||||
if (!_authSession.first_factor)
|
||||
return BluebirdPromise.reject(
|
||||
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE));
|
||||
if (!authSession.first_factor)
|
||||
return reject(new exceptions.AccessDeniedError(
|
||||
Util.format("%s: %s.", FIRST_FACTOR_NOT_VALIDATED_MESSAGE, "first factor is false")));
|
||||
|
||||
if (authenticationMethod == "two_factor" && !_authSession.second_factor)
|
||||
return BluebirdPromise.reject(
|
||||
new exceptions.AccessDeniedError(SECOND_FACTOR_NOT_VALIDATED_MESSAGE));
|
||||
if (authenticationMethod == "two_factor" && !authSession.second_factor)
|
||||
return reject(new exceptions.AccessDeniedError(
|
||||
Util.format("%s: %s.", SECOND_FACTOR_NOT_VALIDATED_MESSAGE, "second factor is false")));
|
||||
|
||||
const isAllowed = vars.accessController.isAccessAllowed(domain, path, username, groups);
|
||||
if (!isAllowed) return BluebirdPromise.reject(
|
||||
if (!isAllowed) return reject(
|
||||
new exceptions.DomainAccessDenied(Util.format("User '%s' does not have access to '%s'",
|
||||
username, domain)));
|
||||
return BluebirdPromise.resolve();
|
||||
|
||||
resolve();
|
||||
})
|
||||
.then(function () {
|
||||
return verify_inactivity(req, _authSession,
|
||||
return verify_inactivity(req, authSession,
|
||||
vars.config, vars.logger);
|
||||
})
|
||||
.then(function () {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
});
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
import sinon = require("sinon");
|
||||
import IdentityValidator = require("../src/lib/IdentityCheckMiddleware");
|
||||
import AuthenticationSessionHandler = require("../src/lib/AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../src/lib/AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../types/AuthenticationSession";
|
||||
import { UserDataStore } from "../src/lib/storage/UserDataStore";
|
||||
import exceptions = require("../src/lib/Exceptions");
|
||||
|
@ -155,14 +155,10 @@ describe("test identity check process", function () {
|
|||
req.query.identity_token = "token";
|
||||
|
||||
req.session = {};
|
||||
let authSession: AuthenticationSession;
|
||||
const authSession: AuthenticationSession = AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
const callback = IdentityValidator.get_finish_validation(identityValidable, vars);
|
||||
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
return callback(req as any, res as any, undefined);
|
||||
})
|
||||
return callback(req as any, res as any, undefined)
|
||||
.then(function () { return BluebirdPromise.reject("Should fail"); })
|
||||
.catch(function () {
|
||||
Assert.equal(authSession.identity_check.userid, "user");
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -28,7 +28,7 @@ describe("test validator", function () {
|
|||
"data.regulation should have required property 'max_retries'",
|
||||
"data.session should have required property 'secret'",
|
||||
"Storage must be either 'local' or 'mongo'",
|
||||
"Notifier must be either 'filesystem', 'email' or 'smtp'"
|
||||
"A notifier needs to be declared when server is used with two-factor"
|
||||
]);
|
||||
|
||||
Assert.deepStrictEqual(Validator.isValid({
|
||||
|
@ -54,7 +54,7 @@ describe("test validator", function () {
|
|||
}
|
||||
}), [
|
||||
"data.storage has unknown key 'abc'",
|
||||
"data.notifier has unknown key 'abcd'"
|
||||
"Notifier must be either 'filesystem', 'email' or 'smtp'"
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -89,4 +89,93 @@ describe("test validator", function () {
|
|||
}
|
||||
}), []);
|
||||
});
|
||||
|
||||
it("should return false when notifier is not defined while there is at least \
|
||||
one second factor enabled sub-domain", function () {
|
||||
const options1 = {
|
||||
ldap: {
|
||||
base_dn: "dc=example,dc=com",
|
||||
password: "password",
|
||||
url: "ldap://ldap",
|
||||
user: "user"
|
||||
},
|
||||
authentication_methods: {
|
||||
default_method: "two_factor"
|
||||
},
|
||||
notifier: {},
|
||||
regulation: {
|
||||
ban_time: 120,
|
||||
find_time: 30,
|
||||
max_retries: 3
|
||||
},
|
||||
session: {
|
||||
secret: "unsecure_secret"
|
||||
},
|
||||
storage: {
|
||||
local: {
|
||||
path: "/var/lib/authelia"
|
||||
}
|
||||
}
|
||||
};
|
||||
const options2 = {
|
||||
ldap: {
|
||||
base_dn: "dc=example,dc=com",
|
||||
password: "password",
|
||||
url: "ldap://ldap",
|
||||
user: "user"
|
||||
},
|
||||
authentication_methods: {
|
||||
default_method: "two_factor"
|
||||
},
|
||||
notifier: {
|
||||
email: {
|
||||
username: "user@gmail.com",
|
||||
password: "pass",
|
||||
sender: "admin@example.com",
|
||||
service: "gmail"
|
||||
}
|
||||
},
|
||||
regulation: {
|
||||
ban_time: 120,
|
||||
find_time: 30,
|
||||
max_retries: 3
|
||||
},
|
||||
session: {
|
||||
secret: "unsecure_secret"
|
||||
},
|
||||
storage: {
|
||||
local: {
|
||||
path: "/var/lib/authelia"
|
||||
}
|
||||
}
|
||||
};
|
||||
const options3 = {
|
||||
ldap: {
|
||||
base_dn: "dc=example,dc=com",
|
||||
password: "password",
|
||||
url: "ldap://ldap",
|
||||
user: "user"
|
||||
},
|
||||
authentication_methods: {
|
||||
default_method: "single_factor"
|
||||
},
|
||||
notifier: {},
|
||||
regulation: {
|
||||
ban_time: 120,
|
||||
find_time: 30,
|
||||
max_retries: 3
|
||||
},
|
||||
session: {
|
||||
secret: "unsecure_secret"
|
||||
},
|
||||
storage: {
|
||||
local: {
|
||||
path: "/var/lib/authelia"
|
||||
}
|
||||
}
|
||||
};
|
||||
Assert.deepStrictEqual(Validator.isValid(options1), ["A notifier needs to be declared when server is used with two-factor"]);
|
||||
Assert.deepStrictEqual(Validator.isValid(options2), []);
|
||||
Assert.deepStrictEqual(Validator.isValid(options3), []);
|
||||
});
|
||||
});
|
|
@ -1,26 +1,38 @@
|
|||
import { IRequestLogger } from "../../src/lib/logging/IRequestLogger";
|
||||
import Sinon = require("sinon");
|
||||
import { RequestLogger } from "../../src/lib/logging/RequestLogger";
|
||||
import Winston = require("winston");
|
||||
import Express = require("express");
|
||||
|
||||
export class RequestLoggerStub implements IRequestLogger {
|
||||
infoStub: Sinon.SinonStub;
|
||||
debugStub: Sinon.SinonStub;
|
||||
errorStub: Sinon.SinonStub;
|
||||
private requestLogger: RequestLogger;
|
||||
|
||||
constructor() {
|
||||
constructor(enableLogging?: boolean) {
|
||||
this.infoStub = Sinon.stub();
|
||||
this.debugStub = Sinon.stub();
|
||||
this.errorStub = Sinon.stub();
|
||||
if (enableLogging)
|
||||
this.requestLogger = new RequestLogger(Winston);
|
||||
}
|
||||
|
||||
info(req: Express.Request, message: string, ...args: any[]): void {
|
||||
return this.infoStub(req, message, ...args);
|
||||
if (this.requestLogger)
|
||||
this.requestLogger.info(req, message, ...args);
|
||||
this.infoStub(req, message, ...args);
|
||||
}
|
||||
|
||||
debug(req: Express.Request, message: string, ...args: any[]): void {
|
||||
return this.debugStub(req, message, ...args);
|
||||
if (this.requestLogger)
|
||||
this.requestLogger.info(req, message, ...args);
|
||||
this.debugStub(req, message, ...args);
|
||||
}
|
||||
|
||||
error(req: Express.Request, message: string, ...args: any[]): void {
|
||||
return this.errorStub(req, message, ...args);
|
||||
if (this.requestLogger)
|
||||
this.requestLogger.info(req, message, ...args);
|
||||
this.errorStub(req, message, ...args);
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ export interface ServerVariablesMock {
|
|||
}
|
||||
|
||||
export class ServerVariablesMockBuilder {
|
||||
static build(): { variables: ServerVariables, mocks: ServerVariablesMock} {
|
||||
static build(enableLogging?: boolean): { variables: ServerVariables, mocks: ServerVariablesMock} {
|
||||
const mocks: ServerVariablesMock = {
|
||||
accessController: new AccessControllerStub(),
|
||||
config: {
|
||||
|
@ -62,7 +62,7 @@ export class ServerVariablesMockBuilder {
|
|||
ldapAuthenticator: new AuthenticatorStub(),
|
||||
ldapEmailsRetriever: new EmailsRetrieverStub(),
|
||||
ldapPasswordUpdater: new PasswordUpdaterStub(),
|
||||
logger: new RequestLoggerStub(),
|
||||
logger: new RequestLoggerStub(enableLogging),
|
||||
notifier: new NotifierStub(),
|
||||
regulator: new RegulatorStub(),
|
||||
totpHandler: new TotpHandlerStub(),
|
||||
|
|
|
@ -53,7 +53,11 @@ export function RequestMock(): RequestMock {
|
|||
return {
|
||||
app: {
|
||||
get: sinon.stub()
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
"x-forwarded-for": "127.0.0.1"
|
||||
},
|
||||
session: {}
|
||||
};
|
||||
}
|
||||
export function ResponseMock(): ResponseMock {
|
||||
|
|
|
@ -4,7 +4,7 @@ import BluebirdPromise = require("bluebird");
|
|||
import Assert = require("assert");
|
||||
import FirstFactorPost = require("../../../src/lib/routes/firstfactor/post");
|
||||
import exceptions = require("../../../src/lib/Exceptions");
|
||||
import AuthenticationSessionHandler = require("../../../src/lib/AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../src/lib/AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../../types/AuthenticationSession";
|
||||
import Endpoints = require("../../../../shared/api");
|
||||
import AuthenticationRegulatorMock = require("../../mocks/AuthenticationRegulator");
|
||||
|
@ -20,6 +20,7 @@ describe("test the first factor validation route", function () {
|
|||
let groups: string[];
|
||||
let vars: ServerVariables;
|
||||
let mocks: ServerVariablesMock;
|
||||
let authSession: AuthenticationSession;
|
||||
|
||||
beforeEach(function () {
|
||||
emails = ["test_ok@example.com"];
|
||||
|
@ -48,6 +49,7 @@ describe("test the first factor validation route", function () {
|
|||
};
|
||||
|
||||
res = ExpressMock.ResponseMock();
|
||||
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
});
|
||||
|
||||
it("should reply with 204 if success", function () {
|
||||
|
@ -56,12 +58,7 @@ describe("test the first factor validation route", function () {
|
|||
emails: emails,
|
||||
groups: groups
|
||||
}));
|
||||
let authSession: AuthenticationSession;
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
return FirstFactorPost.default(vars)(req as any, res as any);
|
||||
})
|
||||
return FirstFactorPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
Assert.equal("username", authSession.userid);
|
||||
Assert(res.send.calledOnce);
|
||||
|
@ -76,18 +73,13 @@ describe("test the first factor validation route", function () {
|
|||
|
||||
it("should set first email address as user session variable", function () {
|
||||
const emails = ["test_ok@example.com"];
|
||||
let authSession: AuthenticationSession;
|
||||
mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password")
|
||||
.returns(BluebirdPromise.resolve({
|
||||
emails: emails,
|
||||
groups: groups
|
||||
}));
|
||||
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
return FirstFactorPost.default(vars)(req as any, res as any);
|
||||
})
|
||||
return FirstFactorPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
Assert.equal("test_ok@example.com", authSession.email);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
import PasswordResetFormPost = require("../../../src/lib/routes/password-reset/form/post");
|
||||
import { PasswordUpdater } from "../../../src/lib/ldap/PasswordUpdater";
|
||||
import AuthenticationSessionHandler = require("../../../src/lib/AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../src/lib/AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../../types/AuthenticationSession";
|
||||
import { UserDataStore } from "../../../src/lib/storage/UserDataStore";
|
||||
import Sinon = require("sinon");
|
||||
import Assert = require("assert");
|
||||
|
@ -15,6 +16,7 @@ describe("test reset password route", function () {
|
|||
let res: ExpressMock.ResponseMock;
|
||||
let vars: ServerVariables;
|
||||
let mocks: ServerVariablesMock;
|
||||
let authSession: AuthenticationSession;
|
||||
|
||||
beforeEach(function () {
|
||||
req = {
|
||||
|
@ -53,14 +55,12 @@ describe("test reset password route", function () {
|
|||
};
|
||||
|
||||
res = ExpressMock.ResponseMock();
|
||||
AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
authSession.userid = "user";
|
||||
authSession.email = "user@example.com";
|
||||
authSession.first_factor = true;
|
||||
authSession.second_factor = false;
|
||||
});
|
||||
});
|
||||
|
||||
describe("test reset password post", () => {
|
||||
it("should update the password and reset auth_session for reauthentication", function () {
|
||||
|
@ -69,14 +69,11 @@ describe("test reset password route", function () {
|
|||
|
||||
mocks.ldapPasswordUpdater.updatePasswordStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.identity_check = {
|
||||
userid: "user",
|
||||
challenge: "reset-password"
|
||||
};
|
||||
return PasswordResetFormPost.default(vars)(req as any, res as any);
|
||||
})
|
||||
return PasswordResetFormPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
}).then(function (_authSession) {
|
||||
|
@ -88,14 +85,11 @@ describe("test reset password route", function () {
|
|||
});
|
||||
|
||||
it("should fail if identity_challenge does not exist", function () {
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.identity_check = {
|
||||
userid: "user",
|
||||
challenge: undefined
|
||||
};
|
||||
return PasswordResetFormPost.default(vars)(req as any, res as any);
|
||||
})
|
||||
return PasswordResetFormPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
Assert.equal(res.status.getCall(0).args[0], 200);
|
||||
Assert.deepEqual(res.send.getCall(0).args[0], {
|
||||
|
@ -111,14 +105,12 @@ describe("test reset password route", function () {
|
|||
mocks.ldapPasswordUpdater.updatePasswordStub
|
||||
.returns(BluebirdPromise.reject("Internal error with LDAP"));
|
||||
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.identity_check = {
|
||||
challenge: "reset-password",
|
||||
userid: "user"
|
||||
};
|
||||
return PasswordResetFormPost.default(vars)(req as any, res as any);
|
||||
}).then(function () {
|
||||
return PasswordResetFormPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
Assert.equal(res.status.getCall(0).args[0], 200);
|
||||
Assert.deepEqual(res.send.getCall(0).args[0], {
|
||||
error: "An error occurred during password reset. Your password has not been changed."
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2,7 +2,6 @@ import Sinon = require("sinon");
|
|||
import winston = require("winston");
|
||||
import RegistrationHandler from "../../../../../src/lib/routes/secondfactor/totp/identity/RegistrationHandler";
|
||||
import { Identity } from "../../../../../types/Identity";
|
||||
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
|
||||
import { UserDataStore } from "../../../../../src/lib/storage/UserDataStore";
|
||||
import assert = require("assert");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Sinon = require("sinon");
|
||||
import assert = require("assert");
|
||||
import winston = require("winston");
|
||||
|
||||
import exceptions = require("../../../../../src/lib/Exceptions");
|
||||
import AuthenticationSessionHandler = require("../../../../../src/lib/AuthenticationSession");
|
||||
import Assert = require("assert");
|
||||
import Exceptions = require("../../../../../src/lib/Exceptions");
|
||||
import { AuthenticationSessionHandler } from "../../../../../src/lib/AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
|
||||
import SignPost = require("../../../../../src/lib/routes/secondfactor/totp/sign/post");
|
||||
import { ServerVariables } from "../../../../../src/lib/ServerVariables";
|
||||
|
@ -27,9 +25,7 @@ describe("test totp route", function () {
|
|||
mocks = s.mocks;
|
||||
const app_get = Sinon.stub();
|
||||
req = {
|
||||
app: {
|
||||
get: Sinon.stub().returns({ logger: winston })
|
||||
},
|
||||
app: {},
|
||||
body: {
|
||||
token: "abc"
|
||||
},
|
||||
|
@ -47,21 +43,18 @@ describe("test totp route", function () {
|
|||
}
|
||||
};
|
||||
mocks.userDataStore.retrieveTOTPSecretStub.returns(BluebirdPromise.resolve(doc));
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (_authSession) {
|
||||
authSession = _authSession;
|
||||
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
authSession.userid = "user";
|
||||
authSession.first_factor = true;
|
||||
authSession.second_factor = false;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should send status code 200 when totp is valid", function () {
|
||||
mocks.totpHandler.validateStub.returns(true);
|
||||
return SignPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
assert.equal(true, authSession.second_factor);
|
||||
Assert.equal(true, authSession.second_factor);
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
|
@ -70,24 +63,13 @@ describe("test totp route", function () {
|
|||
mocks.totpHandler.validateStub.returns(false);
|
||||
return SignPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
assert.equal(false, authSession.second_factor);
|
||||
assert.equal(res.status.getCall(0).args[0], 200);
|
||||
assert.deepEqual(res.send.getCall(0).args[0], {
|
||||
Assert.equal(false, authSession.second_factor);
|
||||
Assert.equal(res.status.getCall(0).args[0], 200);
|
||||
Assert.deepEqual(res.send.getCall(0).args[0], {
|
||||
error: "Operation failed."
|
||||
});
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should send status code 401 when session has not been initiated", function () {
|
||||
mocks.totpHandler.validateStub.returns(true);
|
||||
req.session = {};
|
||||
return SignPost.default(vars)(req as any, res as any)
|
||||
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
|
||||
.catch(function () {
|
||||
assert.equal(401, res.status.getCall(0).args[0]);
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
import sinon = require("sinon");
|
||||
import winston = require("winston");
|
||||
import assert = require("assert");
|
||||
import Sinon = require("sinon");
|
||||
import Assert = require("assert");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
import { Identity } from "../../../../../types/Identity";
|
||||
import RegistrationHandler from "../../../../../src/lib/routes/secondfactor/u2f/identity/RegistrationHandler";
|
||||
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
|
||||
|
||||
import ExpressMock = require("../../../../mocks/express");
|
||||
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
|
||||
import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../mocks/ServerVariablesMockBuilder";
|
||||
import { ServerVariables } from "../../../../../src/lib/ServerVariables";
|
||||
|
||||
describe("test register handler", function () {
|
||||
describe("test U2F register handler", function () {
|
||||
let req: ExpressMock.RequestMock;
|
||||
let res: ExpressMock.ResponseMock;
|
||||
let mocks: ServerVariablesMock;
|
||||
|
@ -46,9 +43,9 @@ describe("test register handler", function () {
|
|||
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.resolve({}));
|
||||
|
||||
res = ExpressMock.ResponseMock();
|
||||
res.send = sinon.spy();
|
||||
res.json = sinon.spy();
|
||||
res.status = sinon.spy();
|
||||
res.send = Sinon.spy();
|
||||
res.json = Sinon.spy();
|
||||
res.status = Sinon.spy();
|
||||
});
|
||||
|
||||
describe("test u2f registration check", test_registration_check);
|
||||
|
@ -63,31 +60,37 @@ describe("test register handler", function () {
|
|||
});
|
||||
});
|
||||
|
||||
it("should fail if userid is missing", function (done) {
|
||||
it("should fail if userid is missing", function () {
|
||||
req.session.auth.first_factor = false;
|
||||
req.session.auth.userid = undefined;
|
||||
|
||||
new RegistrationHandler(vars.logger).preValidationInit(req as any)
|
||||
.catch(function (err: Error) {
|
||||
done();
|
||||
return new RegistrationHandler(vars.logger).preValidationInit(req as any)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject(new Error("should not be here"));
|
||||
},
|
||||
function (err: Error) {
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail if email is missing", function (done) {
|
||||
it("should fail if email is missing", function () {
|
||||
req.session.auth.first_factor = false;
|
||||
req.session.auth.email = undefined;
|
||||
|
||||
new RegistrationHandler(vars.logger).preValidationInit(req as any)
|
||||
.catch(function (err: Error) {
|
||||
done();
|
||||
return new RegistrationHandler(vars.logger).preValidationInit(req as any)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject(new Error("should not be here"));
|
||||
},
|
||||
function (err: Error) {
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should succeed if first factor passed, userid and email are provided", function (done) {
|
||||
new RegistrationHandler(vars.logger).preValidationInit(req as any)
|
||||
.then(function (identity: Identity) {
|
||||
done();
|
||||
});
|
||||
it("should succeed if first factor passed, userid and email are provided", function () {
|
||||
req.session.auth.first_factor = true;
|
||||
req.session.auth.email = "admin@example.com";
|
||||
req.session.auth.userid = "user";
|
||||
return new RegistrationHandler(vars.logger).preValidationInit(req as any);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -3,7 +3,8 @@ import sinon = require("sinon");
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import assert = require("assert");
|
||||
import U2FRegisterPost = require("../../../../../src/lib/routes/secondfactor/u2f/register/post");
|
||||
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../../../src/lib/AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
|
||||
import ExpressMock = require("../../../../mocks/express");
|
||||
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
|
||||
import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../mocks/ServerVariablesMockBuilder";
|
||||
|
@ -15,6 +16,7 @@ describe("test u2f routes: register", function () {
|
|||
let res: ExpressMock.ResponseMock;
|
||||
let mocks: ServerVariablesMock;
|
||||
let vars: ServerVariables;
|
||||
let authSession: AuthenticationSession;
|
||||
|
||||
beforeEach(function () {
|
||||
req = ExpressMock.RequestMock();
|
||||
|
@ -48,6 +50,8 @@ describe("test u2f routes: register", function () {
|
|||
res.send = sinon.spy();
|
||||
res.json = sinon.spy();
|
||||
res.status = sinon.spy();
|
||||
|
||||
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
});
|
||||
|
||||
describe("test registration", test_registration);
|
||||
|
@ -62,20 +66,14 @@ describe("test u2f routes: register", function () {
|
|||
};
|
||||
mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve(expectedStatus));
|
||||
|
||||
return AuthenticationSession.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.register_request = {
|
||||
appId: "app",
|
||||
challenge: "challenge",
|
||||
keyHandle: "key",
|
||||
version: "U2F_V2"
|
||||
};
|
||||
return U2FRegisterPost.default(vars)(req as any, res as any);
|
||||
})
|
||||
return U2FRegisterPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
return AuthenticationSession.get(req as any, vars.logger);
|
||||
})
|
||||
.then(function (authSession) {
|
||||
assert.equal("user", mocks.userDataStore.saveU2FRegistrationStub.getCall(0).args[0]);
|
||||
assert.equal(authSession.identity_check, undefined);
|
||||
});
|
||||
|
@ -84,8 +82,6 @@ describe("test u2f routes: register", function () {
|
|||
it("should return error message on finishRegistration error", function () {
|
||||
mocks.u2f.checkRegistrationStub.returns({ errorCode: 500 });
|
||||
|
||||
return AuthenticationSession.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.register_request = {
|
||||
appId: "app",
|
||||
challenge: "challenge",
|
||||
|
@ -93,8 +89,7 @@ describe("test u2f routes: register", function () {
|
|||
version: "U2F_V2"
|
||||
};
|
||||
|
||||
return U2FRegisterPost.default(vars)(req as any, res as any);
|
||||
})
|
||||
return U2FRegisterPost.default(vars)(req as any, res as any)
|
||||
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
|
||||
.catch(function () {
|
||||
assert.equal(200, res.status.getCall(0).args[0]);
|
||||
|
@ -107,11 +102,8 @@ describe("test u2f routes: register", function () {
|
|||
|
||||
it("should return error message when register_request is not provided", function () {
|
||||
mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve());
|
||||
return AuthenticationSession.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.register_request = undefined;
|
||||
return U2FRegisterPost.default(vars)(req as any, res as any);
|
||||
})
|
||||
return U2FRegisterPost.default(vars)(req as any, res as any)
|
||||
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
|
||||
.catch(function () {
|
||||
assert.equal(200, res.status.getCall(0).args[0]);
|
||||
|
@ -124,11 +116,8 @@ describe("test u2f routes: register", function () {
|
|||
|
||||
it("should return error message when no auth request has been initiated", function () {
|
||||
mocks.u2f.checkRegistrationStub.returns(BluebirdPromise.resolve());
|
||||
return AuthenticationSession.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.register_request = undefined;
|
||||
return U2FRegisterPost.default(vars)(req as any, res as any);
|
||||
})
|
||||
return U2FRegisterPost.default(vars)(req as any, res as any)
|
||||
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
|
||||
.catch(function () {
|
||||
assert.equal(200, res.status.getCall(0).args[0]);
|
||||
|
@ -140,11 +129,8 @@ describe("test u2f routes: register", function () {
|
|||
});
|
||||
|
||||
it("should return error message when identity has not been verified", function () {
|
||||
return AuthenticationSession.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.identity_check = undefined;
|
||||
return U2FRegisterPost.default(vars)(req as any, res as any);
|
||||
})
|
||||
return U2FRegisterPost.default(vars)(req as any, res as any)
|
||||
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
|
||||
.catch(function () {
|
||||
assert.equal(200, res.status.getCall(0).args[0]);
|
||||
|
|
|
@ -3,7 +3,6 @@ import sinon = require("sinon");
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import Assert = require("assert");
|
||||
import U2FRegisterRequestGet = require("../../../../../src/lib/routes/secondfactor/u2f/register_request/get");
|
||||
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
|
||||
import ExpressMock = require("../../../../mocks/express");
|
||||
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
|
||||
import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../../../mocks/ServerVariablesMockBuilder";
|
||||
|
|
|
@ -3,7 +3,6 @@ import sinon = require("sinon");
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import Assert = require("assert");
|
||||
import U2FSignPost = require("../../../../../src/lib/routes/secondfactor/u2f/sign/post");
|
||||
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
|
||||
import { ServerVariables } from "../../../../../src/lib/ServerVariables";
|
||||
import winston = require("winston");
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ import sinon = require("sinon");
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import assert = require("assert");
|
||||
import U2FSignRequestGet = require("../../../../../src/lib/routes/secondfactor/u2f/sign_request/get");
|
||||
import AuthenticationSessionHandler = require("../../../../../src/lib/AuthenticationSession");
|
||||
import { AuthenticationSession } from "../../../../../types/AuthenticationSession";
|
||||
import ExpressMock = require("../../../../mocks/express");
|
||||
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
|
||||
import U2FMock = require("../../../../mocks/u2f");
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
|
||||
import Assert = require("assert");
|
||||
import VerifyGet = require("../../../src/lib/routes/verify/get");
|
||||
import AuthenticationSessionHandler = require("../../../src/lib/AuthenticationSession");
|
||||
import { AuthenticationSessionHandler } from "../../../src/lib/AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../../types/AuthenticationSession";
|
||||
import { AuthenticationMethodCalculator } from "../../../src/lib/AuthenticationMethodCalculator";
|
||||
import { AuthenticationMethodsConfiguration } from "../../../src/lib/configuration/Configuration";
|
||||
import Sinon = require("sinon");
|
||||
import winston = require("winston");
|
||||
|
@ -18,37 +17,31 @@ describe("test /verify endpoint", function () {
|
|||
let res: ExpressMock.ResponseMock;
|
||||
let mocks: ServerVariablesMock;
|
||||
let vars: ServerVariables;
|
||||
let authSession: AuthenticationSession;
|
||||
|
||||
beforeEach(function () {
|
||||
req = ExpressMock.RequestMock();
|
||||
res = ExpressMock.ResponseMock();
|
||||
req.session = {};
|
||||
req.query = {
|
||||
redirect: "http://redirect.url"
|
||||
};
|
||||
req.app = {
|
||||
get: Sinon.stub().returns({ logger: winston })
|
||||
};
|
||||
AuthenticationSessionHandler.reset(req as any);
|
||||
req.headers = {};
|
||||
req.headers.host = "secret.example.com";
|
||||
const s = ServerVariablesMockBuilder.build();
|
||||
mocks = s.mocks;
|
||||
vars = s.variables;
|
||||
vars.config.authentication_methods.default_method = "two_factor";
|
||||
|
||||
authSession = AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
});
|
||||
|
||||
it("should be already authenticated", function () {
|
||||
req.session = {};
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
AuthenticationSessionHandler.reset(req as any);
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.first_factor = true;
|
||||
authSession.second_factor = true;
|
||||
authSession.userid = "myuser";
|
||||
authSession.groups = ["mygroup", "othergroup"];
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
.then(function () {
|
||||
Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "myuser");
|
||||
Sinon.assert.calledWithExactly(res.setHeader, "Remote-Groups", "mygroup,othergroup");
|
||||
|
@ -57,11 +50,7 @@ describe("test /verify endpoint", function () {
|
|||
});
|
||||
|
||||
function test_session(_authSession: AuthenticationSession, status_code: number) {
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession = _authSession;
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert.equal(status_code, res.status.getCall(0).args[0]);
|
||||
});
|
||||
|
@ -134,8 +123,6 @@ describe("test /verify endpoint", function () {
|
|||
});
|
||||
|
||||
it("should not be authenticated when domain is not allowed for user", function () {
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.first_factor = true;
|
||||
authSession.second_factor = true;
|
||||
authSession.userid = "myuser";
|
||||
|
@ -153,7 +140,6 @@ describe("test /verify endpoint", function () {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("given user tries to access a basic auth endpoint", function () {
|
||||
beforeEach(function () {
|
||||
|
@ -168,12 +154,9 @@ describe("test /verify endpoint", function () {
|
|||
|
||||
it("should be authenticated when first factor is validated and second factor is not", function () {
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.first_factor = true;
|
||||
authSession.userid = "user1";
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert(res.status.calledWith(204));
|
||||
Assert(res.send.calledOnce);
|
||||
|
@ -182,11 +165,8 @@ describe("test /verify endpoint", function () {
|
|||
|
||||
it("should be rejected with 401 when first factor is not validated", function () {
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.first_factor = false;
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
.then(function () {
|
||||
Assert(res.status.calledWith(401));
|
||||
});
|
||||
|
@ -199,15 +179,12 @@ describe("test /verify endpoint", function () {
|
|||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
const currentTime = new Date().getTime() - 1000;
|
||||
AuthenticationSessionHandler.reset(req as any);
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.first_factor = true;
|
||||
authSession.second_factor = true;
|
||||
authSession.userid = "myuser";
|
||||
authSession.groups = ["mygroup", "othergroup"];
|
||||
authSession.last_activity_datetime = currentTime;
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
.then(function () {
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
})
|
||||
|
@ -221,15 +198,12 @@ describe("test /verify endpoint", function () {
|
|||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
const currentTime = new Date().getTime() - 1000;
|
||||
AuthenticationSessionHandler.reset(req as any);
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger)
|
||||
.then(function (authSession) {
|
||||
authSession.first_factor = true;
|
||||
authSession.second_factor = true;
|
||||
authSession.userid = "myuser";
|
||||
authSession.groups = ["mygroup", "othergroup"];
|
||||
authSession.last_activity_datetime = currentTime;
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any)
|
||||
.then(function () {
|
||||
return AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
})
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -1,16 +1,36 @@
|
|||
Feature: Non authenticated users have no access to certain pages
|
||||
|
||||
Scenario Outline: Anonymous user has no access to protected pages
|
||||
When I visit "<url>"
|
||||
Then I get an error <error code>
|
||||
Scenario: Anonymous user has no access to protected pages
|
||||
Then I get the following status code when requesting:
|
||||
| url | code | method |
|
||||
| https://auth.test.local:8080/secondfactor | 401 | GET |
|
||||
| https://auth.test.local:8080/secondfactor/u2f/identity/start | 401 | GET |
|
||||
| https://auth.test.local:8080/secondfactor/u2f/identity/finish | 401 | GET |
|
||||
| https://auth.test.local:8080/secondfactor/totp/identity/start | 401 | GET |
|
||||
| https://auth.test.local:8080/secondfactor/totp/identity/finish | 401 | GET |
|
||||
| https://auth.test.local:8080/api/totp | 401 | POST |
|
||||
| https://auth.test.local:8080/api/u2f/sign_request | 401 | GET |
|
||||
| https://auth.test.local:8080/api/u2f/sign | 401 | POST |
|
||||
| https://auth.test.local:8080/api/u2f/register_request | 401 | GET |
|
||||
| https://auth.test.local:8080/api/u2f/register | 401 | POST |
|
||||
|
||||
Examples:
|
||||
| url | error code |
|
||||
| https://auth.test.local:8080/secondfactor | 401 |
|
||||
| https://auth.test.local:8080/verify | 401 |
|
||||
| https://auth.test.local:8080/secondfactor/u2f/identity/start | 401 |
|
||||
| https://auth.test.local:8080/secondfactor/u2f/identity/finish | 401 |
|
||||
| https://auth.test.local:8080/secondfactor/totp/identity/start | 401 |
|
||||
| https://auth.test.local:8080/secondfactor/totp/identity/finish | 401 |
|
||||
| https://auth.test.local:8080/password-reset/identity/start | 401 |
|
||||
| https://auth.test.local:8080/password-reset/identity/finish | 401 |
|
||||
|
||||
@needs-single_factor-config
|
||||
@need-registered-user-john
|
||||
Scenario: User does not have acces to second factor related endpoints when in single factor mode
|
||||
Given I post "https://auth.test.local:8080/api/firstfactor" with body:
|
||||
| key | value |
|
||||
| username | john |
|
||||
| password | password |
|
||||
Then I get the following status code when requesting:
|
||||
| url | code | method |
|
||||
| https://auth.test.local:8080/secondfactor | 401 | GET |
|
||||
| https://auth.test.local:8080/secondfactor/u2f/identity/start | 401 | GET |
|
||||
| https://auth.test.local:8080/secondfactor/u2f/identity/finish | 401 | GET |
|
||||
| https://auth.test.local:8080/secondfactor/totp/identity/start | 401 | GET |
|
||||
| https://auth.test.local:8080/secondfactor/totp/identity/finish | 401 | GET |
|
||||
| https://auth.test.local:8080/api/totp | 401 | POST |
|
||||
| https://auth.test.local:8080/api/u2f/sign_request | 401 | GET |
|
||||
| https://auth.test.local:8080/api/u2f/sign | 401 | POST |
|
||||
| https://auth.test.local:8080/api/u2f/register_request | 401 | GET |
|
||||
| https://auth.test.local:8080/api/u2f/register | 401 | POST |
|
|
@ -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/"
|
|
@ -5,6 +5,7 @@ import Fs = require("fs");
|
|||
import Speakeasy = require("speakeasy");
|
||||
import CustomWorld = require("../support/world");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Request = require("request-promise");
|
||||
|
||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||
When(/^I visit "(https:\/\/[a-zA-Z0-9:%&._\/=?-]+)"$/, function (link: string) {
|
||||
|
@ -104,4 +105,29 @@ and I use TOTP token handle {stringInDoubleQuotes}",
|
|||
}
|
||||
return BluebirdPromise.all(promises);
|
||||
});
|
||||
|
||||
function endpointReplyWith(context: any, link: string, method: string,
|
||||
returnCode: number) {
|
||||
return Request(link, {
|
||||
method: method
|
||||
})
|
||||
.then(function (response: string) {
|
||||
Assert(response.indexOf("Error " + returnCode) >= 0);
|
||||
return BluebirdPromise.resolve();
|
||||
}, function (response: any) {
|
||||
Assert.equal(response.statusCode, returnCode);
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
Then("the following endpoints reply with:", function (dataTable: Cucumber.TableDefinition) {
|
||||
const promises = [];
|
||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
||||
const url: string = (dataTable.hashes() as any)[i].url;
|
||||
const method: string = (dataTable.hashes() as any)[i].method;
|
||||
const code: number = (dataTable.hashes() as any)[i].code;
|
||||
promises.push(endpointReplyWith(this, url, method, code));
|
||||
}
|
||||
return BluebirdPromise.all(promises);
|
||||
});
|
||||
});
|
|
@ -36,6 +36,13 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
|
|||
");
|
||||
}
|
||||
|
||||
function createSingleFactorConfiguration(): BluebirdPromise<void> {
|
||||
return exec("\
|
||||
cat config.template.yml | \
|
||||
sed 's/default_method: two_factor/default_method: single_factor/' > config.test.yml \
|
||||
");
|
||||
}
|
||||
|
||||
function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise<void>) {
|
||||
Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
|
||||
return cb()
|
||||
|
@ -54,6 +61,7 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
|
|||
|
||||
declareNeedsConfiguration("regulation", createRegulationConfiguration);
|
||||
declareNeedsConfiguration("inactivity", createInactivityConfiguration);
|
||||
declareNeedsConfiguration("single_factor", createSingleFactorConfiguration);
|
||||
|
||||
function registerUser(context: any, username: string) {
|
||||
let secret: Speakeasy.Key;
|
||||
|
|
|
@ -1,9 +1,72 @@
|
|||
import Cucumber = require("cucumber");
|
||||
import seleniumWebdriver = require("selenium-webdriver");
|
||||
import Assert = require("assert");
|
||||
import Request = require("request-promise");
|
||||
import Bluebird = require("bluebird");
|
||||
|
||||
Cucumber.defineSupportCode(function ({ Given, When, Then, Before, After }) {
|
||||
Before(function () {
|
||||
this.jar = Request.jar();
|
||||
})
|
||||
|
||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||
Then("I get an error {number}", function (code: number) {
|
||||
return this.getErrorPage(code);
|
||||
});
|
||||
|
||||
When("I request {stringInDoubleQuotes} with method {stringInDoubleQuotes}",
|
||||
function (url: string, method: string) {
|
||||
const that = this;
|
||||
|
||||
})
|
||||
|
||||
function requestAndExpectStatusCode(ctx: any, url: string, method: string,
|
||||
expectedStatusCode: number) {
|
||||
return Request(url, {
|
||||
method: method,
|
||||
jar: ctx.jar
|
||||
})
|
||||
.then(function (body: string) {
|
||||
return Bluebird.resolve(parseInt(body.match(/Error ([0-9]{3})/)[1]));
|
||||
}, function (response: any) {
|
||||
return Bluebird.resolve(response.statusCode)
|
||||
})
|
||||
.then(function (statusCode: number) {
|
||||
try {
|
||||
Assert.equal(statusCode, expectedStatusCode);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(url);
|
||||
console.log("%s (actual) != %s (expected)", statusCode,
|
||||
expectedStatusCode);
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Then("I get the following status code when requesting:",
|
||||
function (dataTable: Cucumber.TableDefinition) {
|
||||
const promises: Bluebird<void>[] = [];
|
||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
||||
const url: string = (dataTable.hashes() as any)[i].url;
|
||||
const method: string = (dataTable.hashes() as any)[i].method;
|
||||
const code: number = (dataTable.hashes() as any)[i].code;
|
||||
promises.push(requestAndExpectStatusCode(this, url, method, code));
|
||||
}
|
||||
return Bluebird.all(promises);
|
||||
})
|
||||
|
||||
When("I post {stringInDoubleQuotes} with body:", function (url: string,
|
||||
dataTable: Cucumber.TableDefinition) {
|
||||
const body = {};
|
||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
||||
const key = (dataTable.hashes() as any)[i].key;
|
||||
const value = (dataTable.hashes() as any)[i].value;
|
||||
body[key] = value;
|
||||
}
|
||||
return Request.post(url, {
|
||||
body: body,
|
||||
jar: this.jar,
|
||||
json: true
|
||||
});
|
||||
})
|
||||
});
|
|
@ -7,6 +7,8 @@ import Assert = require("assert");
|
|||
import Request = require("request-promise");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
|
||||
|
||||
function CustomWorld() {
|
||||
const that = this;
|
||||
this.driver = new seleniumWebdriver.Builder()
|
||||
|
@ -38,8 +40,13 @@ function CustomWorld() {
|
|||
.findElement(seleniumWebdriver.By.tagName("h1")).getText();
|
||||
})
|
||||
.then(function (txt: string) {
|
||||
try {
|
||||
Assert.equal(txt, "Error " + code);
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(txt);
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
this.clickOnButton = function (buttonText: string) {
|
||||
|
|
Loading…
Reference in New Issue