Initial
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace LightlessSyncShared.Utils;
|
||||
|
||||
public class AllowedControllersFeatureProvider : ControllerFeatureProvider
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly Type[] _allowedTypes;
|
||||
|
||||
public AllowedControllersFeatureProvider(params Type[] allowedTypes)
|
||||
{
|
||||
_allowedTypes = allowedTypes;
|
||||
}
|
||||
|
||||
protected override bool IsController(TypeInfo typeInfo)
|
||||
{
|
||||
return base.IsController(typeInfo) && _allowedTypes.Contains(typeInfo.AsType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
using LightlessSync.API.Data.Enum;
|
||||
|
||||
namespace LightlessSyncShared.Utils;
|
||||
public record ClientMessage(MessageSeverity Severity, string Message, string UID);
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Text;
|
||||
|
||||
namespace LightlessSyncShared.Utils.Configuration;
|
||||
|
||||
public class AuthServiceConfiguration : LightlessConfigurationBase
|
||||
{
|
||||
public string GeoIPDbCityFile { get; set; } = string.Empty;
|
||||
public bool UseGeoIP { get; set; } = false;
|
||||
public int FailedAuthForTempBan { get; set; } = 5;
|
||||
public int TempBanDurationInMinutes { get; set; } = 5;
|
||||
public List<string> WhitelistedIps { get; set; } = new();
|
||||
public Uri PublicOAuthBaseUri { get; set; } = null;
|
||||
public string? DiscordOAuthClientSecret { get; set; } = null;
|
||||
public string? DiscordOAuthClientId { get; set; } = null;
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine(base.ToString());
|
||||
sb.AppendLine($"{nameof(RedisPool)} => {RedisPool}");
|
||||
sb.AppendLine($"{nameof(GeoIPDbCityFile)} => {GeoIPDbCityFile}");
|
||||
sb.AppendLine($"{nameof(UseGeoIP)} => {UseGeoIP}");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace LightlessSyncShared.Utils.Configuration;
|
||||
|
||||
public interface ILightlessConfiguration
|
||||
{
|
||||
T GetValueOrDefault<T>(string key, T defaultValue);
|
||||
T GetValue<T>(string key);
|
||||
string SerializeValue(string key, string defaultValue);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LightlessSyncShared.Utils.Configuration;
|
||||
|
||||
public class LightlessConfigurationBase : ILightlessConfiguration
|
||||
{
|
||||
public int DbContextPoolSize { get; set; } = 100;
|
||||
public string Jwt { get; set; } = string.Empty;
|
||||
public Uri MainServerAddress { get; set; }
|
||||
public int RedisPool { get; set; } = 50;
|
||||
public int MetricsPort { get; set; }
|
||||
public string RedisConnectionString { get; set; } = string.Empty;
|
||||
public string ShardName { get; set; } = string.Empty;
|
||||
|
||||
public T GetValue<T>(string key)
|
||||
{
|
||||
var prop = GetType().GetProperty(key);
|
||||
if (prop == null) throw new KeyNotFoundException(key);
|
||||
if (prop.PropertyType != typeof(T)) throw new ArgumentException($"Requested {key} with T:{typeof(T)}, where {key} is {prop.PropertyType}");
|
||||
return (T)prop.GetValue(this);
|
||||
}
|
||||
|
||||
public T GetValueOrDefault<T>(string key, T defaultValue)
|
||||
{
|
||||
var prop = GetType().GetProperty(key);
|
||||
if (prop.PropertyType != typeof(T)) throw new ArgumentException($"Requested {key} with T:{typeof(T)}, where {key} is {prop.PropertyType}");
|
||||
if (prop == null) return defaultValue;
|
||||
return (T)prop.GetValue(this);
|
||||
}
|
||||
|
||||
public string SerializeValue(string key, string defaultValue)
|
||||
{
|
||||
var prop = GetType().GetProperty(key);
|
||||
if (prop == null) return defaultValue;
|
||||
if (prop.GetCustomAttribute<RemoteConfigurationAttribute>() == null) return defaultValue;
|
||||
return JsonSerializer.Serialize(prop.GetValue(this), prop.PropertyType);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine(base.ToString());
|
||||
sb.AppendLine($"{nameof(MainServerAddress)} => {MainServerAddress}");
|
||||
sb.AppendLine($"{nameof(RedisConnectionString)} => {RedisConnectionString}");
|
||||
sb.AppendLine($"{nameof(ShardName)} => {ShardName}");
|
||||
sb.AppendLine($"{nameof(DbContextPoolSize)} => {DbContextPoolSize}");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Text;
|
||||
|
||||
namespace LightlessSyncShared.Utils.Configuration;
|
||||
|
||||
public class ServerConfiguration : LightlessConfigurationBase
|
||||
{
|
||||
[RemoteConfiguration]
|
||||
public Uri CdnFullUrl { get; set; } = null;
|
||||
|
||||
[RemoteConfiguration]
|
||||
public Version ExpectedClientVersion { get; set; } = new Version(0, 0, 0);
|
||||
|
||||
[RemoteConfiguration]
|
||||
public int MaxExistingGroupsByUser { get; set; } = 3;
|
||||
|
||||
[RemoteConfiguration]
|
||||
public int MaxGroupUserCount { get; set; } = 100;
|
||||
|
||||
[RemoteConfiguration]
|
||||
public int MaxJoinedGroupsByUser { get; set; } = 6;
|
||||
|
||||
[RemoteConfiguration]
|
||||
public bool PurgeUnusedAccounts { get; set; } = false;
|
||||
|
||||
[RemoteConfiguration]
|
||||
public int PurgeUnusedAccountsPeriodInDays { get; set; } = 14;
|
||||
|
||||
[RemoteConfiguration]
|
||||
public int MaxCharaDataByUser { get; set; } = 10;
|
||||
|
||||
[RemoteConfiguration]
|
||||
public int MaxCharaDataByUserVanity { get; set; } = 50;
|
||||
public bool RunPermissionCleanupOnStartup { get; set; } = true;
|
||||
public int HubExecutionConcurrencyFilter { get; set; } = 50;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine(base.ToString());
|
||||
sb.AppendLine($"{nameof(CdnFullUrl)} => {CdnFullUrl}");
|
||||
sb.AppendLine($"{nameof(RedisConnectionString)} => {RedisConnectionString}");
|
||||
sb.AppendLine($"{nameof(ExpectedClientVersion)} => {ExpectedClientVersion}");
|
||||
sb.AppendLine($"{nameof(MaxExistingGroupsByUser)} => {MaxExistingGroupsByUser}");
|
||||
sb.AppendLine($"{nameof(MaxJoinedGroupsByUser)} => {MaxJoinedGroupsByUser}");
|
||||
sb.AppendLine($"{nameof(MaxGroupUserCount)} => {MaxGroupUserCount}");
|
||||
sb.AppendLine($"{nameof(PurgeUnusedAccounts)} => {PurgeUnusedAccounts}");
|
||||
sb.AppendLine($"{nameof(PurgeUnusedAccountsPeriodInDays)} => {PurgeUnusedAccountsPeriodInDays}");
|
||||
sb.AppendLine($"{nameof(RunPermissionCleanupOnStartup)} => {RunPermissionCleanupOnStartup}");
|
||||
sb.AppendLine($"{nameof(HubExecutionConcurrencyFilter)} => {HubExecutionConcurrencyFilter}");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Text;
|
||||
|
||||
namespace LightlessSyncShared.Utils.Configuration;
|
||||
|
||||
public class ServicesConfiguration : LightlessConfigurationBase
|
||||
{
|
||||
public string DiscordBotToken { get; set; } = string.Empty;
|
||||
public ulong? DiscordChannelForMessages { get; set; } = null;
|
||||
public ulong? DiscordChannelForCommands { get; set; } = null;
|
||||
public ulong? DiscordRoleAprilFools2024 { get; set; } = null;
|
||||
public ulong? DiscordChannelForBotLog { get; set; } = null!;
|
||||
public ulong? DiscordRoleRegistered { get; set; } = null!;
|
||||
public bool KickNonRegisteredUsers { get; set; } = false;
|
||||
public Uri MainServerAddress { get; set; } = null;
|
||||
public Dictionary<ulong, string> VanityRoles { get; set; } = new Dictionary<ulong, string>();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine(base.ToString());
|
||||
sb.AppendLine($"{nameof(DiscordBotToken)} => {DiscordBotToken}");
|
||||
sb.AppendLine($"{nameof(MainServerAddress)} => {MainServerAddress}");
|
||||
sb.AppendLine($"{nameof(DiscordChannelForMessages)} => {DiscordChannelForMessages}");
|
||||
sb.AppendLine($"{nameof(DiscordChannelForCommands)} => {DiscordChannelForCommands}");
|
||||
sb.AppendLine($"{nameof(DiscordRoleAprilFools2024)} => {DiscordRoleAprilFools2024}");
|
||||
sb.AppendLine($"{nameof(DiscordRoleRegistered)} => {DiscordRoleRegistered}");
|
||||
sb.AppendLine($"{nameof(KickNonRegisteredUsers)} => {KickNonRegisteredUsers}");
|
||||
foreach (var role in VanityRoles)
|
||||
{
|
||||
sb.AppendLine($"{nameof(VanityRoles)} => {role.Key} = {role.Value}");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace LightlessSyncShared.Utils.Configuration;
|
||||
|
||||
public class ShardConfiguration
|
||||
{
|
||||
public List<string> Continents { get; set; }
|
||||
public string FileMatch { get; set; }
|
||||
public Dictionary<string, Uri> RegionUris { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Text;
|
||||
|
||||
namespace LightlessSyncShared.Utils.Configuration;
|
||||
|
||||
public class StaticFilesServerConfiguration : LightlessConfigurationBase
|
||||
{
|
||||
public bool IsDistributionNode { get; set; } = false;
|
||||
public Uri MainFileServerAddress { get; set; } = null;
|
||||
public Uri DistributionFileServerAddress { get; set; } = null;
|
||||
public int ForcedDeletionOfFilesAfterHours { get; set; } = -1;
|
||||
public double CacheSizeHardLimitInGiB { get; set; } = -1;
|
||||
public int UnusedFileRetentionPeriodInDays { get; set; } = 14;
|
||||
public string CacheDirectory { get; set; }
|
||||
public int DownloadQueueSize { get; set; } = 50;
|
||||
public int DownloadTimeoutSeconds { get; set; } = 5;
|
||||
public int DownloadQueueReleaseSeconds { get; set; } = 15;
|
||||
public int DownloadQueueClearLimit { get; set; } = 15000;
|
||||
public int CleanupCheckInMinutes { get; set; } = 15;
|
||||
public bool UseColdStorage { get; set; } = false;
|
||||
public string ColdStorageDirectory { get; set; } = null;
|
||||
public double ColdStorageSizeHardLimitInGiB { get; set; } = -1;
|
||||
public int ColdStorageUnusedFileRetentionPeriodInDays { get; set; } = 30;
|
||||
[RemoteConfiguration]
|
||||
public double SpeedTestHoursRateLimit { get; set; } = 0.5;
|
||||
[RemoteConfiguration]
|
||||
public Uri CdnFullUrl { get; set; } = null;
|
||||
public ShardConfiguration? ShardConfiguration { get; set; } = null;
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine(base.ToString());
|
||||
sb.AppendLine($"{nameof(MainFileServerAddress)} => {MainFileServerAddress}");
|
||||
sb.AppendLine($"{nameof(ForcedDeletionOfFilesAfterHours)} => {ForcedDeletionOfFilesAfterHours}");
|
||||
sb.AppendLine($"{nameof(CacheSizeHardLimitInGiB)} => {CacheSizeHardLimitInGiB}");
|
||||
sb.AppendLine($"{nameof(UseColdStorage)} => {UseColdStorage}");
|
||||
sb.AppendLine($"{nameof(ColdStorageDirectory)} => {ColdStorageDirectory}");
|
||||
sb.AppendLine($"{nameof(ColdStorageSizeHardLimitInGiB)} => {ColdStorageSizeHardLimitInGiB}");
|
||||
sb.AppendLine($"{nameof(ColdStorageUnusedFileRetentionPeriodInDays)} => {ColdStorageUnusedFileRetentionPeriodInDays}");
|
||||
sb.AppendLine($"{nameof(UnusedFileRetentionPeriodInDays)} => {UnusedFileRetentionPeriodInDays}");
|
||||
sb.AppendLine($"{nameof(CacheDirectory)} => {CacheDirectory}");
|
||||
sb.AppendLine($"{nameof(DownloadQueueSize)} => {DownloadQueueSize}");
|
||||
sb.AppendLine($"{nameof(DownloadQueueReleaseSeconds)} => {DownloadQueueReleaseSeconds}");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace LightlessSyncShared.Utils;
|
||||
|
||||
public class IdBasedUserIdProvider : IUserIdProvider
|
||||
{
|
||||
public string GetUserId(HubConnectionContext context)
|
||||
{
|
||||
return context.User!.Claims.SingleOrDefault(c => string.Equals(c.Type, LightlessClaimTypes.Uid, StringComparison.Ordinal))?.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace LightlessSyncShared.Utils;
|
||||
|
||||
public static class LightlessClaimTypes
|
||||
{
|
||||
public const string Uid = "uid";
|
||||
public const string Alias = "alias";
|
||||
public const string CharaIdent = "character_identification";
|
||||
public const string Internal = "internal";
|
||||
public const string Expires = "expiration_date";
|
||||
public const string Continent = "continent";
|
||||
public const string DiscordUser = "discord_user";
|
||||
public const string DiscordId = "discord_user_id";
|
||||
public const string OAuthLoginToken = "oauth_login_token";
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace LightlessSyncShared.Utils;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class RemoteConfigurationAttribute : Attribute { }
|
||||
@@ -0,0 +1,65 @@
|
||||
using LightlessSyncShared.Utils.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace LightlessSyncShared.Utils;
|
||||
|
||||
public class ServerTokenGenerator
|
||||
{
|
||||
private readonly IOptionsMonitor<LightlessConfigurationBase> _configuration;
|
||||
private readonly ILogger<ServerTokenGenerator> _logger;
|
||||
|
||||
private Dictionary<string, string> _tokenDictionary { get; set; } = new(StringComparer.Ordinal);
|
||||
public string Token
|
||||
{
|
||||
get
|
||||
{
|
||||
var currentJwt = _configuration.CurrentValue.Jwt;
|
||||
if (_tokenDictionary.TryGetValue(currentJwt, out var token))
|
||||
{
|
||||
return token;
|
||||
}
|
||||
|
||||
return GenerateToken();
|
||||
}
|
||||
}
|
||||
|
||||
public ServerTokenGenerator(IOptionsMonitor<LightlessConfigurationBase> configuration, ILogger<ServerTokenGenerator> logger)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private string GenerateToken()
|
||||
{
|
||||
var signingKey = _configuration.CurrentValue.Jwt;
|
||||
var authSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(signingKey));
|
||||
|
||||
var token = new SecurityTokenDescriptor()
|
||||
{
|
||||
Subject = new ClaimsIdentity(new List<Claim>()
|
||||
{
|
||||
new Claim(LightlessClaimTypes.Uid, _configuration.CurrentValue.ShardName),
|
||||
new Claim(LightlessClaimTypes.Internal, "true"),
|
||||
new Claim(LightlessClaimTypes.Expires, DateTime.Now.AddYears(1).Ticks.ToString(CultureInfo.InvariantCulture))
|
||||
}),
|
||||
SigningCredentials = new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256Signature),
|
||||
Expires = DateTime.Now.AddYears(1)
|
||||
};
|
||||
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var jwt = handler.CreateJwtSecurityToken(token);
|
||||
var rawData = jwt.RawData;
|
||||
|
||||
_tokenDictionary[signingKey] = rawData;
|
||||
|
||||
_logger.LogInformation("Generated Token: {data}", rawData);
|
||||
|
||||
return rawData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using LightlessSyncShared.Data;
|
||||
using LightlessSyncShared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace LightlessSyncShared.Utils;
|
||||
|
||||
public static class SharedDbFunctions
|
||||
{
|
||||
public static async Task<(bool, string)> MigrateOrDeleteGroup(LightlessDbContext context, Group group, List<GroupPair> groupPairs, int maxGroupsByUser)
|
||||
{
|
||||
bool groupHasMigrated = false;
|
||||
string newOwner = string.Empty;
|
||||
foreach (var potentialNewOwner in groupPairs.OrderByDescending(p => p.IsModerator).ThenByDescending(p => p.IsPinned).ToList())
|
||||
{
|
||||
groupHasMigrated = await TryMigrateGroup(context, group, potentialNewOwner.GroupUserUID, maxGroupsByUser).ConfigureAwait(false);
|
||||
|
||||
if (groupHasMigrated)
|
||||
{
|
||||
newOwner = potentialNewOwner.GroupUserUID;
|
||||
potentialNewOwner.IsPinned = true;
|
||||
potentialNewOwner.IsModerator = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!groupHasMigrated)
|
||||
{
|
||||
context.GroupPairs.RemoveRange(groupPairs);
|
||||
context.Groups.Remove(group);
|
||||
}
|
||||
|
||||
return (groupHasMigrated, newOwner);
|
||||
}
|
||||
|
||||
public static async Task PurgeUser(ILogger _logger, User user, LightlessDbContext dbContext, int maxGroupsByUser)
|
||||
{
|
||||
_logger.LogInformation("Purging user: {uid}", user.UID);
|
||||
|
||||
var secondaryUsers = await dbContext.Auth.Include(u => u.User)
|
||||
.Where(u => u.PrimaryUserUID == user.UID).Select(c => c.User).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
foreach (var secondaryUser in secondaryUsers)
|
||||
{
|
||||
await PurgeUser(_logger, secondaryUser, dbContext, maxGroupsByUser).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var lodestone = dbContext.LodeStoneAuth.SingleOrDefault(a => a.User.UID == user.UID);
|
||||
|
||||
var userProfileData = await dbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == user.UID).ConfigureAwait(false);
|
||||
|
||||
if (lodestone != null)
|
||||
{
|
||||
dbContext.Remove(lodestone);
|
||||
}
|
||||
|
||||
if (userProfileData != null)
|
||||
{
|
||||
dbContext.Remove(userProfileData);
|
||||
}
|
||||
|
||||
var auth = dbContext.Auth.Single(a => a.UserUID == user.UID);
|
||||
|
||||
var userFiles = dbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == user.UID).ToList();
|
||||
dbContext.Files.RemoveRange(userFiles);
|
||||
|
||||
var ownPairData = dbContext.ClientPairs.Where(u => u.User.UID == user.UID).ToList();
|
||||
dbContext.ClientPairs.RemoveRange(ownPairData);
|
||||
var otherPairData = dbContext.ClientPairs.Include(u => u.User)
|
||||
.Where(u => u.OtherUser.UID == user.UID).ToList();
|
||||
dbContext.ClientPairs.RemoveRange(otherPairData);
|
||||
|
||||
var userJoinedGroups = await dbContext.GroupPairs.Include(g => g.Group).Where(u => u.GroupUserUID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
foreach (var userGroupPair in userJoinedGroups)
|
||||
{
|
||||
bool ownerHasLeft = string.Equals(userGroupPair.Group.OwnerUID, user.UID, StringComparison.Ordinal);
|
||||
|
||||
if (ownerHasLeft)
|
||||
{
|
||||
var groupPairs = await dbContext.GroupPairs.Where(g => g.GroupGID == userGroupPair.GroupGID && g.GroupUserUID != user.UID).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
if (!groupPairs.Any())
|
||||
{
|
||||
_logger.LogInformation("Group {gid} has no new owner, deleting", userGroupPair.GroupGID);
|
||||
dbContext.Groups.Remove(userGroupPair.Group);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = await MigrateOrDeleteGroup(dbContext, userGroupPair.Group, groupPairs, maxGroupsByUser).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
dbContext.GroupPairs.Remove(userGroupPair);
|
||||
}
|
||||
|
||||
var defaultPermissions = await dbContext.UserDefaultPreferredPermissions.Where(u => u.UserUID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||
var groupPermissions = await dbContext.GroupPairPreferredPermissions.Where(u => u.UserUID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||
var individualPermissions = await dbContext.Permissions.Where(u => u.UserUID == user.UID || u.OtherUserUID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||
var bannedinGroups = await dbContext.GroupBans.Where(u => u.BannedUserUID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||
var hasBannedInGroups = await dbContext.GroupBans.Where(u => u.BannedByUID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
dbContext.GroupPairPreferredPermissions.RemoveRange(groupPermissions);
|
||||
dbContext.UserDefaultPreferredPermissions.RemoveRange(defaultPermissions);
|
||||
dbContext.Permissions.RemoveRange(individualPermissions);
|
||||
dbContext.GroupBans.RemoveRange(bannedinGroups);
|
||||
dbContext.GroupBans.RemoveRange(hasBannedInGroups);
|
||||
|
||||
_logger.LogInformation("User purged: {uid}", user.UID);
|
||||
|
||||
dbContext.Auth.Remove(auth);
|
||||
dbContext.Users.Remove(user);
|
||||
|
||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task<bool> TryMigrateGroup(LightlessDbContext context, Group group, string potentialNewOwnerUid, int maxGroupsByUser)
|
||||
{
|
||||
var newOwnerOwnedGroups = await context.Groups.CountAsync(g => g.OwnerUID == potentialNewOwnerUid).ConfigureAwait(false);
|
||||
if (newOwnerOwnedGroups >= maxGroupsByUser)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
group.OwnerUID = potentialNewOwnerUid;
|
||||
group.Alias = null;
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
49
LightlessSyncServer/LightlessSyncShared/Utils/StringUtils.cs
Normal file
49
LightlessSyncServer/LightlessSyncShared/Utils/StringUtils.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace LightlessSyncShared.Utils;
|
||||
|
||||
public static class StringUtils
|
||||
{
|
||||
public static string GenerateRandomString(int length, string? allowableChars = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(allowableChars))
|
||||
allowableChars = @"ABCDEFGHJKLMNPQRSTUVWXYZ0123456789";
|
||||
|
||||
// Generate random data
|
||||
var rnd = RandomNumberGenerator.GetBytes(length);
|
||||
|
||||
// Generate the output string
|
||||
var allowable = allowableChars.ToCharArray();
|
||||
var l = allowable.Length;
|
||||
var chars = new char[length];
|
||||
for (var i = 0; i < length; i++)
|
||||
chars[i] = allowable[rnd[i] % l];
|
||||
|
||||
return new string(chars);
|
||||
}
|
||||
|
||||
public static string Sha256String(string input)
|
||||
{
|
||||
using var sha256 = SHA256.Create();
|
||||
return BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(input))).Replace("-", "", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ListUtils
|
||||
{
|
||||
private static Random rng = new();
|
||||
|
||||
public static void Shuffle<T>(this IList<T> list)
|
||||
{
|
||||
int n = list.Count;
|
||||
while (n > 1)
|
||||
{
|
||||
n--;
|
||||
int k = rng.Next(n + 1);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user