feat: login handling & proper password storage
All checks were successful
Test if Server can be built / build-server (push) Successful in 37s

This commit is contained in:
Timothy Schenk 2023-08-13 22:27:24 +02:00
parent 1dd62a0491
commit c282d42c61
6 changed files with 48 additions and 15 deletions

View file

@ -4,16 +4,18 @@ using CouchDB.Driver.Types;
public class Account : CouchDocument public class Account : CouchDocument
{ {
public Account(string username, string password, string email, byte permissionLevel) public Account(string username, byte[] password, string email, byte permissionLevel, byte[] salt)
{ {
this.Username = username; this.Username = username;
this.Password = password; this.Password = password;
this.Email = email; this.Email = email;
this.PermissionLevel = permissionLevel; this.PermissionLevel = permissionLevel;
this.Salt = salt;
} }
public string Username { get; set; } public string Username { get; set; }
public string Password { get; set; } public byte[] Password { get; set; }
public string Email { get; set; } public string Email { get; set; }
public byte PermissionLevel { get; set; } public byte PermissionLevel { get; set; }
public byte[] Salt { get; set; }
} }

View file

@ -1,7 +1,12 @@
namespace Server.PacketHandlers; namespace Server.PacketHandlers;
using System.Security.Cryptography;
using System.Text;
using CouchDB.Driver.Query.Extensions;
using DB; using DB;
using DB.Documents; using DB.Documents;
using DotNext;
using Konscious.Security.Cryptography;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NetCoreServer; using NetCoreServer;
@ -23,27 +28,50 @@ public class LoginHandler : IPacketHandler<LoginInfoPacket>
public async Task HandleAsync(LoginInfoPacket packet, TcpSession session) public async Task HandleAsync(LoginInfoPacket packet, TcpSession session)
{ {
var loginResponseReason = LoginResponseReason.Error;
this.logger.LogInformation("Login data: Username {Username} & Password {Password}", packet.Username, this.logger.LogInformation("Login data: Username {Username} & Password {Password}", packet.Username,
packet.Password); packet.Password);
var account = this.wonderkingContext.Accounts.FirstOrDefault(a => a.Username == packet.Username); var account = this.wonderkingContext.Accounts.FirstOrDefault(a => a.Username == packet.Username);
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
// "Use Argon2id with a minimum configuration of 19 MiB of memory, an iteration count of 2, and 1 degree of parallelism."
var argon2Id = new Argon2id(Encoding.ASCII.GetBytes(packet.Password));
argon2Id.MemorySize = 1024 * 40;
argon2Id.Iterations = 4;
argon2Id.DegreeOfParallelism = 2;
if (account == null) if (account == null)
{ {
if (this.configuration.GetSection("Testing").GetValue<bool>("CreateAccountOnLogin")) if (this.configuration.GetSection("Testing").GetValue<bool>("CreateAccountOnLogin"))
{ {
var result = argon2Id.Salt = RandomNumberGenerator.GetBytes(128);
this.wonderkingContext.Accounts.AddAsync(new Account(packet.Username, packet.Password, "", 0)); var finalAccount =
await result; await this.wonderkingContext.Accounts.AddAsync(new Account(packet.Username, Array.Empty<byte>(), "",
0, argon2Id.Salt));
argon2Id.AssociatedData = Guid.Parse(finalAccount.Id).ToByteArray();
finalAccount.Password = await argon2Id.GetBytesAsync(128);
await this.wonderkingContext.Accounts.AddOrUpdateAsync(finalAccount);
loginResponseReason = LoginResponseReason.Ok;
} }
else else
{ {
// TODO: Send Message that account does not exist // TODO: Send Message that account does not exist
this.logger.LogInformation("Requested account for user: {Username} does not exist", packet.Username); this.logger.LogInformation("Requested account for user: {Username} does not exist", packet.Username);
loginResponseReason = LoginResponseReason.AccountDoesNotExit;
} }
} }
else
{
argon2Id.Salt = account.Salt;
argon2Id.AssociatedData = Guid.Parse(account.Id).ToByteArray();
var tempPasswordBytes = await argon2Id.GetBytesAsync(128);
loginResponseReason = tempPasswordBytes.SequenceEqual(account.Password)
? LoginResponseReason.Ok
: LoginResponseReason.WrongPassword;
}
var loginResponsePacket = new LoginResponsePacket var loginResponsePacket = new LoginResponsePacket
{ {
ResponseReason = LoginResponseReason.Ok, ResponseReason = loginResponseReason,
ChannelData = new[] { new ServerChannelData { ChannelId = 0, LoadPercentage = 75, ServerId = 0 } }, ChannelData = new[] { new ServerChannelData { ChannelId = 0, LoadPercentage = 75, ServerId = 0 } },
UnknownFlag = 1, UnknownFlag = 1,
IsGameMaster = true IsGameMaster = true

View file

@ -11,8 +11,8 @@ public class LoginInfoPacket : IPacket
public void Deserialize(byte[] data) public void Deserialize(byte[] data)
{ {
this.Username = Encoding.ASCII.GetString(data, 0, 20); this.Username = Encoding.ASCII.GetString(data, 0, 20).TrimEnd('\0');
this.Password = Encoding.ASCII.GetString(data, 20, 31); this.Password = Encoding.ASCII.GetString(data, 20, 31).TrimEnd('\0');
} }
public byte[] Serialize() public byte[] Serialize()

View file

@ -3,7 +3,7 @@
public enum LoginResponseReason : byte public enum LoginResponseReason : byte
{ {
Ok, Ok,
AcountNotExist, AccountDoesNotExit,
WrongPassword, WrongPassword,
Error, Error,
DuplicateConnection, DuplicateConnection,

View file

@ -30,6 +30,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="JetBrains.Annotations" Version="2023.2.0"/> <PackageReference Include="JetBrains.Annotations" Version="2023.2.0"/>
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.0" />
<PackageReference Include="MassTransit" Version="8.0.16"/> <PackageReference Include="MassTransit" Version="8.0.16"/>
<PackageReference Include="MassTransit.Analyzers" Version="8.0.16"> <PackageReference Include="MassTransit.Analyzers" Version="8.0.16">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -70,4 +71,10 @@
</PackageReference> </PackageReference>
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0"/> <PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0"/>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>

View file

@ -102,21 +102,17 @@ public class PacketDistributorService : IHostedService
public void AddPacket(RawPacket rawPacket) public void AddPacket(RawPacket rawPacket)
{ {
this.concurrentQueue.Enqueue(rawPacket); this.concurrentQueue.Enqueue(rawPacket);
Task.Run(this.DequeueRawPacketAsync); this.DequeueRawPacket();
this.logger.LogInformation("Packet with ID: {MessageOperationCode} has been received", this.logger.LogInformation("Packet with ID: {MessageOperationCode} has been received",
rawPacket.OperationCode); rawPacket.OperationCode);
} }
private async Task DequeueRawPacketAsync() private void DequeueRawPacket()
{ {
if (this.concurrentQueue.TryDequeue(out var item)) if (this.concurrentQueue.TryDequeue(out var item))
{ {
ThreadPool.QueueUserWorkItem(this.InvokePacketHandler, item, true); ThreadPool.QueueUserWorkItem(this.InvokePacketHandler, item, true);
} }
else
{
await Task.Delay(100); // Delay to prevent busy-waiting, can be adjusted based on needs
}
} }
private void InvokePacketHandler(RawPacket item) private void InvokePacketHandler(RawPacket item)