260 lines
8.1 KiB
TypeScript
260 lines
8.1 KiB
TypeScript
|
|
||
|
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();
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|