better account deletion, add keys to password change,add updating user storage
All checks were successful
Server Build / publish (push) Successful in 30s
Voice Build / publish (push) Successful in 26s

This commit is contained in:
olcxja 2026-06-23 23:23:00 +02:00
commit 499de238cb
5 changed files with 168 additions and 28 deletions

View file

@ -44,7 +44,10 @@ public class BackgroundDelete : BackgroundService
try try
{ {
string username = await Account.Utils.NameFromId(id); 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.DeleteDirectory(account);
Fs.WriteFile($"{LarpixServer.Utils.Utils.ACCOUNTS_FREEID_DIR}/{id}", []); Fs.WriteFile($"{LarpixServer.Utils.Utils.ACCOUNTS_FREEID_DIR}/{id}", []);
Console.WriteLine($"Deleted {id} ({username})"); Console.WriteLine($"Deleted {id} ({username})");

View file

@ -53,8 +53,7 @@ public class Requests
userLock.Release(); userLock.Release();
} }
await context.Response.WriteAsync( await context.Response.WriteAsync("success:account.deleted.scheduled");
"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");
} }
public static async Task Create(HttpContext context, Func<Task> next, IQueryCollection query, public static async Task Create(HttpContext context, Func<Task> next, IQueryCollection query,
@ -232,33 +231,32 @@ public class Requests
ulong id = ulong.Parse(await Fs.ReadFile($"{ACCOUNTS_DIR}/last")); ulong id = ulong.Parse(await Fs.ReadFile($"{ACCOUNTS_DIR}/last"));
id++; id++;
var freeid = Path.GetFileName(Directory.EnumerateFiles(ACCOUNTS_FREEID_DIR).FirstOrDefault()); if (id == ulong.MaxValue)
if (freeid != null)
{ {
id = ulong.Parse(freeid); await context.Response.WriteAsync("error:accounts.slots.full");
Fs.DeleteFile($"{ACCOUNTS_FREEID_DIR}/{freeid}"); return;
}
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 Fs.WriteFile($"{ACCOUNTS_DIR}/last", Encoding.UTF8.GetBytes(id.ToString()));
SemaphoreSlim userLock = Utils.GetUserLock(id.ToString()); SemaphoreSlim userLock = Utils.GetUserLock(id.ToString());
await userLock.WaitAsync(); await userLock.WaitAsync();
try try
{ {
await Fs.WriteFile($"{ACCOUNTS_NAME_DIR}/{lowerName}", Encoding.UTF8.GetBytes(id.ToString())); 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)); try
await Fs.WriteFile($"{ACCOUNTS_DATA_DIR}/{id.ToString()}/secret", {
Encoding.UTF8.GetBytes(entry.pass)); 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 _); createHolder.TryRemove(serialized.idKey, out _);
await Utils.UpdateLastLogin(id.ToString()); await Utils.UpdateLastLogin(id.ToString());
} }
@ -408,7 +406,7 @@ public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollec
string id = Utils.GetValidIdOrZero(idQuery.ToString()); string id = Utils.GetValidIdOrZero(idQuery.ToString());
string body = await LoadBody(bodyReader); string body = await LoadBody(bodyReader);
string password = await Utils.GetPassword(id); 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); secret = await Utils.NonceDecryptBody(id, password, secret);
string auth = await Utils.Auth(id, password, secret); string auth = await Utils.Auth(id, password, secret);
@ -419,6 +417,20 @@ public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollec
return; 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); SemaphoreSlim userLock = Utils.GetUserLock(id);
await userLock.WaitAsync(); await userLock.WaitAsync();
try try
@ -429,7 +441,30 @@ public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollec
return; 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 finally
{ {
@ -620,6 +655,30 @@ public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollec
await Utils.GetUserKeys(id) await Utils.GetUserKeys(id)
, password)); , password));
break; 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": case "user/dm/list":
await context.Response.WriteAsync( await context.Response.WriteAsync(
Encryption.Encryption.EncryptString( Encryption.Encryption.EncryptString(

View file

@ -304,6 +304,72 @@ public class Utils
return await Fs.ReadFile(path); return await Fs.ReadFile(path);
} }
public static async Task<string> 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<byte[]> 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<string> 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 = "") 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}"); 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"; return "success:username.changed";
} }

View file

@ -396,7 +396,7 @@ public class Requests
msg.type = "larp.text"; msg.type = "larp.text";
msg.content = content; msg.content = content;
msg.attachment = attachment; msg.attachment = attachment;
msg.key = "0"; // Flag for encrypted content msg.key = "0"; //empty = no encryption, value = encryption key index
msg.pervious = ""; msg.pervious = "";
msg.responded = responded ?? ""; msg.responded = responded ?? "";
msg.reactions = ""; msg.reactions = "";
@ -435,7 +435,7 @@ public class Requests
} }
} }
} }
return "success:message.sent"; return $"success:{nextId}";
} }
public static async Task<string> DmMessageDelete(string id, string body) public static async Task<string> DmMessageDelete(string id, string body)

View file

@ -30,7 +30,7 @@ public class Utils
public static int DIR_CACHE_SIZE = 800000; public static int DIR_CACHE_SIZE = 800000;
public static int LOCK_SIZE = 1000000; 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 PRIVATE_STORAGE_LIMIT = 5242880;
public static int PUBLIC_STORAGE_LIMIT = 10485760; public static int PUBLIC_STORAGE_LIMIT = 10485760;