diff --git a/LarpixServer/Federation/Receiver.cs b/LarpixServer/Federation/Receiver.cs index 3cf508c..1c33a9c 100644 --- a/LarpixServer/Federation/Receiver.cs +++ b/LarpixServer/Federation/Receiver.cs @@ -1,6 +1,124 @@ +using System.Text; +using System.Text.Json; +using LarpixServer.Filesystem; +using LarpixServer.Utils.Jsons; + +using static LarpixServer.Utils.Utils; + namespace LarpixServer.Federation; public class Receiver { - + public static async Task FederationRequest(HttpContext context, Func next, IQueryCollection query, StreamReader bodyReader) + { + if (!query.TryGetValue("d", out var domain)) + { + return; + } + + string body = await LoadBody(bodyReader); + Universal2String serializedBody = JsonSerializer.Deserialize( + body, + AppJsonSerializerContext.Default.Universal2String + ); + + switch (serializedBody.string1) + { + case "user/id/get": + await context.Response.WriteAsync( await Account.Utils.IdFromName(serializedBody.string2) ); + return; + case "dm/invite/send": + string[] ids = serializedBody.string2.Split(','); //0=receiver, 1=sender + + if (!await Sender.HasSentDmInvite(ids[1], domain, ids[0])) + { + await context.Response.WriteAsync( "Bad request" ); + return; + } + + SemaphoreSlim userLock = Account.Utils.GetUserLock(ids[0]); + await userLock.WaitAsync(); + try + { + string inviteFile = ACCOUNTS_DATA_DIR + $"/{ids[0]}/dminvites/recv/{ids[1]};{domain}"; + if (Fs.Exists(inviteFile)) + { + await context.Response.WriteAsync("User already invited"); + return; + } + await Fs.WriteFile(inviteFile, Encoding.UTF8.GetBytes("0")); + } + finally + { + + userLock.Release(); + } + + await context.Response.WriteAsync("User invited"); + + return; + case "dm/invite/hassent": + ids = serializedBody.string2.Split(','); //0=sender, 1=receiver + + string inviteSentFile = ACCOUNTS_DATA_DIR + $"/{ids[0]}/dminvites/sent/{ids[1]};{domain}"; + if (!Fs.Exists(inviteSentFile)) + { + await context.Response.WriteAsync( "false" ); + return; + } + + await context.Response.WriteAsync( "true" ); + return; + case "dm/add": + ids = serializedBody.string2.Split(','); //0=creator (inv receiver), 1=inviter + + + //did i really sent an invite to this user???? idkkk maybeeeee yeahhh i did this + inviteSentFile = ACCOUNTS_DATA_DIR + $"/{ids[0]}/dminvites/sent/{ids[1]};{domain}"; + if (!Fs.Exists(inviteSentFile)) + { + await context.Response.WriteAsync( "Failed to add. No invite found" ); + return; + } + Fs.DeleteFile(inviteSentFile); + //now full invite is deleted :333333333333333333333333333 + //we need to add to dm :33333333333333333333 + + + userLock = Account.Utils.GetUserLock(ids[0]); + await userLock.WaitAsync(); + try + { + //best dmId creation ever + List users = new List(){$"{ids[0]};{DOMAIN}", $"{ids[1]};{domain}"}; + users.Sort(); + string dmId = $"{users[0]}_{users[1]}"; + + string dms2path = $"{ACCOUNTS_DATA_DIR}/{ids[0]}/dms"; + string dms2; + if (await Account.Utils.NameFromId(ids[0]) == "") //if user account just got deleted + { + await context.Response.WriteAsync("DM accepted"); + return; + } + if (!Fs.Exists(dms2path)) + { + dms2 = ""; + } + else + { + dms2 = Encoding.UTF8.GetString(await Fs.ReadFile(dms2path)); + } + + await Fs.WriteFile(dms2path, Encoding.UTF8.GetBytes($"{dmId};{dms2}")); + } + finally + { + userLock.Release(); + } + return; + } + + return; + } } \ No newline at end of file diff --git a/LarpixServer/Federation/Sender.cs b/LarpixServer/Federation/Sender.cs index bfe06ff..ff432ad 100644 --- a/LarpixServer/Federation/Sender.cs +++ b/LarpixServer/Federation/Sender.cs @@ -1,24 +1,52 @@ +using System.Text; +using System.Text.Json; +using LarpixServer.Utils.Jsons; + +using static LarpixServer.Utils.Utils; + namespace LarpixServer.Federation; public class Sender { + public static async Task FederationRequest(string endpoint, string value, string domain) + { + HttpClient client = new HttpClient(); + + Universal2String universal = new Universal2String(); + universal.string1 = endpoint; + universal.string2 = value; + + string body = JsonSerializer.Serialize( + universal, + AppJsonSerializerContext.Default.Universal2String + ); + + var content = new StringContent(body, Encoding.UTF8, "text/plain"); + + return (await client.PostAsync($"http://{domain}/_larpix/federation?d={DOMAIN}", content)).Content.ReadAsStringAsync().Result; + } + public static async Task GetUserId(string username, string domain) { - return "?"; + return await FederationRequest("user/id/get", username, domain); } public static async Task HasSentDmInvite(string senderId, string domain, string receiverId) //receiver is always from local domain { - return true; + if (await FederationRequest("dm/invite/hassent", $"{senderId},{receiverId}", domain) == "true") + { + return true; + } + return false; } - public static async Task CreateDm(string id, string domain, string dmId) + public static async Task AddToDm(string receiverId, string domain, string senderId) //receiver is always from local domain { - + return await FederationRequest("dm/add", $"{receiverId},{senderId}", domain); } public static async Task DmInvite(string senderId, string domain, string receiverId) //sender is always from local domain { - return "User invited"; + return await FederationRequest("dm/invite/send", $"{receiverId},{senderId}", domain); } } \ No newline at end of file diff --git a/LarpixServer/Program.cs b/LarpixServer/Program.cs index fb25df4..378c54d 100644 --- a/LarpixServer/Program.cs +++ b/LarpixServer/Program.cs @@ -82,7 +82,7 @@ public class Program switch (path) { - case "/restart": + case "/_larpix/restart": if (!query.TryGetValue("skey", out var skey)) { context.Response.ContentType = mimeTypes["html"]; @@ -113,7 +113,7 @@ public class Program var lifetime = context.RequestServices.GetRequiredService(); lifetime.StopApplication(); return; - case "/clearcache": //kiedys bedzie endpoint cache + case "/_larpix/clearcache": //kiedys bedzie endpoint cache if (!query.TryGetValue("skey", out skey)) { context.Response.ContentType = mimeTypes["html"]; @@ -135,34 +135,37 @@ public class Program ulong removed = Fs.ClearCache(p); await context.Response.WriteAsync($"Cache cleared with pattern: {p}. {removed} elements removed"); return; - case "/createaccount": + case "/_larpix/createaccount": await Account.Requests.Create(context, next, query, reader); return; - case "/deleteaccount": + case "/_larpix/deleteaccount": await Account.Requests.Delete(context, next, query, reader); return; - case "/auth": + case "/_larpix/auth": await Account.Requests.Auth(context, next, query, reader); return; - case "/chpass": + case "/_larpix/chpass": await Account.Requests.ChangePassword(context, next, query, reader); return; - case "/chname": + case "/_larpix/chname": await Account.Requests.ChangeUsername(context, next, query, reader); return; - case "/correctedname": + case "/_larpix/correctedname": await Account.Requests.CorrectedName(context, next, query); return; - case "/nextnonce": + case "/_larpix/nextnonce": await Account.Requests.NextNonce(context, next, query); return; - case "/encryptedrequest": + case "/_larpix/encryptedrequest": await Account.Requests.EncryptedRequest(context, next, query, reader); return; - case "/user/key/getpublic": + case "/_larpix/federation": + await Federation.Receiver.FederationRequest(context, next, query, reader); + return; + case "/_larpix/user/key/getpublic": await Account.Requests.GetUserPublicKey(context, next, query, reader); return; - case "/user/storage/public/getentry": + case "/_larpix/user/storage/public/getentry": await Account.Requests.GetUserPublicStorageEntry(context, next, query, reader); return; } diff --git a/LarpixServer/Room/Requests.cs b/LarpixServer/Room/Requests.cs index 24c59dd..89be4ff 100644 --- a/LarpixServer/Room/Requests.cs +++ b/LarpixServer/Room/Requests.cs @@ -14,43 +14,42 @@ public class Requests { if (!Account.Utils.IsUserLocal(username2, out string domain)) //federation { - username2 = username2.Split(":")[0]; - string remoteId2 = await Federation.Sender.GetUserId(username2, domain); - await Fs.WriteFile(ACCOUNTS_DATA_DIR + $"/{id}/dminvites/sent/{remoteId2};{domain}", []); - return await Federation.Sender.DmInvite(id, domain, remoteId2); + SemaphoreSlim userLock = Account.Utils.GetUserLock(id); + await userLock.WaitAsync(); + try + { + username2 = username2.Split(":")[0]; + string remoteId2 = await Federation.Sender.GetUserId(username2, domain); + await Fs.WriteFile(ACCOUNTS_DATA_DIR + $"/{id}/dminvites/sent/{remoteId2};{domain}", []); //only save sent while federating + return await Federation.Sender.DmInvite(id, domain, remoteId2); + } + finally + { + userLock.Release(); + } } username2 = username2.Split(":")[0]; string id2 = await Account.Utils.IdFromName(username2); SemaphoreSlim userLock2 = Account.Utils.GetUserLock(id2); await userLock2.WaitAsync(); - SemaphoreSlim userLock = Account.Utils.GetUserLock(id); - await userLock.WaitAsync(); + try { - string inviteSentFile = ACCOUNTS_DATA_DIR + $"/{id}/dminvites/sent/{id2};{domain}"; string inviteFile = ACCOUNTS_DATA_DIR + $"/{id2}/dminvites/recv/{id};{DOMAIN}"; if (Fs.Exists(inviteFile)) { - string inviteState = Encoding.UTF8.GetString(await Fs.ReadFile(inviteFile)); - switch (inviteState) - { - case "0": //invited - await Fs.WriteFile(inviteSentFile, []); //just in case - return "User already invited"; - case "1": //accepted - return "You already have a DM with this user"; - } - + return "User already invited"; } await Fs.WriteFile(inviteFile, Encoding.UTF8.GetBytes("0")); - await Fs.WriteFile(inviteSentFile, []); //we should also save that this user invited someone, because this will act as dm accept verification while federating + + //im not saving this like ts is litterally local scenario + //await Fs.WriteFile(inviteSentFile, []); //we should also save that this user invited someone, because this will act as dm accept verification while federating } finally { userLock2.Release(); - userLock.Release(); } return "User invited"; @@ -81,14 +80,16 @@ public class Requests if (isUserLocal) { + /* string inviteSentFile = ACCOUNTS_DATA_DIR + $"/{id2}/dminvites/sent/{id};{DOMAIN}"; if (!Fs.Exists(inviteSentFile)) { return "You can't create a DM without an invitation"; } Fs.DeleteFile(inviteSentFile); + */ // local scenario, there is no need to check sent } - else //also check this on federated server + else //check sent on federated server { if (!await Federation.Sender.HasSentDmInvite(id2, domain, id)) { @@ -99,144 +100,123 @@ public class Requests string inviteFile = ACCOUNTS_DATA_DIR + $"/{id}/dminvites/recv/{id2};{domain}"; if (Fs.Exists(inviteFile)) { - string inviteState = Encoding.UTF8.GetString(await Fs.ReadFile(inviteFile)); - switch (inviteState) + + List<(string file, string todo, string content)> restoreList = new(); + + try //try because federation is involved, if it fails we should just clean all written files { - case "0": //invited + Fs.DeleteFile(inviteFile); //remove invite bc now its accepted (error = no invite & no dm) + + + //best dmId creation ever + List users = new List(){$"{id};{DOMAIN}", $"{id2};{domain}"}; + users.Sort(); + string dmId = $"{users[0]}_{users[1]}"; + - List<(string file, string todo, string content)> restoreList = new(); - try //try because federation is involved, if it fails we should just clean all written files + await Fs.WriteFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/members", + Encoding.UTF8.GetBytes($"{id}:{DOMAIN};{id2}:{domain}")); + await Fs.WriteFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages/last", Encoding.UTF8.GetBytes($"0")); + + + Message startingMessage = new(); + startingMessage.attachment = ""; + startingMessage.author = "0"; + startingMessage.timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); + startingMessage.type = "larp.info"; + startingMessage.content = + "At the beginning of a DM, you should always verify that the person you're talking to is the intended recipient"; + startingMessage.key = ""; //no key == NOT encrypted + startingMessage.pervious = ""; + + await Fs.WriteFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/keys/0/{id};{DOMAIN}", //klucz dla uzytkownika tworzącego jest juz gotowy i ma zwykly zapis + Encoding.UTF8.GetBytes(serializedBody.string2)); + + + Universal2String keys = JsonSerializer.Deserialize( + await Account.Utils.GetUserKeys(id), + AppJsonSerializerContext.Default.Universal2String + ); + await Fs.WriteFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/keys/0/{id2};{domain}", + Encoding.UTF8.GetBytes($"SETUP:{keys.string2};{serializedBody.string3}")); //jezeli mamy setup to [1] to jest publiczny klucz drugiej osoby, + //a string 3 to zaszyfrowany klucz pokoju ktory musi odszyfrowac za pomoca swoich kluczy i tego publicznego + + await Fs.WriteFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages/0", Encoding.UTF8.GetBytes( + JsonSerializer.Serialize(startingMessage, AppJsonSerializerContext.Default.Message) + )); + + restoreList.Add(($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}", "deletef", "")); + + string dmspath = $"{ACCOUNTS_DATA_DIR}/{id}/dms"; + string dms; + if (!Fs.Exists(dmspath)) + { + dms = ""; + } + else + { + dms = Encoding.UTF8.GetString(await Fs.ReadFile(dmspath)); + } + restoreList.Add((dmspath, "write", dms)); + await Fs.WriteFile(dmspath, Encoding.UTF8.GetBytes($"{dmId};{dms}")); + + if (isUserLocal) + { + SemaphoreSlim userLock = Account.Utils.GetUserLock(id2); + await userLock.WaitAsync(); + try { - restoreList.Add((inviteFile, "delete", "")); - await Fs.WriteFile(inviteFile, Encoding.UTF8.GetBytes("1")); //we are setting this to 1, - //so now we should have an option to delete id from dmslist and still search it - - - - BigInteger dmId = - BigInteger.Parse(Encoding.UTF8.GetString(await Fs.ReadFile($"{ROOMS_DIR}/lastdm"))); - dmId++; - var freeid = - Path.GetFileName(Directory.EnumerateFiles(ROOMS_DIR + "/freedms").FirstOrDefault()); - - - - if (freeid != null) + string dms2path = $"{ACCOUNTS_DATA_DIR}/{id2}/dms"; + string dms2; + if (await Account.Utils.NameFromId(id2) == "") //if user2 account just got deleted { - dmId = BigInteger.Parse(freeid); - Fs.DeleteFile($"{ROOMS_DIR}/freedms/{freeid}"); - restoreList.Add(($"{ROOMS_DIR}/freedms/{freeid}", "write", "")); + return "DM accepted"; + } + if (!Fs.Exists(dms2path)) + { + dms2 = ""; } else { - restoreList.Add(($"{ROOMS_DIR}/lastdm", "write", Encoding.UTF8.GetString(await Fs.ReadFile($"{ROOMS_DIR}/lastdm")))); - await Fs.WriteFile($"{ROOMS_DIR}/lastdm", Encoding.UTF8.GetBytes(dmId.ToString())); + dms2 = Encoding.UTF8.GetString(await Fs.ReadFile(dms2path)); } - await Fs.WriteFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/members", - Encoding.UTF8.GetBytes($"{id}:{DOMAIN};{id2}:{domain}")); - await Fs.WriteFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages/last", Encoding.UTF8.GetBytes($"0")); - - - Message startingMessage = new(); - startingMessage.attachment = ""; - startingMessage.author = "0"; - startingMessage.timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); - startingMessage.type = "larp.info"; - startingMessage.content = - "At the beginning of a DM, you should always verify that the person you're talking to is the intended recipient"; - startingMessage.key = ""; //no key == NOT encrypted - startingMessage.pervious = ""; - - await Fs.WriteFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/keys/0/{id};{DOMAIN}", //klucz dla uzytkownika tworzącego jest juz gotowy i ma zwykly zapis - Encoding.UTF8.GetBytes(serializedBody.string2)); - - - Universal2String keys = JsonSerializer.Deserialize( - await Account.Utils.GetUserKeys(id), - AppJsonSerializerContext.Default.Universal2String - ); - await Fs.WriteFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/keys/0/{id2};{domain}", - Encoding.UTF8.GetBytes($"SETUP:{keys.string2};{serializedBody.string3}")); //jezeli mamy setup to [1] to jest publiczny klucz drugiej osoby, - //a string 3 to zaszyfrowany klucz pokoju ktory musi odszyfrowac za pomoca swoich kluczy i tego publicznego - - await Fs.WriteFile($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}/messages/0", Encoding.UTF8.GetBytes( - JsonSerializer.Serialize(startingMessage, AppJsonSerializerContext.Default.Message) - )); - - restoreList.Add(($"{ROOMS_DIR}/dms/{DOMAIN}/{dmId}", "deletef", "")); - - string dmspath = $"{ACCOUNTS_DATA_DIR}/{id}/dms"; - string dms; - if (!Fs.Exists(dmspath)) - { - dms = ""; - } - else - { - dms = Encoding.UTF8.GetString(await Fs.ReadFile(dmspath)); - } - restoreList.Add((dmspath, "write", dms)); - await Fs.WriteFile(dmspath, Encoding.UTF8.GetBytes($"{dmId};{dms}")); - - if (isUserLocal) - { - SemaphoreSlim userLock = Account.Utils.GetUserLock(id2); - await userLock.WaitAsync(); - try - { - string dms2path = $"{ACCOUNTS_DATA_DIR}/{id2}/dms"; - string dms2; - if (await Account.Utils.NameFromId(id2) == "") //if user2 account just got deleted - { - return "DM accepted"; - } - if (!Fs.Exists(dms2path)) - { - dms2 = ""; - } - else - { - dms2 = Encoding.UTF8.GetString(await Fs.ReadFile(dms2path)); - } - - await Fs.WriteFile(dms2path, Encoding.UTF8.GetBytes($"{dmId};{dms2}")); - } - finally - { - userLock.Release(); - } - } - else //federacja - { - await Federation.Sender.CreateDm(id2, domain, dmId.ToString()); - } - + await Fs.WriteFile(dms2path, Encoding.UTF8.GetBytes($"{dmId};{dms2}")); } - catch (Exception e) + finally { - foreach (var entry in restoreList) - { - if (entry.todo == "delete") - { - Fs.DeleteFile(entry.file); - } - if (entry.todo == "deletef") - { - Fs.DeleteDirectory(entry.file); - } - if (entry.todo == "write") - { - await Fs.WriteFile(entry.file, Encoding.UTF8.GetBytes(entry.content)); - } - } - return "Failed to accept DM. Ask for another invite"; + userLock.Release(); } - - return "DM accepted"; - case "1": //accepted - return "You already have a DM with this user"; + } + else //federacja + { + await Federation.Sender.AddToDm(id, domain, id2); + } + } + catch (Exception e) + { + foreach (var entry in restoreList) + { + if (entry.todo == "delete") + { + Fs.DeleteFile(entry.file); + } + if (entry.todo == "deletef") + { + Fs.DeleteDirectory(entry.file); + } + if (entry.todo == "write") + { + await Fs.WriteFile(entry.file, Encoding.UTF8.GetBytes(entry.content)); + } + } + return "Failed to accept DM. Ask for another invite"; + } + + return "DM accepted"; + + } } finally diff --git a/LarpixServer/Utils/Utils.cs b/LarpixServer/Utils/Utils.cs index 2a9ea35..e1a68b5 100644 --- a/LarpixServer/Utils/Utils.cs +++ b/LarpixServer/Utils/Utils.cs @@ -28,7 +28,7 @@ public class Utils public static int DIR_CACHE_SIZE = 800000; public static int LOCK_SIZE = 1000000; - public static int MAX_BODY_SIZE = 65536; + public static int MAX_BODY_SIZE = 524288; //0.5mb public static int PRIVATE_STORAGE_LIMIT = 5242880; public static int PUBLIC_STORAGE_LIMIT = 10485760; @@ -205,10 +205,6 @@ public class Utils { Directory.CreateDirectory(ROOMS_DIR + "/spaces"); } - if (!Directory.Exists(ROOMS_DIR + "/freedms")) - { - Directory.CreateDirectory(ROOMS_DIR + "/freedms"); - } if (!Directory.Exists(ROOMS_DIR + "/freegroups")) { Directory.CreateDirectory(ROOMS_DIR + "/freegroups"); @@ -217,10 +213,6 @@ public class Utils { Directory.CreateDirectory(ROOMS_DIR + "/freespaces"); } - if (!File.Exists(ROOMS_DIR + "/lastdm")) - { - await File.WriteAllTextAsync(ROOMS_DIR + "/lastdm", "0"); - } if (!File.Exists(ROOMS_DIR + "/lastgroup")) { await File.WriteAllTextAsync(ROOMS_DIR + "/lastgroup", "0");