add mare profiles
This commit is contained in:
@@ -43,6 +43,8 @@ namespace MareSynchronosServer.Hubs
|
||||
|
||||
public Task Client_UserUpdateOtherPairPermissions(UserPermissionsDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||
|
||||
public Task Client_UserUpdateProfile(UserDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||
|
||||
public Task Client_UserUpdateSelfPairPermissions(UserPermissionsDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||
}
|
||||
}
|
||||
@@ -12,26 +12,53 @@ namespace MareSynchronosServer.Hubs;
|
||||
|
||||
public partial class MareHub
|
||||
{
|
||||
private async Task UpdateUserOnRedis()
|
||||
{
|
||||
await _redis.AddAsync("UID:" + UserUID, UserCharaIdent, TimeSpan.FromSeconds(60), StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags.FireAndForget).ConfigureAwait(false);
|
||||
}
|
||||
public string UserCharaIdent => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.CharaIdent, StringComparison.Ordinal))?.Value ?? throw new Exception("No Chara Ident in Claims");
|
||||
|
||||
private async Task RemoveUserFromRedis()
|
||||
{
|
||||
await _redis.RemoveAsync("UID:" + UserUID, StackExchange.Redis.CommandFlags.FireAndForget).ConfigureAwait(false);
|
||||
}
|
||||
public string UserUID => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.Uid, StringComparison.Ordinal))?.Value ?? throw new Exception("No UID in Claims");
|
||||
|
||||
private async Task<string> GetUserIdent(string uid)
|
||||
private async Task DeleteUser(User user)
|
||||
{
|
||||
if (uid.IsNullOrEmpty()) return string.Empty;
|
||||
return await _redis.GetAsync<string>("UID:" + uid).ConfigureAwait(false);
|
||||
}
|
||||
var ownPairData = await _dbContext.ClientPairs.Where(u => u.User.UID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||
var auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == user.UID).ConfigureAwait(false);
|
||||
var lodestone = await _dbContext.LodeStoneAuth.SingleOrDefaultAsync(a => a.User.UID == user.UID).ConfigureAwait(false);
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(g => g.GroupUserUID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||
var userProfileData = await _dbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == user.UID).ConfigureAwait(false);
|
||||
|
||||
private async Task<Dictionary<string, string>> GetOnlineUsers(List<string> uids)
|
||||
{
|
||||
var result = await _redis.GetAllAsync<string>(uids.Select(u => "UID:" + u).ToHashSet(StringComparer.Ordinal)).ConfigureAwait(false);
|
||||
return uids.Where(u => result.TryGetValue("UID:" + u, out var ident) && !string.IsNullOrEmpty(ident)).ToDictionary(u => u, u => result["UID:" + u], StringComparer.Ordinal);
|
||||
if (lodestone != null)
|
||||
{
|
||||
_dbContext.Remove(lodestone);
|
||||
}
|
||||
|
||||
if (userProfileData != null)
|
||||
{
|
||||
_dbContext.Remove(userProfileData);
|
||||
}
|
||||
|
||||
while (_dbContext.Files.Any(f => f.Uploader == user))
|
||||
{
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_dbContext.ClientPairs.RemoveRange(ownPairData);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
var otherPairData = await _dbContext.ClientPairs.Include(u => u.User)
|
||||
.Where(u => u.OtherUser.UID == user.UID).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
foreach (var pair in otherPairData)
|
||||
{
|
||||
await Clients.User(pair.UserUID).Client_UserRemoveClientPair(new(user.ToUserData())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var pair in groupPairs)
|
||||
{
|
||||
await UserLeaveGroup(new GroupDto(new GroupData(pair.GroupGID)), user.UID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUsersRegisteredDeleted, 1);
|
||||
|
||||
_dbContext.ClientPairs.RemoveRange(otherPairData);
|
||||
_dbContext.Users.Remove(user);
|
||||
_dbContext.Auth.Remove(auth);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<List<PausedEntry>> GetAllPairedClientsWithPauseState(string? uid = null)
|
||||
@@ -79,43 +106,21 @@ public partial class MareHub
|
||||
return ret.Where(k => !k.IsPaused).Select(k => k.UID).ToList();
|
||||
}
|
||||
|
||||
private async Task<List<string>> SendOnlineToAllPairedUsers()
|
||||
private async Task<Dictionary<string, string>> GetOnlineUsers(List<string> uids)
|
||||
{
|
||||
var usersToSendDataTo = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
var self = await _dbContext.Users.AsNoTracking().SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||
await Clients.Users(usersToSendDataTo).Client_UserSendOnline(new(self.ToUserData(), UserCharaIdent)).ConfigureAwait(false);
|
||||
|
||||
return usersToSendDataTo;
|
||||
var result = await _redis.GetAllAsync<string>(uids.Select(u => "UID:" + u).ToHashSet(StringComparer.Ordinal)).ConfigureAwait(false);
|
||||
return uids.Where(u => result.TryGetValue("UID:" + u, out var ident) && !string.IsNullOrEmpty(ident)).ToDictionary(u => u, u => result["UID:" + u], StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
private async Task<List<string>> SendOfflineToAllPairedUsers()
|
||||
private async Task<string> GetUserIdent(string uid)
|
||||
{
|
||||
var usersToSendDataTo = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
var self = await _dbContext.Users.AsNoTracking().SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||
await Clients.Users(usersToSendDataTo).Client_UserSendOffline(new(self.ToUserData())).ConfigureAwait(false);
|
||||
|
||||
return usersToSendDataTo;
|
||||
if (uid.IsNullOrEmpty()) return string.Empty;
|
||||
return await _redis.GetAsync<string>("UID:" + uid).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public string UserUID => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.Uid, StringComparison.Ordinal))?.Value ?? throw new Exception("No UID in Claims");
|
||||
public string UserCharaIdent => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.CharaIdent, StringComparison.Ordinal))?.Value ?? throw new Exception("No Chara Ident in Claims");
|
||||
|
||||
private async Task UserGroupLeave(GroupPair groupUserPair, List<PausedEntry> allUserPairs, string userIdent, string? uid = null)
|
||||
private async Task RemoveUserFromRedis()
|
||||
{
|
||||
uid ??= UserUID;
|
||||
var userPair = allUserPairs.SingleOrDefault(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal));
|
||||
if (userPair != null)
|
||||
{
|
||||
if (userPair.IsDirectlyPaused != PauseInfo.NoConnection) return;
|
||||
if (userPair.IsPausedPerGroup is PauseInfo.Unpaused) return;
|
||||
}
|
||||
|
||||
var groupUserIdent = await GetUserIdent(groupUserPair.GroupUserUID).ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(groupUserIdent))
|
||||
{
|
||||
await Clients.User(uid).Client_UserSendOffline(new(new(groupUserPair.GroupUserUID))).ConfigureAwait(false);
|
||||
await Clients.User(groupUserPair.GroupUserUID).Client_UserSendOffline(new(new(uid))).ConfigureAwait(false);
|
||||
}
|
||||
await _redis.RemoveAsync("UID:" + UserUID, StackExchange.Redis.CommandFlags.FireAndForget).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task SendGroupDeletedToAll(List<GroupPair> groupUsers)
|
||||
@@ -134,15 +139,22 @@ public partial class MareHub
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(bool IsValid, GroupPair ReferredPair)> TryValidateUserInGroup(string gid, string? uid = null)
|
||||
private async Task<List<string>> SendOfflineToAllPairedUsers()
|
||||
{
|
||||
uid ??= UserUID;
|
||||
var usersToSendDataTo = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
var self = await _dbContext.Users.AsNoTracking().SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||
await Clients.Users(usersToSendDataTo).Client_UserSendOffline(new(self.ToUserData())).ConfigureAwait(false);
|
||||
|
||||
var groupPair = await _dbContext.GroupPairs.Include(c => c.GroupUser)
|
||||
.SingleOrDefaultAsync(g => g.GroupGID == gid && (g.GroupUserUID == uid || g.GroupUser.Alias == uid)).ConfigureAwait(false);
|
||||
if (groupPair == null) return (false, null);
|
||||
return usersToSendDataTo;
|
||||
}
|
||||
|
||||
return (true, groupPair);
|
||||
private async Task<List<string>> SendOnlineToAllPairedUsers()
|
||||
{
|
||||
var usersToSendDataTo = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
var self = await _dbContext.Users.AsNoTracking().SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||
await Clients.Users(usersToSendDataTo).Client_UserSendOnline(new(self.ToUserData(), UserCharaIdent)).ConfigureAwait(false);
|
||||
|
||||
return usersToSendDataTo;
|
||||
}
|
||||
|
||||
private async Task<(bool IsValid, Group ReferredGroup)> TryValidateGroupModeratorOrOwner(string gid)
|
||||
@@ -166,43 +178,38 @@ public partial class MareHub
|
||||
return (string.Equals(group.OwnerUID, UserUID, StringComparison.Ordinal), group);
|
||||
}
|
||||
|
||||
private async Task DeleteUser(User user)
|
||||
private async Task<(bool IsValid, GroupPair ReferredPair)> TryValidateUserInGroup(string gid, string? uid = null)
|
||||
{
|
||||
var ownPairData = await _dbContext.ClientPairs.Where(u => u.User.UID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||
var auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == user.UID).ConfigureAwait(false);
|
||||
var lodestone = await _dbContext.LodeStoneAuth.SingleOrDefaultAsync(a => a.User.UID == user.UID).ConfigureAwait(false);
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(g => g.GroupUserUID == user.UID).ToListAsync().ConfigureAwait(false);
|
||||
uid ??= UserUID;
|
||||
|
||||
if (lodestone != null)
|
||||
var groupPair = await _dbContext.GroupPairs.Include(c => c.GroupUser)
|
||||
.SingleOrDefaultAsync(g => g.GroupGID == gid && (g.GroupUserUID == uid || g.GroupUser.Alias == uid)).ConfigureAwait(false);
|
||||
if (groupPair == null) return (false, null);
|
||||
|
||||
return (true, groupPair);
|
||||
}
|
||||
|
||||
private async Task UpdateUserOnRedis()
|
||||
{
|
||||
await _redis.AddAsync("UID:" + UserUID, UserCharaIdent, TimeSpan.FromSeconds(60), StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags.FireAndForget).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task UserGroupLeave(GroupPair groupUserPair, List<PausedEntry> allUserPairs, string userIdent, string? uid = null)
|
||||
{
|
||||
uid ??= UserUID;
|
||||
var userPair = allUserPairs.SingleOrDefault(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal));
|
||||
if (userPair != null)
|
||||
{
|
||||
_dbContext.Remove(lodestone);
|
||||
if (userPair.IsDirectlyPaused != PauseInfo.NoConnection) return;
|
||||
if (userPair.IsPausedPerGroup is PauseInfo.Unpaused) return;
|
||||
}
|
||||
|
||||
while (_dbContext.Files.Any(f => f.Uploader == user))
|
||||
var groupUserIdent = await GetUserIdent(groupUserPair.GroupUserUID).ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(groupUserIdent))
|
||||
{
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
await Clients.User(uid).Client_UserSendOffline(new(new(groupUserPair.GroupUserUID))).ConfigureAwait(false);
|
||||
await Clients.User(groupUserPair.GroupUserUID).Client_UserSendOffline(new(new(uid))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_dbContext.ClientPairs.RemoveRange(ownPairData);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
var otherPairData = await _dbContext.ClientPairs.Include(u => u.User)
|
||||
.Where(u => u.OtherUser.UID == user.UID).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
foreach (var pair in otherPairData)
|
||||
{
|
||||
await Clients.User(pair.UserUID).Client_UserRemoveClientPair(new(user.ToUserData())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var pair in groupPairs)
|
||||
{
|
||||
await UserLeaveGroup(new GroupDto(new GroupData(pair.GroupGID)), user.UID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUsersRegisteredDeleted, 1);
|
||||
|
||||
_dbContext.ClientPairs.RemoveRange(otherPairData);
|
||||
_dbContext.Users.Remove(user);
|
||||
_dbContext.Auth.Remove(auth);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task UserLeaveGroup(GroupDto dto, string userUid)
|
||||
@@ -272,4 +279,4 @@ public partial class MareHub
|
||||
await UserGroupLeave(groupUserPair, allUserPairs, ident, userUid).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,140 +14,8 @@ namespace MareSynchronosServer.Hubs;
|
||||
|
||||
public partial class MareHub
|
||||
{
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task UserDelete()
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
var userEntry = await _dbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||
var secondaryUsers = await _dbContext.Auth.Include(u => u.User).Where(u => u.PrimaryUserUID == UserUID).Select(c => c.User).ToListAsync().ConfigureAwait(false);
|
||||
foreach (var user in secondaryUsers)
|
||||
{
|
||||
await DeleteUser(user).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await DeleteUser(userEntry).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task<List<OnlineUserIdentDto>> UserGetOnlinePairs()
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
var pairs = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false);
|
||||
|
||||
return pairs.Select(p => new OnlineUserIdentDto(new UserData(p.Key), p.Value)).ToList();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task<List<UserPairDto>> UserGetPairedClients()
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
var query =
|
||||
from userToOther in _dbContext.ClientPairs
|
||||
join otherToUser in _dbContext.ClientPairs
|
||||
on new
|
||||
{
|
||||
user = userToOther.UserUID,
|
||||
other = userToOther.OtherUserUID,
|
||||
|
||||
} equals new
|
||||
{
|
||||
user = otherToUser.OtherUserUID,
|
||||
other = otherToUser.UserUID,
|
||||
} into leftJoin
|
||||
from otherEntry in leftJoin.DefaultIfEmpty()
|
||||
where
|
||||
userToOther.UserUID == UserUID
|
||||
select new
|
||||
{
|
||||
userToOther.OtherUser.Alias,
|
||||
userToOther.IsPaused,
|
||||
OtherIsPaused = otherEntry != null && otherEntry.IsPaused,
|
||||
userToOther.OtherUserUID,
|
||||
IsSynced = otherEntry != null,
|
||||
DisableOwnAnimations = userToOther.DisableAnimations,
|
||||
DisableOwnSounds = userToOther.DisableSounds,
|
||||
DisableOtherAnimations = otherEntry == null ? false : otherEntry.DisableAnimations,
|
||||
DisableOtherSounds = otherEntry == null ? false : otherEntry.DisableSounds
|
||||
};
|
||||
|
||||
var results = await query.AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
|
||||
return results.Select(c =>
|
||||
{
|
||||
var ownPerm = UserPermissions.Paired;
|
||||
ownPerm.SetPaused(c.IsPaused);
|
||||
ownPerm.SetDisableAnimations(c.DisableOwnAnimations);
|
||||
ownPerm.SetDisableSounds(c.DisableOwnSounds);
|
||||
var otherPerm = UserPermissions.NoneSet;
|
||||
otherPerm.SetPaired(c.IsSynced);
|
||||
otherPerm.SetPaused(c.OtherIsPaused);
|
||||
otherPerm.SetDisableAnimations(c.DisableOtherAnimations);
|
||||
otherPerm.SetDisableSounds(c.DisableOtherSounds);
|
||||
return new UserPairDto(new(c.OtherUserUID, c.Alias), ownPerm, otherPerm);
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^[A-Z0-9]{40}$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ECMAScript)]
|
||||
private static partial Regex HashRegex();
|
||||
|
||||
[GeneratedRegex(@"^([a-z0-9_ '+&,\.\-\{\}]+\/)+([a-z0-9_ '+&,\.\-\{\}]+\.[a-z]{3,4})$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ECMAScript)]
|
||||
private static partial Regex GamePathRegex();
|
||||
|
||||
private static readonly string[] AllowedExtensionsForGamePaths = { ".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".scd", ".skp", ".shpk" };
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task UserPushData(UserCharaDataMessageDto dto)
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(dto.CharaData.FileReplacements.Count));
|
||||
|
||||
bool hadInvalidData = false;
|
||||
List<string> invalidGamePaths = new();
|
||||
List<string> invalidFileSwapPaths = new();
|
||||
foreach (var replacement in dto.CharaData.FileReplacements.SelectMany(p => p.Value))
|
||||
{
|
||||
var invalidPaths = replacement.GamePaths.Where(p => !GamePathRegex().IsMatch(p)).ToList();
|
||||
invalidPaths.AddRange(replacement.GamePaths.Where(p => !AllowedExtensionsForGamePaths.Any(e => p.EndsWith(e, StringComparison.OrdinalIgnoreCase))));
|
||||
replacement.GamePaths = replacement.GamePaths.Where(p => !invalidPaths.Contains(p, StringComparer.OrdinalIgnoreCase)).ToArray();
|
||||
bool validGamePaths = replacement.GamePaths.Any();
|
||||
bool validHash = string.IsNullOrEmpty(replacement.Hash) || HashRegex().IsMatch(replacement.Hash);
|
||||
bool validFileSwapPath = string.IsNullOrEmpty(replacement.FileSwapPath) || GamePathRegex().IsMatch(replacement.FileSwapPath);
|
||||
if (!validGamePaths || !validHash || !validFileSwapPath)
|
||||
{
|
||||
_logger.LogCallWarning(MareHubLogger.Args("Invalid Data", "GamePaths", validGamePaths, string.Join(",", invalidPaths), "Hash", validHash, replacement.Hash, "FileSwap", validFileSwapPath, replacement.FileSwapPath));
|
||||
hadInvalidData = true;
|
||||
if (!validFileSwapPath) invalidFileSwapPaths.Add(replacement.FileSwapPath);
|
||||
if (!validGamePaths) invalidGamePaths.AddRange(replacement.GamePaths);
|
||||
if (!validHash) invalidFileSwapPaths.Add(replacement.Hash);
|
||||
}
|
||||
}
|
||||
|
||||
if (hadInvalidData)
|
||||
{
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "One or more of your supplied mods were rejected from the server. Consult /xllog for more information.").ConfigureAwait(false);
|
||||
throw new HubException("Invalid data provided, contact the appropriate mod creator to resolve those issues"
|
||||
+ Environment.NewLine
|
||||
+ string.Join(Environment.NewLine, invalidGamePaths.Select(p => "Invalid Game Path: " + p))
|
||||
+ Environment.NewLine
|
||||
+ string.Join(Environment.NewLine, invalidFileSwapPaths.Select(p => "Invalid FileSwap Path: " + p)));
|
||||
}
|
||||
|
||||
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
var idents = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false);
|
||||
|
||||
var recipients = allPairedUsers.Where(f => dto.Recipients.Select(r => r.UID).Contains(f, StringComparer.Ordinal)).ToList();
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(idents.Count, recipients.Count()));
|
||||
|
||||
await Clients.Users(recipients).Client_UserReceiveCharacterData(new OnlineUserCharaDataDto(new UserData(UserUID), dto.CharaData)).ConfigureAwait(false);
|
||||
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushData);
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, recipients.Count());
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task UserAddPair(UserDto dto)
|
||||
{
|
||||
@@ -214,6 +82,255 @@ public partial class MareHub
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task UserDelete()
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
var userEntry = await _dbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||
var secondaryUsers = await _dbContext.Auth.Include(u => u.User).Where(u => u.PrimaryUserUID == UserUID).Select(c => c.User).ToListAsync().ConfigureAwait(false);
|
||||
foreach (var user in secondaryUsers)
|
||||
{
|
||||
await DeleteUser(user).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await DeleteUser(userEntry).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task<List<OnlineUserIdentDto>> UserGetOnlinePairs()
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
var pairs = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false);
|
||||
|
||||
return pairs.Select(p => new OnlineUserIdentDto(new UserData(p.Key), p.Value)).ToList();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task<List<UserPairDto>> UserGetPairedClients()
|
||||
{
|
||||
_logger.LogCallInfo();
|
||||
|
||||
var query =
|
||||
from userToOther in _dbContext.ClientPairs
|
||||
join otherToUser in _dbContext.ClientPairs
|
||||
on new
|
||||
{
|
||||
user = userToOther.UserUID,
|
||||
other = userToOther.OtherUserUID,
|
||||
} equals new
|
||||
{
|
||||
user = otherToUser.OtherUserUID,
|
||||
other = otherToUser.UserUID,
|
||||
} into leftJoin
|
||||
from otherEntry in leftJoin.DefaultIfEmpty()
|
||||
where
|
||||
userToOther.UserUID == UserUID
|
||||
select new
|
||||
{
|
||||
userToOther.OtherUser.Alias,
|
||||
userToOther.IsPaused,
|
||||
OtherIsPaused = otherEntry != null && otherEntry.IsPaused,
|
||||
userToOther.OtherUserUID,
|
||||
IsSynced = otherEntry != null,
|
||||
DisableOwnAnimations = userToOther.DisableAnimations,
|
||||
DisableOwnSounds = userToOther.DisableSounds,
|
||||
DisableOtherAnimations = otherEntry == null ? false : otherEntry.DisableAnimations,
|
||||
DisableOtherSounds = otherEntry == null ? false : otherEntry.DisableSounds
|
||||
};
|
||||
|
||||
var results = await query.AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
|
||||
return results.Select(c =>
|
||||
{
|
||||
var ownPerm = UserPermissions.Paired;
|
||||
ownPerm.SetPaused(c.IsPaused);
|
||||
ownPerm.SetDisableAnimations(c.DisableOwnAnimations);
|
||||
ownPerm.SetDisableSounds(c.DisableOwnSounds);
|
||||
var otherPerm = UserPermissions.NoneSet;
|
||||
otherPerm.SetPaired(c.IsSynced);
|
||||
otherPerm.SetPaused(c.OtherIsPaused);
|
||||
otherPerm.SetDisableAnimations(c.DisableOtherAnimations);
|
||||
otherPerm.SetDisableSounds(c.DisableOtherSounds);
|
||||
return new UserPairDto(new(c.OtherUserUID, c.Alias), ownPerm, otherPerm);
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task<UserProfileDto> UserGetProfile(UserDto user)
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(user));
|
||||
|
||||
var allUserPairs = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
|
||||
if (!allUserPairs.Contains(user.User.UID, StringComparer.Ordinal) && !string.Equals(user.User.UID, UserUID, StringComparison.Ordinal))
|
||||
{
|
||||
return new UserProfileDto(user.User, false, null, null, "Due to the pause status you cannot access this users profile.");
|
||||
}
|
||||
|
||||
var data = await _dbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == user.User.UID).ConfigureAwait(false);
|
||||
if (data == null) return new UserProfileDto(user.User, false, null, null, null);
|
||||
|
||||
if (data.FlaggedForReport) return new UserProfileDto(user.User, true, null, null, "This profile is flagged for report and pending evaluation");
|
||||
if (data.ProfileDisabled) return new UserProfileDto(user.User, true, null, null, "This profile was permanently disabled");
|
||||
|
||||
return new UserProfileDto(user.User, false, data.IsNSFW, data.Base64ProfileImage, data.UserDescription);
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task UserPushData(UserCharaDataMessageDto dto)
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(dto.CharaData.FileReplacements.Count));
|
||||
|
||||
bool hadInvalidData = false;
|
||||
List<string> invalidGamePaths = new();
|
||||
List<string> invalidFileSwapPaths = new();
|
||||
foreach (var replacement in dto.CharaData.FileReplacements.SelectMany(p => p.Value))
|
||||
{
|
||||
var invalidPaths = replacement.GamePaths.Where(p => !GamePathRegex().IsMatch(p)).ToList();
|
||||
invalidPaths.AddRange(replacement.GamePaths.Where(p => !AllowedExtensionsForGamePaths.Any(e => p.EndsWith(e, StringComparison.OrdinalIgnoreCase))));
|
||||
replacement.GamePaths = replacement.GamePaths.Where(p => !invalidPaths.Contains(p, StringComparer.OrdinalIgnoreCase)).ToArray();
|
||||
bool validGamePaths = replacement.GamePaths.Any();
|
||||
bool validHash = string.IsNullOrEmpty(replacement.Hash) || HashRegex().IsMatch(replacement.Hash);
|
||||
bool validFileSwapPath = string.IsNullOrEmpty(replacement.FileSwapPath) || GamePathRegex().IsMatch(replacement.FileSwapPath);
|
||||
if (!validGamePaths || !validHash || !validFileSwapPath)
|
||||
{
|
||||
_logger.LogCallWarning(MareHubLogger.Args("Invalid Data", "GamePaths", validGamePaths, string.Join(",", invalidPaths), "Hash", validHash, replacement.Hash, "FileSwap", validFileSwapPath, replacement.FileSwapPath));
|
||||
hadInvalidData = true;
|
||||
if (!validFileSwapPath) invalidFileSwapPaths.Add(replacement.FileSwapPath);
|
||||
if (!validGamePaths) invalidGamePaths.AddRange(replacement.GamePaths);
|
||||
if (!validHash) invalidFileSwapPaths.Add(replacement.Hash);
|
||||
}
|
||||
}
|
||||
|
||||
if (hadInvalidData)
|
||||
{
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "One or more of your supplied mods were rejected from the server. Consult /xllog for more information.").ConfigureAwait(false);
|
||||
throw new HubException("Invalid data provided, contact the appropriate mod creator to resolve those issues"
|
||||
+ Environment.NewLine
|
||||
+ string.Join(Environment.NewLine, invalidGamePaths.Select(p => "Invalid Game Path: " + p))
|
||||
+ Environment.NewLine
|
||||
+ string.Join(Environment.NewLine, invalidFileSwapPaths.Select(p => "Invalid FileSwap Path: " + p)));
|
||||
}
|
||||
|
||||
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
var idents = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false);
|
||||
|
||||
var recipients = allPairedUsers.Where(f => dto.Recipients.Select(r => r.UID).Contains(f, StringComparer.Ordinal)).ToList();
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(idents.Count, recipients.Count()));
|
||||
|
||||
await Clients.Users(recipients).Client_UserReceiveCharacterData(new OnlineUserCharaDataDto(new UserData(UserUID), dto.CharaData)).ConfigureAwait(false);
|
||||
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushData);
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, recipients.Count());
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task UserRemovePair(UserDto dto)
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(dto));
|
||||
|
||||
if (string.Equals(dto.User.UID, UserUID, StringComparison.Ordinal)) return;
|
||||
|
||||
// check if client pair even exists
|
||||
ClientPair callerPair =
|
||||
await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == UserUID && w.OtherUserUID == dto.User.UID).ConfigureAwait(false);
|
||||
if (callerPair == null) return;
|
||||
|
||||
bool callerHadPaused = callerPair.IsPaused;
|
||||
|
||||
// delete from database, send update info to users pair list
|
||||
_dbContext.ClientPairs.Remove(callerPair);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(dto, "Success"));
|
||||
|
||||
await Clients.User(UserUID).Client_UserRemoveClientPair(dto).ConfigureAwait(false);
|
||||
|
||||
// check if opposite entry exists
|
||||
var oppositeClientPair = OppositeEntry(dto.User.UID);
|
||||
if (oppositeClientPair == null) return;
|
||||
|
||||
// check if other user is online, if no then there is no need to do anything further
|
||||
var otherIdent = await GetUserIdent(dto.User.UID).ConfigureAwait(false);
|
||||
if (otherIdent == null) return;
|
||||
|
||||
// get own ident and
|
||||
await Clients.User(dto.User.UID)
|
||||
.Client_UserUpdateOtherPairPermissions(new UserPermissionsDto(new UserData(UserUID),
|
||||
UserPermissions.NoneSet)).ConfigureAwait(false);
|
||||
|
||||
// if the other user had paused the user the state will be offline for either, do nothing
|
||||
bool otherHadPaused = oppositeClientPair.IsPaused;
|
||||
if (!callerHadPaused && otherHadPaused) return;
|
||||
|
||||
var allUsers = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||
var pauseEntry = allUsers.SingleOrDefault(f => string.Equals(f.UID, dto.User.UID, StringComparison.Ordinal));
|
||||
var isPausedInGroup = pauseEntry == null || pauseEntry.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection;
|
||||
|
||||
// if neither user had paused each other and both are in unpaused groups, state will be online for both, do nothing
|
||||
if (!callerHadPaused && !otherHadPaused && !isPausedInGroup) return;
|
||||
|
||||
// if neither user had paused each other and either is not in an unpaused group with each other, change state to offline
|
||||
if (!callerHadPaused && !otherHadPaused && isPausedInGroup)
|
||||
{
|
||||
await Clients.User(UserUID).Client_UserSendOffline(dto).ConfigureAwait(false);
|
||||
await Clients.User(dto.User.UID).Client_UserSendOffline(new(new(UserUID))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// if the caller had paused other but not the other has paused the caller and they are in an unpaused group together, change state to online
|
||||
if (callerHadPaused && !otherHadPaused && !isPausedInGroup)
|
||||
{
|
||||
await Clients.User(UserUID).Client_UserSendOnline(new(dto.User, otherIdent)).ConfigureAwait(false);
|
||||
await Clients.User(dto.User.UID).Client_UserSendOnline(new(new(UserUID), UserCharaIdent)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task UserReportProfile(UserProfileReportDto dto)
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(dto));
|
||||
|
||||
UserProfileDataReport report = await _dbContext.UserProfileReports.SingleOrDefaultAsync(u => u.ReportedUserUID == dto.User.UID && u.ReportingUserUID == UserUID).ConfigureAwait(false);
|
||||
if (report != null)
|
||||
{
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "You already reported this profile and it's pending validation").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
UserProfileData profile = await _dbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == dto.User.UID).ConfigureAwait(false);
|
||||
if (profile == null)
|
||||
{
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "This user has no profile").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
UserProfileDataReport reportToAdd = new()
|
||||
{
|
||||
ReportDate = DateTime.UtcNow,
|
||||
ReportingUserUID = UserUID,
|
||||
ReportReason = dto.ProfileReport,
|
||||
ReportedUserUID = dto.User.UID,
|
||||
};
|
||||
|
||||
profile.FlaggedForReport = true;
|
||||
|
||||
await _dbContext.UserProfileReports.AddAsync(reportToAdd).ConfigureAwait(false);
|
||||
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
await Clients.User(dto.User.UID).Client_ReceiveServerMessage(MessageSeverity.Warning, "Your Mare profile has been reported and disabled for admin validation").ConfigureAwait(false);
|
||||
|
||||
var allPairedUsers = await GetAllPairedUnpausedUsers(dto.User.UID).ConfigureAwait(false);
|
||||
var pairs = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false);
|
||||
|
||||
await Clients.Users(pairs.Select(p => p.Key)).Client_UserUpdateProfile(new(dto.User)).ConfigureAwait(false);
|
||||
await Clients.Users(dto.User.UID).Client_UserUpdateProfile(new(dto.User)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task UserSetPairPermissions(UserPermissionsDto dto)
|
||||
{
|
||||
@@ -262,67 +379,94 @@ public partial class MareHub
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Identified")]
|
||||
public async Task UserRemovePair(UserDto dto)
|
||||
public async Task UserSetProfile(UserProfileDto dto)
|
||||
{
|
||||
_logger.LogCallInfo(MareHubLogger.Args(dto));
|
||||
|
||||
if (string.Equals(dto.User.UID, UserUID, StringComparison.Ordinal)) return;
|
||||
if (!string.Equals(dto.User.UID, UserUID, StringComparison.Ordinal)) throw new HubException("Cannot modify profile data for anyone but yourself");
|
||||
|
||||
// check if client pair even exists
|
||||
ClientPair callerPair =
|
||||
await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == UserUID && w.OtherUserUID == dto.User.UID).ConfigureAwait(false);
|
||||
if (callerPair == null) return;
|
||||
var existingData = await _dbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == dto.User.UID).ConfigureAwait(false);
|
||||
|
||||
bool callerHadPaused = callerPair.IsPaused;
|
||||
if (existingData.FlaggedForReport)
|
||||
{
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile is currently flagged for report and cannot be edited").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingData.ProfileDisabled)
|
||||
{
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile was permanently disabled and cannot be edited").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(dto.ProfilePictureBase64))
|
||||
{
|
||||
byte[] imageData = Convert.FromBase64String(dto.ProfilePictureBase64);
|
||||
using MemoryStream ms = new(imageData);
|
||||
var format = await Image.DetectFormatAsync(ms).ConfigureAwait(false);
|
||||
if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is not in PNG format").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
using var image = Image.Load<Rgba32>(imageData);
|
||||
|
||||
if (image.Width > 256 || image.Height > 256 || (imageData.Length > 250 * 1024))
|
||||
{
|
||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is larger than 256x256 or more than 250KiB.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingData != null)
|
||||
{
|
||||
if (string.Equals("", dto.ProfilePictureBase64, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
existingData.Base64ProfileImage = null;
|
||||
}
|
||||
else if (dto.ProfilePictureBase64 != null)
|
||||
{
|
||||
existingData.Base64ProfileImage = dto.ProfilePictureBase64;
|
||||
}
|
||||
|
||||
if (dto.IsNSFW != null)
|
||||
{
|
||||
existingData.IsNSFW = dto.IsNSFW.Value;
|
||||
}
|
||||
|
||||
if (dto.Description != null)
|
||||
{
|
||||
existingData.UserDescription = dto.Description;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UserProfileData userProfileData = new()
|
||||
{
|
||||
UserUID = dto.User.UID,
|
||||
Base64ProfileImage = dto.ProfilePictureBase64 ?? null,
|
||||
UserDescription = dto.Description ?? null,
|
||||
IsNSFW = dto.IsNSFW ?? false
|
||||
};
|
||||
|
||||
await _dbContext.UserProfileData.AddAsync(userProfileData).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// delete from database, send update info to users pair list
|
||||
_dbContext.ClientPairs.Remove(callerPair);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(MareHubLogger.Args(dto, "Success"));
|
||||
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
var pairs = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false);
|
||||
|
||||
await Clients.User(UserUID).Client_UserRemoveClientPair(dto).ConfigureAwait(false);
|
||||
|
||||
// check if opposite entry exists
|
||||
var oppositeClientPair = OppositeEntry(dto.User.UID);
|
||||
if (oppositeClientPair == null) return;
|
||||
|
||||
// check if other user is online, if no then there is no need to do anything further
|
||||
var otherIdent = await GetUserIdent(dto.User.UID).ConfigureAwait(false);
|
||||
if (otherIdent == null) return;
|
||||
|
||||
// get own ident and
|
||||
await Clients.User(dto.User.UID)
|
||||
.Client_UserUpdateOtherPairPermissions(new UserPermissionsDto(new UserData(UserUID),
|
||||
UserPermissions.NoneSet)).ConfigureAwait(false);
|
||||
|
||||
|
||||
// if the other user had paused the user the state will be offline for either, do nothing
|
||||
bool otherHadPaused = oppositeClientPair.IsPaused;
|
||||
if (!callerHadPaused && otherHadPaused) return;
|
||||
|
||||
var allUsers = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||
var pauseEntry = allUsers.SingleOrDefault(f => string.Equals(f.UID, dto.User.UID, StringComparison.Ordinal));
|
||||
var isPausedInGroup = pauseEntry == null || pauseEntry.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection;
|
||||
|
||||
// if neither user had paused each other and both are in unpaused groups, state will be online for both, do nothing
|
||||
if (!callerHadPaused && !otherHadPaused && !isPausedInGroup) return;
|
||||
|
||||
// if neither user had paused each other and either is not in an unpaused group with each other, change state to offline
|
||||
if (!callerHadPaused && !otherHadPaused && isPausedInGroup)
|
||||
{
|
||||
await Clients.User(UserUID).Client_UserSendOffline(dto).ConfigureAwait(false);
|
||||
await Clients.User(dto.User.UID).Client_UserSendOffline(new(new(UserUID))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// if the caller had paused other but not the other has paused the caller and they are in an unpaused group together, change state to online
|
||||
if (callerHadPaused && !otherHadPaused && !isPausedInGroup)
|
||||
{
|
||||
await Clients.User(UserUID).Client_UserSendOnline(new(dto.User, otherIdent)).ConfigureAwait(false);
|
||||
await Clients.User(dto.User.UID).Client_UserSendOnline(new(new(UserUID), UserCharaIdent)).ConfigureAwait(false);
|
||||
}
|
||||
await Clients.Users(pairs.Select(p => p.Key)).Client_UserUpdateProfile(new(dto.User)).ConfigureAwait(false);
|
||||
await Clients.Caller.Client_UserUpdateProfile(new(dto.User)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^([a-z0-9_ '+&,\.\-\{\}]+\/)+([a-z0-9_ '+&,\.\-\{\}]+\.[a-z]{3,4})$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ECMAScript)]
|
||||
private static partial Regex GamePathRegex();
|
||||
|
||||
[GeneratedRegex(@"^[A-Z0-9]{40}$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ECMAScript)]
|
||||
private static partial Regex HashRegex();
|
||||
|
||||
private ClientPair OppositeEntry(string otherUID) =>
|
||||
_dbContext.ClientPairs.AsNoTracking().SingleOrDefault(w => w.User.UID == otherUID && w.OtherUser.UID == UserUID);
|
||||
}
|
||||
}
|
||||
@@ -28,10 +28,9 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="7.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="7.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.27.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user