using SharpChat.Auth; using SharpChat.Bans; using SharpChat.Channels; using SharpChat.Configuration; using SharpChat.Messages; using SharpChat.Snowflake; using SharpChat.SockChat.S2CPackets; using ZLogger; namespace SharpChat.C2SPacketHandlers; public class AuthC2SPacketHandler( AuthClient authClient, BansClient bansClient, ChannelsContext channelsCtx, RandomSnowflake snowflake, CachedValue<int> maxMsgLength, CachedValue<int> maxConns ) : C2SPacketHandler { public bool IsMatch(C2SPacketHandlerContext ctx) { return ctx.CheckPacketId("1"); } public async Task Handle(C2SPacketHandlerContext ctx) { string[] args = ctx.SplitText(3); string? authMethod = args.ElementAtOrDefault(1); string? authToken = args.ElementAtOrDefault(2); if(string.IsNullOrWhiteSpace(authMethod) || string.IsNullOrWhiteSpace(authToken)) { await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid)); ctx.Connection.Dispose(); return; } if(authMethod.All(c => c is >= '0' and <= '9') && authToken.Contains(':')) { string[] tokenParts = authToken.Split(':', 2); authMethod = tokenParts[0]; authToken = tokenParts[1]; } try { AuthResult authResult = await authClient.AuthVerify( ctx.Connection.RemoteAddress, authMethod, authToken ); BanInfo? banInfo = await bansClient.BanGet(authResult.UserId, ctx.Connection.RemoteAddress); if(banInfo is not null) { ctx.Connection.Logger.ZLogInformation($"User {authResult.UserId} is banned."); await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.Banned, banInfo.IsPermanent ? DateTimeOffset.MaxValue : banInfo.ExpiresAt)); ctx.Connection.Dispose(); return; } await ctx.Chat.ContextAccess.WaitAsync(); try { User? user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == authResult.UserId); if(user == null) user = new User( authResult.UserId, authResult.UserName ?? $"({authResult.UserId})", authResult.UserColour, authResult.UserRank, authResult.UserPermissions ); else await ctx.Chat.UpdateUser( user, userName: authResult.UserName ?? $"({authResult.UserId})", colour: authResult.UserColour, rank: authResult.UserRank, perms: authResult.UserPermissions ); // Enforce a maximum amount of connections per user if(ctx.Chat.Connections.Count(conn => conn.User == user) >= maxConns) { await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.MaxSessions)); ctx.Connection.Dispose(); return; } ctx.Connection.BumpPing(); ctx.Connection.User = user; await 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)) await ctx.Connection.Send(new CommandResponseS2CPacket(0, LCR.WELCOME, false, line)); } Channel channel = channelsCtx.DefaultChannel; if(!ctx.Chat.IsInChannel(user, channel)) { long msgId = snowflake.Next(); await ctx.Chat.SendTo(channel, new UserConnectS2CPacket(msgId, DateTimeOffset.Now, user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions)); await ctx.Chat.Messages.LogMessage(msgId, "user:connect", channel.Name, user.UserId, user.UserName, user.Colour, user.Rank, user.NickName, user.Permissions); } await ctx.Connection.Send(new AuthSuccessS2CPacket( user.UserId, user.LegacyNameWithStatus, user.Colour, user.Rank, user.Permissions, channel.Name, maxMsgLength )); await ctx.Connection.Send(new ContextUsersS2CPacket( ctx.Chat.GetChannelUsers(channel).Except([user]).OrderByDescending(u => u.Rank) .Select(u => new ContextUsersS2CPacket.Entry( u.UserId, u.LegacyNameWithStatus, u.Colour, u.Rank, u.Permissions, true )) )); IEnumerable<Message> msgs = await ctx.Chat.Messages.GetMessages(channel.Name); foreach(Message msg in msgs) await ctx.Connection.Send(new ContextMessageS2CPacket(msg)); await ctx.Connection.Send(new ContextChannelsS2CPacket( ctx.Chat.Channels.GetChannels(user.Rank) .Select(c => new ContextChannelsS2CPacket.Entry(c.Name, c.HasPassword, c.IsTemporary)) )); ctx.Chat.Users.Add(user); ctx.Chat.ChannelUsers.Add(new Context.ChannelUserAssoc(user.UserId, channel.Name)); ctx.Chat.UserLastChannel[user.UserId] = channel; } finally { ctx.Chat.ContextAccess.Release(); } } catch(AuthFailedException ex) { ctx.Connection.Logger.ZLogWarning($"Failed to authenticate (expected): {ex}"); await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.AuthInvalid)); ctx.Connection.Dispose(); throw; } catch(Exception ex) { ctx.Connection.Logger.ZLogError($"Failed to authenticate (unexpected): {ex}"); await ctx.Connection.Send(new AuthFailS2CPacket(AuthFailS2CPacket.Reason.Exception)); ctx.Connection.Dispose(); throw; } } }