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
|
||||
Protocol.md = Protocol.md
|
||||
README.md = README.md
|
||||
start.sh = start.sh
|
||||
EndProjectSection
|
||||
EndProject
|
||||
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 Timer BumpTimer { get; }
|
||||
|
||||
public BanManager Bans { get; }
|
||||
public readonly object BansAccess = new();
|
||||
|
||||
public ChannelManager Channels { get; }
|
||||
public UserManager Users { get; }
|
||||
public ChatEventManager Events { get; }
|
||||
|
@ -28,7 +25,6 @@ namespace SharpChat {
|
|||
public ChatContext(HttpClient httpClient, SockChatServer server) {
|
||||
HttpClient = httpClient;
|
||||
Server = server;
|
||||
Bans = new(httpClient, this);
|
||||
Users = new(this);
|
||||
Channels = new(this);
|
||||
Events = new(this);
|
||||
|
@ -37,26 +33,13 @@ namespace SharpChat {
|
|||
}
|
||||
|
||||
public void Update() {
|
||||
lock(BansAccess)
|
||||
Bans.RemoveExpired();
|
||||
CheckPings();
|
||||
}
|
||||
|
||||
public void BanUser(ChatUser user, DateTimeOffset? until = null, bool banIPs = false, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
|
||||
if(until.HasValue && until.Value <= DateTimeOffset.UtcNow)
|
||||
until = null;
|
||||
|
||||
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
|
||||
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
|
||||
if(duration > TimeSpan.Zero)
|
||||
user.Send(new ForceDisconnectPacket(ForceDisconnectReason.Banned, DateTimeOffset.Now + duration));
|
||||
else
|
||||
user.Send(new ForceDisconnectPacket(ForceDisconnectReason.Kicked));
|
||||
|
||||
user.Close();
|
||||
|
@ -194,7 +177,6 @@ namespace SharpChat {
|
|||
Events?.Dispose();
|
||||
Channels?.Dispose();
|
||||
Users?.Dispose();
|
||||
Bans?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,12 +120,12 @@ namespace SharpChat {
|
|||
public ChatUser() {
|
||||
}
|
||||
|
||||
public ChatUser(FlashiiAuth auth) {
|
||||
public ChatUser(FlashiiAuthInfo auth) {
|
||||
UserId = auth.UserId;
|
||||
ApplyAuth(auth, true);
|
||||
}
|
||||
|
||||
public void ApplyAuth(FlashiiAuth auth, bool invalidateRestrictions = false) {
|
||||
public void ApplyAuth(FlashiiAuthInfo auth, bool invalidateRestrictions = false) {
|
||||
Username = auth.Username;
|
||||
|
||||
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 {
|
||||
public static class FlashiiUrls {
|
||||
private const string BASE_URL_FILE = @"msz_url.txt";
|
||||
private const string BASE_URL_FALLBACK = @"https://flashii.net";
|
||||
private const string BASE_URL_FILE = "msz_url.txt";
|
||||
private const string BASE_URL_FALLBACK = "https://flashii.net";
|
||||
|
||||
private const string AUTH = @"/_sockchat/verify";
|
||||
private const string BANS = @"/_sockchat/bans";
|
||||
private const string BUMP = @"/_sockchat/bump";
|
||||
private const string VERIFY = "/_sockchat/verify";
|
||||
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 VerifyURL { get; }
|
||||
|
||||
public static string BansCheckURL { get; }
|
||||
public static string BansCreateURL { get; }
|
||||
public static string BansRevokeURL { get; }
|
||||
public static string BansListURL { get; }
|
||||
|
||||
static FlashiiUrls() {
|
||||
AuthURL = GetURL(AUTH);
|
||||
BansURL = GetURL(BANS);
|
||||
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() {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using SharpChat.Flashii;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
|
@ -11,16 +12,13 @@ namespace SharpChat.Packet {
|
|||
|
||||
public class AuthFailPacket : ServerPacket {
|
||||
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;
|
||||
|
||||
if(reason == AuthFailReason.Banned) {
|
||||
if(!expires.HasValue)
|
||||
throw new ArgumentNullException(nameof(expires));
|
||||
Expires = expires.Value;
|
||||
}
|
||||
if(reason == AuthFailReason.Banned)
|
||||
BanInfo = fbi ?? throw new ArgumentNullException(nameof(fbi));
|
||||
}
|
||||
|
||||
public override IEnumerable<string> Pack() {
|
||||
|
@ -45,10 +43,10 @@ namespace SharpChat.Packet {
|
|||
if(Reason == AuthFailReason.Banned) {
|
||||
sb.Append('\t');
|
||||
|
||||
if(Expires == DateTimeOffset.MaxValue)
|
||||
sb.Append(@"-1");
|
||||
if(BanInfo.IsPermanent)
|
||||
sb.Append("-1");
|
||||
else
|
||||
sb.Append(Expires.ToUnixTimeSeconds());
|
||||
sb.Append(BanInfo.ExpiresAt.ToUnixTimeSeconds());
|
||||
}
|
||||
|
||||
yield return sb.ToString();
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
using System;
|
||||
using SharpChat.Flashii;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpChat.Packet {
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -19,8 +20,10 @@ namespace SharpChat.Packet {
|
|||
sb.Append(DateTimeOffset.Now.ToUnixTimeSeconds());
|
||||
sb.Append("\t-1\t0\fbanlist\f");
|
||||
|
||||
foreach(IBan ban in Bans)
|
||||
sb.AppendFormat(@"<a href=""javascript:void(0);"" onclick=""Chat.SendMessageWrapper('/unban '+ this.innerHTML);"">{0}</a>, ", ban);
|
||||
foreach(FlashiiBanInfo ban in Bans) {
|
||||
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())
|
||||
sb.Length -= 2;
|
||||
|
|
|
@ -31,7 +31,10 @@ namespace SharpChat.Packet {
|
|||
|
||||
if(Reason == ForceDisconnectReason.Banned) {
|
||||
sb.Append('\t');
|
||||
sb.Append(Expires.ToUnixTimeSeconds());
|
||||
if(Expires.Year >= 2100)
|
||||
sb.Append("-1");
|
||||
else
|
||||
sb.Append(Expires.ToUnixTimeSeconds());
|
||||
}
|
||||
|
||||
yield return sb.ToString();
|
||||
|
|
|
@ -11,6 +11,7 @@ using System.Net;
|
|||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpChat {
|
||||
public class SockChatServer : IDisposable {
|
||||
|
@ -19,11 +20,9 @@ namespace SharpChat {
|
|||
#if DEBUG
|
||||
public const int MAX_CONNECTIONS = 9001;
|
||||
public const int FLOOD_KICK_LENGTH = 5;
|
||||
public const bool ENABLE_TYPING_EVENT = true;
|
||||
#else
|
||||
public const int MAX_CONNECTIONS = 5;
|
||||
public const int FLOOD_KICK_LENGTH = 30;
|
||||
public const bool ENABLE_TYPING_EVENT = false;
|
||||
#endif
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
@ -144,14 +143,25 @@ namespace SharpChat {
|
|||
sess.User.RateLimiter.AddTimePoint();
|
||||
|
||||
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;
|
||||
} 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');
|
||||
|
||||
if(args.Length < 1)
|
||||
return;
|
||||
|
||||
|
@ -168,58 +178,80 @@ namespace SharpChat {
|
|||
if(sess.User != null)
|
||||
break;
|
||||
|
||||
DateTimeOffset aBanned;
|
||||
lock(Context.BansAccess)
|
||||
aBanned = Context.Bans.Check(sess.RemoteAddress);
|
||||
|
||||
if(aBanned > DateTimeOffset.UtcNow) {
|
||||
sess.Send(new AuthFailPacket(AuthFailReason.Banned, aBanned));
|
||||
string authMethod = args.ElementAtOrDefault(1);
|
||||
if(string.IsNullOrWhiteSpace(authMethod)) {
|
||||
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
||||
sess.Dispose();
|
||||
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;
|
||||
}
|
||||
|
||||
FlashiiAuth.AttemptAsync(HttpClient, new FlashiiAuthRequest {
|
||||
UserId = aUserId,
|
||||
Token = args[2],
|
||||
IPAddress = sess.RemoteAddress.ToString(),
|
||||
}).ContinueWith(authTask => {
|
||||
if(authTask.IsFaulted) {
|
||||
Logger.Write($@"<{sess.Id}> Auth task fail: {authTask.Exception}");
|
||||
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 () => {
|
||||
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.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
FlashiiAuth auth = authTask.Result;
|
||||
|
||||
if(!auth.Success) {
|
||||
Logger.Debug($@"<{sess.Id}> Auth fail: {auth.Reason}");
|
||||
FlashiiBanInfo fbi;
|
||||
try {
|
||||
fbi = await FlashiiBanInfo.CheckAsync(HttpClient, fai.UserId.ToString(), ipAddr);
|
||||
} catch(Exception ex) {
|
||||
Logger.Write($@"<{sess.Id}> Failed auth ban check: {ex}");
|
||||
sess.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
|
||||
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;
|
||||
}
|
||||
|
||||
ChatUser aUser = Context.Users.Get(auth.UserId);
|
||||
ChatUser aUser = Context.Users.Get(fai.UserId);
|
||||
|
||||
if(aUser == null)
|
||||
aUser = new ChatUser(auth);
|
||||
aUser = new ChatUser(fai);
|
||||
else {
|
||||
aUser.ApplyAuth(auth);
|
||||
aUser.ApplyAuth(fai);
|
||||
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
|
||||
if(aUser.SessionCount >= MAX_CONNECTIONS) {
|
||||
sess.Send(new AuthFailPacket(AuthFailReason.MaxSessions));
|
||||
|
@ -243,7 +275,7 @@ namespace SharpChat {
|
|||
}
|
||||
|
||||
Context.HandleJoin(aUser, Context.Channels.DefaultChannel, sess);
|
||||
});
|
||||
}).Wait();
|
||||
break;
|
||||
|
||||
case "2":
|
||||
|
@ -287,7 +319,7 @@ namespace SharpChat {
|
|||
IChatMessage message = null;
|
||||
|
||||
if(messageText[0] == '/') {
|
||||
message = HandleV1Command(messageText, mUser, mChannel);
|
||||
message = HandleV1Command(messageText, mUser, mChannel, sess);
|
||||
|
||||
if(message == null)
|
||||
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 commandName = parts[0].Replace(@".", string.Empty).ToLowerInvariant();
|
||||
|
||||
|
@ -640,10 +672,13 @@ namespace SharpChat {
|
|||
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) {
|
||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, parts.Length < 2 ? @"User" : parts[1]));
|
||||
if(banUserTarget == null || (banUser = Context.Users.Get(banUserTarget)) == null) {
|
||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_FOUND, true, banUser == null ? @"User" : banUserTarget));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -652,24 +687,44 @@ namespace SharpChat {
|
|||
break;
|
||||
}
|
||||
|
||||
lock(Context.BansAccess)
|
||||
if(Context.Bans.Check(banUser) > DateTimeOffset.Now) {
|
||||
user.Send(new LegacyCommandResponse(LCR.KICK_NOT_ALLOWED, true, banUser.DisplayName));
|
||||
break;
|
||||
}
|
||||
|
||||
DateTimeOffset? banUntil = isBanning ? (DateTimeOffset?)DateTimeOffset.MaxValue : null;
|
||||
|
||||
if(parts.Length > 2) {
|
||||
if(!double.TryParse(parts[2], out double silenceSeconds)) {
|
||||
TimeSpan duration = isBanning ? TimeSpan.MaxValue : TimeSpan.Zero;
|
||||
if(!string.IsNullOrWhiteSpace(banDurationStr) && double.TryParse(banDurationStr, out double durationSeconds)) {
|
||||
if(durationSeconds < 0) {
|
||||
user.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
|
||||
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;
|
||||
case @"pardon":
|
||||
case @"unban":
|
||||
|
@ -678,24 +733,36 @@ namespace SharpChat {
|
|||
break;
|
||||
}
|
||||
|
||||
if(parts.Length < 2) {
|
||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, string.Empty));
|
||||
bool unbanUserTargetIsName = true;
|
||||
string unbanUserTarget = parts.ElementAtOrDefault(1);
|
||||
if(string.IsNullOrWhiteSpace(unbanUserTarget)) {
|
||||
user.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
|
||||
break;
|
||||
}
|
||||
|
||||
BannedUser unbanUser;
|
||||
lock(Context.BansAccess)
|
||||
unbanUser = Context.Bans.GetUser(parts[1]);
|
||||
|
||||
if(unbanUser == null || unbanUser.Expires <= DateTimeOffset.Now) {
|
||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanUser?.Username ?? parts[1]));
|
||||
break;
|
||||
ChatUser unbanUser = Context.Users.Get(unbanUserTarget);
|
||||
if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) {
|
||||
unbanUserTargetIsName = false;
|
||||
unbanUser = Context.Users.Get(unbanUserId);
|
||||
}
|
||||
|
||||
lock(Context.BansAccess)
|
||||
Context.Bans.Remove(unbanUser);
|
||||
if(unbanUser != null)
|
||||
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;
|
||||
case @"pardonip":
|
||||
case @"unbanip":
|
||||
|
@ -704,21 +771,28 @@ namespace SharpChat {
|
|||
break;
|
||||
}
|
||||
|
||||
if(parts.Length < 2 || !IPAddress.TryParse(parts[1], out IPAddress unbanIP)) {
|
||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, string.Empty));
|
||||
string unbanAddrTarget = parts.ElementAtOrDefault(1);
|
||||
if(string.IsNullOrWhiteSpace(unbanAddrTarget) || !IPAddress.TryParse(unbanAddrTarget, out IPAddress unbanAddr)) {
|
||||
user.Send(new LegacyCommandResponse(LCR.COMMAND_FORMAT_ERROR));
|
||||
break;
|
||||
}
|
||||
|
||||
lock(Context.BansAccess) {
|
||||
if(Context.Bans.Check(unbanIP) <= DateTimeOffset.Now) {
|
||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanIP));
|
||||
break;
|
||||
unbanAddrTarget = unbanAddr.ToString();
|
||||
|
||||
Task.Run(async () => {
|
||||
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);
|
||||
}
|
||||
|
||||
user.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanIP));
|
||||
bool wasBanned = await banInfo.RevokeAsync(HttpClient, FlashiiBanInfo.RevokeKind.RemoteAddress);
|
||||
if(wasBanned)
|
||||
user.Send(new LegacyCommandResponse(LCR.USER_UNBANNED, false, unbanAddrTarget));
|
||||
else
|
||||
user.Send(new LegacyCommandResponse(LCR.USER_NOT_BANNED, true, unbanAddrTarget));
|
||||
}).Wait();
|
||||
break;
|
||||
case @"bans": // gets a list of bans
|
||||
case @"banned":
|
||||
|
@ -727,8 +801,11 @@ namespace SharpChat {
|
|||
break;
|
||||
}
|
||||
|
||||
lock(Context.BansAccess)
|
||||
user.Send(new BanListPacket(Context.Bans.All()));
|
||||
Task.Run(async () => {
|
||||
user.Send(new BanListPacket(
|
||||
await FlashiiBanInfo.GetListAsync(HttpClient)
|
||||
));
|
||||
}).Wait();
|
||||
break;
|
||||
case @"silence": // silence a user
|
||||
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