First commit
This commit is contained in:
commit
0ac6ff9196
26 changed files with 2836 additions and 0 deletions
591
LarpixServer/Account/Requests.cs
Normal file
591
LarpixServer/Account/Requests.cs
Normal file
|
|
@ -0,0 +1,591 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using LarpixServer.Filesystem;
|
||||
using LarpixServer.Utils;
|
||||
using LarpixServer.Utils.Jsons;
|
||||
using static LarpixServer.Utils.Utils;
|
||||
|
||||
namespace LarpixServer.Account;
|
||||
|
||||
public class Requests
|
||||
{
|
||||
public static ConcurrentDictionary<string, CreateHolder> createHolder = new();
|
||||
public static ConcurrentDictionary<string, (string, DateTimeOffset)> nonceHolder = new();
|
||||
|
||||
public static SemaphoreSlim createLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
|
||||
public static async Task Delete(HttpContext context, Func<Task> next, IQueryCollection query,
|
||||
StreamReader bodyReader)
|
||||
{
|
||||
if (!query.TryGetValue("u", out var username))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.Request.Headers.TryGetValue("secret", out var secret))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string body = await LoadBody(bodyReader);
|
||||
string id = await Utils.IdFromName(username);
|
||||
string password = await Utils.GetPassword(id);
|
||||
body = await Utils.NonceDecryptBody(username, password, body);
|
||||
string auth = await Utils.Auth(id, password, body);
|
||||
if (auth != Utils.LOGIN_SUCCESS)
|
||||
{
|
||||
await context.Response.WriteAsync(auth);
|
||||
return;
|
||||
}
|
||||
|
||||
SemaphoreSlim userLock = Utils.GetUserLock(id);
|
||||
await userLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
await Fs.WriteFile($"{ACCOUNTS_DATA_DIR}/{id}/lastlogin",
|
||||
Encoding.UTF8.GetBytes(DateTimeOffset.UtcNow.AddMonths(-11).ToUnixTimeSeconds().ToString()));
|
||||
}
|
||||
finally
|
||||
{
|
||||
userLock.Release();
|
||||
}
|
||||
|
||||
await context.Response.WriteAsync(
|
||||
"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,
|
||||
StreamReader bodyReader)
|
||||
{
|
||||
if (!query.TryGetValue("step", out var step))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await createLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
switch (step)
|
||||
{
|
||||
case "init":
|
||||
foreach (var kvp in createHolder) // czyszczenie nieaktywnych od 2 minut requestow
|
||||
{
|
||||
if (kvp.Value.date < DateTimeOffset.UtcNow.AddMinutes(-2))
|
||||
{
|
||||
createHolder.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
context.Response.ContentType = mimeTypes["json"];
|
||||
|
||||
(BigInteger p, BigInteger g, BigInteger pubServer, BigInteger secretServer) serverInfo =
|
||||
Encryption.Encryption.Init();
|
||||
KeyExchangePayload payload = new KeyExchangePayload();
|
||||
payload.p = serverInfo.p.ToString();
|
||||
payload.g = serverInfo.g.ToString();
|
||||
payload.pubServer = serverInfo.pubServer.ToString();
|
||||
payload.idKey = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + "\n";
|
||||
|
||||
while (createHolder.ContainsKey(payload.idKey))
|
||||
{
|
||||
payload.idKey += Random.Shared.Next(0, 10).ToString();
|
||||
}
|
||||
|
||||
CreateHolder dataHolder = new();
|
||||
dataHolder.date = DateTimeOffset.UtcNow;
|
||||
dataHolder.serverInfo = serverInfo;
|
||||
|
||||
createHolder.TryAdd(payload.idKey, dataHolder);
|
||||
|
||||
var serializedPayload = JsonSerializer.Serialize(
|
||||
payload,
|
||||
AppJsonSerializerContext.Default.KeyExchangePayload
|
||||
);
|
||||
|
||||
await context.Response.WriteAsync(serializedPayload);
|
||||
return;
|
||||
case "register":
|
||||
string body = await LoadBody(bodyReader);
|
||||
|
||||
KeyExchangePayloadClient serializedBody = JsonSerializer.Deserialize(
|
||||
body,
|
||||
AppJsonSerializerContext.Default.KeyExchangePayloadClient
|
||||
);
|
||||
if (!createHolder.TryGetValue(serializedBody.idKey, out CreateHolder entry))
|
||||
{
|
||||
await context.Response.WriteAsync("Account request expired");
|
||||
return;
|
||||
}
|
||||
|
||||
entry.date = DateTimeOffset.UtcNow;
|
||||
entry.pubClient = BigInteger.Parse(serializedBody.pubClient);
|
||||
byte[] sharedKey = Encryption.Encryption.CalcCommunicationKey(entry.pubClient,
|
||||
entry.serverInfo.secretServer, entry.serverInfo.p);
|
||||
|
||||
|
||||
entry.name = Encryption.Encryption.Decrypt(serializedBody.username, sharedKey);
|
||||
entry.pass = Encryption.Encryption.Decrypt(serializedBody.password, sharedKey);
|
||||
|
||||
if (!Utils.IsValidUsername(entry.name, out string message))
|
||||
{
|
||||
await context.Response.WriteAsync("Username: " + message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Utils.IsValidPassword(entry.pass, out message))
|
||||
{
|
||||
await context.Response.WriteAsync("Password: " + message);
|
||||
return;
|
||||
}
|
||||
|
||||
(byte[] ImageBytes, string CaptchaText) captchaResult = Captcha.GenerateCaptcha();
|
||||
entry.captcha = captchaResult.CaptchaText;
|
||||
context.Response.ContentType = mimeTypes["webp"];
|
||||
context.Response.ContentLength = captchaResult.ImageBytes.Length;
|
||||
|
||||
await context.Response.Body.WriteAsync(captchaResult.ImageBytes, 0,
|
||||
captchaResult.ImageBytes.Length);
|
||||
return;
|
||||
case "finish":
|
||||
body = await LoadBody(bodyReader);
|
||||
|
||||
CaptchaPayloadClient serialized = JsonSerializer.Deserialize(
|
||||
body,
|
||||
AppJsonSerializerContext.Default.CaptchaPayloadClient
|
||||
);
|
||||
if (!createHolder.TryGetValue(serialized.idKey, out entry))
|
||||
{
|
||||
await context.Response.WriteAsync("Account request expired");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.captcha.ToLower() != serialized.captcha.ToLower())
|
||||
{
|
||||
createHolder.TryRemove(serialized.idKey, out _);
|
||||
await context.Response.WriteAsync("Incorrect captcha. Please try again");
|
||||
return;
|
||||
}
|
||||
|
||||
string lowerName = entry.name.ToLowerInvariant();
|
||||
|
||||
if (Fs.Exists($"{ACCOUNTS_NAME_DIR}/{lowerName}"))
|
||||
{
|
||||
await context.Response.WriteAsync("This username is already taken");
|
||||
return;
|
||||
}
|
||||
|
||||
string registrationString =
|
||||
Encoding.UTF8.GetString(await Fs.ReadFile($"{ACCOUNTS_DIR}/registration"));
|
||||
if (registrationString.StartsWith("0;"))
|
||||
{
|
||||
await context.Response.WriteAsync("Account creation is currently disabled. Try again later");
|
||||
return;
|
||||
}
|
||||
|
||||
if (registrationString.StartsWith("code;"))
|
||||
{
|
||||
bool valid = false;
|
||||
string[] regArray = registrationString.Split(';');
|
||||
foreach (string regEntryString in regArray)
|
||||
{
|
||||
string[] regEntry = regEntryString.Split(":");
|
||||
if (regEntry[0] == "|" + serialized.regKey)
|
||||
{
|
||||
if (regEntry[1] != "infinite")
|
||||
{
|
||||
int left = int.Parse(regEntry[1]) - 1;
|
||||
if (left < 1)
|
||||
{
|
||||
await Fs.WriteFile($"{ACCOUNTS_DIR}/registration",
|
||||
Encoding.UTF8.GetBytes(
|
||||
registrationString.Replace(regEntryString + ";", "")));
|
||||
}
|
||||
}
|
||||
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
await context.Response.WriteAsync(
|
||||
"Account creation is currently disabled. Try again later");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (registrationString.StartsWith("first;"))
|
||||
{
|
||||
await Fs.WriteFile($"{ACCOUNTS_DIR}/registration", Encoding.UTF8.GetBytes("0;"));
|
||||
}
|
||||
|
||||
|
||||
ulong id = ulong.Parse(await Fs.ReadFile($"{ACCOUNTS_DIR}/last"));
|
||||
id++;
|
||||
var freeid = Path.GetFileName(Directory.EnumerateFiles(ACCOUNTS_FREEID_DIR).FirstOrDefault());
|
||||
if (freeid != null)
|
||||
{
|
||||
id = ulong.Parse(freeid);
|
||||
Fs.DeleteFile($"{ACCOUNTS_FREEID_DIR}/{freeid}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (id == ulong.MaxValue)
|
||||
{
|
||||
await context.Response.WriteAsync(
|
||||
"Server is full, cannot create new accounts. Try again later.");
|
||||
return;
|
||||
}
|
||||
|
||||
await Fs.WriteFile($"{ACCOUNTS_DIR}/last", Encoding.UTF8.GetBytes(id.ToString()));
|
||||
}
|
||||
|
||||
SemaphoreSlim userLock = Utils.GetUserLock(id.ToString());
|
||||
await userLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
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));
|
||||
await Fs.WriteFile($"{ACCOUNTS_DATA_DIR}/{id.ToString()}/secret",
|
||||
Encoding.UTF8.GetBytes(entry.pass));
|
||||
createHolder.TryRemove(serialized.idKey, out _);
|
||||
await Utils.UpdateLastLogin(id.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
userLock.Release();
|
||||
}
|
||||
|
||||
await context.Response.WriteAsync("Account created");
|
||||
return;
|
||||
}
|
||||
|
||||
await next();
|
||||
}
|
||||
finally
|
||||
{
|
||||
createLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Auth(HttpContext context, Func<Task> next, IQueryCollection query, StreamReader bodyReader)
|
||||
{
|
||||
if (!query.TryGetValue("u", out var username))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.Request.Headers.TryGetValue("secret", out var secret))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string id = await Utils.IdFromName(username);
|
||||
string password = await Utils.GetPassword(id);
|
||||
secret = await Utils.NonceDecryptBody(username, password, secret);
|
||||
string auth = await Utils.Auth(id, password, secret);
|
||||
|
||||
await context.Response.WriteAsync(auth);
|
||||
return;
|
||||
}
|
||||
|
||||
public static async Task CorrectedName(HttpContext context, Func<Task> next, IQueryCollection query)
|
||||
{
|
||||
if (!query.TryGetValue("u", out var username))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Utils.IsUserLocal(username, out string domain)) //federation :(
|
||||
{
|
||||
return;
|
||||
}
|
||||
username = username.ToString().Split(":")[0];
|
||||
|
||||
await context.Response.WriteAsync(await Utils.NameFromId(await Utils.IdFromName(username)));
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public static async Task NextNonce(HttpContext context, Func<Task> next, IQueryCollection query)
|
||||
{
|
||||
if (!query.TryGetValue("u", out var username))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string plainPass = await Utils.GetPassword(await Utils.IdFromName(username));
|
||||
foreach (var kvp in nonceHolder) //clearowanie nieuzytych nonce
|
||||
{
|
||||
/*
|
||||
if (kvp.Key == username)
|
||||
{
|
||||
nonceHolder.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
*/ //tak teraz sobie mysle moze jednak nie usuwac nonce?? bo co jak jakis cep beedzie ciagle komus spamic i
|
||||
//bedzie mial nizszy ping i gosc nie bedzie mogl zadnego req wyslac, a nawet jak ktos bez secret to odczyta
|
||||
//to nic z tym nie zrobi
|
||||
if (kvp.Value.Item2 < DateTimeOffset.UtcNow.AddMinutes(-2))
|
||||
{
|
||||
nonceHolder.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
else if (kvp.Key == username)
|
||||
{
|
||||
if (nonceHolder.TryGetValue(kvp.Key, out (string, DateTimeOffset) cachedNonce))
|
||||
{
|
||||
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(cachedNonce.Item1,
|
||||
plainPass));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string nonce = Encryption.Encryption.GetRandomString(64);
|
||||
nonceHolder.TryAdd(username, (nonce, DateTimeOffset.UtcNow));
|
||||
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(nonce,
|
||||
plainPass));
|
||||
return;
|
||||
}
|
||||
|
||||
public static async Task ChangePassword(HttpContext context, Func<Task> next, IQueryCollection query,
|
||||
StreamReader bodyReader)
|
||||
{
|
||||
if (!query.TryGetValue("u", out var username))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.Request.Headers.TryGetValue("secret", out var secret))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string id = await Utils.IdFromName(username);
|
||||
string body = await LoadBody(bodyReader);
|
||||
string password = await Utils.GetPassword(id);
|
||||
string newPass = await Utils.NonceDecryptBody(username, password, body);
|
||||
secret = await Utils.NonceDecryptBody(username, password, secret);
|
||||
|
||||
string auth = await Utils.Auth(id, password, secret);
|
||||
|
||||
if (auth != Utils.LOGIN_SUCCESS)
|
||||
{
|
||||
await context.Response.WriteAsync(auth);
|
||||
return;
|
||||
}
|
||||
|
||||
SemaphoreSlim userLock = Utils.GetUserLock(id);
|
||||
await userLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (!Utils.IsValidPassword(newPass, out string message))
|
||||
{
|
||||
await context.Response.WriteAsync(message);
|
||||
return;
|
||||
}
|
||||
|
||||
await Utils.SetPassword(id, newPass);
|
||||
}
|
||||
finally
|
||||
{
|
||||
userLock.Release();
|
||||
}
|
||||
|
||||
await context.Response.WriteAsync("Password changed successfully");
|
||||
return;
|
||||
}
|
||||
|
||||
public static async Task ChangeUsername(HttpContext context, Func<Task> next, IQueryCollection query,
|
||||
StreamReader bodyReader)
|
||||
{
|
||||
if (!query.TryGetValue("u", out var username))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.Request.Headers.TryGetValue("secret", out var secret))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string id = await Utils.IdFromName(username);
|
||||
string body = await LoadBody(bodyReader);
|
||||
string password = await Utils.GetPassword(id);
|
||||
body = await Utils.NonceDecryptBody(username, password, body);
|
||||
secret = await Utils.NonceDecryptBody(username, password, secret);
|
||||
|
||||
string auth = await Utils.Auth(id, secret, password);
|
||||
if (auth != Utils.LOGIN_SUCCESS)
|
||||
{
|
||||
await context.Response.WriteAsync(auth);
|
||||
return;
|
||||
}
|
||||
|
||||
SemaphoreSlim userLock = Utils.GetUserLock(id.ToString());
|
||||
await userLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
await context.Response.WriteAsync(await Utils.SetUsername(id, body));
|
||||
}
|
||||
finally
|
||||
{
|
||||
userLock.Release();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public static async Task<string> UpdateUserKeys(string id, string body)
|
||||
{
|
||||
Universal2String serializedBody = JsonSerializer.Deserialize(
|
||||
body,
|
||||
AppJsonSerializerContext.Default.Universal2String
|
||||
);
|
||||
//string publicKey = serializedBody.string1;
|
||||
//string privateEncryptedKey = serializedBody.string2;
|
||||
await Utils.UpdateUserKeys(id, body);
|
||||
return "Keys updated successfully";
|
||||
}
|
||||
|
||||
public static async Task GetUserPublicKey(HttpContext context, Func<Task> next, IQueryCollection query, StreamReader bodyReader)
|
||||
{
|
||||
if (!query.TryGetValue("u", out var username))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Utils.IsUserLocal(username, out string domain)) //federation :(
|
||||
{
|
||||
return;
|
||||
}
|
||||
username = username.ToString().Split(":")[0];
|
||||
|
||||
string id = await Utils.IdFromName(username);
|
||||
|
||||
Universal2String keys = JsonSerializer.Deserialize(
|
||||
await Utils.GetUserKeys(id),
|
||||
AppJsonSerializerContext.Default.Universal2String
|
||||
);
|
||||
await context.Response.WriteAsync(keys.string2);
|
||||
return;
|
||||
}
|
||||
|
||||
public static async Task GetUserPublicStorageEntry(HttpContext context, Func<Task> next, IQueryCollection query, StreamReader bodyReader)
|
||||
{
|
||||
if (!query.TryGetValue("u", out var username))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!query.TryGetValue("entry", out var entry))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Utils.IsUserLocal(username, out string domain)) //federation :(
|
||||
{
|
||||
return;
|
||||
}
|
||||
username = username.ToString().Split(":")[0];
|
||||
|
||||
string id = await Utils.IdFromName(username);
|
||||
|
||||
byte[] entryByte = await Utils.GetUserPublicStorageEntry(id, entry);
|
||||
|
||||
context.Response.ContentType = mimeTypes["unknown"];
|
||||
context.Response.ContentLength = entryByte.Length;
|
||||
|
||||
await context.Response.Body.WriteAsync(entryByte, 0,
|
||||
entryByte.Length);
|
||||
return;
|
||||
}
|
||||
|
||||
public static async Task EncryptedRequest(HttpContext context, Func<Task> next, IQueryCollection query, StreamReader bodyReader)
|
||||
{
|
||||
if (!query.TryGetValue("u", out var username))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.Request.Headers.TryGetValue("secret", out var secret))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string id = await Utils.IdFromName(username);
|
||||
string body = await LoadBody(bodyReader);
|
||||
string password = await Utils.GetPassword(id);
|
||||
body = await Utils.NonceDecryptBody(username, password, body);
|
||||
secret = await Utils.NonceDecryptBody(username, password, secret);
|
||||
|
||||
Universal2String serializedBody = JsonSerializer.Deserialize(
|
||||
body,
|
||||
AppJsonSerializerContext.Default.Universal2String
|
||||
);
|
||||
|
||||
string auth = await Utils.Auth(id, secret, password);
|
||||
|
||||
if (auth != Utils.LOGIN_SUCCESS)
|
||||
{
|
||||
await context.Response.WriteAsync(Encryption.Encryption.EncryptString(auth, secret));
|
||||
return;
|
||||
}
|
||||
|
||||
SemaphoreSlim userLock = Utils.GetUserLock(id);
|
||||
await userLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
switch (serializedBody.string1)
|
||||
{
|
||||
case "user/key/update":
|
||||
await context.Response.WriteAsync(
|
||||
Encryption.Encryption.EncryptString(
|
||||
await UpdateUserKeys(id, serializedBody.string2)
|
||||
, password));
|
||||
break;
|
||||
case "user/key/get":
|
||||
await context.Response.WriteAsync(
|
||||
Encryption.Encryption.EncryptString(
|
||||
await Utils.GetUserKeys(id)
|
||||
, password));
|
||||
break;
|
||||
case "user/dm/list":
|
||||
await context.Response.WriteAsync(
|
||||
Encryption.Encryption.EncryptString(
|
||||
await Utils.GetUserDms(id)
|
||||
, password));
|
||||
break;
|
||||
case "user/dm/invite":
|
||||
await context.Response.WriteAsync(
|
||||
Encryption.Encryption.EncryptString(
|
||||
await Room.Requests.DmInvite(id, serializedBody.string2)
|
||||
, password));
|
||||
break;
|
||||
case "user/dm/create":
|
||||
await context.Response.WriteAsync(
|
||||
Encryption.Encryption.EncryptString(
|
||||
await Room.Requests.DmCreate(id, serializedBody.string2)
|
||||
, password));
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
userLock.Release();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateHolder
|
||||
{
|
||||
public DateTimeOffset date;
|
||||
public string name;
|
||||
public string pass;
|
||||
public string captcha;
|
||||
public (BigInteger p, BigInteger g, BigInteger pubServer, BigInteger secretServer) serverInfo;
|
||||
public BigInteger pubClient;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue