From 524cc11b3fe1c096fbdeca0e276f2717f4e6af34 Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Fri, 10 Nov 2017 16:29:19 +0100 Subject: [PATCH 1/3] Added possibility to configure the application with a config file or with command line arguments. Currently only selection of the database system and options for those systems are available. --- source/calendarwebapp/authenticator.d | 10 ++- source/calendarwebapp/configuration.d | 125 +++++++++++++++++++++----- source/calendarwebapp/event.d | 10 ++- 3 files changed, 114 insertions(+), 31 deletions(-) diff --git a/source/calendarwebapp/authenticator.d b/source/calendarwebapp/authenticator.d index 09d8d56..84ed9cb 100644 --- a/source/calendarwebapp/authenticator.d +++ b/source/calendarwebapp/authenticator.d @@ -92,6 +92,7 @@ private: @Autowire MySQLPool pool; @Autowire PasswordHasher passwordHasher; + @Value("mysql.table.users") string usersTableName; public: Nullable!AuthInfo checkUser(string username, string password) @trusted @@ -100,7 +101,8 @@ public: scope (exit) cn.close(); auto prepared = cn.prepare( - "SELECT id, username, passwordHash, privilege FROM users WHERE username = ?"); + "SELECT id, username, passwordHash, privilege FROM " + ~ usersTableName ~ " WHERE username = ?"); prepared.setArg(0, username); auto result = prepared.query(); /* checkHash should be called using vibe.core.concurrency.async to @@ -123,7 +125,7 @@ public: scope (exit) cn.close; auto prepared = cn.prepare( - "INSERT INTO users (username, passwordHash, privilege) VALUES(?, ?, ?)"); + "INSERT INTO " ~ usersTableName ~ " (username, passwordHash, privilege) VALUES(?, ?, ?)"); prepared.setArgs(authInfo.username, authInfo.passwordHash, authInfo.privilege.to!uint); prepared.exec(); } @@ -136,7 +138,7 @@ public: auto cn = pool.lockConnection(); scope (exit) cn.close; - auto prepared = cn.prepare("SELECT id, username, passwordHash, privilege FROM users"); + auto prepared = cn.prepare("SELECT id, username, passwordHash, privilege FROM " ~ usersTableName ~ ""); return prepared.querySet.map!(r => toAuthInfo(r)).inputRangeObject; } @@ -145,7 +147,7 @@ public: auto cn = pool.lockConnection(); scope (exit) cn.close; - auto prepared = cn.prepare("DELETE FROM users WHERE id = ?"); + auto prepared = cn.prepare("DELETE FROM " ~ usersTableName ~ " WHERE id = ?"); prepared.setArg(0, id.to!uint); prepared.exec(); } diff --git a/source/calendarwebapp/configuration.d b/source/calendarwebapp/configuration.d index ddf38af..a010cc4 100644 --- a/source/calendarwebapp/configuration.d +++ b/source/calendarwebapp/configuration.d @@ -3,32 +3,50 @@ module calendarwebapp.configuration; import botan.rng.auto_rng : AutoSeededRNG; import botan.rng.rng : RandomNumberGenerator; -import calendarwebapp.authenticator : Authenticator, MongoDBAuthenticator, - MySQLAuthenticator; +import calendarwebapp.authenticator : Authenticator; import calendarwebapp.calendarwebapp : CalendarWebapp; -import calendarwebapp.event : EventStore, MongoDBEventStore, MySQLEventStore; +import calendarwebapp.event : EventStore; import calendarwebapp.passhash : BcryptPasswordHasher, PasswordHasher; -import mysql : MySQLPool; - import poodinis; -import vibe.db.mongo.client : MongoClient; +import vibe.core.log : logInfo; import vibe.db.mongo.collection : MongoCollection; -import vibe.db.mongo.mongo : connectMongoDB; class Context : ApplicationContext { public: override void registerDependencies(shared(DependencyContainer) container) { - auto mongoClient = connectMongoDB("localhost"); - auto pool = new MySQLPool("localhost", "username", "password", "CalendarWebapp"); - container.register!MySQLPool.existingInstance(pool); - container.register!MongoClient.existingInstance(mongoClient); - container.register!(EventStore, MySQLEventStore); - container.register!(Authenticator, MySQLAuthenticator); + container.register!(ValueInjector!Arguments, AppArgumentsInjector); + auto arguments = container.resolve!(AppArgumentsInjector).get(""); + final switch (arguments.database) with (DatabaseArgument) + { + case mongodb: + import vibe.db.mongo.client : MongoClient; + import vibe.db.mongo.mongo : connectMongoDB; + import calendarwebapp.authenticator : MongoDBAuthenticator; + import calendarwebapp.event : MongoDBEventStore; + auto mongoClient = connectMongoDB(arguments.mongodb.host); + container.register!MongoClient.existingInstance(mongoClient); + container.register!(EventStore, MongoDBEventStore!()); + container.register!(Authenticator, MongoDBAuthenticator!()); + logInfo("Using MongoDB as database system"); + break; + case mysql: + import mysql : MySQLPool; + import calendarwebapp.authenticator : MySQLAuthenticator; + import calendarwebapp.event : MySQLEventStore; + + auto pool = new MySQLPool(arguments.mysql.host, arguments.mysql.username, + arguments.mysql.password, arguments.mysql.database); + container.register!MySQLPool.existingInstance(pool); + container.register!(EventStore, MySQLEventStore); + container.register!(Authenticator, MySQLAuthenticator); + logInfo("Using MySQL as database system"); + break; + } container.register!(PasswordHasher, BcryptPasswordHasher); container.register!(RandomNumberGenerator, AutoSeededRNG); container.register!CalendarWebapp; @@ -41,19 +59,18 @@ class StringInjector : ValueInjector!string { private: string[string] config; + @Value() Arguments arguments; + bool initialized = false; public: - this() const @safe pure nothrow - { - // dfmt off - config = ["Database name" : "CalendarWebapp", - "Users collection name": "users", - "Events collection name" : "events"]; - // dfmt on - } - override string get(string key) const @safe pure nothrow + override string get(string key) @safe nothrow { + if (!initialized) + { + config = ["MongoDB database name" : arguments.mongodb.database, + "mysql.table.users" : "users", "mysql.table.events" : "events"]; + } return config[key]; } } @@ -61,8 +78,10 @@ public: class MongoCollectionInjector : ValueInjector!MongoCollection { private: + import vibe.db.mongo.client : MongoClient; + @Autowire MongoClient mongoClient; - @Value("Database name") + @Value("MongoDB database name") string databaseName; public: @@ -71,3 +90,63 @@ public: return mongoClient.getCollection(databaseName ~ "." ~ key); } } + +class AppArgumentsInjector : ValueInjector!Arguments +{ +private: + Arguments arguments; +public: + + this() + { + import vibe.core.args : readOption; + + readOption("database", &arguments.database, "The database system to use."); + readOption("mongodb.host", &arguments.mongodb.host, + "The host of the MongoDB instance to use."); + readOption("mongodb.database", &arguments.mongodb.database, + "The name of the MongoDB database to use."); + readOption("mysql.host", &arguments.mysql.host, "The host of the MySQL instance to use."); + readOption("mysql.username", &arguments.mysql.username, + "The username to use for logging into the MySQL instance."); + readOption("mysql.password", &arguments.mysql.password, + "The password to use for logging into the MySQL instance."); + readOption("mysql.database", &arguments.mysql.database, + "The name of the MySQL database to use."); + } + + override Arguments get(string key) @safe + { + import std.exception : enforce; + + enforce(key == "", "There is only one instance of Arguments, to inject it use @Value()."); + return arguments; + } +} + +enum DatabaseArgument +{ + mongodb, + mysql +} + +struct MySQLArguments +{ + string host = "localhost"; + string username = "username"; + string password = "password"; + string database = "CalendarWebapp"; +} + +struct MongoDBArguments +{ + string host = "localhost"; + string database = "CalendarWebapp"; +} + +struct Arguments +{ + DatabaseArgument database = DatabaseArgument.mongodb; + MySQLArguments mysql; + MongoDBArguments mongodb; +} diff --git a/source/calendarwebapp/event.d b/source/calendarwebapp/event.d index bde695d..587c89a 100644 --- a/source/calendarwebapp/event.d +++ b/source/calendarwebapp/event.d @@ -73,6 +73,8 @@ class MySQLEventStore : EventStore private: import mysql; + @Value("mysql.table.events") string eventsTableName; + public: Event getEvent(string id) { @@ -80,7 +82,7 @@ public: scope (exit) cn.close; auto prepared = cn.prepare( - "SELECT id begin end name description type shout FROM events WHERE id = ?"); + "SELECT id begin end name description type shout FROM " ~ eventsTableName ~ " WHERE id = ?"); prepared.setArg(0, id.to!uint); return toEvent(prepared.query.front); } @@ -91,7 +93,7 @@ public: scope (exit) cn.close; auto prepared = cn.prepare( - "SELECT id, begin, end, name, description, type, shout FROM events"); + "SELECT id, begin, end, name, description, type, shout FROM " ~ eventsTableName ~ ""); return prepared.querySet.map!(r => toEvent(r)).inputRangeObject; } @@ -101,7 +103,7 @@ public: scope (exit) cn.close; auto prepared = cn.prepare( - "INSERT INTO events (begin, end, name, description, type, shout) VALUES(?, ?, ?, ?, ?, ?)"); + "INSERT INTO " ~ eventsTableName ~ " (begin, end, name, description, type, shout) VALUES(?, ?, ?, ?, ?, ?)"); prepared.setArgs(event.begin, event.end, event.name, event.description, event.type.to!uint, event.shout); prepared.exec(); @@ -119,7 +121,7 @@ public: auto cn = pool.lockConnection(); scope (exit) cn.close; - auto prepared = cn.prepare("DELETE FROM events WHERE id = ?"); + auto prepared = cn.prepare("DELETE FROM " ~ eventsTableName ~ " WHERE id = ?"); prepared.setArg(0, id.to!uint); prepared.exec(); } From 5bcbd06a52a443d4425a0e71697a862445e02ad2 Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Fri, 10 Nov 2017 17:09:44 +0100 Subject: [PATCH 2/3] Only register a MongoCollectionInjector, when using MongoDB --- source/calendarwebapp/configuration.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/calendarwebapp/configuration.d b/source/calendarwebapp/configuration.d index a010cc4..3a7cf25 100644 --- a/source/calendarwebapp/configuration.d +++ b/source/calendarwebapp/configuration.d @@ -32,6 +32,7 @@ public: container.register!MongoClient.existingInstance(mongoClient); container.register!(EventStore, MongoDBEventStore!()); container.register!(Authenticator, MongoDBAuthenticator!()); + container.register!(ValueInjector!MongoCollection, MongoCollectionInjector); logInfo("Using MongoDB as database system"); break; case mysql: @@ -51,7 +52,6 @@ public: container.register!(RandomNumberGenerator, AutoSeededRNG); container.register!CalendarWebapp; container.register!(ValueInjector!string, StringInjector); - container.register!(ValueInjector!MongoCollection, MongoCollectionInjector); } } From 5e4e32c025967018e2c5a01071cbe5a03c281b96 Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Thu, 23 Nov 2017 21:40:48 +0100 Subject: [PATCH 3/3] do password hashing asynchronously --- source/calendarwebapp/authenticator.d | 17 ++++++++++------- source/calendarwebapp/calendarwebapp.d | 9 +++++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/source/calendarwebapp/authenticator.d b/source/calendarwebapp/authenticator.d index 0322398..9b41013 100644 --- a/source/calendarwebapp/authenticator.d +++ b/source/calendarwebapp/authenticator.d @@ -30,14 +30,15 @@ private: public: Nullable!AuthInfo checkUser(string username, string password) @safe { - auto result = users.findOne(["username" : username]); - /* checkHash should be called using vibe.core.concurrency.async to - avoid blocking, but https://github.com/vibe-d/vibe.d/issues/1521 is - blocking this */ + import vibe.core.concurrency : async; + + immutable result = users.findOne(["username" : username]); + if (result != Bson(null)) { auto authInfo = result.deserializeBson!AuthInfo; - if (passwordHasher.checkHash(password, authInfo.passwordHash)) + if ((()@trusted => async(() => passwordHasher.checkHash(password, + authInfo.passwordHash)).getResult)()) { return authInfo.nullable; } @@ -94,6 +95,8 @@ private: public: Nullable!AuthInfo checkUser(string username, string password) @trusted { + import vibe.core.concurrency : async; + auto cn = pool.lockConnection(); scope (exit) cn.close(); @@ -107,7 +110,7 @@ public: if (!result.empty) { auto authInfo = toAuthInfo(result.front); - if (passwordHasher.checkHash(password, authInfo.passwordHash)) + if (async(() => passwordHasher.checkHash(password, authInfo.passwordHash)).getResult) { return authInfo.nullable; } @@ -150,7 +153,7 @@ public: private: - AuthInfo toAuthInfo(Row r) + AuthInfo toAuthInfo(in Row r) { import std.conv : to; diff --git a/source/calendarwebapp/calendarwebapp.d b/source/calendarwebapp/calendarwebapp.d index fdc958f..359da88 100644 --- a/source/calendarwebapp/calendarwebapp.d +++ b/source/calendarwebapp/calendarwebapp.d @@ -65,7 +65,6 @@ public: render!("createevent.dt", _error, authInfo); } - @auth(Role.user | Role.admin) @errorDisplay!getCreateevent void postCreateevent(Date begin, Nullable!Date end, string description, string name, EventType type, bool shout) { @@ -109,8 +108,10 @@ public: @auth(Role.admin) @errorDisplay!getCreateuser void postCreateuser(string username, string password, Privilege role) { + import vibe.core.concurrency : async; + authenticator.addUser(AuthInfo("", username, - passwordHasher.generateHash(password), role)); + async(() => passwordHasher.generateHash(password)).getResult, role)); redirect("/users"); } @@ -121,8 +122,8 @@ private: string field; } - SessionVar!(AuthInfo, "authInfo") authInfo = AuthInfo("", - string.init, string.init, Privilege.None); + SessionVar!(AuthInfo, "authInfo") authInfo = AuthInfo("", string.init, + string.init, Privilege.None); @Autowire EventStore eventStore; @Autowire Authenticator authenticator;