From afd43e255399064783c8c04ee041a2f600235847 Mon Sep 17 00:00:00 2001 From: ChronosX88 Date: Thu, 8 Oct 2020 21:10:44 +0400 Subject: [PATCH] Implement DefaultAuthProvider plugin --- .../DefaultAuthProvider.csproj | 14 +++ .../DefaultAuthProvider/PasswordHasher.cs | 51 +++++++++++ .../DefaultAuthProvider/Plugin.cs | 87 +++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 src/ZirconiumPlugins/DefaultAuthProvider/DefaultAuthProvider.csproj create mode 100644 src/ZirconiumPlugins/DefaultAuthProvider/PasswordHasher.cs create mode 100644 src/ZirconiumPlugins/DefaultAuthProvider/Plugin.cs diff --git a/src/ZirconiumPlugins/DefaultAuthProvider/DefaultAuthProvider.csproj b/src/ZirconiumPlugins/DefaultAuthProvider/DefaultAuthProvider.csproj new file mode 100644 index 0000000..6b2d2e9 --- /dev/null +++ b/src/ZirconiumPlugins/DefaultAuthProvider/DefaultAuthProvider.csproj @@ -0,0 +1,14 @@ + + + + netcoreapp3.1 + + + + + runtime + + + + + diff --git a/src/ZirconiumPlugins/DefaultAuthProvider/PasswordHasher.cs b/src/ZirconiumPlugins/DefaultAuthProvider/PasswordHasher.cs new file mode 100644 index 0000000..6cf8942 --- /dev/null +++ b/src/ZirconiumPlugins/DefaultAuthProvider/PasswordHasher.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using Konscious.Security.Cryptography; + +namespace DefaultAuthProvider +{ + public static class PasswordHasher + { + // First item is password hash, and second item is a salt + public static Tuple CreatePasswordHash(string password) + { + var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)); + + var salt = createSalt(); + argon2.Salt = salt; + argon2.DegreeOfParallelism = 2; // four cores + argon2.Iterations = 1; + argon2.MemorySize = 64 * 1024; // 1 GB + + return new Tuple(argon2.GetBytes(32), salt); + } + + private static byte[] _createPasswordHashWithCustomSalt(string password, byte[] salt) + { + var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)); + + argon2.Salt = salt; + argon2.DegreeOfParallelism = 2; // four cores + argon2.Iterations = 1; + argon2.MemorySize = 64 * 1024; // 1 GB + + return argon2.GetBytes(32); + } + + private static byte[] createSalt() + { + var buffer = new byte[16]; + var rng = new RNGCryptoServiceProvider(); + rng.GetBytes(buffer); + return buffer; + } + + public static bool VerifyHash(string password, byte[] salt, byte[] hash) + { + var newHash = _createPasswordHashWithCustomSalt(password, salt); + return hash.SequenceEqual(newHash); + } + } +} \ No newline at end of file diff --git a/src/ZirconiumPlugins/DefaultAuthProvider/Plugin.cs b/src/ZirconiumPlugins/DefaultAuthProvider/Plugin.cs new file mode 100644 index 0000000..e0cee54 --- /dev/null +++ b/src/ZirconiumPlugins/DefaultAuthProvider/Plugin.cs @@ -0,0 +1,87 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver; +using Zirconium.Core; +using Zirconium.Core.Plugins.Interfaces; +using System.Linq; + +namespace DefaultAuthProvider +{ + public class DefaultAuthProviderPlugin : IPluginAPI + { + public string GetPluginUniqueName() + { + return "DefaultAuthProvider"; + } + + public void Initialize(IPluginHostAPI hostModuleAPI) + { + var db = hostModuleAPI.GetRawDatabase(); + hostModuleAPI.ProvideAuth(new DefaultAuthProvider(db)); + } + } + + public class DefaultAuthProvider : IAuthProvider + { + public class User + { + [BsonId] + public ObjectId Id { get; set; } + public string Username { get; set; } + public byte[] Password { get; set; } + public byte[] Salt { get; set; } + } + + private IMongoDatabase _db; + private IMongoCollection usersCol; + + public DefaultAuthProvider(IMongoDatabase db) + { + this._db = db; + this.usersCol = db.GetCollection("default_auth_data"); + _createUsernameUniqueIndex(); + } + + private void _createUsernameUniqueIndex() + { + var options = new CreateIndexOptions() { Unique = true }; + var field = new StringFieldDefinition("Username"); + var indexDefinition = new IndexKeysDefinitionBuilder().Ascending(field); + var createIndexModel = new CreateIndexModel(indexDefinition, options); + usersCol.Indexes.CreateOne(createIndexModel); + } + + public void CreateUser(string username, string pass) + { + var user = new User(); + user.Username = username; // TODO add check on bad chars + var hashed = PasswordHasher.CreatePasswordHash(pass); + System.GC.Collect(); + user.Password = hashed.Item1; + user.Salt = hashed.Item2; + _db.GetCollection("default_auth_data").InsertOne(user); + } + + public string GetAuthProviderName() + { + return "default"; + } + + public bool TestPassword(string username, string pass) + { + var filter = Builders.Filter.Eq("Username", username); + var user = usersCol.Find(filter).FirstOrDefault(); + if (user == null) { + return false; + } + var valid = PasswordHasher.VerifyHash(pass, user.Salt, user.Password); + System.GC.Collect(); + return valid; + } + + public bool TestToken(string token, JWTPayload payload) + { + return true; // TODO add enchanced token validation later + } + } +}