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 : IPacketHandler { private readonly MisuzuClient Misuzu; private readonly ChannelInfo DefaultChannel; private readonly CachedValue MaxMessageLength; private readonly CachedValue MaxConnections; public AuthHandler( MisuzuClient msz, ChannelInfo? defaultChannel, CachedValue maxMsgLength, CachedValue maxConns ) { Misuzu = msz; DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel)); MaxMessageLength = maxMsgLength; MaxConnections = maxConns; } public bool IsMatch(PacketHandlerContext ctx) { return ctx.CheckPacketId("1"); } public void Handle(PacketHandlerContext ctx) { string[] args = ctx.SplitText(3); string? authMethod = args.ElementAtOrDefault(1); if(string.IsNullOrWhiteSpace(authMethod)) { ctx.Connection.Send(new AuthFailPacket(AuthFailPacket.FailReason.AuthInvalid)); ctx.Connection.Dispose(); return; } string? authToken = args.ElementAtOrDefault(2); if(string.IsNullOrWhiteSpace(authToken)) { ctx.Connection.Send(new AuthFailPacket(AuthFailPacket.FailReason.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(AuthFailPacket.FailReason.AuthInvalid)); ctx.Connection.Dispose(); #if DEBUG throw; #else return; #endif } if(fai == null) { Logger.Debug($"<{ctx.Connection.Id}> Auth fail: "); ctx.Connection.Send(new AuthFailPacket(AuthFailPacket.FailReason.Null)); ctx.Connection.Dispose(); return; } if(!fai.Success) { Logger.Debug($"<{ctx.Connection.Id}> Auth fail: {fai.Reason}"); ctx.Connection.Send(new AuthFailPacket(AuthFailPacket.FailReason.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(AuthFailPacket.FailReason.AuthInvalid)); ctx.Connection.Dispose(); #if DEBUG throw; #else return; #endif } if(fbi == null) { Logger.Debug($"<{ctx.Connection.Id}> Ban check fail: "); ctx.Connection.Send(new AuthFailPacket(AuthFailPacket.FailReason.Null)); ctx.Connection.Dispose(); return; } if(fbi.IsBanned && !fbi.HasExpired) { Logger.Write($"<{ctx.Connection.Id}> User is banned."); ctx.Connection.Send(new AuthFailPacket(fbi.ExpiresAt)); ctx.Connection.Dispose(); return; } await ctx.Chat.ContextAccess.WaitAsync(); try { UserInfo? user = ctx.Chat.Users.Get(fai.UserId); if(user == null) user = new UserInfo( fai.UserId, fai.UserName ?? string.Empty, fai.Colour, fai.Rank, fai.Permissions, isSuper: fai.IsSuper ); else ctx.Chat.UpdateUser( user, userName: fai.UserName, colour: fai.Colour, rank: fai.Rank, perms: fai.Permissions, isSuper: fai.IsSuper ); // Enforce a maximum amount of connections per user if(ctx.Chat.Connections.Count(conn => conn.User == user) >= MaxConnections) { ctx.Connection.Send(new AuthFailPacket(AuthFailPacket.FailReason.MaxSessions)); ctx.Connection.Dispose(); return; } ctx.Connection.BumpPing(); ctx.Connection.User = user; ctx.Connection.Send(new MOTDPacket($"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 MOTDPacket(line)); } ctx.Chat.HandleJoin(user, DefaultChannel, ctx.Connection, MaxMessageLength); } finally { ctx.Chat.ContextAccess.Release(); } }).Wait(); } } }