Implement retry mechanism for broken connections to mongo (#258)
Before this patch, when Authelia started, if Mongo was not up and running, Authelia failed to connect and never retried. Now, everytime Authelia faces a broken connection, it tries to reconnect during the next operation.pull/260/head
parent
0dd9a5f815
commit
c503765dd6
|
@ -45,6 +45,10 @@ module.exports = function (grunt) {
|
|||
cmd: "./scripts/run-cucumber.sh",
|
||||
args: ["./test/features"]
|
||||
},
|
||||
"test-complete-config": {
|
||||
cmd: "./node_modules/.bin/mocha",
|
||||
args: ['--colors', '--require', 'ts-node/register', 'test/complete-config/**/*.ts']
|
||||
},
|
||||
"test-minimal-config": {
|
||||
cmd: "./node_modules/.bin/mocha",
|
||||
args: ['--colors', '--require', 'ts-node/register', 'test/minimal-config/**/*.ts']
|
||||
|
@ -187,7 +191,7 @@ module.exports = function (grunt) {
|
|||
grunt.registerTask('test-server', ['env:env-test-server-unit', 'run:test-server-unit'])
|
||||
grunt.registerTask('test-client', ['env:env-test-client-unit', 'run:test-client-unit'])
|
||||
grunt.registerTask('test-unit', ['test-server', 'test-client']);
|
||||
grunt.registerTask('test-int', ['run:test-cucumber', 'run:test-minimal-config']);
|
||||
grunt.registerTask('test-int', ['run:test-cucumber', 'run:test-minimal-config', 'run:test-complete-config']);
|
||||
|
||||
grunt.registerTask('copy-resources', ['copy:resources', 'copy:views', 'copy:images', 'copy:thirdparties', 'concat:css']);
|
||||
grunt.registerTask('generate-config-schema', ['run:generate-config-schema', 'copy:schema']);
|
||||
|
|
|
@ -48,7 +48,8 @@ export default class Server {
|
|||
|
||||
private setup(config: Configuration, app: Express.Application, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
const that = this;
|
||||
return ServerVariablesInitializer.initialize(config, this.requestLogger, deps)
|
||||
return ServerVariablesInitializer.initialize(
|
||||
config, this.globalLogger, this.requestLogger, deps)
|
||||
.then(function (vars: ServerVariables) {
|
||||
Configurator.configure(config, app, vars, deps);
|
||||
return BluebirdPromise.resolve();
|
||||
|
|
|
@ -32,15 +32,16 @@ import { IAccessController } from "./access_control/IAccessController";
|
|||
import { CollectionFactoryFactory } from "./storage/CollectionFactoryFactory";
|
||||
import { ICollectionFactory } from "./storage/ICollectionFactory";
|
||||
import { MongoCollectionFactory } from "./storage/mongo/MongoCollectionFactory";
|
||||
import { MongoConnectorFactory } from "./connectors/mongo/MongoConnectorFactory";
|
||||
import { IMongoClient } from "./connectors/mongo/IMongoClient";
|
||||
|
||||
import { GlobalDependencies } from "../../types/Dependencies";
|
||||
import { ServerVariables } from "./ServerVariables";
|
||||
import { MethodCalculator } from "./authentication/MethodCalculator";
|
||||
import { MongoClient } from "./connectors/mongo/MongoClient";
|
||||
import { IGlobalLogger } from "./logging/IGlobalLogger";
|
||||
|
||||
class UserDataStoreFactory {
|
||||
static create(config: Configuration.Configuration): BluebirdPromise<UserDataStore> {
|
||||
static create(config: Configuration.Configuration, globalLogger: IGlobalLogger): BluebirdPromise<UserDataStore> {
|
||||
if (config.storage.local) {
|
||||
const nedbOptions: Nedb.DataStoreOptions = {
|
||||
filename: config.storage.local.path,
|
||||
|
@ -50,13 +51,12 @@ class UserDataStoreFactory {
|
|||
return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
|
||||
}
|
||||
else if (config.storage.mongo) {
|
||||
const mongoConnectorFactory = new MongoConnectorFactory();
|
||||
const mongoConnector = mongoConnectorFactory.create(config.storage.mongo.url);
|
||||
return mongoConnector.connect(config.storage.mongo.database)
|
||||
.then(function (client: IMongoClient) {
|
||||
const collectionFactory = CollectionFactoryFactory.createMongo(client);
|
||||
const mongoClient = new MongoClient(
|
||||
config.storage.mongo.url,
|
||||
config.storage.mongo.database,
|
||||
globalLogger);
|
||||
const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient);
|
||||
return BluebirdPromise.resolve(new UserDataStore(collectionFactory));
|
||||
});
|
||||
}
|
||||
|
||||
return BluebirdPromise.reject(new Error("Storage backend incorrectly configured."));
|
||||
|
@ -64,8 +64,12 @@ class UserDataStoreFactory {
|
|||
}
|
||||
|
||||
export class ServerVariablesInitializer {
|
||||
static initialize(config: Configuration.Configuration, requestLogger: IRequestLogger,
|
||||
static initialize(
|
||||
config: Configuration.Configuration,
|
||||
globalLogger: IGlobalLogger,
|
||||
requestLogger: IRequestLogger,
|
||||
deps: GlobalDependencies): BluebirdPromise<ServerVariables> {
|
||||
|
||||
const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
|
||||
const notifier = NotifierFactory.build(config.notifier, mailSenderBuilder);
|
||||
const ldapClientFactory = new LdapClientFactory(config.ldap, deps.ldapjs);
|
||||
|
@ -78,7 +82,7 @@ export class ServerVariablesInitializer {
|
|||
const accessController = new AccessController(config.access_control, deps.winston);
|
||||
const totpHandler = new TotpHandler(deps.speakeasy);
|
||||
|
||||
return UserDataStoreFactory.create(config)
|
||||
return UserDataStoreFactory.create(config, globalLogger)
|
||||
.then(function (userDataStore: UserDataStore) {
|
||||
const regulator = new Regulator(userDataStore, config.regulation.max_retries,
|
||||
config.regulation.find_time, config.regulation.ban_time);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import MongoDB = require("mongodb");
|
||||
import Bluebird = require("bluebird");
|
||||
|
||||
export interface IMongoClient {
|
||||
collection(name: string): MongoDB.Collection;
|
||||
collection(name: string): Bluebird<MongoDB.Collection>
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { IMongoClient } from "./IMongoClient";
|
||||
|
||||
export interface IMongoConnector {
|
||||
connect(databaseName: string): BluebirdPromise<IMongoClient>;
|
||||
close(): BluebirdPromise<void>;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import { IMongoConnector } from "./IMongoConnector";
|
||||
|
||||
export interface IMongoConnectorFactory {
|
||||
create(url: string): IMongoConnector;
|
||||
}
|
|
@ -1,38 +1,72 @@
|
|||
import Assert = require("assert");
|
||||
import Sinon = require("sinon");
|
||||
import MongoDB = require("mongodb");
|
||||
import Bluebird = require("bluebird");
|
||||
import { MongoClient } from "./MongoClient";
|
||||
import { GlobalLoggerStub } from "../../logging/GlobalLoggerStub.spec";
|
||||
|
||||
describe("connectors/mongo/MongoClient", function () {
|
||||
let mongoClientConnectStub: Sinon.SinonStub;
|
||||
let mongoDatabase: any;
|
||||
let mongoDatabaseCollectionStub: Sinon.SinonStub;
|
||||
let MongoClientStub: any;
|
||||
let mongoClientStub: any;
|
||||
let mongoDatabaseStub: any;
|
||||
let logger: GlobalLoggerStub = new GlobalLoggerStub();
|
||||
|
||||
describe("collection", function () {
|
||||
before(function() {
|
||||
mongoDatabaseCollectionStub = Sinon.stub();
|
||||
mongoDatabase = {
|
||||
collection: mongoDatabaseCollectionStub
|
||||
mongoClientStub = {
|
||||
db: Sinon.stub()
|
||||
};
|
||||
mongoDatabaseStub = {
|
||||
on: Sinon.stub(),
|
||||
collection: Sinon.stub()
|
||||
}
|
||||
});
|
||||
|
||||
mongoClientConnectStub = Sinon.stub(MongoDB.MongoClient, "connect");
|
||||
mongoClientConnectStub.yields(undefined, mongoDatabase);
|
||||
describe("Connection to mongo is ok", function() {
|
||||
before(function () {
|
||||
MongoClientStub = Sinon.stub(
|
||||
MongoDB.MongoClient, "connect");
|
||||
MongoClientStub.yields(
|
||||
undefined, mongoClientStub);
|
||||
mongoClientStub.db.returns(
|
||||
mongoDatabaseStub);
|
||||
});
|
||||
|
||||
after(function () {
|
||||
mongoClientConnectStub.restore();
|
||||
MongoClientStub.restore();
|
||||
});
|
||||
|
||||
it("should create a collection", function () {
|
||||
const COLLECTION_NAME = "mycollection";
|
||||
const client = new MongoClient(mongoDatabase);
|
||||
const client = new MongoClient("mongo://url", "databasename", logger);
|
||||
|
||||
mongoDatabaseCollectionStub.returns({});
|
||||
|
||||
const collection = client.collection(COLLECTION_NAME);
|
||||
|
||||
Assert(collection);
|
||||
Assert(mongoDatabaseCollectionStub.calledWith(COLLECTION_NAME ));
|
||||
mongoDatabaseStub.collection.returns("COL");
|
||||
return client.collection(COLLECTION_NAME)
|
||||
.then((collection) => mongoDatabaseStub.collection.calledWith(COLLECTION_NAME));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Connection to mongo is broken", function() {
|
||||
before(function () {
|
||||
MongoClientStub = Sinon.stub(
|
||||
MongoDB.MongoClient, "connect");
|
||||
MongoClientStub.yields(
|
||||
new Error("Failed connection"), undefined);
|
||||
});
|
||||
|
||||
after(function () {
|
||||
MongoClientStub.restore();
|
||||
});
|
||||
|
||||
it("should fail creating the collection", function() {
|
||||
const COLLECTION_NAME = "mycollection";
|
||||
const client = new MongoClient("mongo://url", "databasename", logger);
|
||||
|
||||
mongoDatabaseStub.collection.returns("COL");
|
||||
return client.collection(COLLECTION_NAME)
|
||||
.then((collection) => Bluebird.reject(new Error("should not be here")))
|
||||
.error((err) => Bluebird.resolve());
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,15 +1,60 @@
|
|||
|
||||
import MongoDB = require("mongodb");
|
||||
import { IMongoClient } from "./IMongoClient";
|
||||
import Bluebird = require("bluebird");
|
||||
import { AUTHENTICATION_FAILED } from "../../../../../shared/UserMessages";
|
||||
import { IGlobalLogger } from "../../logging/IGlobalLogger";
|
||||
|
||||
export class MongoClient implements IMongoClient {
|
||||
private db: MongoDB.Db;
|
||||
private url: string;
|
||||
private databaseName: string;
|
||||
|
||||
constructor(db: MongoDB.Db) {
|
||||
this.db = db;
|
||||
private database: MongoDB.Db;
|
||||
private client: MongoDB.MongoClient;
|
||||
private logger: IGlobalLogger;
|
||||
|
||||
constructor(
|
||||
url: string,
|
||||
databaseName: string,
|
||||
logger: IGlobalLogger) {
|
||||
|
||||
this.url = url;
|
||||
this.databaseName = databaseName;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
collection(name: string): MongoDB.Collection {
|
||||
return this.db.collection(name);
|
||||
connect(): Bluebird<void> {
|
||||
const that = this;
|
||||
const connectAsync = Bluebird.promisify(MongoDB.MongoClient.connect);
|
||||
return connectAsync(this.url)
|
||||
.then(function (client: MongoDB.MongoClient) {
|
||||
that.database = client.db(that.databaseName);
|
||||
that.database.on("close", () => {
|
||||
that.logger.info("[MongoClient] Lost connection.");
|
||||
});
|
||||
that.database.on("reconnect", () => {
|
||||
that.logger.info("[MongoClient] Reconnected.");
|
||||
});
|
||||
that.client = client;
|
||||
});
|
||||
}
|
||||
|
||||
close(): Bluebird<void> {
|
||||
if (this.client) {
|
||||
this.client.close();
|
||||
this.database = undefined;
|
||||
this.client = undefined;
|
||||
}
|
||||
return Bluebird.resolve();
|
||||
}
|
||||
|
||||
collection(name: string): Bluebird<MongoDB.Collection> {
|
||||
if (!this.client) {
|
||||
const that = this;
|
||||
return this.connect()
|
||||
.then(() => Bluebird.resolve(that.database.collection(name)));
|
||||
}
|
||||
|
||||
return Bluebird.resolve(this.database.collection(name));
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import Sinon = require("sinon");
|
||||
import MongoDB = require("mongodb");
|
||||
import Bluebird = require("bluebird");
|
||||
import { IMongoClient } from "../../../../src/lib/connectors/mongo/IMongoClient";
|
||||
|
||||
export class MongoClientStub implements IMongoClient {
|
||||
|
@ -9,7 +10,7 @@ export class MongoClientStub implements IMongoClient {
|
|||
this.collectionStub = Sinon.stub();
|
||||
}
|
||||
|
||||
collection(name: string): MongoDB.Collection {
|
||||
collection(name: string): Bluebird<MongoDB.Collection> {
|
||||
return this.collectionStub(name);
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import Assert = require("assert");
|
||||
import Sinon = require("sinon");
|
||||
import MongoDB = require("mongodb");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { IMongoClient } from "./IMongoClient";
|
||||
import { MongoConnector } from "./MongoConnector";
|
||||
|
||||
describe("connectors/mongo/MongoConnector", function () {
|
||||
let mongoClientConnectStub: Sinon.SinonStub;
|
||||
|
||||
describe("create", function () {
|
||||
before(function () {
|
||||
mongoClientConnectStub = Sinon.stub(MongoDB.MongoClient, "connect");
|
||||
});
|
||||
|
||||
after(function() {
|
||||
mongoClientConnectStub.restore();
|
||||
});
|
||||
|
||||
it("should create a connector", function () {
|
||||
const client = { db: Sinon.mock() };
|
||||
mongoClientConnectStub.yields(undefined, client);
|
||||
|
||||
const url = "mongodb://test.url";
|
||||
const connector = new MongoConnector(url);
|
||||
return connector.connect("database")
|
||||
.then(function (client: IMongoClient) {
|
||||
Assert(client);
|
||||
Assert(mongoClientConnectStub.calledWith(url));
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail creating a connector", function () {
|
||||
mongoClientConnectStub.yields(new Error("Error while creating mongo client"));
|
||||
|
||||
const url = "mongodb://test.url";
|
||||
const connector = new MongoConnector(url);
|
||||
return connector.connect("database")
|
||||
.then(function () { return BluebirdPromise.reject(new Error("It should not be here")); })
|
||||
.error(function (client: IMongoClient) {
|
||||
Assert(client);
|
||||
Assert(mongoClientConnectStub.calledWith(url));
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,31 +0,0 @@
|
|||
|
||||
import MongoDB = require("mongodb");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { IMongoClient } from "./IMongoClient";
|
||||
import { IMongoConnector } from "./IMongoConnector";
|
||||
import { MongoClient } from "./MongoClient";
|
||||
|
||||
export class MongoConnector implements IMongoConnector {
|
||||
private url: string;
|
||||
private client: MongoDB.MongoClient;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
connect(databaseName: string): BluebirdPromise<IMongoClient> {
|
||||
const that = this;
|
||||
const connectAsync = BluebirdPromise.promisify(MongoDB.MongoClient.connect);
|
||||
return connectAsync(this.url)
|
||||
.then(function (client: MongoDB.MongoClient) {
|
||||
that.client = client;
|
||||
const db = client.db(databaseName);
|
||||
return BluebirdPromise.resolve(new MongoClient(db));
|
||||
});
|
||||
}
|
||||
|
||||
close(): BluebirdPromise<void> {
|
||||
this.client.close();
|
||||
return BluebirdPromise.resolve();
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import Assert = require("assert");
|
||||
import { MongoConnectorFactory } from "./MongoConnectorFactory";
|
||||
|
||||
describe("connectors/mongo/MongoConnectorFactory", function () {
|
||||
describe("create", function () {
|
||||
it("should create a connector", function () {
|
||||
const factory = new MongoConnectorFactory();
|
||||
const connector = factory.create("mongodb://test.url");
|
||||
|
||||
Assert(connector);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { IMongoConnectorFactory } from "./IMongoConnectorFactory";
|
||||
import { IMongoConnector } from "./IMongoConnector";
|
||||
import { MongoConnector } from "./MongoConnector";
|
||||
import MongoDB = require("mongodb");
|
||||
|
||||
export class MongoConnectorFactory implements IMongoConnectorFactory {
|
||||
create(url: string): IMongoConnector {
|
||||
return new MongoConnector(url);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import Sinon = require("sinon");
|
||||
import { GlobalLogger } from "./GlobalLogger";
|
||||
import Winston = require("winston");
|
||||
import Express = require("express");
|
||||
import { IGlobalLogger } from "./IGlobalLogger";
|
||||
|
||||
export class GlobalLoggerStub implements IGlobalLogger {
|
||||
infoStub: Sinon.SinonStub;
|
||||
debugStub: Sinon.SinonStub;
|
||||
errorStub: Sinon.SinonStub;
|
||||
private globalLogger: IGlobalLogger;
|
||||
|
||||
constructor(enableLogging?: boolean) {
|
||||
this.infoStub = Sinon.stub();
|
||||
this.debugStub = Sinon.stub();
|
||||
this.errorStub = Sinon.stub();
|
||||
if (enableLogging)
|
||||
this.globalLogger = new GlobalLogger(Winston);
|
||||
}
|
||||
|
||||
info(message: string, ...args: any[]): void {
|
||||
if (this.globalLogger)
|
||||
this.globalLogger.info(message, ...args);
|
||||
this.infoStub(message, ...args);
|
||||
}
|
||||
|
||||
debug(message: string, ...args: any[]): void {
|
||||
if (this.globalLogger)
|
||||
this.globalLogger.info(message, ...args);
|
||||
this.debugStub(message, ...args);
|
||||
}
|
||||
|
||||
error(message: string, ...args: any[]): void {
|
||||
if (this.globalLogger)
|
||||
this.globalLogger.info(message, ...args);
|
||||
this.errorStub(message, ...args);
|
||||
}
|
||||
}
|
|
@ -7,14 +7,17 @@ import { MongoCollection } from "./MongoCollection";
|
|||
|
||||
describe("storage/mongo/MongoCollection", function () {
|
||||
let mongoCollectionStub: any;
|
||||
let mongoClientStub: MongoClientStub;
|
||||
let findStub: Sinon.SinonStub;
|
||||
let findOneStub: Sinon.SinonStub;
|
||||
let insertStub: Sinon.SinonStub;
|
||||
let updateStub: Sinon.SinonStub;
|
||||
let removeStub: Sinon.SinonStub;
|
||||
let countStub: Sinon.SinonStub;
|
||||
const COLLECTION_NAME = "collection";
|
||||
|
||||
before(function () {
|
||||
mongoClientStub = new MongoClientStub();
|
||||
mongoCollectionStub = Sinon.createStubInstance(require("mongodb").Collection as any);
|
||||
findStub = mongoCollectionStub.find as Sinon.SinonStub;
|
||||
findOneStub = mongoCollectionStub.findOne as Sinon.SinonStub;
|
||||
|
@ -22,15 +25,18 @@ describe("storage/mongo/MongoCollection", function () {
|
|||
updateStub = mongoCollectionStub.update as Sinon.SinonStub;
|
||||
removeStub = mongoCollectionStub.remove as Sinon.SinonStub;
|
||||
countStub = mongoCollectionStub.count as Sinon.SinonStub;
|
||||
mongoClientStub.collectionStub.returns(
|
||||
BluebirdPromise.resolve(mongoCollectionStub)
|
||||
);
|
||||
});
|
||||
|
||||
describe("find", function () {
|
||||
it("should find a document in the collection", function () {
|
||||
const collection = new MongoCollection(mongoCollectionStub);
|
||||
const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
|
||||
findStub.returns({
|
||||
sort: Sinon.stub().returns({
|
||||
limit: Sinon.stub().returns({
|
||||
toArray: Sinon.stub().yields(undefined, [])
|
||||
toArray: Sinon.stub().returns(BluebirdPromise.resolve([]))
|
||||
})
|
||||
})
|
||||
});
|
||||
|
@ -44,8 +50,8 @@ describe("storage/mongo/MongoCollection", function () {
|
|||
|
||||
describe("findOne", function () {
|
||||
it("should find one document in the collection", function () {
|
||||
const collection = new MongoCollection(mongoCollectionStub);
|
||||
findOneStub.yields(undefined, {});
|
||||
const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
|
||||
findOneStub.returns(BluebirdPromise.resolve({}));
|
||||
|
||||
return collection.findOne({ key: "KEY" })
|
||||
.then(function () {
|
||||
|
@ -56,8 +62,8 @@ describe("storage/mongo/MongoCollection", function () {
|
|||
|
||||
describe("insert", function () {
|
||||
it("should insert a document in the collection", function () {
|
||||
const collection = new MongoCollection(mongoCollectionStub);
|
||||
insertStub.yields(undefined, {});
|
||||
const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
|
||||
insertStub.returns(BluebirdPromise.resolve({}));
|
||||
|
||||
return collection.insert({ key: "KEY" })
|
||||
.then(function () {
|
||||
|
@ -68,8 +74,8 @@ describe("storage/mongo/MongoCollection", function () {
|
|||
|
||||
describe("update", function () {
|
||||
it("should update a document in the collection", function () {
|
||||
const collection = new MongoCollection(mongoCollectionStub);
|
||||
updateStub.yields(undefined, {});
|
||||
const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
|
||||
updateStub.returns(BluebirdPromise.resolve({}));
|
||||
|
||||
return collection.update({ key: "KEY" }, { key: "KEY", value: 1 })
|
||||
.then(function () {
|
||||
|
@ -80,8 +86,8 @@ describe("storage/mongo/MongoCollection", function () {
|
|||
|
||||
describe("remove", function () {
|
||||
it("should remove a document in the collection", function () {
|
||||
const collection = new MongoCollection(mongoCollectionStub);
|
||||
removeStub.yields(undefined, {});
|
||||
const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
|
||||
removeStub.returns(BluebirdPromise.resolve({}));
|
||||
|
||||
return collection.remove({ key: "KEY" })
|
||||
.then(function () {
|
||||
|
@ -92,8 +98,8 @@ describe("storage/mongo/MongoCollection", function () {
|
|||
|
||||
describe("count", function () {
|
||||
it("should count documents in the collection", function () {
|
||||
const collection = new MongoCollection(mongoCollectionStub);
|
||||
countStub.yields(undefined, {});
|
||||
const collection = new MongoCollection(COLLECTION_NAME, mongoClientStub);
|
||||
countStub.returns(BluebirdPromise.resolve({}));
|
||||
|
||||
return collection.count({ key: "KEY" })
|
||||
.then(function () {
|
||||
|
|
|
@ -1,44 +1,50 @@
|
|||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Bluebird = require("bluebird");
|
||||
import { ICollection } from "../ICollection";
|
||||
import MongoDB = require("mongodb");
|
||||
import { IMongoClient } from "../../connectors/mongo/IMongoClient";
|
||||
|
||||
|
||||
export class MongoCollection implements ICollection {
|
||||
private collection: MongoDB.Collection;
|
||||
private mongoClient: IMongoClient;
|
||||
private collectionName: string;
|
||||
|
||||
constructor(collection: MongoDB.Collection) {
|
||||
this.collection = collection;
|
||||
constructor(collectionName: string, mongoClient: IMongoClient) {
|
||||
this.collectionName = collectionName;
|
||||
this.mongoClient = mongoClient;
|
||||
}
|
||||
|
||||
find(query: any, sortKeys?: any, count?: number): BluebirdPromise<any> {
|
||||
const q = this.collection.find(query).sort(sortKeys).limit(count);
|
||||
const toArrayAsync = BluebirdPromise.promisify(q.toArray, { context: q });
|
||||
return toArrayAsync();
|
||||
private collection(): Bluebird<MongoDB.Collection> {
|
||||
return this.mongoClient.collection(this.collectionName);
|
||||
}
|
||||
|
||||
findOne(query: any): BluebirdPromise<any> {
|
||||
const findOneAsync = BluebirdPromise.promisify<any, any>(this.collection.findOne, { context: this.collection });
|
||||
return findOneAsync(query);
|
||||
find(query: any, sortKeys?: any, count?: number): Bluebird<any> {
|
||||
return this.collection()
|
||||
.then((collection) => collection.find(query).sort(sortKeys).limit(count))
|
||||
.then((query) => query.toArray());
|
||||
}
|
||||
|
||||
update(query: any, updateQuery: any, options?: any): BluebirdPromise<any> {
|
||||
const updateAsync = BluebirdPromise.promisify<any, any, any, any>(this.collection.update, { context: this.collection });
|
||||
return updateAsync(query, updateQuery, options);
|
||||
findOne(query: any): Bluebird<any> {
|
||||
return this.collection()
|
||||
.then((collection) => collection.findOne(query));
|
||||
}
|
||||
|
||||
remove(query: any): BluebirdPromise<any> {
|
||||
const removeAsync = BluebirdPromise.promisify<any, any>(this.collection.remove, { context: this.collection });
|
||||
return removeAsync(query);
|
||||
update(query: any, updateQuery: any, options?: any): Bluebird<any> {
|
||||
return this.collection()
|
||||
.then((collection) => collection.update(query, updateQuery, options));
|
||||
}
|
||||
|
||||
insert(document: any): BluebirdPromise<any> {
|
||||
const insertAsync = BluebirdPromise.promisify<any, any>(this.collection.insert, { context: this.collection });
|
||||
return insertAsync(document);
|
||||
remove(query: any): Bluebird<any> {
|
||||
return this.collection()
|
||||
.then((collection) => collection.remove(query));
|
||||
}
|
||||
|
||||
count(query: any): BluebirdPromise<number> {
|
||||
const countAsync = BluebirdPromise.promisify<any, any>(this.collection.count, { context: this.collection });
|
||||
return countAsync(query);
|
||||
insert(document: any): Bluebird<any> {
|
||||
return this.collection()
|
||||
.then((collection) => collection.insert(document));
|
||||
}
|
||||
|
||||
count(query: any): Bluebird<number> {
|
||||
return this.collection()
|
||||
.then((collection) => collection.count(query));
|
||||
}
|
||||
}
|
|
@ -14,6 +14,6 @@ export class MongoCollectionFactory implements ICollectionFactory {
|
|||
}
|
||||
|
||||
build(collectionName: string): ICollection {
|
||||
return new MongoCollection(this.mongoClient.collection(collectionName));
|
||||
return new MongoCollection(collectionName, this.mongoClient);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
require("chromedriver");
|
||||
import Environment = require('../environment');
|
||||
|
||||
const includes = [
|
||||
"docker-compose.yml",
|
||||
"example/compose/docker-compose.base.yml",
|
||||
"example/compose/mongo/docker-compose.yml",
|
||||
"example/compose/redis/docker-compose.yml",
|
||||
"example/compose/nginx/backend/docker-compose.yml",
|
||||
"example/compose/nginx/portal/docker-compose.yml",
|
||||
"example/compose/smtp/docker-compose.yml",
|
||||
"example/compose/httpbin/docker-compose.yml",
|
||||
"example/compose/ldap/docker-compose.yml"
|
||||
];
|
||||
|
||||
|
||||
before(function() {
|
||||
this.timeout(20000);
|
||||
this.environment = new Environment.Environment(includes);
|
||||
return this.environment.setup(5000);
|
||||
});
|
||||
|
||||
after(function() {
|
||||
this.timeout(30000);
|
||||
if(process.env.KEEP_ENV != "true") {
|
||||
return this.environment.cleanup();
|
||||
}
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
require("chromedriver");
|
||||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
import WithDriver from '../helpers/with-driver';
|
||||
import LoginAndRegisterTotp from '../helpers/login-and-register-totp';
|
||||
import LoginAs from '../helpers/login-as';
|
||||
import VisitPage from '../helpers/visit-page';
|
||||
|
||||
describe('Connection retry when mongo fails or restarts', function() {
|
||||
this.timeout(20000);
|
||||
WithDriver();
|
||||
|
||||
it('should be able to login after mongo restarts', function() {
|
||||
const that = this;
|
||||
return that.environment.stop_service("mongo")
|
||||
.then(() => that.environment.restart_service("authelia", 2000))
|
||||
.then(() => that.environment.restart_service("mongo"))
|
||||
.then(() => LoginAs(that.driver, "john"));
|
||||
})
|
||||
});
|
|
@ -6,36 +6,54 @@ function docker_compose(includes: string[]) {
|
|||
return `docker-compose ${compose_args}`;
|
||||
}
|
||||
|
||||
export function setup(includes: string[], setupTime: number = 2000): Bluebird<void> {
|
||||
const command = docker_compose(includes) + ' up -d'
|
||||
console.log('Starting up environment.');
|
||||
console.log('Running: %s', command);
|
||||
export class Environment {
|
||||
private includes: string[];
|
||||
constructor(includes: string[]) {
|
||||
this.includes = includes;
|
||||
}
|
||||
|
||||
private runCommand(command: string, timeout?: number): Bluebird<void> {
|
||||
return new Bluebird<void>(function(resolve, reject) {
|
||||
console.log('[ENVIRONMENT] Running: %s', command);
|
||||
exec(command, function(err, stdout, stderr) {
|
||||
if(err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
setTimeout(function() {
|
||||
resolve();
|
||||
}, setupTime);
|
||||
if(!timeout) resolve();
|
||||
else setTimeout(resolve, timeout);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function cleanup(includes: string[]): Bluebird<void> {
|
||||
const command = docker_compose(includes) + ' down';
|
||||
console.log('Shutting down environment.');
|
||||
console.log('Running: %s', command);
|
||||
|
||||
return new Bluebird<void>(function(resolve, reject) {
|
||||
exec(command, function(err, stdout, stderr) {
|
||||
if(err) {
|
||||
reject(err);
|
||||
return;
|
||||
setup(timeout?: number): Bluebird<void> {
|
||||
const command = docker_compose(this.includes) + ' up -d'
|
||||
console.log('[ENVIRONMENT] Starting up...');
|
||||
return this.runCommand(command, timeout);
|
||||
}
|
||||
|
||||
cleanup(): Bluebird<void> {
|
||||
const command = docker_compose(this.includes) + ' down'
|
||||
console.log('[ENVIRONMENT] Cleaning up...');
|
||||
return this.runCommand(command);
|
||||
}
|
||||
|
||||
stop_service(serviceName: string): Bluebird<void> {
|
||||
const command = docker_compose(this.includes) + ' stop ' + serviceName;
|
||||
console.log('[ENVIRONMENT] Stopping service %s...', serviceName);
|
||||
return this.runCommand(command);
|
||||
}
|
||||
|
||||
start_service(serviceName: string): Bluebird<void> {
|
||||
const command = docker_compose(this.includes) + ' start ' + serviceName;
|
||||
console.log('[ENVIRONMENT] Starting service %s...', serviceName);
|
||||
return this.runCommand(command);
|
||||
}
|
||||
|
||||
restart_service(serviceName: string, timeout?: number): Bluebird<void> {
|
||||
const command = docker_compose(this.includes) + ' restart ' + serviceName;
|
||||
console.log('[ENVIRONMENT] Restarting service %s...', serviceName);
|
||||
return this.runCommand(command, timeout);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -4,13 +4,15 @@ import BluebirdPromise = require("bluebird");
|
|||
import ChildProcess = require("child_process");
|
||||
import { UserDataStore } from "../../../server/src/lib/storage/UserDataStore";
|
||||
import { CollectionFactoryFactory } from "../../../server/src/lib/storage/CollectionFactoryFactory";
|
||||
import { MongoConnector } from "../../../server/src/lib/connectors/mongo/MongoConnector";
|
||||
import { IMongoClient } from "../../../server/src/lib/connectors/mongo/IMongoClient";
|
||||
import { TotpHandler } from "../../../server/src/lib/authentication/totp/TotpHandler";
|
||||
import Speakeasy = require("speakeasy");
|
||||
import Request = require("request-promise");
|
||||
import { TOTPSecret } from "../../../server/types/TOTPSecret";
|
||||
import Environment = require("../../environment");
|
||||
import { MongoClient } from "../../../server/src/lib/connectors/mongo/MongoClient";
|
||||
import { GlobalLogger } from "../../../server/src/lib/logging/GlobalLogger";
|
||||
import { GlobalLoggerStub } from "../../../server/src/lib/logging/GlobalLoggerStub.spec";
|
||||
|
||||
setDefaultTimeout(30 * 1000);
|
||||
|
||||
|
@ -28,12 +30,14 @@ const includes = [
|
|||
"example/compose/ldap/docker-compose.yml"
|
||||
]
|
||||
|
||||
const environment = new Environment.Environment(includes);
|
||||
|
||||
BeforeAll(function() {
|
||||
return Environment.setup(includes, 10000);
|
||||
return environment.setup(10000);
|
||||
});
|
||||
|
||||
AfterAll(function() {
|
||||
return Environment.cleanup(includes)
|
||||
return environment.cleanup()
|
||||
});
|
||||
|
||||
Before(function () {
|
||||
|
@ -99,19 +103,16 @@ declareNeedsConfiguration("totp_issuer", createCustomTotpIssuerConfiguration);
|
|||
|
||||
function registerUser(context: any, username: string) {
|
||||
let secret: TOTPSecret;
|
||||
const mongoConnector = new MongoConnector("mongodb://localhost:27017");
|
||||
return mongoConnector.connect("authelia")
|
||||
.then(function (mongoClient: IMongoClient) {
|
||||
const mongoClient = new MongoClient("mongodb://localhost:27017", "authelia", new GlobalLoggerStub());
|
||||
const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient);
|
||||
const userDataStore = new UserDataStore(collectionFactory);
|
||||
|
||||
const generator = new TotpHandler(Speakeasy);
|
||||
secret = generator.generate("user", "authelia.com");
|
||||
return userDataStore.saveTOTPSecret(username, secret);
|
||||
})
|
||||
return userDataStore.saveTOTPSecret(username, secret)
|
||||
.then(function () {
|
||||
context.totpSecrets["REGISTERED"] = secret.base32;
|
||||
return mongoConnector.close();
|
||||
return mongoClient.close();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ import VisitPage from "./visit-page";
|
|||
import FillLoginPageAndClick from './fill-login-page-and-click';
|
||||
import RegisterTotp from './register-totp';
|
||||
import WaitRedirected from './wait-redirected';
|
||||
import LoginAs from './login-as';
|
||||
|
||||
export default function(driver: any, user: string) {
|
||||
return VisitPage(driver, "https://login.example.com:8080/")
|
||||
.then(() => FillLoginPageAndClick(driver, user, "password"))
|
||||
export default function(driver: any, user: string, email?: boolean) {
|
||||
return LoginAs(driver, user)
|
||||
.then(() => WaitRedirected(driver, "https://login.example.com:8080/secondfactor"))
|
||||
.then(() => RegisterTotp(driver));
|
||||
.then(() => RegisterTotp(driver, email));
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import VisitPage from "./visit-page";
|
||||
import FillLoginPageAndClick from './fill-login-page-and-click';
|
||||
import RegisterTotp from './register-totp';
|
||||
import WaitRedirected from './wait-redirected';
|
||||
|
||||
export default function(driver: any, user: string) {
|
||||
return VisitPage(driver, "https://login.example.com:8080/")
|
||||
.then(() => FillLoginPageAndClick(driver, user, "password"));
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
import Fs = require("fs");
|
||||
import Request = require("request-promise");
|
||||
|
||||
function retrieveValidationLinkFromNotificationFile(): Bluebird<string> {
|
||||
return Bluebird.promisify(Fs.readFile)("/tmp/authelia/notification.txt")
|
||||
|
@ -12,13 +13,35 @@ function retrieveValidationLinkFromNotificationFile(): Bluebird<string> {
|
|||
});
|
||||
};
|
||||
|
||||
export default function(driver: any): Bluebird<string> {
|
||||
function retrieveValidationLinkFromEmail(): Bluebird<string> {
|
||||
return Request({
|
||||
method: "GET",
|
||||
uri: "http://localhost:8085/messages",
|
||||
json: true
|
||||
})
|
||||
.then(function (data: any) {
|
||||
const messageId = data[data.length - 1].id;
|
||||
return Request({
|
||||
method: "GET",
|
||||
uri: `http://localhost:8085/messages/${messageId}.html`
|
||||
});
|
||||
})
|
||||
.then(function (data: any) {
|
||||
const regexp = new RegExp(/<a href="(.+)" class="button">Continue<\/a>/);
|
||||
const match = regexp.exec(data);
|
||||
const link = match[1];
|
||||
return Bluebird.resolve(link);
|
||||
});
|
||||
};
|
||||
|
||||
export default function(driver: any, email?: boolean): Bluebird<string> {
|
||||
return driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.className("register-totp")), 5000)
|
||||
.then(function () {
|
||||
return driver.findElement(SeleniumWebdriver.By.className("register-totp")).click();
|
||||
})
|
||||
.then(function () {
|
||||
return retrieveValidationLinkFromNotificationFile();
|
||||
if(email) return retrieveValidationLinkFromEmail();
|
||||
else return retrieveValidationLinkFromNotificationFile();
|
||||
})
|
||||
.then(function (link: string) {
|
||||
return driver.get(link);
|
||||
|
|
|
@ -11,10 +11,11 @@ const includes = [
|
|||
|
||||
before(function() {
|
||||
this.timeout(20000);
|
||||
return Environment.setup(includes);
|
||||
this.environment = new Environment.Environment(includes);
|
||||
return this.environment.setup(2000);
|
||||
});
|
||||
|
||||
after(function() {
|
||||
this.timeout(30000);
|
||||
return Environment.cleanup(includes);
|
||||
return this.environment.cleanup();
|
||||
});
|
Loading…
Reference in New Issue