diff --git a/LarpixServer/Federation/Receiver.cs b/LarpixServer/Federation/Receiver.cs new file mode 100644 index 0000000..3cf508c --- /dev/null +++ b/LarpixServer/Federation/Receiver.cs @@ -0,0 +1,6 @@ +namespace LarpixServer.Federation; + +public class Receiver +{ + +} \ No newline at end of file diff --git a/LarpixServer/Federation/Sender.cs b/LarpixServer/Federation/Sender.cs new file mode 100644 index 0000000..bfe06ff --- /dev/null +++ b/LarpixServer/Federation/Sender.cs @@ -0,0 +1,24 @@ +namespace LarpixServer.Federation; + +public class Sender +{ + public static async Task GetUserId(string username, string domain) + { + return "?"; + } + + public static async Task HasSentDmInvite(string senderId, string domain, string receiverId) //receiver is always from local domain + { + return true; + } + + public static async Task CreateDm(string id, string domain, string dmId) + { + + } + + public static async Task DmInvite(string senderId, string domain, string receiverId) //sender is always from local domain + { + return "User invited"; + } +} \ No newline at end of file diff --git a/LarpixServer/Room/Requests.cs b/LarpixServer/Room/Requests.cs index b44ea1c..24c59dd 100644 --- a/LarpixServer/Room/Requests.cs +++ b/LarpixServer/Room/Requests.cs @@ -14,16 +14,21 @@ public class Requests { if (!Account.Utils.IsUserLocal(username2, out string domain)) //federation { - return ""; + 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); } username2 = username2.Split(":")[0]; string id2 = await Account.Utils.IdFromName(username2); - SemaphoreSlim userLock = Account.Utils.GetUserLock(id2); + 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)) { @@ -31,6 +36,7 @@ public class Requests 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"; @@ -39,10 +45,11 @@ public class Requests } await Fs.WriteFile(inviteFile, Encoding.UTF8.GetBytes("0")); - await Fs.WriteFile(ACCOUNTS_DATA_DIR + $"/{id}/dminvites/sent/{id2};{domain}", []); //we should also save that this user invited someone, because this will act as dm accept verification while federating + 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(); } @@ -67,9 +74,26 @@ public class Requests { id2 = await Account.Utils.IdFromName(username2); } - else //id z federacji + else //get id from federation { - id2 = "?"; + id2 = await Federation.Sender.GetUserId(username2, domain); + } + + 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); + } + else //also check this on federated server + { + if (!await Federation.Sender.HasSentDmInvite(id2, domain, id)) + { + return "You can't create a DM without an invitation"; + } } string inviteFile = ACCOUNTS_DATA_DIR + $"/{id}/dminvites/recv/{id2};{domain}"; @@ -79,108 +103,134 @@ public class Requests switch (inviteState) { case "0": //invited - 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 (!isUserLocal) //federacja + 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 { - //jezeli freeid tez jest wolne na innym serwerze (tez jako freeid) to wszystko zostawiamy tak jak jest - //w przeciwnym razie porownojemy lastid z 2 serwerow i bierzemy to większe + 1 jako docelowe id dma + 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 - //no ale moze to byc problematyczne - } + + + 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) - { - dmId = BigInteger.Parse(freeid); - Fs.DeleteFile($"{ROOMS_DIR}/freedms/{freeid}"); - } - else - { - await Fs.WriteFile($"{ROOMS_DIR}/lastdm", Encoding.UTF8.GetBytes(dmId.ToString())); - } - - await Fs.WriteFile($"{ROOMS_DIR}/dms/{dmId}/members", - Encoding.UTF8.GetBytes($"{id}:{DOMAIN};{id2}:{domain}")); - await Fs.WriteFile($"{ROOMS_DIR}/dms/{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/{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/{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/{dmId}/messages/0", Encoding.UTF8.GetBytes( - JsonSerializer.Serialize(startingMessage, AppJsonSerializerContext.Default.Message) - )); - - string dmspath = $"{ACCOUNTS_DATA_DIR}/{id}/dms"; - string dms; - if (!Fs.Exists(dmspath)) - { - dms = ""; - } - else - { - dms = Encoding.UTF8.GetString(await Fs.ReadFile(dmspath)); - } - - await Fs.WriteFile(dmspath, Encoding.UTF8.GetBytes($"{dmId};{dms}")); - - if (isUserLocal) - { - SemaphoreSlim userLock = Account.Utils.GetUserLock(id2); - await userLock.WaitAsync(); - try + if (freeid != null) { - 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}")); + dmId = BigInteger.Parse(freeid); + Fs.DeleteFile($"{ROOMS_DIR}/freedms/{freeid}"); + restoreList.Add(($"{ROOMS_DIR}/freedms/{freeid}", "write", "")); } - finally + else { - userLock.Release(); + 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())); } - } - else //federacja - { + 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()); + } + + } + 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";