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 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.Password = password;
this.Email = email;
this.PermissionLevel = permissionLevel;
this.Salt = salt;
}
public string Username { get; set; }
public string Password { get; set; }
public byte[] Password { get; set; }
public string Email { get; set; }
public byte PermissionLevel { get; set; }
public byte[] Salt { get; set; }
}

View file

@ -1,7 +1,12 @@
namespace Server.PacketHandlers;
using System.Security.Cryptography;
using System.Text;
using CouchDB.Driver.Query.Extensions;
using DB;
using DB.Documents;
using DotNext;
using Konscious.Security.Cryptography;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NetCoreServer;
@ -23,27 +28,50 @@ public class LoginHandler : IPacketHandler<LoginInfoPacket>
public async Task HandleAsync(LoginInfoPacket packet, TcpSession session)
{
var loginResponseReason = LoginResponseReason.Error;
this.logger.LogInformation("Login data: Username {Username} & Password {Password}", packet.Username,
packet.Password);
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 (this.configuration.GetSection("Testing").GetValue<bool>("CreateAccountOnLogin"))
{
var result =
this.wonderkingContext.Accounts.AddAsync(new Account(packet.Username, packet.Password, "", 0));
await result;
argon2Id.Salt = RandomNumberGenerator.GetBytes(128);
var finalAccount =
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
{
// TODO: Send Message that account does not exist
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
{
ResponseReason = LoginResponseReason.Ok,
ResponseReason = loginResponseReason,
ChannelData = new[] { new ServerChannelData { ChannelId = 0, LoadPercentage = 75, ServerId = 0 } },
UnknownFlag = 1,
IsGameMaster = true

View file

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

View file

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

View file

@ -30,6 +30,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<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.Analyzers" Version="8.0.16">
<PrivateAssets>all</PrivateAssets>
@ -70,4 +71,10 @@
</PackageReference>
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0"/>
</ItemGroup>
<ItemGroup>
<None Update="settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View file

@ -102,21 +102,17 @@ public class PacketDistributorService : IHostedService
public void AddPacket(RawPacket rawPacket)
{
this.concurrentQueue.Enqueue(rawPacket);
Task.Run(this.DequeueRawPacketAsync);
this.DequeueRawPacket();
this.logger.LogInformation("Packet with ID: {MessageOperationCode} has been received",
rawPacket.OperationCode);
}
private async Task DequeueRawPacketAsync()
private void DequeueRawPacket()
{
if (this.concurrentQueue.TryDequeue(out var item))
{
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)