import * as Assert from "assert"; import * as Sinon from "sinon"; import * as MockDate from "mockdate"; import BluebirdPromise = require("bluebird"); import { UserDataStore } from "../../src/lib/storage/UserDataStore"; import { TOTPSecret } from "../../types/TOTPSecret"; import { U2FRegistration } from "../../types/U2FRegistration"; import { AuthenticationTraceDocument } from "../../src/lib/storage/AuthenticationTraceDocument"; import { CollectionStub } from "../mocks/storage/CollectionStub"; import { CollectionFactoryStub } from "../mocks/storage/CollectionFactoryStub"; describe("test user data store", function () { let factory: CollectionFactoryStub; let collection: CollectionStub; let userId: string; let appId: string; let totpSecret: TOTPSecret; let u2fRegistration: U2FRegistration; beforeEach(function () { factory = new CollectionFactoryStub(); collection = new CollectionStub(); userId = "user"; appId = "https://myappId"; totpSecret = { ascii: "abc", base32: "ABCDKZLEFZGREJK", otpauth_url: "totp://test" }; u2fRegistration = { keyHandle: "KEY_HANDLE", publicKey: "publickey" }; }); it("should correctly creates collections", function () { new UserDataStore(factory); Assert.equal(4, factory.buildStub.callCount); Assert(factory.buildStub.calledWith("authentication_traces")); Assert(factory.buildStub.calledWith("identity_validation_tokens")); Assert(factory.buildStub.calledWith("u2f_registrations")); Assert(factory.buildStub.calledWith("totp_secrets")); }); describe("TOTP secrets collection", function () { it("should save a totp secret", function () { factory.buildStub.returns(collection); collection.updateStub.returns(BluebirdPromise.resolve()); const dataStore = new UserDataStore(factory); return dataStore.saveTOTPSecret(userId, totpSecret) .then(function (doc) { Assert(collection.updateStub.calledOnce); Assert(collection.updateStub.calledWith({ userId: userId }, { userId: userId, secret: totpSecret }, { upsert: true })); return BluebirdPromise.resolve(); }); }); it("should retrieve a totp secret", function () { factory.buildStub.returns(collection); collection.findOneStub.withArgs().returns(BluebirdPromise.resolve()); const dataStore = new UserDataStore(factory); return dataStore.retrieveTOTPSecret(userId) .then(function (doc) { Assert(collection.findOneStub.calledOnce); Assert(collection.findOneStub.calledWith({ userId: userId })); return BluebirdPromise.resolve(); }); }); }); describe("U2F secrets collection", function () { it("should save a U2F secret", function () { factory.buildStub.returns(collection); collection.updateStub.returns(BluebirdPromise.resolve()); const dataStore = new UserDataStore(factory); return dataStore.saveU2FRegistration(userId, appId, u2fRegistration) .then(function (doc) { Assert(collection.updateStub.calledOnce); Assert(collection.updateStub.calledWith({ userId: userId, appId: appId }, { userId: userId, appId: appId, registration: u2fRegistration }, { upsert: true })); return BluebirdPromise.resolve(); }); }); it("should retrieve a U2F secret", function () { factory.buildStub.returns(collection); collection.findOneStub.withArgs().returns(BluebirdPromise.resolve()); const dataStore = new UserDataStore(factory); return dataStore.retrieveU2FRegistration(userId, appId) .then(function (doc) { Assert(collection.findOneStub.calledOnce); Assert(collection.findOneStub.calledWith({ userId: userId, appId: appId })); return BluebirdPromise.resolve(); }); }); }); describe("Regulator traces collection", function () { it("should save a trace", function () { factory.buildStub.returns(collection); collection.insertStub.returns(BluebirdPromise.resolve()); const dataStore = new UserDataStore(factory); return dataStore.saveAuthenticationTrace(userId, true) .then(function (doc) { Assert(collection.insertStub.calledOnce); Assert(collection.insertStub.calledWith({ userId: userId, date: Sinon.match.date, isAuthenticationSuccessful: true })); return BluebirdPromise.resolve(); }); }); function should_retrieve_latest_authentication_traces(count: number) { factory.buildStub.returns(collection); collection.findStub.withArgs().returns(BluebirdPromise.resolve()); const dataStore = new UserDataStore(factory); return dataStore.retrieveLatestAuthenticationTraces(userId, count) .then(function (doc: AuthenticationTraceDocument[]) { Assert(collection.findStub.calledOnce); Assert(collection.findStub.calledWith({ userId: userId, }, { date: -1 }, count)); return BluebirdPromise.resolve(); }); } it("should retrieve 3 latest failed authentication traces", function () { should_retrieve_latest_authentication_traces(3); }); }); describe("Identity validation collection", function () { it("should save a identity validation token", function () { factory.buildStub.returns(collection); collection.insertStub.returns(BluebirdPromise.resolve()); const dataStore = new UserDataStore(factory); const maxAge = 400; const token = "TOKEN"; const challenge = "CHALLENGE"; return dataStore.produceIdentityValidationToken(userId, token, challenge, maxAge) .then(function (doc) { Assert(collection.insertStub.calledOnce); Assert(collection.insertStub.calledWith({ userId: userId, token: token, challenge: challenge, maxDate: Sinon.match.date })); return BluebirdPromise.resolve(); }); }); it("should consume an identity token successfully", function () { factory.buildStub.returns(collection); MockDate.set(100); const token = "TOKEN"; const challenge = "CHALLENGE"; collection.findOneStub.withArgs().returns(BluebirdPromise.resolve({ userId: "USER", token: token, challenge: challenge, maxDate: new Date() })); collection.removeStub.returns(BluebirdPromise.resolve()); const dataStore = new UserDataStore(factory); MockDate.set(80); return dataStore.consumeIdentityValidationToken(token, challenge) .then(function (doc) { MockDate.reset(); Assert(collection.findOneStub.calledOnce); Assert(collection.findOneStub.calledWith({ token: token, challenge: challenge })); Assert(collection.removeStub.calledOnce); Assert(collection.removeStub.calledWith({ token: token, challenge: challenge })); return BluebirdPromise.resolve(); }); }); it("should consume an expired identity token", function () { factory.buildStub.returns(collection); MockDate.set(0); const token = "TOKEN"; const challenge = "CHALLENGE"; collection.findOneStub.withArgs().returns(BluebirdPromise.resolve({ userId: "USER", token: token, challenge: challenge, maxDate: new Date() })); const dataStore = new UserDataStore(factory); MockDate.set(80000); return dataStore.consumeIdentityValidationToken(token, challenge) .then(function () { return BluebirdPromise.reject(new Error("should not be here")); }) .catch(function () { MockDate.reset(); Assert(collection.findOneStub.calledOnce); Assert(collection.findOneStub.calledWith({ token: token, challenge: challenge })); return BluebirdPromise.resolve(); }); }); }); });