From 0a33b2d5ee2ec5cd1820d9273d64e73c58375460 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Thu, 21 Sep 2017 22:07:34 +0200 Subject: [PATCH] Add logs to detect redis connection issues earlier Before this fix, the application was simply crashing during execution when connection to redis was failing. Now, it is correctly handled with failing promises and logs have been enabled to clearly see the problem --- Gruntfile.js | 4 +- example/mongo/docker-compose.yml | 2 + package.json | 2 + src/client/lib/secondfactor/index.ts | 78 +++++------ src/server/lib/AuthenticationSession.ts | 69 ++++++---- src/server/lib/AuthenticationValidator.ts | 7 +- src/server/lib/FirstFactorValidator.ts | 10 +- src/server/lib/IdentityCheckMiddleware.ts | 10 +- src/server/lib/RestApi.ts | 49 +++---- .../SessionConfigurationBuilder.ts | 10 +- src/server/lib/routes/FirstFactorBlocker.ts | 23 ++-- src/server/lib/routes/firstfactor/get.ts | 34 +++-- src/server/lib/routes/firstfactor/post.ts | 102 +++++++------- .../lib/routes/password-reset/form/post.ts | 30 +++-- src/server/lib/routes/secondfactor/get.ts | 7 +- .../lib/routes/secondfactor/redirect.ts | 16 ++- .../totp/identity/RegistrationHandler.ts | 71 +++++----- .../lib/routes/secondfactor/totp/sign/post.ts | 13 +- .../u2f/identity/RegistrationHandler.ts | 26 ++-- .../routes/secondfactor/u2f/register/post.ts | 75 ++++++----- .../secondfactor/u2f/register_request/get.ts | 31 +++-- .../lib/routes/secondfactor/u2f/sign/post.ts | 24 ++-- .../secondfactor/u2f/sign_request/get.ts | 63 ++++----- src/server/lib/routes/verify/get.ts | 14 +- test/features/access-control.feature | 18 ++- test/features/authentication.feature | 21 +-- test/features/redirection.feature | 14 +- test/features/regulation.feature | 18 +-- test/features/resilience.feature | 10 +- test/features/restrictions.feature | 2 +- .../step_definitions/authentication.ts | 2 +- test/features/step_definitions/hooks.ts | 77 ++++++++++- test/features/support/world.ts | 3 + .../server/IdentityCheckMiddleware.test.ts | 9 +- .../server/routes/firstfactor/post.test.ts | 18 ++- .../server/routes/password-reset/post.test.ts | 84 +++++++----- .../totp/register/RegistrationHandler.test.ts | 14 +- .../secondfactor/totp/sign/post.test.ts | 14 +- .../u2f/identity/RegistrationHandler.test.ts | 14 +- .../secondfactor/u2f/register/post.test.ts | 86 +++++++----- .../u2f/register_request/get.test.ts | 126 +++++++++--------- .../routes/secondfactor/u2f/sign/post.test.ts | 20 +-- .../secondfactor/u2f/sign_request/get.test.ts | 20 +-- test/unit/server/routes/verify/get.test.ts | 46 ++++--- 44 files changed, 800 insertions(+), 586 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index e499db587..d8593e43a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -112,7 +112,7 @@ module.exports = function (grunt) { } }, client: { - files: ['src/client/**/*.ts', 'test/client/**/*.ts'], + files: ['src/client/**/*.ts'], tasks: ['build-dev'], options: { interrupt: true, @@ -120,7 +120,7 @@ module.exports = function (grunt) { } }, server: { - files: ['src/server/**/*.ts', 'test/server/**/*.ts'], + files: ['src/server/**/*.ts'], tasks: ['build-dev', 'run:docker-restart', 'run:make-dev-views' ], options: { interrupt: true, diff --git a/example/mongo/docker-compose.yml b/example/mongo/docker-compose.yml index 3af27f215..e63fa39fa 100644 --- a/example/mongo/docker-compose.yml +++ b/example/mongo/docker-compose.yml @@ -2,5 +2,7 @@ version: '2' services: mongo: image: mongo:3.4 + ports: + - "27017:27017" networks: - example-network diff --git a/package.json b/package.json index e2e8d20c0..12b899158 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "object-path": "^0.11.3", "pug": "^2.0.0-rc.2", "randomstring": "^1.1.5", + "redis": "^2.8.0", "speakeasy": "^2.0.0", "u2f": "^0.1.2", "winston": "^2.3.1", @@ -63,6 +64,7 @@ "@types/proxyquire": "^1.3.27", "@types/query-string": "^4.3.1", "@types/randomstring": "^1.1.5", + "@types/redis": "^2.6.0", "@types/request": "0.0.46", "@types/selenium-webdriver": "^3.0.4", "@types/sinon": "^2.2.1", diff --git a/src/client/lib/secondfactor/index.ts b/src/client/lib/secondfactor/index.ts index 4a60e101d..ca5aea4b8 100644 --- a/src/client/lib/secondfactor/index.ts +++ b/src/client/lib/secondfactor/index.ts @@ -11,52 +11,52 @@ import { QueryParametersRetriever } from "../QueryParametersRetriever"; export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) { - const notifierTotp = new Notifier(".notification-totp", $); - const notifierU2f = new Notifier(".notification-u2f", $); + const notifierTotp = new Notifier(".notification-totp", $); + const notifierU2f = new Notifier(".notification-u2f", $); - function onAuthenticationSuccess(data: any) { - const redirectUrl = QueryParametersRetriever.get("redirect"); - if (redirectUrl) - window.location.href = redirectUrl; - else - window.location.href = Endpoints.FIRST_FACTOR_GET; - } + function onAuthenticationSuccess(data: any) { + const redirectUrl = QueryParametersRetriever.get("redirect"); + if (redirectUrl) + window.location.href = redirectUrl; + else + window.location.href = Endpoints.FIRST_FACTOR_GET; + } - function onSecondFactorTotpSuccess(data: any) { - onAuthenticationSuccess(data); - } + function onSecondFactorTotpSuccess(data: any) { + onAuthenticationSuccess(data); + } - function onSecondFactorTotpFailure(err: Error) { - notifierTotp.error("Problem with TOTP validation."); - } + function onSecondFactorTotpFailure(err: Error) { + notifierTotp.error("Problem with TOTP validation."); + } - function onU2fAuthenticationSuccess(data: any) { - onAuthenticationSuccess(data); - } + function onU2fAuthenticationSuccess(data: any) { + onAuthenticationSuccess(data); + } - function onU2fAuthenticationFailure() { - notifierU2f.error("Problem with U2F validation. Did you register before authenticating?"); - } + function onU2fAuthenticationFailure() { + notifierU2f.error("Problem with U2F validation. Did you register before authenticating?"); + } - function onTOTPFormSubmitted(): boolean { - const token = $(Constants.TOTP_TOKEN_SELECTOR).val(); - jslogger.debug("TOTP token is %s", token); + function onTOTPFormSubmitted(): boolean { + const token = $(Constants.TOTP_TOKEN_SELECTOR).val(); + jslogger.debug("TOTP token is %s", token); - TOTPValidator.validate(token, $) - .then(onSecondFactorTotpSuccess) - .catch(onSecondFactorTotpFailure); - return false; - } + TOTPValidator.validate(token, $) + .then(onSecondFactorTotpSuccess) + .catch(onSecondFactorTotpFailure); + return false; + } - function onU2FFormSubmitted(): boolean { - jslogger.debug("Start U2F authentication"); - U2FValidator.validate($, notifierU2f, U2fApi) - .then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure); - return false; - } + function onU2FFormSubmitted(): boolean { + jslogger.debug("Start U2F authentication"); + U2FValidator.validate($, notifierU2f, U2fApi) + .then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure); + return false; + } - $(window.document).ready(function () { - $(Constants.TOTP_FORM_SELECTOR).on("submit", onTOTPFormSubmitted); - $(Constants.U2F_FORM_SELECTOR).on("submit", onU2FFormSubmitted); - }); + $(window.document).ready(function () { + $(Constants.TOTP_FORM_SELECTOR).on("submit", onTOTPFormSubmitted); + $(Constants.U2F_FORM_SELECTOR).on("submit", onU2FFormSubmitted); + }); } \ No newline at end of file diff --git a/src/server/lib/AuthenticationSession.ts b/src/server/lib/AuthenticationSession.ts index 287034e4b..0b6214294 100644 --- a/src/server/lib/AuthenticationSession.ts +++ b/src/server/lib/AuthenticationSession.ts @@ -2,39 +2,54 @@ import express = require("express"); import U2f = require("u2f"); +import { ServerVariablesHandler } from "./ServerVariablesHandler"; +import BluebirdPromise = require("bluebird"); export interface AuthenticationSession { + userid: string; + first_factor: boolean; + second_factor: boolean; + identity_check?: { + challenge: string; userid: string; - first_factor: boolean; - second_factor: boolean; - identity_check?: { - challenge: string; - userid: string; - }; - register_request?: U2f.Request; - sign_request?: U2f.Request; - email: 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 { - const authSession: AuthenticationSession = { - first_factor: false, - second_factor: false, - userid: undefined, - email: undefined, - groups: [], - register_request: undefined, - sign_request: undefined, - identity_check: undefined, - redirect: undefined - }; - req.session.auth = authSession; + const logger = ServerVariablesHandler.getLogger(req.app); + logger.debug("Authentication session %s is being reset.", req.sessionID); + req.session.auth = Object.assign({}, INITIAL_AUTHENTICATION_SESSION, {}); } -export function get(req: express.Request): AuthenticationSession { - if (!req.session.auth) - reset(req); - return req.session.auth; +export function get(req: express.Request): BluebirdPromise { + const logger = ServerVariablesHandler.getLogger(req.app); + if (!req.session) { + 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); } \ No newline at end of file diff --git a/src/server/lib/AuthenticationValidator.ts b/src/server/lib/AuthenticationValidator.ts index 52a2b4498..95c6d3681 100644 --- a/src/server/lib/AuthenticationValidator.ts +++ b/src/server/lib/AuthenticationValidator.ts @@ -9,10 +9,11 @@ import AuthenticationSession = require("./AuthenticationSession"); export function validate(req: express.Request): BluebirdPromise { return FirstFactorValidator.validate(req) .then(function () { - const authSession = AuthenticationSession.get(req); + return AuthenticationSession.get(req); + }) + .then(function (authSession: AuthenticationSession.AuthenticationSession) { if (!authSession.second_factor) - return BluebirdPromise.reject("No second factor variable"); - + return BluebirdPromise.reject("No second factor variable."); return BluebirdPromise.resolve(); }); } \ No newline at end of file diff --git a/src/server/lib/FirstFactorValidator.ts b/src/server/lib/FirstFactorValidator.ts index d82a1ad2e..678dfde13 100644 --- a/src/server/lib/FirstFactorValidator.ts +++ b/src/server/lib/FirstFactorValidator.ts @@ -6,9 +6,11 @@ import Exceptions = require("./Exceptions"); import AuthenticationSession = require("./AuthenticationSession"); export function validate(req: express.Request): BluebirdPromise { - const authSession = AuthenticationSession.get(req); - if (!authSession.userid || !authSession.first_factor) - return BluebirdPromise.reject(new Exceptions.FirstFactorValidationError("First factor has not been validated yet.")); + return AuthenticationSession.get(req) + .then(function (authSession: AuthenticationSession.AuthenticationSession) { + 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(); + }); } \ No newline at end of file diff --git a/src/server/lib/IdentityCheckMiddleware.ts b/src/server/lib/IdentityCheckMiddleware.ts index 8523f9f05..84d8631f6 100644 --- a/src/server/lib/IdentityCheckMiddleware.ts +++ b/src/server/lib/IdentityCheckMiddleware.ts @@ -10,7 +10,7 @@ import { IUserDataStore } from "./storage/IUserDataStore"; import { Winston } from "../../types/Dependencies"; import express = require("express"); import ErrorReplies = require("./ErrorReplies"); -import { ServerVariablesHandler } from "./ServerVariablesHandler"; +import { ServerVariablesHandler } from "./ServerVariablesHandler"; import AuthenticationSession = require("./AuthenticationSession"); 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 userDataStore = ServerVariablesHandler.getUserDataStore(req.app); - const authSession = AuthenticationSession.get(req); + let authSession: AuthenticationSession.AuthenticationSession; const identityToken = objectPath.get(req, "query.identity_token"); 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 () { return handler.postValidationInit(req); }) + .then(function () { + return AuthenticationSession.get(req); + }) + .then(function (_authSession: AuthenticationSession.AuthenticationSession) { + authSession = _authSession; + }) .then(function () { return consumeToken(identityToken, handler.challenge(), userDataStore, logger); }) diff --git a/src/server/lib/RestApi.ts b/src/server/lib/RestApi.ts index b4ec25411..efea69063 100644 --- a/src/server/lib/RestApi.ts +++ b/src/server/lib/RestApi.ts @@ -1,5 +1,5 @@ -import express = require("express"); +import Express = require("express"); import { UserDataStore } from "./storage/UserDataStore"; 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 U2FRegisterRequestGet = require("./routes/secondfactor/u2f/register_request/get"); - import ResetPasswordFormPost = require("./routes/password-reset/form/post"); import ResetPasswordRequestPost = require("./routes/password-reset/request/get"); import Error401Get = require("./routes/error/401/get"); import Error403Get = require("./routes/error/403/get"); import Error404Get = require("./routes/error/404/get"); - +import { ServerVariablesHandler } from "./ServerVariablesHandler"; 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 { - static setup(app: express.Application): void { - app.get(Endpoints.FIRST_FACTOR_GET, FirstFactorGet.default); - app.get(Endpoints.SECOND_FACTOR_GET, SecondFactorGet.default); - app.get(Endpoints.LOGOUT_GET, LogoutGet.default); + static setup(app: Express.Application): void { + app.get(Endpoints.FIRST_FACTOR_GET, withLog(FirstFactorGet.default)); + app.get(Endpoints.SECOND_FACTOR_GET, withLog(SecondFactorGet.default)); + app.get(Endpoints.LOGOUT_GET, withLog(LogoutGet.default)); IdentityCheckMiddleware.register(app, Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET, 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, Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET, new ResetPasswordIdentityHandler()); - app.get(Endpoints.RESET_PASSWORD_REQUEST_GET, ResetPasswordRequestPost.default); - app.post(Endpoints.RESET_PASSWORD_FORM_POST, ResetPasswordFormPost.default); + app.get(Endpoints.RESET_PASSWORD_REQUEST_GET, withLog(ResetPasswordRequestPost.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.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); + 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)); } } diff --git a/src/server/lib/configuration/SessionConfigurationBuilder.ts b/src/server/lib/configuration/SessionConfigurationBuilder.ts index 6e0c32afa..3560cbb2b 100644 --- a/src/server/lib/configuration/SessionConfigurationBuilder.ts +++ b/src/server/lib/configuration/SessionConfigurationBuilder.ts @@ -2,6 +2,7 @@ import ExpressSession = require("express-session"); import { AppConfiguration } from "./Configuration"; import { GlobalDependencies } from "../../../types/Dependencies"; +import Redis = require("redis"); export class SessionConfigurationBuilder { @@ -21,9 +22,16 @@ export class SessionConfigurationBuilder { let redisOptions; if (configuration.session.redis.host && configuration.session.redis.port) { - redisOptions = { + const client = Redis.createClient({ host: configuration.session.redis.host, port: configuration.session.redis.port + }); + client.on("error", function (err: Error) { + console.error("Redis error:", err); + }); + redisOptions = { + client: client, + logErrors: true }; } diff --git a/src/server/lib/routes/FirstFactorBlocker.ts b/src/server/lib/routes/FirstFactorBlocker.ts index fb2ea86d8..366e0c842 100644 --- a/src/server/lib/routes/FirstFactorBlocker.ts +++ b/src/server/lib/routes/FirstFactorBlocker.ts @@ -5,21 +5,22 @@ import FirstFactorValidator = require("../FirstFactorValidator"); import Exceptions = require("../Exceptions"); import ErrorReplies = require("../ErrorReplies"); import objectPath = require("object-path"); -import { ServerVariablesHandler } from "../ServerVariablesHandler"; +import { ServerVariablesHandler } from "../ServerVariablesHandler"; import AuthenticationSession = require("../AuthenticationSession"); type Handler = (req: express.Request, res: express.Response) => BluebirdPromise; export default function (callback: Handler): Handler { - return function (req: express.Request, res: express.Response): BluebirdPromise { - const logger = ServerVariablesHandler.getLogger(req.app); + return function (req: express.Request, res: express.Response): BluebirdPromise { + const logger = ServerVariablesHandler.getLogger(req.app); - const authSession = AuthenticationSession.get(req); - logger.debug("AuthSession is %s", JSON.stringify(authSession)); - return FirstFactorValidator.validate(req) - .then(function () { - return callback(req, res); - }) - .catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(res, logger)); - }; + return AuthenticationSession.get(req) + .then(function (authSession) { + return FirstFactorValidator.validate(req); + }) + .then(function () { + return callback(req, res); + }) + .catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(res, logger)); + }; } \ No newline at end of file diff --git a/src/server/lib/routes/firstfactor/get.ts b/src/server/lib/routes/firstfactor/get.ts index 6f6d065cb..981012cca 100644 --- a/src/server/lib/routes/firstfactor/get.ts +++ b/src/server/lib/routes/firstfactor/get.ts @@ -5,19 +5,29 @@ import winston = require("winston"); import Endpoints = require("../../../endpoints"); import AuthenticationValidator = require("../../AuthenticationValidator"); import { ServerVariablesHandler } from "../../ServerVariablesHandler"; +import BluebirdPromise = require("bluebird"); -export default function (req: express.Request, res: express.Response) { - const logger = ServerVariablesHandler.getLogger(req.app); +export default function (req: express.Request, res: express.Response): BluebirdPromise { + 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) - .then(function () { - res.render("already-logged-in", { logout_endpoint: Endpoints.LOGOUT_GET }); - }, function () { - res.render("firstfactor", { - first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST, - reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET - }); - }); + return AuthenticationValidator.validate(req) + .then(function () { + const redirectUrl = req.query.redirect; + if (redirectUrl) { + res.redirect(redirectUrl); + return BluebirdPromise.resolve(); + } + 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(); + }); } \ No newline at end of file diff --git a/src/server/lib/routes/firstfactor/post.ts b/src/server/lib/routes/firstfactor/post.ts index 0cb48c401..62aa2fa24 100644 --- a/src/server/lib/routes/firstfactor/post.ts +++ b/src/server/lib/routes/firstfactor/post.ts @@ -12,63 +12,67 @@ import { ServerVariablesHandler } from "../../ServerVariablesHandler"; import AuthenticationSession = require("../../AuthenticationSession"); export default function (req: express.Request, res: express.Response): BluebirdPromise { - const username: string = req.body.username; - const password: string = req.body.password; + const username: string = req.body.username; + const password: string = req.body.password; - const logger = ServerVariablesHandler.getLogger(req.app); - const ldap = ServerVariablesHandler.getLdapAuthenticator(req.app); - const config = ServerVariablesHandler.getConfiguration(req.app); + const logger = ServerVariablesHandler.getLogger(req.app); + const ldap = ServerVariablesHandler.getLdapAuthenticator(req.app); + const config = ServerVariablesHandler.getConfiguration(req.app); - if (!username || !password) { - const err = new Error("No username or password"); - ErrorReplies.replyWithError401(res, logger)(err); - return BluebirdPromise.reject(err); - } + if (!username || !password) { + const err = new Error("No username or password"); + ErrorReplies.replyWithError401(res, logger)(err); + return BluebirdPromise.reject(err); + } - const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app); - const accessController = ServerVariablesHandler.getAccessController(req.app); - const authSession = AuthenticationSession.get(req); + const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app); + const accessController = ServerVariablesHandler.getAccessController(req.app); + let authSession: AuthenticationSession.AuthenticationSession; - logger.info("1st factor: Starting authentication of user \"%s\"", username); - logger.debug("1st factor: Start bind operation against LDAP"); - logger.debug("1st factor: username=%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: username=%s", username); - return regulator.regulate(username) - .then(function () { - logger.info("1st factor: No regulation applied."); - return ldap.authenticate(username, password); - }) - .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; + return AuthenticationSession.get(req) + .then(function (_authSession: AuthenticationSession.AuthenticationSession) { + authSession = _authSession; + return regulator.regulate(username); + }) + .then(function () { + logger.info("1st factor: No regulation applied."); + return ldap.authenticate(username, password); + }) + .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 groups: string[] = groupsAndEmails.groups; + const emails: string[] = groupsAndEmails.emails; + const groups: string[] = groupsAndEmails.groups; - if (!emails || emails.length <= 0) { - const errMessage = "No emails found. The user should have at least one email address to reset password."; - logger.error("1s factor: %s", errMessage); - return BluebirdPromise.reject(new Error(errMessage)); - } + if (!emails || emails.length <= 0) { + const errMessage = "No emails found. The user should have at least one email address to reset password."; + logger.error("1s factor: %s", errMessage); + return BluebirdPromise.reject(new Error(errMessage)); + } - authSession.email = emails[0]; - authSession.groups = groups; + authSession.email = emails[0]; + authSession.groups = groups; - logger.debug("1st factor: Mark successful authentication to regulator."); - regulator.mark(username, true); + logger.debug("1st factor: Mark successful authentication to regulator."); + regulator.mark(username, true); - res.status(204); - res.send(); - return BluebirdPromise.resolve(); - }) - .catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(res, logger)) - .catch(exceptions.LdapBindError, function (err: Error) { - regulator.mark(username, false); - return ErrorReplies.replyWithError401(res, logger)(err); - }) - .catch(exceptions.AuthenticationRegulationError, ErrorReplies.replyWithError403(res, logger)) - .catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError401(res, logger)) - .catch(ErrorReplies.replyWithError500(res, logger)); + res.status(204); + res.send(); + return BluebirdPromise.resolve(); + }) + .catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(res, logger)) + .catch(exceptions.LdapBindError, function (err: Error) { + regulator.mark(username, false); + return ErrorReplies.replyWithError401(res, logger)(err); + }) + .catch(exceptions.AuthenticationRegulationError, ErrorReplies.replyWithError403(res, logger)) + .catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError401(res, logger)) + .catch(ErrorReplies.replyWithError500(res, logger)); } diff --git a/src/server/lib/routes/password-reset/form/post.ts b/src/server/lib/routes/password-reset/form/post.ts index dfd8cf16e..fe11c6601 100644 --- a/src/server/lib/routes/password-reset/form/post.ts +++ b/src/server/lib/routes/password-reset/form/post.ts @@ -3,7 +3,7 @@ import express = require("express"); import BluebirdPromise = require("bluebird"); import objectPath = require("object-path"); import exceptions = require("../../../Exceptions"); -import { ServerVariablesHandler } from "../../../ServerVariablesHandler"; +import { ServerVariablesHandler } from "../../../ServerVariablesHandler"; import AuthenticationSession = require("../../../AuthenticationSession"); import ErrorReplies = require("../../../ErrorReplies"); @@ -12,23 +12,27 @@ import Constants = require("./../constants"); export default function (req: express.Request, res: express.Response): BluebirdPromise { const logger = ServerVariablesHandler.getLogger(req.app); const ldapPasswordUpdater = ServerVariablesHandler.getLdapPasswordUpdater(req.app); - const authSession = AuthenticationSession.get(req); - + let authSession: AuthenticationSession.AuthenticationSession; const newPassword = objectPath.get(req, "body.password"); - const userid = authSession.identity_check.userid; - const challenge = authSession.identity_check.challenge; - if (challenge != Constants.CHALLENGE) { - res.status(403); - res.send(); - return; - } + return AuthenticationSession.get(req) + .then(function (_authSession: AuthenticationSession.AuthenticationSession) { + authSession = _authSession; + logger.info("POST reset-password: User %s wants to reset his/her password.", + authSession.identity_check.userid); + 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 () { - 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); res.status(204); res.send(); diff --git a/src/server/lib/routes/secondfactor/get.ts b/src/server/lib/routes/secondfactor/get.ts index 4d4e27de3..f7520fae1 100644 --- a/src/server/lib/routes/secondfactor/get.ts +++ b/src/server/lib/routes/secondfactor/get.ts @@ -1,14 +1,17 @@ -import express = require("express"); +import Express = require("express"); import Endpoints = require("../../../endpoints"); import FirstFactorBlocker = require("../FirstFactorBlocker"); import BluebirdPromise = require("bluebird"); +import { ServerVariablesHandler } from "../../ServerVariablesHandler"; const TEMPLATE_NAME = "secondfactor"; export default FirstFactorBlocker.default(handler); -function handler(req: express.Request, res: express.Response): BluebirdPromise { +function handler(req: Express.Request, res: Express.Response): BluebirdPromise { + const logger = ServerVariablesHandler.getLogger(req.app); + logger.debug("secondfactor request is coming from %s", req.originalUrl); res.render(TEMPLATE_NAME, { totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET, u2f_identity_start_endpoint: Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET diff --git a/src/server/lib/routes/secondfactor/redirect.ts b/src/server/lib/routes/secondfactor/redirect.ts index 2e6f5558c..0ab94845d 100644 --- a/src/server/lib/routes/secondfactor/redirect.ts +++ b/src/server/lib/routes/secondfactor/redirect.ts @@ -5,11 +5,15 @@ import winston = require("winston"); import Endpoints = require("../../../endpoints"); import { ServerVariablesHandler } from "../../ServerVariablesHandler"; import AuthenticationSession = require("../../AuthenticationSession"); +import BluebirdPromise = require("bluebird"); -export default function (req: express.Request, res: express.Response) { - const authSession = AuthenticationSession.get(req); - const redirectUrl = req.query.redirect || Endpoints.FIRST_FACTOR_GET; - res.json({ - redirection_url: redirectUrl - }); +export default function (req: express.Request, res: express.Response): BluebirdPromise { + return AuthenticationSession.get(req) + .then(function (authSession: AuthenticationSession.AuthenticationSession) { + const redirectUrl = req.query.redirect || Endpoints.FIRST_FACTOR_GET; + res.json({ + redirection_url: redirectUrl + }); + return BluebirdPromise.resolve(); + }); } \ No newline at end of file diff --git a/src/server/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts b/src/server/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts index 4ac56d99b..b4adee977 100644 --- a/src/server/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts +++ b/src/server/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts @@ -9,7 +9,7 @@ import { PRE_VALIDATION_TEMPLATE } from "../../../../IdentityCheckPreValidationT import Constants = require("../constants"); import Endpoints = require("../../../../../endpoints"); import ErrorReplies = require("../../../../ErrorReplies"); -import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; +import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import AuthenticationSession = require("../../../../AuthenticationSession"); import FirstFactorValidator = require("../../../../FirstFactorValidator"); @@ -21,19 +21,21 @@ export default class RegistrationHandler implements IdentityValidable { } private retrieveIdentity(req: express.Request): BluebirdPromise { - const authSession = AuthenticationSession.get(req); - const userid = authSession.userid; - const email = authSession.email; + return AuthenticationSession.get(req) + .then(function (authSession: AuthenticationSession.AuthenticationSession) { + const userid = authSession.userid; + const email = authSession.email; - if (!(userid && email)) { - return BluebirdPromise.reject(new Error("User ID or email is missing")); - } + if (!(userid && email)) { + return BluebirdPromise.reject(new Error("User ID or email is missing")); + } - const identity = { - email: email, - userid: userid - }; - return BluebirdPromise.resolve(identity); + const identity = { + email: email, + userid: userid + }; + return BluebirdPromise.resolve(identity); + }); } preValidationInit(req: express.Request): BluebirdPromise { @@ -52,33 +54,34 @@ export default class RegistrationHandler implements IdentityValidable { return FirstFactorValidator.validate(req); } - postValidationResponse(req: express.Request, res: express.Response) { + postValidationResponse(req: express.Request, res: express.Response): BluebirdPromise { 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; - const challenge = authSession.identity_check.challenge; + if (challenge != Constants.CHALLENGE || !userid) { + res.status(403); + res.send(); + return BluebirdPromise.reject(new Error("Bad challenge.")); + } - if (challenge != Constants.CHALLENGE || !userid) { - res.status(403); - res.send(); - return; - } + const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); + const totpGenerator = ServerVariablesHandler.getTOTPGenerator(req.app); + const secret = totpGenerator.generate(); - const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); - const totpGenerator = ServerVariablesHandler.getTOTPGenerator(req.app); - const secret = totpGenerator.generate(); + logger.debug("POST new-totp-secret: save the TOTP secret in DB"); + return userDataStore.saveTOTPSecret(userid, secret) + .then(function () { + objectPath.set(req, "session", undefined); - logger.debug("POST new-totp-secret: save the TOTP secret in DB"); - userDataStore.saveTOTPSecret(userid, secret) - .then(function () { - objectPath.set(req, "session", undefined); - - res.render(Constants.TEMPLATE_NAME, { - base32_secret: secret.base32, - otpauth_url: secret.otpauth_url, - 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)); } diff --git a/src/server/lib/routes/secondfactor/totp/sign/post.ts b/src/server/lib/routes/secondfactor/totp/sign/post.ts index 91564a827..62059249a 100644 --- a/src/server/lib/routes/secondfactor/totp/sign/post.ts +++ b/src/server/lib/routes/secondfactor/totp/sign/post.ts @@ -16,17 +16,18 @@ const UNAUTHORIZED_MESSAGE = "Unauthorized access"; export default FirstFactorBlocker(handler); export function handler(req: express.Request, res: express.Response): BluebirdPromise { + let authSession: AuthenticationSession.AuthenticationSession; 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 totpValidator = ServerVariablesHandler.getTOTPValidator(req.app); const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); - logger.debug("POST 2ndfactor totp: Fetching secret for user %s", userid); - return userDataStore.retrieveTOTPSecret(userid) + return AuthenticationSession.get(req) + .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) { logger.debug("POST 2ndfactor totp: TOTP secret is %s", JSON.stringify(doc)); return totpValidator.validate(token, doc.secret.base32); diff --git a/src/server/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts b/src/server/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts index a76b24bc8..002904e74 100644 --- a/src/server/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts +++ b/src/server/lib/routes/secondfactor/u2f/identity/RegistrationHandler.ts @@ -20,20 +20,22 @@ export default class RegistrationHandler implements IdentityValidable { return CHALLENGE; } - private retrieveIdentity(req: express.Request) { - const authSession = AuthenticationSession.get(req); - const userid = authSession.userid; - const email = authSession.email; + private retrieveIdentity(req: express.Request): BluebirdPromise { + return AuthenticationSession.get(req) + .then(function (authSession: AuthenticationSession.AuthenticationSession) { + const userid = authSession.userid; + const email = authSession.email; - if (!(userid && email)) { - return BluebirdPromise.reject("User ID or email is missing"); - } + if (!(userid && email)) { + return BluebirdPromise.reject(new Error("User ID or email is missing")); + } - const identity = { - email: email, - userid: userid - }; - return BluebirdPromise.resolve(identity); + const identity = { + email: email, + userid: userid + }; + return BluebirdPromise.resolve(identity); + }); } preValidationInit(req: express.Request): BluebirdPromise { diff --git a/src/server/lib/routes/secondfactor/u2f/register/post.ts b/src/server/lib/routes/secondfactor/u2f/register/post.ts index 6e3f0abf2..a0bae8b73 100644 --- a/src/server/lib/routes/secondfactor/u2f/register/post.ts +++ b/src/server/lib/routes/secondfactor/u2f/register/post.ts @@ -18,53 +18,54 @@ export default FirstFactorBlocker(handler); function handler(req: express.Request, res: express.Response): BluebirdPromise { - const authSession = AuthenticationSession.get(req); - const registrationRequest = authSession.register_request; + let authSession: AuthenticationSession.AuthenticationSession; + 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.send(); return BluebirdPromise.reject(new Error("No registration request")); - } + } - if (!authSession.identity_check + if (!authSession.identity_check || authSession.identity_check.challenge != "u2f-register") { res.status(403); res.send(); 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); - const u2f = ServerVariablesHandler.getU2F(req.app); - const userid = authSession.userid; - const appid = u2f_common.extract_app_id(req); - const logger = ServerVariablesHandler.getLogger(req.app); + return BluebirdPromise.resolve(u2f.checkRegistration(registrationRequest, registrationResponse)); + }) + .then(function (u2fResult: U2f.RegistrationResult | U2f.Error): BluebirdPromise { + if (objectPath.has(u2fResult, "errorCode")) + return BluebirdPromise.reject(new Error("Error while registering.")); - const registrationResponse: U2f.RegistrationData = req.body; - - logger.info("U2F register: Finishing registration"); - logger.debug("U2F register: registrationRequest = %s", JSON.stringify(registrationRequest)); - logger.debug("U2F register: registrationResponse = %s", JSON.stringify(registrationResponse)); - - BluebirdPromise.resolve(u2f.checkRegistration(registrationRequest, registrationResponse)) - .then(function (u2fResult: U2f.RegistrationResult | U2f.Error): BluebirdPromise { - if (objectPath.has(u2fResult, "errorCode")) - return BluebirdPromise.reject(new Error("Error while registering.")); - - const registrationResult: U2f.RegistrationResult = u2fResult as U2f.RegistrationResult; - logger.info("U2F register: Store regisutration and reply"); - logger.debug("U2F register: registration = %s", JSON.stringify(registrationResult)); - const registration: U2FRegistration = { - 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)); + const registrationResult: U2f.RegistrationResult = u2fResult as U2f.RegistrationResult; + logger.info("U2F register: Store regisutration and reply"); + logger.debug("U2F register: registration = %s", JSON.stringify(registrationResult)); + const registration: U2FRegistration = { + keyHandle: registrationResult.keyHandle, + publicKey: registrationResult.publicKey + }; + return userDataStore.saveU2FRegistration(authSession.userid, appid, registration); + }) + .then(function () { + authSession.identity_check = undefined; + redirect(req, res); + return BluebirdPromise.resolve(); + }) + .catch(ErrorReplies.replyWithError500(res, logger)); } diff --git a/src/server/lib/routes/secondfactor/u2f/register_request/get.ts b/src/server/lib/routes/secondfactor/u2f/register_request/get.ts index e0d28fb98..9b13698f1 100644 --- a/src/server/lib/routes/secondfactor/u2f/register_request/get.ts +++ b/src/server/lib/routes/secondfactor/u2f/register_request/get.ts @@ -8,29 +8,34 @@ import express = require("express"); import U2f = require("u2f"); import FirstFactorBlocker from "../../../FirstFactorBlocker"; import ErrorReplies = require("../../../../ErrorReplies"); -import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; +import {  ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import AuthenticationSession = require("../../../../AuthenticationSession"); export default FirstFactorBlocker(handler); function handler(req: express.Request, res: express.Response): BluebirdPromise { const logger = ServerVariablesHandler.getLogger(req.app); - const authSession = AuthenticationSession.get(req); + let authSession: AuthenticationSession.AuthenticationSession; - if (!authSession.identity_check - || authSession.identity_check.challenge != "u2f-register") { - res.status(403); - res.send(); - return; - } + return AuthenticationSession.get(req) + .then(function (_authSession: AuthenticationSession.AuthenticationSession) { + authSession = _authSession; - const u2f = ServerVariablesHandler.getU2F(req.app); - const appid: string = u2f_common.extract_app_id(req); + if (!authSession.identity_check + || 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)); - logger.info("U2F register_request: Starting registration for appId %s", appid); + const u2f = ServerVariablesHandler.getU2F(req.app); + 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) { logger.debug("U2F register_request: registrationRequest = %s", JSON.stringify(registrationRequest)); authSession.register_request = registrationRequest; diff --git a/src/server/lib/routes/secondfactor/u2f/sign/post.ts b/src/server/lib/routes/secondfactor/u2f/sign/post.ts index 610b498cd..893751240 100644 --- a/src/server/lib/routes/secondfactor/u2f/sign/post.ts +++ b/src/server/lib/routes/secondfactor/u2f/sign/post.ts @@ -11,7 +11,7 @@ import exceptions = require("../../../../Exceptions"); import FirstFactorBlocker from "../../../FirstFactorBlocker"; import redirect from "../../redirect"; import ErrorReplies = require("../../../../ErrorReplies"); -import {  ServerVariablesHandler } from "../../../../ServerVariablesHandler"; +import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import AuthenticationSession = require("../../../../AuthenticationSession"); export default FirstFactorBlocker(handler); @@ -19,17 +19,21 @@ export default FirstFactorBlocker(handler); export function handler(req: express.Request, res: express.Response): BluebirdPromise { const logger = ServerVariablesHandler.getLogger(req.app); const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); - const authSession = AuthenticationSession.get(req); + let authSession: AuthenticationSession.AuthenticationSession; - if (!authSession.sign_request) { - const err = new Error("No sign request"); - ErrorReplies.replyWithError401(res, logger)(err); - return BluebirdPromise.reject(err); - } + return AuthenticationSession.get(req) + .then(function (_authSession: AuthenticationSession.AuthenticationSession) { + authSession = _authSession; + 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 appid = u2f_common.extract_app_id(req); - return userDataStore.retrieveU2FRegistration(userid, appid) + const userid = authSession.userid; + const appid = u2f_common.extract_app_id(req); + return userDataStore.retrieveU2FRegistration(userid, appid); + }) .then(function (doc: U2FRegistrationDocument): BluebirdPromise { const appId = u2f_common.extract_app_id(req); const u2f = ServerVariablesHandler.getU2F(req.app); diff --git a/src/server/lib/routes/secondfactor/u2f/sign_request/get.ts b/src/server/lib/routes/secondfactor/u2f/sign_request/get.ts index 98ff0f572..0755a01ac 100644 --- a/src/server/lib/routes/secondfactor/u2f/sign_request/get.ts +++ b/src/server/lib/routes/secondfactor/u2f/sign_request/get.ts @@ -11,43 +11,46 @@ import exceptions = require("../../../../Exceptions"); import { SignMessage } from "./SignMessage"; import FirstFactorBlocker from "../../../FirstFactorBlocker"; import ErrorReplies = require("../../../../ErrorReplies"); -import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; +import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import AuthenticationSession = require("../../../../AuthenticationSession"); export default FirstFactorBlocker(handler); export function handler(req: express.Request, res: express.Response): BluebirdPromise { - const logger = ServerVariablesHandler.getLogger(req.app); - const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); - const authSession = AuthenticationSession.get(req); + const logger = ServerVariablesHandler.getLogger(req.app); + const userDataStore = ServerVariablesHandler.getUserDataStore(req.app); + let authSession: AuthenticationSession.AuthenticationSession; + const appId = u2f_common.extract_app_id(req); - const userId = authSession.userid; - const appId = u2f_common.extract_app_id(req); - return userDataStore.retrieveU2FRegistration(userId, appId) - .then(function (doc: U2FRegistrationDocument): BluebirdPromise { - if (!doc) - return BluebirdPromise.reject(new exceptions.AccessDeniedError("No U2F registration found")); + return AuthenticationSession.get(req) + .then(function (_authSession: AuthenticationSession.AuthenticationSession) { + authSession = _authSession; + return userDataStore.retrieveU2FRegistration(authSession.userid, appId); + }) + .then(function (doc: U2FRegistrationDocument): BluebirdPromise { + if (!doc) + return BluebirdPromise.reject(new exceptions.AccessDeniedError("No U2F registration found")); - const u2f = ServerVariablesHandler.getU2F(req.app); - const appId: string = u2f_common.extract_app_id(req); - 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)); + const u2f = ServerVariablesHandler.getU2F(req.app); + const appId: string = u2f_common.extract_app_id(req); + 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)); - const request = u2f.request(appId, doc.registration.keyHandle); - const authenticationMessage: SignMessage = { - request: request, - keyHandle: doc.registration.keyHandle - }; - return BluebirdPromise.resolve(authenticationMessage); - }) - .then(function (authenticationMessage: SignMessage) { - logger.info("U2F sign_request: Store authentication request and reply"); - logger.debug("U2F sign_request: authenticationRequest=%s", authenticationMessage); - authSession.sign_request = authenticationMessage.request; - res.json(authenticationMessage); - return BluebirdPromise.resolve(); - }) - .catch(exceptions.AccessDeniedError, ErrorReplies.replyWithError401(res, logger)) - .catch(ErrorReplies.replyWithError500(res, logger)); + const request = u2f.request(appId, doc.registration.keyHandle); + const authenticationMessage: SignMessage = { + request: request, + keyHandle: doc.registration.keyHandle + }; + return BluebirdPromise.resolve(authenticationMessage); + }) + .then(function (authenticationMessage: SignMessage) { + logger.info("U2F sign_request: Store authentication request and reply"); + logger.debug("U2F sign_request: authenticationRequest=%s", authenticationMessage); + authSession.sign_request = authenticationMessage.request; + res.json(authenticationMessage); + return BluebirdPromise.resolve(); + }) + .catch(exceptions.AccessDeniedError, ErrorReplies.replyWithError401(res, logger)) + .catch(ErrorReplies.replyWithError500(res, logger)); } diff --git a/src/server/lib/routes/verify/get.ts b/src/server/lib/routes/verify/get.ts index 57485074b..45b7974b1 100644 --- a/src/server/lib/routes/verify/get.ts +++ b/src/server/lib/routes/verify/get.ts @@ -8,18 +8,22 @@ import exceptions = require("../../Exceptions"); import winston = require("winston"); import AuthenticationValidator = require("../../AuthenticationValidator"); import ErrorReplies = require("../../ErrorReplies"); -import { ServerVariablesHandler } from "../../ServerVariablesHandler"; +import {  ServerVariablesHandler } from "../../ServerVariablesHandler"; import AuthenticationSession = require("../../AuthenticationSession"); function verify_filter(req: express.Request, res: express.Response): BluebirdPromise { const logger = ServerVariablesHandler.getLogger(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)); - res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] + req.headers["x-original-uri"])); + return AuthenticationSession.get(req) + .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 () { const username = authSession.userid; const groups = authSession.groups; diff --git a/test/features/access-control.feature b/test/features/access-control.feature index d853a62ba..7f1663512 100644 --- a/test/features/access-control.feature +++ b/test/features/access-control.feature @@ -1,7 +1,11 @@ Feature: User has access restricted access to domains + @need-registered-user-john 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: | url | | 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://mx2.mail.test.local:8080/secret.html | + @need-registered-user-bob 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: | url | | https://public.test.local:8080/secret.html | @@ -24,8 +32,12 @@ Feature: User has access restricted access to domains | url | | https://secret1.test.local:8080/secret.html | + @need-registered-user-harry 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: | url | | https://public.test.local:8080/secret.html | diff --git a/test/features/authentication.feature b/test/features/authentication.feature index 5625f6fea..e1ed84e28 100644 --- a/test/features/authentication.feature +++ b/test/features/authentication.feature @@ -14,7 +14,7 @@ Feature: User validate first factor And I click on "Sign in" 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/" And I login with user "john" and password "password" And I register a TOTP secret called "Sec0" @@ -31,23 +31,6 @@ Feature: User validate first factor And I click on "TOTP" Then I get a notification of type "error" with message "Problem with TOTP validation." - Scenario: User logs out - 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 + Scenario: Logout redirects user to redirect URL given in parameter When I visit "https://auth.test.local:8080/logout?redirect=https://www.google.fr" Then I'm redirected to "https://www.google.fr" diff --git a/test/features/redirection.feature b/test/features/redirection.feature index 6ce54c648..9884a1398 100644 --- a/test/features/redirection.feature +++ b/test/features/redirection.feature @@ -5,19 +5,17 @@ Feature: User is correctly redirected When I click on the link to secret.test.local 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 - Given I'm on 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://public.test.local:8080/secret.html" - When I login with user "john" and password "badpassword" + When I visit "https://public.test.local:8080/secret.html" + And I login with user "john" and password "badpassword" And I clear field "username" 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" 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" And I visit "https://secret.test.local:8080/secret.html" 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 use "Sec0" as TOTP token handle And I click on "TOTP" - Then I'm redirected to "https://public.test.local:8080/secret.html" \ No newline at end of file + Then I'm redirected to "https://public.test.local:8080/secret.html" diff --git a/test/features/regulation.feature b/test/features/regulation.feature index 835d980d2..54e40b79a 100644 --- a/test/features/regulation.feature +++ b/test/features/regulation.feature @@ -1,14 +1,9 @@ Feature: Authelia regulates authentication to avoid brute force @needs-test-config + @need-registered-user-blackhat Scenario: Attacker tries too many authentication in a short period of time and get banned 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 "password" to "bad-password" 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." @needs-test-config + @need-registered-user-blackhat Scenario: User is unbanned after a configured amount of time - 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/" + Given I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html" And I set field "username" to "blackhat" And I set field "password" to "bad-password" And I click on "Sign in" @@ -45,7 +35,7 @@ Feature: Authelia regulates authentication to avoid brute force When I wait 6 seconds And I set field "password" to "password" 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" Then I have access to: | url | diff --git a/test/features/resilience.feature b/test/features/resilience.feature index 9a63dfd28..755cca02d 100644 --- a/test/features/resilience.feature +++ b/test/features/resilience.feature @@ -1,20 +1,18 @@ Feature: Authelia keeps user sessions despite the application restart + @need-authenticated-user-john Scenario: Session is still valid after Authelia restarts - When I register TOTP and login with user "john" and password "password" - And the application restarts + When the application restarts Then I have access to: | url | | https://secret.test.local:8080/secret.html | + @need-registered-user-john 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 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 use "Sec0" as TOTP token handle + And I use "REGISTERED" as TOTP token handle And I click on "TOTP" Then I have access to: | url | diff --git a/test/features/restrictions.feature b/test/features/restrictions.feature index e343d249a..c2cf9889d 100644 --- a/test/features/restrictions.feature +++ b/test/features/restrictions.feature @@ -1,6 +1,6 @@ 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 "" Then I get an error diff --git a/test/features/step_definitions/authentication.ts b/test/features/step_definitions/authentication.ts index ba07f82fb..a6285d4c8 100644 --- a/test/features/step_definitions/authentication.ts +++ b/test/features/step_definitions/authentication.ts @@ -6,7 +6,7 @@ import Speakeasy = require("speakeasy"); import CustomWorld = require("../support/world"); 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); }); diff --git a/test/features/step_definitions/hooks.ts b/test/features/step_definitions/hooks.ts index a39c96a5a..184a66d21 100644 --- a/test/features/step_definitions/hooks.ts +++ b/test/features/step_definitions/hooks.ts @@ -2,23 +2,90 @@ import Cucumber = require("cucumber"); import fs = require("fs"); import BluebirdPromise = require("bluebird"); 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); }); -Cucumber.defineSupportCode(function({ After, Before }) { +Cucumber.defineSupportCode(function ({ After, Before }) { const exec = BluebirdPromise.promisify(ChildProcess.exec); - After(function() { + After(function () { 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"); }); - 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"); }); + + 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 { + 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); }); \ No newline at end of file diff --git a/test/features/support/world.ts b/test/features/support/world.ts index fe5b5db48..8828ad9bd 100644 --- a/test/features/support/world.ts +++ b/test/features/support/world.ts @@ -91,6 +91,9 @@ function CustomWorld() { }; this.useTotpTokenHandle = function (totpSecretHandle: string) { + if (!this.totpSecrets[totpSecretHandle]) + throw new Error("No available TOTP token handle " + totpSecretHandle); + const token = Speakeasy.totp({ secret: this.totpSecrets[totpSecretHandle], encoding: "base32" diff --git a/test/unit/server/IdentityCheckMiddleware.test.ts b/test/unit/server/IdentityCheckMiddleware.test.ts index c2914359c..d187c9980 100644 --- a/test/unit/server/IdentityCheckMiddleware.test.ts +++ b/test/unit/server/IdentityCheckMiddleware.test.ts @@ -155,9 +155,14 @@ describe("test identity check process", function () { req.query.identity_token = "token"; req.session = {}; - const authSession = AuthenticationSession.get(req as any); + let authSession: AuthenticationSession.AuthenticationSession; 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"); }) .catch(function () { assert.equal(authSession.identity_check.userid, "user"); diff --git a/test/unit/server/routes/firstfactor/post.test.ts b/test/unit/server/routes/firstfactor/post.test.ts index 91ee1b746..4dd5e043b 100644 --- a/test/unit/server/routes/firstfactor/post.test.ts +++ b/test/unit/server/routes/firstfactor/post.test.ts @@ -45,6 +45,7 @@ describe("test the first factor validation route", function () { req = { app: { + get: sinon.stub().returns({ logger: winston }) }, body: { username: "username", @@ -77,8 +78,12 @@ describe("test the first factor validation route", function () { emails: emails, groups: groups })); - const authSession = AuthenticationSession.get(req as any); - return FirstFactorPost.default(req as any, res as any) + let authSession: AuthenticationSession.AuthenticationSession; + return AuthenticationSession.get(req as any) + .then(function (_authSession: AuthenticationSession.AuthenticationSession) { + authSession = _authSession; + return FirstFactorPost.default(req as any, res as any); + }) .then(function () { assert.equal("username", authSession.userid); 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 () { 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") .returns(BluebirdPromise.resolve({ emails: emails, 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 () { assert.equal("test_ok@example.com", authSession.email); }); diff --git a/test/unit/server/routes/password-reset/post.test.ts b/test/unit/server/routes/password-reset/post.test.ts index 73e46e6da..71a2cf505 100644 --- a/test/unit/server/routes/password-reset/post.test.ts +++ b/test/unit/server/routes/password-reset/post.test.ts @@ -16,7 +16,6 @@ describe("test reset password route", function () { let req: ExpressMock.RequestMock; let res: ExpressMock.ResponseMock; let configuration: any; - let authSession: AuthenticationSession.AuthenticationSession; let serverVariables: ServerVariablesMock.ServerVariablesMock; beforeEach(function () { @@ -25,7 +24,7 @@ describe("test reset password route", function () { userid: "user" }, app: { - get: Sinon.stub() + get: Sinon.stub().returns({ logger: winston }) }, session: {}, headers: { @@ -34,11 +33,6 @@ describe("test reset password route", function () { }; 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 = { inMemoryOnly: true @@ -65,54 +59,72 @@ describe("test reset password route", function () { } as any; 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", () => { it("should update the password and reset auth_session for reauthentication", function () { - authSession.identity_check = { - userid: "user", - challenge: "reset-password" - }; req.body = {}; req.body.password = "new-password"; (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 () { - 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(authSession.first_factor, false); - assert.equal(authSession.second_factor, false); + assert.equal(_authSession.first_factor, false); + assert.equal(_authSession.second_factor, false); return BluebirdPromise.resolve(); }); }); - it("should fail if identity_challenge does not exist", function (done) { - authSession.identity_check = { - userid: "user", - challenge: undefined - }; - res.send = Sinon.spy(function () { - assert.equal(res.status.getCall(0).args[0], 403); - done(); - }); - PasswordResetFormPost.default(req as any, res as any); + it("should fail if identity_challenge does not exist", function () { + return AuthenticationSession.get(req as any) + .then(function (authSession) { + authSession.identity_check = { + userid: "user", + challenge: undefined + }; + return 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) { - authSession.identity_check = { - challenge: "reset-password", - userid: "user" - }; + it("should fail when ldap fails", function () { req.body = {}; req.body.password = "new-password"; - (serverVariables.ldapPasswordUpdater.updatePassword as Sinon.SinonStub).returns(BluebirdPromise.reject("Internal error with LDAP")); - res.send = Sinon.spy(function () { - assert.equal(res.status.getCall(0).args[0], 500); - done(); - }); - PasswordResetFormPost.default(req as any, res as any); + (serverVariables.ldapPasswordUpdater.updatePassword as Sinon.SinonStub) + .returns(BluebirdPromise.reject("Internal error with LDAP")); + + return AuthenticationSession.get(req as any) + .then(function (authSession) { + 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(); + }); }); }); }); diff --git a/test/unit/server/routes/secondfactor/totp/register/RegistrationHandler.test.ts b/test/unit/server/routes/secondfactor/totp/register/RegistrationHandler.test.ts index 2895c4e81..cf13f90bc 100644 --- a/test/unit/server/routes/secondfactor/totp/register/RegistrationHandler.test.ts +++ b/test/unit/server/routes/secondfactor/totp/register/RegistrationHandler.test.ts @@ -23,11 +23,6 @@ describe("test totp register", function () { req.session = {}; 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.host = "localhost"; @@ -43,6 +38,15 @@ describe("test totp register", function () { mocks.userDataStore.saveTOTPSecretStub.returns(BluebirdPromise.resolve({})); 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); diff --git a/test/unit/server/routes/secondfactor/totp/sign/post.test.ts b/test/unit/server/routes/secondfactor/totp/sign/post.test.ts index 0c6b96847..b7ce7a132 100644 --- a/test/unit/server/routes/secondfactor/totp/sign/post.test.ts +++ b/test/unit/server/routes/secondfactor/totp/sign/post.test.ts @@ -23,6 +23,7 @@ describe("test totp route", function () { const app_get = sinon.stub(); req = { app: { + get: sinon.stub().returns({ logger: winston }) }, body: { token: "abc" @@ -30,11 +31,6 @@ describe("test totp route", function () { session: {} }; 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); res = ExpressMock.ResponseMock(); @@ -52,6 +48,14 @@ describe("test totp route", function () { mocks.logger = winston; mocks.totpValidator = totpValidator; 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; + }); }); diff --git a/test/unit/server/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts b/test/unit/server/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts index 21b04c090..859b614ef 100644 --- a/test/unit/server/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts +++ b/test/unit/server/routes/secondfactor/u2f/identity/RegistrationHandler.test.ts @@ -23,11 +23,6 @@ describe("test register handler", function () { mocks.logger = winston; req.session = {}; 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.host = "localhost"; @@ -44,6 +39,15 @@ describe("test register handler", function () { 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.email = "user@example.com"; + authSession.first_factor = true; + authSession.second_factor = false; + }); }); describe("test u2f registration check", test_registration_check); diff --git a/test/unit/server/routes/secondfactor/u2f/register/post.test.ts b/test/unit/server/routes/secondfactor/u2f/register/post.test.ts index 5c5dfd804..664fcea93 100644 --- a/test/unit/server/routes/secondfactor/u2f/register/post.test.ts +++ b/test/unit/server/routes/secondfactor/u2f/register/post.test.ts @@ -17,7 +17,6 @@ describe("test u2f routes: register", function () { let req: ExpressMock.RequestMock; let res: ExpressMock.ResponseMock; let mocks: ServerVariablesMock.ServerVariablesMock; - let authSession: AuthenticationSession.AuthenticationSession; beforeEach(function () { req = ExpressMock.RequestMock(); @@ -26,16 +25,6 @@ describe("test u2f routes: register", function () { mocks.logger = winston; 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.host = "localhost"; @@ -50,6 +39,18 @@ describe("test u2f routes: register", function () { res.send = sinon.spy(); res.json = 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); @@ -64,16 +65,22 @@ describe("test u2f routes: register", function () { }; const u2f_mock = U2FMock.U2FMock(); u2f_mock.checkRegistration.returns(BluebirdPromise.resolve(expectedStatus)); - - authSession.register_request = { - appId: "app", - challenge: "challenge", - keyHandle: "key", - version: "U2F_V2" - }; 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 AuthenticationSession.get(req as any); + }) + .then(function (authSession) { assert.equal("user", mocks.userDataStore.saveU2FRegistrationStub.getCall(0).args[0]); assert.equal(authSession.identity_check, undefined); }); @@ -83,15 +90,19 @@ describe("test u2f routes: register", function () { const user_key_container = {}; const u2f_mock = U2FMock.U2FMock(); u2f_mock.checkRegistration.returns({ errorCode: 500 }); - - authSession.register_request = { - appId: "app", - challenge: "challenge", - keyHandle: "key", - version: "U2F_V2" - }; 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")); }) .catch(function () { assert.equal(500, res.status.getCall(0).args[0]); @@ -104,9 +115,12 @@ describe("test u2f routes: register", function () { const u2f_mock = U2FMock.U2FMock(); u2f_mock.checkRegistration.returns(BluebirdPromise.resolve()); - authSession.register_request = undefined; 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")); }) .catch(function () { assert.equal(403, res.status.getCall(0).args[0]); @@ -119,9 +133,12 @@ describe("test u2f routes: register", function () { const u2f_mock = U2FMock.U2FMock(); u2f_mock.checkRegistration.returns(BluebirdPromise.resolve()); - authSession.register_request = undefined; 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")); }) .catch(function () { 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 () { - authSession.identity_check = undefined; - return U2FRegisterPost.default(req as any, res as any) + return AuthenticationSession.get(req 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")); }) .catch(function () { assert.equal(403, res.status.getCall(0).args[0]); diff --git a/test/unit/server/routes/secondfactor/u2f/register_request/get.test.ts b/test/unit/server/routes/secondfactor/u2f/register_request/get.test.ts index a3e770646..e647f9442 100644 --- a/test/unit/server/routes/secondfactor/u2f/register_request/get.test.ts +++ b/test/unit/server/routes/secondfactor/u2f/register_request/get.test.ts @@ -14,82 +14,84 @@ import ServerVariablesMock = require("../../../../mocks/ServerVariablesMock"); import U2f = require("u2f"); describe("test u2f routes: register_request", function () { - let req: ExpressMock.RequestMock; - let res: ExpressMock.ResponseMock; - let mocks: ServerVariablesMock.ServerVariablesMock; - let authSession: AuthenticationSession.AuthenticationSession; + let req: ExpressMock.RequestMock; + let res: ExpressMock.ResponseMock; + let mocks: ServerVariablesMock.ServerVariablesMock; + let authSession: AuthenticationSession.AuthenticationSession; - beforeEach(function () { - req = ExpressMock.RequestMock(); - req.app = {}; - mocks = ServerVariablesMock.mock(req.app); - mocks.logger = winston; - req.session = {}; - AuthenticationSession.reset(req as any); - authSession = AuthenticationSession.get(req as any); + beforeEach(function () { + req = ExpressMock.RequestMock(); + req.app = {}; + mocks = ServerVariablesMock.mock(req.app); + mocks.logger = winston; + req.session = {}; + AuthenticationSession.reset(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.first_factor = true; authSession.second_factor = false; authSession.identity_check = { - challenge: "u2f-register", - userid: "user" + challenge: "u2f-register", + userid: "user" }; + }); + }); - req.headers = {}; - req.headers.host = "localhost"; + describe("test registration request", () => { + 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 = { - 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(); + mocks.u2f = u2f_mock; + return U2FRegisterRequestGet.default(req as any, res as any) + .then(function () { + assert.deepEqual(expectedRequest, res.json.getCall(0).args[0]); + }); }); - describe("test registration request", () => { - 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)); + it("should return internal error on registration request", function (done) { + res.send = sinon.spy(function (data: any) { + assert.equal(500, res.status.getCall(0).args[0]); + done(); + }); + const user_key_container = {}; + const u2f_mock = U2FMock.U2FMock(); + u2f_mock.request.returns(BluebirdPromise.reject("Internal error")); - mocks.u2f = u2f_mock; - return U2FRegisterRequestGet.default(req as any, res as any) - .then(function () { - assert.deepEqual(expectedRequest, res.json.getCall(0).args[0]); - }); - }); + mocks.u2f = u2f_mock; + U2FRegisterRequestGet.default(req as any, res as any); + }); - it("should return internal error on registration request", function (done) { - res.send = sinon.spy(function (data: any) { - assert.equal(500, res.status.getCall(0).args[0]); - done(); - }); - 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); + it("should return forbidden if identity has not been verified", function () { + authSession.identity_check = undefined; + return U2FRegisterRequestGet.default(req as any, res as any) + .then(function () { + assert.equal(403, res.status.getCall(0).args[0]); }); }); + }); }); diff --git a/test/unit/server/routes/secondfactor/u2f/sign/post.test.ts b/test/unit/server/routes/secondfactor/u2f/sign/post.test.ts index 0d8a1ce81..c033a287b 100644 --- a/test/unit/server/routes/secondfactor/u2f/sign/post.test.ts +++ b/test/unit/server/routes/secondfactor/u2f/sign/post.test.ts @@ -27,14 +27,6 @@ describe("test u2f routes: sign", function () { 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.host = "localhost"; @@ -46,6 +38,18 @@ describe("test u2f routes: sign", function () { 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.first_factor = true; + authSession.second_factor = false; + authSession.identity_check = { + challenge: "u2f-register", + userid: "user" + }; + }); }); it("should return status code 204", function () { diff --git a/test/unit/server/routes/secondfactor/u2f/sign_request/get.test.ts b/test/unit/server/routes/secondfactor/u2f/sign_request/get.test.ts index b3026b03a..82fd79cb8 100644 --- a/test/unit/server/routes/secondfactor/u2f/sign_request/get.test.ts +++ b/test/unit/server/routes/secondfactor/u2f/sign_request/get.test.ts @@ -31,14 +31,6 @@ describe("test u2f routes: sign_request", function () { 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.host = "localhost"; @@ -51,6 +43,18 @@ describe("test u2f routes: sign_request", function () { 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.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 () { diff --git a/test/unit/server/routes/verify/get.test.ts b/test/unit/server/routes/verify/get.test.ts index 65cb5ab32..6aeb6a34a 100644 --- a/test/unit/server/routes/verify/get.test.ts +++ b/test/unit/server/routes/verify/get.test.ts @@ -24,6 +24,9 @@ describe("test authentication token verification", function () { req = ExpressMock.RequestMock(); res = ExpressMock.ResponseMock(); + req.app = { + get: sinon.stub().returns({ logger: winston }) + }; req.session = {}; AuthenticationSession.reset(req as any); req.headers = {}; @@ -37,12 +40,13 @@ describe("test authentication token verification", function () { it("should be already authenticated", function () { req.session = {}; AuthenticationSession.reset(req as any); - const authSession = AuthenticationSession.get(req as any); - authSession.first_factor = true; - authSession.second_factor = true; - authSession.userid = "myuser"; - - return VerifyGet.default(req as express.Request, res as any) + return AuthenticationSession.get(req as any) + .then(function (authSession: AuthenticationSession.AuthenticationSession) { + authSession.first_factor = true; + authSession.second_factor = true; + authSession.userid = "myuser"; + return VerifyGet.default(req as express.Request, res as any); + }) .then(function () { 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 () { - const authSession = AuthenticationSession.get(req as any); - authSession.first_factor = true; - authSession.second_factor = true; - authSession.userid = "myuser"; + return AuthenticationSession.get(req as any) + .then(function (authSession: AuthenticationSession.AuthenticationSession) { + authSession.first_factor = true; + 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.withArgs("test.example.com", "user", ["group1", "group2"]).returns(true); + accessController.isDomainAllowedForUser.returns(false); + accessController.isDomainAllowedForUser.withArgs("test.example.com", "user", ["group1", "group2"]).returns(true); - return test_unauthorized_403({ - first_factor: true, - second_factor: true, - userid: "user", - groups: ["group1", "group2"], - email: undefined - }); + return test_unauthorized_403({ + first_factor: true, + second_factor: true, + userid: "user", + groups: ["group1", "group2"], + email: undefined + }); + }); }); }); });