import Sinon = require("sinon"); import BluebirdPromise = require("bluebird"); import Assert = require("assert"); import { AuthenticationRegulator } from "../src/lib/AuthenticationRegulator"; import MockDate = require("mockdate"); import exceptions = require("../src/lib/Exceptions"); import { UserDataStoreStub } from "./mocks/storage/UserDataStoreStub"; describe("test authentication regulator", function () { const USER1 = "USER1"; const USER2 = "USER2"; let userDataStoreStub: UserDataStoreStub; beforeEach(function () { userDataStoreStub = new UserDataStoreStub(); const dataStore: { [userId: string]: { userId: string, date: Date, isAuthenticationSuccessful: boolean }[] } = { [USER1]: [], [USER2]: [] }; userDataStoreStub.saveAuthenticationTraceStub.callsFake(function (userId, isAuthenticationSuccessful) { dataStore[userId].unshift({ userId: userId, date: new Date(), isAuthenticationSuccessful: isAuthenticationSuccessful, }); return BluebirdPromise.resolve(); }); userDataStoreStub.retrieveLatestAuthenticationTracesStub.callsFake(function (userId, count) { const ret = (dataStore[userId].length <= count) ? dataStore[userId] : dataStore[userId].slice(0, 3); return BluebirdPromise.resolve(ret); }); }); afterEach(function () { MockDate.reset(); }); function markAuthenticationAt(regulator: AuthenticationRegulator, user: string, time: string, success: boolean) { MockDate.set(time); return regulator.mark(user, success); } it("should mark 2 authentication and regulate (accept)", function () { const regulator = new AuthenticationRegulator(userDataStoreStub, 3, 10, 10); return regulator.mark(USER1, false) .then(function () { return regulator.mark(USER1, true); }) .then(function () { return regulator.regulate(USER1); }); }); it("should mark 3 authentications and regulate (reject)", function () { const regulator = new AuthenticationRegulator(userDataStoreStub, 3, 10, 10); return regulator.mark(USER1, false) .then(function () { return regulator.mark(USER1, false); }) .then(function () { return regulator.mark(USER1, false); }) .then(function () { return regulator.regulate(USER1); }) .then(function () { return BluebirdPromise.reject(new Error("should not be here!")); }) .catch(exceptions.AuthenticationRegulationError, function () { return BluebirdPromise.resolve(); }); }); it("should mark 1 failed, 1 successful and 1 failed authentications within minimum time and regulate (accept)", function () { const regulator = new AuthenticationRegulator(userDataStoreStub, 3, 60, 30); return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:00", false) .then(function () { return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:10", true); }) .then(function () { return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:20", false); }) .then(function () { return regulator.regulate(USER1); }) .then(function () { return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:30", false); }) .then(function () { return regulator.regulate(USER1); }) .then(function () { return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:39", false); }) .then(function () { return regulator.regulate(USER1); }) .then(function () { return BluebirdPromise.reject(new Error("should not be here!")); }, function () { return BluebirdPromise.resolve(); }); }); it("should regulate user if number of failures is greater than 3 in allowed time lapse", function () { function markAuthentications(regulator: AuthenticationRegulator, user: string) { return markAuthenticationAt(regulator, user, "1/2/2000 00:00:00", false) .then(function () { return markAuthenticationAt(regulator, user, "1/2/2000 00:00:45", false); }) .then(function () { return markAuthenticationAt(regulator, user, "1/2/2000 00:01:05", false); }) .then(function () { return regulator.regulate(user); }); } const regulator1 = new AuthenticationRegulator(userDataStoreStub, 3, 60, 60); const regulator2 = new AuthenticationRegulator(userDataStoreStub, 3, 2 * 60, 60); const p1 = markAuthentications(regulator1, USER1); const p2 = markAuthentications(regulator2, USER2); return BluebirdPromise.join(p1, p2) .then(function () { return BluebirdPromise.reject(new Error("should not be here...")); }, function () { Assert(p1.isFulfilled()); Assert(p2.isRejected()); }); }); it("should user wait after regulation to authenticate again", function () { function markAuthentications(regulator: AuthenticationRegulator, user: string) { return markAuthenticationAt(regulator, user, "1/2/2000 00:00:00", false) .then(function () { return markAuthenticationAt(regulator, user, "1/2/2000 00:00:10", false); }) .then(function () { return markAuthenticationAt(regulator, user, "1/2/2000 00:00:15", false); }) .then(function () { return markAuthenticationAt(regulator, user, "1/2/2000 00:00:25", false); }) .then(function () { MockDate.set("1/2/2000 00:00:54"); return regulator.regulate(user); }) .then(function () { return BluebirdPromise.reject(new Error("should fail at this time")); }, function () { MockDate.set("1/2/2000 00:00:56"); return regulator.regulate(user); }); } const regulator = new AuthenticationRegulator(userDataStoreStub, 4, 30, 30); return markAuthentications(regulator, USER1); }); it("should disable regulation when max_retries is set to 0", function () { const maxRetries = 0; const regulator = new AuthenticationRegulator(userDataStoreStub, maxRetries, 60, 30); return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:00", false) .then(function () { return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:10", false); }) .then(function () { return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:15", false); }) .then(function () { return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:25", false); }) .then(function () { MockDate.set("1/2/2000 00:00:26"); return regulator.regulate(USER1); }); }); });