Every public endpoints return 200 with harmonized error messages or 401

Now, /verify can return 401 or 403 depending on the user authentication.
Every public API endpoints and pages return 200 with error message in
JSON body or 401 if the user is not authorized.

This policy makes it complicated for an attacker to know what is the source of
the failure and hide server-side bugs (not returning 500), bugs being potential
threats.
This commit is contained in:
Clement Michaud 2017-10-10 23:03:30 +02:00
parent d5035b8704
commit 56fdc40290
39 changed files with 325 additions and 249 deletions

View File

@ -53,7 +53,7 @@ module.exports = function (grunt) {
}, },
"include-minified-script": { "include-minified-script": {
cmd: "sed", cmd: "sed",
args: ["-i", "s/authelia\.js/authelia.min.js/", `${buildDir}/server/src/views/layout/layout.pug`] args: ["-i", "s/authelia.\(js\|css\)/authelia.min.\1/", `${buildDir}/server/src/views/layout/layout.pug`]
} }
}, },
copy: { copy: {

View File

@ -3,6 +3,7 @@ import BluebirdPromise = require("bluebird");
import Endpoints = require("../../../../shared/api"); import Endpoints = require("../../../../shared/api");
import Constants = require("../../../../shared/constants"); import Constants = require("../../../../shared/constants");
import Util = require("util"); import Util = require("util");
import UserMessages = require("../../../../shared/UserMessages");
export function validate(username: string, password: string, export function validate(username: string, password: string,
redirectUrl: string, $: JQueryStatic): BluebirdPromise<string> { redirectUrl: string, $: JQueryStatic): BluebirdPromise<string> {
@ -24,11 +25,15 @@ export function validate(username: string, password: string,
password: password, password: password,
} }
}) })
.done(function (data: { redirect: string }) { .done(function (body: any) {
resolve(data.redirect); if (body && body.error) {
reject(new Error(body.error));
return;
}
resolve(body.redirect);
}) })
.fail(function (xhr: JQueryXHR, textStatus: string) { .fail(function (xhr: JQueryXHR, textStatus: string) {
reject(new Error("Authetication failed. Please check your credentials.")); reject(new Error(UserMessages.AUTHENTICATION_FAILED));
}); });
}); });
} }

View File

@ -5,6 +5,7 @@ import { Notifier } from "../Notifier";
import { QueryParametersRetriever } from "../QueryParametersRetriever"; import { QueryParametersRetriever } from "../QueryParametersRetriever";
import Constants = require("../../../../shared/constants"); import Constants = require("../../../../shared/constants");
import Endpoints = require("../../../../shared/api"); import Endpoints = require("../../../../shared/api");
import UserMessages = require("../../../../shared/UserMessages");
export default function (window: Window, $: JQueryStatic, export default function (window: Window, $: JQueryStatic,
firstFactorValidator: typeof FirstFactorValidator, jslogger: typeof JSLogger) { firstFactorValidator: typeof FirstFactorValidator, jslogger: typeof JSLogger) {
@ -23,18 +24,14 @@ export default function (window: Window, $: JQueryStatic,
} }
function onFirstFactorSuccess(redirectUrl: string) { function onFirstFactorSuccess(redirectUrl: string) {
jslogger.debug("First factor validated."); window.location.href = redirectUrl;
window.location.href = redirectUrl;
} }
function onFirstFactorFailure(err: Error) { function onFirstFactorFailure(err: Error) {
jslogger.debug("First factor failed."); notifier.error(UserMessages.AUTHENTICATION_FAILED);
notifier.error("Authentication failed. Please double check your credentials.");
} }
$(window.document).ready(function () { $(window.document).ready(function () {
jslogger.info("Enter first factor");
$("form").on("submit", onFormSubmitted); $("form").on("submit", onFormSubmitted);
}); });
} }

View File

@ -1,6 +1,8 @@
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import Endpoints = require("../../../../shared/api"); import Endpoints = require("../../../../shared/api");
import UserMessages = require("../../../../shared/UserMessages");
import Constants = require("./constants"); import Constants = require("./constants");
import { Notifier } from "../Notifier"; import { Notifier } from "../Notifier";
@ -12,8 +14,12 @@ export default function (window: Window, $: JQueryStatic) {
$.post(Endpoints.RESET_PASSWORD_FORM_POST, { $.post(Endpoints.RESET_PASSWORD_FORM_POST, {
password: newPassword, password: newPassword,
}) })
.done(function (data) { .done(function (body: any) {
resolve(data); if (body && body.error) {
reject(new Error(body.error));
return;
}
resolve(body);
}) })
.fail(function (xhr, status) { .fail(function (xhr, status) {
reject(status); reject(status);
@ -26,22 +32,21 @@ export default function (window: Window, $: JQueryStatic) {
const password2 = $("#password2").val(); const password2 = $("#password2").val();
if (!password1 || !password2) { if (!password1 || !password2) {
notifier.warning("You must enter your new password twice."); notifier.warning(UserMessages.MISSING_PASSWORD);
return false; return false;
} }
if (password1 != password2) { if (password1 != password2) {
notifier.warning("The passwords are different."); notifier.warning(UserMessages.DIFFERENT_PASSWORDS);
return false; return false;
} }
modifyPassword(password1) modifyPassword(password1)
.then(function () { .then(function () {
notifier.success("Your password has been changed. Please log in again.");
window.location.href = Endpoints.FIRST_FACTOR_GET; window.location.href = Endpoints.FIRST_FACTOR_GET;
}) })
.error(function () { .error(function () {
notifier.warning("An error occurred during password reset. Your password has not been changed."); notifier.error(UserMessages.RESET_PASSWORD_FAILED);
}); });
return false; return false;
} }

View File

@ -2,11 +2,12 @@
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import Endpoints = require("../../../../shared/api"); import Endpoints = require("../../../../shared/api");
import UserMessages = require("../../../../shared/UserMessages");
import Constants = require("./constants"); import Constants = require("./constants");
import jslogger = require("js-logger"); import jslogger = require("js-logger");
import { Notifier } from "../Notifier"; import { Notifier } from "../Notifier";
export default function(window: Window, $: JQueryStatic) { export default function (window: Window, $: JQueryStatic) {
const notifier = new Notifier(".notification", $); const notifier = new Notifier(".notification", $);
function requestPasswordReset(username: string) { function requestPasswordReset(username: string) {
@ -14,7 +15,11 @@ export default function(window: Window, $: JQueryStatic) {
$.get(Endpoints.RESET_PASSWORD_IDENTITY_START_GET, { $.get(Endpoints.RESET_PASSWORD_IDENTITY_START_GET, {
userid: username, userid: username,
}) })
.done(function () { .done(function (body: any) {
if (body && body.error) {
reject(new Error(body.error));
return;
}
resolve(); resolve();
}) })
.fail(function (xhr: JQueryXHR, textStatus: string) { .fail(function (xhr: JQueryXHR, textStatus: string) {
@ -27,25 +32,24 @@ export default function(window: Window, $: JQueryStatic) {
const username = $("#username").val(); const username = $("#username").val();
if (!username) { if (!username) {
notifier.warning("You must provide your username to reset your password."); notifier.warning(UserMessages.MISSING_USERNAME);
return; return;
} }
requestPasswordReset(username) requestPasswordReset(username)
.then(function () { .then(function () {
notifier.success("An email has been sent to you. Follow the link to change your password."); notifier.success(UserMessages.MAIL_SENT);
setTimeout(function () { setTimeout(function () {
window.location.replace(Endpoints.FIRST_FACTOR_GET); window.location.replace(Endpoints.FIRST_FACTOR_GET);
}, 1000); }, 1000);
}) })
.error(function () { .error(function () {
notifier.warning("Are you sure this is your username?"); notifier.error(UserMessages.MAIL_NOT_SENT);
}); });
return false; return false;
} }
$(document).ready(function () { $(document).ready(function () {
jslogger.debug("Reset password request form setup");
$(Constants.FORM_SELECTOR).on("submit", onFormSubmitted); $(Constants.FORM_SELECTOR).on("submit", onFormSubmitted);
}); });
} }

View File

@ -3,20 +3,24 @@ import BluebirdPromise = require("bluebird");
import Endpoints = require("../../../../shared/api"); import Endpoints = require("../../../../shared/api");
export function validate(token: string, $: JQueryStatic): BluebirdPromise<string> { export function validate(token: string, $: JQueryStatic): BluebirdPromise<string> {
return new BluebirdPromise<string>(function (resolve, reject) { return new BluebirdPromise<string>(function (resolve, reject) {
$.ajax({ $.ajax({
url: Endpoints.SECOND_FACTOR_TOTP_POST, url: Endpoints.SECOND_FACTOR_TOTP_POST,
data: { data: {
token: token, token: token,
}, },
method: "POST", method: "POST",
dataType: "json" dataType: "json"
} as JQueryAjaxSettings) } as JQueryAjaxSettings)
.done(function (data: any) { .done(function (body: any) {
resolve(data); if (body && body.error) {
}) reject(new Error(body.error));
.fail(function (xhr: JQueryXHR, textStatus: string) { return;
reject(new Error(textStatus)); }
}); resolve(body);
}); })
.fail(function (xhr: JQueryXHR, textStatus: string) {
reject(new Error(textStatus));
});
});
} }

View File

@ -4,59 +4,64 @@ import U2f = require("u2f");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import { SignMessage } from "../../../../shared/SignMessage"; import { SignMessage } from "../../../../shared/SignMessage";
import Endpoints = require("../../../../shared/api"); import Endpoints = require("../../../../shared/api");
import UserMessages = require("../../../../shared/UserMessages");
import { INotifier } from "../INotifier"; import { INotifier } from "../INotifier";
function finishU2fAuthentication(responseData: U2fApi.SignResponse, $: JQueryStatic): BluebirdPromise<void> { function finishU2fAuthentication(responseData: U2fApi.SignResponse, $: JQueryStatic): BluebirdPromise<void> {
return new BluebirdPromise<void>(function (resolve, reject) { return new BluebirdPromise<void>(function (resolve, reject) {
$.ajax({ $.ajax({
url: Endpoints.SECOND_FACTOR_U2F_SIGN_POST, url: Endpoints.SECOND_FACTOR_U2F_SIGN_POST,
data: responseData, data: responseData,
method: "POST", method: "POST",
dataType: "json" dataType: "json"
} as JQueryAjaxSettings) } as JQueryAjaxSettings)
.done(function (data) { .done(function (body: any) {
resolve(data); if (body && body.error) {
}) reject(new Error(body.error));
.fail(function (xhr: JQueryXHR, textStatus: string) { return;
reject(new Error(textStatus)); }
}); resolve(body);
}); })
.fail(function (xhr: JQueryXHR, textStatus: string) {
reject(new Error(textStatus));
});
});
} }
function startU2fAuthentication($: JQueryStatic, notifier: INotifier, u2fApi: typeof U2fApi): BluebirdPromise<void> { function startU2fAuthentication($: JQueryStatic, notifier: INotifier, u2fApi: typeof U2fApi): BluebirdPromise<void> {
return new BluebirdPromise<void>(function (resolve, reject) { return new BluebirdPromise<void>(function (resolve, reject) {
$.get(Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, {}, undefined, "json") $.get(Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, {}, undefined, "json")
.done(function (signResponse: SignMessage) { .done(function (signResponse: SignMessage) {
notifier.info("Please touch the token"); notifier.info(UserMessages.PLEASE_TOUCH_TOKEN);
const signRequest: U2fApi.SignRequest = { const signRequest: U2fApi.SignRequest = {
appId: signResponse.request.appId, appId: signResponse.request.appId,
challenge: signResponse.request.challenge, challenge: signResponse.request.challenge,
keyHandle: signResponse.keyHandle, // linked to the client session cookie keyHandle: signResponse.keyHandle, // linked to the client session cookie
version: "U2F_V2" version: "U2F_V2"
}; };
u2fApi.sign([signRequest], 60) u2fApi.sign([signRequest], 60)
.then(function (signResponse: U2fApi.SignResponse) { .then(function (signResponse: U2fApi.SignResponse) {
finishU2fAuthentication(signResponse, $) finishU2fAuthentication(signResponse, $)
.then(function (data) { .then(function (data) {
resolve(data); resolve(data);
}, function (err) { }, function (err) {
notifier.error("Error when finish U2F transaction"); notifier.error(UserMessages.U2F_TRANSACTION_FINISH_FAILED);
reject(err); reject(err);
}); });
}) })
.catch(function (err: Error) { .catch(function (err: Error) {
reject(err); reject(err);
}); });
}) })
.fail(function (xhr: JQueryXHR, textStatus: string) { .fail(function (xhr: JQueryXHR, textStatus: string) {
reject(new Error(textStatus)); reject(new Error(textStatus));
}); });
}); });
} }
export function validate($: JQueryStatic, notifier: INotifier, u2fApi: typeof U2fApi): BluebirdPromise<void> { export function validate($: JQueryStatic, notifier: INotifier, u2fApi: typeof U2fApi): BluebirdPromise<void> {
return startU2fAuthentication($, notifier, u2fApi); return startU2fAuthentication($, notifier, u2fApi);
} }

View File

@ -9,7 +9,7 @@ import { Notifier } from "../Notifier";
import { QueryParametersRetriever } from "../QueryParametersRetriever"; import { QueryParametersRetriever } from "../QueryParametersRetriever";
import Endpoints = require("../../../../shared/api"); import Endpoints = require("../../../../shared/api");
import ServerConstants = require("../../../../shared/constants"); import ServerConstants = require("../../../../shared/constants");
import UserMessages = require("../../../../shared/UserMessages");
export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) { export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) {
const notifierTotp = new Notifier(".notification-totp", $); const notifierTotp = new Notifier(".notification-totp", $);
@ -20,7 +20,7 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi)
if (redirectUrl) if (redirectUrl)
window.location.href = redirectUrl; window.location.href = redirectUrl;
else else
notifier.success("Authentication succeeded. You can now access your services."); notifier.success(UserMessages.AUTHENTICATION_SUCCEEDED);
} }
function onSecondFactorTotpSuccess(data: any) { function onSecondFactorTotpSuccess(data: any) {
@ -28,7 +28,7 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi)
} }
function onSecondFactorTotpFailure(err: Error) { function onSecondFactorTotpFailure(err: Error) {
notifierTotp.error("Problem with TOTP validation."); notifierTotp.error(UserMessages.AUTHENTICATION_TOTP_FAILED);
} }
function onU2fAuthenticationSuccess(data: any) { function onU2fAuthenticationSuccess(data: any) {
@ -36,13 +36,11 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi)
} }
function onU2fAuthenticationFailure() { function onU2fAuthenticationFailure() {
notifierU2f.error("Problem with U2F validation. Did you register before authenticating?"); notifierU2f.error(UserMessages.AUTHENTICATION_U2F_FAILED);
} }
function onTOTPFormSubmitted(): boolean { function onTOTPFormSubmitted(): boolean {
const token = $(Constants.TOTP_TOKEN_SELECTOR).val(); const token = $(Constants.TOTP_TOKEN_SELECTOR).val();
jslogger.debug("TOTP token is %s", token);
TOTPValidator.validate(token, $) TOTPValidator.validate(token, $)
.then(onSecondFactorTotpSuccess) .then(onSecondFactorTotpSuccess)
.catch(onSecondFactorTotpFailure); .catch(onSecondFactorTotpFailure);
@ -50,7 +48,6 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi)
} }
function onU2FFormSubmitted(): boolean { function onU2FFormSubmitted(): boolean {
jslogger.debug("Start U2F authentication");
U2FValidator.validate($, notifierU2f, U2fApi) U2FValidator.validate($, notifierU2f, U2fApi)
.then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure); .then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure);
return false; return false;

View File

@ -5,6 +5,7 @@ import u2fApi = require("u2f-api");
import jslogger = require("js-logger"); import jslogger = require("js-logger");
import { Notifier } from "../Notifier"; import { Notifier } from "../Notifier";
import Endpoints = require("../../../../shared/api"); import Endpoints = require("../../../../shared/api");
import UserMessages = require("../../../../shared/UserMessages");
export default function (window: Window, $: JQueryStatic) { export default function (window: Window, $: JQueryStatic) {
const notifier = new Notifier(".notification", $); const notifier = new Notifier(".notification", $);
@ -16,8 +17,12 @@ export default function (window: Window, $: JQueryStatic) {
return new BluebirdPromise<string>(function (resolve, reject) { return new BluebirdPromise<string>(function (resolve, reject) {
$.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, registrationData, undefined, "json") $.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, registrationData, undefined, "json")
.done(function (data) { .done(function (body: any) {
resolve(data.redirection_url); if (body && body.error) {
reject(new Error(body.error));
return;
}
resolve(body.redirection_url);
}) })
.fail(function (xhr, status) { .fail(function (xhr, status) {
reject(); reject();
@ -29,8 +34,6 @@ export default function (window: Window, $: JQueryStatic) {
return new BluebirdPromise<string>(function (resolve, reject) { return new BluebirdPromise<string>(function (resolve, reject) {
$.get(Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, {}, undefined, "json") $.get(Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, {}, undefined, "json")
.done(function (registrationRequest: U2f.Request) { .done(function (registrationRequest: U2f.Request) {
jslogger.debug("registrationRequest = %s", JSON.stringify(registrationRequest));
const registerRequest: u2fApi.RegisterRequest = registrationRequest; const registerRequest: u2fApi.RegisterRequest = registrationRequest;
u2fApi.register([registerRequest], [], 120) u2fApi.register([registerRequest], [], 120)
.then(function (res: u2fApi.RegisterResponse) { .then(function (res: u2fApi.RegisterResponse) {
@ -47,7 +50,7 @@ export default function (window: Window, $: JQueryStatic) {
} }
function onRegisterFailure(err: Error) { function onRegisterFailure(err: Error) {
notifier.error("Problem while registering your U2F device."); notifier.error(UserMessages.REGISTRATION_U2F_FAILED);
} }
$(document).ready(function () { $(document).ready(function () {

View File

@ -38,7 +38,7 @@ describe("test FirstFactorValidator", function () {
describe("should fail first factor validation", () => { describe("should fail first factor validation", () => {
it("should fail with error", () => { it("should fail with error", () => {
return should_fail_first_factor_validation("Authetication failed. Please check your credentials."); return should_fail_first_factor_validation("Authentication failed. Please check your credentials.");
}); });
}); });
}); });

View File

@ -5,33 +5,33 @@ import BluebirdPromise = require("bluebird");
import Assert = require("assert"); import Assert = require("assert");
describe("test TOTPValidator", function () { describe("test TOTPValidator", function () {
it("should initiate an identity check successfully", () => { it("should initiate an identity check successfully", () => {
const postPromise = JQueryMock.JQueryDeferredMock(); const postPromise = JQueryMock.JQueryDeferredMock();
postPromise.done.yields(); postPromise.done.yields();
postPromise.done.returns(postPromise); postPromise.done.returns(postPromise);
const jqueryMock = JQueryMock.JQueryMock(); const jqueryMock = JQueryMock.JQueryMock();
jqueryMock.jquery.ajax.returns(postPromise); jqueryMock.jquery.ajax.returns(postPromise);
return TOTPValidator.validate("totp_token", jqueryMock.jquery as any); return TOTPValidator.validate("totp_token", jqueryMock.jquery as any);
}); });
it("should fail validating TOTP token", () => { it("should fail validating TOTP token", () => {
const errorMessage = "Error while validating TOTP token"; const errorMessage = "Error while validating TOTP token";
const postPromise = JQueryMock.JQueryDeferredMock(); const postPromise = JQueryMock.JQueryDeferredMock();
postPromise.fail.yields(undefined, errorMessage); postPromise.fail.yields(undefined, errorMessage);
postPromise.done.returns(postPromise); postPromise.done.returns(postPromise);
const jqueryMock = JQueryMock.JQueryMock(); const jqueryMock = JQueryMock.JQueryMock();
jqueryMock.jquery.ajax.returns(postPromise); jqueryMock.jquery.ajax.returns(postPromise);
return TOTPValidator.validate("totp_token", jqueryMock.jquery as any) return TOTPValidator.validate("totp_token", jqueryMock.jquery as any)
.then(function () { .then(function () {
return BluebirdPromise.reject(new Error("Registration successfully finished while it should have not.")); return BluebirdPromise.reject(new Error("Registration successfully finished while it should have not."));
}, function (err: Error) { }, function (err: Error) {
Assert.equal(errorMessage, err.message); Assert.equal(errorMessage, err.message);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
}); });
}); });

View File

@ -66,8 +66,8 @@
"@types/query-string": "^4.3.1", "@types/query-string": "^4.3.1",
"@types/randomstring": "^1.1.5", "@types/randomstring": "^1.1.5",
"@types/redis": "^2.6.0", "@types/redis": "^2.6.0",
"@types/request": "0.0.46", "@types/request": "^2.0.5",
"@types/request-promise": "^4.1.37", "@types/request-promise": "^4.1.38",
"@types/selenium-webdriver": "^3.0.4", "@types/selenium-webdriver": "^3.0.4",
"@types/sinon": "^2.2.1", "@types/sinon": "^2.2.1",
"@types/speakeasy": "^2.0.1", "@types/speakeasy": "^2.0.1",
@ -96,7 +96,7 @@
"power-assert": "^1.4.4", "power-assert": "^1.4.4",
"proxyquire": "^1.8.0", "proxyquire": "^1.8.0",
"query-string": "^4.3.4", "query-string": "^4.3.4",
"request": "^2.81.0", "request": "^2.83.0",
"request-promise": "^4.2.2", "request-promise": "^4.2.2",
"selenium-webdriver": "^3.5.0", "selenium-webdriver": "^3.5.0",
"should": "^11.1.1", "should": "^11.1.1",

View File

@ -3,12 +3,12 @@ import BluebirdPromise = require("bluebird");
import { IRequestLogger } from "./logging/IRequestLogger"; import { IRequestLogger } from "./logging/IRequestLogger";
function replyWithError(req: express.Request, res: express.Response, function replyWithError(req: express.Request, res: express.Response,
code: number, logger: IRequestLogger): (err: Error) => void { code: number, logger: IRequestLogger, body?: Object): (err: Error) => void {
return function (err: Error): void { return function (err: Error): void {
logger.error(req, "Reply with error %d: %s", code, err.message); logger.error(req, "Reply with error %d: %s", code, err.message);
logger.debug(req, "%s", err.stack); logger.debug(req, "%s", err.stack);
res.status(code); res.status(code);
res.send(); res.send(body);
}; };
} }
@ -27,7 +27,7 @@ export function replyWithError403(req: express.Request,
return replyWithError(req, res, 403, logger); return replyWithError(req, res, 403, logger);
} }
export function replyWithError500(req: express.Request, export function replyWithError200(req: express.Request,
res: express.Response, logger: IRequestLogger) { res: express.Response, logger: IRequestLogger, message: string) {
return replyWithError(req, res, 500, logger); return replyWithError(req, res, 200, logger, { error: message });
} }

View File

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

View File

@ -28,8 +28,10 @@ export interface IdentityValidable {
preValidationInit(req: express.Request): BluebirdPromise<Identity.Identity>; preValidationInit(req: express.Request): BluebirdPromise<Identity.Identity>;
postValidationInit(req: express.Request): BluebirdPromise<void>; postValidationInit(req: express.Request): BluebirdPromise<void>;
preValidationResponse(req: express.Request, res: express.Response): void; // Serves a page after identity check request // Serves a page after identity check request
postValidationResponse(req: express.Request, res: express.Response): void; // Serves the page if identity validated preValidationResponse(req: express.Request, res: express.Response): void;
// Serves the page if identity validated
postValidationResponse(req: express.Request, res: express.Response): void;
mailSubject(): string; mailSubject(): string;
} }
@ -50,7 +52,8 @@ function consumeToken(token: string, challenge: string, userDataStore: IUserData
return userDataStore.consumeIdentityValidationToken(token, challenge); return userDataStore.consumeIdentityValidationToken(token, challenge);
} }
export function register(app: express.Application, pre_validation_endpoint: string, post_validation_endpoint: string, handler: IdentityValidable) { export function register(app: express.Application, pre_validation_endpoint: string,
post_validation_endpoint: string, handler: IdentityValidable) {
app.get(pre_validation_endpoint, get_start_validation(handler, post_validation_endpoint)); app.get(pre_validation_endpoint, get_start_validation(handler, post_validation_endpoint));
app.get(post_validation_endpoint, get_finish_validation(handler)); app.get(post_validation_endpoint, get_finish_validation(handler));
} }
@ -91,14 +94,13 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque
handler.postValidationResponse(req, res); handler.postValidationResponse(req, res);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger)) .catch(ErrorReplies.replyWithError401(req, res, logger));
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(req, res, logger))
.catch(ErrorReplies.replyWithError500(req, res, logger));
}; };
} }
export function get_start_validation(handler: IdentityValidable, postValidationEndpoint: string): express.RequestHandler { export function get_start_validation(handler: IdentityValidable, postValidationEndpoint: string)
: express.RequestHandler {
return function (req: express.Request, res: express.Response): BluebirdPromise<void> { return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const logger = ServerVariablesHandler.getLogger(req.app); const logger = ServerVariablesHandler.getLogger(req.app);
const notifier = ServerVariablesHandler.getNotifier(req.app); const notifier = ServerVariablesHandler.getNotifier(req.app);
@ -113,13 +115,15 @@ export function get_start_validation(handler: IdentityValidable, postValidationE
logger.info(req, "Start identity validation of user \"%s\"", userid); logger.info(req, "Start identity validation of user \"%s\"", userid);
if (!(email && userid)) if (!(email && userid))
return BluebirdPromise.reject(new Exceptions.IdentityError("Missing user id or email address")); return BluebirdPromise.reject(new Exceptions.IdentityError(
"Missing user id or email address"));
return createAndSaveToken(userid, handler.challenge(), userDataStore); return createAndSaveToken(userid, handler.challenge(), userDataStore);
}) })
.then(function (token: string) { .then(function (token: string) {
const host = req.get("Host"); const host = req.get("Host");
const link_url = util.format("https://%s%s?identity_token=%s", host, postValidationEndpoint, token); const link_url = util.format("https://%s%s?identity_token=%s", host,
postValidationEndpoint, token);
logger.info(req, "Notification sent to user \"%s\"", identity.userid); logger.info(req, "Notification sent to user \"%s\"", identity.userid);
return notifier.notify(identity, handler.mailSubject(), link_url); return notifier.notify(identity, handler.mailSubject(), link_url);
}) })
@ -127,9 +131,6 @@ export function get_start_validation(handler: IdentityValidable, postValidationE
handler.preValidationResponse(req, res); handler.preValidationResponse(req, res);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger)) .catch(ErrorReplies.replyWithError401(req, res, logger));
.catch(Exceptions.IdentityError, ErrorReplies.replyWithError400(req, res, logger))
.catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(req, res, logger))
.catch(ErrorReplies.replyWithError500(req, res, logger));
}; };
} }

View File

@ -7,6 +7,7 @@ import ErrorReplies = require("../ErrorReplies");
import objectPath = require("object-path"); import objectPath = require("object-path");
import { ServerVariablesHandler } from "../ServerVariablesHandler"; import { ServerVariablesHandler } from "../ServerVariablesHandler";
import AuthenticationSession = require("../AuthenticationSession"); import AuthenticationSession = require("../AuthenticationSession");
import UserMessages = require("../../../../shared/UserMessages");
type Handler = (req: express.Request, res: express.Response) => BluebirdPromise<void>; type Handler = (req: express.Request, res: express.Response) => BluebirdPromise<void>;
@ -21,6 +22,6 @@ export default function (callback: Handler): Handler {
.then(function () { .then(function () {
return callback(req, res); return callback(req, res);
}) })
.catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger)); .catch(ErrorReplies.replyWithError401(req, res, logger));
}; };
} }

View File

@ -12,6 +12,7 @@ import { ServerVariablesHandler } from "../../ServerVariablesHandler";
import AuthenticationSession = require("../../AuthenticationSession"); import AuthenticationSession = require("../../AuthenticationSession");
import Constants = require("../../../../../shared/constants"); import Constants = require("../../../../../shared/constants");
import { DomainExtractor } from "../../utils/DomainExtractor"; import { DomainExtractor } from "../../utils/DomainExtractor";
import UserMessages = require("../../../../../shared/UserMessages");
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> { export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
const username: string = req.body.username; const username: string = req.body.username;
@ -22,9 +23,7 @@ export default function (req: express.Request, res: express.Response): BluebirdP
const config = ServerVariablesHandler.getConfiguration(req.app); const config = ServerVariablesHandler.getConfiguration(req.app);
if (!username || !password) { if (!username || !password) {
const err = new Error("No username or password"); return BluebirdPromise.reject(new Error("No username or password."));
ErrorReplies.replyWithError401(req, res, logger)(err);
return BluebirdPromise.reject(err);
} }
const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app); const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app);
@ -93,12 +92,9 @@ export default function (req: express.Request, res: express.Response): BluebirdP
} }
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(req, res, logger))
.catch(exceptions.LdapBindError, function (err: Error) { .catch(exceptions.LdapBindError, function (err: Error) {
regulator.mark(username, false); regulator.mark(username, false);
return ErrorReplies.replyWithError401(req, res, logger)(err); return ErrorReplies.replyWithError200(req, res, logger, UserMessages.OPERATION_FAILED)(err);
}) })
.catch(exceptions.AuthenticationRegulationError, ErrorReplies.replyWithError403(req, res, logger)) .catch(ErrorReplies.replyWithError200(req, res, logger, UserMessages.OPERATION_FAILED));
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError401(req, res, logger))
.catch(ErrorReplies.replyWithError500(req, res, logger));
} }

View File

@ -6,6 +6,7 @@ import exceptions = require("../../../Exceptions");
import { ServerVariablesHandler } from "../../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../AuthenticationSession"); import AuthenticationSession = require("../../../AuthenticationSession");
import ErrorReplies = require("../../../ErrorReplies"); import ErrorReplies = require("../../../ErrorReplies");
import UserMessages = require("../../../../../../shared/UserMessages");
import Constants = require("./../constants"); import Constants = require("./../constants");
@ -23,8 +24,6 @@ export default function (req: express.Request, res: express.Response): BluebirdP
logger.debug(req, "Challenge %s", authSession.identity_check.challenge); logger.debug(req, "Challenge %s", authSession.identity_check.challenge);
if (authSession.identity_check.challenge != Constants.CHALLENGE) { if (authSession.identity_check.challenge != Constants.CHALLENGE) {
res.status(403);
res.send();
return BluebirdPromise.reject(new Error("Bad challenge.")); return BluebirdPromise.reject(new Error("Bad challenge."));
} }
return ldapPasswordUpdater.updatePassword(authSession.identity_check.userid, newPassword); return ldapPasswordUpdater.updatePassword(authSession.identity_check.userid, newPassword);
@ -37,5 +36,5 @@ export default function (req: express.Request, res: express.Response): BluebirdP
res.send(); res.send();
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(ErrorReplies.replyWithError500(req, res, logger)); .catch(ErrorReplies.replyWithError200(req, res, logger, UserMessages.RESET_PASSWORD_FAILED));
} }

View File

@ -18,5 +18,6 @@ export default function (req: express.Request, res: express.Response): BluebirdP
}); });
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(ErrorReplies.replyWithError500(req, res, logger)); .catch(ErrorReplies.replyWithError200(req, res, logger,
"Unexpected error."));
} }

View File

@ -11,6 +11,7 @@ import Endpoints = require("../../../../../../../shared/api");
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession"); import AuthenticationSession = require("../../../../AuthenticationSession");
import UserMessages = require("../../../../../../../shared/UserMessages");
import FirstFactorValidator = require("../../../../FirstFactorValidator"); import FirstFactorValidator = require("../../../../FirstFactorValidator");
@ -62,8 +63,6 @@ export default class RegistrationHandler implements IdentityValidable {
const challenge = authSession.identity_check.challenge; const challenge = authSession.identity_check.challenge;
if (challenge != Constants.CHALLENGE || !userid) { if (challenge != Constants.CHALLENGE || !userid) {
res.status(403);
res.send();
return BluebirdPromise.reject(new Error("Bad challenge.")); return BluebirdPromise.reject(new Error("Bad challenge."));
} }
@ -83,7 +82,7 @@ export default class RegistrationHandler implements IdentityValidable {
}); });
}); });
}) })
.catch(ErrorReplies.replyWithError500(req, res, logger)); .catch(ErrorReplies.replyWithError200(req, res, logger, UserMessages.OPERATION_FAILED));
} }
mailSubject(): string { mailSubject(): string {

View File

@ -10,6 +10,7 @@ import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariablesHandler } from "./../../../../ServerVariablesHandler"; import { ServerVariablesHandler } from "./../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession"); import AuthenticationSession = require("../../../../AuthenticationSession");
import UserMessages = require("../../../../../../../shared/UserMessages");
const UNAUTHORIZED_MESSAGE = "Unauthorized access"; const UNAUTHORIZED_MESSAGE = "Unauthorized access";
@ -25,7 +26,7 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr
return AuthenticationSession.get(req) return AuthenticationSession.get(req)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) { .then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession; authSession = _authSession;
logger.info(req, "Initiate TOTP validation for user '%s'", authSession.userid); logger.info(req, "Initiate TOTP validation for user '%s'.", authSession.userid);
return userDataStore.retrieveTOTPSecret(authSession.userid); return userDataStore.retrieveTOTPSecret(authSession.userid);
}) })
.then(function (doc: TOTPSecretDocument) { .then(function (doc: TOTPSecretDocument) {
@ -33,11 +34,11 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr
return totpValidator.validate(token, doc.secret.base32); return totpValidator.validate(token, doc.secret.base32);
}) })
.then(function () { .then(function () {
logger.debug(req, "TOTP validation succeeded"); logger.debug(req, "TOTP validation succeeded.");
authSession.second_factor = true; authSession.second_factor = true;
redirect(req, res); redirect(req, res);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(exceptions.InvalidTOTPError, ErrorReplies.replyWithError401(req, res, logger)) .catch(ErrorReplies.replyWithError200(req, res, logger,
.catch(ErrorReplies.replyWithError500(req, res, logger)); UserMessages.OPERATION_FAILED));
} }

View File

@ -12,6 +12,7 @@ import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession"); import AuthenticationSession = require("../../../../AuthenticationSession");
import UserMessages = require("../../../../../../../shared/UserMessages");
export default FirstFactorBlocker(handler); export default FirstFactorBlocker(handler);
@ -31,15 +32,11 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
const registrationRequest = authSession.register_request; const registrationRequest = authSession.register_request;
if (!registrationRequest) { if (!registrationRequest) {
res.status(403);
res.send();
return BluebirdPromise.reject(new Error("No registration request")); return BluebirdPromise.reject(new Error("No registration request"));
} }
if (!authSession.identity_check if (!authSession.identity_check
|| authSession.identity_check.challenge != "u2f-register") { || authSession.identity_check.challenge != "u2f-register") {
res.status(403);
res.send();
return BluebirdPromise.reject(new Error("Bad challenge for registration request")); return BluebirdPromise.reject(new Error("Bad challenge for registration request"));
} }
@ -67,5 +64,6 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
redirect(req, res); redirect(req, res);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(ErrorReplies.replyWithError500(req, res, logger)); .catch(ErrorReplies.replyWithError200(req, res, logger,
UserMessages.OPERATION_FAILED));
} }

View File

@ -10,6 +10,7 @@ import FirstFactorBlocker from "../../../FirstFactorBlocker";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import {  ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import {  ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession"); import AuthenticationSession = require("../../../../AuthenticationSession");
import UserMessages = require("../../../../../../../shared/UserMessages");
export default FirstFactorBlocker(handler); export default FirstFactorBlocker(handler);
@ -41,5 +42,6 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise<v
res.json(registrationRequest); res.json(registrationRequest);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(ErrorReplies.replyWithError500(req, res, logger)); .catch(ErrorReplies.replyWithError200(req, res, logger,
UserMessages.OPERATION_FAILED));
} }

View File

@ -13,6 +13,7 @@ import redirect from "../../redirect";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession"); import AuthenticationSession = require("../../../../AuthenticationSession");
import UserMessages = require("../../../../../../../shared/UserMessages");
export default FirstFactorBlocker(handler); export default FirstFactorBlocker(handler);
@ -50,6 +51,7 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr
redirect(req, res); redirect(req, res);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(ErrorReplies.replyWithError500(req, res, logger)); .catch(ErrorReplies.replyWithError200(req, res, logger,
UserMessages.OPERATION_FAILED));
} }

View File

@ -13,6 +13,7 @@ import FirstFactorBlocker from "../../../FirstFactorBlocker";
import ErrorReplies = require("../../../../ErrorReplies"); import ErrorReplies = require("../../../../ErrorReplies");
import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../../../ServerVariablesHandler";
import AuthenticationSession = require("../../../../AuthenticationSession"); import AuthenticationSession = require("../../../../AuthenticationSession");
import UserMessages = require("../../../../../../../shared/UserMessages");
export default FirstFactorBlocker(handler); export default FirstFactorBlocker(handler);
@ -50,7 +51,7 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr
res.json(authenticationMessage); res.json(authenticationMessage);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(exceptions.AccessDeniedError, ErrorReplies.replyWithError401(req, res, logger)) .catch(ErrorReplies.replyWithError200(req, res, logger,
.catch(ErrorReplies.replyWithError500(req, res, logger)); UserMessages.OPERATION_FAILED));
} }

View File

@ -67,7 +67,10 @@ export default function (req: express.Request, res: express.Response): BluebirdP
res.send(); res.send();
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}) })
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError403(req, res, logger)) // The user is authenticated but has restricted access -> 403
.catch(exceptions.DomainAccessDenied, ErrorReplies
.replyWithError403(req, res, logger))
// The user is not yet authenticated -> 401
.catch(ErrorReplies.replyWithError401(req, res, logger)); .catch(ErrorReplies.replyWithError401(req, res, logger));
} }

View File

@ -5,7 +5,7 @@ html
title Authelia - 2FA title Authelia - 2FA
meta(name="viewport", content="width=device-width, initial-scale=1.0")/ meta(name="viewport", content="width=device-width, initial-scale=1.0")/
link(rel="icon", href="/img/icon.png" type="image/png" sizes="32x32")/ link(rel="icon", href="/img/icon.png" type="image/png" sizes="32x32")/
link(rel="stylesheet", type="text/css", href="/css/authelia.min.css")/ link(rel="stylesheet", type="text/css", href="/css/authelia.css")/
if redirection_url if redirection_url
<meta http-equiv="refresh" content="5;url=#{redirection_url}"> <meta http-equiv="refresh" content="5;url=#{redirection_url}">
body body

View File

@ -5,7 +5,7 @@ import AuthenticationSession = require("../src/lib/AuthenticationSession");
import { UserDataStore } from "../src/lib/storage/UserDataStore"; import { UserDataStore } from "../src/lib/storage/UserDataStore";
import exceptions = require("../src/lib/Exceptions"); import exceptions = require("../src/lib/Exceptions");
import assert = require("assert"); import Assert = require("assert");
import Promise = require("bluebird"); import Promise = require("bluebird");
import express = require("express"); import express = require("express");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
@ -69,11 +69,11 @@ describe("test identity check process", function () {
return callback(req as any, res as any, undefined) return callback(req as any, res as any, undefined)
.then(function () { return BluebirdPromise.reject("Should fail"); }) .then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () { .catch(function () {
assert.equal(res.status.getCall(0).args[0], 401); Assert.equal(res.status.getCall(0).args[0], 401);
}); });
}); });
it("should send 400 if email is missing in provided identity", function () { it("should send 401 if email is missing in provided identity", function () {
const identity = { userid: "abc" }; const identity = { userid: "abc" };
identityValidable.preValidationInit.returns(BluebirdPromise.resolve(identity)); identityValidable.preValidationInit.returns(BluebirdPromise.resolve(identity));
@ -82,11 +82,11 @@ describe("test identity check process", function () {
return callback(req as any, res as any, undefined) return callback(req as any, res as any, undefined)
.then(function () { return BluebirdPromise.reject("Should fail"); }) .then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () { .catch(function () {
assert.equal(res.status.getCall(0).args[0], 400); Assert.equal(res.status.getCall(0).args[0], 401);
}); });
}); });
it("should send 400 if userid is missing in provided identity", function () { it("should send 401 if userid is missing in provided identity", function () {
const endpoint = "/protected"; const endpoint = "/protected";
const identity = { email: "abc@example.com" }; const identity = { email: "abc@example.com" };
@ -96,7 +96,7 @@ describe("test identity check process", function () {
return callback(req as any, res as any, undefined) return callback(req as any, res as any, undefined)
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function (err: Error) { .catch(function (err: Error) {
assert.equal(res.status.getCall(0).args[0], 400); Assert.equal(res.status.getCall(0).args[0], 401);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
}); });
@ -111,23 +111,23 @@ describe("test identity check process", function () {
return callback(req as any, res as any, undefined) return callback(req as any, res as any, undefined)
.then(function () { .then(function () {
assert(notifier.notify.calledOnce); Assert(notifier.notify.calledOnce);
assert(mocks.userDataStore.produceIdentityValidationTokenStub.calledOnce); Assert(mocks.userDataStore.produceIdentityValidationTokenStub.calledOnce);
assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[0], "user"); Assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[0], "user");
assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[3], 240000); Assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[3], 240000);
}); });
}); });
} }
function test_finish_get_handler() { function test_finish_get_handler() {
it("should send 403 if no identity_token is provided", function () { it("should send 401 if no identity_token is provided", function () {
const callback = IdentityValidator.get_finish_validation(identityValidable); const callback = IdentityValidator.get_finish_validation(identityValidable);
return callback(req as any, res as any, undefined) return callback(req as any, res as any, undefined)
.then(function () { return BluebirdPromise.reject("Should fail"); }) .then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () { .catch(function () {
assert.equal(res.status.getCall(0).args[0], 403); Assert.equal(res.status.getCall(0).args[0], 401);
}); });
}); });
@ -138,7 +138,7 @@ describe("test identity check process", function () {
return callback(req as any, res as any, undefined); return callback(req as any, res as any, undefined);
}); });
it("should return 500 if identity_token is provided but invalid", function () { it("should return 401 if identity_token is provided but invalid", function () {
req.query.identity_token = "token"; req.query.identity_token = "token";
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.reject(new Error("Invalid token"))); mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.reject(new Error("Invalid token")));
@ -147,7 +147,7 @@ describe("test identity check process", function () {
return callback(req as any, res as any, undefined) return callback(req as any, res as any, undefined)
.then(function () { return BluebirdPromise.reject("Should fail"); }) .then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () { .catch(function () {
assert.equal(res.status.getCall(0).args[0], 500); Assert.equal(res.status.getCall(0).args[0], 401);
}); });
}); });
@ -165,7 +165,7 @@ describe("test identity check process", function () {
}) })
.then(function () { return BluebirdPromise.reject("Should fail"); }) .then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () { .catch(function () {
assert.equal(authSession.identity_check.userid, "user"); Assert.equal(authSession.identity_check.userid, "user");
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
}); });

View File

@ -1,7 +1,7 @@
import sinon = require("sinon"); import sinon = require("sinon");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import assert = require("assert"); import Assert = require("assert");
import winston = require("winston"); import winston = require("winston");
import FirstFactorPost = require("../../../src/lib/routes/firstfactor/post"); import FirstFactorPost = require("../../../src/lib/routes/firstfactor/post");
@ -87,8 +87,8 @@ describe("test the first factor validation route", function () {
return FirstFactorPost.default(req as any, res as any); return FirstFactorPost.default(req as any, res as any);
}) })
.then(function () { .then(function () {
assert.equal("username", authSession.userid); Assert.equal("username", authSession.userid);
assert(res.send.calledOnce); Assert(res.send.calledOnce);
}); });
}); });
@ -113,27 +113,32 @@ describe("test the first factor validation route", function () {
return FirstFactorPost.default(req as any, res as any); return FirstFactorPost.default(req as any, res as any);
}) })
.then(function () { .then(function () {
assert.equal("test_ok@example.com", authSession.email); Assert.equal("test_ok@example.com", authSession.email);
}); });
}); });
it("should return status code 401 when LDAP authenticator throws", function () { it("should return error message when LDAP authenticator throws", function () {
(serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password") (serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password")
.returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials"))); .returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials")));
return FirstFactorPost.default(req as any, res as any) return FirstFactorPost.default(req as any, res as any)
.then(function () { .then(function () {
assert.equal(401, res.status.getCall(0).args[0]); Assert.equal(res.status.getCall(0).args[0], 200);
assert.equal(regulator.mark.getCall(0).args[0], "username"); Assert.equal(regulator.mark.getCall(0).args[0], "username");
Assert.deepEqual(res.send.getCall(0).args[0], {
error: "Operation failed."
});
}); });
}); });
it("should return status code 403 when regulator rejects authentication", function () { it("should return error message when regulator rejects authentication", function () {
const err = new exceptions.AuthenticationRegulationError("Authentication regulation..."); const err = new exceptions.AuthenticationRegulationError("Authentication regulation...");
regulator.regulate.returns(BluebirdPromise.reject(err)); regulator.regulate.returns(BluebirdPromise.reject(err));
return FirstFactorPost.default(req as any, res as any) return FirstFactorPost.default(req as any, res as any)
.then(function () { .then(function () {
assert.equal(403, res.status.getCall(0).args[0]); Assert.equal(res.status.getCall(0).args[0], 200);
assert.equal(1, res.send.callCount); Assert.deepEqual(res.send.getCall(0).args[0], {
error: "Operation failed."
});
}); });
}); });
}); });

View File

@ -6,7 +6,7 @@ import { ServerVariablesHandler } from "../../../src/lib/ServerVariablesHandler"
import { UserDataStore } from "../../../src/lib/storage/UserDataStore"; import { UserDataStore } from "../../../src/lib/storage/UserDataStore";
import Sinon = require("sinon"); import Sinon = require("sinon");
import winston = require("winston"); import winston = require("winston");
import assert = require("assert"); import Assert = require("assert");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import ExpressMock = require("../../mocks/express"); import ExpressMock = require("../../mocks/express");
@ -85,9 +85,9 @@ describe("test reset password route", function () {
.then(function () { .then(function () {
return AuthenticationSession.get(req as any); return AuthenticationSession.get(req as any);
}).then(function (_authSession: AuthenticationSession.AuthenticationSession) { }).then(function (_authSession: AuthenticationSession.AuthenticationSession) {
assert.equal(res.status.getCall(0).args[0], 204); Assert.equal(res.status.getCall(0).args[0], 204);
assert.equal(_authSession.first_factor, false); Assert.equal(_authSession.first_factor, false);
assert.equal(_authSession.second_factor, false); Assert.equal(_authSession.second_factor, false);
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
}); });
@ -102,7 +102,10 @@ describe("test reset password route", function () {
return PasswordResetFormPost.default(req as any, res as any); return PasswordResetFormPost.default(req as any, res as any);
}) })
.then(function () { .then(function () {
assert.equal(res.status.getCall(0).args[0], 403); Assert.equal(res.status.getCall(0).args[0], 200);
Assert.deepEqual(res.send.getCall(0).args[0], {
error: "An error occurred during password reset. Your password has not been changed."
});
}); });
}); });
@ -121,7 +124,10 @@ describe("test reset password route", function () {
}; };
return PasswordResetFormPost.default(req as any, res as any); return PasswordResetFormPost.default(req as any, res as any);
}).then(function () { }).then(function () {
assert.equal(res.status.getCall(0).args[0], 500); Assert.equal(res.status.getCall(0).args[0], 200);
Assert.deepEqual(res.send.getCall(0).args[0], {
error: "An error occurred during password reset. Your password has not been changed."
});
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
}); });

View File

@ -84,7 +84,7 @@ describe("test u2f routes: register", function () {
}); });
}); });
it("should return unauthorized on finishRegistration error", function () { it("should return error message on finishRegistration error", function () {
const user_key_container = {}; const user_key_container = {};
const u2f_mock = U2FMock.U2FMock(); const u2f_mock = U2FMock.U2FMock();
u2f_mock.checkRegistration.returns({ errorCode: 500 }); u2f_mock.checkRegistration.returns({ errorCode: 500 });
@ -103,12 +103,15 @@ describe("test u2f routes: register", function () {
}) })
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(500, res.status.getCall(0).args[0]); assert.equal(200, res.status.getCall(0).args[0]);
assert.deepEqual(res.send.getCall(0).args[0], {
error: "Operation failed."
});
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
}); });
it("should return 403 when register_request is not provided", function () { it("should return error message when register_request is not provided", function () {
const user_key_container = {}; const user_key_container = {};
const u2f_mock = U2FMock.U2FMock(); const u2f_mock = U2FMock.U2FMock();
u2f_mock.checkRegistration.returns(BluebirdPromise.resolve()); u2f_mock.checkRegistration.returns(BluebirdPromise.resolve());
@ -121,12 +124,15 @@ describe("test u2f routes: register", function () {
}) })
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(403, res.status.getCall(0).args[0]); assert.equal(200, res.status.getCall(0).args[0]);
assert.deepEqual(res.send.getCall(0).args[0], {
error: "Operation failed."
});
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
}); });
it("should return forbidden error when no auth request has been initiated", function () { it("should return error message when no auth request has been initiated", function () {
const user_key_container = {}; const user_key_container = {};
const u2f_mock = U2FMock.U2FMock(); const u2f_mock = U2FMock.U2FMock();
u2f_mock.checkRegistration.returns(BluebirdPromise.resolve()); u2f_mock.checkRegistration.returns(BluebirdPromise.resolve());
@ -139,12 +145,15 @@ describe("test u2f routes: register", function () {
}) })
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(403, res.status.getCall(0).args[0]); assert.equal(200, res.status.getCall(0).args[0]);
assert.deepEqual(res.send.getCall(0).args[0], {
error: "Operation failed."
});
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
}); });
it("should return forbidden error when identity has not been verified", function () { it("should return error message when identity has not been verified", function () {
return AuthenticationSession.get(req as any) return AuthenticationSession.get(req as any)
.then(function (authSession) { .then(function (authSession) {
authSession.identity_check = undefined; authSession.identity_check = undefined;
@ -152,7 +161,10 @@ describe("test u2f routes: register", function () {
}) })
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function () { .catch(function () {
assert.equal(403, res.status.getCall(0).args[0]); assert.equal(200, res.status.getCall(0).args[0]);
assert.deepEqual(res.send.getCall(0).args[0], {
error: "Operation failed."
});
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
}); });
}); });

View File

@ -1,7 +1,7 @@
import sinon = require("sinon"); import sinon = require("sinon");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import assert = require("assert"); import Assert = require("assert");
import U2FRegisterRequestGet = require("../../../../../src/lib/routes/secondfactor/u2f/register_request/get"); import U2FRegisterRequestGet = require("../../../../../src/lib/routes/secondfactor/u2f/register_request/get");
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession"); import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
import { ServerVariablesHandler } from "../../../../../src/lib/ServerVariablesHandler"; import { ServerVariablesHandler } from "../../../../../src/lib/ServerVariablesHandler";
@ -67,28 +67,31 @@ describe("test u2f routes: register_request", function () {
mocks.u2f = u2f_mock; mocks.u2f = u2f_mock;
return U2FRegisterRequestGet.default(req as any, res as any) return U2FRegisterRequestGet.default(req as any, res as any)
.then(function () { .then(function () {
assert.deepEqual(expectedRequest, res.json.getCall(0).args[0]); Assert.deepEqual(expectedRequest, res.json.getCall(0).args[0]);
}); });
}); });
it("should return internal error on registration request", function (done) { it("should return internal error on registration request", function () {
res.send = sinon.spy(function (data: any) { res.send = sinon.spy();
assert.equal(500, res.status.getCall(0).args[0]);
done();
});
const user_key_container = {}; const user_key_container = {};
const u2f_mock = U2FMock.U2FMock(); const u2f_mock = U2FMock.U2FMock();
u2f_mock.request.returns(BluebirdPromise.reject("Internal error")); u2f_mock.request.returns(BluebirdPromise.reject("Internal error"));
mocks.u2f = u2f_mock; mocks.u2f = u2f_mock;
U2FRegisterRequestGet.default(req as any, res as any); return U2FRegisterRequestGet.default(req as any, res as any)
.then(function() {
Assert.equal(res.status.getCall(0).args[0], 200);
Assert.deepEqual(res.send.getCall(0).args[0], {
error: "Operation failed."
});
});
}); });
it("should return forbidden if identity has not been verified", function () { it("should return forbidden if identity has not been verified", function () {
authSession.identity_check = undefined; authSession.identity_check = undefined;
return U2FRegisterRequestGet.default(req as any, res as any) return U2FRegisterRequestGet.default(req as any, res as any)
.then(function () { .then(function () {
assert.equal(403, res.status.getCall(0).args[0]); Assert.equal(403, res.status.getCall(0).args[0]);
}); });
}); });
}); });

View File

@ -97,7 +97,9 @@ describe("test u2f routes: sign", function () {
mocks.u2f = u2f_mock; mocks.u2f = u2f_mock;
return U2FSignPost.default(req as any, res as any) return U2FSignPost.default(req as any, res as any)
.then(function () { .then(function () {
Assert.equal(500, res.status.getCall(0).args[0]); Assert.equal(res.status.getCall(0).args[0], 200);
Assert.deepEqual(res.send.getCall(0).args[0],
{ error: "Operation failed." });
}); });
}); });
}); });

View File

@ -127,25 +127,18 @@ describe("Private pages of the server must not be accessible without session", f
}); });
describe("Second factor endpoints must be protected if first factor is not validated", function () { describe("Second factor endpoints must be protected if first factor is not validated", function () {
function should_post_and_reply_with(url: string, status_code: number): BluebirdPromise<void> {
return requestp.postAsync(url).then(function (response: request.RequestResponse) {
Assert.equal(response.statusCode, status_code);
return BluebirdPromise.resolve();
});
}
function should_get_and_reply_with(url: string, status_code: number): BluebirdPromise<void> {
return requestp.getAsync(url).then(function (response: request.RequestResponse) {
Assert.equal(response.statusCode, status_code);
return BluebirdPromise.resolve();
});
}
function should_post_and_reply_with_401(url: string): BluebirdPromise<void> { function should_post_and_reply_with_401(url: string): BluebirdPromise<void> {
return should_post_and_reply_with(url, 401); return requestp.postAsync(url).then(function (response: request.RequestResponse) {
Assert.equal(response.statusCode, 401);
return BluebirdPromise.resolve();
});
} }
function should_get_and_reply_with_401(url: string): BluebirdPromise<void> { function should_get_and_reply_with_401(url: string): BluebirdPromise<void> {
return should_get_and_reply_with(url, 401); return requestp.getAsync(url).then(function (response: request.RequestResponse) {
Assert.equal(response.statusCode, 401);
return BluebirdPromise.resolve();
});
} }
it("should block " + Endpoints.SECOND_FACTOR_GET, function () { it("should block " + Endpoints.SECOND_FACTOR_GET, function () {

23
shared/UserMessages.ts Normal file
View File

@ -0,0 +1,23 @@
export const AUTHENTICATION_FAILED = "Authentication failed. Please check your credentials.";
export const AUTHENTICATION_SUCCEEDED = "Authentication succeeded. You can now access your services.";
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?";
export const U2F_TRANSACTION_FINISH_FAILED = "U2F validation failed unexpectedly.";
export const PLEASE_TOUCH_TOKEN = "Please touch the token on your U2F device.";
export const REGISTRATION_U2F_FAILED = "Registration of U2F device failed.";
export const DIFFERENT_PASSWORDS = "The passwords are different.";
export const MISSING_PASSWORD = "You must enter your password twice.";
export const RESET_PASSWORD_FAILED = "An error occurred during password reset. Your password has not been changed.";
// Password reset request
export const MISSING_USERNAME = "You must provide your username to reset your password.";
export const MAIL_SENT = "An email has been sent to you. Follow the link to change your password.";
export const MAIL_NOT_SENT = "The email cannot be sent. Please retry in few minutes.";
export const UNAUTHORIZED_OPERATION = "You are not allowed to perform this operation.";
export const OPERATION_FAILED = "Operation failed.";

View File

@ -12,7 +12,7 @@ Feature: Authentication scenarii
When I set field "username" to "john" When I set field "username" to "john"
And I set field "password" to "bad-password" And I set field "password" to "bad-password"
And I click on "Sign in" And I click on "Sign in"
Then I get a notification of type "error" with message "Authentication failed. Please double check your credentials." Then I get a notification of type "error" with message "Authentication failed. Please check your credentials."
Scenario: User registers TOTP secret and succeeds authentication Scenario: User registers TOTP secret and succeeds authentication
Given I visit "https://auth.test.local:8080/" Given I visit "https://auth.test.local:8080/"
@ -31,7 +31,7 @@ Feature: Authentication scenarii
And I login with user "john" and password "password" And I login with user "john" and password "password"
And I use "BADTOKEN" as TOTP token And I use "BADTOKEN" as TOTP token
And I click on "TOTP" And I click on "TOTP"
Then I get a notification of type "error" with message "Problem with TOTP validation." Then I get a notification of type "error" with message "Authentication failed. Have you already registered your secret?"
Scenario: Logout redirects user to redirect URL given in parameter Scenario: Logout redirects user to redirect URL given in parameter
When I visit "https://auth.test.local:8080/logout?redirect=https://home.test.local:8080/" When I visit "https://auth.test.local:8080/logout?redirect=https://home.test.local:8080/"

View File

@ -7,16 +7,16 @@ Feature: Authelia regulates authentication to avoid brute force
And I set field "username" to "blackhat" And I set field "username" to "blackhat"
And I set field "password" to "bad-password" And I set field "password" to "bad-password"
And I click on "Sign in" And I click on "Sign in"
And I get a notification of type "error" with message "Authentication failed. Please double check your credentials." And I get a notification of type "error" with message "Authentication failed. Please check your credentials."
And I set field "password" to "bad-password" And I set field "password" to "bad-password"
And I click on "Sign in" And I click on "Sign in"
And I get a notification of type "error" with message "Authentication failed. Please double check your credentials." And I get a notification of type "error" with message "Authentication failed. Please check your credentials."
And I set field "password" to "bad-password" And I set field "password" to "bad-password"
And I click on "Sign in" And I click on "Sign in"
And I get a notification of type "error" with message "Authentication failed. Please double check your credentials." And I get a notification of type "error" with message "Authentication failed. Please check your credentials."
When I set field "password" to "password" When I set field "password" to "password"
And I click on "Sign in" And I click on "Sign in"
Then I get a notification of type "error" with message "Authentication failed. Please double check your credentials." Then I get a notification of type "error" with message "Authentication failed. Please check your credentials."
@needs-test-config @needs-test-config
@need-registered-user-blackhat @need-registered-user-blackhat
@ -25,13 +25,13 @@ Feature: Authelia regulates authentication to avoid brute force
And I set field "username" to "blackhat" And I set field "username" to "blackhat"
And I set field "password" to "bad-password" And I set field "password" to "bad-password"
And I click on "Sign in" And I click on "Sign in"
And I get a notification of type "error" with message "Authentication failed. Please double check your credentials." And I get a notification of type "error" with message "Authentication failed. Please check your credentials."
And I set field "password" to "bad-password" And I set field "password" to "bad-password"
And I click on "Sign in" And I click on "Sign in"
And I get a notification of type "error" with message "Authentication failed. Please double check your credentials." And I get a notification of type "error" with message "Authentication failed. Please check your credentials."
And I set field "password" to "bad-password" And I set field "password" to "bad-password"
And I click on "Sign in" And I click on "Sign in"
And I get a notification of type "error" with message "Authentication failed. Please double check your credentials." And I get a notification of type "error" with message "Authentication failed. Please check your credentials."
When I wait 6 seconds When I wait 6 seconds
And I set field "password" to "password" And I set field "password" to "password"
And I click on "Sign in" And I click on "Sign in"

View File

@ -11,6 +11,12 @@ Feature: User is able to reset his password
And I click on "Reset Password" And I click on "Reset Password"
Then I get a notification of type "success" with message "An email has been sent to you. Follow the link to change your password." Then I get a notification of type "success" with message "An email has been sent to you. Follow the link to change your password."
Scenario: Request password for unexisting user should behave like existing user
Given I'm on https://auth.test.local:8080/password-reset/request
When I set field "username" to "fake_user"
And I click on "Reset Password"
Then I get a notification of type "success" with message "An email has been sent to you. Follow the link to change your password."
Scenario: User resets his password Scenario: User resets his password
Given I'm on https://auth.test.local:8080/password-reset/request Given I'm on https://auth.test.local:8080/password-reset/request
And I set field "username" to "james" And I set field "username" to "james"

View File

@ -9,8 +9,8 @@ Feature: Non authenticated users have no access to certain pages
| https://auth.test.local:8080/secondfactor | 401 | | https://auth.test.local:8080/secondfactor | 401 |
| https://auth.test.local:8080/verify | 401 | | https://auth.test.local:8080/verify | 401 |
| https://auth.test.local:8080/secondfactor/u2f/identity/start | 401 | | https://auth.test.local:8080/secondfactor/u2f/identity/start | 401 |
| https://auth.test.local:8080/secondfactor/u2f/identity/finish | 403 | | https://auth.test.local:8080/secondfactor/u2f/identity/finish | 401 |
| https://auth.test.local:8080/secondfactor/totp/identity/start | 401 | | https://auth.test.local:8080/secondfactor/totp/identity/start | 401 |
| https://auth.test.local:8080/secondfactor/totp/identity/finish | 403 | | https://auth.test.local:8080/secondfactor/totp/identity/finish | 401 |
| https://auth.test.local:8080/password-reset/identity/start | 403 | | https://auth.test.local:8080/password-reset/identity/start | 401 |
| https://auth.test.local:8080/password-reset/identity/finish | 403 | | https://auth.test.local:8080/password-reset/identity/finish | 401 |