diff --git a/PasswordHasher.cs b/PasswordHasher.cs new file mode 100644 index 0000000..d8fbae5 --- /dev/null +++ b/PasswordHasher.cs @@ -0,0 +1,109 @@ +using System; +using System.Security.Cryptography; +using System.Text; +using Konscious.Security.Cryptography; + +namespace PasswordHasher +{ + /// + /// Provides secure password hashing functionality using Argon2id algorithm + /// + public class PasswordHasher + { + /// + /// Size of the salt in bytes + /// + private const int SaltSize = 16; + + /// + /// Size of the hash in bytes + /// + private const int HashSize = 32; + + /// + /// Number of threads to use for parallel computation + /// + private const int DegreeOfParallelism = 1; + + /// + /// Number of iterations for the Argon2id algorithm + /// + private const int Iterations = 2; + + /// + /// Memory size in KB to use + /// + private const int MemorySize = 19456; // 19 MB + + /// + /// Generates a secure hash of a password using Argon2id with a random salt + /// + /// The plain text password to hash + /// A Base64 string containing the combined salt and hash + /// Thrown when password is null + public string HashPassword(string password) + { + // Generate a random salt + byte[] salt = new byte[SaltSize]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(salt); + } + + // Create hash + byte[] hash = HashPassword(password, salt); + + // Combine salt and hash + var combinedBytes = new byte[salt.Length + hash.Length]; + Array.Copy(salt, 0, combinedBytes, 0, salt.Length); + Array.Copy(hash, 0, combinedBytes, salt.Length, hash.Length); + + // Convert to base64 for storage + return Convert.ToBase64String(combinedBytes); + } + + /// + /// Generates a password hash using Argon2id with a specific salt + /// + /// The plain text password + /// The salt to use for hashing + /// A byte array containing the password hash + private byte[] HashPassword(string password, byte[] salt) + { + var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)) + { + Salt = salt, + DegreeOfParallelism = DegreeOfParallelism, + Iterations = Iterations, + MemorySize = MemorySize + }; + return argon2.GetBytes(HashSize); + } + + /// + /// Verifies if a password matches a stored hash + /// + /// The plain text password to verify + /// The stored hash in Base64 format + /// True if the password matches the hash, false otherwise + /// Thrown when password or hashedPassword are null + /// Thrown when hashedPassword is not in valid Base64 format + public bool VerifyPassword(string password, string hashedPassword) + { + // Decode the stored hash + byte[] combinedBytes = Convert.FromBase64String(hashedPassword); + + // Extract salt and hash + byte[] salt = new byte[SaltSize]; + byte[] hash = new byte[HashSize]; + Array.Copy(combinedBytes, 0, salt, 0, SaltSize); + Array.Copy(combinedBytes, SaltSize, hash, 0, HashSize); + + // Compute hash for the input password + byte[] newHash = HashPassword(password, salt); + + // Compare the hashes + return CryptographicOperations.FixedTimeEquals(hash, newHash); + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index 65fb96a..33ea9c4 100644 --- a/Program.cs +++ b/Program.cs @@ -1,57 +1,60 @@ -using SurrealDb.Net; +using System.ComponentModel.Design; +using SurrealDb.Net; using SurrealDb.Net.Models; using SurrealDb.Net.Models.Auth; using System.Text.Json; - -const string TABLE = "person"; +using PasswordHasher; using var db = new SurrealDbClient("ws://127.0.0.1:8000/rpc"); await db.SignIn(new RootAuth { Username = "root", Password = "secret" }); await db.Use("test", "test"); +var user = new Users +{ + Username = "Keeper317", + Email = "Keeper317@gmail.com", + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + LastLogin = DateTime.Now, + TwoFactorEnabled = false, + EmailVerified = false, + AccountStatus = (int) AccountStatuses.Active, + OnlineStatus = (int) OnlineStatuses.Online, +}; +var created = await db.Create("users", user); +Console.WriteLine($"Created Person: {ToJsonString(created)}"); +var hasher = new PasswordHasher.PasswordHasher(); +user.Password = hasher.HashPassword(created.Id + "password"); +user.UpdatedAt = DateTime.Now; +user.LastLogin = DateTime.Now; +user.Id = created.Id; + +bool isValid = hasher.VerifyPassword(created.Id + "password", user.Password); +Console.WriteLine($"Password match: {isValid}"); + +var updated = await db.Merge("users", new() { Id = created.Id, Password = user.Password }); -// var person = new Person -// { -// Title = "Founder CEO", -// Name = new() { FirstName = "Tobie", LastName = "Morgan Hitchcock" }, -// Marketing = true -// }; -// var created = await db.Create(TABLE, person); -// Console.WriteLine($"Created Person: {ToJsonString(created)}"); -// // var updated = await db.Merge( -// new() { Id = (TABLE, "3bi2i54plwqfqe8u2qiq"), Marketing = false } +// new() { Id = (TABLE, "8b4nwczy6x8f8zd5sslq"), Marketing = true } // ); -// Console.WriteLine($"Updated Person: {ToJsonString(updated)}"); +Console.WriteLine($"Updated Person: {ToJsonString(updated)}"); // -// var people = await db.Select(TABLE); -// Console.WriteLine($"Select Person: {ToJsonString(people)}"); - -var queryResponse = await db.Query( - $"SELECT Marketing, count() AS Count FROM type::table({TABLE}) GROUP BY Marketing" -); -// await db.Delete(TABLE); - +var people = await db.Select("users"); +Console.WriteLine($"Select Person: {ToJsonString(people)}"); +// +// var queryResponse = await db.Query($"SELECT Marketing, count() AS Count FROM type::table({TABLE}) GROUP BY Marketing"); // var groups = queryResponse.GetValue>(0); -Console.WriteLine($"Get Value as group: {ToJsonString(queryResponse)}"); +// Console.WriteLine($"Get Value as group: {ToJsonString(groups)}"); + +await db.Delete("users"); + static string ToJsonString(object? o) { return JsonSerializer.Serialize(o, new JsonSerializerOptions { WriteIndented = true, }); } -public class Person : Record -{ - public string? Title { get; set; } - public Name? Name { get; set; } - public bool Marketing { get; set; } -} -public class Name -{ - public string? FirstName { get; set; } - public string? LastName { get; set; } -} public class ResponsibilityMerge : Record { public bool Marketing { get; set; } @@ -60,4 +63,111 @@ public class Group { public bool Marketing { get; set; } public int Count { get; set; } +} + +public class Users : Record +{ + public required string Username { get; set; } + public string? Password { get; set; } + public required string Email { get; set; } + public required DateTime CreatedAt { get; set; } + public required DateTime UpdatedAt { get; set; } + public required DateTime LastLogin { get; set; } + public bool TwoFactorEnabled { get; set; } + public bool EmailVerified { get; set; } + public required int AccountStatus { get; set; } + public required int OnlineStatus { get; set; } +} + +public class PasswordHash : Record +{ + public string? Password { get; set; } +} + +public class Sessions : Record +{ + public required string UserId { get; set; } + public required string TokenHash { get; set; } + public required DateTime IssuedAt { get; set; } + public required DateTime ExpiresAt { get; set; } + public DateTime? LastUsedAt { get; set; } + public bool Revoked { get; set; } + public required string DeviceName { get; set; } + public required string IpAddress { get; set; } + public required string UserAgent { get; set; } +} + +public class PasswordReset : Record +{ + public required string UserId { get; set; } + public required string TokenHash { get; set; } + public required DateTime CreatedAt { get; set; } + public required DateTime ExpiresAt { get; set; } + public bool Revoked { get; set; } +} + +public class Licenses : Record +{ + public required string UserId { get; set; } + public required int LicenseType { get; set; } + public required int Status { get; set; } + public required DateTime CreatedAt { get; set; } + public required DateTime StartsAt { get; set; } + public required DateTime UpdatedAt { get; set; } + public required DateTime ExpiresAt { get; set; } + +} + +public class AuthAudits : Record +{ + public required string UserId { get; set; } + public required int EventType { get; set; } + public bool Success { get; set; } + public required string IpAddress { get; set; } + public required string UserAgent { get; set; } + public required string Details { get; set; } + public required DateTime CreatedAt { get; set; } +} +enum AccountStatuses +{ + Active, + Suspended, + Banned, + Deleted +} + +enum OnlineStatuses +{ + Online, + Busy, + DND, + Invisible, + Offline +} + +enum LicenseStatuses +{ + Active, + Expired, + Renewable, + Revoked +} + +enum LicenseType +{ + Free, + Basic, + Advanced, + Pro, + Enterprise +} + +enum LogEvents +{ + LoginSuccess, + LoginFailure, + LogoutSuccess, + LogoutFailure, + PasswordResetSuccess, + PasswordResetFailure, } \ No newline at end of file diff --git a/RelayCore.csproj b/RelayCore.csproj index 9a93daa..fc62708 100644 --- a/RelayCore.csproj +++ b/RelayCore.csproj @@ -9,6 +9,7 @@ + diff --git a/obj/Debug/net9.0/RelayCore.AssemblyInfo.cs b/obj/Debug/net9.0/RelayCore.AssemblyInfo.cs index af8b2b1..8e7d44b 100644 --- a/obj/Debug/net9.0/RelayCore.AssemblyInfo.cs +++ b/obj/Debug/net9.0/RelayCore.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("RelayCore")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+ccbb3e874a5d2b1d5f9bc39960c235c07b5fe29d")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+7682ce0429634f5b008d85c59acf4ed3838121ef")] [assembly: System.Reflection.AssemblyProductAttribute("RelayCore")] [assembly: System.Reflection.AssemblyTitleAttribute("RelayCore")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/obj/Debug/net9.0/RelayCore.AssemblyInfoInputs.cache b/obj/Debug/net9.0/RelayCore.AssemblyInfoInputs.cache index dbf795f..56d3ba3 100644 --- a/obj/Debug/net9.0/RelayCore.AssemblyInfoInputs.cache +++ b/obj/Debug/net9.0/RelayCore.AssemblyInfoInputs.cache @@ -1 +1 @@ -4f5764b42df7cbe872550eb268495ab3739cb29ded503ffcfb83b6b63bb3a5ea +86555fdeb5734425e2303585c6b325bb51b77f8679eb62d09e5e490545061734 diff --git a/obj/Debug/net9.0/RelayCore.assets.cache b/obj/Debug/net9.0/RelayCore.assets.cache index 445fb03..8889c27 100644 Binary files a/obj/Debug/net9.0/RelayCore.assets.cache and b/obj/Debug/net9.0/RelayCore.assets.cache differ diff --git a/obj/RelayCore.csproj.nuget.dgspec.json b/obj/RelayCore.csproj.nuget.dgspec.json index d17716a..4555919 100644 --- a/obj/RelayCore.csproj.nuget.dgspec.json +++ b/obj/RelayCore.csproj.nuget.dgspec.json @@ -51,6 +51,10 @@ "net9.0": { "targetAlias": "net9.0", "dependencies": { + "Konscious.Security.Cryptography.Argon2": { + "target": "Package", + "version": "[1.3.1, )" + }, "SurrealDb.Net": { "target": "Package", "version": "[0.9.0, )" diff --git a/obj/project.assets.json b/obj/project.assets.json index a42fc44..4837f80 100644 --- a/obj/project.assets.json +++ b/obj/project.assets.json @@ -27,6 +27,36 @@ "lib/net8.0/Dahomey.Cbor.dll": {} } }, + "Konscious.Security.Cryptography.Argon2/1.3.1": { + "type": "package", + "dependencies": { + "Konscious.Security.Cryptography.Blake2": "1.1.1", + "System.Memory": "4.5.4" + }, + "compile": { + "lib/net8.0/Konscious.Security.Cryptography.Argon2.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Konscious.Security.Cryptography.Argon2.dll": { + "related": ".xml" + } + } + }, + "Konscious.Security.Cryptography.Blake2/1.1.1": { + "type": "package", + "compile": { + "lib/net8.0/Konscious.Security.Cryptography.Blake2.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net8.0/Konscious.Security.Cryptography.Blake2.dll": { + "related": ".xml" + } + } + }, "Microsoft.Bcl.AsyncInterfaces/6.0.0": { "type": "package", "compile": { @@ -412,6 +442,15 @@ } } }, + "System.Memory/4.5.4": { + "type": "package", + "compile": { + "ref/netcoreapp2.1/_._": {} + }, + "runtime": { + "lib/netcoreapp2.1/_._": {} + } + }, "System.Reactive/6.0.0": { "type": "package", "compile": { @@ -511,6 +550,44 @@ "lib/netstandard2.0/Dahomey.Cbor.dll" ] }, + "Konscious.Security.Cryptography.Argon2/1.3.1": { + "sha512": "T+OAGwzYYXftahpOxO7J4xA5K6urxwGnWQf3M+Jpi+76Azv/0T3M5SuN+h7/QvXuiqNw3ZEZ5QqVLI5ygDAylw==", + "type": "package", + "path": "konscious.security.cryptography.argon2/1.3.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "konscious.security.cryptography.argon2.1.3.1.nupkg.sha512", + "konscious.security.cryptography.argon2.nuspec", + "lib/net46/Konscious.Security.Cryptography.Argon2.dll", + "lib/net46/Konscious.Security.Cryptography.Argon2.xml", + "lib/net6.0/Konscious.Security.Cryptography.Argon2.dll", + "lib/net6.0/Konscious.Security.Cryptography.Argon2.xml", + "lib/net8.0/Konscious.Security.Cryptography.Argon2.dll", + "lib/net8.0/Konscious.Security.Cryptography.Argon2.xml", + "lib/netstandard1.3/Konscious.Security.Cryptography.Argon2.dll", + "lib/netstandard1.3/Konscious.Security.Cryptography.Argon2.xml" + ] + }, + "Konscious.Security.Cryptography.Blake2/1.1.1": { + "sha512": "odwOyzj/J/lHJZNwFWJGU/LRecBShupAJ2S8TQqZfhUe9niHzu/voBYK5wuVKsvSpzbfupKQYZguVyIk1sgOkQ==", + "type": "package", + "path": "konscious.security.cryptography.blake2/1.1.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "konscious.security.cryptography.blake2.1.1.1.nupkg.sha512", + "konscious.security.cryptography.blake2.nuspec", + "lib/net46/Konscious.Security.Cryptography.Blake2.dll", + "lib/net46/Konscious.Security.Cryptography.Blake2.xml", + "lib/net6.0/Konscious.Security.Cryptography.Blake2.dll", + "lib/net6.0/Konscious.Security.Cryptography.Blake2.xml", + "lib/net8.0/Konscious.Security.Cryptography.Blake2.dll", + "lib/net8.0/Konscious.Security.Cryptography.Blake2.xml", + "lib/netstandard1.3/Konscious.Security.Cryptography.Blake2.dll", + "lib/netstandard1.3/Konscious.Security.Cryptography.Blake2.xml" + ] + }, "Microsoft.Bcl.AsyncInterfaces/6.0.0": { "sha512": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==", "type": "package", @@ -1131,6 +1208,29 @@ "system.linq.async.nuspec" ] }, + "System.Memory/4.5.4": { + "sha512": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==", + "type": "package", + "path": "system.memory/4.5.4", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/System.Memory.dll", + "lib/net461/System.Memory.xml", + "lib/netcoreapp2.1/_._", + "lib/netstandard1.1/System.Memory.dll", + "lib/netstandard1.1/System.Memory.xml", + "lib/netstandard2.0/System.Memory.dll", + "lib/netstandard2.0/System.Memory.xml", + "ref/netcoreapp2.1/_._", + "system.memory.4.5.4.nupkg.sha512", + "system.memory.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, "System.Reactive/6.0.0": { "sha512": "31kfaW4ZupZzPsI5PVe77VhnvFF55qgma7KZr/E0iFTs6fmdhhG8j0mgEx620iLTey1EynOkEfnyTjtNEpJzGw==", "type": "package", @@ -1239,6 +1339,7 @@ }, "projectFileDependencyGroups": { "net9.0": [ + "Konscious.Security.Cryptography.Argon2 >= 1.3.1", "SurrealDb.Net >= 0.9.0" ] }, @@ -1293,6 +1394,10 @@ "net9.0": { "targetAlias": "net9.0", "dependencies": { + "Konscious.Security.Cryptography.Argon2": { + "target": "Package", + "version": "[1.3.1, )" + }, "SurrealDb.Net": { "target": "Package", "version": "[0.9.0, )" diff --git a/obj/project.nuget.cache b/obj/project.nuget.cache index eacb7d3..f2556c4 100644 --- a/obj/project.nuget.cache +++ b/obj/project.nuget.cache @@ -1,11 +1,13 @@ { "version": 2, - "dgSpecHash": "FgXgk22yPWQ=", + "dgSpecHash": "hVKcd/jquSo=", "success": true, "projectFilePath": "D:\\DDI\\Relay\\RelayChat\\RelayCore\\RelayCore.csproj", "expectedPackageFiles": [ "C:\\Users\\Core\\.nuget\\packages\\concurrenthashset\\1.3.0\\concurrenthashset.1.3.0.nupkg.sha512", "C:\\Users\\Core\\.nuget\\packages\\dahomey.cbor\\1.24.3\\dahomey.cbor.1.24.3.nupkg.sha512", + "C:\\Users\\Core\\.nuget\\packages\\konscious.security.cryptography.argon2\\1.3.1\\konscious.security.cryptography.argon2.1.3.1.nupkg.sha512", + "C:\\Users\\Core\\.nuget\\packages\\konscious.security.cryptography.blake2\\1.1.1\\konscious.security.cryptography.blake2.1.1.1.nupkg.sha512", "C:\\Users\\Core\\.nuget\\packages\\microsoft.bcl.asyncinterfaces\\6.0.0\\microsoft.bcl.asyncinterfaces.6.0.0.nupkg.sha512", "C:\\Users\\Core\\.nuget\\packages\\microsoft.extensions.configuration\\9.0.4\\microsoft.extensions.configuration.9.0.4.nupkg.sha512", "C:\\Users\\Core\\.nuget\\packages\\microsoft.extensions.configuration.abstractions\\9.0.4\\microsoft.extensions.configuration.abstractions.9.0.4.nupkg.sha512", @@ -27,6 +29,7 @@ "C:\\Users\\Core\\.nuget\\packages\\system.collections.immutable\\9.0.4\\system.collections.immutable.9.0.4.nupkg.sha512", "C:\\Users\\Core\\.nuget\\packages\\system.io.pipelines\\7.0.0\\system.io.pipelines.7.0.0.nupkg.sha512", "C:\\Users\\Core\\.nuget\\packages\\system.linq.async\\6.0.1\\system.linq.async.6.0.1.nupkg.sha512", + "C:\\Users\\Core\\.nuget\\packages\\system.memory\\4.5.4\\system.memory.4.5.4.nupkg.sha512", "C:\\Users\\Core\\.nuget\\packages\\system.reactive\\6.0.0\\system.reactive.6.0.0.nupkg.sha512", "C:\\Users\\Core\\.nuget\\packages\\system.threading.channels\\8.0.0\\system.threading.channels.8.0.0.nupkg.sha512", "C:\\Users\\Core\\.nuget\\packages\\systemtextjsonpatch\\4.2.0\\systemtextjsonpatch.4.2.0.nupkg.sha512", diff --git a/obj/project.packagespec.json b/obj/project.packagespec.json index a71aff7..6e15b5d 100644 --- a/obj/project.packagespec.json +++ b/obj/project.packagespec.json @@ -1 +1 @@ -"restore":{"projectUniqueName":"D:\\DDI\\Relay\\RelayChat\\RelayCore\\RelayCore.csproj","projectName":"RelayCore","projectPath":"D:\\DDI\\Relay\\RelayChat\\RelayCore\\RelayCore.csproj","outputPath":"D:\\DDI\\Relay\\RelayChat\\RelayCore\\obj\\","projectStyle":"PackageReference","fallbackFolders":["C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"],"originalTargetFrameworks":["net9.0"],"sources":{"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\":{},"C:\\Program Files\\dotnet\\library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net9.0":{"targetAlias":"net9.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.300"}"frameworks":{"net9.0":{"targetAlias":"net9.0","dependencies":{"SurrealDb.Net":{"target":"Package","version":"[0.9.0, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Program Files\\dotnet\\sdk\\9.0.305/PortableRuntimeIdentifierGraph.json"}} \ No newline at end of file +"restore":{"projectUniqueName":"D:\\DDI\\Relay\\RelayChat\\RelayCore\\RelayCore.csproj","projectName":"RelayCore","projectPath":"D:\\DDI\\Relay\\RelayChat\\RelayCore\\RelayCore.csproj","outputPath":"D:\\DDI\\Relay\\RelayChat\\RelayCore\\obj\\","projectStyle":"PackageReference","fallbackFolders":["C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"],"originalTargetFrameworks":["net9.0"],"sources":{"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\":{},"C:\\Program Files\\dotnet\\library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net9.0":{"targetAlias":"net9.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.300"}"frameworks":{"net9.0":{"targetAlias":"net9.0","dependencies":{"Konscious.Security.Cryptography.Argon2":{"target":"Package","version":"[1.3.1, )"},"SurrealDb.Net":{"target":"Package","version":"[0.9.0, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"C:\\Program Files\\dotnet\\sdk\\9.0.305/PortableRuntimeIdentifierGraph.json"}} \ No newline at end of file diff --git a/obj/rider.project.model.nuget.info b/obj/rider.project.model.nuget.info index 1fabd17..f9a00fa 100644 --- a/obj/rider.project.model.nuget.info +++ b/obj/rider.project.model.nuget.info @@ -1 +1 @@ -17731137598642406 \ No newline at end of file +17734474740469652 \ No newline at end of file diff --git a/obj/rider.project.restore.info b/obj/rider.project.restore.info index 264d953..84908d7 100644 --- a/obj/rider.project.restore.info +++ b/obj/rider.project.restore.info @@ -1 +1 @@ -17731190668997575 \ No newline at end of file +17734474749787742 \ No newline at end of file