Merge pull request #89 from clems4ever/redis-connection-issue-logs

Make failing connection to redis more clear in the logs
pull/92/head
Clément Michaud 2017-09-22 21:05:33 +02:00 committed by GitHub
commit ae5b647d23
44 changed files with 800 additions and 586 deletions

View File

@ -112,7 +112,7 @@ module.exports = function (grunt) {
} }
}, },
client: { client: {
files: ['src/client/**/*.ts', 'test/client/**/*.ts'], files: ['src/client/**/*.ts'],
tasks: ['build-dev'], tasks: ['build-dev'],
options: { options: {
interrupt: true, interrupt: true,
@ -120,7 +120,7 @@ module.exports = function (grunt) {
} }
}, },
server: { server: {
files: ['src/server/**/*.ts', 'test/server/**/*.ts'], files: ['src/server/**/*.ts'],
tasks: ['build-dev', 'run:docker-restart', 'run:make-dev-views' ], tasks: ['build-dev', 'run:docker-restart', 'run:make-dev-views' ],
options: { options: {
interrupt: true, interrupt: true,

View File

@ -2,5 +2,7 @@ version: '2'
services: services:
mongo: mongo:
image: mongo:3.4 image: mongo:3.4
ports:
- "27017:27017"
networks: networks:
- example-network - example-network

View File

@ -37,6 +37,7 @@
"object-path": "^0.11.3", "object-path": "^0.11.3",
"pug": "^2.0.0-rc.2", "pug": "^2.0.0-rc.2",
"randomstring": "^1.1.5", "randomstring": "^1.1.5",
"redis": "^2.8.0",
"speakeasy": "^2.0.0", "speakeasy": "^2.0.0",
"u2f": "^0.1.2", "u2f": "^0.1.2",
"winston": "^2.3.1", "winston": "^2.3.1",
@ -63,6 +64,7 @@
"@types/proxyquire": "^1.3.27", "@types/proxyquire": "^1.3.27",
"@types/query-string": "^4.3.1", "@types/query-string": "^4.3.1",
"@types/randomstring": "^1.1.5", "@types/randomstring": "^1.1.5",
"@types/redis": "^2.6.0",
"@types/request": "0.0.46", "@types/request": "0.0.46",
"@types/selenium-webdriver": "^3.0.4", "@types/selenium-webdriver": "^3.0.4",
"@types/sinon": "^2.2.1", "@types/sinon": "^2.2.1",

View File

@ -11,52 +11,52 @@ import { QueryParametersRetriever } from "../QueryParametersRetriever";
export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) { export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) {
const notifierTotp = new Notifier(".notification-totp", $); const notifierTotp = new Notifier(".notification-totp", $);
const notifierU2f = new Notifier(".notification-u2f", $); const notifierU2f = new Notifier(".notification-u2f", $);
function onAuthenticationSuccess(data: any) { function onAuthenticationSuccess(data: any) {
const redirectUrl = QueryParametersRetriever.get("redirect"); const redirectUrl = QueryParametersRetriever.get("redirect");
if (redirectUrl) if (redirectUrl)
window.location.href = redirectUrl; window.location.href = redirectUrl;
else else
window.location.href = Endpoints.FIRST_FACTOR_GET; window.location.href = Endpoints.FIRST_FACTOR_GET;
} }
function onSecondFactorTotpSuccess(data: any) { function onSecondFactorTotpSuccess(data: any) {
onAuthenticationSuccess(data); onAuthenticationSuccess(data);
} }
function onSecondFactorTotpFailure(err: Error) { function onSecondFactorTotpFailure(err: Error) {
notifierTotp.error("Problem with TOTP validation."); notifierTotp.error("Problem with TOTP validation.");
} }
function onU2fAuthenticationSuccess(data: any) { function onU2fAuthenticationSuccess(data: any) {
onAuthenticationSuccess(data); onAuthenticationSuccess(data);
} }
function onU2fAuthenticationFailure() { function onU2fAuthenticationFailure() {
notifierU2f.error("Problem with U2F validation. Did you register before authenticating?"); notifierU2f.error("Problem with U2F validation. Did you register before authenticating?");
} }
function onTOTPFormSubmitted(): boolean { function onTOTPFormSubmitted(): boolean {
const token = $(Constants.TOTP_TOKEN_SELECTOR).val(); const token = $(Constants.TOTP_TOKEN_SELECTOR).val();
jslogger.debug("TOTP token is %s", token); jslogger.debug("TOTP token is %s", token);
TOTPValidator.validate(token, $) TOTPValidator.validate(token, $)
.then(onSecondFactorTotpSuccess) .then(onSecondFactorTotpSuccess)
.catch(onSecondFactorTotpFailure); .catch(onSecondFactorTotpFailure);
return false; return false;
} }
function onU2FFormSubmitted(): boolean { function onU2FFormSubmitted(): boolean {
jslogger.debug("Start U2F authentication"); jslogger.debug("Start U2F authentication");
U2FValidator.validate($, notifierU2f, U2fApi) U2FValidator.validate($, notifierU2f, U2fApi)
.then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure); .then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure);
return false; return false;
} }
$(window.document).ready(function () { $(window.document).ready(function () {
$(Constants.TOTP_FORM_SELECTOR).on("submit", onTOTPFormSubmitted); $(Constants.TOTP_FORM_SELECTOR).on("submit", onTOTPFormSubmitted);
$(Constants.U2F_FORM_SELECTOR).on("submit", onU2FFormSubmitted); $(Constants.U2F_FORM_SELECTOR).on("submit", onU2FFormSubmitted);
}); });
} }

View File

@ -2,39 +2,54 @@
import express = require("express"); import express = require("express");
import U2f = require("u2f"); import U2f = require("u2f");
import { ServerVariablesHandler } from "./ServerVariablesHandler";
import BluebirdPromise = require("bluebird");
export interface AuthenticationSession { export interface AuthenticationSession {
userid: string;
first_factor: boolean;
second_factor: boolean;
identity_check?: {
challenge: string;
userid: string; userid: string;
first_factor: boolean; };
second_factor: boolean; register_request?: U2f.Request;
identity_check?: { sign_request?: U2f.Request;
challenge: string; email: string;
userid: string; groups: string[];
}; redirect?: string;
register_request?: U2f.Request;
sign_request?: U2f.Request;
email: string;
groups: string[];
redirect?: string;
} }
const INITIAL_AUTHENTICATION_SESSION: AuthenticationSession = {
first_factor: false,
second_factor: false,
userid: undefined,
email: undefined,
groups: [],
register_request: undefined,
sign_request: undefined,
identity_check: undefined,
redirect: undefined
};
export function reset(req: express.Request): void { export function reset(req: express.Request): void {
const authSession: AuthenticationSession = { const logger = ServerVariablesHandler.getLogger(req.app);
first_factor: false, logger.debug("Authentication session %s is being reset.", req.sessionID);
second_factor: false, req.session.auth = Object.assign({}, INITIAL_AUTHENTICATION_SESSION, {});
userid: undefined,
email: undefined,
groups: [],
register_request: undefined,
sign_request: undefined,
identity_check: undefined,
redirect: undefined
};
req.session.auth = authSession;
} }
export function get(req: express.Request): AuthenticationSession { export function get(req: express.Request): BluebirdPromise<AuthenticationSession> {
if (!req.session.auth) const logger = ServerVariablesHandler.getLogger(req.app);
reset(req); if (!req.session) {
return req.session.auth; const errorMsg = "Something is wrong with session cookies. Please check Redis is running and Authelia can contact it.";
logger.error(errorMsg);
return BluebirdPromise.reject(new Error(errorMsg));
}
if (!req.session.auth) {
logger.debug("Authentication session %s was undefined. Resetting.", req.sessionID);
reset(req);
}
return BluebirdPromise.resolve(req.session.auth);
} }

View File

@ -9,10 +9,11 @@ import AuthenticationSession = require("./AuthenticationSession");
export function validate(req: express.Request): BluebirdPromise<void> { export function validate(req: express.Request): BluebirdPromise<void> {
return FirstFactorValidator.validate(req) return FirstFactorValidator.validate(req)
.then(function () { .then(function () {
const authSession = AuthenticationSession.get(req); return AuthenticationSession.get(req);
})
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
if (!authSession.second_factor) if (!authSession.second_factor)
return BluebirdPromise.reject("No second factor variable"); return BluebirdPromise.reject("No second factor variable.");
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
} }

View File

@ -6,9 +6,11 @@ import Exceptions = require("./Exceptions");
import AuthenticationSession = require("./AuthenticationSession"); import AuthenticationSession = require("./AuthenticationSession");
export function validate(req: express.Request): BluebirdPromise<void> { export function validate(req: express.Request): BluebirdPromise<void> {
const authSession = AuthenticationSession.get(req); return AuthenticationSession.get(req)
if (!authSession.userid || !authSession.first_factor) .then(function (authSession: AuthenticationSession.AuthenticationSession) {
return BluebirdPromise.reject(new Exceptions.FirstFactorValidationError("First factor has not been validated yet.")); if (!authSession.userid || !authSession.first_factor)
return BluebirdPromise.reject(new Exceptions.FirstFactorValidationError("First factor has not been validated yet."));
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
});
} }

View File

@ -10,7 +10,7 @@ import { IUserDataStore } from "./storage/IUserDataStore";
import { Winston } from "../../types/Dependencies"; import { Winston } from "../../types/Dependencies";
import express = require("express"); import express = require("express");
import ErrorReplies = require("./ErrorReplies"); import ErrorReplies = require("./ErrorReplies");
import { ServerVariablesHandler } from "./ServerVariablesHandler"; import { ServerVariablesHandler } from "./ServerVariablesHandler";
import AuthenticationSession = require("./AuthenticationSession"); import AuthenticationSession = require("./AuthenticationSession");
import Identity = require("../../types/Identity"); import Identity = require("../../types/Identity");
@ -66,7 +66,7 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
const authSession = AuthenticationSession.get(req); let authSession: AuthenticationSession.AuthenticationSession;
const identityToken = objectPath.get<express.Request, string>(req, "query.identity_token"); const identityToken = objectPath.get<express.Request, string>(req, "query.identity_token");
logger.info("GET identity_check: identity token provided is %s", identityToken); logger.info("GET identity_check: identity token provided is %s", identityToken);
@ -74,6 +74,12 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
.then(function () { .then(function () {
return handler.postValidationInit(req); return handler.postValidationInit(req);
}) })
.then(function () {
return AuthenticationSession.get(req);
})
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
})
.then(function () { .then(function () {
return consumeToken(identityToken, handler.challenge(), userDataStore, logger); return consumeToken(identityToken, handler.challenge(), userDataStore, logger);
}) })

View File

@ -1,5 +1,5 @@
import express = require("express"); import Express = require("express");
import { UserDataStore } from "./storage/UserDataStore"; import { UserDataStore } from "./storage/UserDataStore";
import { Winston } from "../../types/Dependencies"; import { Winston } from "../../types/Dependencies";
@ -23,22 +23,29 @@ import U2FSignRequestGet = require("./routes/secondfactor/u2f/sign_request/get")
import U2FRegisterPost = require("./routes/secondfactor/u2f/register/post"); import U2FRegisterPost = require("./routes/secondfactor/u2f/register/post");
import U2FRegisterRequestGet = require("./routes/secondfactor/u2f/register_request/get"); import U2FRegisterRequestGet = require("./routes/secondfactor/u2f/register_request/get");
import ResetPasswordFormPost = require("./routes/password-reset/form/post"); import ResetPasswordFormPost = require("./routes/password-reset/form/post");
import ResetPasswordRequestPost = require("./routes/password-reset/request/get"); import ResetPasswordRequestPost = require("./routes/password-reset/request/get");
import Error401Get = require("./routes/error/401/get"); import Error401Get = require("./routes/error/401/get");
import Error403Get = require("./routes/error/403/get"); import Error403Get = require("./routes/error/403/get");
import Error404Get = require("./routes/error/404/get"); import Error404Get = require("./routes/error/404/get");
import { ServerVariablesHandler } from "./ServerVariablesHandler";
import Endpoints = require("../endpoints"); import Endpoints = require("../endpoints");
function withLog(fn: (req: Express.Request, res: Express.Response) => void) {
return function(req: Express.Request, res: Express.Response) {
const logger = ServerVariablesHandler.getLogger(req.app);
logger.info("Request %s handled on %s", req.method, req.originalUrl);
fn(req, res);
};
}
export class RestApi { export class RestApi {
static setup(app: express.Application): void { static setup(app: Express.Application): void {
app.get(Endpoints.FIRST_FACTOR_GET, FirstFactorGet.default); app.get(Endpoints.FIRST_FACTOR_GET, withLog(FirstFactorGet.default));
app.get(Endpoints.SECOND_FACTOR_GET, SecondFactorGet.default); app.get(Endpoints.SECOND_FACTOR_GET, withLog(SecondFactorGet.default));
app.get(Endpoints.LOGOUT_GET, LogoutGet.default); app.get(Endpoints.LOGOUT_GET, withLog(LogoutGet.default));
IdentityCheckMiddleware.register(app, Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET, IdentityCheckMiddleware.register(app, Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET, new TOTPRegistrationIdentityHandler()); Endpoints.SECOND_FACTOR_TOTP_IDENTITY_FINISH_GET, new TOTPRegistrationIdentityHandler());
@ -49,25 +56,21 @@ export class RestApi {
IdentityCheckMiddleware.register(app, Endpoints.RESET_PASSWORD_IDENTITY_START_GET, IdentityCheckMiddleware.register(app, Endpoints.RESET_PASSWORD_IDENTITY_START_GET,
Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET, new ResetPasswordIdentityHandler()); Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET, new ResetPasswordIdentityHandler());
app.get(Endpoints.RESET_PASSWORD_REQUEST_GET, ResetPasswordRequestPost.default); app.get(Endpoints.RESET_PASSWORD_REQUEST_GET, withLog(ResetPasswordRequestPost.default));
app.post(Endpoints.RESET_PASSWORD_FORM_POST, ResetPasswordFormPost.default); app.post(Endpoints.RESET_PASSWORD_FORM_POST, withLog(ResetPasswordFormPost.default));
app.get(Endpoints.VERIFY_GET, VerifyGet.default); app.get(Endpoints.VERIFY_GET, withLog(VerifyGet.default));
app.post(Endpoints.FIRST_FACTOR_POST, withLog(FirstFactorPost.default));
app.post(Endpoints.SECOND_FACTOR_TOTP_POST, withLog(TOTPSignGet.default));
app.post(Endpoints.FIRST_FACTOR_POST, FirstFactorPost.default); app.get(Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, withLog(U2FSignRequestGet.default));
app.post(Endpoints.SECOND_FACTOR_U2F_SIGN_POST, withLog(U2FSignPost.default));
app.get(Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, withLog(U2FRegisterRequestGet.default));
app.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, withLog(U2FRegisterPost.default));
app.post(Endpoints.SECOND_FACTOR_TOTP_POST, TOTPSignGet.default); app.get(Endpoints.ERROR_401_GET, withLog(Error401Get.default));
app.get(Endpoints.ERROR_403_GET, withLog(Error403Get.default));
app.get(Endpoints.ERROR_404_GET, withLog(Error404Get.default));
app.get(Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, U2FSignRequestGet.default);
app.post(Endpoints.SECOND_FACTOR_U2F_SIGN_POST, U2FSignPost.default);
app.get(Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, U2FRegisterRequestGet.default);
app.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, U2FRegisterPost.default);
app.get(Endpoints.ERROR_401_GET, Error401Get.default);
app.get(Endpoints.ERROR_403_GET, Error403Get.default);
app.get(Endpoints.ERROR_404_GET, Error404Get.default);
} }
} }

View File

@ -2,6 +2,7 @@
import ExpressSession = require("express-session"); import ExpressSession = require("express-session");
import { AppConfiguration } from "./Configuration"; import { AppConfiguration } from "./Configuration";
import { GlobalDependencies } from "../../../types/Dependencies"; import { GlobalDependencies } from "../../../types/Dependencies";
import Redis = require("redis");
export class SessionConfigurationBuilder { export class SessionConfigurationBuilder {
@ -21,9 +22,16 @@ export class SessionConfigurationBuilder {
let redisOptions; let redisOptions;
if (configuration.session.redis.host if (configuration.session.redis.host
&& configuration.session.redis.port) { && configuration.session.redis.port) {
redisOptions = { const client = Redis.createClient({
host: configuration.session.redis.host, host: configuration.session.redis.host,
port: configuration.session.redis.port port: configuration.session.redis.port
});
client.on("error", function (err: Error) {
console.error("Redis error:", err);
});
redisOptions = {
client: client,
logErrors: true
}; };
} }

View File

@ -5,21 +5,22 @@ import FirstFactorValidator = require("../FirstFactorValidator");
import Exceptions = require("../Exceptions"); import Exceptions = require("../Exceptions");
import ErrorReplies = require("../ErrorReplies"); import ErrorReplies = require("../ErrorReplies");
import objectPath = require("object-path"); import objectPath = require("object-path");
import { ServerVariablesHandler } from "../ServerVariablesHandler"; import { ServerVariablesHandler } from "../ServerVariablesHandler";
import AuthenticationSession = require("../AuthenticationSession"); import AuthenticationSession = require("../AuthenticationSession");
type Handler = (req: express.Request, res: express.Response) => BluebirdPromise<void>; type Handler = (req: express.Request, res: express.Response) => BluebirdPromise<void>;
export default function (callback: Handler): Handler { export default function (callback: Handler): Handler {
return function (req: express.Request, res: express.Response): BluebirdPromise<void> { return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
const authSession = AuthenticationSession.get(req); return AuthenticationSession.get(req)
logger.debug("AuthSession is %s", JSON.stringify(authSession)); .then(function (authSession) {
return FirstFactorValidator.validate(req) return FirstFactorValidator.validate(req);
.then(function () { })
return callback(req, res); .then(function () {
}) return callback(req, res);
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(res, logger)); })
}; .catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(res, logger));
};
} }

View File

@ -5,19 +5,29 @@ import winston = require("winston");
import Endpoints = require("../../../endpoints"); import Endpoints = require("../../../endpoints");
import AuthenticationValidator = require("../../AuthenticationValidator"); import AuthenticationValidator = require("../../AuthenticationValidator");
import { ServerVariablesHandler } from "../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../ServerVariablesHandler";
import BluebirdPromise = require("bluebird");
export default function (req: express.Request, res: express.Response) { export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
logger.debug("First factor: headers are %s", JSON.stringify(req.headers)); logger.debug("First factor: headers are %s", JSON.stringify(req.headers));
AuthenticationValidator.validate(req) return AuthenticationValidator.validate(req)
.then(function () { .then(function () {
res.render("already-logged-in", { logout_endpoint: Endpoints.LOGOUT_GET }); const redirectUrl = req.query.redirect;
}, function () { if (redirectUrl) {
res.render("firstfactor", { res.redirect(redirectUrl);
first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST, return BluebirdPromise.resolve();
reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET }
}); else {
}); res.render("already-logged-in", { logout_endpoint: Endpoints.LOGOUT_GET });
return BluebirdPromise.resolve();
}
}, function () {
res.render("firstfactor", {
first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET
});
return BluebirdPromise.resolve();
});
} }

View File

@ -12,63 +12,67 @@ import { ServerVariablesHandler } from "../../ServerVariablesHandler";
import AuthenticationSession = require("../../AuthenticationSession"); import AuthenticationSession = require("../../AuthenticationSession");
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> { export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const username: string = req.body.username; const username: string = req.body.username;
const password: string = req.body.password; const password: string = req.body.password;
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
const ldap = ServerVariablesHandler.getLdapAuthenticator(req.app); const ldap = ServerVariablesHandler.getLdapAuthenticator(req.app);
const config = ServerVariablesHandler.getConfiguration(req.app); const config = ServerVariablesHandler.getConfiguration(req.app);
if (!username || !password) { if (!username || !password) {
const err = new Error("No username or password"); const err = new Error("No username or password");
ErrorReplies.replyWithError401(res, logger)(err); ErrorReplies.replyWithError401(res, logger)(err);
return BluebirdPromise.reject(err); return BluebirdPromise.reject(err);
} }
const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app); const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app);
const accessController = ServerVariablesHandler.getAccessController(req.app); const accessController = ServerVariablesHandler.getAccessController(req.app);
const authSession = AuthenticationSession.get(req); let authSession: AuthenticationSession.AuthenticationSession;
logger.info("1st factor: Starting authentication of user \"%s\"", username); logger.info("1st factor: Starting authentication of user \"%s\"", username);
logger.debug("1st factor: Start bind operation against LDAP"); logger.debug("1st factor: Start bind operation against LDAP");
logger.debug("1st factor: username=%s", username); logger.debug("1st factor: username=%s", username);
return regulator.regulate(username) return AuthenticationSession.get(req)
.then(function () { .then(function (_authSession: AuthenticationSession.AuthenticationSession) {
logger.info("1st factor: No regulation applied."); authSession = _authSession;
return ldap.authenticate(username, password); return regulator.regulate(username);
}) })
.then(function (groupsAndEmails: GroupsAndEmails) { .then(function () {
logger.info("1st factor: LDAP binding successful. Retrieved information about user are %s", logger.info("1st factor: No regulation applied.");
JSON.stringify(groupsAndEmails)); return ldap.authenticate(username, password);
authSession.userid = username; })
authSession.first_factor = true; .then(function (groupsAndEmails: GroupsAndEmails) {
logger.info("1st factor: LDAP binding successful. Retrieved information about user are %s",
JSON.stringify(groupsAndEmails));
authSession.userid = username;
authSession.first_factor = true;
const emails: string[] = groupsAndEmails.emails; const emails: string[] = groupsAndEmails.emails;
const groups: string[] = groupsAndEmails.groups; const groups: string[] = groupsAndEmails.groups;
if (!emails || emails.length <= 0) { if (!emails || emails.length <= 0) {
const errMessage = "No emails found. The user should have at least one email address to reset password."; const errMessage = "No emails found. The user should have at least one email address to reset password.";
logger.error("1s factor: %s", errMessage); logger.error("1s factor: %s", errMessage);
return BluebirdPromise.reject(new Error(errMessage)); return BluebirdPromise.reject(new Error(errMessage));
} }
authSession.email = emails[0]; authSession.email = emails[0];
authSession.groups = groups; authSession.groups = groups;
logger.debug("1st factor: Mark successful authentication to regulator."); logger.debug("1st factor: Mark successful authentication to regulator.");
regulator.mark(username, true); regulator.mark(username, true);
res.status(204); res.status(204);
res.send(); res.send();
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(res, logger)) .catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(res, logger))
.catch(exceptions.LdapBindError, function (err: Error) { .catch(exceptions.LdapBindError, function (err: Error) {
regulator.mark(username, false); regulator.mark(username, false);
return ErrorReplies.replyWithError401(res, logger)(err); return ErrorReplies.replyWithError401(res, logger)(err);
}) })
.catch(exceptions.AuthenticationRegulationError, ErrorReplies.replyWithError403(res, logger)) .catch(exceptions.AuthenticationRegulationError, ErrorReplies.replyWithError403(res, logger))
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError401(res, logger)) .catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError401(res, logger))
.catch(ErrorReplies.replyWithError500(res, logger)); .catch(ErrorReplies.replyWithError500(res, logger));
} }

View File

@ -3,7 +3,7 @@ import express = require("express");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import objectPath = require("object-path"); import objectPath = require("object-path");
import exceptions = require("../../../Exceptions"); import exceptions = require("../../../Exceptions");
import { ServerVariablesHandler } from "../../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../AuthenticationSession"); import AuthenticationSession = require("../../../AuthenticationSession");
import ErrorReplies = require("../../../ErrorReplies"); import ErrorReplies = require("../../../ErrorReplies");
@ -12,23 +12,27 @@ import Constants = require("./../constants");
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> { export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
const ldapPasswordUpdater = ServerVariablesHandler.getLdapPasswordUpdater(req.app); const ldapPasswordUpdater = ServerVariablesHandler.getLdapPasswordUpdater(req.app);
const authSession = AuthenticationSession.get(req); let authSession: AuthenticationSession.AuthenticationSession;
const newPassword = objectPath.get<express.Request, string>(req, "body.password"); const newPassword = objectPath.get<express.Request, string>(req, "body.password");
const userid = authSession.identity_check.userid; return AuthenticationSession.get(req)
const challenge = authSession.identity_check.challenge; .then(function (_authSession: AuthenticationSession.AuthenticationSession) {
if (challenge != Constants.CHALLENGE) { authSession = _authSession;
res.status(403); logger.info("POST reset-password: User %s wants to reset his/her password.",
res.send(); authSession.identity_check.userid);
return; logger.info("POST reset-password: Challenge %s", authSession.identity_check.challenge);
}
logger.info("POST reset-password: User %s wants to reset his/her password", userid); if (authSession.identity_check.challenge != Constants.CHALLENGE) {
res.status(403);
res.send();
return BluebirdPromise.reject(new Error("Bad challenge."));
}
return ldapPasswordUpdater.updatePassword(userid, newPassword) return ldapPasswordUpdater.updatePassword(authSession.identity_check.userid, newPassword);
})
.then(function () { .then(function () {
logger.info("POST reset-password: Password reset for user '%s'", userid); logger.info("POST reset-password: Password reset for user '%s'",
authSession.identity_check.userid);
AuthenticationSession.reset(req); AuthenticationSession.reset(req);
res.status(204); res.status(204);
res.send(); res.send();

View File

@ -1,14 +1,17 @@
import express = require("express"); import Express = require("express");
import Endpoints = require("../../../endpoints"); import Endpoints = require("../../../endpoints");
import FirstFactorBlocker = require("../FirstFactorBlocker"); import FirstFactorBlocker = require("../FirstFactorBlocker");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
const TEMPLATE_NAME = "secondfactor"; const TEMPLATE_NAME = "secondfactor";
export default FirstFactorBlocker.default(handler); export default FirstFactorBlocker.default(handler);
function handler(req: express.Request, res: express.Response): BluebirdPromise<void> { function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app);
logger.debug("secondfactor request is coming from %s", req.originalUrl);
res.render(TEMPLATE_NAME, { res.render(TEMPLATE_NAME, {
totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET, totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
u2f_identity_start_endpoint: Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET u2f_identity_start_endpoint: Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET

View File

@ -5,11 +5,15 @@ import winston = require("winston");
import Endpoints = require("../../../endpoints"); import Endpoints = require("../../../endpoints");
import { ServerVariablesHandler } from "../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../ServerVariablesHandler";
import AuthenticationSession = require("../../AuthenticationSession"); import AuthenticationSession = require("../../AuthenticationSession");
import BluebirdPromise = require("bluebird");
export default function (req: express.Request, res: express.Response) { export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const authSession = AuthenticationSession.get(req); return AuthenticationSession.get(req)
const redirectUrl = req.query.redirect || Endpoints.FIRST_FACTOR_GET; .then(function (authSession: AuthenticationSession.AuthenticationSession) {
res.json({ const redirectUrl = req.query.redirect || Endpoints.FIRST_FACTOR_GET;
redirection_url: redirectUrl res.json({
}); redirection_url: redirectUrl
});
return BluebirdPromise.resolve();
});
} }

View File

@ -9,7 +9,7 @@ import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationT
import Constants = require("../constants"); import Constants = require("../constants");
import Endpoints = require("../../../../../endpoints"); import Endpoints = require("../../../../../endpoints");
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession"); import AuthenticationSession = require("../../../../AuthenticationSession");
import FirstFactorValidator = require("../../../../FirstFactorValidator"); import FirstFactorValidator = require("../../../../FirstFactorValidator");
@ -21,19 +21,21 @@ export default class RegistrationHandler implements IdentityValidable {
} }
private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> { private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
const authSession = AuthenticationSession.get(req); return AuthenticationSession.get(req)
const userid = authSession.userid; .then(function (authSession: AuthenticationSession.AuthenticationSession) {
const email = authSession.email; const userid = authSession.userid;
const email = authSession.email;
if (!(userid && email)) { if (!(userid && email)) {
return BluebirdPromise.reject(new Error("User ID or email is missing")); return BluebirdPromise.reject(new Error("User ID or email is missing"));
} }
const identity = { const identity = {
email: email, email: email,
userid: userid userid: userid
}; };
return BluebirdPromise.resolve(identity); return BluebirdPromise.resolve(identity);
});
} }
preValidationInit(req: express.Request): BluebirdPromise<Identity> { preValidationInit(req: express.Request): BluebirdPromise<Identity> {
@ -52,33 +54,34 @@ export default class RegistrationHandler implements IdentityValidable {
return FirstFactorValidator.validate(req); return FirstFactorValidator.validate(req);
} }
postValidationResponse(req: express.Request, res: express.Response) { postValidationResponse(req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
const authSession = AuthenticationSession.get(req); return AuthenticationSession.get(req)
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
const userid = authSession.identity_check.userid;
const challenge = authSession.identity_check.challenge;
const userid = authSession.identity_check.userid; if (challenge != Constants.CHALLENGE || !userid) {
const challenge = authSession.identity_check.challenge; res.status(403);
res.send();
return BluebirdPromise.reject(new Error("Bad challenge."));
}
if (challenge != Constants.CHALLENGE || !userid) { const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
res.status(403); const totpGenerator = ServerVariablesHandler.getTOTPGenerator(req.app);
res.send(); const secret = totpGenerator.generate();
return;
}
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); logger.debug("POST new-totp-secret: save the TOTP secret in DB");
const totpGenerator = ServerVariablesHandler.getTOTPGenerator(req.app); return userDataStore.saveTOTPSecret(userid, secret)
const secret = totpGenerator.generate(); .then(function () {
objectPath.set(req, "session", undefined);
logger.debug("POST new-totp-secret: save the TOTP secret in DB"); res.render(Constants.TEMPLATE_NAME, {
userDataStore.saveTOTPSecret(userid, secret) base32_secret: secret.base32,
.then(function () { otpauth_url: secret.otpauth_url,
objectPath.set(req, "session", undefined); login_endpoint: Endpoints.FIRST_FACTOR_GET
});
res.render(Constants.TEMPLATE_NAME, { });
base32_secret: secret.base32,
otpauth_url: secret.otpauth_url,
login_endpoint: Endpoints.FIRST_FACTOR_GET
});
}) })
.catch(ErrorReplies.replyWithError500(res, logger)); .catch(ErrorReplies.replyWithError500(res, logger));
} }

View File

@ -16,17 +16,18 @@ const UNAUTHORIZED_MESSAGE = "Unauthorized access";
export default FirstFactorBlocker(handler); export default FirstFactorBlocker(handler);
export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> { export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
let authSession: AuthenticationSession.AuthenticationSession;
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
const authSession = AuthenticationSession.get(req);
const userid = authSession.userid;
logger.info("POST 2ndfactor totp: Initiate TOTP validation for user %s", userid);
const token = req.body.token; const token = req.body.token;
const totpValidator = ServerVariablesHandler.getTOTPValidator(req.app); const totpValidator = ServerVariablesHandler.getTOTPValidator(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
logger.debug("POST 2ndfactor totp: Fetching secret for user %s", userid); return AuthenticationSession.get(req)
return userDataStore.retrieveTOTPSecret(userid) .then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
logger.info("POST 2ndfactor totp: Initiate TOTP validation for user %s", authSession.userid);
return userDataStore.retrieveTOTPSecret(authSession.userid);
})
.then(function (doc: TOTPSecretDocument) { .then(function (doc: TOTPSecretDocument) {
logger.debug("POST 2ndfactor totp: TOTP secret is %s", JSON.stringify(doc)); logger.debug("POST 2ndfactor totp: TOTP secret is %s", JSON.stringify(doc));
return totpValidator.validate(token, doc.secret.base32); return totpValidator.validate(token, doc.secret.base32);

View File

@ -20,20 +20,22 @@ export default class RegistrationHandler implements IdentityValidable {
return CHALLENGE; return CHALLENGE;
} }
private retrieveIdentity(req: express.Request) { private retrieveIdentity(req: express.Request): BluebirdPromise<Identity> {
const authSession = AuthenticationSession.get(req); return AuthenticationSession.get(req)
const userid = authSession.userid; .then(function (authSession: AuthenticationSession.AuthenticationSession) {
const email = authSession.email; const userid = authSession.userid;
const email = authSession.email;
if (!(userid && email)) { if (!(userid && email)) {
return BluebirdPromise.reject("User ID or email is missing"); return BluebirdPromise.reject(new Error("User ID or email is missing"));
} }
const identity = { const identity = {
email: email, email: email,
userid: userid userid: userid
}; };
return BluebirdPromise.resolve(identity); return BluebirdPromise.resolve(identity);
});
} }
preValidationInit(req: express.Request): BluebirdPromise<Identity> { preValidationInit(req: express.Request): BluebirdPromise<Identity> {

View File

@ -18,53 +18,54 @@ export default FirstFactorBlocker(handler);
function handler(req: express.Request, res: express.Response): BluebirdPromise<void> { function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
const authSession = AuthenticationSession.get(req); let authSession: AuthenticationSession.AuthenticationSession;
const registrationRequest = authSession.register_request; const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
const u2f = ServerVariablesHandler.getU2F(req.app);
const appid = u2f_common.extract_app_id(req);
const logger = ServerVariablesHandler.getLogger(req.app);
const registrationResponse: U2f.RegistrationData = req.body;
if (!registrationRequest) { return AuthenticationSession.get(req)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
const registrationRequest = authSession.register_request;
if (!registrationRequest) {
res.status(403); res.status(403);
res.send(); res.send();
return BluebirdPromise.reject(new Error("No registration request")); return BluebirdPromise.reject(new Error("No registration request"));
} }
if (!authSession.identity_check if (!authSession.identity_check
|| authSession.identity_check.challenge != "u2f-register") { || authSession.identity_check.challenge != "u2f-register") {
res.status(403); res.status(403);
res.send(); res.send();
return BluebirdPromise.reject(new Error("Bad challenge for registration request")); return BluebirdPromise.reject(new Error("Bad challenge for registration request"));
} }
logger.info("U2F register: Finishing registration");
logger.debug("U2F register: registrationRequest = %s", JSON.stringify(registrationRequest));
logger.debug("U2F register: registrationResponse = %s", JSON.stringify(registrationResponse));
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); return BluebirdPromise.resolve(u2f.checkRegistration(registrationRequest, registrationResponse));
const u2f = ServerVariablesHandler.getU2F(req.app); })
const userid = authSession.userid; .then(function (u2fResult: U2f.RegistrationResult | U2f.Error): BluebirdPromise<void> {
const appid = u2f_common.extract_app_id(req); if (objectPath.has(u2fResult, "errorCode"))
const logger = ServerVariablesHandler.getLogger(req.app); return BluebirdPromise.reject(new Error("Error while registering."));
const registrationResponse: U2f.RegistrationData = req.body; const registrationResult: U2f.RegistrationResult = u2fResult as U2f.RegistrationResult;
logger.info("U2F register: Store regisutration and reply");
logger.info("U2F register: Finishing registration"); logger.debug("U2F register: registration = %s", JSON.stringify(registrationResult));
logger.debug("U2F register: registrationRequest = %s", JSON.stringify(registrationRequest)); const registration: U2FRegistration = {
logger.debug("U2F register: registrationResponse = %s", JSON.stringify(registrationResponse)); keyHandle: registrationResult.keyHandle,
publicKey: registrationResult.publicKey
BluebirdPromise.resolve(u2f.checkRegistration(registrationRequest, registrationResponse)) };
.then(function (u2fResult: U2f.RegistrationResult | U2f.Error): BluebirdPromise<void> { return userDataStore.saveU2FRegistration(authSession.userid, appid, registration);
if (objectPath.has(u2fResult, "errorCode")) })
return BluebirdPromise.reject(new Error("Error while registering.")); .then(function () {
authSession.identity_check = undefined;
const registrationResult: U2f.RegistrationResult = u2fResult as U2f.RegistrationResult; redirect(req, res);
logger.info("U2F register: Store regisutration and reply"); return BluebirdPromise.resolve();
logger.debug("U2F register: registration = %s", JSON.stringify(registrationResult)); })
const registration: U2FRegistration = { .catch(ErrorReplies.replyWithError500(res, logger));
keyHandle: registrationResult.keyHandle,
publicKey: registrationResult.publicKey
};
return userDataStore.saveU2FRegistration(userid, appid, registration);
})
.then(function () {
authSession.identity_check = undefined;
redirect(req, res);
return BluebirdPromise.resolve();
})
.catch(ErrorReplies.replyWithError500(res, logger));
} }

View File

@ -8,29 +8,34 @@ import express = require("express");
import U2f = require("u2f"); import U2f = require("u2f");
import FirstFactorBlocker from "../../../FirstFactorBlocker"; import FirstFactorBlocker from "../../../FirstFactorBlocker";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import {  ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession"); import AuthenticationSession = require("../../../../AuthenticationSession");
export default FirstFactorBlocker(handler); export default FirstFactorBlocker(handler);
function handler(req: express.Request, res: express.Response): BluebirdPromise<void> { function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
const authSession = AuthenticationSession.get(req); let authSession: AuthenticationSession.AuthenticationSession;
if (!authSession.identity_check return AuthenticationSession.get(req)
|| authSession.identity_check.challenge != "u2f-register") { .then(function (_authSession: AuthenticationSession.AuthenticationSession) {
res.status(403); authSession = _authSession;
res.send();
return;
}
const u2f = ServerVariablesHandler.getU2F(req.app); if (!authSession.identity_check
const appid: string = u2f_common.extract_app_id(req); || authSession.identity_check.challenge != "u2f-register") {
res.status(403);
res.send();
return BluebirdPromise.reject(new Error("Bad challenge."));
}
logger.debug("U2F register_request: headers=%s", JSON.stringify(req.headers)); const u2f = ServerVariablesHandler.getU2F(req.app);
logger.info("U2F register_request: Starting registration for appId %s", appid); const appid: string = u2f_common.extract_app_id(req);
return BluebirdPromise.resolve(u2f.request(appid)) logger.debug("U2F register_request: headers=%s", JSON.stringify(req.headers));
logger.info("U2F register_request: Starting registration for appId %s", appid);
return BluebirdPromise.resolve(u2f.request(appid));
})
.then(function (registrationRequest: U2f.Request) { .then(function (registrationRequest: U2f.Request) {
logger.debug("U2F register_request: registrationRequest = %s", JSON.stringify(registrationRequest)); logger.debug("U2F register_request: registrationRequest = %s", JSON.stringify(registrationRequest));
authSession.register_request = registrationRequest; authSession.register_request = registrationRequest;

View File

@ -11,7 +11,7 @@ import exceptions = require("../../../../Exceptions");
import FirstFactorBlocker from "../../../FirstFactorBlocker"; import FirstFactorBlocker from "../../../FirstFactorBlocker";
import redirect from "../../redirect"; import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import {  ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession"); import AuthenticationSession = require("../../../../AuthenticationSession");
export default FirstFactorBlocker(handler); export default FirstFactorBlocker(handler);
@ -19,17 +19,21 @@ export default FirstFactorBlocker(handler);
export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> { export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
const authSession = AuthenticationSession.get(req); let authSession: AuthenticationSession.AuthenticationSession;
if (!authSession.sign_request) { return AuthenticationSession.get(req)
const err = new Error("No sign request"); .then(function (_authSession: AuthenticationSession.AuthenticationSession) {
ErrorReplies.replyWithError401(res, logger)(err); authSession = _authSession;
return BluebirdPromise.reject(err); if (!authSession.sign_request) {
} const err = new Error("No sign request");
ErrorReplies.replyWithError401(res, logger)(err);
return BluebirdPromise.reject(err);
}
const userid = authSession.userid; const userid = authSession.userid;
const appid = u2f_common.extract_app_id(req); const appid = u2f_common.extract_app_id(req);
return userDataStore.retrieveU2FRegistration(userid, appid) return userDataStore.retrieveU2FRegistration(userid, appid);
})
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<U2f.SignatureResult | U2f.Error> { .then(function (doc: U2FRegistrationDocument): BluebirdPromise<U2f.SignatureResult | U2f.Error> {
const appId = u2f_common.extract_app_id(req); const appId = u2f_common.extract_app_id(req);
const u2f = ServerVariablesHandler.getU2F(req.app); const u2f = ServerVariablesHandler.getU2F(req.app);

View File

@ -11,43 +11,46 @@ import exceptions = require("../../../../Exceptions");
import { SignMessage } from "./SignMessage"; import { SignMessage } from "./SignMessage";
import FirstFactorBlocker from "../../../FirstFactorBlocker"; import FirstFactorBlocker from "../../../FirstFactorBlocker";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession"); import AuthenticationSession = require("../../../../AuthenticationSession");
export default FirstFactorBlocker(handler); export default FirstFactorBlocker(handler);
export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> { export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
const authSession = AuthenticationSession.get(req); let authSession: AuthenticationSession.AuthenticationSession;
const appId = u2f_common.extract_app_id(req);
const userId = authSession.userid; return AuthenticationSession.get(req)
const appId = u2f_common.extract_app_id(req); .then(function (_authSession: AuthenticationSession.AuthenticationSession) {
return userDataStore.retrieveU2FRegistration(userId, appId) authSession = _authSession;
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<SignMessage> { return userDataStore.retrieveU2FRegistration(authSession.userid, appId);
if (!doc) })
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No U2F registration found")); .then(function (doc: U2FRegistrationDocument): BluebirdPromise<SignMessage> {
if (!doc)
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No U2F registration found"));
const u2f = ServerVariablesHandler.getU2F(req.app); const u2f = ServerVariablesHandler.getU2F(req.app);
const appId: string = u2f_common.extract_app_id(req); const appId: string = u2f_common.extract_app_id(req);
logger.info("U2F sign_request: Start authentication to app %s", appId); logger.info("U2F sign_request: Start authentication to app %s", appId);
logger.debug("U2F sign_request: appId=%s, keyHandle=%s", appId, JSON.stringify(doc.registration.keyHandle)); logger.debug("U2F sign_request: appId=%s, keyHandle=%s", appId, JSON.stringify(doc.registration.keyHandle));
const request = u2f.request(appId, doc.registration.keyHandle); const request = u2f.request(appId, doc.registration.keyHandle);
const authenticationMessage: SignMessage = { const authenticationMessage: SignMessage = {
request: request, request: request,
keyHandle: doc.registration.keyHandle keyHandle: doc.registration.keyHandle
}; };
return BluebirdPromise.resolve(authenticationMessage); return BluebirdPromise.resolve(authenticationMessage);
}) })
.then(function (authenticationMessage: SignMessage) { .then(function (authenticationMessage: SignMessage) {
logger.info("U2F sign_request: Store authentication request and reply"); logger.info("U2F sign_request: Store authentication request and reply");
logger.debug("U2F sign_request: authenticationRequest=%s", authenticationMessage); logger.debug("U2F sign_request: authenticationRequest=%s", authenticationMessage);
authSession.sign_request = authenticationMessage.request; authSession.sign_request = authenticationMessage.request;
res.json(authenticationMessage); res.json(authenticationMessage);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(exceptions.AccessDeniedError, ErrorReplies.replyWithError401(res, logger)) .catch(exceptions.AccessDeniedError, ErrorReplies.replyWithError401(res, logger))
.catch(ErrorReplies.replyWithError500(res, logger)); .catch(ErrorReplies.replyWithError500(res, logger));
} }

View File

@ -8,18 +8,22 @@ import exceptions = require("../../Exceptions");
import winston = require("winston"); import winston = require("winston");
import AuthenticationValidator = require("../../AuthenticationValidator"); import AuthenticationValidator = require("../../AuthenticationValidator");
import ErrorReplies = require("../../ErrorReplies"); import ErrorReplies = require("../../ErrorReplies");
import { ServerVariablesHandler } from "../../ServerVariablesHandler"; import {  ServerVariablesHandler } from "../../ServerVariablesHandler";
import AuthenticationSession = require("../../AuthenticationSession"); import AuthenticationSession = require("../../AuthenticationSession");
function verify_filter(req: express.Request, res: express.Response): BluebirdPromise<void> { function verify_filter(req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
const accessController = ServerVariablesHandler.getAccessController(req.app); const accessController = ServerVariablesHandler.getAccessController(req.app);
const authSession = AuthenticationSession.get(req); let authSession: AuthenticationSession.AuthenticationSession;
logger.debug("Verify: headers are %s", JSON.stringify(req.headers)); return AuthenticationSession.get(req)
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] + req.headers["x-original-uri"])); .then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
logger.debug("Verify: headers are %s", JSON.stringify(req.headers));
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] + req.headers["x-original-uri"]));
return AuthenticationValidator.validate(req) return AuthenticationValidator.validate(req);
})
.then(function () { .then(function () {
const username = authSession.userid; const username = authSession.userid;
const groups = authSession.groups; const groups = authSession.groups;

View File

@ -1,7 +1,11 @@
Feature: User has access restricted access to domains Feature: User has access restricted access to domains
@need-registered-user-john
Scenario: User john has admin access Scenario: User john has admin access
When I register TOTP and login with user "john" and password "password" When I visit "https://auth.test.local:8080"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
Then I have access to: Then I have access to:
| url | | url |
| https://public.test.local:8080/secret.html | | https://public.test.local:8080/secret.html |
@ -11,8 +15,12 @@ Feature: User has access restricted access to domains
| https://mx1.mail.test.local:8080/secret.html | | https://mx1.mail.test.local:8080/secret.html |
| https://mx2.mail.test.local:8080/secret.html | | https://mx2.mail.test.local:8080/secret.html |
@need-registered-user-bob
Scenario: User bob has restricted access Scenario: User bob has restricted access
When I register TOTP and login with user "bob" and password "password" When I visit "https://auth.test.local:8080"
And I login with user "bob" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
Then I have access to: Then I have access to:
| url | | url |
| https://public.test.local:8080/secret.html | | https://public.test.local:8080/secret.html |
@ -24,8 +32,12 @@ Feature: User has access restricted access to domains
| url | | url |
| https://secret1.test.local:8080/secret.html | | https://secret1.test.local:8080/secret.html |
@need-registered-user-harry
Scenario: User harry has restricted access Scenario: User harry has restricted access
When I register TOTP and login with user "harry" and password "password" When I visit "https://auth.test.local:8080"
And I login with user "harry" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
Then I have access to: Then I have access to:
| url | | url |
| https://public.test.local:8080/secret.html | | https://public.test.local:8080/secret.html |

View File

@ -14,7 +14,7 @@ Feature: User validate first factor
And I click on "Sign in" And I click on "Sign in"
Then I get a notification of type "error" with message "Authentication failed. Please double check your credentials." Then I get a notification of type "error" with message "Authentication failed. Please double check your credentials."
Scenario: User succeeds TOTP second factor Scenario: User registers TOTP secret and succeeds authentication
Given I visit "https://auth.test.local:8080/" Given I visit "https://auth.test.local:8080/"
And I login with user "john" and password "password" And I login with user "john" and password "password"
And I register a TOTP secret called "Sec0" And I register a TOTP secret called "Sec0"
@ -31,23 +31,6 @@ Feature: User validate first factor
And I click on "TOTP" And I click on "TOTP"
Then I get a notification of type "error" with message "Problem with TOTP validation." Then I get a notification of type "error" with message "Problem with TOTP validation."
Scenario: User logs out Scenario: Logout redirects user to redirect URL given in parameter
Given I visit "https://auth.test.local:8080/"
And I login with user "john" and password "password"
And I register a TOTP secret called "Sec0"
And I visit "https://auth.test.local:8080/"
And I login with user "john" and password "password"
And I use "Sec0" as TOTP token handle
When I visit "https://auth.test.local:8080/logout?redirect=https://www.google.fr"
And I visit "https://secret.test.local:8080/secret.html"
Then I'm redirected to "https://auth.test.local:8080/"
Scenario: Logout redirects user
Given I visit "https://auth.test.local:8080/"
And I login with user "john" and password "password"
And I register a TOTP secret called "Sec0"
And I visit "https://auth.test.local:8080/"
And I login with user "john" and password "password"
And I use "Sec0" as TOTP token handle
When I visit "https://auth.test.local:8080/logout?redirect=https://www.google.fr" When I visit "https://auth.test.local:8080/logout?redirect=https://www.google.fr"
Then I'm redirected to "https://www.google.fr" Then I'm redirected to "https://www.google.fr"

View File

@ -5,19 +5,17 @@ Feature: User is correctly redirected
When I click on the link to secret.test.local When I click on the link to secret.test.local
Then I'm redirected to "https://auth.test.local:8080/" Then I'm redirected to "https://auth.test.local:8080/"
@need-registered-user-john
Scenario: User is redirected to home page after several authentication tries Scenario: User is redirected to home page after several authentication tries
Given I'm on https://auth.test.local:8080/ When I visit "https://public.test.local:8080/secret.html"
And I login with user "john" and password "password" And I login with user "john" and password "badpassword"
And I register a TOTP secret called "Sec0"
And I visit "https://public.test.local:8080/secret.html"
When I login with user "john" and password "badpassword"
And I clear field "username" And I clear field "username"
And I login with user "john" and password "password" And I login with user "john" and password "password"
And I use "Sec0" as TOTP token handle And I use "REGISTERED" as TOTP token handle
And I click on "TOTP" And I click on "TOTP"
Then I'm redirected to "https://public.test.local:8080/secret.html" Then I'm redirected to "https://public.test.local:8080/secret.html"
Scenario: User Harry does not have access to https://secret.test.local:8080/secret.html and thus he must get an error 401 Scenario: User Harry does not have access to https://secret.test.local:8080/secret.html and thus he must get an error 403
When I register TOTP and login with user "harry" and password "password" When I register TOTP and login with user "harry" and password "password"
And I visit "https://secret.test.local:8080/secret.html" And I visit "https://secret.test.local:8080/secret.html"
Then I get an error 403 Then I get an error 403
@ -44,4 +42,4 @@ Feature: User is correctly redirected
And I login with user "john" and password "password" And I login with user "john" and password "password"
And I use "Sec0" as TOTP token handle And I use "Sec0" as TOTP token handle
And I click on "TOTP" And I click on "TOTP"
Then I'm redirected to "https://public.test.local:8080/secret.html" Then I'm redirected to "https://public.test.local:8080/secret.html"

View File

@ -1,14 +1,9 @@
Feature: Authelia regulates authentication to avoid brute force Feature: Authelia regulates authentication to avoid brute force
@needs-test-config @needs-test-config
@need-registered-user-blackhat
Scenario: Attacker tries too many authentication in a short period of time and get banned Scenario: Attacker tries too many authentication in a short period of time and get banned
Given I visit "https://auth.test.local:8080/" Given I visit "https://auth.test.local:8080/"
And I login with user "blackhat" and password "password"
And I register a TOTP secret called "Sec0"
And I visit "https://auth.test.local:8080/"
And I login with user "blackhat" and password "password" and I use TOTP token handle "Sec0"
And I visit "https://auth.test.local:8080/logout?redirect=https://auth.test.local:8080/"
And I visit "https://auth.test.local:8080/"
And I set field "username" to "blackhat" And I set field "username" to "blackhat"
And I set field "password" to "bad-password" And I set field "password" to "bad-password"
And I click on "Sign in" And I click on "Sign in"
@ -24,14 +19,9 @@ Feature: Authelia regulates authentication to avoid brute force
Then I get a notification of type "error" with message "Authentication failed. Please double check your credentials." Then I get a notification of type "error" with message "Authentication failed. Please double check your credentials."
@needs-test-config @needs-test-config
@need-registered-user-blackhat
Scenario: User is unbanned after a configured amount of time Scenario: User is unbanned after a configured amount of time
Given I visit "https://auth.test.local:8080/" Given I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
And I login with user "blackhat" and password "password"
And I register a TOTP secret called "Sec0"
And I visit "https://auth.test.local:8080/"
And I login with user "blackhat" and password "password" and I use TOTP token handle "Sec0"
And I visit "https://auth.test.local:8080/logout?redirect=https://auth.test.local:8080/"
And I visit "https://auth.test.local:8080/"
And I set field "username" to "blackhat" And I set field "username" to "blackhat"
And I set field "password" to "bad-password" And I set field "password" to "bad-password"
And I click on "Sign in" And I click on "Sign in"
@ -45,7 +35,7 @@ Feature: Authelia regulates authentication to avoid brute force
When I wait 6 seconds When I wait 6 seconds
And I set field "password" to "password" And I set field "password" to "password"
And I click on "Sign in" And I click on "Sign in"
And I use "Sec0" as TOTP token handle And I use "REGISTERED" as TOTP token handle
And I click on "TOTP" And I click on "TOTP"
Then I have access to: Then I have access to:
| url | | url |

View File

@ -1,20 +1,18 @@
Feature: Authelia keeps user sessions despite the application restart Feature: Authelia keeps user sessions despite the application restart
@need-authenticated-user-john
Scenario: Session is still valid after Authelia restarts Scenario: Session is still valid after Authelia restarts
When I register TOTP and login with user "john" and password "password" When the application restarts
And the application restarts
Then I have access to: Then I have access to:
| url | | url |
| https://secret.test.local:8080/secret.html | | https://secret.test.local:8080/secret.html |
@need-registered-user-john
Scenario: Secrets are stored even when Authelia restarts Scenario: Secrets are stored even when Authelia restarts
Given I visit "https://auth.test.local:8080/"
And I login with user "john" and password "password"
And I register a TOTP secret called "Sec0"
When the application restarts When the application restarts
And I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fsecret.test.local%3A8080%2Fsecret.html" And I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fsecret.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password" And I login with user "john" and password "password"
And I use "Sec0" as TOTP token handle And I use "REGISTERED" as TOTP token handle
And I click on "TOTP" And I click on "TOTP"
Then I have access to: Then I have access to:
| url | | url |

View File

@ -1,6 +1,6 @@
Feature: Non authenticated users have no access to certain pages Feature: Non authenticated users have no access to certain pages
Scenario Outline: User has no access to protected pages Scenario Outline: Anonymous user has no access to protected pages
When I visit "<url>" When I visit "<url>"
Then I get an error <error code> Then I get an error <error code>

View File

@ -6,7 +6,7 @@ import Speakeasy = require("speakeasy");
import CustomWorld = require("../support/world"); import CustomWorld = require("../support/world");
Cucumber.defineSupportCode(function ({ Given, When, Then }) { Cucumber.defineSupportCode(function ({ Given, When, Then }) {
When(/^I visit "(https:\/\/[a-z0-9:.\/=?-]+)"$/, function (link: string) { When(/^I visit "(https:\/\/[a-zA-Z0-9:%.\/=?-]+)"$/, function (link: string) {
return this.visit(link); return this.visit(link);
}); });

View File

@ -2,23 +2,90 @@ import Cucumber = require("cucumber");
import fs = require("fs"); import fs = require("fs");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import ChildProcess = require("child_process"); import ChildProcess = require("child_process");
import { UserDataStore } from "../../../src/server/lib/storage/UserDataStore";
import { CollectionFactoryFactory } from "../../../src/server/lib/storage/CollectionFactoryFactory";
import { MongoConnector } from "../../../src/server/lib/connectors/mongo/MongoConnector";
import { IMongoClient } from "../../../src/server/lib/connectors/mongo/IMongoClient";
import { TOTPGenerator } from "../../../src/server/lib/TOTPGenerator";
import Speakeasy = require("speakeasy");
Cucumber.defineSupportCode(function({ setDefaultTimeout }) { Cucumber.defineSupportCode(function ({ setDefaultTimeout }) {
setDefaultTimeout(20 * 1000); setDefaultTimeout(20 * 1000);
}); });
Cucumber.defineSupportCode(function({ After, Before }) { Cucumber.defineSupportCode(function ({ After, Before }) {
const exec = BluebirdPromise.promisify(ChildProcess.exec); const exec = BluebirdPromise.promisify(ChildProcess.exec);
After(function() { After(function () {
return this.driver.quit(); return this.driver.quit();
}); });
Before({tags: "@needs-test-config", timeout: 15 * 1000}, function () { Before({ tags: "@needs-test-config", timeout: 20 * 1000 }, function () {
return exec("./scripts/example-commit/dc-example.sh -f docker-compose.test.yml up -d authelia && sleep 2"); return exec("./scripts/example-commit/dc-example.sh -f docker-compose.test.yml up -d authelia && sleep 2");
}); });
After({tags: "@needs-test-config", timeout: 15 * 1000}, function () { After({ tags: "@needs-test-config", timeout: 20 * 1000 }, function () {
return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 2"); return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 2");
}); });
function registerUser(context: any, username: string) {
let secret: Speakeasy.Key;
const mongoConnector = new MongoConnector("mongodb://localhost:27017/authelia");
return mongoConnector.connect()
.then(function (mongoClient: IMongoClient) {
const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient);
const userDataStore = new UserDataStore(collectionFactory);
const generator = new TOTPGenerator(Speakeasy);
secret = generator.generate();
return userDataStore.saveTOTPSecret(username, secret);
})
.then(function () {
context.totpSecrets["REGISTERED"] = secret.base32;
});
}
function declareNeedRegisteredUserHooks(username: string) {
Before({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () {
return registerUser(this, username);
});
After({ tags: "@need-registered-user-" + username, timeout: 15 * 1000 }, function () {
this.totpSecrets["REGISTERED"] = undefined;
});
}
function needAuthenticatedUser(context: any, username: string): BluebirdPromise<void> {
return context.visit("https://auth.test.local:8080/")
.then(function () {
return registerUser(context, username);
})
.then(function () {
return context.loginWithUserPassword(username, "password");
})
.then(function () {
return context.useTotpTokenHandle("REGISTERED");
})
.then(function() {
return context.clickOnButton("TOTP");
});
}
function declareNeedAuthenticatedUserHooks(username: string) {
Before({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () {
return needAuthenticatedUser(this, username);
});
After({ tags: "@need-authenticated-user-" + username, timeout: 15 * 1000 }, function () {
this.totpSecrets["REGISTERED"] = undefined;
});
}
function declareHooksForUser(username: string) {
declareNeedRegisteredUserHooks(username);
declareNeedAuthenticatedUserHooks(username);
}
const users = ["harry", "john", "bob", "blackhat"];
users.forEach(declareHooksForUser);
}); });

View File

@ -91,6 +91,9 @@ function CustomWorld() {
}; };
this.useTotpTokenHandle = function (totpSecretHandle: string) { this.useTotpTokenHandle = function (totpSecretHandle: string) {
if (!this.totpSecrets[totpSecretHandle])
throw new Error("No available TOTP token handle " + totpSecretHandle);
const token = Speakeasy.totp({ const token = Speakeasy.totp({
secret: this.totpSecrets[totpSecretHandle], secret: this.totpSecrets[totpSecretHandle],
encoding: "base32" encoding: "base32"

View File

@ -155,9 +155,14 @@ describe("test identity check process", function () {
req.query.identity_token = "token"; req.query.identity_token = "token";
req.session = {}; req.session = {};
const authSession = AuthenticationSession.get(req as any); let authSession: AuthenticationSession.AuthenticationSession;
const callback = IdentityValidator.get_finish_validation(identityValidable); const callback = IdentityValidator.get_finish_validation(identityValidable);
return callback(req as any, res as any, undefined)
return AuthenticationSession.get(req as any)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
return callback(req as any, res as any, undefined);
})
.then(function () { return BluebirdPromise.reject("Should fail"); }) .then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () { .catch(function () {
assert.equal(authSession.identity_check.userid, "user"); assert.equal(authSession.identity_check.userid, "user");

View File

@ -45,6 +45,7 @@ describe("test the first factor validation route", function () {
req = { req = {
app: { app: {
get: sinon.stub().returns({ logger: winston })
}, },
body: { body: {
username: "username", username: "username",
@ -77,8 +78,12 @@ describe("test the first factor validation route", function () {
emails: emails, emails: emails,
groups: groups groups: groups
})); }));
const authSession = AuthenticationSession.get(req as any); let authSession: AuthenticationSession.AuthenticationSession;
return FirstFactorPost.default(req as any, res as any) return AuthenticationSession.get(req as any)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
return FirstFactorPost.default(req as any, res as any);
})
.then(function () { .then(function () {
assert.equal("username", authSession.userid); assert.equal("username", authSession.userid);
assert(res.send.calledOnce); assert(res.send.calledOnce);
@ -94,13 +99,18 @@ describe("test the first factor validation route", function () {
it("should set first email address as user session variable", function () { it("should set first email address as user session variable", function () {
const emails = ["test_ok@example.com"]; const emails = ["test_ok@example.com"];
const authSession = AuthenticationSession.get(req as any); let authSession: AuthenticationSession.AuthenticationSession;
(serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password") (serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password")
.returns(BluebirdPromise.resolve({ .returns(BluebirdPromise.resolve({
emails: emails, emails: emails,
groups: groups groups: groups
})); }));
return FirstFactorPost.default(req as any, res as any)
return AuthenticationSession.get(req as any)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
return FirstFactorPost.default(req as any, res as any);
})
.then(function () { .then(function () {
assert.equal("test_ok@example.com", authSession.email); assert.equal("test_ok@example.com", authSession.email);
}); });

View File

@ -16,7 +16,6 @@ describe("test reset password route", function () {
let req: ExpressMock.RequestMock; let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let configuration: any; let configuration: any;
let authSession: AuthenticationSession.AuthenticationSession;
let serverVariables: ServerVariablesMock.ServerVariablesMock; let serverVariables: ServerVariablesMock.ServerVariablesMock;
beforeEach(function () { beforeEach(function () {
@ -25,7 +24,7 @@ describe("test reset password route", function () {
userid: "user" userid: "user"
}, },
app: { app: {
get: Sinon.stub() get: Sinon.stub().returns({ logger: winston })
}, },
session: {}, session: {},
headers: { headers: {
@ -34,11 +33,6 @@ describe("test reset password route", function () {
}; };
AuthenticationSession.reset(req as any); AuthenticationSession.reset(req as any);
authSession = AuthenticationSession.get(req as any);
authSession.userid = "user";
authSession.email = "user@example.com";
authSession.first_factor = true;
authSession.second_factor = false;
const options = { const options = {
inMemoryOnly: true inMemoryOnly: true
@ -65,54 +59,72 @@ describe("test reset password route", function () {
} as any; } as any;
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
AuthenticationSession.get(req as any)
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
authSession.userid = "user";
authSession.email = "user@example.com";
authSession.first_factor = true;
authSession.second_factor = false;
});
}); });
describe("test reset password post", () => { describe("test reset password post", () => {
it("should update the password and reset auth_session for reauthentication", function () { it("should update the password and reset auth_session for reauthentication", function () {
authSession.identity_check = {
userid: "user",
challenge: "reset-password"
};
req.body = {}; req.body = {};
req.body.password = "new-password"; req.body.password = "new-password";
(serverVariables.ldapPasswordUpdater.updatePassword as sinon.SinonStub).returns(BluebirdPromise.resolve()); (serverVariables.ldapPasswordUpdater.updatePassword as sinon.SinonStub).returns(BluebirdPromise.resolve());
return PasswordResetFormPost.default(req as any, res as any)
return AuthenticationSession.get(req as any)
.then(function (authSession) {
authSession.identity_check = {
userid: "user",
challenge: "reset-password"
};
return PasswordResetFormPost.default(req as any, res as any);
})
.then(function () { .then(function () {
const authSession = AuthenticationSession.get(req as any); return AuthenticationSession.get(req as any);
}).then(function (_authSession: AuthenticationSession.AuthenticationSession) {
assert.equal(res.status.getCall(0).args[0], 204); assert.equal(res.status.getCall(0).args[0], 204);
assert.equal(authSession.first_factor, false); assert.equal(_authSession.first_factor, false);
assert.equal(authSession.second_factor, false); assert.equal(_authSession.second_factor, false);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
}); });
it("should fail if identity_challenge does not exist", function (done) { it("should fail if identity_challenge does not exist", function () {
authSession.identity_check = { return AuthenticationSession.get(req as any)
userid: "user", .then(function (authSession) {
challenge: undefined authSession.identity_check = {
}; userid: "user",
res.send = Sinon.spy(function () { challenge: undefined
assert.equal(res.status.getCall(0).args[0], 403); };
done(); return PasswordResetFormPost.default(req as any, res as any);
}); })
PasswordResetFormPost.default(req as any, res as any); .then(function () {
assert.equal(res.status.getCall(0).args[0], 403);
});
}); });
it("should fail when ldap fails", function (done) { it("should fail when ldap fails", function () {
authSession.identity_check = {
challenge: "reset-password",
userid: "user"
};
req.body = {}; req.body = {};
req.body.password = "new-password"; req.body.password = "new-password";
(serverVariables.ldapPasswordUpdater.updatePassword as Sinon.SinonStub).returns(BluebirdPromise.reject("Internal error with LDAP")); (serverVariables.ldapPasswordUpdater.updatePassword as Sinon.SinonStub)
res.send = Sinon.spy(function () { .returns(BluebirdPromise.reject("Internal error with LDAP"));
assert.equal(res.status.getCall(0).args[0], 500);
done(); return AuthenticationSession.get(req as any)
}); .then(function (authSession) {
PasswordResetFormPost.default(req as any, res as any); authSession.identity_check = {
challenge: "reset-password",
userid: "user"
};
return PasswordResetFormPost.default(req as any, res as any);
}).then(function () {
assert.equal(res.status.getCall(0).args[0], 500);
return BluebirdPromise.resolve();
});
}); });
}); });
}); });

View File

@ -23,11 +23,6 @@ describe("test totp register", function () {
req.session = {}; req.session = {};
AuthenticationSession.reset(req as any); AuthenticationSession.reset(req as any);
authSession = AuthenticationSession.get(req as any);
authSession.userid = "user";
authSession.email = "user@example.com";
authSession.first_factor = true;
authSession.second_factor = false;
req.headers = {}; req.headers = {};
req.headers.host = "localhost"; req.headers.host = "localhost";
@ -43,6 +38,15 @@ describe("test totp register", function () {
mocks.userDataStore.saveTOTPSecretStub.returns(BluebirdPromise.resolve({})); mocks.userDataStore.saveTOTPSecretStub.returns(BluebirdPromise.resolve({}));
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
return AuthenticationSession.get(req as any)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
authSession.userid = "user";
authSession.email = "user@example.com";
authSession.first_factor = true;
authSession.second_factor = false;
});
}); });
describe("test totp registration check", test_registration_check); describe("test totp registration check", test_registration_check);

View File

@ -23,6 +23,7 @@ describe("test totp route", function () {
const app_get = sinon.stub(); const app_get = sinon.stub();
req = { req = {
app: { app: {
get: sinon.stub().returns({ logger: winston })
}, },
body: { body: {
token: "abc" token: "abc"
@ -30,11 +31,6 @@ describe("test totp route", function () {
session: {} session: {}
}; };
AuthenticationSession.reset(req as any); AuthenticationSession.reset(req as any);
authSession = AuthenticationSession.get(req as any);
authSession.userid = "user";
authSession.first_factor = true;
authSession.second_factor = false;
const mocks = ServerVariablesMock.mock(req.app); const mocks = ServerVariablesMock.mock(req.app);
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
@ -52,6 +48,14 @@ describe("test totp route", function () {
mocks.logger = winston; mocks.logger = winston;
mocks.totpValidator = totpValidator; mocks.totpValidator = totpValidator;
mocks.config = config; mocks.config = config;
return AuthenticationSession.get(req as any)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
authSession.userid = "user";
authSession.first_factor = true;
authSession.second_factor = false;
});
}); });

View File

@ -23,11 +23,6 @@ describe("test register handler", function () {
mocks.logger = winston; mocks.logger = winston;
req.session = {}; req.session = {};
AuthenticationSession.reset(req as any); AuthenticationSession.reset(req as any);
authSession = AuthenticationSession.get(req as any);
authSession.userid = "user";
authSession.email = "user@example.com";
authSession.first_factor = true;
authSession.second_factor = false;
req.headers = {}; req.headers = {};
req.headers.host = "localhost"; req.headers.host = "localhost";
@ -44,6 +39,15 @@ describe("test register handler", function () {
res.send = sinon.spy(); res.send = sinon.spy();
res.json = sinon.spy(); res.json = sinon.spy();
res.status = sinon.spy(); res.status = sinon.spy();
return AuthenticationSession.get(req as any)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
authSession.userid = "user";
authSession.email = "user@example.com";
authSession.first_factor = true;
authSession.second_factor = false;
});
}); });
describe("test u2f registration check", test_registration_check); describe("test u2f registration check", test_registration_check);

View File

@ -17,7 +17,6 @@ describe("test u2f routes: register", function () {
let req: ExpressMock.RequestMock; let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock.ServerVariablesMock; let mocks: ServerVariablesMock.ServerVariablesMock;
let authSession: AuthenticationSession.AuthenticationSession;
beforeEach(function () { beforeEach(function () {
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
@ -26,16 +25,6 @@ describe("test u2f routes: register", function () {
mocks.logger = winston; mocks.logger = winston;
req.session = {}; req.session = {};
AuthenticationSession.reset(req as any);
authSession = AuthenticationSession.get(req as any);
authSession.userid = "user";
authSession.first_factor = true;
authSession.second_factor = false;
authSession.identity_check = {
challenge: "u2f-register",
userid: "user"
};
req.headers = {}; req.headers = {};
req.headers.host = "localhost"; req.headers.host = "localhost";
@ -50,6 +39,18 @@ describe("test u2f routes: register", function () {
res.send = sinon.spy(); res.send = sinon.spy();
res.json = sinon.spy(); res.json = sinon.spy();
res.status = sinon.spy(); res.status = sinon.spy();
AuthenticationSession.reset(req as any);
return AuthenticationSession.get(req as any)
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
authSession.userid = "user";
authSession.first_factor = true;
authSession.second_factor = false;
authSession.identity_check = {
challenge: "u2f-register",
userid: "user"
};
});
}); });
describe("test registration", test_registration); describe("test registration", test_registration);
@ -64,16 +65,22 @@ describe("test u2f routes: register", function () {
}; };
const u2f_mock = U2FMock.U2FMock(); const u2f_mock = U2FMock.U2FMock();
u2f_mock.checkRegistration.returns(BluebirdPromise.resolve(expectedStatus)); u2f_mock.checkRegistration.returns(BluebirdPromise.resolve(expectedStatus));
authSession.register_request = {
appId: "app",
challenge: "challenge",
keyHandle: "key",
version: "U2F_V2"
};
mocks.u2f = u2f_mock; mocks.u2f = u2f_mock;
return U2FRegisterPost.default(req as any, res as any)
return AuthenticationSession.get(req as any)
.then(function (authSession) {
authSession.register_request = {
appId: "app",
challenge: "challenge",
keyHandle: "key",
version: "U2F_V2"
};
return U2FRegisterPost.default(req as any, res as any);
})
.then(function () { .then(function () {
return AuthenticationSession.get(req as any);
})
.then(function (authSession) {
assert.equal("user", mocks.userDataStore.saveU2FRegistrationStub.getCall(0).args[0]); assert.equal("user", mocks.userDataStore.saveU2FRegistrationStub.getCall(0).args[0]);
assert.equal(authSession.identity_check, undefined); assert.equal(authSession.identity_check, undefined);
}); });
@ -83,15 +90,19 @@ describe("test u2f routes: register", function () {
const user_key_container = {}; const user_key_container = {};
const u2f_mock = U2FMock.U2FMock(); const u2f_mock = U2FMock.U2FMock();
u2f_mock.checkRegistration.returns({ errorCode: 500 }); u2f_mock.checkRegistration.returns({ errorCode: 500 });
authSession.register_request = {
appId: "app",
challenge: "challenge",
keyHandle: "key",
version: "U2F_V2"
};
mocks.u2f = u2f_mock; mocks.u2f = u2f_mock;
return U2FRegisterPost.default(req as any, res as any)
return AuthenticationSession.get(req as any)
.then(function (authSession) {
authSession.register_request = {
appId: "app",
challenge: "challenge",
keyHandle: "key",
version: "U2F_V2"
};
return U2FRegisterPost.default(req as any, res as any);
})
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(500, res.status.getCall(0).args[0]); assert.equal(500, res.status.getCall(0).args[0]);
@ -104,9 +115,12 @@ describe("test u2f routes: register", function () {
const u2f_mock = U2FMock.U2FMock(); const u2f_mock = U2FMock.U2FMock();
u2f_mock.checkRegistration.returns(BluebirdPromise.resolve()); u2f_mock.checkRegistration.returns(BluebirdPromise.resolve());
authSession.register_request = undefined;
mocks.u2f = u2f_mock; mocks.u2f = u2f_mock;
return U2FRegisterPost.default(req as any, res as any) return AuthenticationSession.get(req as any)
.then(function (authSession) {
authSession.register_request = undefined;
return U2FRegisterPost.default(req as any, res as any);
})
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(403, res.status.getCall(0).args[0]); assert.equal(403, res.status.getCall(0).args[0]);
@ -119,9 +133,12 @@ describe("test u2f routes: register", function () {
const u2f_mock = U2FMock.U2FMock(); const u2f_mock = U2FMock.U2FMock();
u2f_mock.checkRegistration.returns(BluebirdPromise.resolve()); u2f_mock.checkRegistration.returns(BluebirdPromise.resolve());
authSession.register_request = undefined;
mocks.u2f = u2f_mock; mocks.u2f = u2f_mock;
return U2FRegisterPost.default(req as any, res as any) return AuthenticationSession.get(req as any)
.then(function (authSession) {
authSession.register_request = undefined;
return U2FRegisterPost.default(req as any, res as any);
})
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(403, res.status.getCall(0).args[0]); assert.equal(403, res.status.getCall(0).args[0]);
@ -130,8 +147,11 @@ describe("test u2f routes: register", function () {
}); });
it("should return forbidden error when identity has not been verified", function () { it("should return forbidden error when identity has not been verified", function () {
authSession.identity_check = undefined; return AuthenticationSession.get(req as any)
return U2FRegisterPost.default(req as any, res as any) .then(function (authSession) {
authSession.identity_check = undefined;
return U2FRegisterPost.default(req as any, res as any);
})
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(403, res.status.getCall(0).args[0]); assert.equal(403, res.status.getCall(0).args[0]);

View File

@ -14,82 +14,84 @@ import ServerVariablesMock = require("../../../../mocks/ServerVariablesMock");
import U2f = require("u2f"); import U2f = require("u2f");
describe("test u2f routes: register_request", function () { describe("test u2f routes: register_request", function () {
let req: ExpressMock.RequestMock; let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock; let res: ExpressMock.ResponseMock;
let mocks: ServerVariablesMock.ServerVariablesMock; let mocks: ServerVariablesMock.ServerVariablesMock;
let authSession: AuthenticationSession.AuthenticationSession; let authSession: AuthenticationSession.AuthenticationSession;
beforeEach(function () { beforeEach(function () {
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
req.app = {}; req.app = {};
mocks = ServerVariablesMock.mock(req.app); mocks = ServerVariablesMock.mock(req.app);
mocks.logger = winston; mocks.logger = winston;
req.session = {}; req.session = {};
AuthenticationSession.reset(req as any); AuthenticationSession.reset(req as any);
authSession = AuthenticationSession.get(req as any);
req.headers = {};
req.headers.host = "localhost";
const options = {
inMemoryOnly: true
};
mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
res = ExpressMock.ResponseMock();
res.send = sinon.spy();
res.json = sinon.spy();
res.status = sinon.spy();
return AuthenticationSession.get(req as any)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
authSession.userid = "user"; authSession.userid = "user";
authSession.first_factor = true; authSession.first_factor = true;
authSession.second_factor = false; authSession.second_factor = false;
authSession.identity_check = { authSession.identity_check = {
challenge: "u2f-register", challenge: "u2f-register",
userid: "user" userid: "user"
}; };
});
});
req.headers = {}; describe("test registration request", () => {
req.headers.host = "localhost"; it("should send back the registration request and save it in the session", function () {
const expectedRequest = {
test: "abc"
};
const user_key_container = {};
const u2f_mock = U2FMock.U2FMock();
u2f_mock.request.returns(BluebirdPromise.resolve(expectedRequest));
const options = { mocks.u2f = u2f_mock;
inMemoryOnly: true return U2FRegisterRequestGet.default(req as any, res as any)
}; .then(function () {
assert.deepEqual(expectedRequest, res.json.getCall(0).args[0]);
});
mocks.userDataStore.saveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({}));
res = ExpressMock.ResponseMock();
res.send = sinon.spy();
res.json = sinon.spy();
res.status = sinon.spy();
}); });
describe("test registration request", () => { it("should return internal error on registration request", function (done) {
it("should send back the registration request and save it in the session", function () { res.send = sinon.spy(function (data: any) {
const expectedRequest = { assert.equal(500, res.status.getCall(0).args[0]);
test: "abc" done();
}; });
const user_key_container = {}; const user_key_container = {};
const u2f_mock = U2FMock.U2FMock(); const u2f_mock = U2FMock.U2FMock();
u2f_mock.request.returns(BluebirdPromise.resolve(expectedRequest)); u2f_mock.request.returns(BluebirdPromise.reject("Internal error"));
mocks.u2f = u2f_mock; mocks.u2f = u2f_mock;
return U2FRegisterRequestGet.default(req as any, res as any) U2FRegisterRequestGet.default(req as any, res as any);
.then(function () { });
assert.deepEqual(expectedRequest, res.json.getCall(0).args[0]);
});
});
it("should return internal error on registration request", function (done) { it("should return forbidden if identity has not been verified", function () {
res.send = sinon.spy(function (data: any) { authSession.identity_check = undefined;
assert.equal(500, res.status.getCall(0).args[0]); return U2FRegisterRequestGet.default(req as any, res as any)
done(); .then(function () {
}); assert.equal(403, res.status.getCall(0).args[0]);
const user_key_container = {};
const u2f_mock = U2FMock.U2FMock();
u2f_mock.request.returns(BluebirdPromise.reject("Internal error"));
mocks.u2f = u2f_mock;
U2FRegisterRequestGet.default(req as any, res as any);
});
it("should return forbidden if identity has not been verified", function (done) {
res.send = sinon.spy(function (data: any) {
assert.equal(403, res.status.getCall(0).args[0]);
done();
});
authSession.identity_check = undefined;
U2FRegisterRequestGet.default(req as any, res as any);
}); });
}); });
});
}); });

View File

@ -27,14 +27,6 @@ describe("test u2f routes: sign", function () {
req.session = {}; req.session = {};
AuthenticationSession.reset(req as any); AuthenticationSession.reset(req as any);
authSession = AuthenticationSession.get(req as any);
authSession.userid = "user";
authSession.first_factor = true;
authSession.second_factor = false;
authSession.identity_check = {
challenge: "u2f-register",
userid: "user"
};
req.headers = {}; req.headers = {};
req.headers.host = "localhost"; req.headers.host = "localhost";
@ -46,6 +38,18 @@ describe("test u2f routes: sign", function () {
res.send = sinon.spy(); res.send = sinon.spy();
res.json = sinon.spy(); res.json = sinon.spy();
res.status = sinon.spy(); res.status = sinon.spy();
return AuthenticationSession.get(req as any)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
authSession.userid = "user";
authSession.first_factor = true;
authSession.second_factor = false;
authSession.identity_check = {
challenge: "u2f-register",
userid: "user"
};
});
}); });
it("should return status code 204", function () { it("should return status code 204", function () {

View File

@ -31,14 +31,6 @@ describe("test u2f routes: sign_request", function () {
req.session = {}; req.session = {};
AuthenticationSession.reset(req as any); AuthenticationSession.reset(req as any);
authSession = AuthenticationSession.get(req as any);
authSession.userid = "user";
authSession.first_factor = true;
authSession.second_factor = false;
authSession.identity_check = {
challenge: "u2f-register",
userid: "user"
};
req.headers = {}; req.headers = {};
req.headers.host = "localhost"; req.headers.host = "localhost";
@ -51,6 +43,18 @@ describe("test u2f routes: sign_request", function () {
res.send = sinon.spy(); res.send = sinon.spy();
res.json = sinon.spy(); res.json = sinon.spy();
res.status = sinon.spy(); res.status = sinon.spy();
return AuthenticationSession.get(req as any)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
authSession.userid = "user";
authSession.first_factor = true;
authSession.second_factor = false;
authSession.identity_check = {
challenge: "u2f-register",
userid: "user"
};
});
}); });
it("should send back the sign request and save it in the session", function () { it("should send back the sign request and save it in the session", function () {

View File

@ -24,6 +24,9 @@ describe("test authentication token verification", function () {
req = ExpressMock.RequestMock(); req = ExpressMock.RequestMock();
res = ExpressMock.ResponseMock(); res = ExpressMock.ResponseMock();
req.app = {
get: sinon.stub().returns({ logger: winston })
};
req.session = {}; req.session = {};
AuthenticationSession.reset(req as any); AuthenticationSession.reset(req as any);
req.headers = {}; req.headers = {};
@ -37,12 +40,13 @@ describe("test authentication token verification", function () {
it("should be already authenticated", function () { it("should be already authenticated", function () {
req.session = {}; req.session = {};
AuthenticationSession.reset(req as any); AuthenticationSession.reset(req as any);
const authSession = AuthenticationSession.get(req as any); return AuthenticationSession.get(req as any)
authSession.first_factor = true; .then(function (authSession: AuthenticationSession.AuthenticationSession) {
authSession.second_factor = true; authSession.first_factor = true;
authSession.userid = "myuser"; authSession.second_factor = true;
authSession.userid = "myuser";
return VerifyGet.default(req as express.Request, res as any) return VerifyGet.default(req as express.Request, res as any);
})
.then(function () { .then(function () {
assert.equal(204, res.status.getCall(0).args[0]); assert.equal(204, res.status.getCall(0).args[0]);
}); });
@ -113,23 +117,25 @@ describe("test authentication token verification", function () {
}); });
it("should not be authenticated when domain is not allowed for user", function () { it("should not be authenticated when domain is not allowed for user", function () {
const authSession = AuthenticationSession.get(req as any); return AuthenticationSession.get(req as any)
authSession.first_factor = true; .then(function (authSession: AuthenticationSession.AuthenticationSession) {
authSession.second_factor = true; authSession.first_factor = true;
authSession.userid = "myuser"; authSession.second_factor = true;
authSession.userid = "myuser";
req.headers.host = "test.example.com"; req.headers.host = "test.example.com";
accessController.isDomainAllowedForUser.returns(false); accessController.isDomainAllowedForUser.returns(false);
accessController.isDomainAllowedForUser.withArgs("test.example.com", "user", ["group1", "group2"]).returns(true); accessController.isDomainAllowedForUser.withArgs("test.example.com", "user", ["group1", "group2"]).returns(true);
return test_unauthorized_403({ return test_unauthorized_403({
first_factor: true, first_factor: true,
second_factor: true, second_factor: true,
userid: "user", userid: "user",
groups: ["group1", "group2"], groups: ["group1", "group2"],
email: undefined email: undefined
}); });
});
}); });
}); });
}); });