Removed internal ban handling and integrate with Misuzu.
This commit is contained in:
parent
5e3eecda8c
commit
36f3ff6385
14 changed files with 455 additions and 443 deletions
|
@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||||
LICENSE = LICENSE
|
LICENSE = LICENSE
|
||||||
Protocol.md = Protocol.md
|
Protocol.md = Protocol.md
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
|
start.sh = start.sh
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
|
|
|
@ -1,192 +0,0 @@
|
||||||
using SharpChat.Flashii;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SharpChat {
|
|
||||||
public interface IBan {
|
|
||||||
DateTimeOffset Expires { get; }
|
|
||||||
string ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BannedUser : IBan {
|
|
||||||
public long UserId { get; set; }
|
|
||||||
public DateTimeOffset Expires { get; set; }
|
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
public BannedUser() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public BannedUser(FlashiiBan fb) {
|
|
||||||
UserId = fb.UserId;
|
|
||||||
Expires = fb.Expires;
|
|
||||||
Username = fb.Username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() {
|
|
||||||
return Username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BannedIPAddress : IBan {
|
|
||||||
public IPAddress Address { get; set; }
|
|
||||||
public DateTimeOffset Expires { get; set; }
|
|
||||||
|
|
||||||
public BannedIPAddress() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public BannedIPAddress(FlashiiBan fb) {
|
|
||||||
Address = IPAddress.Parse(fb.UserIP);
|
|
||||||
Expires = fb.Expires;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() {
|
|
||||||
return Address.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BanManager : IDisposable {
|
|
||||||
private readonly List<IBan> BanList = new();
|
|
||||||
|
|
||||||
private readonly HttpClient HttpClient;
|
|
||||||
public readonly ChatContext Context;
|
|
||||||
|
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
|
||||||
|
|
||||||
public BanManager(HttpClient httpClient, ChatContext context) {
|
|
||||||
HttpClient = httpClient;
|
|
||||||
Context = context;
|
|
||||||
RefreshFlashiiBans().Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(ChatUser user, DateTimeOffset expires) {
|
|
||||||
if(expires <= DateTimeOffset.Now)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock(BanList) {
|
|
||||||
BannedUser ban = BanList.OfType<BannedUser>().FirstOrDefault(x => x.UserId == user.UserId);
|
|
||||||
|
|
||||||
if(ban == null)
|
|
||||||
Add(new BannedUser { UserId = user.UserId, Expires = expires, Username = user.Username });
|
|
||||||
else
|
|
||||||
ban.Expires = expires;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(IPAddress addr, DateTimeOffset expires) {
|
|
||||||
if(expires <= DateTimeOffset.Now)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock(BanList) {
|
|
||||||
BannedIPAddress ban = BanList.OfType<BannedIPAddress>().FirstOrDefault(x => x.Address.Equals(addr));
|
|
||||||
|
|
||||||
if(ban == null)
|
|
||||||
Add(new BannedIPAddress { Address = addr, Expires = expires });
|
|
||||||
else
|
|
||||||
ban.Expires = expires;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Add(IBan ban) {
|
|
||||||
if(ban == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock(BanList)
|
|
||||||
if(!BanList.Contains(ban))
|
|
||||||
BanList.Add(ban);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Remove(ChatUser user) {
|
|
||||||
lock(BanList)
|
|
||||||
BanList.RemoveAll(x => x is BannedUser ub && ub.UserId == user.UserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Remove(IPAddress addr) {
|
|
||||||
lock(BanList)
|
|
||||||
BanList.RemoveAll(x => x is BannedIPAddress ib && ib.Address.Equals(addr));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Remove(IBan ban) {
|
|
||||||
lock(BanList)
|
|
||||||
BanList.Remove(ban);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTimeOffset Check(ChatUser user) {
|
|
||||||
if(user == null)
|
|
||||||
return DateTimeOffset.MinValue;
|
|
||||||
|
|
||||||
lock(BanList)
|
|
||||||
return BanList.OfType<BannedUser>().Where(x => x.UserId == user.UserId).FirstOrDefault()?.Expires ?? DateTimeOffset.MinValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTimeOffset Check(IPAddress addr) {
|
|
||||||
if(addr == null)
|
|
||||||
return DateTimeOffset.MinValue;
|
|
||||||
|
|
||||||
lock(BanList)
|
|
||||||
return BanList.OfType<BannedIPAddress>().Where(x => x.Address.Equals(addr)).FirstOrDefault()?.Expires ?? DateTimeOffset.MinValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BannedUser GetUser(string username) {
|
|
||||||
if(username == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if(!long.TryParse(username, out long userId))
|
|
||||||
userId = 0;
|
|
||||||
|
|
||||||
lock(BanList)
|
|
||||||
return BanList.OfType<BannedUser>().FirstOrDefault(x => x.Username.ToLowerInvariant() == username.ToLowerInvariant() || (userId > 0 && x.UserId == userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public BannedIPAddress GetIPAddress(IPAddress addr) {
|
|
||||||
lock(BanList)
|
|
||||||
return BanList.OfType<BannedIPAddress>().FirstOrDefault(x => x.Address.Equals(addr));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveExpired() {
|
|
||||||
lock(BanList)
|
|
||||||
BanList.RemoveAll(x => x.Expires <= DateTimeOffset.Now);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RefreshFlashiiBans() {
|
|
||||||
IEnumerable<FlashiiBan> bans = await FlashiiBan.GetListAsync(HttpClient);
|
|
||||||
|
|
||||||
if(!bans.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock(BanList)
|
|
||||||
foreach(FlashiiBan fb in bans) {
|
|
||||||
if(!BanList.OfType<BannedUser>().Any(x => x.UserId == fb.UserId))
|
|
||||||
Add(new BannedUser(fb));
|
|
||||||
if(!BanList.OfType<BannedIPAddress>().Any(x => x.Address.ToString() == fb.UserIP))
|
|
||||||
Add(new BannedIPAddress(fb));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<IBan> All() {
|
|
||||||
lock(BanList)
|
|
||||||
return BanList.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
~BanManager() {
|
|
||||||
DoDispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
DoDispose();
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DoDispose() {
|
|
||||||
if(IsDisposed)
|
|
||||||
return;
|
|
||||||
IsDisposed = true;
|
|
||||||
|
|
||||||
BanList.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,9 +16,6 @@ namespace SharpChat {
|
||||||
public SockChatServer Server { get; }
|
public SockChatServer Server { get; }
|
||||||
public Timer BumpTimer { get; }
|
public Timer BumpTimer { get; }
|
||||||
|
|
||||||
public BanManager Bans { get; }
|
|
||||||
public readonly object BansAccess = new();
|
|
||||||
|
|
||||||
public ChannelManager Channels { get; }
|
public ChannelManager Channels { get; }
|
||||||
public UserManager Users { get; }
|
public UserManager Users { get; }
|
||||||
public ChatEventManager Events { get; }
|
public ChatEventManager Events { get; }
|
||||||
|
@ -28,7 +25,6 @@ namespace SharpChat {
|
||||||
public ChatContext(HttpClient httpClient, SockChatServer server) {
|
public ChatContext(HttpClient httpClient, SockChatServer server) {
|
||||||
HttpClient = httpClient;
|
HttpClient = httpClient;
|
||||||
Server = server;
|
Server = server;
|
||||||
Bans = new(httpClient, this);
|
|
||||||
Users = new(this);
|
Users = new(this);
|
||||||
Channels = new(this);
|
Channels = new(this);
|
||||||
Events = new(this);
|
Events = new(this);
|
||||||
|
@ -37,26 +33,13 @@ namespace SharpChat {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update() {
|
public void Update() {
|
||||||
lock(BansAccess)
|
|
||||||
Bans.RemoveExpired();
|
|
||||||
CheckPings();
|
CheckPings();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BanUser(ChatUser user, DateTimeOffset? until = null, bool banIPs = false, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
|
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
|
||||||
if(until.HasValue && until.Value <= DateTimeOffset.UtcNow)
|
if(duration > TimeSpan.Zero)
|
||||||
until = null;
|
user.Send(new ForceDisconnectPacket(ForceDisconnectReason.Banned, DateTimeOffset.Now + duration));
|
||||||
|
else
|
||||||
if(until.HasValue) {
|
|
||||||
user.Send(new ForceDisconnectPacket(ForceDisconnectReason.Banned, until.Value));
|
|
||||||
|
|
||||||
lock(BansAccess) {
|
|
||||||
Bans.Add(user, until.Value);
|
|
||||||
|
|
||||||
if(banIPs)
|
|
||||||
foreach(IPAddress ip in user.RemoteAddresses)
|
|
||||||
Bans.Add(ip, until.Value);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
user.Send(new ForceDisconnectPacket(ForceDisconnectReason.Kicked));
|
user.Send(new ForceDisconnectPacket(ForceDisconnectReason.Kicked));
|
||||||
|
|
||||||
user.Close();
|
user.Close();
|
||||||
|
@ -194,7 +177,6 @@ namespace SharpChat {
|
||||||
Events?.Dispose();
|
Events?.Dispose();
|
||||||
Channels?.Dispose();
|
Channels?.Dispose();
|
||||||
Users?.Dispose();
|
Users?.Dispose();
|
||||||
Bans?.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,12 +120,12 @@ namespace SharpChat {
|
||||||
public ChatUser() {
|
public ChatUser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChatUser(FlashiiAuth auth) {
|
public ChatUser(FlashiiAuthInfo auth) {
|
||||||
UserId = auth.UserId;
|
UserId = auth.UserId;
|
||||||
ApplyAuth(auth, true);
|
ApplyAuth(auth, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyAuth(FlashiiAuth auth, bool invalidateRestrictions = false) {
|
public void ApplyAuth(FlashiiAuthInfo auth, bool invalidateRestrictions = false) {
|
||||||
Username = auth.Username;
|
Username = auth.Username;
|
||||||
|
|
||||||
if(Status == ChatUserStatus.Offline)
|
if(Status == ChatUserStatus.Offline)
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SharpChat.Flashii {
|
|
||||||
public class FlashiiAuthRequest {
|
|
||||||
[JsonPropertyName(@"user_id")]
|
|
||||||
public long UserId { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName(@"token")]
|
|
||||||
public string Token { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName(@"ip")]
|
|
||||||
public string IPAddress { get; set; }
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public string Hash
|
|
||||||
=> string.Join(@"#", UserId, Token, IPAddress).GetSignedHash();
|
|
||||||
|
|
||||||
public byte[] GetJSON() {
|
|
||||||
return JsonSerializer.SerializeToUtf8Bytes(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FlashiiAuth {
|
|
||||||
[JsonPropertyName(@"success")]
|
|
||||||
public bool Success { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName(@"reason")]
|
|
||||||
public string Reason { get; set; } = @"none";
|
|
||||||
|
|
||||||
[JsonPropertyName(@"user_id")]
|
|
||||||
public long UserId { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName(@"username")]
|
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName(@"colour_raw")]
|
|
||||||
public int ColourRaw { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName(@"hierarchy")]
|
|
||||||
public int Rank { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName(@"is_silenced")]
|
|
||||||
public DateTimeOffset SilencedUntil { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName(@"perms")]
|
|
||||||
public ChatUserPermissions Permissions { get; set; }
|
|
||||||
|
|
||||||
public static async Task<FlashiiAuth> AttemptAsync(HttpClient httpClient, FlashiiAuthRequest authRequest) {
|
|
||||||
if(httpClient == null)
|
|
||||||
throw new ArgumentNullException(nameof(httpClient));
|
|
||||||
if(authRequest == null)
|
|
||||||
throw new ArgumentNullException(nameof(authRequest));
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
if(authRequest.UserId >= 10000)
|
|
||||||
return new FlashiiAuth {
|
|
||||||
Success = true,
|
|
||||||
UserId = authRequest.UserId,
|
|
||||||
Username = @"Misaka-" + (authRequest.UserId - 10000),
|
|
||||||
ColourRaw = (RNG.Next(0, 255) << 16) | (RNG.Next(0, 255) << 8) | RNG.Next(0, 255),
|
|
||||||
Rank = 0,
|
|
||||||
SilencedUntil = DateTimeOffset.MinValue,
|
|
||||||
Permissions = ChatUserPermissions.SendMessage | ChatUserPermissions.EditOwnMessage | ChatUserPermissions.DeleteOwnMessage,
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
HttpRequestMessage request = new(HttpMethod.Post, FlashiiUrls.AuthURL) {
|
|
||||||
Content = new ByteArrayContent(authRequest.GetJSON()),
|
|
||||||
Headers = {
|
|
||||||
{ @"X-SharpChat-Signature", authRequest.Hash },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpResponseMessage response = await httpClient.SendAsync(request);
|
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<FlashiiAuth>(
|
|
||||||
await response.Content.ReadAsByteArrayAsync()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
64
SharpChat/Flashii/FlashiiAuthInfo.cs
Normal file
64
SharpChat/Flashii/FlashiiAuthInfo.cs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharpChat.Flashii {
|
||||||
|
public class FlashiiAuthInfo {
|
||||||
|
[JsonPropertyName(@"success")]
|
||||||
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName(@"reason")]
|
||||||
|
public string Reason { get; set; } = @"none";
|
||||||
|
|
||||||
|
[JsonPropertyName(@"user_id")]
|
||||||
|
public long UserId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName(@"username")]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName(@"colour_raw")]
|
||||||
|
public int ColourRaw { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName(@"hierarchy")]
|
||||||
|
public int Rank { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName(@"is_silenced")]
|
||||||
|
public DateTimeOffset SilencedUntil { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName(@"perms")]
|
||||||
|
public ChatUserPermissions Permissions { get; set; }
|
||||||
|
|
||||||
|
private const string SIG_FMT = "verify#{0}#{1}#{2}";
|
||||||
|
|
||||||
|
public static async Task<FlashiiAuthInfo> VerifyAsync(HttpClient client, string method, string token, string ipAddr) {
|
||||||
|
if(client == null)
|
||||||
|
throw new ArgumentNullException(nameof(client));
|
||||||
|
|
||||||
|
method ??= string.Empty;
|
||||||
|
token ??= string.Empty;
|
||||||
|
ipAddr ??= string.Empty;
|
||||||
|
|
||||||
|
string sig = string.Format(SIG_FMT, method, token, ipAddr);
|
||||||
|
|
||||||
|
HttpRequestMessage req = new(HttpMethod.Post, FlashiiUrls.VerifyURL) {
|
||||||
|
Content = new FormUrlEncodedContent(new Dictionary<string, string> {
|
||||||
|
{ "method", method },
|
||||||
|
{ "token", token },
|
||||||
|
{ "ipaddr", ipAddr },
|
||||||
|
}),
|
||||||
|
Headers = {
|
||||||
|
{ @"X-SharpChat-Signature", sig.GetSignedHash() },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpResponseMessage res = await client.SendAsync(req);
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<FlashiiAuthInfo>(
|
||||||
|
await res.Content.ReadAsByteArrayAsync()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SharpChat.Flashii {
|
|
||||||
public class FlashiiBan {
|
|
||||||
private const string STRING = @"givemethebeans";
|
|
||||||
|
|
||||||
[JsonPropertyName(@"id")]
|
|
||||||
public int UserId { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName(@"ip")]
|
|
||||||
public string UserIP { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName(@"expires")]
|
|
||||||
public DateTimeOffset Expires { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName(@"username")]
|
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
public static async Task<IEnumerable<FlashiiBan>> GetListAsync(HttpClient httpClient) {
|
|
||||||
if(httpClient == null)
|
|
||||||
throw new ArgumentNullException(nameof(httpClient));
|
|
||||||
|
|
||||||
HttpRequestMessage request = new(HttpMethod.Get, FlashiiUrls.BansURL) {
|
|
||||||
Headers = {
|
|
||||||
{ @"X-SharpChat-Signature", STRING.GetSignedHash() },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpResponseMessage response = await httpClient.SendAsync(request);
|
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<IEnumerable<FlashiiBan>>(await response.Content.ReadAsByteArrayAsync());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
183
SharpChat/Flashii/FlashiiBanInfo.cs
Normal file
183
SharpChat/Flashii/FlashiiBanInfo.cs
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharpChat.Flashii {
|
||||||
|
public class FlashiiBanInfo {
|
||||||
|
[JsonPropertyName("is_ban")]
|
||||||
|
public bool IsBanned { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("user_id")]
|
||||||
|
public string UserId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("ip_addr")]
|
||||||
|
public string RemoteAddress { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_perma")]
|
||||||
|
public bool IsPermanent { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("expires")]
|
||||||
|
public DateTimeOffset ExpiresAt { get; set; }
|
||||||
|
|
||||||
|
// only populated in list request
|
||||||
|
[JsonPropertyName("user_name")]
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName(@"user_colour")]
|
||||||
|
public int UserColourRaw { get; set; }
|
||||||
|
|
||||||
|
public bool HasExpired => !IsPermanent && DateTimeOffset.UtcNow >= ExpiresAt;
|
||||||
|
|
||||||
|
public ChatColour UserColour => ChatColour.FromMisuzu(UserColourRaw);
|
||||||
|
|
||||||
|
private const string CHECK_SIG_FMT = "check#{0}#{1}#{2}#{3}";
|
||||||
|
private const string REVOKE_SIG_FMT = "revoke#{0}#{1}#{2}";
|
||||||
|
private const string CREATE_SIG_FMT = "create#{0}#{1}#{2}#{3}#{4}#{5}#{6}#{7}";
|
||||||
|
private const string LIST_SIG_FMT = "list#{0}";
|
||||||
|
|
||||||
|
public static async Task<FlashiiBanInfo> CheckAsync(HttpClient client, string userId = null, string ipAddr = null, bool userIdIsName = false) {
|
||||||
|
if(client == null)
|
||||||
|
throw new ArgumentNullException(nameof(client));
|
||||||
|
|
||||||
|
userId ??= string.Empty;
|
||||||
|
ipAddr ??= string.Empty;
|
||||||
|
|
||||||
|
string userIdIsNameStr = userIdIsName ? "1" : "0";
|
||||||
|
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
|
||||||
|
string url = string.Format(FlashiiUrls.BansCheckURL, Uri.EscapeDataString(userId), Uri.EscapeDataString(ipAddr), Uri.EscapeDataString(now), Uri.EscapeDataString(userIdIsNameStr));
|
||||||
|
string sig = string.Format(CHECK_SIG_FMT, now, userId, ipAddr, userIdIsNameStr);
|
||||||
|
|
||||||
|
HttpRequestMessage req = new(HttpMethod.Get, url) {
|
||||||
|
Headers = {
|
||||||
|
{ "X-SharpChat-Signature", sig.GetSignedHash() },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpResponseMessage res = await client.SendAsync(req);
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<FlashiiBanInfo>(
|
||||||
|
await res.Content.ReadAsByteArrayAsync()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<FlashiiBanInfo[]> GetListAsync(HttpClient client) {
|
||||||
|
if(client == null)
|
||||||
|
throw new ArgumentNullException(nameof(client));
|
||||||
|
|
||||||
|
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
|
||||||
|
string url = string.Format(FlashiiUrls.BansListURL, Uri.EscapeDataString(now));
|
||||||
|
string sig = string.Format(LIST_SIG_FMT, now);
|
||||||
|
|
||||||
|
HttpRequestMessage req = new(HttpMethod.Get, url) {
|
||||||
|
Headers = {
|
||||||
|
{ "X-SharpChat-Signature", sig.GetSignedHash() },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpResponseMessage res = await client.SendAsync(req);
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<FlashiiBanInfo[]>(
|
||||||
|
await res.Content.ReadAsByteArrayAsync()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RevokeKind {
|
||||||
|
UserId,
|
||||||
|
RemoteAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> RevokeAsync(HttpClient client, RevokeKind kind) {
|
||||||
|
if(client == null)
|
||||||
|
throw new ArgumentNullException(nameof(client));
|
||||||
|
|
||||||
|
string type = kind switch {
|
||||||
|
RevokeKind.UserId => "user",
|
||||||
|
RevokeKind.RemoteAddress => "addr",
|
||||||
|
_ => throw new ArgumentException("Invalid kind specified.", nameof(kind)),
|
||||||
|
};
|
||||||
|
|
||||||
|
string target = kind switch {
|
||||||
|
RevokeKind.UserId => UserId,
|
||||||
|
RevokeKind.RemoteAddress => RemoteAddress,
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
|
||||||
|
string url = string.Format(FlashiiUrls.BansRevokeURL, Uri.EscapeDataString(type), Uri.EscapeDataString(target), Uri.EscapeDataString(now));
|
||||||
|
string sig = string.Format(REVOKE_SIG_FMT, now, type, target);
|
||||||
|
|
||||||
|
HttpRequestMessage req = new(HttpMethod.Delete, url) {
|
||||||
|
Headers = {
|
||||||
|
{ "X-SharpChat-Signature", sig.GetSignedHash() },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpResponseMessage res = await client.SendAsync(req);
|
||||||
|
|
||||||
|
if(res.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
res.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
return res.StatusCode == HttpStatusCode.NoContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task CreateAsync(
|
||||||
|
HttpClient client,
|
||||||
|
string targetId,
|
||||||
|
string targetAddr,
|
||||||
|
string modId,
|
||||||
|
string modAddr,
|
||||||
|
TimeSpan duration,
|
||||||
|
string reason
|
||||||
|
) {
|
||||||
|
if(client == null)
|
||||||
|
throw new ArgumentNullException(nameof(client));
|
||||||
|
if(string.IsNullOrWhiteSpace(targetAddr))
|
||||||
|
throw new ArgumentNullException(nameof(targetAddr));
|
||||||
|
if(string.IsNullOrWhiteSpace(modAddr))
|
||||||
|
throw new ArgumentNullException(nameof(modAddr));
|
||||||
|
if(duration <= TimeSpan.Zero)
|
||||||
|
return;
|
||||||
|
|
||||||
|
modId ??= string.Empty;
|
||||||
|
targetId ??= string.Empty;
|
||||||
|
reason ??= string.Empty;
|
||||||
|
|
||||||
|
string isPerma = duration == TimeSpan.MaxValue ? "1" : "0";
|
||||||
|
string durationStr = duration == TimeSpan.MaxValue ? "-1" : duration.TotalSeconds.ToString();
|
||||||
|
|
||||||
|
string now = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
|
||||||
|
string sig = string.Format(
|
||||||
|
CREATE_SIG_FMT,
|
||||||
|
now, targetId, targetAddr,
|
||||||
|
modId, modAddr,
|
||||||
|
durationStr, isPerma, reason
|
||||||
|
);
|
||||||
|
|
||||||
|
HttpRequestMessage req = new(HttpMethod.Post, FlashiiUrls.BansCreateURL) {
|
||||||
|
Headers = {
|
||||||
|
{ "X-SharpChat-Signature", sig.GetSignedHash() },
|
||||||
|
},
|
||||||
|
Content = new FormUrlEncodedContent(new Dictionary<string, string> {
|
||||||
|
{ "t", now },
|
||||||
|
{ "ui", targetId },
|
||||||
|
{ "ua", targetAddr },
|
||||||
|
{ "mi", modId },
|
||||||
|
{ "ma", modAddr },
|
||||||
|
{ "d", durationStr },
|
||||||
|
{ "p", isPerma },
|
||||||
|
{ "r", reason },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpResponseMessage res = await client.SendAsync(req);
|
||||||
|
|
||||||
|
res.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,21 +2,32 @@
|
||||||
|
|
||||||
namespace SharpChat.Flashii {
|
namespace SharpChat.Flashii {
|
||||||
public static class FlashiiUrls {
|
public static class FlashiiUrls {
|
||||||
private const string BASE_URL_FILE = @"msz_url.txt";
|
private const string BASE_URL_FILE = "msz_url.txt";
|
||||||
private const string BASE_URL_FALLBACK = @"https://flashii.net";
|
private const string BASE_URL_FALLBACK = "https://flashii.net";
|
||||||
|
|
||||||
private const string AUTH = @"/_sockchat/verify";
|
private const string VERIFY = "/_sockchat/verify";
|
||||||
private const string BANS = @"/_sockchat/bans";
|
private const string BUMP = "/_sockchat/bump";
|
||||||
private const string BUMP = @"/_sockchat/bump";
|
|
||||||
|
private const string BANS_CHECK = "/_sockchat/bans/check?u={0}&a={1}&x={2}&n={3}";
|
||||||
|
private const string BANS_CREATE = "/_sockchat/bans/create";
|
||||||
|
private const string BANS_REVOKE = "/_sockchat/bans/revoke?t={0}&s={1}&x={2}";
|
||||||
|
private const string BANS_LIST = "/_sockchat/bans/list?x={0}";
|
||||||
|
|
||||||
public static string AuthURL { get; }
|
|
||||||
public static string BansURL { get; }
|
|
||||||
public static string BumpURL { get; }
|
public static string BumpURL { get; }
|
||||||
|
public static string VerifyURL { get; }
|
||||||
|
|
||||||
|
public static string BansCheckURL { get; }
|
||||||
|
public static string BansCreateURL { get; }
|
||||||
|
public static string BansRevokeURL { get; }
|
||||||
|
public static string BansListURL { get; }
|
||||||
|
|
||||||
static FlashiiUrls() {
|
static FlashiiUrls() {
|
||||||
AuthURL = GetURL(AUTH);
|
|
||||||
BansURL = GetURL(BANS);
|
|
||||||
BumpURL = GetURL(BUMP);
|
BumpURL = GetURL(BUMP);
|
||||||
|
VerifyURL = GetURL(VERIFY);
|
||||||
|
BansCheckURL = GetURL(BANS_CHECK);
|
||||||
|
BansCreateURL = GetURL(BANS_CREATE);
|
||||||
|
BansRevokeURL = GetURL(BANS_REVOKE);
|
||||||
|
BansListURL = GetURL(BANS_LIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetBaseURL() {
|
public static string GetBaseURL() {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using SharpChat.Flashii;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
@ -11,16 +12,13 @@ namespace SharpChat.Packet {
|
||||||
|
|
||||||
public class AuthFailPacket : ServerPacket {
|
public class AuthFailPacket : ServerPacket {
|
||||||
public AuthFailReason Reason { get; private set; }
|
public AuthFailReason Reason { get; private set; }
|
||||||
public DateTimeOffset Expires { get; private set; }
|
public FlashiiBanInfo BanInfo { get; private set; }
|
||||||
|
|
||||||
public AuthFailPacket(AuthFailReason reason, DateTimeOffset? expires = null) {
|
public AuthFailPacket(AuthFailReason reason, FlashiiBanInfo fbi = null) {
|
||||||
Reason = reason;
|
Reason = reason;
|
||||||
|
|
||||||
if(reason == AuthFailReason.Banned) {
|
if(reason == AuthFailReason.Banned)
|
||||||
if(!expires.HasValue)
|
BanInfo = fbi ?? throw new ArgumentNullException(nameof(fbi));
|
||||||
throw new ArgumentNullException(nameof(expires));
|
|
||||||
Expires = expires.Value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<string> Pack() {
|
public override IEnumerable<string> Pack() {
|
||||||
|
@ -45,10 +43,10 @@ namespace SharpChat.Packet {
|
||||||
if(Reason == AuthFailReason.Banned) {
|
if(Reason == AuthFailReason.Banned) {
|
||||||
sb.Append('\t');
|
sb.Append('\t');
|
||||||
|
|
||||||
if(Expires == DateTimeOffset.MaxValue)
|
if(BanInfo.IsPermanent)
|
||||||
sb.Append(@"-1");
|
sb.Append("-1");
|
||||||
else
|
else
|
||||||
sb.Append(Expires.ToUnixTimeSeconds());
|
sb.Append(BanInfo.ExpiresAt.ToUnixTimeSeconds());
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return sb.ToString();
|
yield return sb.ToString();
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
using System;
|
using SharpChat.Flashii;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace SharpChat.Packet {
|
namespace SharpChat.Packet {
|
||||||
public class BanListPacket : ServerPacket {
|
public class BanListPacket : ServerPacket {
|
||||||
public IEnumerable<IBan> Bans { get; private set; }
|
public IEnumerable<FlashiiBanInfo> Bans { get; private set; }
|
||||||
|
|
||||||
public BanListPacket(IEnumerable<IBan> bans) {
|
public BanListPacket(IEnumerable<FlashiiBanInfo> bans) {
|
||||||
Bans = bans ?? throw new ArgumentNullException(nameof(bans));
|
Bans = bans ?? throw new ArgumentNullException(nameof(bans));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,8 +20,10 @@ namespace SharpChat.Packet {
|
||||||
sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
|
sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
|
||||||
sb.Append("\t-1\t0\fbanlist\f");
|
sb.Append("\t-1\t0\fbanlist\f");
|
||||||
|
|
||||||
foreach(IBan ban in Bans)
|
foreach(FlashiiBanInfo ban in Bans) {
|
||||||
sb.AppendFormat(@"<a href=""javascript:void(0);"" onclick=""Chat.SendMessageWrapper('/unban '+ this.innerHTML);"">{0}</a>, ", ban);
|
string banStr = string.IsNullOrEmpty(ban.UserName) ? ban.RemoteAddress : ban.UserName;
|
||||||
|
sb.AppendFormat(@"<a href=""javascript:void(0);"" onclick=""Chat.SendMessageWrapper('/unban '+ this.innerHTML);"">{0}</a>, ", banStr);
|
||||||
|
}
|
||||||
|
|
||||||
if(Bans.Any())
|
if(Bans.Any())
|
||||||
sb.Length -= 2;
|
sb.Length -= 2;
|
||||||
|
|
|
@ -31,7 +31,10 @@ namespace SharpChat.Packet {
|
||||||
|
|
||||||
if(Reason == ForceDisconnectReason.Banned) {
|
if(Reason == ForceDisconnectReason.Banned) {
|
||||||
sb.Append('\t');
|
sb.Append('\t');
|
||||||
sb.Append(Expires.ToUnixTimeSeconds());
|
if(Expires.Year >= 2100)
|
||||||
|
sb.Append("-1");
|
||||||
|
else
|
||||||
|
sb.Append(Expires.ToUnixTimeSeconds());
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return sb.ToString();
|
yield return sb.ToString();
|
||||||
|
|
|
@ -11,6 +11,7 @@ using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SharpChat {
|
namespace SharpChat {
|
||||||
public class SockChatServer : IDisposable {
|
public class SockChatServer : IDisposable {
|
||||||
|
@ -19,11 +20,9 @@ namespace SharpChat {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
public const int MAX_CONNECTIONS = 9001;
|
public const int MAX_CONNECTIONS = 9001;
|
||||||
public const int FLOOD_KICK_LENGTH = 5;
|
public const int FLOOD_KICK_LENGTH = 5;
|
||||||
public const bool ENABLE_TYPING_EVENT = true;
|
|
||||||
#else
|
#else
|
||||||
public const int MAX_CONNECTIONS = 5;
|
public const int MAX_CONNECTIONS = 5;
|
||||||
public const int FLOOD_KICK_LENGTH = 30;
|
public const int FLOOD_KICK_LENGTH = 30;
|
||||||
public const bool ENABLE_TYPING_EVENT = false;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
@ -144,14 +143,25 @@ namespace SharpChat {
|
||||||
sess.User.RateLimiter.AddTimePoint();
|
sess.User.RateLimiter.AddTimePoint();
|
||||||
|
|
||||||
if(sess.User.RateLimiter.State == ChatRateLimitState.Kick) {
|
if(sess.User.RateLimiter.State == ChatRateLimitState.Kick) {
|
||||||
Context.BanUser(sess.User, DateTimeOffset.UtcNow.AddSeconds(FLOOD_KICK_LENGTH), false, UserDisconnectReason.Flood);
|
Task.Run(async () => {
|
||||||
|
TimeSpan duration = TimeSpan.FromSeconds(FLOOD_KICK_LENGTH);
|
||||||
|
|
||||||
|
await FlashiiBanInfo.CreateAsync(
|
||||||
|
HttpClient,
|
||||||
|
sess.User.UserId.ToString(), sess.RemoteAddress.ToString(),
|
||||||
|
string.Empty, "::1",
|
||||||
|
duration,
|
||||||
|
"Kicked from chat for flood protection."
|
||||||
|
);
|
||||||
|
|
||||||
|
Context.BanUser(sess.User, duration, UserDisconnectReason.Flood);
|
||||||
|
}).Wait();
|
||||||
return;
|
return;
|
||||||
} else if(sess.User.RateLimiter.State == ChatRateLimitState.Warning)
|
} else if(sess.User.RateLimiter.State == ChatRateLimitState.Warning)
|
||||||
sess.User.Send(new FloodWarningPacket()); // make it so this thing only sends once
|
sess.User.Send(new FloodWarningPacket());
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] args = msg.Split('\t');
|
string[] args = msg.Split('\t');
|
||||||
|
|
||||||
if(args.Length < 1)
|
if(args.Length < 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -168,58 +178,80 @@ namespace SharpChat {
|
||||||
if(sess.User != null)
|
if(sess.User != null)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
DateTimeOffset aBanned;
|
string authMethod = args.ElementAtOrDefault(1);
|
||||||
lock(Context.BansAccess)
|
if(string.IsNullOrWhiteSpace(authMethod)) {
|
||||||
aBanned = Context.Bans.Check(sess.RemoteAddress);
|
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
||||||
|
|
||||||
if(aBanned > DateTimeOffset.UtcNow) {
|
|
||||||
sess.Send(new AuthFailPacket(AuthFailReason.Banned, aBanned));
|
|
||||||
sess.Dispose();
|
sess.Dispose();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(args.Length < 3 || !long.TryParse(args[1], out long aUserId))
|
string authToken = args.ElementAtOrDefault(2);
|
||||||
|
if(string.IsNullOrWhiteSpace(authToken)) {
|
||||||
|
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
||||||
|
sess.Dispose();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
FlashiiAuth.AttemptAsync(HttpClient, new FlashiiAuthRequest {
|
if(authMethod.All(c => c is >= '0' and <= '9') && authToken.Contains(':')) {
|
||||||
UserId = aUserId,
|
string[] tokenParts = authToken.Split(':', 2);
|
||||||
Token = args[2],
|
authMethod = tokenParts[0];
|
||||||
IPAddress = sess.RemoteAddress.ToString(),
|
authToken = tokenParts[1];
|
||||||
}).ContinueWith(authTask => {
|
}
|
||||||
if(authTask.IsFaulted) {
|
|
||||||
Logger.Write($@"<{sess.Id}> Auth task fail: {authTask.Exception}");
|
Task.Run(async () => {
|
||||||
|
FlashiiAuthInfo fai;
|
||||||
|
string ipAddr = sess.RemoteAddress.ToString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
fai = await FlashiiAuthInfo.VerifyAsync(HttpClient, authMethod, authToken, ipAddr);
|
||||||
|
} catch(Exception ex) {
|
||||||
|
Logger.Write($@"<{sess.Id}> Failed to authenticate: {ex}");
|
||||||
|
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
||||||
|
sess.Dispose();
|
||||||
|
#if DEBUG
|
||||||
|
throw;
|
||||||
|
#else
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!fai.Success) {
|
||||||
|
Logger.Debug($@"<{sess.Id}> Auth fail: {fai.Reason}");
|
||||||
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
||||||
sess.Dispose();
|
sess.Dispose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FlashiiAuth auth = authTask.Result;
|
FlashiiBanInfo fbi;
|
||||||
|
try {
|
||||||
if(!auth.Success) {
|
fbi = await FlashiiBanInfo.CheckAsync(HttpClient, fai.UserId.ToString(), ipAddr);
|
||||||
Logger.Debug($@"<{sess.Id}> Auth fail: {auth.Reason}");
|
} catch(Exception ex) {
|
||||||
|
Logger.Write($@"<{sess.Id}> Failed auth ban check: {ex}");
|
||||||
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
||||||
sess.Dispose();
|
sess.Dispose();
|
||||||
|
#if DEBUG
|
||||||
|
throw;
|
||||||
|
#else
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fbi.IsBanned && !fbi.HasExpired) {
|
||||||
|
Logger.Write($@"<{sess.Id}> User is banned.");
|
||||||
|
sess.Send(new AuthFailPacket(AuthFailReason.Banned, fbi));
|
||||||
|
sess.Dispose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatUser aUser = Context.Users.Get(auth.UserId);
|
ChatUser aUser = Context.Users.Get(fai.UserId);
|
||||||
|
|
||||||
if(aUser == null)
|
if(aUser == null)
|
||||||
aUser = new ChatUser(auth);
|
aUser = new ChatUser(fai);
|
||||||
else {
|
else {
|
||||||
aUser.ApplyAuth(auth);
|
aUser.ApplyAuth(fai);
|
||||||
aUser.Channel?.Send(new UserUpdatePacket(aUser));
|
aUser.Channel?.Send(new UserUpdatePacket(aUser));
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(Context.BansAccess)
|
|
||||||
aBanned = Context.Bans.Check(aUser);
|
|
||||||
|
|
||||||
if(aBanned > DateTimeOffset.Now) {
|
|
||||||
sess.Send(new AuthFailPacket(AuthFailReason.Banned, aBanned));
|
|
||||||
sess.Dispose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enforce a maximum amount of connections per user
|
// Enforce a maximum amount of connections per user
|
||||||
if(aUser.SessionCount >= MAX_CONNECTIONS) {
|
if(aUser.SessionCount >= MAX_CONNECTIONS) {
|
||||||
sess.Send(new AuthFailPacket(AuthFailReason.MaxSessions));
|
sess.Send(new AuthFailPacket(AuthFailReason.MaxSessions));
|
||||||
|
@ -243,7 +275,7 @@ namespace SharpChat {
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.HandleJoin(aUser, Context.Channels.DefaultChannel, sess);
|
Context.HandleJoin(aUser, Context.Channels.DefaultChannel, sess);
|
||||||
});
|
}).Wait();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "2":
|
case "2":
|
||||||
|
@ -287,7 +319,7 @@ namespace SharpChat {
|
||||||
IChatMessage message = null;
|
IChatMessage message = null;
|
||||||
|
|
||||||
if(messageText[0] == '/') {
|
if(messageText[0] == '/') {
|
||||||
message = HandleV1Command(messageText, mUser, mChannel);
|
message = HandleV1Command(messageText, mUser, mChannel, sess);
|
||||||
|
|
||||||
if(message == null)
|
if(message == null)
|
||||||
break;
|
break;
|
||||||
|
@ -307,7 +339,7 @@ namespace SharpChat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IChatMessage HandleV1Command(string message, ChatUser user, ChatChannel channel) {
|
public IChatMessage HandleV1Command(string message, ChatUser user, ChatChannel channel, ChatUserSession sess) {
|
||||||
string[] parts = message[1..].Split(' ');
|
string[] parts = message[1..].Split(' ');
|
||||||
string commandName = parts[0].Replace(@".", string.Empty).ToLowerInvariant();
|
string commandName = parts[0].Replace(@".", string.Empty).ToLowerInvariant();
|
||||||
|
|
||||||
|
@ -640,10 +672,13 @@ namespace SharpChat {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatUser banUser;
|
string banUserTarget = parts.ElementAtOrDefault(1);
|
||||||
|
string banDurationStr = parts.ElementAtOrDefault(2);
|
||||||
|
int banReasonIndex = 2;
|
||||||
|
ChatUser banUser = null;
|
||||||
|
|
||||||
if(parts.Length < 2 || (banUser = Context.Users.Get(parts[1])) == null) {
|
if(banUserTarget == null || (banUser = Context.Users.Get(banUserTarget)) == null) {
|
||||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, parts.Length < 2 ? @"User" : parts[1]));
|
user.Send(new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, banUser == null ? @"User" : banUserTarget));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -652,24 +687,44 @@ namespace SharpChat {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(Context.BansAccess)
|
TimeSpan duration = isBanning ? TimeSpan.MaxValue : TimeSpan.Zero;
|
||||||
if(Context.Bans.Check(banUser) > DateTimeOffset.Now) {
|
if(!string.IsNullOrWhiteSpace(banDurationStr) && double.TryParse(banDurationStr, out double durationSeconds)) {
|
||||||
user.Send(new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.DisplayName));
|
if(durationSeconds < 0) {
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTimeOffset? banUntil = isBanning ? (DateTimeOffset?)DateTimeOffset.MaxValue : null;
|
|
||||||
|
|
||||||
if(parts.Length > 2) {
|
|
||||||
if(!double.TryParse(parts[2], out double silenceSeconds)) {
|
|
||||||
user.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
|
user.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
banUntil = DateTimeOffset.UtcNow.AddSeconds(silenceSeconds);
|
duration = TimeSpan.FromSeconds(durationSeconds);
|
||||||
|
++banReasonIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.BanUser(banUser, banUntil, isBanning);
|
if(duration <= TimeSpan.Zero) {
|
||||||
|
Context.BanUser(banUser, duration);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
string banReason = string.Join(' ', parts.Skip(banReasonIndex));
|
||||||
|
|
||||||
|
Task.Run(async () => {
|
||||||
|
// obviously it makes no sense to only check for one ip address but that's current misuzu limitations
|
||||||
|
FlashiiBanInfo fbi = await FlashiiBanInfo.CheckAsync(
|
||||||
|
HttpClient, banUser.UserId.ToString(), banUser.RemoteAddresses.First().ToString()
|
||||||
|
);
|
||||||
|
|
||||||
|
if(fbi.IsBanned && !fbi.HasExpired) {
|
||||||
|
user.Send(new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.DisplayName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await FlashiiBanInfo.CreateAsync(
|
||||||
|
HttpClient,
|
||||||
|
banUser.UserId.ToString(), banUser.RemoteAddresses.First().ToString(),
|
||||||
|
user.UserId.ToString(), sess.RemoteAddress.ToString(),
|
||||||
|
duration, banReason
|
||||||
|
);
|
||||||
|
|
||||||
|
Context.BanUser(banUser, duration);
|
||||||
|
}).Wait();
|
||||||
break;
|
break;
|
||||||
case @"pardon":
|
case @"pardon":
|
||||||
case @"unban":
|
case @"unban":
|
||||||
|
@ -678,24 +733,36 @@ namespace SharpChat {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(parts.Length < 2) {
|
bool unbanUserTargetIsName = true;
|
||||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, string.Empty));
|
string unbanUserTarget = parts.ElementAtOrDefault(1);
|
||||||
|
if(string.IsNullOrWhiteSpace(unbanUserTarget)) {
|
||||||
|
user.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
BannedUser unbanUser;
|
ChatUser unbanUser = Context.Users.Get(unbanUserTarget);
|
||||||
lock(Context.BansAccess)
|
if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) {
|
||||||
unbanUser = Context.Bans.GetUser(parts[1]);
|
unbanUserTargetIsName = false;
|
||||||
|
unbanUser = Context.Users.Get(unbanUserId);
|
||||||
if(unbanUser == null || unbanUser.Expires <= DateTimeOffset.Now) {
|
|
||||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUser?.Username ?? parts[1]));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(Context.BansAccess)
|
if(unbanUser != null)
|
||||||
Context.Bans.Remove(unbanUser);
|
unbanUserTarget = unbanUser.UserId.ToString();
|
||||||
|
|
||||||
user.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanUser));
|
Task.Run(async () => {
|
||||||
|
FlashiiBanInfo banInfo = await FlashiiBanInfo.CheckAsync(HttpClient, unbanUserTarget, userIdIsName: unbanUserTargetIsName);
|
||||||
|
|
||||||
|
if(!banInfo.IsBanned || banInfo.HasExpired) {
|
||||||
|
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wasBanned = await banInfo.RevokeAsync(HttpClient, FlashiiBanInfo.RevokeKind.UserId);
|
||||||
|
if(wasBanned)
|
||||||
|
user.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanUserTarget));
|
||||||
|
else
|
||||||
|
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUserTarget));
|
||||||
|
}).Wait();
|
||||||
break;
|
break;
|
||||||
case @"pardonip":
|
case @"pardonip":
|
||||||
case @"unbanip":
|
case @"unbanip":
|
||||||
|
@ -704,21 +771,28 @@ namespace SharpChat {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(parts.Length < 2 || !IPAddress.TryParse(parts[1], out IPAddress unbanIP)) {
|
string unbanAddrTarget = parts.ElementAtOrDefault(1);
|
||||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, string.Empty));
|
if(string.IsNullOrWhiteSpace(unbanAddrTarget) || !IPAddress.TryParse(unbanAddrTarget, out IPAddress unbanAddr)) {
|
||||||
|
user.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(Context.BansAccess) {
|
unbanAddrTarget = unbanAddr.ToString();
|
||||||
if(Context.Bans.Check(unbanIP) <= DateTimeOffset.Now) {
|
|
||||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanIP));
|
Task.Run(async () => {
|
||||||
break;
|
FlashiiBanInfo banInfo = await FlashiiBanInfo.CheckAsync(HttpClient, ipAddr: unbanAddrTarget);
|
||||||
|
|
||||||
|
if(!banInfo.IsBanned || banInfo.HasExpired) {
|
||||||
|
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.Bans.Remove(unbanIP);
|
bool wasBanned = await banInfo.RevokeAsync(HttpClient, FlashiiBanInfo.RevokeKind.RemoteAddress);
|
||||||
}
|
if(wasBanned)
|
||||||
|
user.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanAddrTarget));
|
||||||
user.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanIP));
|
else
|
||||||
|
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget));
|
||||||
|
}).Wait();
|
||||||
break;
|
break;
|
||||||
case @"bans": // gets a list of bans
|
case @"bans": // gets a list of bans
|
||||||
case @"banned":
|
case @"banned":
|
||||||
|
@ -727,8 +801,11 @@ namespace SharpChat {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock(Context.BansAccess)
|
Task.Run(async () => {
|
||||||
user.Send(new BanListPacket(Context.Bans.All()));
|
user.Send(new BanListPacket(
|
||||||
|
await FlashiiBanInfo.GetListAsync(HttpClient)
|
||||||
|
));
|
||||||
|
}).Wait();
|
||||||
break;
|
break;
|
||||||
case @"silence": // silence a user
|
case @"silence": // silence a user
|
||||||
if(!user.Can(ChatUserPermissions.SilenceUser)) {
|
if(!user.Can(ChatUserPermissions.SilenceUser)) {
|
||||||
|
|
6
start.sh
Normal file
6
start.sh
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
|
export LANG=en_US.UTF-8
|
||||||
|
|
||||||
|
dotnet run --project SharpChat -c Release
|
Loading…
Reference in a new issue