2017-09-17 17:52:41 +02:00
|
|
|
module calendarwebapp.authenticator;
|
|
|
|
|
2017-10-27 18:58:46 +02:00
|
|
|
import calendarwebapp.passhash : PasswordHasher;
|
|
|
|
|
2017-09-17 17:52:41 +02:00
|
|
|
import poodinis;
|
|
|
|
|
2017-11-09 02:24:15 +01:00
|
|
|
import std.conv : to;
|
2017-10-27 17:09:55 +02:00
|
|
|
import std.range : InputRange;
|
2017-10-26 20:08:16 +02:00
|
|
|
import std.typecons : nullable, Nullable;
|
|
|
|
|
2017-10-27 17:09:55 +02:00
|
|
|
import vibe.data.bson;
|
2017-11-09 02:24:15 +01:00
|
|
|
|
2017-09-17 17:52:41 +02:00
|
|
|
import vibe.db.mongo.collection : MongoCollection;
|
|
|
|
|
|
|
|
interface Authenticator
|
|
|
|
{
|
2017-10-26 20:08:16 +02:00
|
|
|
Nullable!AuthInfo checkUser(string username, string password) @safe;
|
2017-11-09 02:24:15 +01:00
|
|
|
void addUser(AuthInfo authInfo);
|
|
|
|
InputRange!AuthInfo getAllUsers();
|
|
|
|
void removeUser(string id);
|
2017-09-17 17:52:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class MongoDBAuthenticator(Collection = MongoCollection) : Authenticator
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
@Value("users")
|
|
|
|
Collection users;
|
2017-10-27 18:58:46 +02:00
|
|
|
@Autowire PasswordHasher passwordHasher;
|
2017-09-17 17:52:41 +02:00
|
|
|
|
|
|
|
public:
|
2017-10-26 20:08:16 +02:00
|
|
|
Nullable!AuthInfo checkUser(string username, string password) @safe
|
2017-09-17 17:52:41 +02:00
|
|
|
{
|
2017-10-15 16:43:39 +02:00
|
|
|
auto result = users.findOne(["username" : username]);
|
2017-10-27 18:58:46 +02:00
|
|
|
/* checkHash should be called using vibe.core.concurrency.async to
|
2017-10-15 16:43:39 +02:00
|
|
|
avoid blocking, but https://github.com/vibe-d/vibe.d/issues/1521 is
|
|
|
|
blocking this */
|
2017-10-26 20:08:16 +02:00
|
|
|
if (result != Bson(null))
|
|
|
|
{
|
|
|
|
auto authInfo = result.deserializeBson!AuthInfo;
|
2017-10-27 18:58:46 +02:00
|
|
|
if (passwordHasher.checkHash(password, authInfo.passwordHash))
|
2017-10-26 20:08:16 +02:00
|
|
|
{
|
|
|
|
return authInfo.nullable;
|
|
|
|
}
|
|
|
|
}
|
2017-10-27 17:09:55 +02:00
|
|
|
return Nullable!AuthInfo.init;
|
2017-09-17 17:52:41 +02:00
|
|
|
}
|
2017-10-27 17:09:55 +02:00
|
|
|
|
|
|
|
void addUser(AuthInfo authInfo) @safe
|
|
|
|
{
|
2017-11-09 02:24:15 +01:00
|
|
|
import std.conv : ConvException;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (!BsonObjectID.fromString(authInfo.id).valid)
|
|
|
|
throw new ConvException("invalid BsonObjectID.");
|
|
|
|
}
|
|
|
|
catch (ConvException)
|
|
|
|
{
|
|
|
|
authInfo.id = BsonObjectID.generate.to!string;
|
|
|
|
}
|
2017-10-27 17:09:55 +02:00
|
|
|
|
|
|
|
users.insert(authInfo.serializeToBson);
|
|
|
|
}
|
|
|
|
|
|
|
|
InputRange!AuthInfo getAllUsers() @safe
|
|
|
|
{
|
|
|
|
import std.algorithm : map;
|
|
|
|
import std.range : inputRangeObject;
|
|
|
|
|
|
|
|
return users.find().map!(deserializeBson!AuthInfo).inputRangeObject;
|
|
|
|
}
|
|
|
|
|
2017-11-09 02:24:15 +01:00
|
|
|
void removeUser(string id) @safe
|
2017-10-27 17:09:55 +02:00
|
|
|
{
|
|
|
|
users.remove(["_id" : id]);
|
2017-09-17 17:52:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-27 18:03:55 +02:00
|
|
|
enum Privilege
|
2017-10-26 20:08:16 +02:00
|
|
|
{
|
2017-10-27 18:03:55 +02:00
|
|
|
None,
|
2017-10-26 20:08:16 +02:00
|
|
|
User,
|
|
|
|
Admin
|
2017-09-17 17:52:41 +02:00
|
|
|
}
|
|
|
|
|
2017-10-30 02:31:48 +01:00
|
|
|
class MySQLAuthenticator : Authenticator
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
import mysql;
|
|
|
|
|
|
|
|
@Autowire MySQLPool pool;
|
2017-11-09 02:24:15 +01:00
|
|
|
@Autowire PasswordHasher passwordHasher;
|
2017-11-10 16:29:19 +01:00
|
|
|
@Value("mysql.table.users") string usersTableName;
|
2017-10-30 02:31:48 +01:00
|
|
|
|
|
|
|
public:
|
2017-11-09 02:24:15 +01:00
|
|
|
Nullable!AuthInfo checkUser(string username, string password) @trusted
|
2017-10-30 02:31:48 +01:00
|
|
|
{
|
|
|
|
auto cn = pool.lockConnection();
|
|
|
|
scope (exit)
|
|
|
|
cn.close();
|
2017-11-09 02:24:15 +01:00
|
|
|
auto prepared = cn.prepare(
|
2017-11-10 16:29:19 +01:00
|
|
|
"SELECT id, username, passwordHash, privilege FROM "
|
|
|
|
~ usersTableName ~ " WHERE username = ?");
|
2017-10-30 02:31:48 +01:00
|
|
|
prepared.setArg(0, username);
|
|
|
|
auto result = prepared.query();
|
2017-11-09 02:24:15 +01:00
|
|
|
/* checkHash should be called using vibe.core.concurrency.async to
|
2017-10-30 02:31:48 +01:00
|
|
|
avoid blocking, but https://github.com/vibe-d/vibe.d/issues/1521 is
|
|
|
|
blocking this */
|
2017-11-09 02:24:15 +01:00
|
|
|
if (!result.empty)
|
|
|
|
{
|
|
|
|
auto authInfo = toAuthInfo(result.front);
|
|
|
|
if (passwordHasher.checkHash(password, authInfo.passwordHash))
|
|
|
|
{
|
|
|
|
return authInfo.nullable;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Nullable!AuthInfo.init;
|
|
|
|
}
|
|
|
|
|
|
|
|
void addUser(AuthInfo authInfo)
|
|
|
|
{
|
|
|
|
auto cn = pool.lockConnection();
|
|
|
|
scope (exit)
|
|
|
|
cn.close;
|
|
|
|
auto prepared = cn.prepare(
|
2017-11-10 16:29:19 +01:00
|
|
|
"INSERT INTO " ~ usersTableName ~ " (username, passwordHash, privilege) VALUES(?, ?, ?)");
|
2017-11-09 02:24:15 +01:00
|
|
|
prepared.setArgs(authInfo.username, authInfo.passwordHash, authInfo.privilege.to!uint);
|
|
|
|
prepared.exec();
|
|
|
|
}
|
|
|
|
|
|
|
|
InputRange!AuthInfo getAllUsers()
|
|
|
|
{
|
|
|
|
import std.algorithm : map;
|
|
|
|
import std.range : inputRangeObject;
|
|
|
|
|
|
|
|
auto cn = pool.lockConnection();
|
|
|
|
scope (exit)
|
|
|
|
cn.close;
|
2017-11-10 16:29:19 +01:00
|
|
|
auto prepared = cn.prepare("SELECT id, username, passwordHash, privilege FROM " ~ usersTableName ~ "");
|
2017-11-09 02:24:15 +01:00
|
|
|
return prepared.querySet.map!(r => toAuthInfo(r)).inputRangeObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
void removeUser(string id)
|
|
|
|
{
|
|
|
|
auto cn = pool.lockConnection();
|
|
|
|
scope (exit)
|
|
|
|
cn.close;
|
2017-11-10 16:29:19 +01:00
|
|
|
auto prepared = cn.prepare("DELETE FROM " ~ usersTableName ~ " WHERE id = ?");
|
2017-11-09 02:24:15 +01:00
|
|
|
prepared.setArg(0, id.to!uint);
|
|
|
|
prepared.exec();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
AuthInfo toAuthInfo(Row r)
|
|
|
|
{
|
|
|
|
import std.conv : to;
|
|
|
|
|
|
|
|
AuthInfo authInfo;
|
|
|
|
authInfo.id = r[0].get!uint.to!string;
|
|
|
|
authInfo.username = r[1].get!string;
|
|
|
|
authInfo.passwordHash = r[2].get!string;
|
|
|
|
authInfo.privilege = r[3].get!uint.to!Privilege;
|
|
|
|
return authInfo;
|
2017-10-30 02:31:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-17 17:52:41 +02:00
|
|
|
struct AuthInfo
|
|
|
|
{
|
2017-10-26 20:08:16 +02:00
|
|
|
import vibe.data.serialization : name;
|
|
|
|
|
2017-11-09 02:24:15 +01:00
|
|
|
@name("_id") string id;
|
2017-10-26 20:08:16 +02:00
|
|
|
string username;
|
|
|
|
string passwordHash;
|
2017-10-27 18:03:55 +02:00
|
|
|
Privilege privilege;
|
2017-10-26 20:08:16 +02:00
|
|
|
|
2017-10-27 15:13:02 +02:00
|
|
|
mixin(generateAuthMethods);
|
2017-10-26 20:08:16 +02:00
|
|
|
|
2017-10-27 15:13:02 +02:00
|
|
|
private:
|
|
|
|
static string generateAuthMethods() pure @safe
|
2017-10-26 20:08:16 +02:00
|
|
|
{
|
2017-10-27 15:13:02 +02:00
|
|
|
import std.conv : to;
|
|
|
|
import std.format : format;
|
|
|
|
import std.traits : EnumMembers;
|
|
|
|
|
|
|
|
string ret;
|
2017-10-27 18:03:55 +02:00
|
|
|
foreach (member; EnumMembers!Privilege)
|
2017-10-27 15:13:02 +02:00
|
|
|
{
|
|
|
|
ret ~= q{
|
|
|
|
bool is%s() const pure @safe nothrow
|
|
|
|
{
|
2017-10-27 18:03:55 +02:00
|
|
|
return privilege == Privilege.%s;
|
2017-10-27 15:13:02 +02:00
|
|
|
}
|
|
|
|
}.format(member.to!string, member.to!string);
|
|
|
|
}
|
|
|
|
return ret;
|
2017-10-26 20:08:16 +02:00
|
|
|
}
|
2017-09-17 17:52:41 +02:00
|
|
|
}
|