mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
using RadicadeServer.RecNet.Enums.Players;
|
||||
|
||||
namespace RadicadeServer.RecNet.Accounts
|
||||
{
|
||||
public class AccountCreationData
|
||||
{
|
||||
public Platforms platform { get; set; }
|
||||
public string platformId { get; set; } = null!;
|
||||
public string deviceId { get; set; } = null!;
|
||||
public string? password { get; set; }
|
||||
|
||||
|
||||
public DeviceClass deviceClass { get; set; }
|
||||
|
||||
|
||||
|
||||
public string? username { get; set; }
|
||||
|
||||
public DateTime? birthday { get; set; }
|
||||
|
||||
public DateTime? birthdayUTC { get; set; }
|
||||
|
||||
public string? email { get; set; }
|
||||
|
||||
public string? avatar { get; set; }
|
||||
|
||||
public string? avatarJson { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace RadicadeServer.Utils
|
||||
{
|
||||
public static class RegistrationGuard
|
||||
{
|
||||
public static readonly TimeSpan Window = TimeSpan.FromMinutes(10);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static int maxCreationsPerIp = 20;
|
||||
private static int maxCreationsPerDevice = 10;
|
||||
private static TimeSpan minimumDeviceInterval = TimeSpan.FromSeconds(10);
|
||||
|
||||
public static int MaxCreationsPerIp => maxCreationsPerIp;
|
||||
|
||||
public static int MaxCreationsPerDevice => maxCreationsPerDevice;
|
||||
|
||||
public static TimeSpan MinimumDeviceInterval => minimumDeviceInterval;
|
||||
|
||||
public static void Configure(int maxCreationsPerIp, int maxCreationsPerDevice, TimeSpan minimumDeviceInterval)
|
||||
{
|
||||
RegistrationGuard.maxCreationsPerIp = maxCreationsPerIp;
|
||||
RegistrationGuard.maxCreationsPerDevice = maxCreationsPerDevice;
|
||||
RegistrationGuard.minimumDeviceInterval = minimumDeviceInterval;
|
||||
Reset();
|
||||
}
|
||||
|
||||
private sealed class Counter
|
||||
{
|
||||
public readonly List<DateTime> Events = [];
|
||||
public DateTime LastCreation = DateTime.MinValue;
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<string, Counter> ByIp = new(StringComparer.Ordinal);
|
||||
private static readonly ConcurrentDictionary<string, Counter> ByDevice = new(StringComparer.Ordinal);
|
||||
private static readonly ConcurrentDictionary<string, SemaphoreSlim> IdentityGates = new(StringComparer.Ordinal);
|
||||
|
||||
public static string? Check(string? ip, string? deviceId)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(deviceId))
|
||||
{
|
||||
var device = ByDevice.GetOrAdd(deviceId, _ => new Counter());
|
||||
lock (device)
|
||||
{
|
||||
device.Events.RemoveAll(evt => now - evt > Window);
|
||||
if (now - device.LastCreation < MinimumDeviceInterval)
|
||||
return "An account was just created on this device. Please wait a moment and try again.";
|
||||
if (device.Events.Count >= MaxCreationsPerDevice)
|
||||
return "Too many accounts have been created on this device. Please try again later.";
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ip))
|
||||
{
|
||||
var source = ByIp.GetOrAdd(ip, _ => new Counter());
|
||||
lock (source)
|
||||
{
|
||||
source.Events.RemoveAll(evt => now - evt > Window);
|
||||
if (source.Events.Count >= MaxCreationsPerIp)
|
||||
return "Too many accounts have been created from this connection. Please try again later.";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Record(string? ip, string? deviceId)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(deviceId))
|
||||
{
|
||||
var device = ByDevice.GetOrAdd(deviceId, _ => new Counter());
|
||||
lock (device)
|
||||
{
|
||||
device.Events.RemoveAll(evt => now - evt > Window);
|
||||
device.Events.Add(now);
|
||||
device.LastCreation = now;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ip))
|
||||
{
|
||||
var source = ByIp.GetOrAdd(ip, _ => new Counter());
|
||||
lock (source)
|
||||
{
|
||||
source.Events.RemoveAll(evt => now - evt > Window);
|
||||
source.Events.Add(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string IdentityKey(string platform, string? platformId, string? deviceId) =>
|
||||
platform + "|" + (platformId?.Trim() ?? string.Empty) + "|" + (deviceId?.Trim() ?? string.Empty);
|
||||
|
||||
public static Task<IDisposable> LockNameAsync(string name) =>
|
||||
LockIdentityAsync("name|" + UsernameRules.ComparisonKey(name));
|
||||
|
||||
public static async Task<IDisposable> LockIdentityAsync(string key)
|
||||
{
|
||||
var gate = IdentityGates.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
|
||||
await gate.WaitAsync();
|
||||
return new Releaser(gate);
|
||||
}
|
||||
|
||||
private sealed class Releaser(SemaphoreSlim gate) : IDisposable
|
||||
{
|
||||
private int released;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref released, 1) == 0) gate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
ByIp.Clear();
|
||||
ByDevice.Clear();
|
||||
IdentityGates.Clear();
|
||||
}
|
||||
|
||||
internal static string Describe(string? ip, string? deviceId) =>
|
||||
"ip=" + (ip ?? "<none>") + " device=" + (deviceId ?? "<none>");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user