diff --git a/Gruntfile.js b/Gruntfile.js index e499db58..d8593e43 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 3af27f21..e63fa39f 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 e2e8d20c..12b89915 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 4a60e101..ca5aea4b 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 287034e4..0b621429 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 52a2b449..95c6d368 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 d82a1ad2..678dfde1 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 8523f9f0..84d8631f 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 b4ec2541..efea6906 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 6e0c32af..3560cbb2 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 fb2ea86d..366e0c84 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 6f6d065c..981012cc 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 0cb48c40..62aa2fa2 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 dfd8cf16..fe11c660 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 4d4e27de..f7520fae 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 2e6f5558..0ab94845 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 4ac56d99..b4adee97 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 91564a82..62059249 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 a76b24bc..002904e7 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 6e3f0abf..a0bae8b7 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 e0d28fb9..9b13698f 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 610b498c..89375124 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 98ff0f57..0755a01a 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 57485074..45b7974b 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 d853a62b..7f166351 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 5625f6fe..e1ed84e2 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 6ce54c64..9884a139 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 835d980d..54e40b79 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 9a63dfd2..755cca02 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 e343d249..c2cf9889 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 ba07f82f..a6285d4c 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 a39c96a5..184a66d2 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 fe5b5db4..8828ad9b 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 c2914359..d187c998 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 91ee1b74..4dd5e043 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 73e46e6d..71a2cf50 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 2895c4e8..cf13f90b 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 0c6b9684..b7ce7a13 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 21b04c09..859b614e 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 5c5dfd80..664fcea9 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 a3e77064..e647f944 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 0d8a1ce8..c033a287 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 b3026b03..82fd79cb 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 65cb5ab3..6aeb6a34 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 + }); + }); }); }); });