Fix open redirection vulnerability.

In order to redirect the user after authentication, Authelia uses
rd query parameter provided by the proxy. However an attacker could
use phishing to make the user be redirected to a bad domain. In order
to avoid the user to be redirected to a bad location, Authelia now
verifies the redirection URL is under the protected domain.
This commit is contained in:
Clement Michaud 2018-10-27 18:18:25 +02:00 committed by Clement Michaud
parent 5f8e33d6ac
commit 42581dfe93
29 changed files with 248 additions and 105 deletions

View File

@ -9,6 +9,9 @@ module.exports = function (grunt) {
},
"env-test-client-unit": {
TS_NODE_PROJECT: "client/tsconfig.json"
},
"env-test-shared-unit": {
TS_NODE_PROJECT: "server/tsconfig.json"
}
},
run: {
@ -37,6 +40,10 @@ module.exports = function (grunt) {
cmd: "./node_modules/.bin/mocha",
args: ['--colors', '--require', 'ts-node/register', 'server/src/**/*.spec.ts']
},
"test-shared-unit": {
cmd: "./node_modules/.bin/mocha",
args: ['--colors', '--require', 'ts-node/register', 'shared/**/*.spec.ts']
},
"test-client-unit": {
cmd: "./node_modules/.bin/mocha",
args: ['--colors', '--require', 'ts-node/register', 'client/test/**/*.test.ts']
@ -193,8 +200,9 @@ module.exports = function (grunt) {
grunt.registerTask('compile-client', ['run:lint-client', 'run:compile-client'])
grunt.registerTask('test-server', ['env:env-test-server-unit', 'run:test-server-unit'])
grunt.registerTask('test-shared', ['env:env-test-shared-unit', 'run:test-shared-unit'])
grunt.registerTask('test-client', ['env:env-test-client-unit', 'run:test-client-unit'])
grunt.registerTask('test-unit', ['test-server', 'test-client']);
grunt.registerTask('test-unit', ['test-server', 'test-client', 'test-shared']);
grunt.registerTask('test-int', ['run:test-cucumber', 'run:test-minimal-config', 'run:test-complete-config', 'run:test-inactivity']);
grunt.registerTask('copy-resources', ['copy:resources', 'copy:views', 'copy:images', 'copy:thirdparties', 'concat:css']);

View File

@ -0,0 +1,10 @@
import { BelongToDomain } from "../../../shared/BelongToDomain";
export function SafeRedirect(url: string, cb: () => void): void {
const domain = window.location.hostname.split(".").slice(-2).join(".");
if (url.startsWith("/") || BelongToDomain(url, domain)) {
window.location.href = url;
return;
}
cb();
}

View File

@ -6,6 +6,7 @@ import { QueryParametersRetriever } from "../QueryParametersRetriever";
import Constants = require("../../../../shared/constants");
import Endpoints = require("../../../../shared/api");
import UserMessages = require("../../../../shared/UserMessages");
import { SafeRedirect } from "../SafeRedirect";
export default function (window: Window, $: JQueryStatic,
firstFactorValidator: typeof FirstFactorValidator, jslogger: typeof JSLogger) {
@ -28,7 +29,9 @@ export default function (window: Window, $: JQueryStatic,
}
function onFirstFactorSuccess(redirectUrl: string) {
window.location.href = redirectUrl;
SafeRedirect(redirectUrl, () => {
notifier.error("Cannot redirect to an external domain.");
});
}
function onFirstFactorFailure(err: Error) {

View File

@ -1,7 +1,6 @@
import U2f = require("u2f");
import U2fApi from "u2f-api";
import BluebirdPromise = require("bluebird");
import { SignMessage } from "../../../../shared/SignMessage";
import Endpoints = require("../../../../shared/api");
import UserMessages = require("../../../../shared/UserMessages");
import { INotifier } from "../INotifier";
@ -31,24 +30,13 @@ function finishU2fAuthentication(responseData: U2fApi.SignResponse,
});
}
function startU2fAuthentication($: JQueryStatic, notifier: INotifier)
: BluebirdPromise<string> {
export function validate($: JQueryStatic): BluebirdPromise<string> {
return GetPromised($, Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, {},
undefined, "json")
.then(function (signRequest: U2f.Request) {
notifier.info(UserMessages.PLEASE_TOUCH_TOKEN);
return U2fApi.sign(signRequest, 60);
})
.then(function (signResponse: U2fApi.SignResponse) {
return finishU2fAuthentication(signResponse, $);
});
}
export function validate($: JQueryStatic, notifier: INotifier) {
return startU2fAuthentication($, notifier)
.catch(function (err: Error) {
notifier.error(UserMessages.U2F_TRANSACTION_FINISH_FAILED);
return BluebirdPromise.reject(err);
});
}

View File

@ -3,38 +3,44 @@ import U2FValidator = require("./U2FValidator");
import ClientConstants = require("./constants");
import { Notifier } from "../Notifier";
import { QueryParametersRetriever } from "../QueryParametersRetriever";
import Endpoints = require("../../../../shared/api");
import ServerConstants = require("../../../../shared/constants");
import UserMessages = require("../../../../shared/UserMessages");
import SharedConstants = require("../../../../shared/constants");
import { SafeRedirect } from "../SafeRedirect";
export default function (window: Window, $: JQueryStatic) {
const notifierTotp = new Notifier(".notification-totp", $);
const notifierU2f = new Notifier(".notification-u2f", $);
const notifier = new Notifier(".notification", $);
function onAuthenticationSuccess(serverRedirectUrl: string, notifier: Notifier) {
if (QueryParametersRetriever.get(SharedConstants.REDIRECT_QUERY_PARAM))
window.location.href = QueryParametersRetriever.get(SharedConstants.REDIRECT_QUERY_PARAM);
else if (serverRedirectUrl)
window.location.href = serverRedirectUrl;
else
function onAuthenticationSuccess(serverRedirectUrl: string) {
const queryRedirectUrl = QueryParametersRetriever.get(SharedConstants.REDIRECT_QUERY_PARAM);
if (queryRedirectUrl) {
SafeRedirect(queryRedirectUrl, () => {
notifier.error(UserMessages.CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN);
});
} else if (serverRedirectUrl) {
SafeRedirect(serverRedirectUrl, () => {
notifier.error(UserMessages.CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN);
});
} else {
notifier.success(UserMessages.AUTHENTICATION_SUCCEEDED);
}
}
function onSecondFactorTotpSuccess(redirectUrl: string) {
onAuthenticationSuccess(redirectUrl, notifierTotp);
onAuthenticationSuccess(redirectUrl);
}
function onSecondFactorTotpFailure(err: Error) {
notifierTotp.error(UserMessages.AUTHENTICATION_TOTP_FAILED);
notifier.error(UserMessages.AUTHENTICATION_TOTP_FAILED);
}
function onU2fAuthenticationSuccess(redirectUrl: string) {
onAuthenticationSuccess(redirectUrl, notifierU2f);
onAuthenticationSuccess(redirectUrl);
}
function onU2fAuthenticationFailure() {
notifierU2f.error(UserMessages.AUTHENTICATION_U2F_FAILED);
// TODO(clems4ever): we should not display this error message until a device
// is registered.
// notifier.error(UserMessages.AUTHENTICATION_U2F_FAILED);
}
function onTOTPFormSubmitted(): boolean {
@ -47,7 +53,7 @@ export default function (window: Window, $: JQueryStatic) {
$(window.document).ready(function () {
$(ClientConstants.TOTP_FORM_SELECTOR).on("submit", onTOTPFormSubmitted);
U2FValidator.validate($, notifierU2f)
U2FValidator.validate($)
.then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure);
});
}

View File

@ -8,6 +8,7 @@ import Endpoints = require("../../../../shared/api");
import UserMessages = require("../../../../shared/UserMessages");
import { RedirectionMessage } from "../../../../shared/RedirectionMessage";
import { ErrorMessage } from "../../../../shared/ErrorMessage";
import { SafeRedirect } from "../SafeRedirect";
export default function (window: Window, $: JQueryStatic) {
const notifier = new Notifier(".notification", $);
@ -44,7 +45,9 @@ export default function (window: Window, $: JQueryStatic) {
$(document).ready(function () {
requestRegistration()
.then((redirectionUrl: string) => {
document.location.href = redirectionUrl;
SafeRedirect(redirectionUrl, () => {
notifier.error(UserMessages.CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN);
});
})
.catch((err) => {
onRegisterFailure(err);

View File

@ -7,45 +7,61 @@ import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler
import Constants = require("../../../../../shared/constants");
import Util = require("util");
import { ServerVariables } from "../../ServerVariables";
import { SafeRedirector } from "../../utils/SafeRedirection";
function getRedirectParam(req: express.Request) {
function getRedirectParam(
req: express.Request) {
return req.query[Constants.REDIRECT_QUERY_PARAM] != "undefined"
? req.query[Constants.REDIRECT_QUERY_PARAM]
: undefined;
}
function redirectToSecondFactorPage(req: express.Request, res: express.Response) {
function redirectToSecondFactorPage(
req: express.Request,
res: express.Response) {
const redirectUrl = getRedirectParam(req);
if (!redirectUrl)
res.redirect(Endpoints.SECOND_FACTOR_GET);
else
res.redirect(Util.format("%s?%s=%s", Endpoints.SECOND_FACTOR_GET,
Constants.REDIRECT_QUERY_PARAM,
redirectUrl));
res.redirect(
Util.format("%s?%s=%s",
Endpoints.SECOND_FACTOR_GET,
Constants.REDIRECT_QUERY_PARAM,
redirectUrl));
}
function redirectToService(req: express.Request, res: express.Response) {
function redirectToService(
req: express.Request,
res: express.Response,
redirector: SafeRedirector) {
const redirectUrl = getRedirectParam(req);
if (!redirectUrl)
if (!redirectUrl) {
res.redirect(Endpoints.LOGGED_IN);
else
res.redirect(redirectUrl);
} else {
redirector.redirectOrElse(res, redirectUrl, Endpoints.LOGGED_IN);
}
}
function renderFirstFactor(res: express.Response) {
function renderFirstFactor(
res: express.Response) {
res.render("firstfactor", {
first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET
});
}
export default function (vars: ServerVariables) {
export default function (
vars: ServerVariables) {
const redirector = new SafeRedirector(vars.config.session.domain);
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
return new BluebirdPromise(function (resolve, reject) {
const authSession = AuthenticationSessionHandler.get(req, vars.logger);
if (authSession.first_factor) {
if (authSession.second_factor)
redirectToService(req, res);
redirectToService(req, res, redirector);
else
redirectToSecondFactorPage(req, res);
resolve();

View File

@ -9,7 +9,7 @@ import Endpoint = require("../../../../../shared/api");
import ErrorReplies = require("../../ErrorReplies");
import { AuthenticationSessionHandler } from "../../AuthenticationSessionHandler";
import Constants = require("../../../../../shared/constants");
import { DomainExtractor } from "../../utils/DomainExtractor";
import { DomainExtractor } from "../../../../../shared/DomainExtractor";
import UserMessages = require("../../../../../shared/UserMessages");
import { MethodCalculator } from "../../authentication/MethodCalculator";
import { ServerVariables } from "../../ServerVariables";
@ -51,14 +51,16 @@ export default function (vars: ServerVariables) {
authSession.userid = username;
authSession.keep_me_logged_in = keepMeLoggedIn;
authSession.first_factor = true;
const redirectUrl = req.query[Constants.REDIRECT_QUERY_PARAM] !== "undefined"
const redirectUrl: string = req.query[Constants.REDIRECT_QUERY_PARAM] !== "undefined"
// Fuck, don't know why it is a string!
? req.query[Constants.REDIRECT_QUERY_PARAM]
: undefined;
const emails: string[] = groupsAndEmails.emails;
const groups: string[] = groupsAndEmails.groups;
const redirectHost: string = DomainExtractor.fromUrl(redirectUrl);
const domain = DomainExtractor.fromUrl(redirectUrl);
const redirectHost = (domain) ? domain : "";
const authMethod = MethodCalculator.compute(
vars.config.authentication_methods, redirectHost);
vars.logger.debug(req, "Authentication method for \"%s\" is \"%s\"",
@ -72,7 +74,7 @@ export default function (vars: ServerVariables) {
vars.regulator.mark(username, true);
if (authMethod == "single_factor") {
let newRedirectionUrl: string = redirectUrl;
let newRedirectionUrl = redirectUrl;
if (!newRedirectionUrl)
newRedirectionUrl = Endpoint.LOGGED_IN;
res.send({

View File

@ -24,7 +24,7 @@ export default function (vars: ServerVariables) {
})
.then(function (doc: U2FRegistrationDocument): BluebirdPromise<void> {
if (!doc)
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No U2F registration found"));
return BluebirdPromise.reject(new exceptions.AccessDeniedError("No U2F registration document found."));
const appId: string = u2f_common.extract_app_id(req);
vars.logger.info(req, "Start authentication of app '%s'", appId);

View File

@ -4,7 +4,7 @@ import ObjectPath = require("object-path");
import { ServerVariables } from "../../ServerVariables";
import { AuthenticationSession }
from "../../../../types/AuthenticationSession";
import { DomainExtractor } from "../../utils/DomainExtractor";
import { DomainExtractor } from "../../../../../shared/DomainExtractor";
import { MethodCalculator } from "../../authentication/MethodCalculator";
import AccessControl from "./access_control";

View File

@ -6,7 +6,7 @@ import ObjectPath = require("object-path");
import Exceptions = require("../../Exceptions");
import { Configuration } from "../../configuration/schema/Configuration";
import Constants = require("../../../../../shared/constants");
import { DomainExtractor } from "../../utils/DomainExtractor";
import { DomainExtractor } from "../../../../../shared/DomainExtractor";
import { ServerVariables } from "../../ServerVariables";
import { MethodCalculator } from "../../authentication/MethodCalculator";
import { IRequestLogger } from "../../logging/IRequestLogger";

View File

@ -1,6 +0,0 @@
export class DomainExtractor {
static fromUrl(url: string): string {
if (!url) return "";
return url.match(/https?:\/\/([^\/:]+).*/)[1];
}
}

View File

@ -0,0 +1,33 @@
import Assert = require("assert");
import Sinon = require("sinon");
import { SafeRedirector } from "./SafeRedirection";
describe("web_server/middlewares/SafeRedirection", () => {
describe("Url is in protected domain", () => {
before(() => {
this.redirector = new SafeRedirector("example.com");
this.res = {redirect: Sinon.stub()};
});
it("should redirect to provided url", () => {
this.redirector.redirectOrElse(this.res,
"https://mysubdomain.example.com:8080/abc",
"https://authelia.example.com");
Assert(this.res.redirect.calledWith("https://mysubdomain.example.com:8080/abc"));
});
it("should redirect to default url when wrong domain", () => {
this.redirector.redirectOrElse(this.res,
"https://mysubdomain.domain.rtf:8080/abc",
"https://authelia.example.com");
Assert(this.res.redirect.calledWith("https://authelia.example.com"));
});
it("should redirect to default url when not terminating by domain", () => {
this.redirector.redirectOrElse(this.res,
"https://mysubdomain.example.com.rtf:8080/abc",
"https://authelia.example.com");
Assert(this.res.redirect.calledWith("https://authelia.example.com"));
});
});
});

View File

@ -0,0 +1,22 @@
import Express = require("express");
import { DomainExtractor } from "../../../../shared/DomainExtractor";
import { BelongToDomain } from "../../../../shared/BelongToDomain";
export class SafeRedirector {
private domain: string;
constructor(domain: string) {
this.domain = domain;
}
redirectOrElse(
res: Express.Response,
url: string,
defaultUrl: string): void {
if (BelongToDomain(url, this.domain)) {
res.redirect(url);
}
res.redirect(defaultUrl);
}
}

View File

@ -8,7 +8,7 @@ block form-header
block content
div
div(class="notification notification-totp")
div(class="notification")
h3 Hi <b>#{username}</b>
div(class="row")
div(class="u2f-token")

8
shared/BelongToDomain.ts Normal file
View File

@ -0,0 +1,8 @@
import { DomainExtractor } from "./DomainExtractor";
export function BelongToDomain(url: string, domain: string): boolean {
const urlDomain =  DomainExtractor.fromUrl(url);
if (!urlDomain) return false;
const idx = urlDomain.indexOf(domain);
return idx + domain.length == urlDomain.length;
}

View File

@ -1,7 +1,7 @@
import { DomainExtractor } from "./DomainExtractor";
import Assert = require("assert");
describe("utils/DomainExtractor", function () {
describe.only("shared/DomainExtractor", function () {
describe("test fromUrl", function () {
it("should return domain from https url", function () {
const domain = DomainExtractor.fromUrl("https://www.example.com/test/abc");
@ -17,5 +17,16 @@ describe("utils/DomainExtractor", function () {
const domain = DomainExtractor.fromUrl("https://www.example.com:8080/test/abc");
Assert.equal(domain, "www.example.com");
});
it("should return domain when url contains redirect param", function () {
const domain0 = DomainExtractor.fromUrl("https://www.example.com:8080/test/abc?rd=https://cool.test.com");
Assert.equal(domain0, "www.example.com");
const domain1 = DomainExtractor.fromUrl("https://login.example.com:8080/?rd=https://public.example.com:8080/");
Assert.equal(domain1, "login.example.com");
const domain2 = DomainExtractor.fromUrl("https://single_factor.example.com:8080/secret.html");
Assert.equal(domain2, "single_factor.example.com");
});
});
});

11
shared/DomainExtractor.ts Normal file
View File

@ -0,0 +1,11 @@
export class DomainExtractor {
static fromUrl(url: string): string {
if (!url) return;
const matches = url.match(/(https?:\/\/)?([a-zA-Z0-9_.-]+).*/);
if (matches.length > 2) {
return matches[2];
}
return;
}
}

View File

@ -2,6 +2,8 @@
export const AUTHENTICATION_FAILED = "Authentication failed. Please check your credentials.";
export const AUTHENTICATION_SUCCEEDED = "Authentication succeeded. You can now access your services.";
export const CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN = "Cannot redirect to an external domain.";
export const AUTHENTICATION_U2F_FAILED = "Authentication failed. Have you already registered your device?";
export const AUTHENTICATION_TOTP_FAILED = "Authentication failed. Have you already registered your secret?";

View File

@ -0,0 +1,41 @@
import WithDriver from "../helpers/with-driver";
import LoginAndRegisterTotp from "../helpers/login-and-register-totp";
import SeeNotification from "../helpers/see-notification";
import VisitPage from "../helpers/visit-page";
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/fill-login-page-and-click';
import ValidateTotp from "../helpers/validate-totp";
import {CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN} from '../../shared/UserMessages';
/*
* Authelia should not be vulnerable to open redirection. Otherwise it would aid an
* attacker in conducting a phishing attack.
*
* To avoid the issue, Authelia's client scans the URL and prevent any redirection if
* the URL is pointing to an external domain.
*/
describe("Redirection should be performed only if in domain", function() {
this.timeout(10000);
WithDriver();
before(function() {
const that = this;
return LoginAndRegisterTotp(this.driver, "john", true)
.then((secret: string) => that.secret = secret)
});
function DoNotRedirect(url: string) {
it(`should see an error message instead of redirecting to ${url}`, function() {
const driver = this.driver;
const secret = this.secret;
return VisitPage(driver, `https://login.example.com:8080/?rd=${url}`)
.then(() => FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password'))
.then(() => ValidateTotp(driver, secret))
.then(() => SeeNotification(driver, "error", CANNOT_REDIRECT_TO_EXTERNAL_DOMAIN))
.then(() => driver.get(`https://login.example.com:8080/logout`));
});
}
DoNotRedirect("www.google.fr");
DoNotRedirect("http://www.google.fr");
DoNotRedirect("https://www.google.fr");
})

View File

@ -1,19 +1,17 @@
require("chromedriver");
import SeleniumWebdriver = require("selenium-webdriver");
import WithDriver from '../helpers/with-driver';
import LoginAndRegisterTotp from '../helpers/login-and-register-totp';
import LoginAs from '../helpers/login-as';
import VisitPage from '../helpers/visit-page';
import fullLogin from '../helpers/full-login';
import loginAndRegisterTotp from '../helpers/login-and-register-totp';
describe('Connection retry when mongo fails or restarts', function() {
this.timeout(20000);
describe("Connection retry when mongo fails or restarts", function() {
this.timeout(30000);
WithDriver();
it('should be able to login after mongo restarts', function() {
it("should be able to login after mongo restarts", function() {
const that = this;
return that.environment.stop_service("mongo")
.then(() => that.environment.restart_service("authelia", 2000))
.then(() => that.environment.restart_service("mongo"))
.then(() => LoginAs(that.driver, "john"));
let secret;
return loginAndRegisterTotp(that.driver, "john", true)
.then(_secret => secret = _secret)
.then(() => that.environment.restart_service("mongo", 1000))
.then(() => fullLogin(that.driver, "https://admin.example.com:8080/secret.html", "john", secret));
})
});

View File

@ -15,7 +15,7 @@ export class Environment {
private runCommand(command: string, timeout?: number): Bluebird<void> {
return new Bluebird<void>((resolve, reject) => {
console.log('[ENVIRONMENT] Running: %s', command);
exec(command, (err, stdout, stderr) => {
exec(command, (err: any, stdout: any, stderr: any) => {
if(err) {
reject(err);
return;

View File

@ -0,0 +1,12 @@
import VisitPage from "./visit-page";
import FillLoginPageWithUserAndPasswordAndClick from "./fill-login-page-and-click";
import ValidateTotp from "./validate-totp";
import WaitRedirected from "./wait-redirected";
// Validate the two factors!
export default function(driver: any, url: string, user: string, secret: string) {
return VisitPage(driver, `https://login.example.com:8080/?rd=${url}`)
.then(() => FillLoginPageWithUserAndPasswordAndClick(driver, user, 'password'))
.then(() => ValidateTotp(driver, secret))
.then(() => WaitRedirected(driver, "https://admin.example.com:8080/secret.html"));
}

View File

@ -1,10 +1,9 @@
import VisitPage from "./visit-page";
import FillLoginPageAndClick from './fill-login-page-and-click';
import RegisterTotp from './register-totp';
import WaitRedirected from './wait-redirected';
import LoginAs from './login-as';
import Bluebird = require("bluebird");
export default function(driver: any, user: string, email?: boolean) {
export default function(driver: any, user: string, email?: boolean): Bluebird<string> {
return LoginAs(driver, user)
.then(() => WaitRedirected(driver, "https://login.example.com:8080/secondfactor"))
.then(() => RegisterTotp(driver, email));

View File

@ -1,7 +1,5 @@
import VisitPage from "./visit-page";
import FillLoginPageAndClick from './fill-login-page-and-click';
import RegisterTotp from './register-totp';
import WaitRedirected from './wait-redirected';
export default function(driver: any, user: string) {
return VisitPage(driver, "https://login.example.com:8080/")

View File

@ -1,12 +1,8 @@
import Bluebird = require("bluebird");
import SeleniumWebdriver = require("selenium-webdriver");
import Fs = require("fs");
import Speakeasy = require("speakeasy");
import WithDriver from '../helpers/with-driver';
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/fill-login-page-and-click';
import WaitRedirected from '../helpers/wait-redirected';
import VisitPage from '../helpers/visit-page';
import SeeNotification from '../helpers/see-notification';
import {AUTHENTICATION_FAILED} from '../../shared/UserMessages';
/**
* When user provides bad password,
@ -28,7 +24,7 @@ describe("Provide bad password", function() {
it('should get a notification message', function() {
this.timeout(10000);
return SeeNotification(this.driver, "error", "Authentication failed. Please check your credentials.");
return SeeNotification(this.driver, "error", AUTHENTICATION_FAILED);
});
});
});

View File

@ -1,17 +1,11 @@
require("chromedriver");
import Bluebird = require("bluebird");
import SeleniumWebdriver = require("selenium-webdriver");
import Fs = require("fs");
import Speakeasy = require("speakeasy");
import WithDriver from '../helpers/with-driver';
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/fill-login-page-and-click';
import WaitRedirected from '../helpers/wait-redirected';
import VisitPage from '../helpers/visit-page';
import RegisterTotp from '../helpers/register-totp';
import ValidateTotp from '../helpers/validate-totp';
import AccessSecret from "../helpers/access-secret";
import LoginAndRegisterTotp from '../helpers/login-and-register-totp';
import seeNotification from "../helpers/see-notification";
import {AUTHENTICATION_TOTP_FAILED} from '../../shared/UserMessages';
/**
* Given john has registered a TOTP secret,
@ -24,7 +18,6 @@ describe('Fail TOTP challenge', function() {
describe('successfully login as john', function() {
before(function() {
const that = this;
return LoginAndRegisterTotp(this.driver, "john", true);
});
@ -39,7 +32,7 @@ describe('Fail TOTP challenge', function() {
});
it("get a notification message", function() {
return seeNotification(this.driver, "error", "Authentication failed. Have you already registered your secret?");
return seeNotification(this.driver, "error", AUTHENTICATION_TOTP_FAILED);
});
});
});

View File

@ -1,7 +1,6 @@
require("chromedriver");
import Bluebird = require("bluebird");
import ChildProcess = require("child_process");
import SeleniumWebdriver = require("selenium-webdriver");
import WithDriver from '../helpers/with-driver';
import VisitPage from '../helpers/visit-page';

View File

@ -1,13 +1,9 @@
require("chromedriver");
import Bluebird = require("bluebird");
import SeleniumWebdriver = require("selenium-webdriver");
import Fs = require("fs");
import Speakeasy = require("speakeasy");
import WithDriver from '../helpers/with-driver';
import FillLoginPageWithUserAndPasswordAndClick from '../helpers/fill-login-page-and-click';
import WaitRedirected from '../helpers/wait-redirected';
import VisitPage from '../helpers/visit-page';
import RegisterTotp from '../helpers/register-totp';
import ValidateTotp from '../helpers/validate-totp';
import AccessSecret from "../helpers/access-secret";
import LoginAndRegisterTotp from '../helpers/login-and-register-totp';
@ -37,15 +33,9 @@ describe('Validate TOTP factor', function() {
const driver = this.driver;
return VisitPage(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html")
.then(() => {
return FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password');
})
.then(() => {
return ValidateTotp(driver, secret);
})
.then(() => {
return WaitRedirected(driver, "https://admin.example.com:8080/secret.html")
});
.then(() => FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password'))
.then(() => ValidateTotp(driver, secret))
.then(() => WaitRedirected(driver, "https://admin.example.com:8080/secret.html"));
});
it("should access the secret", function() {