using SharpChat.Config; using SharpChat.Misuzu; using SharpChat.Packet; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; namespace SharpChat.PacketHandlers { public class AuthHandler : IChatPacketHandler { private readonly MisuzuClient Misuzu; private readonly ChatChannel DefaultChannel; private readonly CachedValue MaxMessageLength; private readonly CachedValue MaxConnections; public AuthHandler( MisuzuClient msz, ChatChannel defaultChannel, CachedValue maxMsgLength, CachedValue maxConns ) { Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel)); MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength)); MaxConnections = maxConns ?? throw new ArgumentNullException(nameof(maxConns)); } public bool IsMatch(ChatPacketHandlerContext ctx) { return ctx.CheckPacketId("1"); } public void Handle(ChatPacketHandlerContext ctx) { string[] args = ctx.SplitText(3); string authMethod = args.ElementAtOrDefault(1); if(string.IsNullOrWhiteSpace(authMethod)) { ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Dispose(); return; } string authToken = args.ElementAtOrDefault(2); if(string.IsNullOrWhiteSpace(authToken)) { ctx.Connection.Send(new AuthFailPacket(AuthFailReason.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]; } 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 AuthFailPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Dispose(); #if DEBUG throw; #else return; #endif } if(!fai.Success) { Logger.Debug($"<{ctx.Connection.Id}> Auth fail: {fai.Reason}"); ctx.Connection.Send(new AuthFailPacket(AuthFailReason.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 AuthFailPacket(AuthFailReason.AuthInvalid)); ctx.Connection.Dispose(); #if DEBUG throw; #else return; #endif } if(fbi.IsBanned && !fbi.HasExpired) { Logger.Write($"<{ctx.Connection.Id}> User is banned."); ctx.Connection.Send(new AuthFailPacket(AuthFailReason.Banned, fbi)); ctx.Connection.Dispose(); return; } await ctx.Chat.ContextAccess.WaitAsync(); try { ChatUser user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId); if(user == null) user = new ChatUser( fai.UserId, fai.UserName, fai.Colour, fai.Rank, fai.Permissions ); else ctx.Chat.UpdateUser( user, userName: fai.UserName, status: ChatUserStatus.Online, 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 AuthFailPacket(AuthFailReason.MaxSessions)); ctx.Connection.Dispose(); return; } ctx.Connection.BumpPing(); ctx.Connection.User = user; ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, $"Welcome to Flashii Chat, {user.UserName}!")); if(File.Exists("welcome.txt")) { IEnumerable 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 LegacyCommandResponse(LCR.WELCOME, false, line)); } ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength); } finally { ctx.Chat.ContextAccess.Release(); } }).Wait(); } } }