From 499de238cbf469c2efeaa3fab1318340f1005bc6 Mon Sep 17 00:00:00 2001 From: olcxja Date: Tue, 23 Jun 2026 23:23:00 +0200 Subject: [PATCH] better account deletion, add keys to password change,add updating user storage --- LarpixServer/Account/BackgroundDelete.cs | 5 +- LarpixServer/Account/Requests.cs | 105 ++++++++++++++++++----- LarpixServer/Account/Utils.cs | 80 ++++++++++++++++- LarpixServer/Room/Requests.cs | 4 +- LarpixServer/Utils/Utils.cs | 2 +- 5 files changed, 168 insertions(+), 28 deletions(-) diff --git a/LarpixServer/Account/BackgroundDelete.cs b/LarpixServer/Account/BackgroundDelete.cs index e30026b..6807006 100644 --- a/LarpixServer/Account/BackgroundDelete.cs +++ b/LarpixServer/Account/BackgroundDelete.cs @@ -44,7 +44,10 @@ public class BackgroundDelete : BackgroundService try { string username = await Account.Utils.NameFromId(id); - Fs.DeleteFile($"{LarpixServer.Utils.Utils.ACCOUNTS_NAME_DIR}/{username.ToLower()}"); + if (!string.IsNullOrEmpty(username)) + { + Fs.DeleteFile($"{LarpixServer.Utils.Utils.ACCOUNTS_NAME_DIR}/{username.ToLower()}"); + } Fs.DeleteDirectory(account); Fs.WriteFile($"{LarpixServer.Utils.Utils.ACCOUNTS_FREEID_DIR}/{id}", []); Console.WriteLine($"Deleted {id} ({username})"); diff --git a/LarpixServer/Account/Requests.cs b/LarpixServer/Account/Requests.cs index 52df624..8b7e714 100644 --- a/LarpixServer/Account/Requests.cs +++ b/LarpixServer/Account/Requests.cs @@ -53,8 +53,7 @@ public class Requests userLock.Release(); } - await context.Response.WriteAsync( - "ok|Your account will be deleted in one month|Please make sure you are logged out on all devices. Logging back into your account during this period will stop the deletion process"); + await context.Response.WriteAsync("success:account.deleted.scheduled"); } public static async Task Create(HttpContext context, Func next, IQueryCollection query, @@ -232,33 +231,32 @@ public class Requests ulong id = ulong.Parse(await Fs.ReadFile($"{ACCOUNTS_DIR}/last")); id++; - var freeid = Path.GetFileName(Directory.EnumerateFiles(ACCOUNTS_FREEID_DIR).FirstOrDefault()); - if (freeid != null) + if (id == ulong.MaxValue) { - id = ulong.Parse(freeid); - Fs.DeleteFile($"{ACCOUNTS_FREEID_DIR}/{freeid}"); - } - else - { - if (id == ulong.MaxValue) - { - await context.Response.WriteAsync( - "error:accounts.slots.full"); - return; - } - - await Fs.WriteFile($"{ACCOUNTS_DIR}/last", Encoding.UTF8.GetBytes(id.ToString())); + await context.Response.WriteAsync("error:accounts.slots.full"); + return; } + await Fs.WriteFile($"{ACCOUNTS_DIR}/last", Encoding.UTF8.GetBytes(id.ToString())); SemaphoreSlim userLock = Utils.GetUserLock(id.ToString()); await userLock.WaitAsync(); try { await Fs.WriteFile($"{ACCOUNTS_NAME_DIR}/{lowerName}", Encoding.UTF8.GetBytes(id.ToString())); - await Fs.WriteFile($"{ACCOUNTS_DATA_DIR}/{id.ToString()}/username", - Encoding.UTF8.GetBytes(entry.name)); - await Fs.WriteFile($"{ACCOUNTS_DATA_DIR}/{id.ToString()}/secret", - Encoding.UTF8.GetBytes(entry.pass)); + + try + { + await Fs.WriteFile($"{ACCOUNTS_DATA_DIR}/{id.ToString()}/username", + Encoding.UTF8.GetBytes(entry.name)); + await Fs.WriteFile($"{ACCOUNTS_DATA_DIR}/{id.ToString()}/secret", + Encoding.UTF8.GetBytes(entry.pass)); + } + catch (Exception) + { + Fs.DeleteFile($"{ACCOUNTS_NAME_DIR}/{lowerName}"); + throw; + } + createHolder.TryRemove(serialized.idKey, out _); await Utils.UpdateLastLogin(id.ToString()); } @@ -408,7 +406,7 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec string id = Utils.GetValidIdOrZero(idQuery.ToString()); string body = await LoadBody(bodyReader); string password = await Utils.GetPassword(id); - string newPass = await Utils.NonceDecryptBody(id, password, body, false); + string bodyDecrypted = await Utils.NonceDecryptBody(id, password, body, false); secret = await Utils.NonceDecryptBody(id, password, secret); string auth = await Utils.Auth(id, password, secret); @@ -419,6 +417,20 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec return; } + string newPass = bodyDecrypted; + string newKeysBody = null; + + try + { + Universal2String parsedBody = JsonSerializer.Deserialize(bodyDecrypted, AppJsonSerializerContext.Default.Universal2String); + if (parsedBody != null && !string.IsNullOrWhiteSpace(parsedBody.string1) && !string.IsNullOrWhiteSpace(parsedBody.string2)) + { + newPass = parsedBody.string1; + newKeysBody = parsedBody.string2; + } + } + catch { } + SemaphoreSlim userLock = Utils.GetUserLock(id); await userLock.WaitAsync(); try @@ -429,7 +441,30 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec return; } - await Utils.SetPassword(id, newPass); + string oldKeys = null; + if (newKeysBody != null) + { + oldKeys = await Utils.GetUserKeys(id); + string keyUpdateResult = await UpdateUserKeys(id, newKeysBody); + if (keyUpdateResult.StartsWith("error:")) + { + await context.Response.WriteAsync(keyUpdateResult); + return; + } + } + + try + { + await Utils.SetPassword(id, newPass); + } + catch (Exception) + { + if (newKeysBody != null && oldKeys != null) + { + await Utils.UpdateUserKeys(id, oldKeys); + } + throw; + } } finally { @@ -620,6 +655,30 @@ public static async Task Auth(HttpContext context, Func next, IQueryCollec await Utils.GetUserKeys(id) , password)); break; + case "user/storage/public/update": + { + Universal2String storageUpdate = JsonSerializer.Deserialize( + serializedBody.string2, + AppJsonSerializerContext.Default.Universal2String + ); + await context.Response.WriteAsync( + Encryption.Encryption.EncryptString( + await Utils.UpdateUserPublicStorageEntry(id, storageUpdate.string1, storageUpdate.string2) + , password)); + break; + } + case "user/storage/private/update": + { + Universal2String storageUpdate = JsonSerializer.Deserialize( + serializedBody.string2, + AppJsonSerializerContext.Default.Universal2String + ); + await context.Response.WriteAsync( + Encryption.Encryption.EncryptString( + await Utils.UpdateUserPrivateStorageEntry(id, storageUpdate.string1, storageUpdate.string2) + , password)); + break; + } case "user/dm/list": await context.Response.WriteAsync( Encryption.Encryption.EncryptString( diff --git a/LarpixServer/Account/Utils.cs b/LarpixServer/Account/Utils.cs index f1b0188..803584a 100644 --- a/LarpixServer/Account/Utils.cs +++ b/LarpixServer/Account/Utils.cs @@ -304,6 +304,72 @@ public class Utils return await Fs.ReadFile(path); } + public static async Task UpdateUserPublicStorageEntry(string id, string entry, string contentBase64) + { + if (string.IsNullOrEmpty(entry) || entry.Contains("..") || entry.Contains("/") || entry.Contains("\\")) + { + return "error:invalid.entry.name"; + } + byte[] contentBytes; + try + { + contentBytes = Convert.FromBase64String(contentBase64); + } + catch + { + contentBytes = Encoding.UTF8.GetBytes(contentBase64); + } + + if (contentBytes.Length > PUBLIC_STORAGE_LIMIT) + { + return "error:storage.limit.exceeded"; + } + + string path = $"{ACCOUNTS_DATA_DIR}/{id}/storage/public/{entry}"; + await Fs.WriteFile(path, contentBytes); + return "success:storage.entry.updated"; + } + + public static async Task GetUserPrivateStorageEntry(string id, string entry) + { + if (string.IsNullOrEmpty(entry) || entry.Contains("..") || entry.Contains("/") || entry.Contains("\\")) + { + return new byte[] {}; + } + string path = $"{ACCOUNTS_DATA_DIR}/{id}/storage/private/{entry}"; + if (!Fs.Exists(path)) + { + return new byte[] {}; + } + return await Fs.ReadFile(path); + } + + public static async Task UpdateUserPrivateStorageEntry(string id, string entry, string contentBase64) + { + if (string.IsNullOrEmpty(entry) || entry.Contains("..") || entry.Contains("/") || entry.Contains("\\")) + { + return "error:invalid.entry.name"; + } + byte[] contentBytes; + try + { + contentBytes = Convert.FromBase64String(contentBase64); + } + catch + { + contentBytes = Encoding.UTF8.GetBytes(contentBase64); + } + + if (contentBytes.Length > PRIVATE_STORAGE_LIMIT) + { + return "error:storage.limit.exceeded"; + } + + string path = $"{ACCOUNTS_DATA_DIR}/{id}/storage/private/{entry}"; + await Fs.WriteFile(path, contentBytes); + return "success:storage.entry.updated"; + } + public static async Task UpdateUserDm(string id, string dmId, string isRead = "false", string timestamp = "") { @@ -394,7 +460,19 @@ public class Utils Fs.MoveFile($"{ACCOUNTS_NAME_DIR}/{lowerOldName}", $"{ACCOUNTS_NAME_DIR}/{lowerNewName}"); } - await Fs.WriteFile($"{ACCOUNTS_DATA_DIR}/{id}/username", Encoding.UTF8.GetBytes(newName)); + + try + { + await Fs.WriteFile($"{ACCOUNTS_DATA_DIR}/{id}/username", Encoding.UTF8.GetBytes(newName)); + } + catch (Exception) + { + if (lowerNewName != lowerOldName) + { + Fs.MoveFile($"{ACCOUNTS_NAME_DIR}/{lowerNewName}", $"{ACCOUNTS_NAME_DIR}/{lowerOldName}"); + } + throw; + } return "success:username.changed"; } diff --git a/LarpixServer/Room/Requests.cs b/LarpixServer/Room/Requests.cs index 221698d..f881d6e 100644 --- a/LarpixServer/Room/Requests.cs +++ b/LarpixServer/Room/Requests.cs @@ -396,7 +396,7 @@ public class Requests msg.type = "larp.text"; msg.content = content; msg.attachment = attachment; - msg.key = "0"; // Flag for encrypted content + msg.key = "0"; //empty = no encryption, value = encryption key index msg.pervious = ""; msg.responded = responded ?? ""; msg.reactions = ""; @@ -435,7 +435,7 @@ public class Requests } } } - return "success:message.sent"; + return $"success:{nextId}"; } public static async Task DmMessageDelete(string id, string body) diff --git a/LarpixServer/Utils/Utils.cs b/LarpixServer/Utils/Utils.cs index b993045..dfe4a01 100644 --- a/LarpixServer/Utils/Utils.cs +++ b/LarpixServer/Utils/Utils.cs @@ -30,7 +30,7 @@ public class Utils public static int DIR_CACHE_SIZE = 800000; public static int LOCK_SIZE = 1000000; - public static int MAX_BODY_SIZE = 524288; //0.5mb + public static int MAX_BODY_SIZE = 20971520; //20mb //2621440; //2.5mb public static int PRIVATE_STORAGE_LIMIT = 5242880; public static int PUBLIC_STORAGE_LIMIT = 10485760;