Split out the Flashii interaction code into a separate library.

This commit is contained in:
flash 2025-04-26 19:42:23 +00:00
commit 2eba089a21
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
55 changed files with 769 additions and 552 deletions

View file

@ -1,15 +1,16 @@
using SharpChat.Config;
using SharpChat.Misuzu;
using SharpChat.Auth;
using SharpChat.Bans;
using SharpChat.Configuration;
using SharpChat.S2CPackets;
namespace SharpChat.C2SPacketHandlers {
public class AuthC2SPacketHandler(
MisuzuClient msz,
AuthClient authClient,
BansClient bansClient,
Channel defaultChannel,
CachedValue<int> maxMsgLength,
CachedValue<int> maxConns
) : C2SPacketHandler {
private readonly MisuzuClient Misuzu = msz ?? throw new ArgumentNullException(nameof(msz));
private readonly Channel DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel));
private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));
private readonly CachedValue<int> MaxConnections = maxConns ?? throw new ArgumentNullException(nameof(maxConns));
@ -22,14 +23,9 @@ namespace SharpChat.C2SPacketHandlers {
string[] args = ctx.SplitText(3);
string? authMethod = args.ElementAtOrDefault(1);
if(string.IsNullOrWhiteSpace(authMethod)) {
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
ctx.Connection.Dispose();
return;
}
string? authToken = args.ElementAtOrDefault(2);
if(string.IsNullOrWhiteSpace(authToken)) {
if(string.IsNullOrWhiteSpace(authMethod) || string.IsNullOrWhiteSpace(authToken)) {
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
ctx.Connection.Dispose();
return;
@ -42,93 +38,75 @@ namespace SharpChat.C2SPacketHandlers {
}
Task.Run(async () => {
MisuzuAuthInfo? fai;
string ipAddr = ctx.Connection.RemoteAddress.ToString();
try {
fai = await Misuzu.AuthVerifyAsync(authMethod, authToken, ipAddr);
} catch(Exception ex) {
Logger.Write($"<{ctx.Connection.Id}> Failed to authenticate: {ex}");
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
ctx.Connection.Dispose();
#if DEBUG
throw;
#else
return;
#endif
}
AuthResult authResult = await authClient.AuthVerifyAsync(
ctx.Connection.RemoteAddress,
authMethod,
authToken
);
if(fai?.Success != true) {
Logger.Debug($"<{ctx.Connection.Id}> Auth fail: {fai?.Reason ?? "unknown"}");
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
ctx.Connection.Dispose();
return;
}
MisuzuBanInfo? fbi;
try {
fbi = await Misuzu.CheckBanAsync(fai.UserId.ToString(), ipAddr);
} catch(Exception ex) {
Logger.Write($"<{ctx.Connection.Id}> Failed auth ban check: {ex}");
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
ctx.Connection.Dispose();
#if DEBUG
throw;
#else
return;
#endif
}
if(fbi?.IsBanned == true && !fbi.HasExpired) {
Logger.Write($"<{ctx.Connection.Id}> User is banned.");
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.Banned, fbi.IsPermanent ? DateTimeOffset.MaxValue : fbi.ExpiresAt));
ctx.Connection.Dispose();
return;
}
await ctx.Chat.ContextAccess.WaitAsync();
try {
User? user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId);
if(user == null)
user = new User(
fai.UserId,
fai.UserName ?? $"({fai.UserId})",
fai.Colour,
fai.Rank,
fai.Permissions
);
else
ctx.Chat.UpdateUser(
user,
userName: fai.UserName ?? $"({fai.UserId})",
colour: fai.Colour,
rank: fai.Rank,
perms: fai.Permissions
);
// Enforce a maximum amount of connections per user
if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) {
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.MaxSessions));
BanInfo? banInfo = await bansClient.BanGetAsync(authResult.UserId, ctx.Connection.RemoteAddress);
if(banInfo is not null) {
Logger.Write($"<{ctx.Connection.Id}> User is banned.");
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.Banned, banInfo.IsPermanent ? DateTimeOffset.MaxValue : banInfo.ExpiresAt));
ctx.Connection.Dispose();
return;
}
ctx.Connection.BumpPing();
ctx.Connection.User = user;
ctx.Connection.Send(new CommandResponseS2CPacket(0, LCR.WELCOME, false, $"Welcome to Flashii Chat, {user.UserName}!"));
await ctx.Chat.ContextAccess.WaitAsync();
try {
User? user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == authResult.UserId);
if(File.Exists("welcome.txt")) {
IEnumerable<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x));
string? line = lines.ElementAtOrDefault(RNG.Next(lines.Count()));
if(user == null)
user = new User(
authResult.UserId,
authResult.UserName ?? $"({authResult.UserId})",
authResult.UserColour,
authResult.UserRank,
authResult.UserPermissions
);
else
ctx.Chat.UpdateUser(
user,
userName: authResult.UserName ?? $"({authResult.UserId})",
colour: authResult.UserColour,
rank: authResult.UserRank,
perms: authResult.UserPermissions
);
if(!string.IsNullOrWhiteSpace(line))
ctx.Connection.Send(new CommandResponseS2CPacket(0, LCR.WELCOME, false, line));
// Enforce a maximum amount of connections per user
if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) {
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.MaxSessions));
ctx.Connection.Dispose();
return;
}
ctx.Connection.BumpPing();
ctx.Connection.User = user;
ctx.Connection.Send(new CommandResponseS2CPacket(0, LCR.WELCOME, false, $"Welcome to Flashii Chat, {user.UserName}!"));
if(File.Exists("welcome.txt")) {
IEnumerable<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x));
string? line = lines.ElementAtOrDefault(RNG.Next(lines.Count()));
if(!string.IsNullOrWhiteSpace(line))
ctx.Connection.Send(new CommandResponseS2CPacket(0, LCR.WELCOME, false, line));
}
ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength);
} finally {
ctx.Chat.ContextAccess.Release();
}
ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength);
} finally {
ctx.Chat.ContextAccess.Release();
} catch(AuthFailedException ex) {
Logger.Write($"<{ctx.Connection.Id}> Failed to authenticate: {ex}");
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid));
ctx.Connection.Dispose();
throw;
} catch(Exception ex) {
Logger.Write($"<{ctx.Connection.Id}> Failed to authenticate: {ex}");
ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.Exception));
ctx.Connection.Dispose();
throw;
}
}).Wait();
}