feat: login handling & proper password storage
All checks were successful
Test if Server can be built / build-server (push) Successful in 37s
All checks were successful
Test if Server can be built / build-server (push) Successful in 37s
This commit is contained in:
parent
1dd62a0491
commit
c282d42c61
6 changed files with 48 additions and 15 deletions
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
public enum LoginResponseReason : byte
|
public enum LoginResponseReason : byte
|
||||||
{
|
{
|
||||||
Ok,
|
Ok,
|
||||||
AcountNotExist,
|
AccountDoesNotExit,
|
||||||
WrongPassword,
|
WrongPassword,
|
||||||
Error,
|
Error,
|
||||||
DuplicateConnection,
|
DuplicateConnection,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue