115 lines
No EOL
4.5 KiB
C#
115 lines
No EOL
4.5 KiB
C#
namespace LarpixVoice;
|
|
|
|
public class Utils
|
|
{
|
|
public static int PORT = 8091;
|
|
public static int MAIN_PORT = 8090;
|
|
public static string SERVER_KEY = "SecretThingOMGSoHardToGuess";
|
|
public static string MAIN_URL = "";
|
|
|
|
// Rate limits, requests per minute (fixed 60s window).
|
|
public static int ROOM_USERS_RATE_LIMIT = 120;
|
|
|
|
// Shared with the main server: authenticates internal validation requests.
|
|
public static string VOICE_KEY = "";
|
|
|
|
private static readonly System.Net.Http.HttpClient AuthHttp = new()
|
|
{
|
|
Timeout = TimeSpan.FromSeconds(3)
|
|
};
|
|
|
|
public static void LoadEnv()
|
|
{
|
|
if (File.Exists(".env"))
|
|
{
|
|
|
|
foreach (var line in File.ReadAllLines(".env"))
|
|
{
|
|
var trimmed = line.Trim();
|
|
if (string.IsNullOrWhiteSpace(trimmed) || trimmed.StartsWith("#")) continue;
|
|
|
|
var parts = trimmed.Split('=', 2, StringSplitOptions.None);
|
|
if (parts.Length == 2)
|
|
{
|
|
var key = parts[0].Trim();
|
|
var val = parts[1];
|
|
int commentIndex = val.IndexOf('#');
|
|
if (commentIndex >= 0)
|
|
{
|
|
val = val.Substring(0, commentIndex);
|
|
}
|
|
val = val.Trim();
|
|
|
|
Environment.SetEnvironmentVariable(key, val);
|
|
}
|
|
}
|
|
}
|
|
|
|
string? port = Environment.GetEnvironmentVariable("PORT_VOICE");
|
|
if (port != null)
|
|
{
|
|
PORT = int.Parse(port);
|
|
}
|
|
port = Environment.GetEnvironmentVariable("PORT");
|
|
if (port != null)
|
|
{
|
|
MAIN_PORT = int.Parse(port);
|
|
}
|
|
SERVER_KEY = Environment.GetEnvironmentVariable("SERVER_KEY") ?? SERVER_KEY;
|
|
VOICE_KEY = Environment.GetEnvironmentVariable("VOICE_KEY") ?? VOICE_KEY;
|
|
MAIN_URL = Environment.GetEnvironmentVariable("MAIN_URL") ?? $"http://127.0.0.1:{MAIN_PORT}";
|
|
|
|
string? roomUsersRateLimitStr = Environment.GetEnvironmentVariable("ROOM_USERS_RATE_LIMIT");
|
|
if (roomUsersRateLimitStr != null && int.TryParse(roomUsersRateLimitStr, out int parsedRoomUsersLimit))
|
|
{
|
|
ROOM_USERS_RATE_LIMIT = parsedRoomUsersLimit;
|
|
}
|
|
}
|
|
|
|
// Asks the main server to validate (and atomically consume) the ticket
|
|
// blob presented on a connection's first frame. Fail-closed: any network
|
|
// problem, bad HMAC, timeout or "deny" response rejects the connection.
|
|
public static async Task<bool> AuthorizeConnection(string publicId, string iv, string ct, string roomId, ulong userId)
|
|
{
|
|
if (string.IsNullOrEmpty(VOICE_KEY) || string.IsNullOrEmpty(MAIN_URL)) return false;
|
|
|
|
try
|
|
{
|
|
long ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
|
string body = System.Text.Json.JsonSerializer.Serialize(
|
|
new VoiceAuthBody(roomId, userId.ToString(), publicId, iv, ct),
|
|
JsonAppSerializerContext.Default.VoiceAuthBody
|
|
);
|
|
|
|
byte[] keyBytes = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(VOICE_KEY));
|
|
using var hmac = new System.Security.Cryptography.HMACSHA256(keyBytes);
|
|
string mac = System.Buffers.Text.Base64Url.EncodeToString(
|
|
hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"{ts}.{body}"))
|
|
);
|
|
|
|
using var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, $"{MAIN_URL}/_larpix/voice/auth");
|
|
request.Headers.Add("X-Voice-Ts", ts.ToString());
|
|
request.Headers.Add("X-Voice-Mac", mac);
|
|
request.Content = new System.Net.Http.StringContent(body, System.Text.Encoding.UTF8, "application/json");
|
|
|
|
using System.Net.Http.HttpResponseMessage response = await AuthHttp.SendAsync(request);
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
Console.WriteLine($"Voice auth: main HTTP {(int)response.StatusCode}");
|
|
return false;
|
|
}
|
|
string content = await response.Content.ReadAsStringAsync();
|
|
if (content.Trim() != "ok")
|
|
{
|
|
Console.WriteLine($"Voice auth: main denied ({content.Trim()})");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Voice auth: validation request failed ({ex.Message})");
|
|
return false;
|
|
}
|
|
}
|
|
} |