Improve federation design in DM creation

This commit is contained in:
olcxja 2026-04-24 09:17:04 +02:00
commit 8de0e0cacb
3 changed files with 174 additions and 94 deletions

View file

@ -0,0 +1,6 @@
namespace LarpixServer.Federation;
public class Receiver
{
}

View file

@ -0,0 +1,24 @@
namespace LarpixServer.Federation;
public class Sender
{
public static async Task<string> GetUserId(string username, string domain)
{
return "?";
}
public static async Task<bool> 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<string> DmInvite(string senderId, string domain, string receiverId) //sender is always from local domain
{
return "User invited";
}
}

View file

@ -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";