diff --git a/source/calendarwebapp/authenticator.d b/source/calendarwebapp/authenticator.d index 0322398..6456c64 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; } @@ -90,15 +91,19 @@ private: @Autowire MySQLPool pool; @Autowire PasswordHasher passwordHasher; + @Value("mysql.table.users") string usersTableName; public: Nullable!AuthInfo checkUser(string username, string password) @trusted { + import vibe.core.concurrency : async; + auto cn = pool.lockConnection(); 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 @@ -107,7 +112,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; } @@ -121,7 +126,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(); } @@ -134,7 +139,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; } @@ -143,14 +148,14 @@ 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(); } 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; diff --git a/source/calendarwebapp/configuration.d b/source/calendarwebapp/configuration.d index 1ef363d..aa3f530 100644 --- a/source/calendarwebapp/configuration.d +++ b/source/calendarwebapp/configuration.d @@ -1,34 +1,53 @@ module calendarwebapp.configuration; -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 : PasswordHasher, SHA256PasswordHasher; -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!()); + container.register!(ValueInjector!MongoCollection, MongoCollectionInjector); + 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, SHA256PasswordHasher); container.register!CalendarWebapp; container.register!(ValueInjector!string, StringInjector); - container.register!(ValueInjector!MongoCollection, MongoCollectionInjector); } } @@ -36,19 +55,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]; } } @@ -56,8 +74,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: @@ -66,3 +86,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 e373a5e..d7fb1a9 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(); @@ -123,7 +125,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(); }