mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Notifications to users do not use notifyjs anymore. They are more common and located in the form areas to improve visibility on mobile devices.
This commit is contained in:
parent
0b8ac83566
commit
50636587a8
|
@ -162,7 +162,7 @@ module.exports = function (grunt) {
|
||||||
grunt.registerTask('docker-restart', ['run:docker-restart']);
|
grunt.registerTask('docker-restart', ['run:docker-restart']);
|
||||||
|
|
||||||
grunt.registerTask('unit-tests', ['run:unit-tests']);
|
grunt.registerTask('unit-tests', ['run:unit-tests']);
|
||||||
grunt.registerTask('integration-tests', ['run:unit-tests']);
|
grunt.registerTask('integration-tests', ['run:integration-tests']);
|
||||||
|
|
||||||
grunt.registerTask('test', ['unit-tests']);
|
grunt.registerTask('test', ['unit-tests']);
|
||||||
};
|
};
|
||||||
|
|
|
@ -88,7 +88,6 @@
|
||||||
"jsdom": "^11.0.0",
|
"jsdom": "^11.0.0",
|
||||||
"mocha": "^3.4.2",
|
"mocha": "^3.4.2",
|
||||||
"mockdate": "^2.0.1",
|
"mockdate": "^2.0.1",
|
||||||
"notifyjs-browser": "^0.4.2",
|
|
||||||
"nyc": "^10.3.2",
|
"nyc": "^10.3.2",
|
||||||
"power-assert": "^1.4.4",
|
"power-assert": "^1.4.4",
|
||||||
"proxyquire": "^1.8.0",
|
"proxyquire": "^1.8.0",
|
||||||
|
|
|
@ -19,4 +19,43 @@ body {
|
||||||
.poweredby {
|
.poweredby {
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
color: #6b6b6b;
|
color: #6b6b6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* notifications */
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification img {
|
||||||
|
width: 24px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification i,
|
||||||
|
.notification span {
|
||||||
|
display:table-cell;
|
||||||
|
vertical-align:middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
border: 1px solid #9cb1ff;
|
||||||
|
background-color: rgb(192, 220, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
border: 1px solid #65ec7c;
|
||||||
|
background-color: rgb(163, 255, 157);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
border: 1px solid #ffa3a3;
|
||||||
|
background-color: rgb(255, 175, 175);
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
border: 1px solid #ffd743;
|
||||||
|
background-color: rgb(255, 230, 143);
|
||||||
}
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
|
|
||||||
import FirstFactorValidator = require("./firstfactor/FirstFactorValidator");
|
import FirstFactorValidator = require("./lib/firstfactor/FirstFactorValidator");
|
||||||
|
|
||||||
import FirstFactor from "./firstfactor/index";
|
import FirstFactor from "./lib/firstfactor/index";
|
||||||
import SecondFactor from "./secondfactor/index";
|
import SecondFactor from "./lib/secondfactor/index";
|
||||||
import TOTPRegister from "./totp-register/totp-register";
|
import TOTPRegister from "./lib/totp-register/totp-register";
|
||||||
import U2fRegister from "./u2f-register/u2f-register";
|
import U2fRegister from "./lib/u2f-register/u2f-register";
|
||||||
import ResetPasswordRequest from "./reset-password/reset-password-request";
|
import ResetPasswordRequest from "./lib/reset-password/reset-password-request";
|
||||||
import ResetPasswordForm from "./reset-password/reset-password-form";
|
import ResetPasswordForm from "./lib/reset-password/reset-password-form";
|
||||||
import jslogger = require("js-logger");
|
import jslogger = require("js-logger");
|
||||||
import jQuery = require("jquery");
|
import jQuery = require("jquery");
|
||||||
import u2fApi = require("u2f-api");
|
import u2fApi = require("u2f-api");
|
||||||
|
@ -14,8 +14,6 @@ import u2fApi = require("u2f-api");
|
||||||
jslogger.useDefaults();
|
jslogger.useDefaults();
|
||||||
jslogger.setLevel(jslogger.INFO);
|
jslogger.setLevel(jslogger.INFO);
|
||||||
|
|
||||||
require("notifyjs-browser")(jQuery);
|
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
firstfactor: function () {
|
firstfactor: function () {
|
||||||
FirstFactor(window, jQuery, FirstFactorValidator, jslogger);
|
FirstFactor(window, jQuery, FirstFactorValidator, jslogger);
|
||||||
|
|
14
src/client/lib/INotifier.ts
Normal file
14
src/client/lib/INotifier.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
declare type Handler = () => void;
|
||||||
|
|
||||||
|
export interface Handlers {
|
||||||
|
onFadedIn: Handler;
|
||||||
|
onFadedOut: Handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INotifier {
|
||||||
|
success(msg: string, handlers?: Handlers): void;
|
||||||
|
error(msg: string, handlers?: Handlers): void;
|
||||||
|
warning(msg: string, handlers?: Handlers): void;
|
||||||
|
info(msg: string, handlers?: Handlers): void;
|
||||||
|
}
|
45
src/client/lib/Notifier.ts
Normal file
45
src/client/lib/Notifier.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
|
||||||
|
import util = require("util");
|
||||||
|
import { INotifier, Handlers } from "./INotifier";
|
||||||
|
|
||||||
|
export class Notifier implements INotifier {
|
||||||
|
private element: JQuery;
|
||||||
|
|
||||||
|
constructor(selector: string, $: JQueryStatic) {
|
||||||
|
this.element = $(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
private displayAndFadeout(msg: string, statusType: string, handlers?: Handlers): void {
|
||||||
|
const that = this;
|
||||||
|
const FADE_TIME = 500;
|
||||||
|
const html = util.format('<i><img src="/img/notifications/%s.png" alt="status %s"/></i>\
|
||||||
|
<span>%s</span>', statusType, statusType, msg);
|
||||||
|
this.element.html(html);
|
||||||
|
this.element.addClass(statusType);
|
||||||
|
this.element.fadeIn(FADE_TIME, function() {
|
||||||
|
handlers.onFadedIn();
|
||||||
|
})
|
||||||
|
.delay(4000)
|
||||||
|
.fadeOut(FADE_TIME, function() {
|
||||||
|
that.element.removeClass(statusType);
|
||||||
|
handlers.onFadedOut();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
success(msg: string, handlers?: Handlers) {
|
||||||
|
this.displayAndFadeout(msg, "success", handlers);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(msg: string, handlers?: Handlers) {
|
||||||
|
this.displayAndFadeout(msg, "error", handlers);
|
||||||
|
}
|
||||||
|
|
||||||
|
warning(msg: string, handlers?: Handlers) {
|
||||||
|
this.displayAndFadeout(msg, "warning", handlers);
|
||||||
|
}
|
||||||
|
|
||||||
|
info(msg: string, handlers?: Handlers) {
|
||||||
|
this.displayAndFadeout(msg, "info", handlers);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
|
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import Endpoints = require("../../server/endpoints");
|
import Endpoints = require("../../../server/endpoints");
|
||||||
|
|
||||||
export function validate(username: string, password: string, $: JQueryStatic): BluebirdPromise < void> {
|
export function validate(username: string, password: string, $: JQueryStatic): BluebirdPromise<void> {
|
||||||
return new BluebirdPromise<void>(function (resolve, reject) {
|
return new BluebirdPromise<void>(function (resolve, reject) {
|
||||||
$.post(Endpoints.FIRST_FACTOR_POST, {
|
$.post(Endpoints.FIRST_FACTOR_POST, {
|
||||||
username: username,
|
username: username,
|
||||||
|
@ -12,9 +12,7 @@ export function validate(username: string, password: string, $: JQueryStatic): B
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.fail(function (xhr: JQueryXHR, textStatus: string) {
|
.fail(function (xhr: JQueryXHR, textStatus: string) {
|
||||||
if (xhr.status == 401)
|
reject(new Error("Authetication failed. Please check your credentials."));
|
||||||
reject(new Error("Authetication failed. Please check your credentials."));
|
|
||||||
reject(new Error(textStatus));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -1,10 +1,15 @@
|
||||||
import FirstFactorValidator = require("./FirstFactorValidator");
|
import FirstFactorValidator = require("./FirstFactorValidator");
|
||||||
import JSLogger = require("js-logger");
|
import JSLogger = require("js-logger");
|
||||||
import UISelectors = require("./UISelectors");
|
import UISelectors = require("./UISelectors");
|
||||||
|
import { Notifier } from "../Notifier";
|
||||||
|
|
||||||
import Endpoints = require("../../server/endpoints");
|
import Endpoints = require("../../../server/endpoints");
|
||||||
|
|
||||||
|
export default function (window: Window, $: JQueryStatic,
|
||||||
|
firstFactorValidator: typeof FirstFactorValidator, jslogger: typeof JSLogger) {
|
||||||
|
|
||||||
|
const notifier = new Notifier(".notification", $);
|
||||||
|
|
||||||
export default function (window: Window, $: JQueryStatic, firstFactorValidator: typeof FirstFactorValidator, jslogger: typeof JSLogger) {
|
|
||||||
function onFormSubmitted() {
|
function onFormSubmitted() {
|
||||||
const username: string = $(UISelectors.USERNAME_FIELD_ID).val();
|
const username: string = $(UISelectors.USERNAME_FIELD_ID).val();
|
||||||
const password: string = $(UISelectors.PASSWORD_FIELD_ID).val();
|
const password: string = $(UISelectors.PASSWORD_FIELD_ID).val();
|
||||||
|
@ -27,7 +32,7 @@ export default function (window: Window, $: JQueryStatic, firstFactorValidator:
|
||||||
jslogger.debug("First factor failed.");
|
jslogger.debug("First factor failed.");
|
||||||
|
|
||||||
$(UISelectors.PASSWORD_FIELD_ID).val("");
|
$(UISelectors.PASSWORD_FIELD_ID).val("");
|
||||||
$.notify("Error during authentication: " + err.message, "error");
|
notifier.error("Authentication failed. Please double check your credentials.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
|
||||||
import Endpoints = require("../../server/endpoints");
|
import Endpoints = require("../../../server/endpoints");
|
||||||
import Constants = require("./constants");
|
import Constants = require("./constants");
|
||||||
|
import { Notifier } from "../Notifier";
|
||||||
|
|
||||||
export default function (window: Window, $: JQueryStatic) {
|
export default function (window: Window, $: JQueryStatic) {
|
||||||
|
const notifier = new Notifier(".notification", $);
|
||||||
|
|
||||||
function modifyPassword(newPassword: string) {
|
function modifyPassword(newPassword: string) {
|
||||||
return new BluebirdPromise(function (resolve, reject) {
|
return new BluebirdPromise(function (resolve, reject) {
|
||||||
$.post(Endpoints.RESET_PASSWORD_FORM_POST, {
|
$.post(Endpoints.RESET_PASSWORD_FORM_POST, {
|
||||||
|
@ -23,22 +26,22 @@ export default function (window: Window, $: JQueryStatic) {
|
||||||
const password2 = $("#password2").val();
|
const password2 = $("#password2").val();
|
||||||
|
|
||||||
if (!password1 || !password2) {
|
if (!password1 || !password2) {
|
||||||
$.notify("You must enter your new password twice.", "warn");
|
notifier.warning("You must enter your new password twice.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password1 != password2) {
|
if (password1 != password2) {
|
||||||
$.notify("The passwords are different", "warn");
|
notifier.warning("The passwords are different.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
modifyPassword(password1)
|
modifyPassword(password1)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
$.notify("Your password has been changed. Please login again", "success");
|
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 () {
|
||||||
$.notify("An error occurred during password change.", "warn");
|
notifier.warning("An error occurred during password reset. Your password has not been changed.");
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
|
@ -1,11 +1,14 @@
|
||||||
|
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
|
|
||||||
import Endpoints = require("../../server/endpoints");
|
import Endpoints = require("../../../server/endpoints");
|
||||||
import Constants = require("./constants");
|
import Constants = require("./constants");
|
||||||
import jslogger = require("js-logger");
|
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) {
|
function requestPasswordReset(username: string) {
|
||||||
return new BluebirdPromise(function (resolve, reject) {
|
return new BluebirdPromise(function (resolve, reject) {
|
||||||
$.get(Endpoints.RESET_PASSWORD_IDENTITY_START_GET, {
|
$.get(Endpoints.RESET_PASSWORD_IDENTITY_START_GET, {
|
||||||
|
@ -24,19 +27,19 @@ export default function(window: Window, $: JQueryStatic) {
|
||||||
const username = $("#username").val();
|
const username = $("#username").val();
|
||||||
|
|
||||||
if (!username) {
|
if (!username) {
|
||||||
$.notify("You must provide your username to reset your password.", "warn");
|
notifier.warning("You must provide your username to reset your password.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestPasswordReset(username)
|
requestPasswordReset(username)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
$.notify("An email has been sent. Click on the link to change your password.", "success");
|
notifier.success("An email has been sent to you. Follow the link to change your password.");
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
window.location.replace(Endpoints.FIRST_FACTOR_GET);
|
window.location.replace(Endpoints.FIRST_FACTOR_GET);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
})
|
})
|
||||||
.error(function () {
|
.error(function () {
|
||||||
$.notify("Are you sure this is your username?", "warn");
|
notifier.warning("Are you sure this is your username?");
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import Endpoints = require("../../server/endpoints");
|
import Endpoints = require("../../../server/endpoints");
|
||||||
|
|
||||||
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) {
|
|
@ -2,8 +2,9 @@
|
||||||
import U2fApi = require("u2f-api");
|
import U2fApi = require("u2f-api");
|
||||||
import U2f = require("u2f");
|
import U2f = require("u2f");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import { SignMessage } from "../../server/lib/routes/secondfactor/u2f/sign_request/SignMessage";
|
import { SignMessage } from "../../../server/lib/routes/secondfactor/u2f/sign_request/SignMessage";
|
||||||
import Endpoints = require("../../server/endpoints");
|
import Endpoints = require("../../../server/endpoints");
|
||||||
|
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) {
|
||||||
|
@ -22,11 +23,11 @@ function finishU2fAuthentication(responseData: U2fApi.SignResponse, $: JQuerySta
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function startU2fAuthentication($: JQueryStatic, 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) {
|
||||||
$.notify("Please touch the token", "info");
|
notifier.info("Please touch the token");
|
||||||
|
|
||||||
const signRequest: U2fApi.SignRequest = {
|
const signRequest: U2fApi.SignRequest = {
|
||||||
appId: signResponse.request.appId,
|
appId: signResponse.request.appId,
|
||||||
|
@ -41,7 +42,7 @@ function startU2fAuthentication($: JQueryStatic, u2fApi: typeof U2fApi): Bluebir
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
resolve(data);
|
resolve(data);
|
||||||
}, function (err) {
|
}, function (err) {
|
||||||
$.notify("Error when finish U2F transaction", "error");
|
notifier.error("Error when finish U2F transaction");
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -56,6 +57,6 @@ function startU2fAuthentication($: JQueryStatic, u2fApi: typeof U2fApi): Bluebir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function validate($: JQueryStatic, u2fApi: typeof U2fApi): BluebirdPromise<void> {
|
export function validate($: JQueryStatic, notifier: INotifier, u2fApi: typeof U2fApi): BluebirdPromise<void> {
|
||||||
return startU2fAuthentication($, u2fApi);
|
return startU2fAuthentication($, notifier, u2fApi);
|
||||||
}
|
}
|
|
@ -4,24 +4,25 @@ import jslogger = require("js-logger");
|
||||||
|
|
||||||
import TOTPValidator = require("./TOTPValidator");
|
import TOTPValidator = require("./TOTPValidator");
|
||||||
import U2FValidator = require("./U2FValidator");
|
import U2FValidator = require("./U2FValidator");
|
||||||
|
import Endpoints = require("../../../server/endpoints");
|
||||||
import Endpoints = require("../../server/endpoints");
|
|
||||||
|
|
||||||
import Constants = require("./constants");
|
import Constants = require("./constants");
|
||||||
|
import { Notifier } from "../Notifier";
|
||||||
|
|
||||||
|
|
||||||
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 notifierU2f = new Notifier(".notification-u2f", $);
|
||||||
|
|
||||||
function onAuthenticationSuccess(data: any) {
|
function onAuthenticationSuccess(data: any) {
|
||||||
window.location.href = data.redirection_url;
|
window.location.href = data.redirection_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function onSecondFactorTotpSuccess(data: any) {
|
function onSecondFactorTotpSuccess(data: any) {
|
||||||
onAuthenticationSuccess(data);
|
onAuthenticationSuccess(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSecondFactorTotpFailure(err: Error) {
|
function onSecondFactorTotpFailure(err: Error) {
|
||||||
$.notify("Error while validating TOTP token. Cause: " + err.message, "error");
|
notifierTotp.error("Problem with TOTP validation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
function onU2fAuthenticationSuccess(data: any) {
|
function onU2fAuthenticationSuccess(data: any) {
|
||||||
|
@ -29,10 +30,9 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onU2fAuthenticationFailure() {
|
function onU2fAuthenticationFailure() {
|
||||||
$.notify("Problem with U2F authentication. Did you register before authenticating?", "warn");
|
notifierU2f.error("Problem with U2F validation. Did you register before authenticating?");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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);
|
jslogger.debug("TOTP token is %s", token);
|
||||||
|
@ -45,7 +45,7 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi)
|
||||||
|
|
||||||
function onU2FFormSubmitted(): boolean {
|
function onU2FFormSubmitted(): boolean {
|
||||||
jslogger.debug("Start U2F authentication");
|
jslogger.debug("Start U2F authentication");
|
||||||
U2FValidator.validate($, U2fApi)
|
U2FValidator.validate($, notifierU2f, U2fApi)
|
||||||
.then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure);
|
.then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
62
src/client/lib/u2f-register/u2f-register.ts
Normal file
62
src/client/lib/u2f-register/u2f-register.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
|
||||||
|
import BluebirdPromise = require("bluebird");
|
||||||
|
import U2f = require("u2f");
|
||||||
|
import u2fApi = require("u2f-api");
|
||||||
|
import Endpoints = require("../../../server/endpoints");
|
||||||
|
import jslogger = require("js-logger");
|
||||||
|
import { Notifier } from "../Notifier";
|
||||||
|
|
||||||
|
export default function (window: Window, $: JQueryStatic) {
|
||||||
|
const notifier = new Notifier(".notification", $);
|
||||||
|
|
||||||
|
function checkRegistration(regResponse: u2fApi.RegisterResponse): BluebirdPromise<string> {
|
||||||
|
const registrationData: U2f.RegistrationData = regResponse;
|
||||||
|
|
||||||
|
jslogger.debug("registrationResponse = %s", JSON.stringify(registrationData));
|
||||||
|
|
||||||
|
return new BluebirdPromise<string>(function (resolve, reject) {
|
||||||
|
$.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, registrationData, undefined, "json")
|
||||||
|
.done(function (data) {
|
||||||
|
resolve(data.redirection_url);
|
||||||
|
})
|
||||||
|
.fail(function (xhr, status) {
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestRegistration(): BluebirdPromise<string> {
|
||||||
|
return new BluebirdPromise<string>(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) {
|
||||||
|
return checkRegistration(res);
|
||||||
|
})
|
||||||
|
.then(function (redirectionUrl: string) {
|
||||||
|
resolve(redirectionUrl);
|
||||||
|
})
|
||||||
|
.catch(function (err: Error) {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRegisterFailure(err: Error) {
|
||||||
|
notifier.error("Problem while registering your U2F device.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
requestRegistration()
|
||||||
|
.then(function (redirectionUrl: string) {
|
||||||
|
document.location.href = redirectionUrl;
|
||||||
|
})
|
||||||
|
.error(function (err) {
|
||||||
|
onRegisterFailure(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,53 +0,0 @@
|
||||||
|
|
||||||
import BluebirdPromise = require("bluebird");
|
|
||||||
import U2f = require("u2f");
|
|
||||||
import u2fApi = require("u2f-api");
|
|
||||||
|
|
||||||
import Endpoints = require("../../server/endpoints");
|
|
||||||
import jslogger = require("js-logger");
|
|
||||||
|
|
||||||
export default function(window: Window, $: JQueryStatic) {
|
|
||||||
|
|
||||||
function checkRegistration(regResponse: u2fApi.RegisterResponse, fn: (err: Error) => void) {
|
|
||||||
const registrationData: U2f.RegistrationData = regResponse;
|
|
||||||
|
|
||||||
jslogger.debug("registrationResponse = %s", JSON.stringify(registrationData));
|
|
||||||
|
|
||||||
$.post(Endpoints.SECOND_FACTOR_U2F_REGISTER_POST, registrationData, undefined, "json")
|
|
||||||
.done(function (data) {
|
|
||||||
document.location.href = data.redirection_url;
|
|
||||||
})
|
|
||||||
.fail(function (xhr, status) {
|
|
||||||
$.notify("Error when finish U2F transaction" + status);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestRegistration(fn: (err: Error) => void) {
|
|
||||||
$.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) {
|
|
||||||
checkRegistration(res, fn);
|
|
||||||
})
|
|
||||||
.catch(function (err: Error) {
|
|
||||||
fn(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRegisterFailure(err: Error) {
|
|
||||||
$.notify("Problem authenticating with U2F.", "error");
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
requestRegistration(function (err: Error) {
|
|
||||||
if (err) {
|
|
||||||
onRegisterFailure(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ block form-header
|
||||||
<img class="header-img" src="/img/user.png" alt="">
|
<img class="header-img" src="/img/user.png" alt="">
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
<div class="notification"></div>
|
||||||
<form class="form-signin">
|
<form class="form-signin">
|
||||||
<div class="form-inputs">
|
<div class="form-inputs">
|
||||||
<input type="text" class="form-control" id="username" placeholder="Username" required autofocus>
|
<input type="text" class="form-control" id="username" placeholder="Username" required autofocus>
|
||||||
|
|
|
@ -9,6 +9,7 @@ block form-header
|
||||||
<p>Set your new password and confirm it.</p>
|
<p>Set your new password and confirm it.</p>
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
<div class="notification"></div>
|
||||||
<form class="form-signin">
|
<form class="form-signin">
|
||||||
<div class="form-inputs">
|
<div class="form-inputs">
|
||||||
<input class="form-control" type="password" name="password1" id="password1" placeholder="New password" required="required" />
|
<input class="form-control" type="password" name="password1" id="password1" placeholder="New password" required="required" />
|
||||||
|
|
|
@ -9,6 +9,7 @@ block form-header
|
||||||
<p>After giving your username, you will receive an email to change your password.</p>
|
<p>After giving your username, you will receive an email to change your password.</p>
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
<div class="notification"></div>
|
||||||
<form class="form-signin">
|
<form class="form-signin">
|
||||||
<div class="form-inputs">
|
<div class="form-inputs">
|
||||||
<input type="text" class="form-control" name="username" id="username" placeholder="Your username" required="required" />
|
<input type="text" class="form-control" name="username" id="username" placeholder="Your username" required="required" />
|
||||||
|
|
|
@ -5,6 +5,7 @@ block form-header
|
||||||
<img class="header-img" src="../img/padlock.png" alt="">
|
<img class="header-img" src="../img/padlock.png" alt="">
|
||||||
|
|
||||||
block content
|
block content
|
||||||
|
<div class="notification notification-totp"></div>
|
||||||
<form class="form-signin totp">
|
<form class="form-signin totp">
|
||||||
<div class="form-inputs">
|
<div class="form-inputs">
|
||||||
<input type="text" class="form-control" id="token" placeholder="Token" required autofocus>
|
<input type="text" class="form-control" id="token" placeholder="Token" required autofocus>
|
||||||
|
@ -14,6 +15,7 @@ block content
|
||||||
<span class="clearfix"></span>
|
<span class="clearfix"></span>
|
||||||
</form>
|
</form>
|
||||||
<hr>
|
<hr>
|
||||||
|
<div class="notification notification-u2f"></div>
|
||||||
<form class="form-signin u2f">
|
<form class="form-signin u2f">
|
||||||
<button class="btn btn-lg btn-primary btn-block u2f-button" type="submit">U2F</button>
|
<button class="btn btn-lg btn-primary btn-block u2f-button" type="submit">U2F</button>
|
||||||
a(href=u2f_identity_start_endpoint, class="pull-right link register-u2f") Need to register?
|
a(href=u2f_identity_start_endpoint, class="pull-right link register-u2f") Need to register?
|
||||||
|
|
4
src/types/jquery-notify.d.ts
vendored
4
src/types/jquery-notify.d.ts
vendored
|
@ -1,4 +0,0 @@
|
||||||
|
|
||||||
interface JQueryStatic {
|
|
||||||
notify: any;
|
|
||||||
}
|
|
|
@ -12,7 +12,7 @@ Feature: User validate first factor
|
||||||
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 with message "Error during authentication: Authetication failed. Please check your credentials."
|
Then I get a notification of type "error" with message "Authentication failed. Please double check your credentials."
|
||||||
|
|
||||||
Scenario: User succeeds TOTP second factor
|
Scenario: User succeeds TOTP second factor
|
||||||
Given I visit "https://auth.test.local:8080/"
|
Given I visit "https://auth.test.local:8080/"
|
||||||
|
@ -29,7 +29,7 @@ Feature: User validate first factor
|
||||||
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 with message "Error while validating TOTP token. Cause: error"
|
Then I get a notification of type "error" with message "Problem with TOTP validation."
|
||||||
|
|
||||||
Scenario: User logs out
|
Scenario: User logs out
|
||||||
Given I visit "https://auth.test.local:8080/"
|
Given I visit "https://auth.test.local:8080/"
|
||||||
|
|
|
@ -9,7 +9,7 @@ Feature: User is able to reset 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
|
||||||
When I set field "username" to "james"
|
When I set field "username" to "james"
|
||||||
And I click on "Reset Password"
|
And I click on "Reset Password"
|
||||||
Then I get a notification with message "An email has been sent. Click on 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: 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
|
||||||
|
@ -30,4 +30,4 @@ Feature: User is able to reset his password
|
||||||
And I set field "password1" to "newpassword"
|
And I set field "password1" to "newpassword"
|
||||||
And I set field "password2" to "newpassword2"
|
And I set field "password2" to "newpassword2"
|
||||||
And I click on "Reset Password"
|
And I click on "Reset Password"
|
||||||
Then I get a notification with message "The passwords are different"
|
Then I get a notification of type "warning" with message "The passwords are different."
|
||||||
|
|
|
@ -13,5 +13,4 @@ Feature: Non authenticated users have no access to certain pages
|
||||||
| 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 | 403 |
|
||||||
| https://auth.test.local:8080/password-reset/identity/start | 403 |
|
| https://auth.test.local:8080/password-reset/identity/start | 403 |
|
||||||
| https://auth.test.local:8080/password-reset/identity/finish | 403 |
|
| https://auth.test.local:8080/password-reset/identity/finish | 403 |
|
||||||
|
|
|
@ -38,15 +38,6 @@ Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||||
return this.useTotpTokenHandle(handle);
|
return this.useTotpTokenHandle(handle);
|
||||||
});
|
});
|
||||||
|
|
||||||
Then("I get a notification with message {stringInDoubleQuotes}", function (notificationMessage: string) {
|
|
||||||
const that = this;
|
|
||||||
that.driver.sleep(500);
|
|
||||||
return this.driver
|
|
||||||
.findElement(seleniumWebdriver.By.className("notifyjs-corner"))
|
|
||||||
.findElement(seleniumWebdriver.By.tagName("span"))
|
|
||||||
.findElement(seleniumWebdriver.By.xpath("//span[contains(.,'" + notificationMessage + "')]"));
|
|
||||||
});
|
|
||||||
|
|
||||||
When("I visit {stringInDoubleQuotes} and get redirected {stringInDoubleQuotes}", function (url: string, redirectUrl: string) {
|
When("I visit {stringInDoubleQuotes} and get redirected {stringInDoubleQuotes}", function (url: string, redirectUrl: string) {
|
||||||
const that = this;
|
const that = this;
|
||||||
return this.driver.get(url)
|
return this.driver.get(url)
|
||||||
|
|
25
test/features/step_definitions/notifications.ts
Normal file
25
test/features/step_definitions/notifications.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import Cucumber = require("cucumber");
|
||||||
|
import seleniumWebdriver = require("selenium-webdriver");
|
||||||
|
import Assert = require("assert");
|
||||||
|
import Fs = require("fs");
|
||||||
|
import CustomWorld = require("../support/world");
|
||||||
|
|
||||||
|
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||||
|
Then("I get a notification of type {stringInDoubleQuotes} with message {stringInDoubleQuotes}",
|
||||||
|
function (notificationType: string, notificationMessage: string) {
|
||||||
|
const that = this;
|
||||||
|
const notificationEl = this.driver.findElement(seleniumWebdriver.By.className("notification"));
|
||||||
|
return this.driver.wait(seleniumWebdriver.until.elementIsVisible(notificationEl), 2000)
|
||||||
|
.then(function () {
|
||||||
|
return notificationEl.getText();
|
||||||
|
})
|
||||||
|
.then(function (txt: string) {
|
||||||
|
Assert.equal(notificationMessage, txt);
|
||||||
|
return notificationEl.getAttribute("class");
|
||||||
|
})
|
||||||
|
.then(function(classes: string) {
|
||||||
|
Assert(classes.indexOf(notificationType) > -1, "Class '" + notificationType + "' not found in notification element.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -3,6 +3,7 @@ import seleniumWebdriver = require("selenium-webdriver");
|
||||||
import Cucumber = require("cucumber");
|
import Cucumber = require("cucumber");
|
||||||
import Fs = require("fs");
|
import Fs = require("fs");
|
||||||
import Speakeasy = require("speakeasy");
|
import Speakeasy = require("speakeasy");
|
||||||
|
import Assert = require("assert");
|
||||||
|
|
||||||
function CustomWorld() {
|
function CustomWorld() {
|
||||||
const that = this;
|
const that = this;
|
||||||
|
@ -26,16 +27,26 @@ function CustomWorld() {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getErrorPage = function (code: number) {
|
this.getErrorPage = function (code: number) {
|
||||||
return this.driver
|
const that = this;
|
||||||
.findElement(seleniumWebdriver.By.tagName("h1"))
|
return this.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.tagName("h1")), 2000)
|
||||||
.findElement(seleniumWebdriver.By.xpath("//h1[contains(.,'Error " + code + "')]"));
|
.then(function () {
|
||||||
|
return that.driver
|
||||||
|
.findElement(seleniumWebdriver.By.tagName("h1")).getText();
|
||||||
|
})
|
||||||
|
.then(function (txt: string) {
|
||||||
|
Assert.equal(txt, "Error " + code);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.clickOnButton = function (buttonText: string) {
|
this.clickOnButton = function (buttonText: string) {
|
||||||
return this.driver
|
const that = this;
|
||||||
.findElement(seleniumWebdriver.By.tagName("button"))
|
return this.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.tagName("button")), 2000)
|
||||||
.findElement(seleniumWebdriver.By.xpath("//button[contains(.,'" + buttonText + "')]"))
|
.then(function () {
|
||||||
.click();
|
return that.driver
|
||||||
|
.findElement(seleniumWebdriver.By.tagName("button"))
|
||||||
|
.findElement(seleniumWebdriver.By.xpath("//button[contains(.,'" + buttonText + "')]"))
|
||||||
|
.click();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.loginWithUserPassword = function (username: string, password: string) {
|
this.loginWithUserPassword = function (username: string, password: string) {
|
||||||
|
@ -68,7 +79,7 @@ function CustomWorld() {
|
||||||
return that.driver.get(link);
|
return that.driver.get(link);
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id("secret")), 1000);
|
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id("secret")), 5000);
|
||||||
})
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return that.driver.findElement(seleniumWebdriver.By.id("secret")).getText();
|
return that.driver.findElement(seleniumWebdriver.By.id("secret")).getText();
|
||||||
|
|
71
test/unit/client/Notifier.test.ts
Normal file
71
test/unit/client/Notifier.test.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
|
||||||
|
import Assert = require("assert");
|
||||||
|
import Sinon = require("sinon");
|
||||||
|
import JQueryMock = require("./mocks/jquery");
|
||||||
|
|
||||||
|
import { Notifier } from "../../../src/client/lib/Notifier";
|
||||||
|
|
||||||
|
describe("test notifier", function() {
|
||||||
|
const SELECTOR = "dummy-selector";
|
||||||
|
const MESSAGE = "This is a message";
|
||||||
|
let jqueryMock: { jquery: JQueryMock.JQueryMock, element: JQueryMock.JQueryElementsMock };
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
jqueryMock = JQueryMock.JQueryMock();
|
||||||
|
});
|
||||||
|
|
||||||
|
function should_fade_in_and_out_on_notification(notificationType: string): void {
|
||||||
|
const fadeInReturn = {
|
||||||
|
delay: Sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
const delayReturn = {
|
||||||
|
fadeOut: Sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
jqueryMock.element.fadeIn.returns(fadeInReturn);
|
||||||
|
jqueryMock.element.fadeIn.yields();
|
||||||
|
delayReturn.fadeOut.yields();
|
||||||
|
|
||||||
|
fadeInReturn.delay.returns(delayReturn);
|
||||||
|
|
||||||
|
function onFadedInCallback() {
|
||||||
|
Assert(jqueryMock.element.fadeIn.calledOnce);
|
||||||
|
Assert(jqueryMock.element.addClass.calledWith(notificationType));
|
||||||
|
Assert(!jqueryMock.element.removeClass.calledWith(notificationType));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFadedOutCallback() {
|
||||||
|
Assert(jqueryMock.element.removeClass.calledWith(notificationType));
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifier = new Notifier(SELECTOR, jqueryMock.jquery as any);
|
||||||
|
|
||||||
|
// Call the method by its name... Bad but allows code reuse.
|
||||||
|
(notifier as any)[notificationType](MESSAGE, {
|
||||||
|
onFadedIn: onFadedInCallback,
|
||||||
|
onFadedOut: onFadedOutCallback
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert(jqueryMock.element.fadeIn.calledOnce);
|
||||||
|
Assert(fadeInReturn.delay.calledOnce);
|
||||||
|
Assert(delayReturn.fadeOut.calledOnce);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
it("should fade in and fade out an error message", function() {
|
||||||
|
should_fade_in_and_out_on_notification("error");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fade in and fade out an info message", function() {
|
||||||
|
should_fade_in_and_out_on_notification("info");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fade in and fade out an warning message", function() {
|
||||||
|
should_fade_in_and_out_on_notification("warning");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fade in and fade out an success message", function() {
|
||||||
|
should_fade_in_and_out_on_notification("success");
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
import FirstFactorValidator = require("../../../../src/client/firstfactor/FirstFactorValidator");
|
import FirstFactorValidator = require("../../../../src/client/lib/firstfactor/FirstFactorValidator");
|
||||||
import JQueryMock = require("../mocks/jquery");
|
import JQueryMock = require("../mocks/jquery");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
|
@ -11,23 +11,23 @@ describe("test FirstFactorValidator", function () {
|
||||||
postPromise.done.returns(postPromise);
|
postPromise.done.returns(postPromise);
|
||||||
|
|
||||||
const jqueryMock = JQueryMock.JQueryMock();
|
const jqueryMock = JQueryMock.JQueryMock();
|
||||||
jqueryMock.post.returns(postPromise);
|
jqueryMock.jquery.post.returns(postPromise);
|
||||||
|
|
||||||
return FirstFactorValidator.validate("username", "password", jqueryMock as any);
|
return FirstFactorValidator.validate("username", "password", jqueryMock.jquery as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
function should_fail_first_factor_validation(statusCode: number, errorMessage: string) {
|
function should_fail_first_factor_validation(errorMessage: string) {
|
||||||
const xhr = {
|
const xhr = {
|
||||||
status: statusCode
|
status: 401
|
||||||
};
|
};
|
||||||
const postPromise = JQueryMock.JQueryDeferredMock();
|
const postPromise = JQueryMock.JQueryDeferredMock();
|
||||||
postPromise.fail.yields(xhr, errorMessage);
|
postPromise.fail.yields(xhr, errorMessage);
|
||||||
postPromise.done.returns(postPromise);
|
postPromise.done.returns(postPromise);
|
||||||
|
|
||||||
const jqueryMock = JQueryMock.JQueryMock();
|
const jqueryMock = JQueryMock.JQueryMock();
|
||||||
jqueryMock.post.returns(postPromise);
|
jqueryMock.jquery.post.returns(postPromise);
|
||||||
|
|
||||||
return FirstFactorValidator.validate("username", "password", jqueryMock as any)
|
return FirstFactorValidator.validate("username", "password", jqueryMock.jquery as any)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return BluebirdPromise.reject(new Error("First factor validation successfully finished while it should have not."));
|
return BluebirdPromise.reject(new Error("First factor validation successfully finished while it should have not."));
|
||||||
}, function (err: Error) {
|
}, function (err: Error) {
|
||||||
|
@ -37,12 +37,8 @@ describe("test FirstFactorValidator", function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("should fail first factor validation", () => {
|
describe("should fail first factor validation", () => {
|
||||||
it("should fail with error 500", () => {
|
it("should fail with error", () => {
|
||||||
return should_fail_first_factor_validation(500, "Internal error");
|
return should_fail_first_factor_validation("Authetication failed. Please check your credentials.");
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail with error 401", () => {
|
|
||||||
return should_fail_first_factor_validation(401, "Authetication failed. Please check your credentials.");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,87 +0,0 @@
|
||||||
|
|
||||||
import Endpoints = require("../../../../src/server/endpoints");
|
|
||||||
import BluebirdPromise = require("bluebird");
|
|
||||||
|
|
||||||
import UISelectors = require("../../../../src/client/firstfactor/UISelectors");
|
|
||||||
import firstfactor from "../../../../src/client/firstfactor/index";
|
|
||||||
import JQueryMock = require("../mocks/jquery");
|
|
||||||
import Assert = require("assert");
|
|
||||||
import sinon = require("sinon");
|
|
||||||
import jslogger = require("js-logger");
|
|
||||||
|
|
||||||
describe("test first factor page", () => {
|
|
||||||
it("should validate first factor", () => {
|
|
||||||
const jQuery = JQueryMock.JQueryMock();
|
|
||||||
const window = {
|
|
||||||
location: {
|
|
||||||
search: "?redirect=https://example.com",
|
|
||||||
href: ""
|
|
||||||
},
|
|
||||||
document: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const thenSpy = sinon.spy();
|
|
||||||
const FirstFactorValidator: any = {
|
|
||||||
validate: sinon.stub().returns({ then: thenSpy })
|
|
||||||
};
|
|
||||||
|
|
||||||
firstfactor(window as Window, jQuery as any, FirstFactorValidator, jslogger);
|
|
||||||
const readyCallback = jQuery.getCall(0).returnValue.ready.getCall(0).args[0];
|
|
||||||
readyCallback();
|
|
||||||
|
|
||||||
const onSubmitCallback = jQuery.getCall(1).returnValue.on.getCall(0).args[1];
|
|
||||||
jQuery.onCall(2).returns({ val: sinon.stub() });
|
|
||||||
jQuery.onCall(3).returns({ val: sinon.stub() });
|
|
||||||
jQuery.onCall(4).returns({ val: sinon.stub() });
|
|
||||||
jQuery.onCall(5).returns({ val: sinon.stub() });
|
|
||||||
|
|
||||||
onSubmitCallback();
|
|
||||||
|
|
||||||
const successCallback = thenSpy.getCall(0).args[0];
|
|
||||||
successCallback();
|
|
||||||
|
|
||||||
Assert.equal(window.location.href, Endpoints.SECOND_FACTOR_GET);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fail to validate first factor", () => {
|
|
||||||
let jQuery: JQueryMock.JQueryMock;
|
|
||||||
beforeEach(function () {
|
|
||||||
jQuery = JQueryMock.JQueryMock();
|
|
||||||
const window = {
|
|
||||||
location: {
|
|
||||||
search: "?redirect=https://example.com",
|
|
||||||
href: ""
|
|
||||||
},
|
|
||||||
document: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const thenSpy = sinon.spy();
|
|
||||||
const FirstFactorValidator: any = {
|
|
||||||
validate: sinon.stub().returns({ then: thenSpy })
|
|
||||||
};
|
|
||||||
|
|
||||||
firstfactor(window as Window, jQuery as any, FirstFactorValidator, jslogger);
|
|
||||||
const readyCallback = jQuery.getCall(0).returnValue.ready.getCall(0).args[0];
|
|
||||||
readyCallback();
|
|
||||||
|
|
||||||
const onSubmitCallback = jQuery.getCall(1).returnValue.on.getCall(0).args[1];
|
|
||||||
jQuery.onCall(2).returns({ val: sinon.stub() });
|
|
||||||
jQuery.onCall(3).returns({ val: sinon.stub() });
|
|
||||||
jQuery.onCall(4).returns({ val: sinon.stub() });
|
|
||||||
jQuery.onCall(5).returns({ val: sinon.stub() });
|
|
||||||
|
|
||||||
onSubmitCallback();
|
|
||||||
|
|
||||||
const failureCallback = thenSpy.getCall(0).args[1];
|
|
||||||
failureCallback(new Error("Error when validating first factor"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should notify the user there is a failure", function () {
|
|
||||||
Assert(jQuery.notify.calledOnce);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reset the password field", function () {
|
|
||||||
Assert.equal(jQuery.getCall(4).returnValue.val.getCall(0).args[0], "");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
33
test/unit/client/mocks/NotifierStub.ts
Normal file
33
test/unit/client/mocks/NotifierStub.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
import Sinon = require("sinon");
|
||||||
|
import { INotifier } from "../../../../src/client/lib/INotifier";
|
||||||
|
|
||||||
|
export class NotifierStub implements INotifier {
|
||||||
|
successStub: Sinon.SinonStub;
|
||||||
|
errorStub: Sinon.SinonStub;
|
||||||
|
warnStub: Sinon.SinonStub;
|
||||||
|
infoStub: Sinon.SinonStub;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.successStub = Sinon.stub();
|
||||||
|
this.errorStub = Sinon.stub();
|
||||||
|
this.warnStub = Sinon.stub();
|
||||||
|
this.infoStub = Sinon.stub();
|
||||||
|
}
|
||||||
|
|
||||||
|
success(msg: string) {
|
||||||
|
this.successStub();
|
||||||
|
}
|
||||||
|
|
||||||
|
error(msg: string) {
|
||||||
|
this.errorStub();
|
||||||
|
}
|
||||||
|
|
||||||
|
warning(msg: string) {
|
||||||
|
this.warnStub();
|
||||||
|
}
|
||||||
|
|
||||||
|
info(msg: string) {
|
||||||
|
this.infoStub();
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,17 +10,32 @@ export interface JQueryMock extends sinon.SinonStub {
|
||||||
notify: sinon.SinonStub;
|
notify: sinon.SinonStub;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface JQueryElementsMock {
|
||||||
|
ready: sinon.SinonStub;
|
||||||
|
show: sinon.SinonStub;
|
||||||
|
hide: sinon.SinonStub;
|
||||||
|
html: sinon.SinonStub;
|
||||||
|
addClass: sinon.SinonStub;
|
||||||
|
removeClass: sinon.SinonStub;
|
||||||
|
fadeIn: sinon.SinonStub;
|
||||||
|
on: sinon.SinonStub;
|
||||||
|
}
|
||||||
|
|
||||||
export interface JQueryDeferredMock {
|
export interface JQueryDeferredMock {
|
||||||
done: sinon.SinonStub;
|
done: sinon.SinonStub;
|
||||||
fail: sinon.SinonStub;
|
fail: sinon.SinonStub;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function JQueryMock(): JQueryMock {
|
export function JQueryMock(): { jquery: JQueryMock, element: JQueryElementsMock } {
|
||||||
const jquery = sinon.stub() as any;
|
const jquery = sinon.stub() as any;
|
||||||
const jqueryInstance = {
|
const jqueryInstance: JQueryElementsMock = {
|
||||||
ready: sinon.stub(),
|
ready: sinon.stub(),
|
||||||
show: sinon.stub(),
|
show: sinon.stub(),
|
||||||
hide: sinon.stub(),
|
hide: sinon.stub(),
|
||||||
|
html: sinon.stub(),
|
||||||
|
addClass: sinon.stub(),
|
||||||
|
removeClass: sinon.stub(),
|
||||||
|
fadeIn: sinon.stub(),
|
||||||
on: sinon.stub()
|
on: sinon.stub()
|
||||||
};
|
};
|
||||||
jquery.ajax = sinon.stub();
|
jquery.ajax = sinon.stub();
|
||||||
|
@ -28,7 +43,10 @@ export function JQueryMock(): JQueryMock {
|
||||||
jquery.post = sinon.stub();
|
jquery.post = sinon.stub();
|
||||||
jquery.notify = sinon.stub();
|
jquery.notify = sinon.stub();
|
||||||
jquery.returns(jqueryInstance);
|
jquery.returns(jqueryInstance);
|
||||||
return jquery;
|
return {
|
||||||
|
jquery: jquery,
|
||||||
|
element: jqueryInstance
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function JQueryDeferredMock(): JQueryDeferredMock {
|
export function JQueryDeferredMock(): JQueryDeferredMock {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
import TOTPValidator = require("../../../../src/client/secondfactor/TOTPValidator");
|
import TOTPValidator = require("../../../../src/client/lib/secondfactor/TOTPValidator");
|
||||||
import JQueryMock = require("../mocks/jquery");
|
import JQueryMock = require("../mocks/jquery");
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
|
@ -11,9 +11,9 @@ describe("test TOTPValidator", function () {
|
||||||
postPromise.done.returns(postPromise);
|
postPromise.done.returns(postPromise);
|
||||||
|
|
||||||
const jqueryMock = JQueryMock.JQueryMock();
|
const jqueryMock = JQueryMock.JQueryMock();
|
||||||
jqueryMock.ajax.returns(postPromise);
|
jqueryMock.jquery.ajax.returns(postPromise);
|
||||||
|
|
||||||
return TOTPValidator.validate("totp_token", jqueryMock as any);
|
return TOTPValidator.validate("totp_token", jqueryMock.jquery as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail validating TOTP token", () => {
|
it("should fail validating TOTP token", () => {
|
||||||
|
@ -24,9 +24,9 @@ describe("test TOTPValidator", function () {
|
||||||
postPromise.done.returns(postPromise);
|
postPromise.done.returns(postPromise);
|
||||||
|
|
||||||
const jqueryMock = JQueryMock.JQueryMock();
|
const jqueryMock = JQueryMock.JQueryMock();
|
||||||
jqueryMock.ajax.returns(postPromise);
|
jqueryMock.jquery.ajax.returns(postPromise);
|
||||||
|
|
||||||
return TOTPValidator.validate("totp_token", jqueryMock 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) {
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
|
|
||||||
import U2FValidator = require("../../../../src/client/secondfactor/U2FValidator");
|
import U2FValidator = require("../../../../src/client/lib/secondfactor/U2FValidator");
|
||||||
|
import { INotifier } from "../../../../src/client/lib/INotifier";
|
||||||
import JQueryMock = require("../mocks/jquery");
|
import JQueryMock = require("../mocks/jquery");
|
||||||
import U2FApiMock = require("../mocks/u2f-api");
|
import U2FApiMock = require("../mocks/u2f-api");
|
||||||
import { SignMessage } from "../../../../src/server/lib/routes/secondfactor/u2f/sign_request/SignMessage";
|
import { SignMessage } from "../../../../src/server/lib/routes/secondfactor/u2f/sign_request/SignMessage";
|
||||||
import BluebirdPromise = require("bluebird");
|
import BluebirdPromise = require("bluebird");
|
||||||
import Assert = require("assert");
|
import Assert = require("assert");
|
||||||
|
import { NotifierStub } from "../mocks/NotifierStub";
|
||||||
|
|
||||||
describe("test U2F validation", function () {
|
describe("test U2F validation", function () {
|
||||||
|
let notifier: INotifier;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
notifier = new NotifierStub();
|
||||||
|
});
|
||||||
|
|
||||||
it("should validate the U2F device", () => {
|
it("should validate the U2F device", () => {
|
||||||
const signatureRequest: SignMessage = {
|
const signatureRequest: SignMessage = {
|
||||||
keyHandle: "keyhandle",
|
keyHandle: "keyhandle",
|
||||||
|
@ -28,10 +36,10 @@ describe("test U2F validation", function () {
|
||||||
postPromise.done.returns(postPromise);
|
postPromise.done.returns(postPromise);
|
||||||
|
|
||||||
const jqueryMock = JQueryMock.JQueryMock();
|
const jqueryMock = JQueryMock.JQueryMock();
|
||||||
jqueryMock.get.returns(getPromise);
|
jqueryMock.jquery.get.returns(getPromise);
|
||||||
jqueryMock.ajax.returns(postPromise);
|
jqueryMock.jquery.ajax.returns(postPromise);
|
||||||
|
|
||||||
return U2FValidator.validate(jqueryMock as any, u2fClient as any);
|
return U2FValidator.validate(jqueryMock.jquery as any, notifier, u2fClient as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fail during initial authentication request", () => {
|
it("should fail during initial authentication request", () => {
|
||||||
|
@ -42,9 +50,9 @@ describe("test U2F validation", function () {
|
||||||
getPromise.fail.yields(undefined, "Error while issuing authentication request");
|
getPromise.fail.yields(undefined, "Error while issuing authentication request");
|
||||||
|
|
||||||
const jqueryMock = JQueryMock.JQueryMock();
|
const jqueryMock = JQueryMock.JQueryMock();
|
||||||
jqueryMock.get.returns(getPromise);
|
jqueryMock.jquery.get.returns(getPromise);
|
||||||
|
|
||||||
return U2FValidator.validate(jqueryMock as any, u2fClient as any)
|
return U2FValidator.validate(jqueryMock.jquery as any, notifier, u2fClient as any)
|
||||||
.catch(function(err: Error) {
|
.catch(function(err: Error) {
|
||||||
Assert.equal("Error while issuing authentication request", err.message);
|
Assert.equal("Error while issuing authentication request", err.message);
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
|
@ -68,9 +76,9 @@ describe("test U2F validation", function () {
|
||||||
getPromise.done.returns(getPromise);
|
getPromise.done.returns(getPromise);
|
||||||
|
|
||||||
const jqueryMock = JQueryMock.JQueryMock();
|
const jqueryMock = JQueryMock.JQueryMock();
|
||||||
jqueryMock.get.returns(getPromise);
|
jqueryMock.jquery.get.returns(getPromise);
|
||||||
|
|
||||||
return U2FValidator.validate(jqueryMock as any, u2fClient as any)
|
return U2FValidator.validate(jqueryMock.jquery as any, notifier, u2fClient as any)
|
||||||
.catch(function(err: Error) {
|
.catch(function(err: Error) {
|
||||||
Assert.equal("Device unable to sign", err.message);
|
Assert.equal("Device unable to sign", err.message);
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
|
@ -98,10 +106,10 @@ describe("test U2F validation", function () {
|
||||||
postPromise.done.returns(postPromise);
|
postPromise.done.returns(postPromise);
|
||||||
|
|
||||||
const jqueryMock = JQueryMock.JQueryMock();
|
const jqueryMock = JQueryMock.JQueryMock();
|
||||||
jqueryMock.get.returns(getPromise);
|
jqueryMock.jquery.get.returns(getPromise);
|
||||||
jqueryMock.ajax.returns(postPromise);
|
jqueryMock.jquery.ajax.returns(postPromise);
|
||||||
|
|
||||||
return U2FValidator.validate(jqueryMock as any, u2fClient as any)
|
return U2FValidator.validate(jqueryMock.jquery as any, notifier, u2fClient as any)
|
||||||
.catch(function(err: Error) {
|
.catch(function(err: Error) {
|
||||||
Assert.equal("Error while finishing authentication", err.message);
|
Assert.equal("Error while finishing authentication", err.message);
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
import sinon = require("sinon");
|
import sinon = require("sinon");
|
||||||
import assert = require("assert");
|
import assert = require("assert");
|
||||||
|
|
||||||
import UISelector = require("../../../../src/client/totp-register/ui-selector");
|
import UISelector = require("../../../../src/client/lib/totp-register/ui-selector");
|
||||||
import TOTPRegister = require("../../../../src/client/totp-register/totp-register");
|
import TOTPRegister = require("../../../../src/client/lib/totp-register/totp-register");
|
||||||
|
|
||||||
describe("test totp-register", function() {
|
describe("test totp-register", function() {
|
||||||
let jqueryMock: any;
|
let jqueryMock: any;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user