diff --git a/LarpixServer/Account/Requests.cs b/LarpixServer/Account/Requests.cs index 6ff642c..78062d2 100644 --- a/LarpixServer/Account/Requests.cs +++ b/LarpixServer/Account/Requests.cs @@ -14,6 +14,27 @@ public class Requests { public static ConcurrentDictionary createHolder = new(); public static ConcurrentDictionary nonceHolder = new(); + public static ConcurrentDictionary actionRateLimits = new(); + + public static bool IsActionRateLimited(string id) + { + if (ACTION_RATE_LIMIT <= 0) return false; + var now = DateTime.UtcNow; + + actionRateLimits.AddOrUpdate(id, + _ => (1, now.AddMinutes(1)), + (_, old) => { + if (now > old.resetTime) return (1, now.AddMinutes(1)); + return (old.count + 1, old.resetTime); + } + ); + + if (actionRateLimits.TryGetValue(id, out var record) && record.count > ACTION_RATE_LIMIT) + { + return true; + } + return false; + } public static SemaphoreSlim createLock = new SemaphoreSlim(1, 1); @@ -625,6 +646,20 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec return; } + string[] actionRequests = { + "user/dm/invite", "user/dm/invite/revoke", "user/dm/invite/decline", "user/dm/create", + "dm/message/send", "dm/message/delete", "dm/message/react", "dm/key/update", + "user/storage/public/update", "user/storage/private/update", "dm/read" + }; + if (System.Array.IndexOf(actionRequests, serializedBody.string1) >= 0) + { + if (IsActionRateLimited(id)) + { + await context.Response.WriteAsync(Encryption.Encryption.EncryptString("error:rate.limit", password)); + return; + } + } + SemaphoreSlim userLock = Utils.GetUserLock(id); await userLock.WaitAsync(); try @@ -733,6 +768,16 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec await context.Response.WriteAsync(Encryption.Encryption.EncryptString(await Room.Requests.GetDmMessages(id, msgGet.string1, msgGet.string3, msgGet.string2), password)); break; } + case "dm/read": + { + Universal2String readGet = JsonSerializer.Deserialize( + serializedBody.string2, + AppJsonSerializerContext.Default.Universal2String + ); + await Account.Utils.UpdateUserDm(id, readGet.string1, "true", ""); + await context.Response.WriteAsync(Encryption.Encryption.EncryptString("success:dm.read", password)); + break; + } case "dm/key/get": await context.Response.WriteAsync( Encryption.Encryption.EncryptString( @@ -789,6 +834,12 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec return; } + if (IsActionRateLimited(id)) + { + await context.Response.WriteAsync(Encryption.Encryption.EncryptString("error:rate.limit", password)); + return; + } + byte[] bodyBytes = await LarpixServer.Utils.Utils.LoadBodyBytes(context.Request.Body, LarpixServer.Utils.Utils.PUBLIC_STORAGE_LIMIT); byte[] fileBytes = await Utils.NonceDecryptBytes(id, password, bodyBytes, true); diff --git a/LarpixServer/Account/Utils.cs b/LarpixServer/Account/Utils.cs index 76292ef..d1af889 100644 --- a/LarpixServer/Account/Utils.cs +++ b/LarpixServer/Account/Utils.cs @@ -432,7 +432,10 @@ public class Utils } fileDm.string1 = dmId; - fileDm.string2 = timestamp; + if (timestamp != "") + { + fileDm.string2 = timestamp; + } fileDm.string3 = isRead; await Fs.WriteFile(dmPath, Encoding.UTF8.GetBytes(JsonSerializer.Serialize(fileDm, AppJsonSerializerContext.Default.Universal3String))); diff --git a/LarpixServer/Room/Requests.cs b/LarpixServer/Room/Requests.cs index f881d6e..c5023c4 100644 --- a/LarpixServer/Room/Requests.cs +++ b/LarpixServer/Room/Requests.cs @@ -97,7 +97,7 @@ public class Requests return Encoding.UTF8.GetString(await Fs.ReadFile(path)); } - public static async Task UpdateDmKey(string id, string dmId, string key) + public static async Task UpdateDmKey(string id, string dmId, string key) //nie powinno byc takich rzeczy jak ta, jak juz powinnismy tworzyc nowy klucz, np. 1 i pozniej w wiadomosci wpisywac Message.key = "1" i nim szyfrowac { if (!await IsMemberOfDm(id, dmId)) { @@ -414,7 +414,7 @@ public class Requests if (memberId == id) { await Account.Utils.UpdateUserDm(memberId, dmId, "true", msg.timestamp); - await Utils.Utils.SendWsMessage(memberId, $"dm_message:{dmId}"); + await Utils.Utils.SendWsMessage(memberId, $"dm_message:{dmId}:read"); } else { @@ -430,7 +430,7 @@ public class Requests { userLock.Release(); } - await Utils.Utils.SendWsMessage(memberId, $"dm_message:{dmId}"); + await Utils.Utils.SendWsMessage(memberId, $"dm_message:{dmId}:unread"); }); } } @@ -459,8 +459,10 @@ public class Requests ); if (msg.author != id) return "error:forbidden"; + if (msg.type == "larp.redacted") return "error:message.already.deleted"; msg.type = "larp.redacted"; + msg.reactions = ""; msg.content = "{blah(larp.redacted)}"; msg.attachment = ""; msg.key = ""; @@ -474,7 +476,7 @@ public class Requests string memberId = Account.Utils.GetValidIdOrZero(memberWD); if (isLocal) { - await Utils.Utils.SendWsMessage(memberId, $"dm_message:{dmId}"); + await Utils.Utils.SendWsMessage(memberId, $"dm_message:{dmId}:update"); } } return "success:message.deleted"; @@ -501,6 +503,8 @@ public class Requests AppJsonSerializerContext.Default.Message ); + if (msg.type == "larp.redacted") return "error:message.already.deleted"; + msg.reactions = reactions ?? ""; await Fs.WriteFile(msgPath, Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg, AppJsonSerializerContext.Default.Message))); @@ -512,7 +516,7 @@ public class Requests string memberId = Account.Utils.GetValidIdOrZero(memberWD); if (isLocal) { - await Utils.Utils.SendWsMessage(memberId, $"dm_message:{dmId}"); + await Utils.Utils.SendWsMessage(memberId, $"dm_message:{dmId}:update"); } } return "success:message.reacted"; diff --git a/LarpixServer/Utils/Utils.cs b/LarpixServer/Utils/Utils.cs index c6a3cdc..6396a2a 100644 --- a/LarpixServer/Utils/Utils.cs +++ b/LarpixServer/Utils/Utils.cs @@ -31,6 +31,7 @@ public class Utils public static int LOCK_SIZE = 1000000; public static int MAX_BODY_SIZE = 20971520; //20mb //2621440; //2.5mb + public static int ACTION_RATE_LIMIT = 120; public static int PRIVATE_STORAGE_LIMIT = 5242880; public static int PUBLIC_STORAGE_LIMIT = 10485760; @@ -259,6 +260,12 @@ public class Utils PORT = int.Parse(port); } + string? actionRateLimitStr = Environment.GetEnvironmentVariable("ACTION_RATE_LIMIT"); + if (actionRateLimitStr != null && int.TryParse(actionRateLimitStr, out int parsedLimit)) + { + ACTION_RATE_LIMIT = parsedLimit; + } + VOICE_IPS.Clear(); port = Environment.GetEnvironmentVariable("VOICE_IPS");