Fix user locks & change return strings to blah.blah.blah
All checks were successful
Server Build / publish (push) Successful in 27s
Voice Build / publish (push) Successful in 25s

This commit is contained in:
olcxja 2026-05-10 20:55:24 +02:00
commit 7da97dbeb6
6 changed files with 56 additions and 49 deletions

View file

@ -118,7 +118,7 @@ public class Requests
); );
if (!createHolder.TryGetValue(serializedBody.idKey, out CreateHolder entry)) if (!createHolder.TryGetValue(serializedBody.idKey, out CreateHolder entry))
{ {
await context.Response.WriteAsync("Account request expired"); await context.Response.WriteAsync("error:account.creation.request.expired");
return; return;
} }
@ -133,13 +133,13 @@ public class Requests
if (!Utils.IsValidUsername(entry.name, out string message)) if (!Utils.IsValidUsername(entry.name, out string message))
{ {
await context.Response.WriteAsync("Username: " + message); await context.Response.WriteAsync($"error:invalid.username.{{{message}}}");
return; return;
} }
if (!Utils.IsValidPassword(entry.pass, out message)) if (!Utils.IsValidPassword(entry.pass, out message))
{ {
await context.Response.WriteAsync("Password: " + message); await context.Response.WriteAsync($"error:invalid.password.{{{message}}}");
return; return;
} }
@ -162,14 +162,14 @@ public class Requests
); );
if (!createHolder.TryGetValue(serialized.idKey, out var entry)) if (!createHolder.TryGetValue(serialized.idKey, out var entry))
{ {
await context.Response.WriteAsync("Account request expired"); await context.Response.WriteAsync("error:account.creation.request.expired");
return; return;
} }
if (entry.captcha.ToLower() != serialized.captcha.ToLower()) if (entry.captcha.ToLower() != serialized.captcha.ToLower())
{ {
createHolder.TryRemove(serialized.idKey, out _); createHolder.TryRemove(serialized.idKey, out _);
await context.Response.WriteAsync("Incorrect captcha. Please try again"); await context.Response.WriteAsync("error:incorrect.captcha");
return; return;
} }
@ -177,7 +177,7 @@ public class Requests
if (Fs.Exists($"{ACCOUNTS_NAME_DIR}/{lowerName}")) if (Fs.Exists($"{ACCOUNTS_NAME_DIR}/{lowerName}"))
{ {
await context.Response.WriteAsync("This username is already taken"); await context.Response.WriteAsync("error:username.taken");
return; return;
} }
@ -185,7 +185,7 @@ public class Requests
Encoding.UTF8.GetString(await Fs.ReadFile($"{ACCOUNTS_DIR}/registration")); Encoding.UTF8.GetString(await Fs.ReadFile($"{ACCOUNTS_DIR}/registration"));
if (registrationString.StartsWith("0;")) if (registrationString.StartsWith("0;"))
{ {
await context.Response.WriteAsync("Account creation is currently disabled. Try again later"); await context.Response.WriteAsync("error:registration.disabled");
return; return;
} }
@ -217,7 +217,7 @@ public class Requests
if (!valid) if (!valid)
{ {
await context.Response.WriteAsync( await context.Response.WriteAsync(
"Account creation is currently disabled. Try again later"); "error:registration.disabled");
return; return;
} }
} }
@ -240,7 +240,7 @@ public class Requests
if (id == ulong.MaxValue) if (id == ulong.MaxValue)
{ {
await context.Response.WriteAsync( await context.Response.WriteAsync(
"Server is full, cannot create new accounts. Try again later."); "error:accounts.slots.full");
return; return;
} }
@ -264,7 +264,7 @@ public class Requests
userLock.Release(); userLock.Release();
} }
await context.Response.WriteAsync("Account created"); await context.Response.WriteAsync("success:account.created");
return; return;
} }
} }
@ -389,7 +389,7 @@ public class Requests
userLock.Release(); userLock.Release();
} }
await context.Response.WriteAsync("Password changed successfully"); await context.Response.WriteAsync("success:password.changed");
return; return;
} }
@ -444,7 +444,7 @@ public class Requests
//string publicKey = serializedBody.string1; //string publicKey = serializedBody.string1;
//string privateEncryptedKey = serializedBody.string2; //string privateEncryptedKey = serializedBody.string2;
await Utils.UpdateUserKeys(id, body); await Utils.UpdateUserKeys(id, body);
return "Keys updated successfully"; return "success:keys.updated";
} }
public static async Task GetUserPublicKey(HttpContext context, Func<Task> next, IQueryCollection query, StreamReader bodyReader) public static async Task GetUserPublicKey(HttpContext context, Func<Task> next, IQueryCollection query, StreamReader bodyReader)
@ -565,6 +565,11 @@ public class Requests
await Room.Requests.DmCreate(id, serializedBody.string2) await Room.Requests.DmCreate(id, serializedBody.string2)
, password)); , password));
break; break;
default:
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(
$"error:unknown.request.{{{serializedBody.string1}}}"
, password));
break;
} }
} }
finally finally

View file

@ -13,7 +13,7 @@ public class Utils
{ {
private static readonly Regex USERNAME_REGEX = new Regex(@"^[a-zA-Z0-9_]+$"); private static readonly Regex USERNAME_REGEX = new Regex(@"^[a-zA-Z0-9_]+$");
public static string LOGIN_SUCCESS = "Login successful"; public static string LOGIN_SUCCESS = "success:login.successful";
public static ConcurrentDictionary<string, SemaphoreSlim> userLocks = new(); public static ConcurrentDictionary<string, SemaphoreSlim> userLocks = new();
public static ConcurrentQueue<string> keyQueue = new(); public static ConcurrentQueue<string> keyQueue = new();
@ -58,16 +58,16 @@ public class Utils
public static bool IsValidUsername(string username, out string message) public static bool IsValidUsername(string username, out string message)
{ {
message = "Username must be 3-18 characters long"; message = "error:username.length.{3-18}";
if (string.IsNullOrWhiteSpace(username)) return false; if (string.IsNullOrWhiteSpace(username)) return false;
if (username.Length is < 3 or > 18) return false; if (username.Length is < 3 or > 18) return false;
message = "Only letters, numbers, and underscores allowed"; message = "error:username.conditions.allowed.{letters,numbers,underscores}";
return USERNAME_REGEX.IsMatch(username); return USERNAME_REGEX.IsMatch(username);
} }
public static bool IsValidPassword(string password, out string message) public static bool IsValidPassword(string password, out string message)
{ {
message = "Password is not hashed properly"; message = "error:password.not.hashed.properly";
if (password.Length != 128) if (password.Length != 128)
{ {
return false; return false;
@ -126,11 +126,11 @@ public class Utils
{ {
if (!Fs.Exists($"{ACCOUNTS_DATA_DIR}/{id}")) if (!Fs.Exists($"{ACCOUNTS_DATA_DIR}/{id}"))
{ {
return "Invalid username or password"; return "error:invalid.username.or.password";
} }
if (password != password2) if (password != password2)
{ {
return "Invalid password"; return "error:invalid.password";
} }
await UpdateLastLogin(id); await UpdateLastLogin(id);
@ -141,7 +141,7 @@ public class Utils
{ {
if (!Requests.nonceHolder.TryGetValue(username, out (string, DateTimeOffset) nonce)) if (!Requests.nonceHolder.TryGetValue(username, out (string, DateTimeOffset) nonce))
{ {
return "Invalid nonce"; return "error:invalid.nonce";
} }
string decBody = Encryption.Encryption.PacketDecPass(body, password, nonce.Item1); string decBody = Encryption.Encryption.PacketDecPass(body, password, nonce.Item1);
if (delEntry) if (delEntry)
@ -275,7 +275,7 @@ public class Utils
{ {
if (!Fs.Exists($"{ACCOUNTS_DATA_DIR}/{id}/secret")) if (!Fs.Exists($"{ACCOUNTS_DATA_DIR}/{id}/secret"))
{ {
return "Account does not exist"; return "error:account.not.exist";
} }
if (!IsValidUsername(newName, out string message)) if (!IsValidUsername(newName, out string message))
@ -290,13 +290,13 @@ public class Utils
{ {
if (Fs.Exists($"{ACCOUNTS_NAME_DIR}/{lowerNewName}")) if (Fs.Exists($"{ACCOUNTS_NAME_DIR}/{lowerNewName}"))
{ {
return "This username is already taken"; return "error:username.taken";
} }
Fs.MoveFile($"{ACCOUNTS_NAME_DIR}/{lowerOldName}", $"{ACCOUNTS_NAME_DIR}/{lowerNewName}"); Fs.MoveFile($"{ACCOUNTS_NAME_DIR}/{lowerOldName}", $"{ACCOUNTS_NAME_DIR}/{lowerNewName}");
} }
await Fs.WriteFile($"{ACCOUNTS_DATA_DIR}/{id}/username", Encoding.UTF8.GetBytes(newName)); await Fs.WriteFile($"{ACCOUNTS_DATA_DIR}/{id}/username", Encoding.UTF8.GetBytes(newName));
return "Username changed successfully"; return "success:username.changed";
} }
} }

View file

@ -32,7 +32,7 @@ public class Receiver
if (!await Sender.HasSentDmInvite(ids[1], domain, ids[0])) if (!await Sender.HasSentDmInvite(ids[1], domain, ids[0]))
{ {
await context.Response.WriteAsync( "Bad request" ); await context.Response.WriteAsync( "error:bad.request" );
return; return;
} }
@ -43,7 +43,7 @@ public class Receiver
string inviteFile = ACCOUNTS_DATA_DIR + $"/{ids[0]}/dminvites/recv/{ids[1]};{domain}"; string inviteFile = ACCOUNTS_DATA_DIR + $"/{ids[0]}/dminvites/recv/{ids[1]};{domain}";
if (Fs.Exists(inviteFile)) if (Fs.Exists(inviteFile))
{ {
await context.Response.WriteAsync("User already invited"); await context.Response.WriteAsync("info:user.already.invited");
return; return;
} }
await Fs.WriteFile(inviteFile, Encoding.UTF8.GetBytes("0")); await Fs.WriteFile(inviteFile, Encoding.UTF8.GetBytes("0"));
@ -54,7 +54,7 @@ public class Receiver
userLock.Release(); userLock.Release();
} }
await context.Response.WriteAsync("User invited"); await context.Response.WriteAsync("success:user.invited");
return; return;
case "dm/invite/hassent": case "dm/invite/hassent":
@ -77,7 +77,7 @@ public class Receiver
inviteSentFile = ACCOUNTS_DATA_DIR + $"/{ids[0]}/dminvites/sent/{ids[1]};{domain}"; inviteSentFile = ACCOUNTS_DATA_DIR + $"/{ids[0]}/dminvites/sent/{ids[1]};{domain}";
if (!Fs.Exists(inviteSentFile)) if (!Fs.Exists(inviteSentFile))
{ {
await context.Response.WriteAsync( "Failed to add. No invite found" ); await context.Response.WriteAsync( "error:no.invite.found" );
return; return;
} }
Fs.DeleteFile(inviteSentFile); Fs.DeleteFile(inviteSentFile);
@ -99,7 +99,7 @@ public class Receiver
DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString()); DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString());
await context.Response.WriteAsync("DM accepted"); await context.Response.WriteAsync("success:dm.accepted");
return; return;
} }

View file

@ -184,6 +184,7 @@ public class Program
} }
catch (Exception ex) catch (Exception ex)
{ {
await context.Response.WriteAsync($"error:unknown.error");
Console.WriteLine(ex); Console.WriteLine(ex);
} }

View file

@ -14,23 +14,24 @@ public class Requests
{ {
if (!Account.Utils.IsUserLocal(username2, out string domain)) //federation if (!Account.Utils.IsUserLocal(username2, out string domain)) //federation
{ {
SemaphoreSlim userLock = Account.Utils.GetUserLock(id);
await userLock.WaitAsync(); username2 = username2.Split(":")[0];
try string remoteId2 = await Federation.Sender.GetUserId(username2, domain);
{ await Fs.WriteFile(ACCOUNTS_DATA_DIR + $"/{id}/dminvites/sent/{remoteId2};{domain}", []); //only save sent while federating
username2 = username2.Split(":")[0]; return await Federation.Sender.DmInvite(id, domain, remoteId2);
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]; username2 = username2.Split(":")[0];
string id2 = await Account.Utils.IdFromName(username2); string id2 = await Account.Utils.IdFromName(username2);
if (id2 == "0")
{
return "error:user.not.found";
}
if (id2 == id)
{
return "error:cant.invite.urself";
}
SemaphoreSlim userLock2 = Account.Utils.GetUserLock(id2); SemaphoreSlim userLock2 = Account.Utils.GetUserLock(id2);
await userLock2.WaitAsync(); await userLock2.WaitAsync();
@ -39,7 +40,7 @@ public class Requests
string inviteFile = ACCOUNTS_DATA_DIR + $"/{id2}/dminvites/recv/{id};{DOMAIN}"; string inviteFile = ACCOUNTS_DATA_DIR + $"/{id2}/dminvites/recv/{id};{DOMAIN}";
if (Fs.Exists(inviteFile)) if (Fs.Exists(inviteFile))
{ {
return "User already invited"; return "info:user.already.invited";
} }
await Fs.WriteFile(inviteFile, Encoding.UTF8.GetBytes("0")); await Fs.WriteFile(inviteFile, Encoding.UTF8.GetBytes("0"));
@ -52,7 +53,7 @@ public class Requests
userLock2.Release(); userLock2.Release();
} }
return "User invited"; return "success:user.invited";
} }
public static async Task<string> DmCreate(string id, string body) public static async Task<string> DmCreate(string id, string body)
@ -93,7 +94,7 @@ public class Requests
{ {
if (!await Federation.Sender.HasSentDmInvite(id2, domain, id)) if (!await Federation.Sender.HasSentDmInvite(id2, domain, id))
{ {
return "You can't create a DM without an invitation"; return "error:cant.create.dm.without.invitation";
} }
} }
@ -123,7 +124,7 @@ public class Requests
startingMessage.timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); startingMessage.timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
startingMessage.type = "larp.info"; startingMessage.type = "larp.info";
startingMessage.content = startingMessage.content =
"At the beginning of a DM, you should always verify that the person you're talking to is the intended recipient"; "dm.begin.notice";
startingMessage.key = ""; //no key == NOT encrypted startingMessage.key = ""; //no key == NOT encrypted
startingMessage.pervious = ""; startingMessage.pervious = "";
@ -169,10 +170,10 @@ public class Requests
catch (Exception e) catch (Exception e)
{ {
return "Failed to accept DM. Ask for another invite"; return "error:failed.accept.dm";
} }
return "DM accepted"; return "success:dm.accepted";
} }
@ -181,6 +182,6 @@ public class Requests
{ {
createLock.Release(); createLock.Release();
} }
return "You can't create a DM without an invitation"; return "error:cant.create.dm.without.invitation";
} }
} }

View file

@ -109,7 +109,7 @@ public class Utils
totalRead += read; totalRead += read;
if (totalRead > MAX_BODY_SIZE) if (totalRead > MAX_BODY_SIZE)
throw new Exception("Body too large"); throw new Exception("error:body.too.large");
bodyBuilder.Append(buffer, 0, read); bodyBuilder.Append(buffer, 0, read);
} }