Merge pull request #140 from clems4ever/improve-endpoint-errors

Every public endpoints return 200 with harmonized error messages or 401
pull/144/head
Clément Michaud 2017-10-14 12:22:24 +02:00 committed by GitHub
commit f041b946d9
39 changed files with 325 additions and 249 deletions

View File

@ -59,7 +59,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

@ -67,8 +67,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",
@ -97,7 +97,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.email, handler.mailSubject(), link_url); return notifier.notify(identity.email, 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

@ -128,25 +128,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 () {

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 |