diff --git a/Gruntfile.js b/Gruntfile.js index 92bd0d4bd..5564951cf 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -59,7 +59,7 @@ module.exports = function (grunt) { }, "include-minified-script": { 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: { diff --git a/client/src/lib/firstfactor/FirstFactorValidator.ts b/client/src/lib/firstfactor/FirstFactorValidator.ts index 175f3c2c8..60a4d17c3 100644 --- a/client/src/lib/firstfactor/FirstFactorValidator.ts +++ b/client/src/lib/firstfactor/FirstFactorValidator.ts @@ -3,6 +3,7 @@ import BluebirdPromise = require("bluebird"); import Endpoints = require("../../../../shared/api"); import Constants = require("../../../../shared/constants"); import Util = require("util"); +import UserMessages = require("../../../../shared/UserMessages"); export function validate(username: string, password: string, redirectUrl: string, $: JQueryStatic): BluebirdPromise { @@ -24,11 +25,15 @@ export function validate(username: string, password: string, password: password, } }) - .done(function (data: { redirect: string }) { - resolve(data.redirect); + .done(function (body: any) { + if (body && body.error) { + reject(new Error(body.error)); + return; + } + resolve(body.redirect); }) .fail(function (xhr: JQueryXHR, textStatus: string) { - reject(new Error("Authetication failed. Please check your credentials.")); + reject(new Error(UserMessages.AUTHENTICATION_FAILED)); }); }); } diff --git a/client/src/lib/firstfactor/index.ts b/client/src/lib/firstfactor/index.ts index 91de7e001..0c5f9828d 100644 --- a/client/src/lib/firstfactor/index.ts +++ b/client/src/lib/firstfactor/index.ts @@ -5,6 +5,7 @@ import { Notifier } from "../Notifier"; import { QueryParametersRetriever } from "../QueryParametersRetriever"; import Constants = require("../../../../shared/constants"); import Endpoints = require("../../../../shared/api"); +import UserMessages = require("../../../../shared/UserMessages"); export default function (window: Window, $: JQueryStatic, firstFactorValidator: typeof FirstFactorValidator, jslogger: typeof JSLogger) { @@ -23,18 +24,14 @@ export default function (window: Window, $: JQueryStatic, } function onFirstFactorSuccess(redirectUrl: string) { - jslogger.debug("First factor validated."); - window.location.href = redirectUrl; + window.location.href = redirectUrl; } function onFirstFactorFailure(err: Error) { - jslogger.debug("First factor failed."); - notifier.error("Authentication failed. Please double check your credentials."); + notifier.error(UserMessages.AUTHENTICATION_FAILED); } - $(window.document).ready(function () { - jslogger.info("Enter first factor"); $("form").on("submit", onFormSubmitted); }); } diff --git a/client/src/lib/reset-password/reset-password-form.ts b/client/src/lib/reset-password/reset-password-form.ts index 12f60d777..888026f62 100644 --- a/client/src/lib/reset-password/reset-password-form.ts +++ b/client/src/lib/reset-password/reset-password-form.ts @@ -1,6 +1,8 @@ import BluebirdPromise = require("bluebird"); import Endpoints = require("../../../../shared/api"); +import UserMessages = require("../../../../shared/UserMessages"); + import Constants = require("./constants"); import { Notifier } from "../Notifier"; @@ -12,8 +14,12 @@ export default function (window: Window, $: JQueryStatic) { $.post(Endpoints.RESET_PASSWORD_FORM_POST, { password: newPassword, }) - .done(function (data) { - resolve(data); + .done(function (body: any) { + if (body && body.error) { + reject(new Error(body.error)); + return; + } + resolve(body); }) .fail(function (xhr, status) { reject(status); @@ -26,22 +32,21 @@ export default function (window: Window, $: JQueryStatic) { const password2 = $("#password2").val(); if (!password1 || !password2) { - notifier.warning("You must enter your new password twice."); + notifier.warning(UserMessages.MISSING_PASSWORD); return false; } if (password1 != password2) { - notifier.warning("The passwords are different."); + notifier.warning(UserMessages.DIFFERENT_PASSWORDS); return false; } modifyPassword(password1) .then(function () { - notifier.success("Your password has been changed. Please log in again."); window.location.href = Endpoints.FIRST_FACTOR_GET; }) .error(function () { - notifier.warning("An error occurred during password reset. Your password has not been changed."); + notifier.error(UserMessages.RESET_PASSWORD_FAILED); }); return false; } diff --git a/client/src/lib/reset-password/reset-password-request.ts b/client/src/lib/reset-password/reset-password-request.ts index 0685e1760..426bad34b 100644 --- a/client/src/lib/reset-password/reset-password-request.ts +++ b/client/src/lib/reset-password/reset-password-request.ts @@ -2,11 +2,12 @@ import BluebirdPromise = require("bluebird"); import Endpoints = require("../../../../shared/api"); +import UserMessages = require("../../../../shared/UserMessages"); import Constants = require("./constants"); import jslogger = require("js-logger"); import { Notifier } from "../Notifier"; -export default function(window: Window, $: JQueryStatic) { +export default function (window: Window, $: JQueryStatic) { const notifier = new Notifier(".notification", $); function requestPasswordReset(username: string) { @@ -14,7 +15,11 @@ export default function(window: Window, $: JQueryStatic) { $.get(Endpoints.RESET_PASSWORD_IDENTITY_START_GET, { userid: username, }) - .done(function () { + .done(function (body: any) { + if (body && body.error) { + reject(new Error(body.error)); + return; + } resolve(); }) .fail(function (xhr: JQueryXHR, textStatus: string) { @@ -27,25 +32,24 @@ export default function(window: Window, $: JQueryStatic) { const username = $("#username").val(); if (!username) { - notifier.warning("You must provide your username to reset your password."); + notifier.warning(UserMessages.MISSING_USERNAME); return; } requestPasswordReset(username) .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 () { window.location.replace(Endpoints.FIRST_FACTOR_GET); }, 1000); }) .error(function () { - notifier.warning("Are you sure this is your username?"); + notifier.error(UserMessages.MAIL_NOT_SENT); }); - return false; + return false; } $(document).ready(function () { - jslogger.debug("Reset password request form setup"); $(Constants.FORM_SELECTOR).on("submit", onFormSubmitted); }); } diff --git a/client/src/lib/secondfactor/TOTPValidator.ts b/client/src/lib/secondfactor/TOTPValidator.ts index 824b591a7..5d845edc5 100644 --- a/client/src/lib/secondfactor/TOTPValidator.ts +++ b/client/src/lib/secondfactor/TOTPValidator.ts @@ -3,20 +3,24 @@ import BluebirdPromise = require("bluebird"); import Endpoints = require("../../../../shared/api"); export function validate(token: string, $: JQueryStatic): BluebirdPromise { - return new BluebirdPromise(function (resolve, reject) { - $.ajax({ - url: Endpoints.SECOND_FACTOR_TOTP_POST, - data: { - token: token, - }, - method: "POST", - dataType: "json" - } as JQueryAjaxSettings) - .done(function (data: any) { - resolve(data); - }) - .fail(function (xhr: JQueryXHR, textStatus: string) { - reject(new Error(textStatus)); - }); - }); + return new BluebirdPromise(function (resolve, reject) { + $.ajax({ + url: Endpoints.SECOND_FACTOR_TOTP_POST, + data: { + token: token, + }, + method: "POST", + dataType: "json" + } as JQueryAjaxSettings) + .done(function (body: any) { + if (body && body.error) { + reject(new Error(body.error)); + return; + } + resolve(body); + }) + .fail(function (xhr: JQueryXHR, textStatus: string) { + reject(new Error(textStatus)); + }); + }); } \ No newline at end of file diff --git a/client/src/lib/secondfactor/U2FValidator.ts b/client/src/lib/secondfactor/U2FValidator.ts index 66c7751aa..0806e2418 100644 --- a/client/src/lib/secondfactor/U2FValidator.ts +++ b/client/src/lib/secondfactor/U2FValidator.ts @@ -4,59 +4,64 @@ import U2f = require("u2f"); import BluebirdPromise = require("bluebird"); import { SignMessage } from "../../../../shared/SignMessage"; import Endpoints = require("../../../../shared/api"); +import UserMessages = require("../../../../shared/UserMessages"); import { INotifier } from "../INotifier"; function finishU2fAuthentication(responseData: U2fApi.SignResponse, $: JQueryStatic): BluebirdPromise { - return new BluebirdPromise(function (resolve, reject) { - $.ajax({ - url: Endpoints.SECOND_FACTOR_U2F_SIGN_POST, - data: responseData, - method: "POST", - dataType: "json" - } as JQueryAjaxSettings) - .done(function (data) { - resolve(data); - }) - .fail(function (xhr: JQueryXHR, textStatus: string) { - reject(new Error(textStatus)); - }); - }); + return new BluebirdPromise(function (resolve, reject) { + $.ajax({ + url: Endpoints.SECOND_FACTOR_U2F_SIGN_POST, + data: responseData, + method: "POST", + dataType: "json" + } as JQueryAjaxSettings) + .done(function (body: any) { + if (body && body.error) { + reject(new Error(body.error)); + return; + } + resolve(body); + }) + .fail(function (xhr: JQueryXHR, textStatus: string) { + reject(new Error(textStatus)); + }); + }); } function startU2fAuthentication($: JQueryStatic, notifier: INotifier, u2fApi: typeof U2fApi): BluebirdPromise { - return new BluebirdPromise(function (resolve, reject) { - $.get(Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, {}, undefined, "json") - .done(function (signResponse: SignMessage) { - notifier.info("Please touch the token"); + return new BluebirdPromise(function (resolve, reject) { + $.get(Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, {}, undefined, "json") + .done(function (signResponse: SignMessage) { + notifier.info(UserMessages.PLEASE_TOUCH_TOKEN); - const signRequest: U2fApi.SignRequest = { - appId: signResponse.request.appId, - challenge: signResponse.request.challenge, - keyHandle: signResponse.keyHandle, // linked to the client session cookie - version: "U2F_V2" - }; + const signRequest: U2fApi.SignRequest = { + appId: signResponse.request.appId, + challenge: signResponse.request.challenge, + keyHandle: signResponse.keyHandle, // linked to the client session cookie + version: "U2F_V2" + }; - u2fApi.sign([signRequest], 60) - .then(function (signResponse: U2fApi.SignResponse) { - finishU2fAuthentication(signResponse, $) - .then(function (data) { - resolve(data); - }, function (err) { - notifier.error("Error when finish U2F transaction"); - reject(err); - }); - }) - .catch(function (err: Error) { - reject(err); - }); - }) - .fail(function (xhr: JQueryXHR, textStatus: string) { - reject(new Error(textStatus)); - }); - }); + u2fApi.sign([signRequest], 60) + .then(function (signResponse: U2fApi.SignResponse) { + finishU2fAuthentication(signResponse, $) + .then(function (data) { + resolve(data); + }, function (err) { + notifier.error(UserMessages.U2F_TRANSACTION_FINISH_FAILED); + reject(err); + }); + }) + .catch(function (err: Error) { + reject(err); + }); + }) + .fail(function (xhr: JQueryXHR, textStatus: string) { + reject(new Error(textStatus)); + }); + }); } export function validate($: JQueryStatic, notifier: INotifier, u2fApi: typeof U2fApi): BluebirdPromise { - return startU2fAuthentication($, notifier, u2fApi); + return startU2fAuthentication($, notifier, u2fApi); } diff --git a/client/src/lib/secondfactor/index.ts b/client/src/lib/secondfactor/index.ts index 52875254e..c829dea8b 100644 --- a/client/src/lib/secondfactor/index.ts +++ b/client/src/lib/secondfactor/index.ts @@ -9,7 +9,7 @@ import { Notifier } from "../Notifier"; import { QueryParametersRetriever } from "../QueryParametersRetriever"; import Endpoints = require("../../../../shared/api"); import ServerConstants = require("../../../../shared/constants"); - +import UserMessages = require("../../../../shared/UserMessages"); export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) { const notifierTotp = new Notifier(".notification-totp", $); @@ -20,7 +20,7 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) if (redirectUrl) window.location.href = redirectUrl; else - notifier.success("Authentication succeeded. You can now access your services."); + notifier.success(UserMessages.AUTHENTICATION_SUCCEEDED); } function onSecondFactorTotpSuccess(data: any) { @@ -28,7 +28,7 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) } function onSecondFactorTotpFailure(err: Error) { - notifierTotp.error("Problem with TOTP validation."); + notifierTotp.error(UserMessages.AUTHENTICATION_TOTP_FAILED); } function onU2fAuthenticationSuccess(data: any) { @@ -36,13 +36,11 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) } function onU2fAuthenticationFailure() { - notifierU2f.error("Problem with U2F validation. Did you register before authenticating?"); + notifierU2f.error(UserMessages.AUTHENTICATION_U2F_FAILED); } function onTOTPFormSubmitted(): boolean { const token = $(Constants.TOTP_TOKEN_SELECTOR).val(); - jslogger.debug("TOTP token is %s", token); - TOTPValidator.validate(token, $) .then(onSecondFactorTotpSuccess) .catch(onSecondFactorTotpFailure); @@ -50,7 +48,6 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) } function onU2FFormSubmitted(): boolean { - jslogger.debug("Start U2F authentication"); U2FValidator.validate($, notifierU2f, U2fApi) .then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure); return false; diff --git a/client/src/lib/u2f-register/u2f-register.ts b/client/src/lib/u2f-register/u2f-register.ts index f895bbf60..f66e3c80e 100644 --- a/client/src/lib/u2f-register/u2f-register.ts +++ b/client/src/lib/u2f-register/u2f-register.ts @@ -5,6 +5,7 @@ import u2fApi = require("u2f-api"); import jslogger = require("js-logger"); import { Notifier } from "../Notifier"; import Endpoints = require("../../../../shared/api"); +import UserMessages = require("../../../../shared/UserMessages"); export default function (window: Window, $: JQueryStatic) { const notifier = new Notifier(".notification", $); @@ -16,8 +17,12 @@ export default function (window: Window, $: JQueryStatic) { return new BluebirdPromise(function (resolve, reject) { $.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, registrationData, undefined, "json") - .done(function (data) { - resolve(data.redirection_url); + .done(function (body: any) { + if (body && body.error) { + reject(new Error(body.error)); + return; + } + resolve(body.redirection_url); }) .fail(function (xhr, status) { reject(); @@ -29,8 +34,6 @@ export default function (window: Window, $: JQueryStatic) { return new BluebirdPromise(function (resolve, reject) { $.get(Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, {}, undefined, "json") .done(function (registrationRequest: U2f.Request) { - jslogger.debug("registrationRequest = %s", JSON.stringify(registrationRequest)); - const registerRequest: u2fApi.RegisterRequest = registrationRequest; u2fApi.register([registerRequest], [], 120) .then(function (res: u2fApi.RegisterResponse) { @@ -47,7 +50,7 @@ export default function (window: Window, $: JQueryStatic) { } function onRegisterFailure(err: Error) { - notifier.error("Problem while registering your U2F device."); + notifier.error(UserMessages.REGISTRATION_U2F_FAILED); } $(document).ready(function () { diff --git a/client/test/firstfactor/FirstFactorValidator.test.ts b/client/test/firstfactor/FirstFactorValidator.test.ts index acae7c0d7..ac8353278 100644 --- a/client/test/firstfactor/FirstFactorValidator.test.ts +++ b/client/test/firstfactor/FirstFactorValidator.test.ts @@ -38,7 +38,7 @@ describe("test FirstFactorValidator", function () { describe("should fail first factor validation", () => { 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."); }); }); }); \ No newline at end of file diff --git a/client/test/secondfactor/TOTPValidator.test.ts b/client/test/secondfactor/TOTPValidator.test.ts index e03b0b9ad..5952eb611 100644 --- a/client/test/secondfactor/TOTPValidator.test.ts +++ b/client/test/secondfactor/TOTPValidator.test.ts @@ -5,33 +5,33 @@ import BluebirdPromise = require("bluebird"); import Assert = require("assert"); describe("test TOTPValidator", function () { - it("should initiate an identity check successfully", () => { - const postPromise = JQueryMock.JQueryDeferredMock(); - postPromise.done.yields(); - postPromise.done.returns(postPromise); + it("should initiate an identity check successfully", () => { + const postPromise = JQueryMock.JQueryDeferredMock(); + postPromise.done.yields(); + postPromise.done.returns(postPromise); - const jqueryMock = JQueryMock.JQueryMock(); - jqueryMock.jquery.ajax.returns(postPromise); + const jqueryMock = JQueryMock.JQueryMock(); + 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", () => { - const errorMessage = "Error while validating TOTP token"; + it("should fail validating TOTP token", () => { + const errorMessage = "Error while validating TOTP token"; - const postPromise = JQueryMock.JQueryDeferredMock(); - postPromise.fail.yields(undefined, errorMessage); - postPromise.done.returns(postPromise); + const postPromise = JQueryMock.JQueryDeferredMock(); + postPromise.fail.yields(undefined, errorMessage); + postPromise.done.returns(postPromise); - const jqueryMock = JQueryMock.JQueryMock(); - jqueryMock.jquery.ajax.returns(postPromise); + const jqueryMock = JQueryMock.JQueryMock(); + jqueryMock.jquery.ajax.returns(postPromise); - return TOTPValidator.validate("totp_token", jqueryMock.jquery as any) - .then(function () { - return BluebirdPromise.reject(new Error("Registration successfully finished while it should have not.")); - }, function (err: Error) { - Assert.equal(errorMessage, err.message); - return BluebirdPromise.resolve(); - }); - }); + return TOTPValidator.validate("totp_token", jqueryMock.jquery as any) + .then(function () { + return BluebirdPromise.reject(new Error("Registration successfully finished while it should have not.")); + }, function (err: Error) { + Assert.equal(errorMessage, err.message); + return BluebirdPromise.resolve(); + }); + }); }); \ No newline at end of file diff --git a/package.json b/package.json index c30331f86..c2fef38ec 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,8 @@ "@types/query-string": "^4.3.1", "@types/randomstring": "^1.1.5", "@types/redis": "^2.6.0", - "@types/request": "0.0.46", - "@types/request-promise": "^4.1.37", + "@types/request": "^2.0.5", + "@types/request-promise": "^4.1.38", "@types/selenium-webdriver": "^3.0.4", "@types/sinon": "^2.2.1", "@types/speakeasy": "^2.0.1", @@ -97,7 +97,7 @@ "power-assert": "^1.4.4", "proxyquire": "^1.8.0", "query-string": "^4.3.4", - "request": "^2.81.0", + "request": "^2.83.0", "request-promise": "^4.2.2", "selenium-webdriver": "^3.5.0", "should": "^11.1.1", diff --git a/server/src/lib/ErrorReplies.ts b/server/src/lib/ErrorReplies.ts index 6e40e8db7..cd6a4599e 100644 --- a/server/src/lib/ErrorReplies.ts +++ b/server/src/lib/ErrorReplies.ts @@ -3,12 +3,12 @@ import BluebirdPromise = require("bluebird"); import { IRequestLogger } from "./logging/IRequestLogger"; 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 { logger.error(req, "Reply with error %d: %s", code, err.message); logger.debug(req, "%s", err.stack); res.status(code); - res.send(); + res.send(body); }; } @@ -27,7 +27,7 @@ export function replyWithError403(req: express.Request, return replyWithError(req, res, 403, logger); } -export function replyWithError500(req: express.Request, - res: express.Response, logger: IRequestLogger) { - return replyWithError(req, res, 500, logger); +export function replyWithError200(req: express.Request, + res: express.Response, logger: IRequestLogger, message: string) { + return replyWithError(req, res, 200, logger, { error: message }); } \ No newline at end of file diff --git a/server/src/lib/FirstFactorValidator.ts b/server/src/lib/FirstFactorValidator.ts index 678dfde13..045d63f01 100644 --- a/server/src/lib/FirstFactorValidator.ts +++ b/server/src/lib/FirstFactorValidator.ts @@ -9,7 +9,9 @@ export function validate(req: express.Request): BluebirdPromise { return AuthenticationSession.get(req) .then(function (authSession: AuthenticationSession.AuthenticationSession) { if (!authSession.userid || !authSession.first_factor) - return BluebirdPromise.reject(new Exceptions.FirstFactorValidationError("First factor has not been validated yet.")); + return BluebirdPromise.reject( + new Exceptions.FirstFactorValidationError( + "First factor has not been validated yet.")); return BluebirdPromise.resolve(); }); diff --git a/server/src/lib/IdentityCheckMiddleware.ts b/server/src/lib/IdentityCheckMiddleware.ts index 0625c13df..07c83cb07 100644 --- a/server/src/lib/IdentityCheckMiddleware.ts +++ b/server/src/lib/IdentityCheckMiddleware.ts @@ -28,8 +28,10 @@ export interface IdentityValidable { preValidationInit(req: express.Request): BluebirdPromise; postValidationInit(req: express.Request): BluebirdPromise; - preValidationResponse(req: express.Request, res: express.Response): void; // Serves a page after identity check request - postValidationResponse(req: express.Request, res: express.Response): void; // Serves the page if identity validated + // Serves a page after identity check request + preValidationResponse(req: express.Request, res: express.Response): void; + // Serves the page if identity validated + postValidationResponse(req: express.Request, res: express.Response): void; mailSubject(): string; } @@ -50,7 +52,8 @@ function consumeToken(token: string, challenge: string, userDataStore: IUserData 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(post_validation_endpoint, get_finish_validation(handler)); } @@ -91,14 +94,13 @@ export function get_finish_validation(handler: IdentityValidable): express.Reque handler.postValidationResponse(req, res); return BluebirdPromise.resolve(); }) - .catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger)) - .catch(Exceptions.AccessDeniedError, ErrorReplies.replyWithError403(req, res, logger)) - .catch(ErrorReplies.replyWithError500(req, res, logger)); + .catch(ErrorReplies.replyWithError401(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 { const logger = ServerVariablesHandler.getLogger(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); 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); }) .then(function (token: string) { 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); 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); return BluebirdPromise.resolve(); }) - .catch(Exceptions.FirstFactorValidationError, 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)); + .catch(ErrorReplies.replyWithError401(req, res, logger)); }; } diff --git a/server/src/lib/routes/FirstFactorBlocker.ts b/server/src/lib/routes/FirstFactorBlocker.ts index 0cab96e9f..41b88240c 100644 --- a/server/src/lib/routes/FirstFactorBlocker.ts +++ b/server/src/lib/routes/FirstFactorBlocker.ts @@ -7,6 +7,7 @@ import ErrorReplies = require("../ErrorReplies"); import objectPath = require("object-path"); import { ServerVariablesHandler } from "../ServerVariablesHandler"; import AuthenticationSession = require("../AuthenticationSession"); +import UserMessages = require("../../../../shared/UserMessages"); type Handler = (req: express.Request, res: express.Response) => BluebirdPromise; @@ -21,6 +22,6 @@ export default function (callback: Handler): Handler { .then(function () { return callback(req, res); }) - .catch(Exceptions.FirstFactorValidationError, ErrorReplies.replyWithError401(req, res, logger)); + .catch(ErrorReplies.replyWithError401(req, res, logger)); }; } \ No newline at end of file diff --git a/server/src/lib/routes/firstfactor/post.ts b/server/src/lib/routes/firstfactor/post.ts index b7ba04327..086700bbf 100644 --- a/server/src/lib/routes/firstfactor/post.ts +++ b/server/src/lib/routes/firstfactor/post.ts @@ -12,6 +12,7 @@ import { ServerVariablesHandler } from "../../ServerVariablesHandler"; import AuthenticationSession = require("../../AuthenticationSession"); import Constants = require("../../../../../shared/constants"); import { DomainExtractor } from "../../utils/DomainExtractor"; +import UserMessages = require("../../../../../shared/UserMessages"); export default function (req: express.Request, res: express.Response): BluebirdPromise { 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); if (!username || !password) { - const err = new Error("No username or password"); - ErrorReplies.replyWithError401(req, res, logger)(err); - return BluebirdPromise.reject(err); + return BluebirdPromise.reject(new Error("No username or password.")); } const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app); @@ -93,12 +92,9 @@ export default function (req: express.Request, res: express.Response): BluebirdP } return BluebirdPromise.resolve(); }) - .catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(req, res, logger)) .catch(exceptions.LdapBindError, function (err: Error) { 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(exceptions.DomainAccessDenied, ErrorReplies.replyWithError401(req, res, logger)) - .catch(ErrorReplies.replyWithError500(req, res, logger)); + .catch(ErrorReplies.replyWithError200(req, res, logger, UserMessages.OPERATION_FAILED)); } diff --git a/server/src/lib/routes/password-reset/form/post.ts b/server/src/lib/routes/password-reset/form/post.ts index d84c4c863..15dba4be2 100644 --- a/server/src/lib/routes/password-reset/form/post.ts +++ b/server/src/lib/routes/password-reset/form/post.ts @@ -6,6 +6,7 @@ import exceptions = require("../../../Exceptions"); import { ServerVariablesHandler } from "../../../ServerVariablesHandler"; import AuthenticationSession = require("../../../AuthenticationSession"); import ErrorReplies = require("../../../ErrorReplies"); +import UserMessages = require("../../../../../../shared/UserMessages"); 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); if (authSession.identity_check.challenge != Constants.CHALLENGE) { - res.status(403); - res.send(); return BluebirdPromise.reject(new Error("Bad challenge.")); } return ldapPasswordUpdater.updatePassword(authSession.identity_check.userid, newPassword); @@ -37,5 +36,5 @@ export default function (req: express.Request, res: express.Response): BluebirdP res.send(); return BluebirdPromise.resolve(); }) - .catch(ErrorReplies.replyWithError500(req, res, logger)); + .catch(ErrorReplies.replyWithError200(req, res, logger, UserMessages.RESET_PASSWORD_FAILED)); } diff --git a/server/src/lib/routes/secondfactor/redirect.ts b/server/src/lib/routes/secondfactor/redirect.ts index 4761f2a94..4f258e8ab 100644 --- a/server/src/lib/routes/secondfactor/redirect.ts +++ b/server/src/lib/routes/secondfactor/redirect.ts @@ -18,5 +18,6 @@ export default function (req: express.Request, res: express.Response): BluebirdP }); return BluebirdPromise.resolve(); }) - .catch(ErrorReplies.replyWithError500(req, res, logger)); + .catch(ErrorReplies.replyWithError200(req, res, logger, + "Unexpected error.")); } \ No newline at end of file diff --git a/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts b/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts index a395bf132..d289773fe 100644 --- a/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts +++ b/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts @@ -11,6 +11,7 @@ import Endpoints = require("../../../../../../../shared/api"); import ErrorReplies = require("../../../../ErrorReplies"); import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import AuthenticationSession = require("../../../../AuthenticationSession"); +import UserMessages = require("../../../../../../../shared/UserMessages"); import FirstFactorValidator = require("../../../../FirstFactorValidator"); @@ -62,8 +63,6 @@ export default class RegistrationHandler implements IdentityValidable { const challenge = authSession.identity_check.challenge; if (challenge != Constants.CHALLENGE || !userid) { - res.status(403); - res.send(); 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 { diff --git a/server/src/lib/routes/secondfactor/totp/sign/post.ts b/server/src/lib/routes/secondfactor/totp/sign/post.ts index 134de6533..d9b5afd9f 100644 --- a/server/src/lib/routes/secondfactor/totp/sign/post.ts +++ b/server/src/lib/routes/secondfactor/totp/sign/post.ts @@ -10,6 +10,7 @@ import redirect from "../../redirect"; import ErrorReplies = require("../../../../ErrorReplies"); import { ServerVariablesHandler } from "./../../../../ServerVariablesHandler"; import AuthenticationSession = require("../../../../AuthenticationSession"); +import UserMessages = require("../../../../../../../shared/UserMessages"); const UNAUTHORIZED_MESSAGE = "Unauthorized access"; @@ -25,7 +26,7 @@ export function handler(req: express.Request, res: express.Response): BluebirdPr return AuthenticationSession.get(req) .then(function (_authSession: AuthenticationSession.AuthenticationSession) { 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); }) .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); }) .then(function () { - logger.debug(req, "TOTP validation succeeded"); + logger.debug(req, "TOTP validation succeeded."); authSession.second_factor = true; redirect(req, res); return BluebirdPromise.resolve(); }) - .catch(exceptions.InvalidTOTPError, ErrorReplies.replyWithError401(req, res, logger)) - .catch(ErrorReplies.replyWithError500(req, res, logger)); + .catch(ErrorReplies.replyWithError200(req, res, logger, + UserMessages.OPERATION_FAILED)); } diff --git a/server/src/lib/routes/secondfactor/u2f/register/post.ts b/server/src/lib/routes/secondfactor/u2f/register/post.ts index 1a64ad64d..6d7e9d4b1 100644 --- a/server/src/lib/routes/secondfactor/u2f/register/post.ts +++ b/server/src/lib/routes/secondfactor/u2f/register/post.ts @@ -12,6 +12,7 @@ import redirect from "../../redirect"; import ErrorReplies = require("../../../../ErrorReplies"); import { ServerVariablesHandler } from "../../../../ServerVariablesHandler"; import AuthenticationSession = require("../../../../AuthenticationSession"); +import UserMessages = require("../../../../../../../shared/UserMessages"); export default FirstFactorBlocker(handler); @@ -31,15 +32,11 @@ function handler(req: express.Request, res: express.Response): BluebirdPromise 403 + .catch(exceptions.DomainAccessDenied, ErrorReplies + .replyWithError403(req, res, logger)) + // The user is not yet authenticated -> 401 .catch(ErrorReplies.replyWithError401(req, res, logger)); } diff --git a/server/src/views/layout/layout.pug b/server/src/views/layout/layout.pug index 3f95cb9bc..24d96080a 100644 --- a/server/src/views/layout/layout.pug +++ b/server/src/views/layout/layout.pug @@ -5,7 +5,7 @@ html title Authelia - 2FA 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="stylesheet", type="text/css", href="/css/authelia.min.css")/ + link(rel="stylesheet", type="text/css", href="/css/authelia.css")/ if redirection_url body diff --git a/server/test/IdentityCheckMiddleware.test.ts b/server/test/IdentityCheckMiddleware.test.ts index 37388c3e6..62416179b 100644 --- a/server/test/IdentityCheckMiddleware.test.ts +++ b/server/test/IdentityCheckMiddleware.test.ts @@ -5,7 +5,7 @@ import AuthenticationSession = require("../src/lib/AuthenticationSession"); import { UserDataStore } from "../src/lib/storage/UserDataStore"; import exceptions = require("../src/lib/Exceptions"); -import assert = require("assert"); +import Assert = require("assert"); import Promise = require("bluebird"); import express = require("express"); import BluebirdPromise = require("bluebird"); @@ -69,11 +69,11 @@ describe("test identity check process", function () { return callback(req as any, res as any, undefined) .then(function () { return BluebirdPromise.reject("Should fail"); }) .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" }; 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) .then(function () { return BluebirdPromise.reject("Should fail"); }) .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 identity = { email: "abc@example.com" }; @@ -96,7 +96,7 @@ describe("test identity check process", function () { return callback(req as any, res as any, undefined) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .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(); }); }); @@ -111,23 +111,23 @@ describe("test identity check process", function () { return callback(req as any, res as any, undefined) .then(function () { - assert(notifier.notify.calledOnce); - assert(mocks.userDataStore.produceIdentityValidationTokenStub.calledOnce); - assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[0], "user"); - assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[3], 240000); + Assert(notifier.notify.calledOnce); + Assert(mocks.userDataStore.produceIdentityValidationTokenStub.calledOnce); + Assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[0], "user"); + Assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[3], 240000); }); }); } 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); return callback(req as any, res as any, undefined) .then(function () { return BluebirdPromise.reject("Should fail"); }) .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); }); - 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"; 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) .then(function () { return BluebirdPromise.reject("Should fail"); }) .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"); }) .catch(function () { - assert.equal(authSession.identity_check.userid, "user"); + Assert.equal(authSession.identity_check.userid, "user"); return BluebirdPromise.resolve(); }); }); diff --git a/server/test/routes/firstfactor/post.test.ts b/server/test/routes/firstfactor/post.test.ts index 17b3548c9..89c1d72e8 100644 --- a/server/test/routes/firstfactor/post.test.ts +++ b/server/test/routes/firstfactor/post.test.ts @@ -1,7 +1,7 @@ import sinon = require("sinon"); import BluebirdPromise = require("bluebird"); -import assert = require("assert"); +import Assert = require("assert"); import winston = require("winston"); 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); }) .then(function () { - assert.equal("username", authSession.userid); - assert(res.send.calledOnce); + Assert.equal("username", authSession.userid); + 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); }) .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") .returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials"))); return FirstFactorPost.default(req as any, res as any) .then(function () { - assert.equal(401, res.status.getCall(0).args[0]); - assert.equal(regulator.mark.getCall(0).args[0], "username"); + Assert.equal(res.status.getCall(0).args[0], 200); + 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..."); regulator.regulate.returns(BluebirdPromise.reject(err)); return FirstFactorPost.default(req as any, res as any) .then(function () { - assert.equal(403, res.status.getCall(0).args[0]); - assert.equal(1, res.send.callCount); + Assert.equal(res.status.getCall(0).args[0], 200); + Assert.deepEqual(res.send.getCall(0).args[0], { + error: "Operation failed." + }); }); }); }); diff --git a/server/test/routes/password-reset/post.test.ts b/server/test/routes/password-reset/post.test.ts index 86fda49cd..436715ad0 100644 --- a/server/test/routes/password-reset/post.test.ts +++ b/server/test/routes/password-reset/post.test.ts @@ -6,7 +6,7 @@ import { ServerVariablesHandler } from "../../../src/lib/ServerVariablesHandler" import { UserDataStore } from "../../../src/lib/storage/UserDataStore"; import Sinon = require("sinon"); import winston = require("winston"); -import assert = require("assert"); +import Assert = require("assert"); import BluebirdPromise = require("bluebird"); import ExpressMock = require("../../mocks/express"); @@ -85,9 +85,9 @@ describe("test reset password route", function () { .then(function () { return AuthenticationSession.get(req as any); }).then(function (_authSession: AuthenticationSession.AuthenticationSession) { - assert.equal(res.status.getCall(0).args[0], 204); - assert.equal(_authSession.first_factor, false); - assert.equal(_authSession.second_factor, false); + Assert.equal(res.status.getCall(0).args[0], 204); + Assert.equal(_authSession.first_factor, false); + Assert.equal(_authSession.second_factor, false); return BluebirdPromise.resolve(); }); }); @@ -102,7 +102,10 @@ describe("test reset password route", function () { return PasswordResetFormPost.default(req as any, res as any); }) .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); }).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(); }); }); diff --git a/server/test/routes/secondfactor/u2f/register/post.test.ts b/server/test/routes/secondfactor/u2f/register/post.test.ts index 601e7f099..86d0cd01d 100644 --- a/server/test/routes/secondfactor/u2f/register/post.test.ts +++ b/server/test/routes/secondfactor/u2f/register/post.test.ts @@ -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 u2f_mock = U2FMock.U2FMock(); 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")); }) .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(); }); }); - 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 u2f_mock = U2FMock.U2FMock(); 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")); }) .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(); }); }); - 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 u2f_mock = U2FMock.U2FMock(); 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")); }) .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(); }); }); - 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) .then(function (authSession) { authSession.identity_check = undefined; @@ -152,7 +161,10 @@ describe("test u2f routes: register", function () { }) .then(function () { return BluebirdPromise.reject(new Error("It should fail")); }) .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(); }); }); diff --git a/server/test/routes/secondfactor/u2f/register_request/get.test.ts b/server/test/routes/secondfactor/u2f/register_request/get.test.ts index 7465beb1e..168dca30d 100644 --- a/server/test/routes/secondfactor/u2f/register_request/get.test.ts +++ b/server/test/routes/secondfactor/u2f/register_request/get.test.ts @@ -1,7 +1,7 @@ import sinon = require("sinon"); import BluebirdPromise = require("bluebird"); -import assert = require("assert"); +import Assert = require("assert"); import U2FRegisterRequestGet = require("../../../../../src/lib/routes/secondfactor/u2f/register_request/get"); import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession"); import { ServerVariablesHandler } from "../../../../../src/lib/ServerVariablesHandler"; @@ -67,28 +67,31 @@ describe("test u2f routes: register_request", function () { mocks.u2f = u2f_mock; return U2FRegisterRequestGet.default(req as any, res as any) .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) { - res.send = sinon.spy(function (data: any) { - assert.equal(500, res.status.getCall(0).args[0]); - done(); - }); + it("should return internal error on registration request", function () { + res.send = sinon.spy(); const user_key_container = {}; const u2f_mock = U2FMock.U2FMock(); u2f_mock.request.returns(BluebirdPromise.reject("Internal error")); mocks.u2f = u2f_mock; - U2FRegisterRequestGet.default(req as any, res as any); + 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 () { authSession.identity_check = undefined; return U2FRegisterRequestGet.default(req as any, res as any) .then(function () { - assert.equal(403, res.status.getCall(0).args[0]); + Assert.equal(403, res.status.getCall(0).args[0]); }); }); }); diff --git a/server/test/routes/secondfactor/u2f/sign/post.test.ts b/server/test/routes/secondfactor/u2f/sign/post.test.ts index 80d7394ea..aef978bbc 100644 --- a/server/test/routes/secondfactor/u2f/sign/post.test.ts +++ b/server/test/routes/secondfactor/u2f/sign/post.test.ts @@ -97,7 +97,9 @@ describe("test u2f routes: sign", function () { mocks.u2f = u2f_mock; return U2FSignPost.default(req as any, res as any) .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." }); }); }); }); diff --git a/server/test/server/PrivatePages.ts b/server/test/server/PrivatePages.ts index dee301190..35cf758c6 100644 --- a/server/test/server/PrivatePages.ts +++ b/server/test/server/PrivatePages.ts @@ -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 () { - function should_post_and_reply_with(url: string, status_code: number): BluebirdPromise { - 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 { - 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 { - 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 { - 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 () { diff --git a/shared/UserMessages.ts b/shared/UserMessages.ts new file mode 100644 index 000000000..40640b1d6 --- /dev/null +++ b/shared/UserMessages.ts @@ -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."; \ No newline at end of file diff --git a/test/features/authentication.feature b/test/features/authentication.feature index 69ea88843..11a600fcb 100644 --- a/test/features/authentication.feature +++ b/test/features/authentication.feature @@ -12,7 +12,7 @@ Feature: Authentication scenarii When I set field "username" to "john" And I set field "password" to "bad-password" 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 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 use "BADTOKEN" as TOTP token 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 When I visit "https://auth.test.local:8080/logout?redirect=https://home.test.local:8080/" diff --git a/test/features/regulation.feature b/test/features/regulation.feature index f361c2dd5..69e5a69ba 100644 --- a/test/features/regulation.feature +++ b/test/features/regulation.feature @@ -7,16 +7,16 @@ Feature: Authelia regulates authentication to avoid brute force And I set field "username" to "blackhat" And I set field "password" to "bad-password" 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 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 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" 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 @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 "password" to "bad-password" 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 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 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 And I set field "password" to "password" And I click on "Sign in" diff --git a/test/features/reset-password.feature b/test/features/reset-password.feature index c386f28c7..ba893f655 100644 --- a/test/features/reset-password.feature +++ b/test/features/reset-password.feature @@ -11,6 +11,12 @@ Feature: User is able to reset his 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." + 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 Given I'm on https://auth.test.local:8080/password-reset/request And I set field "username" to "james" diff --git a/test/features/restrictions.feature b/test/features/restrictions.feature index c2cf9889d..e331482b9 100644 --- a/test/features/restrictions.feature +++ b/test/features/restrictions.feature @@ -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/verify | 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/finish | 403 | - | https://auth.test.local:8080/password-reset/identity/start | 403 | - | https://auth.test.local:8080/password-reset/identity/finish | 403 | \ No newline at end of file + | https://auth.test.local:8080/secondfactor/totp/identity/finish | 401 | + | https://auth.test.local:8080/password-reset/identity/start | 401 | + | https://auth.test.local:8080/password-reset/identity/finish | 401 | \ No newline at end of file